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


인터넷을 검색하면 사업자는 조직 계정이라는 글이 많았고, 사업자등록증도 있으니 그대로 따랐다.
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자가 확장할 수 없는 클래스라면 가능한 한 직렬화 프록시 패턴을 사용하자

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

ITEM 89 인스턴스 수를 통제해야 한다면 readResolve 보다는 열거 타입을 사용하라


바깥에서 생성자를 호출하지 못하게 막는 방식으로 싱글턴을 보장한 코드

public class Elvis {
  public static final Elvis INSTANCE = new Elvis();
  private Elvis() { .. }

  public void leaveTheBuilding() { .. }
}

위 코드는 클래스 선언에 implements Serializable을 추가하면 더이상 싱글턴이 아니게 된다

기본 직렬화를 쓰지 않더라도, 명시적인 readObject를 제공하더라도 소용 없다

어떤 readObject를 사용하든 이클르새가 초기화될 때 만들어진 인스턴스와는 별개인 인스턴스를 반환하게 된다

readResolve 기능을 이용하면 readObject가 만들어낸 인스턴스를 다른 것으로 대체 가능하다

역직렬화한 객체의 클래스가 readResolve 메서드를 적절히 정의해뒀다면 역직렬화 후 새로 생성된 객체를 인수로 이 메서드가 호출되며
이 메서드가 반환한 객체 참조가 새로 생성된 객체를 대신해 반환된다
대부분의 경우 이때 새로 생성된 객체의 참조는 유지하지 않으므로 바로 가비지 컬렉션 대상이 된다

위 Elvis 클래스가 Serializable을 구현한다면 아래의 readReslove 메서드를 추가해 싱글턴을 유지 가능하다

//인스턴스 통제를 위한 readResolve - 개선 가능
private Object readResolve() {
  //진짜 Elvis를 반환하고 가짜 Elvis는 GC 에 맡긴다
  return INSTANCE; 
}

이 메서드는 역직렬화한 객체는 무시하며 클래스 초기화시 만들어진 Elvis 인스턴스를 반환하며 Elvis 인스턴스의 직렬화 형태는 아무런 실 데이터를 가질 이유가 없으니 모든 인스턴스 필드를 transient로 선언해야 한다

readResolve를 인스턴스 통제 목적으로 사용한다면 객체 참조 타입 인스턴스 필드는 모두 transient로 선언해야 한다

  • 이렇게 안하면 MutablePeriod 공격과 비슷한 방식으로 readResolve 메서드가 수행되기 전에 역직렬화된 객체의 참조를 공격할 여지가 남는다

인스턴스 통제를 위해 readResolve를 사용하는 방식이 완전히 쓸모없는 것은 아니다

  • 직렬화 가능 인스턴스 통제 클래스를 작성해야 할때 컴파일 타임에는 어떤 인스턴스들이 있는지 알 수 없는 상황이라면 열거 타입으로 표현하는 것이 불가능하기 때문

readResolve 메서드의 접근성은 매우 중요하다

  • final 클래스에서라면 readResolve 메서드는 private 이어야 한다
  • final이 아닌 클래스에서는 다음의 몇 가지를 주의해서 고려해야 한다
    • private으로 선언하면 하위 클래스에서 사용할 수 없다
    • package-private으로 선언하면 같은 패키지에 속한 하위 클래스에서만 사용할 수 있다
    • protected나 public으로 선언하면 이를 재정의하지 않은 모든 하위 클래스에서 사용할 수 있다
    • protected나 public이면서 하위 클래스에서 재정의하지 않았다면 하위 클래스의 인스턴스를 역직렬화하면 상위 클래스의 인스턴스를 생성하여 ClassCastException을 일으킬 수 있다

불변식을 지키기 위해 인스턴스를 통제해야 한다면 간으한 한 열거 타입을 사용하자

여의치 않은 상황에서 직렬화와 인스턴스 통제가 모두 필요하다면 readResolve 메서드를 작성해 넣어야 한다

  • 그 클래스에서 모든 참조 타입 인스턴스 필드를 transient로 선언해야 한다

+ Recent posts