우분투 24.04 VM 3대(master 1 + worker 2)에 kubeadm으로 쿠버네티스 v1.34 클러스터를 세웠다.
CKA 준비하면서 kubeadm은 여러 번 해봤는데, 이번엔 가이드가 한 줄로 넘어가는 지점을 왜 그렇게 해야 하는지까지 따라가 봤다.
막힌 곳은 세 군데였다.

1. sysctl: 공식 문서는 한 줄, 가이드는 세 줄

공식 문서(Container Runtimes)는 net.ipv4.ip_forward = 1 한 줄만 최소 필수로 둔다.
서드파티 가이드 대부분은 여기에 bridge-nf-call-iptables, bridge-nf-call-ip6tables 두 줄을 더 넣으라고 한다.
차이의 이유는 최신 CNI가 br_netfilter와 bridge sysctl을 스스로 처리하는 경우가 많아져서다.
나는 세 줄 다 넣었다. 켜져 있어서 손해 볼 게 없고, CNI가 안 챙기는 경우에도 안전하다.
bridge-nf-call-iptables가 꺼져 있으면 Service 라우팅과 네트워크 정책이 에러 없이 조용히 어긋난다.

2. containerd의 SystemdCgroup = true

우분투에서 cgroup의 주인은 systemd다.
kubelet과 컨테이너 런타임은 같은 cgroup 드라이버를 써야 하므로 containerd도 systemd로 맞춘다.
설정 파일이 없으면 containerd config default로 만들고, runc options 아래 SystemdCgroup을 true로 바꾼 뒤 재시작.
안 맞추면 파드가 안 뜨거나 노드가 불안정해지는데, 에러 메시지가 불친절해서 원인 찾기가 오래 걸린다.

3. kubeadm init 성공 후 NotReady

노드끼리 핑은 되는데 NotReady였다.
원인은 노드 네트워크가 아니라 파드 네트워크가 아직 없어서다. init 직후엔 CNI가 없으니 정상 상태다.
Flannel을 깔았고, init 때 준 --pod-network-cidr=10.244.0.0/16은 Flannel 기본 대역에 맞춘 값이다.
이 둘이 어긋나면 파드 네트워크가 제대로 안 뜬다.
Flannel과 CoreDNS가 Running으로 올라오면 노드가 Ready로 바뀌고, worker join 뒤 3노드가 전부 Ready가 됐다.

세 곳은 따로 노는 게 아니라 커널(sysctl) → 런타임 cgroup(containerd) → 파드 네트워크(CNI) 순으로 이어져 있었다.
각 단계 명령어와 확인 방법, 공식 문서 근거는 원문에 정리해 뒀다.

👉 kubeadm 클러스터 구축 중 막히는 세 곳: sysctl, containerd SystemdCgroup, NotReady(CNI)
https://progreneur.com/blog/kubeadm-cluster-sysctl-cgroup-notready

'개발 > k8s' 카테고리의 다른 글

[CKA] Mock 2  (0) 2024.03.06

개인사업자라서 구글 플레이 콘솔을 조직 계정으로 만들었다.


인터넷을 검색하면 사업자는 조직 계정이라는 글이 많았고, 사업자등록증도 있으니 그대로 따랐다.
DUNS 번호까지 발급받아 계정을 만들었고, 앱 등록과 게시까지는 아무 경고가 없었다.

문제는 앱에서 실제 결제가 발생하면서 시작됐다.
구글이 본인인증을 다시 요구했고, 결제 프로필이 맞지 않아 그 인증을 통과하지 못했다.
문의를 넣고서야 답을 들었다. 개인사업자는 개인 계정을 새로 만들어 앱을 이전하라는 것이었다.

여기서 배운 게 두 가지다.

- DUNS 번호를 받을 수 있느냐는 판단 기준이 못 된다. 나는 받고도 막혔다
- 한국의 개인사업자는 법적으로 개인이라, 결제 주체가 자연인인지 법인인지가 실질 기준이다

사업장 주소가 가상오피스인 경우 주소 인증에서 한 번 더 걸린다.
전대차 계약서는 인증 수단으로 인정되지 않았고, 통과한 방법은 따로 있었다.

계정 유형 선택부터 주소 인증 통과까지, 겪은 순서대로 원문에 적어 뒀다.
👉 구글 플레이 콘솔 개인사업자 계정: 조직 계정 함정과 가상오피스 주소 인증
https://progreneur.com/blog/google-play-console-personal-account-virtual-office

1. Take a backup of the etcd cluster and save it to /opt/etcd-backup.db 

 

etcd 클러스터를 backup 후 /opt/etcd-backup.db 해당 경로의 해당 파일명으로 저장

 

Docs keyword : etcd backup

https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#backing-up-an-etcd-cluster

 

# help
ETCDCTL_API=3 etcdctl -h 

# spanshot 생성 예시(옵션 사용)
ETCDCTL_API=3 etcdctl  \
--endpoints=https://127.0.0.1:2379 \
--cacert=<trusted-ca-file> \
--cert=<cert-file> \ 
--key=<key-file> \
  snapshot save <backup-file-location>
# endpoint 확인
cat /etc/kubernetes/manifests/etcd.yaml | grep listen

# 인증서 확인
cat /etc/kubernetes/manifests/etcd.yaml | grep file
# spanshot 스크립트 완성
ETCDCTL_API=3 etcdctl  \
--endpoints=https://127.0.0.1:2379    \
--cacert=/etc/kubernetes/pki/etcd/ca.crt  \
--cert=/etc/kubernetes/pki/etcd/server.crt  \
--key=/etc/kubernetes/pki/etcd/server.key    \
snapshot save /opt/etcd-backup.db

 

 

 

2. Create a Pod called redis-storage with image: redis:alpine with a Volume of type emptyDir that lasts for the life of the Pod

 

redis-storage 라는 이름의 파드 생성 redis:alpine 이라는 이미지 ,emptyDir 볼륨 사용

 

  • Pod named 'redis-storage' created
  • Pod 'redis-storage' uses Volume type of emptyDir
  • Pod 'redis-storage' uses volumeMount with mountPath = /data/redis

Docs keyword : emptyDir

https://kubernetes.io/docs/concepts/storage/volumes/#emptydir

 

# redis-storage.yaml 생성(--dry-run=client 을 통해 화면 출력, 해당 내용 yaml 파일 생성) 
k run redis-storage --image=redis:alpine --dry-run=client -o yaml > redis-storage.yaml
 
# redis-storage.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: redis-storage
  name: redis-storage
spec:
  containers:
  - image: redis:alpine
    name: redis-storage
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
# emptyDir 설정 샘플
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi


# redis-storage.yaml - 실제 사용할 pod 에 emptyDir 관련 설정 추가 및 mountPath 변경(/data/redis)
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: redis-storage
  name: redis-storage
spec:
  containers:
  - image: redis:alpine
    name: redis-storage
    volumeMounts:
      - mountPath: /data/redis
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

 

# yaml 로 자원 생성
k create -f redis-storage.yaml

 

3. Create a new pod called super-user-pod with image busybox:1.28. Allow the pod to be able to set system_time

The container should sleep for 4800 seconds.

 

 

busybox:1.28 이미지를 사용해서 super-user-pod 라는 pod 를 생성하고 pod 가 system_time 을 설정할 수 있도록 허용

컨테이너 sleep 설정 4800 초

 

  • Pod: super-user-pod
  • Container Image: busybox:1.28
  • Is SYS_TIME capability set for the container?

Docs keyword : Set capabilities for a Container

https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container

 

# super-user-pod.yaml 생성
k run super-user-pod --image=busybox:1.28 --dry-run=client -o yaml > super-user-pod.yaml
# securityContext 설정 샘플
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-4
spec:
  containers:
  - name: sec-ctx-4
    image: gcr.io/google-samples/node-hello:1.0
    securityContext:
      capabilities:
        add: ["NET_ADMIN", "SYS_TIME"]
# super-user-pod.yaml securityContext 적용
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: super-user-pod
  name: super-user-pod
spec:
  containers:
  - image: busybox:1.28
    securityContext:
      capabilities:
        add: ["SYS_TIME"]
    name: super-user-pod
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
# super-user-pod.yaml sleep 적용
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: super-user-pod
  name: super-user-pod
spec:
  containers:
  - image: busybox:1.28
    securityContext:
      capabilities:
        add: ["SYS_TIME"]
    command: ["sleep","4800"]
    name: super-user-pod
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
# yaml 실행 (설정된 값으로 Pod 생성)
k create -f super-user-pod.yaml

 

 

4. A pod definition file is created at /root/CKA/use-pv.yaml. Make use of this manifest file and mount the persistent volume called pv-1. Ensure the pod is running and the PV is bound.

 
/root/CKA/use-pv.yaml 해당 파일을 사용해서 pod 를 생성하고 pv-1 이라는 pvc 를 연결 후 확인
 

mountPath: /data
persistentVolumeClaim Name: my-pvc

  • persistentVolume Claim configured correctly
  • pod using the correct mountPath
  • pod using the persistent volume claim?

Docs keyword : pv, pvc

https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistent-volumes

https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims

 

# pv 확인 시 pv-1 존재 확인 가능
k get pv

# pv-1 상세 정보
Name:            pv-1
Labels:          <none>
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    
Status:          Available
Claim:           
Reclaim Policy:  Retain
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        10Mi
Node Affinity:   <none>
Message:         
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /opt/data
    HostPathType:  
Events:            <none>
# pvc 설정 샘플
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 8Gi
# pvc.yaml 생성 (storage 값을 pv 와 동일하게 설정)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 10Mi
# pvc 생성
k create -f pvc.yaml
# pvc 생성 확인
k get pvc
# use-pv.yaml 문제에서 제공되는 샘플 yaml (pvc 연결 필요)
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: use-pv
  name: use-pv
spec:
  containers:
  - image: nginx
    name: use-pv
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
# docs pv -> Claims As Volumes 샘플 코드
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim
# use-pv.yaml pvc 연결
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: use-pv
  name: use-pv
spec:
  containers:
  - image: nginx
    name: use-pv
    volumeMounts:
    - mountPath: "/data" # moutPath 지정
      name: mypd
  volumes:                     
    - name: mypd
      persistentVolumeClaim:   # pvc 추가
       claimName: my-pvc       # 생성한 pvc 설정
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
# pod 생성
k create -f use-pv.yaml
# pod 확인
k get pods
# pod 상세 확인
k describe pod use-pv

 

5. Create a new deployment called nginx-deploy, with image nginx:1.16 and 1 replica. Next upgrade the deployment to version 1.17 using rolling update.

 
nginx:1.16 이미지 사용 deployment 를 replica 1개로 지정 후 생성 rolling update 사용 deployment 버전 1.17 로 변경

 

 

  • Deployment : nginx-deploy. Image: nginx:1.16
  • Image: nginx:1.16
  • Task: Upgrade the version of the deployment to 1:17
  • Task: Record the changes for the image upgrade

Docs keyword : deploy upgrade

https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment

# deployment 생성
k create deployment nginx-deploy --image=nginx:1.16 --replicas=1

# update > Updating a Deployment 참고
kubectl set image deployment/nginx-deploy nginx=nginx:1.17

# 버전 변경 확인
k describe deploy nginx-deploy | grep -i image
 
 
 

6. Create a new user called john. Grant him access to the cluster. John should have permission to create, list, get, update and delete pods in the development namespace . The private key exists in the location: /root/CKA/john.key and csr at /root/CKA/john.csr

 

유저 john 생성, 생성한 유저는 클러스터에 접근이 가능해야함, development namespace 에서 create, list, get, update, delete pod 를 할 수 있는 권한이 있어야한다

 

 

Important Note: As of kubernetes 1.19, the CertificateSigningRequest object expects a signerName.
Please refer the documentation to see an example. The documentation tab is available at the top right of terminal.

  • CSR: john-developer Status:Approved
  • Role Name: developer, namespace: development, Resource: Pods
  • Access: User 'john' has appropriate permissions

Docs keyword : Create a CertificateSigningRequest

https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#certificate-signing-requests

 

1. CSR (CertificateSigningRequest) 생성

# 1.csr 생성시 spec.request 에 들어갈 값 조회
cat john.csr | base64 | tr -d "\n"

# 1-1. cat에 대한 결과 (spec.request 에 들어감)
# LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZEQ0NBVHdDQVFBd0R6RU5NQXNHQTFVRUF3d0VhbTlvYmpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRApnZ0VQQURDQ0FRb0NnZ0VCQUpzb21RdHI4eERsUVZNaHFWbkxFVTZrdU5CQ1VKYk42V3ZoODhRVjZrSTI1SlFzCmNPT0VCcEZiYVV5OFJUb3dhY1A1cmJPUUpHZmV1RHQ3M0laUFdTOTJlYUhkQkEyTWI0YjVpL2tWKy8vbzRlczMKOW1pM2s5Q3I1NG1NYWF6OUN5NGo4d2NDVW5yZHF3MkVheU9zb2N1R1hqM0lXUDhtTjBLWnhyN09qTi81YkY4dwp4RkxXcTlDK3crRkRoZzM2bis3WWFVWFdUZ0VkcFpsWStkcmRrZVBUNXQrUmNFZDNBNmc0TWp1ZVhOMjZJVVl3CkdxTU9oRUM1NUxGVmpkYTZ6MlY0QkM0aFJ4NnN4d1h0K25kRlpGNjNCbnRFaFVGSmZwN1lYd1Z6cWxtc3FUU1cKVHlzNVc2amozSDFjNEJyMDNqTWorMnBidDNYS1VzLzg0Um1IaFNzQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQgpDd1VBQTRJQkFRQkowQzE4cFNBODNiamtwZEM4dEtyTTlLWTRsRWErTTN1THNGeTZVbkQxTGNzOXM2ckJxMXZDCkJ5QmZxczhRb3hYVUZqQjcxdVBPY1E1K21Xcjk0R2RkWUtlT3pqdUxrLy9jWGlYUVBFa2U3T3g1c3lkdlYvaGoKcmg0RnVRaUU2NXI5czM1enZySW1xb2lSUjkxMDlDYXRkV3ZiUnRYbUc1aEVGYkwyVyticHJOMzVkUWFjd3VYMQpIWlBvMkQyOThkYmYwODFQNE9xVGROZFB2UGtaWXRxdWtrcmlFb0lub0pBRWpvOWRRaStaK1Zja3ZmbnVOdDFYCnNNdm0vL3FmMEt3cWhHejhueHJ4Q3EwSmVKMThDZnVnVVovTXlwWnVmdEhnVldYckxHaFV6YVdKTFNHbHduMWwKUFF5ZllHeXZkZFpyTHg1dVZvUjZ4czlYeG51dFBJZEEKLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==

# Create a CertificateSigningRequest yaml 생성 샘플
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: myuser
spec:
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZqQ0NBVDRDQVFBd0VURVBNQTBHQTFVRUF3d0dZVzVuWld4aE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRgpBQU9DQVE4QU1JSUJDZ0tDQVFFQTByczhJTHRHdTYxakx2dHhWTTJSVlRWMDNHWlJTWWw0dWluVWo4RElaWjBOCnR2MUZtRVFSd3VoaUZsOFEzcWl0Qm0wMUFSMkNJVXBGd2ZzSjZ4MXF3ckJzVkhZbGlBNVhwRVpZM3ExcGswSDQKM3Z3aGJlK1o2MVNrVHF5SVBYUUwrTWM5T1Nsbm0xb0R2N0NtSkZNMUlMRVI3QTVGZnZKOEdFRjJ6dHBoaUlFMwpub1dtdHNZb3JuT2wzc2lHQ2ZGZzR4Zmd4eW8ybmlneFNVekl1bXNnVm9PM2ttT0x1RVF6cXpkakJ3TFJXbWlECklmMXBMWnoyalVnald4UkhCM1gyWnVVV1d1T09PZnpXM01LaE8ybHEvZi9DdS8wYk83c0x0MCt3U2ZMSU91TFcKcW90blZtRmxMMytqTy82WDNDKzBERHk5aUtwbXJjVDBnWGZLemE1dHJRSURBUUFCb0FBd0RRWUpLb1pJaHZjTgpBUUVMQlFBRGdnRUJBR05WdmVIOGR4ZzNvK21VeVRkbmFjVmQ1N24zSkExdnZEU1JWREkyQTZ1eXN3ZFp1L1BVCkkwZXpZWFV0RVNnSk1IRmQycVVNMjNuNVJsSXJ3R0xuUXFISUh5VStWWHhsdnZsRnpNOVpEWllSTmU3QlJvYXgKQVlEdUI5STZXT3FYbkFvczFqRmxNUG5NbFpqdU5kSGxpT1BjTU1oNndLaTZzZFhpVStHYTJ2RUVLY01jSVUyRgpvU2djUWdMYTk0aEpacGk3ZnNMdm1OQUxoT045UHdNMGM1dVJVejV4T0dGMUtCbWRSeEgvbUNOS2JKYjFRQm1HCkkwYitEUEdaTktXTU0xMzhIQXdoV0tkNjVoVHdYOWl4V3ZHMkh4TG1WQzg0L1BHT0tWQW9FNkpsYWFHdTlQVmkKdjlOSjVaZlZrcXdCd0hKbzZXdk9xVlA3SVFjZmg3d0drWm89Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
  
  
# 1-2. john-csr.yaml 생성 (CertificateSigningRequest 생성 샘플에서 필요한 부분만 변경)
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: john-developer
spec:
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ1ZEQ0NBVHdDQVFBd0R6RU5NQXNHQTFVRUF3d0VhbTlvYmpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRApnZ0VQQURDQ0FRb0NnZ0VCQUpzb21RdHI4eERsUVZNaHFWbkxFVTZrdU5CQ1VKYk42V3ZoODhRVjZrSTI1SlFzCmNPT0VCcEZiYVV5OFJUb3dhY1A1cmJPUUpHZmV1RHQ3M0laUFdTOTJlYUhkQkEyTWI0YjVpL2tWKy8vbzRlczMKOW1pM2s5Q3I1NG1NYWF6OUN5NGo4d2NDVW5yZHF3MkVheU9zb2N1R1hqM0lXUDhtTjBLWnhyN09qTi81YkY4dwp4RkxXcTlDK3crRkRoZzM2bis3WWFVWFdUZ0VkcFpsWStkcmRrZVBUNXQrUmNFZDNBNmc0TWp1ZVhOMjZJVVl3CkdxTU9oRUM1NUxGVmpkYTZ6MlY0QkM0aFJ4NnN4d1h0K25kRlpGNjNCbnRFaFVGSmZwN1lYd1Z6cWxtc3FUU1cKVHlzNVc2amozSDFjNEJyMDNqTWorMnBidDNYS1VzLzg0Um1IaFNzQ0F3RUFBYUFBTUEwR0NTcUdTSWIzRFFFQgpDd1VBQTRJQkFRQkowQzE4cFNBODNiamtwZEM4dEtyTTlLWTRsRWErTTN1THNGeTZVbkQxTGNzOXM2ckJxMXZDCkJ5QmZxczhRb3hYVUZqQjcxdVBPY1E1K21Xcjk0R2RkWUtlT3pqdUxrLy9jWGlYUVBFa2U3T3g1c3lkdlYvaGoKcmg0RnVRaUU2NXI5czM1enZySW1xb2lSUjkxMDlDYXRkV3ZiUnRYbUc1aEVGYkwyVyticHJOMzVkUWFjd3VYMQpIWlBvMkQyOThkYmYwODFQNE9xVGROZFB2UGtaWXRxdWtrcmlFb0lub0pBRWpvOWRRaStaK1Zja3ZmbnVOdDFYCnNNdm0vL3FmMEt3cWhHejhueHJ4Q3EwSmVKMThDZnVnVVovTXlwWnVmdEhnVldYckxHaFV6YVdKTFNHbHduMWwKUFF5ZllHeXZkZFpyTHg1dVZvUjZ4czlYeG51dFBJZEEKLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400  # one day
  usages:
  - client auth
  
# 1-3. yaml 실행 (설정한 값으로 csr 생성)
k create -f john-csr.yaml

# 1-4. csr 생성 확인 시 CONDITION 이 pending 으로 확인됨 (승인 필요)
k get csr

# 1-5. pending 확인을 위해 승인
k certificate approve john-developer

# 1-6. csr 재 확인 CONDITION 이 Approve 로 변경된걸 확인 가능
k get csr

 

2. Role 생성 및 RoleBinding

# 1. role 생성 (create, get, list, update, delete 에 대한 권한 부여 및 네임스페이스=development 지정)
# k create role --help 참고
k create role developer --verb=create,get,list,update,delete --resource=pods -n development

# 2. role 생성 확인
k describe role -n development developer

# 3. Access 권한 확인 (현재는 rolebinding 을 하지 않았기 때문에 두 명령어 모두 no 출력)
# k auth can-i get --help
k auth can-i create pods -n development --as john
k auth can-i get pods -n development --as john

# 4. rolebinding 생성
# k create rolebinding --help
kubectl create rolebinding john-developer --role=developer --user=john -n development

# 5. rolebinding 생성 확인 (john 과 role 이 묶인걸 확인 가능)
k describe rolebinding -n development

# 6. Access 권한 확인 (이제 yes 로 나오면 끝)
k auth can-i create pods -n development --as john
k auth can-i get pods -n development --as john

 

 

7. Create a nginx pod called nginx-resolver using image nginx, expose it internally with a service called nginx-resolver-service. Test that you are able to look up the service and pod names from within the cluster.

Use the image: busybox:1.28 for dns lookup. Record results in /root/CKA/nginx.svc and /root/CKA/nginx.pod

 
nginx 라는 이미지를 사용해서 nginx-resolver pod 를 생성하고, nginx-resolver-service 를 통해서 내부적으로 노출 후 클러스터 내부에서 테스트
테스트시 busybox:1.28 이미지를 사용하고 해당 경로에 결과를 기록 /root/CKA/nginx.svc, /root/CKA/nginx.pod

 

 
  • Pod: nginx-resolver created
  • Service DNS Resolution recorded correctly
  • Pod DNS resolution recorded correctly
 

Docs keyword : DNS

https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/

 

 

# 1. pod 생성
k run nginx-resolver --image=nginx

# 1-1. pod 확인
k get pods

# 2. expose 서비스 연결
k expose pod nginx-resolver --name nginx-resolver-service --port=80

# 2-1. 서비스 확인
k get svc

# 3. 통신 확인을 위한 pod 생성
k run busybox --image=busybox:1.28 -- sleep 3600

# 4. svc nslookup 결과 저장
k exec busybox -- nslookup nginx-resolver-service > /root/CKA/nginx.svc

# 4-1. nginx-resolver pod nslookup 을 위한 ip 체크
k get pods -o wide
NAME             READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE   READINESS GATES
busybox          1/1     Running   0          4m13s   10.244.192.2   node01   <none>           <none>
nginx-resolver   1/1     Running   0          33m     10.244.192.1   node01   <none>           <none>

# 4-2. pod nslookup 결과 저장 (nslookup 이후 문법 Pods - A/AAAA records 참고)
k exec busybox -- nslookup 10-244-192-1.default.pod.cluster.local > /root/CKA/nginx.pod

# 5. 결과 확인
cat /root/CKA/nginx.svc
cat /root/CKA/nginx.pod

 

 

8. Create a static pod on node01 called nginx-critical with image nginx and make sure that it is recreated/restarted automatically in case of a failure.

Use /etc/kubernetes/manifests as the Static Pod path for example.

 
node01 에서 nginx 이미지로 nginx-critical static pod 를 생성하고 오류가 발생하면 자동으로 재생성/실행 되는지 확인
 
  • static pod configured under /etc/kubernetes/manifests ?
  • Pod nginx-critical-node01 is up and running

 

Docs keyword : create static pod

https://kubernetes.io/docs/tasks/configure-pod-container/static-pod/#static-pod-creation

 

 

# 1. restart 를 위한 pod yaml 생성 (controleplane 에서 생성 후 복사)
kubectl run nginx-critical --image=nginx --restart=Always --dry-run=client -o yaml

# 2. node01 이동
ssh node01

# 3. static pod 생성 경로 이동
cd /etc/kubernetes/manifests/

# 4. nginx-ciritical static pod 생성을 위해 yaml 작성
vi nginx-critical

# 4-1. dry-run 으로 생성한 yaml 내용 붙여넣고 저장
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx-critical
  name: nginx-critical
spec:
  containers:
  - image: nginx
    name: nginx-critical
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

# 5. pod 생성 확인 (node01 에서 controleplan 으로 복귀 후 확인)
k get pods

 

양방향 연관관계와 연관관계의 주인

양방향 매핑

객체와 테이블이 관계를 맺는 차이

  • 객체 연관관계 = 2개
    • 회원 -> 팀 연관관계 1개(단방향)
    • 팀 -> 회원 연관관계 1개(단방향)
  • 테이블 연관관계 = 1개
    • 회원 <-> 팀의 연관관계 1개(양방향)

객체의 양방향 관계

  • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단뱡향 관계 2개다.
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다

테이블의 양방향 연관관계

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐
    • 양쪽으로 조인 가능

둘 중 하나로 외래 키를 관리해야 한다.

  • 객체의 두 관계중 하나를 연관관계의 주인으로 정해서 관리를 해야한다
    • Member 에서 값을 바꿀 수도 있고 Team 의 members 에서도 Member 의 값을 바꿀 수 있으니 한쪽을 주인으로 정해야 한다는 것
    • 이 개념이 연관관계의 주인이다

연관관계의 주인(Owner)

  • 양방향 매핑 규칙
    • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
    • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
    • 주인이 아닌쪽은 읽기만 가능하게 설정한다
    • 주인은 mappedBy 속성 사용하지 않는다
    • 주인이 아니면 mappedBy 속성으로 주인 지정
    • 외래 키가 있는 있는 곳을 주인으로 정해라

  • 위 그림에서는 Member.team 이 연관관계의 주인이다

양방향 매핑시 가장 많이 하는 실수

  • 연관관계의 주인에 값을 입력하지 않음
TestMember member = new TestMember();
member.setName("member1");
em.persist(member);

Team team = new Team();
team.setName("TeamA");
team.getMembers().add(member);
em.persist(team);

tx.commit();

  • 위와 같이 연관관계의 주인에 값을 입력하지 않고 사용측에서 값을 입력하면 team_id 가 들어가지 않게 된다

양방향 매핑시 연관관계의 주인에 값을 입력해야 한다

  • 객체지향적으로 생각을 해보면 양쪽으로 값을 모두 셋팅해주는게 맞다
    • 양쪽에 값을 모두 셋팅하자
Team team = new Team();
team.setName("TeamA");
em.persist(team);

TestMember member = new TestMember();
member.setName("member1");

// 연관관계의 주인에 값을 설정한다
member.setTeam(team);
team.getMembers().add(member); // 실수할 가능성이 높음
em.persist(member);

// 위와 같이 셋팅하면 실수할 가능성이 높기 때문에 아래처럼 사용
// 연관관계 편의 메서드 : TestMember 에 team 이 설정되는 경우 team 에서 자동으로 Members 에 값을 넣어주게 설정한다
@Entity
@Table(name = "TEST_MEMBER")
public class TestMember {
  ....
  public void setTeam(Team team) {
      this.team = team;
      team.getMembers().add(this);
  }
}

양방향 연관관계 주의사항

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관관계 편의 메소드를 생성하자
    • 연관관계 편의 메서드는 1 , n 측 모두 넣어도 되지만 둘 중 한 곳만 넣자!!
  • 양방향 매핑시에 무한 루프를 조심하자
    • 예: toString(), lombok, JSON 생성 라이브러리
    • lombok 에서 toString 은 사용하지 말자
    • 컨트롤러에서 Entity 는 절대 반환하지 말자
      • JSON 생성 라이브러리 때문에 무한루프 발생 가능
      • Entity 변경시 API 의 스펙도 변경되는 문제가 발생됨
        • DTO 로 변환해서 반환하자

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
    • 처음에는 단방향 매핑으로 설계를 끝내자
      • 처음엔 1:n 에서 n 방향에 연관관계를 다 설정해주자
      • 실제 애플리케이션 개발 단계에서 양방향 매핑이 필요하다고 판단되면 그때 추가하자
      • 양방향 매핑을 설정하면 고려해야할 부분이 많아진다
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많은 경우 추가하자
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨
    • 테이블에 영향을 주지 않음

연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함
    • 여러가지 고민거리가 사라지게 된다

'개발 > Jpa' 카테고리의 다른 글

기본 키 매핑 방법  (0) 2023.08.25
필드와 컬럼 매핑  (0) 2023.08.25
데이터베이스 스키마 자동생성 옵션 (hibernate.hbm2ddl.auto)  (0) 2023.08.25

연관관계 매핑 기초

연관관계가 필요한 이유

  • 객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다

요구사항

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계다
  • 객체를 테이블에 맞춰서 모델링
  • 연관관계가 없는 객체
// 참조 대신에 외래 키를 그대로 사용하는 방식

@Entity
@Table(name = "TEST_MEMBER")
public class TestMember {
  @Id @GeneratedValue
  @Column(name = "test_member_id")
  private Long id;
  @Column(name = "user_name")
  private String name;
  @Column(name = "TEAM_ID")
  private Long teamId;
}

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;
    private String name;
}
// 외래키 식별자를 직접 다루게 된다

Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 영속
TestMember member = new TestMember();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);

// 식별자로 다시 조회하게 되며 객체지향적인 방법이 아니게 된다
// 객체를 테이블에 맞춰 데이터 중심 모델링시 팀을 조회시 객체지향적이지 않은 코드가 발생하게 된다
TestMember findMember = em.find(TestMember.class, member.getId());
Long teamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, teamId);
  • 객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다.
    • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
    • 객체는 참조를 사용해서 연관된 객체를 찾는다.
    • 테이블과 객체 사이에는 이런 큰 간격이 있다.

객체 지향 모델링

  • 객체 연관관계 사용
// 객체의 참조와 테이블의 외래 키를 매핑

@Entity
@Table(name = "TEST_MEMBER")
public class TestMember {

    @Id @GeneratedValue
    @Column(name = "test_member_id")
    private Long id;
    @Column(name = "user_name")
    private String name;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}    
  • ORM 매핑

Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 영속
TestMember member = new TestMember();
member.setName("member1");
// 단방향 연관관계를 설정하고 참조 객체를 저장한다
member.setTeam(team);
em.persist(member);

// 참조로 연관관계를 조회하고 객체 그래프를 탐색해서 불필요한 코드를 줄일 수 있다
TestMember findMember = em.find(TestMember.class, member.getId());
Team findTeam = findMember.getTeam();
System.out.println("findTeam = " + findTeam.getName());

tx.commit();

기본 키 매핑 방법

  • 직접 할당
    • @Id 만 사용 하면 됨
  • 자동 생성(@GeneratedValue)
    • IDENTITY: 데이터베이스에 위임, MYSQL
    • SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE
    • @SequenceGenerator 필요
    • TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용
    • @TableGenerator 필요
    • AUTO: 방언에 따라 자동 지정, 기본값

IDENTITY 전략

  • 기본 키 생성을 데이터베이스에 위임
    • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용 (예: MySQL의 AUTO_ INCREMENT)
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행 한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
    • 실제 트랜잭션 커밋 시점이 아니라 ID 값 조회를 위해 em.persist() 시점에 실제 insert 쿼리가 날아간다
    • 즉 모아서 한번에 INSERT 하는것이 되지 않는다

SEQUENCE 전략

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트(예: 오라클 시퀀스)
  • 시퀀스를 미리 생성 후 트랜잭션 커밋 시점에 INSERT 쿼리가 날아가기 때문에 한번에 모아서 INSERT 하는게 가능하다
  • 다음 시퀀스를 증가시키기 위해 네트워크 호출시 불필요한 자원이 사용되기 때문에 allocationSize 를 사용해서 미리 설정된 값까지 시퀀스를 증가시켜 DB 통신 없이 시퀀스를 증가 시킬 수 있다
    • 이론적으로는 충분히 큰 값으로 증가시키는게 좋긴 하지만 보통 50~100 정도 사용한다 (중간에 비는 시퀀스가 생기는걸 방지)
  • 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용

SEQUENCE - @SequenceGenerator

  • 테이블마다 다른 시퀀스를 주고 싶다면 사용
  • 주의: allocationSize 기본값 = 50

TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
    • 장점: 모든 데이터베이스에 적용 가능
    • 단점: 성능

TABLE - @TableGenerator - 속성

권장하는 식별자 전략

  • 기본 키 제약 조건: null 아님, 유일, 변하면 안된다.
    • 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
    • 예를 들어 주민등록번호도 기본 키로 적절하기 않다.
  • 권장 : 타입은 Long (10억 넘어도 동작해야 되기 때문) + 대체키 (UUID 등..) + 키 생성전략 사용
    • 절대 비지니스를 키로 끌고 오지 말자

필드와 컬럼 매핑

맵핑 어노테이션

  • @Column 컬럼 매핑
  • @Temporal 날짜 타입 매핑
  • @Enumerated enum 타입 매핑
  • @Lob BLOB, CLOB 매핑
  • @Transient 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

@Column

@Enumerated

 

  • 자바 enum 타입을 맵핑하는 경우 사용한다
  • ORDINAL 은 사용하지 말자 (순서를 저장하기 때문에 실제 Enum 코드 변경시 잘못된 데이터를 바라볼 수 있음)

@Temporal

  • 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용
  • 참고: LocalDate, LocalDateTime 사용시 생략 가능

@Lob

  • 데이터베이스 BLOB, CLOB 타입과 매핑
    • @Lob에는 지정할 수 있는 속성이 없다.
    • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB: String, char[], java.sql.CLOB
    • BLOB: byte[], java.sql. BLOB

@Transient

  • 필드 매핑X
  • 데이터베이스에 저장X, 조회X
  • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
  • @Transient
    • private Integer temp;

데이터베이스 스키마 자동생성 옵션 (hibernate.hbm2ddl.auto)

  • create
    • 기존테이블 삭제 후 다시 생성 (DROP + CREATE)
  • create-drop
    • create와 같으나 종료시점에 테이블 DROP
  • update
    • 변경분만 반영
    • 운영 DB에는 사용하면 안됨
  • validate
    • 엔티티와 테이블이 정상 매핑되었는지만 확인
  • none
    • 사용하지 않음

자동생성 주의사항

  • 운영 장비에는 절대 create, create-drop, update 사용하면 안됨
  • 개발 초기 단계는 create 또는 update
  • 테스트 서버는 update 또는 validate
  • 스테이징과 운영 서버는 validate 또는 none

'개발 > Jpa' 카테고리의 다른 글

양방향 연관관계와 연관관계의 주인  (0) 2023.08.25
기본 키 매핑 방법  (0) 2023.08.25
필드와 컬럼 매핑  (0) 2023.08.25

클라우드를 써야 백업, 리스토어, 보안 등이 더 유리하다

  • 스타트업부터 큰 기업까지 다 클라우드 사용한다 요즘엔

 

글로벌 서비스를 하는 기업일수록 클라우드를 사용해야 더 유리하다

(ID 센터를 만들어서 직원 뽑고 운영하기 힘듬)

 

도커 환경에서 개발시 장점

  • 드라이버를 신경 쓰지 않고 윈도우 컨테이너, 리눅스 컨테이너 구분만 해서 올리면 된다
  • 운영자 입장에서는 스케일아웃(서버 증설)과 스케일업에 유리하다
  • 컨테이너를 사용하면 스케일 아웃, 스케일인 모두 유리하다
  • 컨테이너로 개발하고 클라우드 환경에 올려서 관리하는 경우가 많다

 

버츄얼 박스 설치

1) Oracle Virtual Box 프로그램 준비

https://www.virtualbox.org/wiki/Downloads

 

2) CentOS 7  ISO 파일 준비 (최신 것)

http://down.cloudshell.kr/docker/CentOS-7-x86_64-Minimal-1804.iso

 

Centos 7 설치 및 기본 설정

 

리눅스 ip 확인 방법 3가지

  • ip addr show
  • ip a
  • hostname -I

 

포트포워딩 후

 

3) MobaXterm 프로그램 준비

https://mobaxterm.mobatek.net/download-home-edition.html

 

MobaXterm 으로 포트포워딩 한 ip로 ssh 접속 후 아래 작업 실행

 

 

리눅스 패키지 설치

 

yum install epel-release -y

yum install wget -y

yum install net-tools -y

yum install bind-utils -y

 

방화벽 끄기 (-d는 컴퓨터 껏다 키더라도 다시 안켜지게 설정)

systemctl disable firewalld --now

 

 

 

Selinux 기능 끄기 (docker swarm 기능 구현시 docker network가 생성되지 않는 경우가 있어서 미리 꺼둔다)

vi /etc/sysconfig/selinux

 

 

아래처럼 변경

 

 

 yum update -y

 

업데이트 완료 후 shutdown -h now 로 vm 종료

 

 vm 복사

 

 

 

3개까지 만든다

 

설정 - 네트워크 - 어댑터에 브리지로 3개 모두 변경

  • 포트포워딩 할때 편하다??

 

 

 

 

Kcentos1 만 시작 후 접속

- ip 확인

 

Kcentos1 에 Ssh 접속

 

 

 

vi /etc/hostname -> kcentos1 이런식으로 3개 다 변경 후 shutdown -h now

 

 

Kcentos1은 db 서버로 사용할거라 메모리와 프로세스 증설

 

 

 

 

Kcnetos 1~3까지 모두 실행 후 MobaXterm 으로 모두 접속

 

 

 

Kcentos1~3 도커 설치

 

서버용 어플리케이션은 설치 후 자동으로 시작되지 않기 때문에 시작 명령어를 입력 해준다

 

도커 시작(enable 사용하여 컴퓨터 껏다켜도 시작되게 처리)

 systemctl enable docker --now

 

위에 명령어가 아래 두개 합친거

systemctl start docker

systemctl enable docker

 

도커 시작 후

docker version 으로 설치확인

ITEM 90 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라


Serializable을 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스 생성이 가능해진다

  • 버그와 보안 문제가 일어날 가능성이 커진다

직렬화 프록시 패턴을 사용하면 버그와 보안 문제가 줄어든다

  • 적절한 프록시 패턴은 별로 복잡하지 않다
    • 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static으로 선언한다
      • 이 중첩 클래스가 바로 바깥 클래스의 직렬화 프록시다
    • 중첩 클래스의 생성자는 단 하나여야 하며 바깥 클래스를 매개변수로 받아야 한다
    • 이 생성자는 단순히 인수로 넘어온 인스턴스의 데이터를 복사한다
    • 일관성 검사나 방어적 복사도 필요 없다
    • 설계상 직렬화 프록시의 기본 직렬화 형태는 바깥 클래스의 직렬화 형태로 쓰기에 이상적이다
    • 바깥 클래스와 직렬화 프록시 모두 Serializable을 구현한다고 선언해야 한다
Period 클래스용 직렬화 프록시
Period 클래스는 아주 간단해서 직렬화 프록시도 바깥 클래스와 완전히 같은 필드로 구성되었다

public final class Period {
   private final Date start;
   private final Date end;

    public Period(Date start, Date end) {
      this.start = new Date(start.getTime());
      this.end = new Date(end.getTime());

      if(this.start.compareto(this.end) > 0)
        throw new IllegalArgumentException(start + "가 " + end + "보다 늦다.");

    }

    public Date start() { return new Date(start.getTime()); }   
    public Date end() { return new Date(end.getTime()); }
    public String toString() { return  start + " - " + end; }

    private static class SerializationProxy implements Serializable {
      private final Date start;
      private final Date end;

      SerializationProxy(Period p) {
        this.start = p.start;
        this.end = p.end;
      }

      private static final long = serialVersionUID = 12312312314123;

      /**
      * 바깥 클래스와 논리적으로 동일한 인스턴스를 반환하는 readResolve 메서드
      * 역직렬화시 직렬화 시스템이 직렬화 프록시를 다시 바깥 클래스의 인스턴스로 변환하게 해준다
      * readResolve 메서드는 공개된 API만을 사용해 바깥 클래스의 인스턴스를 생성하는데 이 패턴이 아름다운 이유가 바로 이거다
      * 이 패턴은 직렬화의 언어도단적 특성을 상당 부분 제거한다
      * 일반 인스턴스를 만들 때와 똑같은 생성자 정적 팩터리 혹은 다른 메서드를 사용해 역직렬화된 인스턴스를 생성하는 것이다
      * 따라서 역직렬화된 인스턴스가 해당 클래스의 불변식을 만족하는지 검사할 또 다른 수단을 강구하지 않아도 된다
      * 그 클래스의 정적 팩터리나 생성자가 불변식을 확인해주고 인스턴스 메서드들이 불변식을 잘 지켜준다면 따로 더 해줘야 할 게 없다
      */
      // Period.SerializationProxy용 readReslove 메서드
      private Object readReslove() {
        return new Period(start, end);  //public 생성자 사용
      }
    }

  /**
  * 직렬화 프록시 패턴용 (범용적인 메서드라 직렬화 프록시를 사용하는 모든 클래스에 그대로 복사해서 사용가능)
  * 자바의 직렬화 시스템이 바깥 클래스의 인스턴스 대신 SerializationProxy의 인슽너스를 반환하게 하는 역할
  * 직렬화가 이뤄지기 전에 바깥 클래스의 인스턴스를 직렬화 프록시로 변환해준다
  */
  private Object writeReplace() {
    return new SerializationProxy(this);
  }

 writeReplace 덕분에 직렬화 시스템은 결코 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없다
 하지만 공격자는 불변식을 훼손하고자 이런 시도를 해 볼 수 있는데 readObject 메서드를 바깥 클래스에 추가하면 그런 공격도 막을 수 있다

 //직렬화 프록시 패턴용 readObject 메서드
 private void readObject(ObjectInputStream stream) throws InvalidObjectException {
  throw new InvalidObjectException("프록시가 필요합니다");
 }

}

방어적 복사 처럼 직렬화 프록시 패턴은 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단해준다

앞의 두 접근법과 다르게 직렬화 프록시는 Period의 필드를 final로 선언해도 되기 때문에 Period 클래스를 진정한 불변으로 만들 수 있다

또 어떤 필드가 기만적인 직렬화 공격의 목표가 될지 고민할 필요도 없고 역직렬화 때 유효성 검사를 수행하지 않아도 된다

직렬화 프록시 패턴이 readObject에서의 방어적 복사보다 강력한 경우가 하나 더 있다

  • 직렬화 프록시 패턴은 역직렬화한 인스턴스와 원래의 인스턴스의 클래스가 달라도 정상 작동한다

직렬화 프록시 패턴에는 두가지 한계가 있다

  1. 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용이 불가능하다
  2. 객체 그래프에 순환이 있는 클래슷에도 적용할 수 없다
  • 직렬화 프록시만 가졌을 뿐 실제객체는 아직 만들어진 것이 아니기 때문에 직렬화 프록시의 readResolve안에서 호출하려고 하면 ClassCastException이 발생할 것이다

직렬화 프록시는 강력하며 안전하지만 속도는 조금 느려질 수도 있다

제3자가 확장할 수 없는 클래스라면 가능한 한 직렬화 프록시 패턴을 사용하자

이 패턴이 아마도 중요한 불변식을 안정적으로 직렬화해주는 가장 쉬운 방법일 것이다

+ Recent posts