Sprint 1의 Object 그려보며 이해하기 강의

강의를 보면서 이런 이미지를 통해 설명을 해주시는데 하나의 이미지에 정보량이 매우 많아 어지러워졌다.

일단 강의를 진행하며 느낀 점음

네임스페이스를 만들고 다음 만드는 오브젝트들이 여기 소속이 된다?

라벨이 같은걸 자기가 관리하는 파드로 본다?

저장소와 연결한다?

이 정도 밖에 안들어왔다.

 

복사 붙여넣기 했던 YAML파일에 내용도 하나도 모르겠어서 이게 어떻게 동작하는지 하나도 눈에 안들어어 왔다.

 

강의가 끝나고 YAML 파일만이라도 이해해보자 해서 알아봤다.

 

쿠버네티스에서의 오브젝트

쿠버네티스는 사용자가 YAML로 ‘어떻게 파드를 만들고, 배포하고, 관리하고 싶은지’를 선언적으로 작성하면, 그 오브젝트를 기준으로 쿠버네티스가 클러스터 상태를 계속 감시하고 자동으로 조정해주는 시스템이다.

오브젝트만 잘 정의해두면, 원하는 상태를 유지하기 위한 배포·복구·확장·관리 작업은 대부분 쿠버네티스가 자동으로 처리해주기 때문에 운영 부담이 매우 적어짐

이게 IoC의 장점을 그대로 따라가는 것 같음

 

오브젝트란?

사용자가 정의한 리소스의 실제 인스턴스

리소스란?

Kubernetes API가 기본적으로 정의하고 제공하는 객체의 종류

사용자가 이 리소스들을 선언하면, Kubernetes가 이를 관리하고 자동화함

이걸로 할 수 있는 것

  1. 선언형 API로 관리됨
    1. YAML만 작성하면 상태 자동 유지
    2. 오브젝트 사라져도 자동 복구
  2. 컨트롤러가 자동으로 관리
    1. 파드가 죽으면 다시 만들고
    2. 서비스가 대상 파드를 자동 추적
    3. 스케줄러가 리소스를 자동으로 노드들에게 분배

Kubernetes가 기본적으로 제공하는 리소스 종류

  • Pod
  • Deployment
  • Service
  • ConfigMap
  • Secret
  • PersistentVolumeClaim
  • 등등

우리가 오브젝트를 선언적으로 만들면

그 오브젝트를 보고 쿠버네티스가 자동화하여 요구사항에 맞게 관리해주는 것

오브젝트의 어떤 파드를 어디에 몇 개만들어 운영할지 어디에 상태를 저장할지 등을 정의해놓는것

서비스의 경우는 어떤 요청을 어디로 보낼지 등

이렇게 작성된 YAML 파일을 JSON으로 변화해 마스터 노드의 컴포넌트인 etcd에 저장됨

1. Namespace

  • anotherclass-123이라는 하나의 프로젝트를 만드는 것
  • 여기에 포함시켜서 리소스들을 분리 관리함
apiVersion: v1
kind: Namespace
metadata:
  name: anotherclass-123
  labels:
    part-of: k8s-anotherclass
    managed-by: dashboard

2. Deployment(앱을 실행하는 핵심)

  • 실제 앱을 실행하는 가장 핵심적인 리소스
  • 이 안에서 몇 개의 파드를 만들 것인지 설정
  • template 안에 파드에 필요한 모든 정보가 들어 있다.

구조

replicas: 파드를 몇 개 띄울지

template: 파드에 들어갈 컨테이너 정보와 환경 정의

nodeSelector: 어떤 노드에 띄울지 지정

resources: CPU, 메모리 등 자원 제한

volumeMounts: 파일을 어디에 붙일지 경로 지정

### ▶ Deployment
apiVersion: apps/v1
kind: Deployment  # 파드를 여러 개 관리하고 자동으로 유지하는 리소스
metadata:
  namespace: anotherclass-123  # 이 Deployment는 해당 네임스페이스에 속함
  name: api-tester-1231  # Deployment 이름 (kubectl 명령에서 사용됨)
  labels:  # 라벨: 이 리소스를 식별하거나 그룹화할 때 사용
    part-of: k8s-anotherclass
    component: backend-server
    name: api-tester
    instance: api-tester-1231
    version: 1.0.0
    managed-by: dashboard
    
spec:  # spec: 이 리소스가 실제로 어떻게 동작해야 하는지 정의하는 부분
  selector:  # selector: 어떤 파드를 이 Deployment가 관리할지 지정함
    matchLabels:  # matchLabels: 라벨이 일치하는 파드를 대상으로 함
      part-of: k8s-anotherclass
      component: backend-server
      name: api-tester
      instance: api-tester-1231
  replicas: 2  # 파드를 2개 유지함 (장애가 나면 다시 생성)
  strategy:
    type: RollingUpdate  # 무중단 배포 전략. 순차적으로 새 파드를 띄우고 기존 파드를 종료함
    
  template:  # 이 템플릿 안에 파드 정의가 들어감
    metadata:
      labels:  # 파드에도 라벨을 붙여서 selector와 연결됨
        part-of: k8s-anotherclass
        component: backend-server
        name: api-tester
        instance: api-tester-1231
        version: 1.0.0
    spec:  # 이 파드 안의 컨테이너 구성 정의
      nodeSelector:
        kubernetes.io/hostname: k8s-master  # 이 파드는 k8s-master 노드에만 배치됨
      containers:
        - name: api-tester-1231  # 컨테이너 이름
          image: 1pro/api-tester:v1.0.0  # 사용할 컨테이너 이미지
          ports:
          - name: http
            containerPort: 8080  # 이 컨테이너가 리슨하는 포트
          envFrom:
            - configMapRef:
                name: api-tester-1231-properties  # 환경변수를 ConfigMap에서 불러옴
          
          startupProbe:  # 시작 시 정상 상태인지 확인하는 probe
            httpGet:
              path: "/startup" # 어떤 경로를 검사할지
              port: 8080 #어떤 포트로 요청할지
            periodSeconds: 5 # 몇 초마다 검사할지
            failureThreshold: 36 # 몇 번 실패하면 실패로 간주할지
          
          readinessProbe:  # 트래픽을 받아도 되는 상태인지 체크
            httpGet:
              path: "/readiness"
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          
          livenessProbe:  # 살아 있는지 체크. 실패 시 컨테이너 재시작
            httpGet:
              path: "/liveness"
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          # 이 컨테이너을 돌릴 때 자원을 얼마나 쓸지
          resources:  # 자원 요청/제한 설정
            requests:  # 최소 자원 (보장)
              memory: "100Mi"
              cpu: "100m"
            limits:  # 최대 자원 (초과 시 제한됨)
              memory: "200Mi"
              cpu: "200m"
          # 볼륨을 어디에 마운트할지
          volumeMounts:
            - name: files
              mountPath: /usr/src/myapp/files/dev  # 파드 내 파일이 마운트될 디렉토리
            - name: secret-datasource
              mountPath: /usr/src/myapp/datasource  # DB 정보가 담긴 Secret을 마운트
      # 어떤 볼륨을 사용할지
      volumes:
        - name: files
          persistentVolumeClaim:
            claimName: api-tester-1231-files  # PVC와 연결
        - name: secret-datasource
          secret:
            secretName: api-tester-1231-postgresql  # Secret과 연결

프로브

컨테이너가 정상적으로 작동 중인지, 준비되었는지, 시작되었는지를 체크하기 위한 건강검사 기능

livenessProbe:
  httpGet: ... # api 
readinessProbe:
  exec: ...
startupProbe:
  tcpSocket: ...

이런 식으로 틀은 정해져 있음

여기에 어떻게 검사할지 채워 넣으면 쿠버네티스가 검사해봄

프로브에 사용할 수 있는 검사 방식은 3가지

HTTP요청

  • httpGet:
  • HTTP API를 호출해 200 응답 등으로 확인

TCP 연결

  • tcpSocket:
  • 포트가 열려 있는지만 확인

명령 실행

  • exec:
  • 컨테이너 내부에서 쉘 명령을 실행

Deployment 는 selector.matchLabels 와 일치하는 라벨을 가진 파드만 관리함

그래서 template.metadata.labels 안에 동일한 라벨을 반드시 붙여줘야 함

Deployment 는 Pod를 직접 생성함

하지만 그 파드를 어떻게 “내가 만든 거야”라고 인식하냐하면

파드의 labels를 보고 selector.matchLabels와 완벽히 같을 때만 그 파드가 자기꺼라고 판단함

만들어질 두개의 파드는 동일한 라벨을 갖게 됨(구분은 할 수 있음 식별자로)

Deployment YAML 분석

apiVersion: apps/v1

  • apps/v1 은 Kubernetes의 앱 관련 리소스에 포함된 버전

kind: Deployment

  • 이 YAML이 정의하는 Kubenetes 리소스의 종류를 나타냄
  • Deployment는 파드를 지속적으로 유지하고, 자동으로 복제하고, 업데이트할 수 있는 컨트롤러 리소스
  1. metadata: Deployment의 메타데이터
    1. 자신이 포함될 네임스페이스 적기
    2. 자신의 이름
    3. 자신의 라벨 정보
  2. spec:이 리소스가 어떻게 동작해야하는지 정의
    1. selector.matchLabels: 자기 소속 pods 찾기위해
    2. replicas: 파드를 몇개 만들지
    3. strategy: 무중단 배포 전략은 뭘 쓸지
      1. type: RollingUpdate
    4. template: 파드 정의
      1. metadata: 파드 메타데이터
        1. labels: 파드의 라벨
      2. spec: 파드가 실제로 어떻게 동작하느가 정의
        1. nodeSelector: 파드가 어디에서 실행될지 정의
        2. containers: 파드에 만들어질 컨테이너 정의
          1. name: 컨테이너 이름
          2. image: 컨테이너 이미지
          3. ports: 컨테이너가 리슨하는 포트
          4. envFrom: 환경변수 받아오는 곳 정의
            1. configMapRef: configMap으로 환경변수 받아옴
          5. startupProbe: 상태확인
          6. readinessProbe: 상태확인
          7. livenessProbe: 상태확인
          8. resorces: 컨테이너가 사용할 자원 제한
          9. volumeMounts: 마운트를 어디다 할지 정의 선언한 볼륨을 붙이는 거
    5. volumes:외부자원을 읽어오는 선언부
      1. name:볼륨이름
      2. persistentVolumeClaim:영구 저장소를 붙이겠다는 의미
        1. claimName: 이미 만들어진 PVC 연결하겠다는거
      3. name: 볼륨이름 정의
      4. secret: 쿠버네티스의 Secret 리소스를 파일 형태로 마운트
        1. secretName:사용할 Secret 리소스의 이름

3. Service (접근 경로 제공)

  • 쿠버네티스에서 파드와 통신할 수 있도록 만들어주는 추상화된 네트워크 엔드포인트
  • 즉 파드를 찾아갈 수 있는 고정된 주소(URL) 역할을 해주는 리소스
  • 파드의 아이피가 계속 바뀌니까 고정된 주소가 필요
[vue.js]
↓axios.get("/api/user") // 상대경로 요청
[Nginx]
↓ proxy_pass [<http://api-tester-1231>](<http://api-tester-1231/>)
[Service: api-tester-1231]
↓ port: 80 → selector로 파드 선택
↓ targetPort: 8080
[Pod 내 컨테이너에서 처리]

이런 식으로 프론트에서는 평소와 같이 요청하게 작성하면

Nginx가 나의 서비스 이름으로 리버스 프록시하면됨(서비스가 리슨하고 있는 포트가 80이니까 그대로)

서비스에게 도착한 요청을 매칭되는 백엔드 파드로 다시 보내주는 것

그 사이에 coreDNS가 http://api-tester-1231 이 요청을 받아 Service의 클러스터 IP로 바꿔주는 과정이 있음

역할

  • 고정 주소 제공: 서비스에 접근할 수 있는 고정 IP/이름 제공
  • 로드밸런싱: 연결된 여러 파트에 트래픽을 자동 분산
  • 파드 셀렉터: 특정 라벨이 붙은 파드만 선택해서 연결
  • 포트 포워딩: 외부 요청을 내부 파드의 포트로 전달
### ▶ Service
apiVersion: v1  # Kubernetes core API 그룹의 버전 v1 사용
kind: Service  # 이 YAML은 Service 리소스를 정의함
metadata:
  namespace: anotherclass-123  # 이 서비스가 속한 네임스페이스
  name: api-tester-1231  # 서비스 이름 (DNS 이름 등으로 사용됨)
  labels:  # 서비스 리소스 자체에 대한 메타데이터
    part-of: k8s-anotherclass  # 프로젝트 그룹 라벨
    component: backend-server  # 백엔드 역할임을 나타냄
    name: api-tester  # 서비스 식별용 이름
    instance: api-tester-1231  # 서비스 인스턴스 구분값
    version: 1.0.0  # 버전 정보
    managed-by: dashboard  # 이 리소스가 생성된 관리 도구 (예: 웹 대시보드)
spec:
  selector:  # 이 서비스가 연결할 대상 Pod을 결정하는 라벨 셀렉터
    part-of: k8s-anotherclass  # 아래의 라벨과 일치하는 Pod을 선택함
    component: backend-server
    name: api-tester
    instance: api-tester-1231
  ports:
    - port: 80  # 클러스터 내부에서 이 서비스에 접근할 때 사용하는 포트 (가상포트)
      targetPort: http  # 실제 Pod 내부 컨테이너의 포트 이름 또는 번호 (예: containerPort: 8080)
      nodePort: 31231  # 클러스터 외부에서 접근 가능한 고정 포트 (Node의 물리 포트)
  type: NodePort  # 외부 사용자도 Node IP + nodePort 조합으로 접근 가능하게 함

Service YAML 분석

apiVersion: v1

  • 이 리소스가 Kubernates API의 v1 버전을 사용한다는 의미
  • Service는 기본 리소스이므로 core 그룹의 v1에 포함됨

kind: Service

  • 이 YAML은 Service라는 리소스 종류를 정의한 것이다.

metadata:메타데이터 정의

namespace:네임스페이스

name:이름

labels:라벨 정보

spec:이 리소스(서비스)가 뭘하는지 정의

selector: Service가 어떤 Pod에 트래픽을 전달할지를 결정하는 기준 이 값과 Pod의 라벨이 정확히 일치하는 경우에만 트래픽을 전달함

ports: 서비스 포트 매핑 정보를 정의하는 것

port: 80 클러스터 내부에서 이 서비스를 호출할 때 사용하는 고정 포트 번호

클러스터 내부 Pod가 http://api-tester-1231:80으로 요청 가능

targetPort: http

  • 이 포트는 파드 안의 컨테이너에서 리슨 중인 포트와 매핑
  • http는 실제 컨테이너 설정에 있는 포트 이름이고, 보통 8080 같은 값과 연결됨

nodePort: 31231

  • 이 서비스는 클러스터 외부에서도 접근 가능한 타입임
  • 외부 사용자는 클러스터에 있는 어느 노드 IP로든 31231번 포트로 접근 가능하게 만듬

전체 흐름

[외부 사용자가 [<http://워커노드IP:31231>](<http://xn--ip-h74i55i:31231/>) 접속]
↓
Service가 포트 80으로 수신
↓
Pod 중 selector와 라벨이 일치하는 대상 선택
↓
Pod 내부 컨테이너의 8080 포트로 요청 전달

여기서 “외부”란

Kubernetes 클러스터 바깥을 의미

  • 개발자의 웹 브라우저
  • 사내 네트워크의 다른 서버
  • 인터넷 사용자 등

“Service로 요청을 보낸다” 의 의미

Kubernetes에서는 직접 Pod로 접근 하지 않는다 → 아이피가 계속 바뀌니까 그래서 중간 중계자를 이용하는데 그게 Service

Service 과정

  1. 외부사용자가 워커노드ip:31231로 요청보내면(VM ip의 포트)
  2. NodePort 감지 → kube-proxy가 감지
    • 각 노드(워커/마스터)에 실행되는 데몬
    • 역할: Service 리소스의 상태를 감시해서 iptables 규칙을 자동 설정함’
    • 즉, Service가 생성되면 kube-proxy가 해당 노드의 커널 네트워크 계층에 규칙을 삽입
  3. kube-proxy란
  4. kube-proxy가 서비스로 라우팅
  5. 클러스트 내부에서만 의미 있는 가상포트 80으로 보냄
  6. Service가 라벨 매칭되는 Pod 선택
  7. 해당 Pod의 컨테이너 8080 포트로 전달

4. ConfigMap & Secret (설정 주입)

ConfigMap

환경 설정값이나 설정 파일 경로 등 민감하지 않은 정보를 저장하는 객체이다.

컨테이너에 환경변수로 주입하거나, 파일처럼 마운트할 수 있다.

Secret

비밀번호, API 키, 인증서처럼 민감한 데이터를 저장하는 리소스

ConfigMap과의 차이점은

  • 데이터가 base64로 인코딩되어 저장
  • 일부 클러스터에서는 자동으로 암호화되도록 설정 가능
  • 접근 권한을 RBAC으로 제한 가능
### ▶ ConfigMap, Secret
apiVersion: v1  # Kubernetes core API 그룹의 버전 v1 사용
kind: ConfigMap  # ConfigMap은 환경 변수나 설정 파일을 저장하는 용도
metadata:
  namespace: anotherclass-123  # 이 ConfigMap이 속한 네임스페이스
  name: api-tester-1231-properties  # 이 ConfigMap의 이름 (Pod에서 참조 가능)
  labels:  # 리소스를 식별하기 위한 라벨들
    part-of: k8s-anotherclass  # 프로젝트 그룹
    component: backend-server  # 역할 구분: 백엔드 서버
    name: api-tester  # 서비스 이름
    instance: api-tester-1231  # 인스턴스 식별용 라벨
    version: 1.0.0  # 버전 정보
    managed-by: dashboard  # 생성/관리 주체

# 실제 저장되는 key-value 형식 데이터들
# 이 값들은 컨테이너의 환경변수로 쓰이거나 파일로 마운트 가능
data:
  spring_profiles_active: "dev"  # Spring 프로파일 설정: dev 환경 사용
  application_role: "ALL"  # 애플리케이션 내부에서 사용할 권한 또는 역할
  postgresql_filepath: "/usr/src/myapp/datasource/postgresql-info.yaml"  # DB 접속 정보를 마운트한 파일 경로

---

apiVersion: v1
kind: Secret  # Secret은 DB 비밀번호 같은 민감한 정보를 저장하는 리소스
metadata:
  namespace: anotherclass-123  # Secret이 속한 네임스페이스
  name: api-tester-1231-postgresql  # Secret 이름 (Pod에서 참조 가능)
  labels:  # 라벨을 통해 리소스를 구분하거나 관리할 수 있음
    part-of: k8s-anotherclass
    component: backend-server
    name: api-tester
    instance: api-tester-1231
    version: 1.0.0
    managed-by: dashboard

# stringData는 Kubernetes가 내부적으로 base64로 변환해 저장함
# 이 내용은 파일처럼 컨테이너에 마운트되며, 애플리케이션에서 읽을 수 있음
stringData:
  postgresql-info.yaml: |  # 이 키 이름은 컨테이너에 마운트될 파일명으로 사용됨
    driver-class-name: "org.postgresql.Driver"  # JDBC 드라이버 설정
    url: "jdbc:postgresql://postgresql:5431"  # DB 접속 URL
    username: "dev"  # DB 사용자명
    password: "dev123"  # DB 비밀번호

5. PVC / PV (저장소)

PVC

Pod가 요청하는 “내가 쓸 저장소 필요해요” 라는 요청서

  • 용량, 접근 방식, 필요한 조건 등을 명시함
  • 실제 저장 공간을 직접 포함하지 않음 → PV에 연결되기를 기다림

PV

클러스터 운영자가 제공하는 실제 디스크 정의

  • 크기, 위치, 노드 제한 등 지정 가능
  • PVC의 조건과 일치하는 경우 자동으로 연결됨
### ▶ PVC, PV
apiVersion: v1
kind: PersistentVolumeClaim  # PVC는 파드가 요청하는 저장소 리소스
metadata:
  namespace: anotherclass-123  # PVC가 속한 네임스페이스
  name: api-tester-1231-files  # 이 PVC의 이름 (파드에서 volumes.claimName으로 참조)
  labels:
    part-of: k8s-anotherclass
    component: backend-server
    name: api-tester
    instance: api-tester-1231-files
    version: 1.0.0
    managed-by: kubectl
spec:
  resources:
    requests:
      storage: 2G  # 요청할 저장 용량 (2GB)
  accessModes:
    - ReadWriteMany  # 여러 파드에서 동시에 읽고 쓰기가 가능함
  selector:
    matchLabels:  # 연결할 PV를 라벨로 선택함
      part-of: k8s-anotherclass
      component: backend-server
      name: api-tester
      instance: api-tester-1231-files

---

apiVersion: v1
kind: PersistentVolume  # PV는 실제 물리/가상 디스크를 정의한 리소스
metadata:
  name: api-tester-1231-files  # PV의 이름 (PVC에서 selector로 참조됨)
  labels:
    part-of: k8s-anotherclass
    component: backend-server
    name: api-tester
    instance: api-tester-1231-files
    version: 1.0.0
    managed-by: dashboard
spec:
  capacity:
    storage: 2G  # 이 PV의 실제 용량 (PVC 요청과 같아야 매칭됨)
  volumeMode: Filesystem  # 파일 시스템 형태로 마운트됨
  accessModes:
    - ReadWriteMany  # 다중 읽기/쓰기를 지원
  local:
    path: "/root/k8s-local-volume/1231"  # 물리적 경로 (노드 로컬 디스크 경로)
  nodeAffinity:  # 이 볼륨이 사용할 수 있는 노드를 제한함
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - {key: kubernetes.io/hostname, operator: In, values: [k8s-master]}  # k8s-master 노드에서만 사용 가능

작동 흐름

  1. PV: 운영자가 "/root/k8s-local-volume/1231" 폴더를 2GB로 지정한 스토리지 등록
  2. PVC: 개발자가 "2GB 필요하고 ReadWriteMany 되면 좋겠어요" 라고 요청
  3. 라벨과 용량이 매칭되면 자동으로 Binding
  4. Pod에서 volumeMounts로 이 PVC를 마운트하면 실제 디스크처럼 사용 가능

6. HPA (자동 확장)

CPU 사용량이나 메모리 사용량이 일정 기준을 넘으면 파드 수를 자동으로 늘려주는 Kubernetes 기능

Deployment 파드 수를 자원 사용량에 따라 정해주는 친구

### ▶ HPA
apiVersion: autoscaling/v2  # HorizontalPodAutoscaler 리소스를 위한 API 버전 (v2는 확장된 기능 포함)
kind: HorizontalPodAutoscaler  # HPA는 부하에 따라 파드 수를 자동으로 조절하는 리소스
metadata:
  namespace: anotherclass-123  # 이 HPA가 속한 네임스페이스
  name: api-tester-1231-default  # HPA 리소스 이름
  labels:
    part-of: k8s-anotherclass
    component: backend-server
    name: api-tester
    instance: api-tester-1231
    version: 1.0.0
    managed-by: dashboard
spec:
  scaleTargetRef:  # HPA가 감시하고 확장/축소할 대상 리소스
    apiVersion: apps/v1
    kind: Deployment  # Deployment를 감시 대상으로 설정
    name: api-tester-1231  # 이 이름을 가진 Deployment의 파드 수를 조절함
  minReplicas: 2  # 최소 파드 수는 2개로 유지
  maxReplicas: 4  # 최대 파드 수는 4개까지 허용
  metrics:
    - type: Resource  # 리소스 기반 확장 (예: CPU, Memory)
      resource:
        name: cpu  # CPU 사용률 기준
        target:
          type: Utilization  # 비율 기반 (퍼센트)
          averageUtilization: 60  # CPU 평균 사용률이 60%를 넘으면 확장
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 120  # 갑작스러운 부하 증가 시, 120초 동안 상태를 안정적으로 유지한 뒤 확장

작동 흐름

  1. Deployment: api-tester-1231을 감시
  2. CPU 평균 사용률이 60% 넘으면 파드를 자동으로 늘림
  3. 최소 파드 수는 2개, 최대는 4개
  4. 갑작스러운 증가를 방지하기 위해 120초 간 안정 상태 확인 후 확장

Deployment에는 이미 2개라고 정해져있음

근데 얘가 갯수를 덮어써서 컨트롤해줌

 

 

마무리

이렇게 YAML 파일만 정리해보니 아까 그림이 눈에 보이기 시작했다.

각각의 오브젝트를 만드는 파일이고

그 파일들은 서로 네임스페이스 안에 들어가 있으며

각각이 연계되어 동작하는 것이다.

 

강의가 30분 안쪽인데

이렇게 정리하는데만 3시간이 걸렸다.

하나를 찾으면 그 답변에 모르는 단어가 2~3개 나오고
그걸 찾으면 또 나오고를 반복하니 3시간이 그냥 가버렸다.

이렇게 하는게 맞는 방법인지 모르겠다..

뭔가 큰틀로 접근하고 싶은데 너무 큰 커서 나의 좁은 시야로는 아직 한 눈에 못보는 것 같다.

+ Recent posts