https://asdf-vm.com/guide/introduction.html

Guide

What is asdf?

Introduction

asdf는 툴 버전 매니저이다. 모든 툴 버전 정의는 하나의 파일(.tool-versions) 파일에 포함된다. 사용자는 깃 레포에서 이 것을 확인하여 팀에게 공유하고, 모든 팀원들이 정확하게 같은 버전의 툴을 사용할 수 있도록 보증할 수 있다.

오래된 작업 방식은 여러 CLI 버전 매니저, 각각의 구분된 API, 설정파일, 구현(ex. @PATH 조작, shims, 환경변수 등등)을 요구했다. asdf는 개발 워크플로우를 단순화하기위해 싱글 인터페이스, 환경설정을 제공하고 간단한 플러그인 인터페이스를 통해 모든 도구와 런타임을 확장할 수 있도록 제공한다.

How It Works

asdf는 사용자의 쉘 설정에 셋업되면 특정 툴들을 관리하기위해 플러그인이 설치된다. 플러그인을 통해 툴이 설치되면 그 각각의 툴을 위한 shims를 가지는 실행가능한 파일이 생성된다. 사용자는 실행파일을 실행하면 asdf가 .tool-versions에 설정된 툴들의 버전을 구분해주는 shim이 실제로 실행된다.

Related Projects

nvm / n / rbenv etc

nvm, n, rbenv와 같은 툴들은 이들 툴들에 의해 설치된 실행파일들을 위한 shim을 생성하는 쉘 스크립트이다.
asdf는 매우 유사하고 이러한 툴/런타임 버전 관리의 영역들과 경쟁하도록 만들어졌다. asdf의 다른점은 툴/런타임 별 매니저를 필요에따라 삭제할 수 있고, 레포의 *-version 파일 별로, 매니저별 다른 명령어들도 필요에 따라 삭제할 수 있는 플러그인 시스템이다.

direvn

현재 디렉토리에 따라 환경변수를 로드하고 언로드할 수 잇는 새로운 기능으로 기존 쉘을 보강한다.

asdf는 환경변수를 관리하지 않는다. 그러나 asdf-direnv 플러그인을 사용해서 direvn의 행위를 asdf와 통합할 수 있다.

Homebrew

The Missing Package Manager for macOS (or Linux)

Homebrew는 사용자의 패키지와 업스트림 의존성(upstream dependencies)을 관리한다. asdf는 업스트림 의존성을 관리하지 않는다. 패지키관리자가 아니고 의존성 리스트를 작게 유지하려고 노력하기 때문에 그것은 사용자가 해야한다.

NixOS

Nix는 패키지 관리와 시스템 설정에 unique approach를 가지고 있는 도구

NixOS는 각각의 툴에 대한 의존성 트리 전체 패키지의 정확한 버전을 관리함으로써 환경을 truly하게 재연하는 것을 목표로 하는 툴이다. 몇몇 기능들은 asdf가 하지 못한다. NixOS는 자체 프로그래밍 언어, 많은 CLI tools, 6만개 이상의 패키지 컬렉션으로 동작한다.
하지만 asdf는 upstream dependency들을 관리하지않으며 패키지 매니저가 아니다.

Why use asdf?

asdf는 플러그인 시스템을 통해 많은 도구들로 팀들이 정확하게 같은 버전의 툴을 사용하는 것을 보증하고 사용자의 쉘 설정에포함되는 단일 쉘 스크립트의 단순함과 친숙함을 제공한다.

Getting Started

Introduction

asdf 설치본은 아래 항목을 포함하고 있다.

  1. 디펜던시 설치
  2. asdf core 다운로드
  3. asdf 설치
  4. 사용자가 관리 하고자하는 각각의 툴, 런타임을 위한 플러그인 설치본
  5. 툴, 런타임 버전 설치본
  6. .tool-versions 설정 파일들을 통한 글로벌/프로젝트 버전 관리 설정

1. Install Dependencies

asdf 는 주로 git과 curl 을 요구한다. 사용자의 패키지 매니저를 구동하기 위해 아래 전체 커맨트 리스트가 있다.(몇몇은 이후 단계에서 자동으로 설치될 수 있다.)

사용자의 시스템 설정에 따라 sudo 가 필요할 수 있다.

2. Download asdf

Official Donwload

git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.13.1

Community Supported Download Methods

공식 git을 이용하는 것을 강력 추천한다.

  • Howbrew : brew install asdf
  • Pacman : git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si 또는 선호에 따라 AUR helper

3. Install asdf

여기에 구성에 영향을 미치는 쉘, OS, 설치 방법에 대한 다른 조합들이 있다. 너의 시스템과 가장 매칭되는 selection을 expand해라.

  • 공식문서 참고

asdf는 사용자의 프레임워크(oh-my-zsh 등)을 source하고, $PATH를 설정한 후 source 해야한다.

Warninig
macOS에서 Bash, Zsh 쉘은 시작되면서 자동으로 path_helper라는 유틸리티를 호출한다. path_helperPATH(and MANPATH)를 rearrange item 해서 특정 순서가 요구되는 툴들의 동작에 inconsistent를 일으킬 수 있다. 이 문제를 해결하기 위해서 맥상의 asdf는 기본적으로 PATH 엔트리들의 가장 앞쪽에(가장 높은 우선위를 가지기 위해) 강제로 추가된다. 이것은 ASDF_FORCE_PREPEND 변수를 통해 설정 가능하다.

PATH의 변경을 적용하기 위해 쉘을 재시작해라. 일반적으로 새로운 탭을 열면 된다.

Core Installation Complete!

asdf 코어 설치가 완료되었다.

asdf는 플러그인을 설치하고 툴을 설치하고 그 툴의 버전을 관리해야 유용하게 사용할 수 있다. 계속되는 가이드는 이것을 어떻게 하는지 알려준다.

4. Install a Plugin

시연을 위해 asdf-nodejs를 통해 nodejs를 설치&설정할 것이다.

Plugin Dependencies

각각의 플러그인들은 종속성이 있기 때문에 우리는 그 종속성이 리스팅되어있어야 하는 플러그인 레포를 체크해야 한다. asdf-nodesjs는 여기

  • Debian apt-get install dirmngr gpg curl gawk
  • CentOS/ Rocky Linux/ AlmaLinux yum install gnupg2 curl gawk
  • macOS brew install gpg gawk

몇몇 플러그인들읜 ponst-install hooks를 가지고 있기 때문에 디펜던시를 먼저 설치해야 한다.

Install the Plugin

asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git

5. Install a Version

이제 우리는 nodejs 플러그인을 가지고 있고 툴의 버전을 설치할 수 있다.
asdf list all nodejs 명령어로 사용가능한 버전을 볼 수 있고 asdf list all nodejs 14로 버전의 서브셋 목록을 볼 수 있다.
latest로 가능한 버전을 볼수도 있다.

asdf install nodejs latest

Note
asdf는 정확한 버전을 강제한다ㅣ latest는 실행 시 가장 최신의 버전을 resolve하는 helper 이다.

6. Set a Version

asdf는 $HOME 디렉토리부터 현재 워킹 디렉토리까지의 모든 .tool-versions 파일에 정의된 툴의 버전을 lookup 한다. lookup은 asdf 관리 도구가 를 실행될 때 just-in-time으로 실행한다.

WARNING
툴의 실행을 위한 version listed 행위가 없으면 에러가 난다. asdf current 는 현재 사용자의 디렉터리로부터 툴의 버전이 resolution인지, absent인지 보여주어 사용자는 어떤 툴이 실행에 실패할 것인지 확인할 수 있다.

Global

기본 전역 설정은 $HOME/.tool-versions에 관리된다. 아래와 같이 전역 버전을 설정할 수 있다.

asdf global nodejs latest

$HOME/.tool-versiosn는 아래와 같이 볼 수 있다.

nodejs 16.5.0

몇몇 OS는 파이썬같이 asdf가 아니라 시스템에 의해 관리되는 툴이 이미 설치되어 있을 수 있다. 사용자는 asdf에게 다시 시스템에 의해 관리되도록 설정해야한다. Versions reference section이 도움이 가이드해줄 것이다.

Local

로컬 버전은 $PWD/.tool-versions 파일(사용자의 현재 working directory)에 정의된다. 대부분 프로젝트의 깃 레포가 될 수 있다. 사용자가 원하는 디렉토리에서 실행한다

asdf local nodejs latest

$PWD/.tool-versions 는 아래와 같이 설정된다

nodejs 16.5.0

Using Existing Tool Version Files

asdf는 다른 오래된 버전 매니저의 버전 파일을 마이그레이션하는 것을 지원한다. rbenv.ruby-version와 같은 경우이다. 이러한 지원은 플러그인 베이스로 지원된다.
asdf-nodejs.nvmrc, .node-version을 통해 지원된다. 이 기능을 활성화하기 위해 사용자의 asdf configuration file인 $HOME/.asdfrc 파일을 아래와 같이 수정하여야 한다.

legacy_version_file = yes

configuration 레퍼런스 페이지를 통해 더 많은 설정을 확인하여라

Guide Complete!

asdf의 Getting Started 가이드가 끝났다. 너는 이제 프로젝트의 nodejs의 버전을 관리할 수 있다. 너의 프로젝트의 각각의 툴 타입을 위해 다음 유사한 단계를 따라해라.

asdf, asdf --help를 실행해서 명령어를 확인하고 더 많은 명령어와 친숙해져라.

Usage

Core

asdf 핵심 명령어 목록은 다소 작지만 많은 워크플로우를 용이하게 할 수 있다.

Installation & Setup

Getting Started 가이드를 참고하라

Exsec

asdf exec <command> [args...]

현재 버전의 shim 명령어를 실행

Env

asdf env <command> [util]

Info

asdf info

OS, Shell, asdf debug information을 출력하기 위한 헬퍼 커맨드. 버그 리포트를 만들 때 이 명령어의 결과를 공유

Reshim

asdf reshim <name> <version>

이 명령어는 패키지의 현재 버전의 shim들을 재생성한다. 기본적으로 shim은 툴을 설치하는 동안 플로그인에 의해 생성된다. npm CLI같은 몇몇 툴들은 실행가능한 글로벌 설치본을 제공한다. 예를들어 npm install -g yarn으로 Yarn을 설치하는 것이다. 이 실행가능한 명령어는 플러그인 라이프사이클에 의해 설치되지 않기 떄문에 이를 위한 shim이 아직까지는 존재하지 않는다. asdf reshum nodejs <version>yarn 처럼 어떤 새로운 실행가능 명령 shim을 강제로 recalculation 한다.

Shim-versions

asdf shum-versions <command>

명령어를 위한 shim을 제공하는 플러그인과 버전 목록을 리스팅한다.

예를들어 Node.js는 node, npm 두 명령어를 설치한다. asdf-nodejs로 더 많은 툴의 버전이 설치되어있다면 shim-versions는 이렇게 리턴될 수 있다.

$ asdf shim-versions node
nodejs 14.8.0
nodejs 14.17.3
nodejs 16.5.0


$ asdf shim-versions npm
nodejs 14.8.0
nodejs 14.17.3
nodejs 16.5.0

Update

asdf plugin update --all

만약 사사용자가 특정 패키지의 업데이트를 원하면 아래와 같이 하면 된다.

asdf plugin update <name>
# asdf plugin update erlang 

이 update 명령어는 플러그인 레포지터리의 default branch of the origin 의 가장 최근 커밋을 가져온다. 버전이 적용된 플러그인과 업데이는 현재 개발중이다.

Remove

asdf plugin remove <name>

플러그인을 지우는 것은 플러그인에 의해 만들어진 모든 툴의 설치본을 지운다. 이것은 툴의 많은 미사용 버전을 cleaning/pruning하기 위한 간편한 방법이다.

Syncing the asdf Short-name Repository

short-name repo는 사용자의 로컬 머신과 싱크되고 주기적으로 refresh 된다. 싱크를 확인하는 방법은 다음과 같다.

  • 싱크 이벤트를 명렁어로 트리거한다.
    • asdf plugin add <name>
    • asdf plugin list all
  • disable_plugin_short_name_repositoryyes로 설정되어있다면 동기화가 중단된다. 더 많은 정보는 asdf config docs를 참고해라.
  • X분 동안 동기화가 이루어지지 않았다면, 동기화가 발생한다.
    • X는 기본 60 이지만 .asdfrcplugin_repository_last_check_duration 옵션에서 설정할 수 있다. asdf config docs를 참고해라.

Versions

Install Version

asdf install <name> <version>
# asdf install erlang 17.3

플러그인이 소스로부터의 컴파일, 다운로드를 지원한다면

본 내용은 2022년 10월 진행한 Google Cloud StudyJab에서 제공하는 Coursera의

  • Architecting with Google Kubernetes Engine: Foundations
  • Architecting with Google Kubernetes Engine: Workload
  • Architecting with Google Kubernetes Engine: Productions

내용을 정리하였음

Foundation

Cloud Computing and Google Cloud

클라우드의 5가지 fundamental

  • On-demand and self-service: No human intervention needed to get resources
    주문형, 셀프 서비스. 별도 사람의 개입 없이 자동화된 인터페이스를 사용하여 리소스(컴퓨트, 스토리지, 네트워크 등)를 바로바로 사용할 수 있음
  • Broad network access: Access from anywhere
    인터넷만 연결되있다면 접속이 가능함
  • Resource pooling: Provider shares resources to customers
    Rapid elasticity: Get more resourcees quickly as needed
    Measured service: Pay only for what you consume
    항장 준비되어있고, 언제든지 고객의 필요에 따라 사용할 수 있는 거대한 리소스 풀을 제공하며, 고객은 물리적 리소스에 대한 걱정을 할 필요가 없음. 언제든지 확장 가능하며 사용하거나 예약한 만큼만 비용을 지불하고, 필요없어진다면 언제든지 삭제하고 비용지불을 멈출 수 있음.

일반적으로 잘 알려진 Virtual machine 부터 Kubernetes 까지 다양한 managed service를 이용할 수 있다.

  • Compute Engine : GCP에서 제공하는 On demand Virtual Machine 서비스.
  • GKE, Google Ckubernetes Engine : GCP에서 제공하는 Managed Kubernetes.
  • App Engine : GCP에서 제공하는 Fully Managed Platform as a Service Framework. 코드만 업로드 하면 인프라 걱정없이 바로 구동시킬 수 있다.
  • Cloud Functions : Serverless execution environment, Functions as a Service. 함수로써 동작할 코드만 넣으면 동작시킬 수 있으며 실행 횟수에 따라 비용을 지불한다.

GKE는 Compute Engine 기반으로 설계되었다. GCP에서 데이터베이스를 이용하려면 Compute Engine으로 VM을 만들어서 직접 database를 설치하거나 또는 GKE에서 데이터베이스 컨테이너를 구동시킬 수 있다.

이 방법 외에도 GCP 제공하는 fully managed database와 storage service를 이용하는 방법도 있다. 이런 방법은 직접 서버, 데이터베이스를 구성하는 수고를 줄여주며 relational 또는 non-relational database를 포함한 다양한 형태의 데이터베이스, 서비스들을 이용할 수 있다.

또한 빅데이터, 머신러닝을 위한 다양한 서비스를 GCP에서 제공하며, 이런 서비스를 이용해 다양한 루틴 작업들을 줄일 수 있다.

Resource Management

GCP에서 사용하는 다양한 서비스의 물리 장치(서버, 하드드라이버 등)는 전 세계의 구글 데이터센터에 펼쳐져있다. GCP는 multi-region, region, zone에 있는 리소스들을 제공한다.

  • multi-region : 3개로 나누어진 GCP 영역. America, Europe, Asia Pacific 으로 나뉜다.
  • region : 같은 대륙에서 지역적으로 나누어져있는 지역. 왕복 네트워크시간 95%가 ms 미만. ex) europe-west2
  • zone : region 안에서 한 지역에 있는 장소. 한곳에 있는 데이터센터들이라고 볼 수 있음. ex) europe-west2-a/b/c

GCP의 compute engine의 인스턴스는 특정 zone에 배치된다. 만약 해당 zone의 상태가 unavailable 상태가 되면 compute engine도 같이 unavailable 상태가 되며, compute engine을 기반으로하는 GKE도 똑같이 영향을 받는다. 이런 리소스를들을 배포할 때 여러 zone에 배치를 한다면 예상치 못한 사고에 내결함성을 가질 수 있게 된다. 전세계의 40%의 인터넷 트래픽을 담당(추정)하는 구글 네트워크는 전세계에 배치된 데이터센터간에 매우 빠른 속도와 짧은 지연시간을 지원하고, 구글의 edge caching network는 이러한 네트워크망을 기반으로 GCP의 서비스들을 매우 빠르게 서비스한다.

대부분의 GCP 서비스들은 Zonal resources(기본적으로 single zone에 배치되는)이다. zonal level, regional level, multi-regional level이 있다. GKE의 node는 zonal resource이며 GKE는 regional level이다. multi-regional level로는 HTTP load balancer, Virtual Private Cloud 등이 있다.

리소스들은 반드시 project에 속한다. project는 GCP에서 조직 구성의 기본 단위이며 리소스, 서비스, 빌링, API, 권한을 설정할 수 있다. 프로젝트들은 변경이 불가능한 고유의 아이디와 넘버를 가진다. 사용자가 지정한 라벨을 통해 프로젝트를 필터링할 수 있다.

프로젝트들은 folder에 속할 수 있으며 폴더는 또 다른 폴더로 들어갈 수 있다. 최상위 grouping 단계는 organization이다. 고유의 변경불가능한 id 값을 가지며 만약 기업이라면 기업의 전체 GCP의 policy를 설정하는 등을 할 수 있다. 이러한 정책들은 하이라키 구조로 하위 레벨 구성에 상속된다.

빌링은 프로젝트 레벨에서 측정될 수 있다.

Billing

빌링은 프로젝트 단위로 누적되며, 프로젝트 설정 시 billing account를 설정해야 한다. 빌링 계정은 결제 정보등 결제와 관련된 모든 정보를 담고있다. 하나 또는 다수의 프로젝트를 빌링 계정에 연결할 수 있다. 매달 또는 임계치에 다다를 때마다 자동으로 청구된다. subaccount를 만들어서 프로젝트별로 결제를 담당할 수 있다. 예를들어 GCP의 리소스를 만들어서 다른 여러 회사들에 판매할 때 활용될 수 있다.

빌링 사고를 방지하기 위해 3가지 무료 빌링 서비스를 사용할 수 있다.

  • Budgets and alerts
    프로젝트 또는 빌링 계정 단위로 budget을 설정하고 설정한 budget에 대한 알람을 설정할 수 있다.
    예를들어 100원의 예산을 설정하고 90%에 대한 알람을 설정하면 90원이 되었을 때 메일로 알람이 오도록 설정할 수 있으며 webhook을 설정해 resource를 삭제하는 등의 트리거도 설정할 수있다.
  • Billing export
    세부 사항을 출력할 수 있으며 big query 등을 설정할 수 있다.
  • Reports
    프로젝트 또는 서비스별 빌링 상황을 시각화하여 볼 수 있다.

Quotas(할당량)으로 제어할 수 도 있다. 할당량의 종류는 아래와 같은 타입이 있다.

  • Rate Quota
    GKE API : 100초당 1000번의 리퀘스트만 받음
  • Allocation Quota
    프로젝트당 5개의 private network 만 허용
  • 각각의 서비스별로 다양한 quota를 설정할 수 있음

Interacting with Google Cloud

  • Google Cloud Console
    Web-based GUI 제공. 프로젝트와 리소스들을 시각화하고 마우스 클릭으로 작업들을 실행할 수 있음
  • Cloud SDK
    gcloud, kubectl, gsutil, bq 등 툴을 포함하고있는 cloud sdk를 머신에 설치해서 사용가능. 스크립트로 이런 명령어를 사용해서 자동화할 수 있음.
  • Cloud Shell
    브라우저에서 gcp resource에 CLI 기반으로 접근 가능한 쉘 제공. Cloud SDK가 이미 설치되어 있어 사용 가능. 임시 VM을 사용한다. 이 임시 VM은 유저별로 하나씩 제공되긴 하지만 쉘을 멈추면 VM도 멈추고 쉘을 시작하면 VM도 다시 시작되므로 프로덕션용으로 사용할 수 없다. 또한 5GB의 persistence storage를 가질 수 있다. 또한 editor 도 있어 간편하게 사용할 수 있다.
  • Cloud Console mobile app
  • REST-based API

Containers and Container Images

  • Dedicated server
    기본적으로 애플리케이션을 배포하기 위해선 적절한 물리적인 장소를 찾고, 파워, 쿨링, 네트워크 등 하드웨어 장비를 준비해서 Kernel, Dependencies, Application Code를 set up하는 과정을 진행했어야 했음.
    만약 애플리케이션을 운영하면서 확장이 필요할 경우 똑같은 과정을 반복하면됨. 이러한 과정은 시간, 비용이 많이 소요됨. 애플리케이션은 OS나 하드웨어에 종속적이였음.
  • Virtual machine
    virtualization 기술의 Hypervisor는 물리적인 서버들의 하드웨어를 가상화해 여러 가상 서버를 실행할 수 있게 하여 애플리케이션을 하드웨어 종속성으로부터 독립시킴. KVM은 대표적인 하이퍼바이저 중 하나임. 서버가 필요할 경우 하이퍼바이저에서 가상서버를 생성하기만 하면 되어 이전 방식보다 획기적으로 비용, 시간이 감소됨. 또한 가상머신 이미지등을 사용해서 쉽고 빠르게 복제본을 생성할 수 있게됨. 이 방식의 단점은 애플리케이션은 여전히 OS의 종속성을 가지고있었고, 서비스 시작을 위해선 가상머신의 boot time이 필요했음. 또한 하나의 머신에 여러 서비스를 구동할 경우 종속성의 문제가 있을 수 있으며 하나의 애플리케이션 문제가 머신에 영향을 미치면 다른 애플리케이션도 영향을 받게 되었음.

더 나은 방식으로, 애플리케이션, dependency 레벨에서 추상화를 구현하여, 이전 방식 처럼 전체 하드웨어, 운영체제를 가상화할 필요 없이 user space만 가상화하면 됨. user space는 커널 위에 있으며 애플리케이션 코드와 dependency가 위치한 공간이며 이것이 container임. 컨테이너는 하드웨어, OS를 가상화하지 않기 때문에 가벼우며 boot time이 없음. 또한 컨테이너간 별도의 user space를 가지기 때문에 서로 독립적이며 같은 kernel 위에서 portable함. 시스템 환경/라이브러리 등 여러 환경에 독립적으로 동작할 수 있음.

리눅스 커널이라면 랩탑, 서버, 가상머신 등의 환경에 영향을 받지않고 언제든 실행을 보장할 수 있음.

애플리케이션 코드와 dependency의 묶음을 image라고하고 컨테이너는 이미지를 running한 인스턴스이다. 개발자는 애플리케이션 코드를 이미지로 빌드해서 portable package를 만들 수 있다. 이미지를 빌드하고 컨테이너를 구동하기 위해선 tool이 필요한데, 오픈소스 프로젝트인 docker가 이 두가지를 모두 제공한다. 하지만 쿠버넷이 하는 것 처럼 컨테이너들을 orchestration하진 않음

컨테이너는 여러 리눅스 기술을 활용해 구성됨.

  • Processes : 리눅스 프로세스는 다른 프로세스와 분리된 각각의 virtual memory address space를 가지며, 빠르게 create/destory 됨
  • namespace : process id number, directory tree, ip 등을 사용하기 위해 사용
  • cgroups : 컨테이너의 자원(cpu, mem 등)을 컨트롤하기 위해 사용
  • union file system : code, dependecy로 layer를 구성하기 위해 사용

이미지는 layer로 구성되어있고 이미지 빌드 도구는 이미지를 빌드하기 위해 container manifest를 참조하는데, docker의 경우 Docker file 이다. Docker file은 이미지의 레이어 정보를 가지고 있는데, 이 레이어들은 read-only이며 컨테이너가 구동되면 맨 마지막에 w/r이 가능한 ephemeral layer가 생성된다.

예제 도커파일의 FROM, COPY, RUN, CMD 명령어는 실행되면서 각각 하나의 layer를 차례차례 만들며 각 레이어는 이전 레이어에서의 변경사항만 가지고 있다. 도커파일을 작성할 때 변동성이 가장 낮은 레이어를 맨 아래(맨 처음)에 두고 차례차례 두는 것이 더 효율적이다.
이미지가 컨테이너로써 실행되면 conatiner layer가 생성되어 해당 레이어는 컨테이너 러닝동안 R/W가 가능하며 컨테이너가 종료되면 사라진다. 즉 컨테이너에서 작업한 데이터를 보존하기 위해선 다른 persistence storage를 찾아야 한다.

위 구조에서 하나의 이미지를 사용하는 여러 컨테이너들은 개별 container layer를 가지고 있으면서 그 아래에 동일한 base image layer를 가지고 있으므로 효율적으로 활용될 수 있음.

새로운 컨테이너를 빌드할 때, 전체 이미지를 복사하는 것이 아니라 변경점이 있는 레이어부터 만든다. 위 그림을 보면 여러개의 컨테이너가 같은 base image(read only)인 ubuntu:18.04 이미지를 공유 하면서, 그 위에 각각의 R/W layer를 가지고 있는 것을 볼 수 있다. 이것이 VM보다 더 빠르게 동작할 수 있는 이유 중 하나이다.

GCP에선 image 관련 환경으로 container registyr인 gcr.io와 이미지 빌드 자동화 도구인 Cloud Build를 제공한다.

Introduction to Kubernetes

쿠버넷은 on-premise 또는 Cloud에서 컨테이너를 컨트롤할 수 있는 오픈소스 플랫폼이다.

  • open source : 벤더에 종속적이지 않은 Cloud Native Computing Foundation 재단의 오픈소스 프로젝트
  • Automation :  deployment, scaling, load balancing, logging, monitoring 등 다양한 management features를 자동화
  • Declarative configuration : 관리자는 복잡한 설정을 이해할 필요 없이 desired state를 정의하고 kuberentes가 이를 달성하기 위한 동작을 수행
  • Imperative configuration : 명령형 설정도 지원함

  1. stateless, stateful application 뿐만 아니라 batched job, deamon tasks 등 다양한 application workload를 지원
  2. 애플리케이션의 사용량에 따라 auto scale in-out을 지원
  3. 리소스의 사용량을 제어할 수 있음
  4. 다양한 플러그인, 확장을 지원함
  5. 오픈소스이기때문에 vendor lock in 없이 다양한 환경에 자유롭게 배포, 이동이 가능함

 

 

 

Introduction to Google Kubernetes Engine

GCP에선 관리형(fully managed) 쿠버넷 서비스로 GKE를 서비스함.

  • GKE 장점
    • GKE에서 fully managed를 해줌. 노드를 생성하기 위해 vm을 provision 하거나 등의 작업들을 자동화된 프로세스로 지원함
    • 컨테이너에 최적화된 OS로 GKE를 구동하여 쉽게 확장 가능
    • 자동으로 버전을 업그레이드할 수 있게 해줌
    • 노드가 탈락되었을 때 자동으로 복구해주는 등을 해줌
    • 클러스터를 자동화된 프로세스로 scale in-out 할수 있음
    • GCP의 다른 container 관련 서비스(registry, Cloud Build) 등과 통합되어 있음
    • GCP의 IAM과 통합 가능
    • GCP의 로깅&모니터링 서비스와 통합 가능
    • 기존 GCP의 네트워크와 통합 가능

Computing Options Detail

GCP에서 compute workload를 구동하기 위해 제공되는 서비스와 특징

  • Compute Engine
    GCP에서 제공하는 virtual machine service
    - infrastructure, OS에 대한 customizing을 원할 때 사용
    - 운영체제를 섞어서 애플리케이션을 구동시키고 싶을 때 사용
    • Fully customizable virtual machines : GCP에서 제공하는 가상머신 서비스. 자유롭게 설정한 가상머신을 제공받음. 현재 160 vCPUs, 3 TB memory 이상을 신청할 수 있음.
    • Persistenct disks and optional local SSDs : persistence storage를 위한 두 가지 옵션 제공. 64TB 이상의 network storage를 제공하며 스냅샷 등 기능 이용 가능. 빠른 I/O를 위한 local SSD도 사용 가능.
    • Glocal load balancing and autoscaling : managed instance group 설정을 통해 global load balancer를 활용하여 auto-scaling을 설정할 수 있음
    • Per-second billing : 배치잡 등에 유용하게 활용할 수 있도록 초당 청구됨. preemptible vm으로 매우 저렴한 가격에 서비스를 이용할 수도 있음.
  • App Engine
    fully managed application platform. 별도의 서버 설정, 배포 설정 없이 코드만으로 서비스를 구동
    - 서버, 배포에 대한 걱정없이 코드만 작성해서 배포하고싶을 때 사용
    - mobile app, web, gaming backend, RESTful API 등에 활용
    • provides a fully managed, code-first platform
    • Streamlines application deployment and scalability
    • Provides support for popular programming languages and application runtimes
    • Supports integrated monitoring, logging, and diagnostics
    • Simplifies version control, canary testing, and rollback
  • GKE
    • Fully managed Kubernetes platform
    • Supports cluster scaling, persistence disks, automated upgrades and auto node repairs
    • Built-in integration with Google Cloud Service
    • Portability across multiple environment(Hibrid computing, Multi cloud computing)
  • Cloud Run
    managed compute platform
    - 특정 이벤트가 발생했을 때 stateless container를 실행하고 싶을 때
    • Enables stateless containers
    • Abstract away infrastructure management
    • Automatically scales up and down
    • Open API and runtime environment
  • Cloud Functions
    for simple, single-purpose functions
    python, go, javscript로 쓰여진 코드만 업로드하면 scaling, HA, fault-tolerant를 자동으로 구성할 수 있음
    • Event-driven, serverless conmpute service
    • Automatic scaling with highly available and fault-tolerant design
    • Charges apply only when your code runs
    • Triggered based on events in Google Cloud services, HTTP endpoints, and Firebase

Kubernetes Concepts

쿠버넷의 두 가지 중요한 메인 컨셉은 object model과 declarative management임.

사용자는 object model의 attribute를 수정해서 state를 변경시킬 수 있음. 이 변경은 declarative 방식으로 되는데 사용자는 쿠버넷이 달성하길 원하는 오브젝트와 오브젝트의 상태를 정의하고 쿠버넷은 사용자가 정의한 상태를 달성하기 위해 동작함. 

쿠버넷에서의 오브젝트는 두 가지 상태를 가지고있음. 사용자가 정의한 상태(desired state), 실제로 클러스터에 구동중인 현재 오브젝트의 상태(current state). object spec은 사용자가 정의한 desired state가 기록된 element이고, object status는 쿠버넷의 control plane이 기록하는 현재 오브젝트의 상태를 기록하는 element.

여기서 controle plane이란 쿠버넷 클러스터의 프로세싱을 담당하는 쿠버넷 구성요소 중 하나.

쿠버넷의 basic building block, smallest deployable object. 컨테이너는 쿠버넷에서 pod으로 구동됨. 하나의 팟은 여러개의 컨테이너로 구성되어있으며 쿠버넷은 pod에게 고유의 ip를 부여하고 pod의 내부 컨테이너는 127.0.0.1로 통신함. 팟의 컨테이너들은 network, storage를 공유함. 

 

nginx 컨테이너를 3개 구동시키고 싶다면, 사용자는 nginx 컨테이너에 대한 object 정보를 기입하면 쿠버넷은 사용자가 작성한 disire state를 current state로 만들기 위해 작업한다.

 사용자가 입력한 desired state와 current state를 비교해서 쿠버넷 컨트롤 플레인이 desired state가 current state와 일치할 때 까지 반복적으로 계속 시도한다. 이런 쿠버넷의 행위를 watch loop라고 한다. 이런 작업은 첫 생성 이후부터 endless로 계속 모니터링되며 desired state와 current state를 일치시키기 위해 동작한다.

 

The Kubernetes Control Plane

쿠버넷 클러스터를 구성하기 위해선 컴퓨터가 필요하다. 클라우드를 포함한 대부분의 쿠버넷 클러스터들은 가상머신을 노드로 사용한다. 클러스터 중 하나의 컴퓨터를 controle plane이라고 부르고 나머지를 nodes라고 부른다.  nodes는 pod의 실행을 담당하고, 컨트롤 플레인은 클러스터의 coordinate를 담당한다. 클러스테에서 중요한 부분을 담당하는 컴포넌트들은 컨트롤 플레인에 배치된다.

  • kube-API server : 클러스터를 컨트롤할 수 있는 기능들을 API로 제공하며 클러스터를 조회하거나 변경하는 모든 쿼리들이 api server를 통해야 한다. 대표적으로 kubectl이 api server를 통해 클러스터와 통신한다.
  • etcd : 쿠버넷 클러스터에 대한 정보 저장소이다. 현재 클러스터/노드 구성 정보, 팟이 어느 노드에서 구동중인지 등의 클러스터 정보를 저장한다. etcd를 direct로 사용해선 절대 안되며 api server로 접근해야 한다.
  • kube-scheduler : 팟의 스케쥴링을 담당한다. 설정 정보를 읽어 팟을 적절한 노드에 배정한다.
  • kube-controller-manager : kube-api-server를 통해 클러스터를 모니터링하며 desired state와 current state를 맞추는 역할을 수행한다.
  • kube-cloud-manager : 쿠버넷 클러스터와 클라우드 프로바이더와 통신하며 여러가지 필요 서비스를 제공하기 위해 사용된다.
  • kubelet : 쿠버넷 클러스터가 각 노드에 설치하는 agent. container runtime을 가지고 있으면서 pod을 구동하고 라이프사이클을 모니터링하면서 api server와 통신한다. 쿠버넷은 여러 컨테이너 런타임을 지원하지만 GKE는 containerd를 사용한다.
  • kube-proxy : 노드들에 구동중인 팟들의 네트워크를 제어하기 위한 컴포넌트. 오픈소스 쿠버넷은 iptables의 firewall을 사용해 해당 기능을 제공한다.

'Cloud' 카테고리의 다른 글

AWS - 인증, IAM  (0) 2022.02.02
Istio - Service Mesh & Istio Basic Concept  (0) 2021.03.07
Docker Daemon 외부 포트 오픈  (0) 2021.03.02
Rook-Ceph Concept, Install  (0) 2021.02.24

팩토리 패턴

객체를 사용하는 측에서 바로 생성하지 않고 중간에 생성을 전담하는 객체를 두고 생성하여 결합도를 낮춘다.

예제

동물 카페(AnimalCafe)에서 동물을 만날 수 있는데 현재는 토끼만 만날 수 있다.

interface Animal { /* ... */ }
class Rabbit implements Animal { /* ... */ }

class AnimalCafe {
  public Animal meet() {
      Animal animal = new Rabbit();
    // 기타 로직(등록, 지불 등)
    return animal;
  }
}

위 코드에서 meet() 클래스는 고객에게 제공할 동물을 선택하고, 제공하기 위해서 기타 로직을 수행한다. 이후 추가 케이스로 강아지(Puppy)가 추가되었고 카페에선 오전엔 토끼, 오후엔 강아지를 제공하려고 한다.

class Puppy implements Animal { /* ... */ }

class AnimalCafe {
  public Animal meet() {
    Animal animal;
      if (지금시간 == 오전)
      animal = new Rabbit();
    else
      animal = new Puppy();
    // 기타 로직(등록, 지불 등)
    return animall;
  }
}

또 다른 동물 종류가 추가되면 meet() 메소드의 수정이 지속적으로 필요하다. meet() 메소드는 조건에 따라서 제공할 동물을 선택하는 작업고객에게 동물을 제공하기 위한 작업 이 두 가지 작업에 대한 책임을 가지고 있다. 두 가지 작업 중 하나라도 변경이 필요하면 meet() 메소드는 수정되어야한다. meet() 메소드가 하나의 책임만을 가지도록 변경하기 위해 조건에 따라서 제공할 동물을 선택하는 작업을 아래처럼 분리할 수 있다.

class AnimalFactory {
  public static Animal pick() {
    if (지금시간 == 오전)
      return Rabbit();
    else
      return new Puppy();
  }
}

class AnimalCafe {
  public Animal meet() {
    Animal animal = AnimalFactory.pick();
    return animall;
  }
}

AnimalCafe 클래스에서 조건에 따라 동물을 생성하는 로직을 AnimalFactory 클래스의 pick() 메소드로 분리하였다. 이로써 AnimalCafe#meet() 메소드의 고객에게 동물을 제공하기 위한 작업에 대한 책임만을 가지게 되었다.

싱글톤 패턴 활용

상황에 따라서 매번 인스턴스를 생성할 필요가 없을 경우 싱글톤 패턴 적용을 아래와 같이 고려할 수 있다.

class Pupply implements Animal() {
  private Animal puppy;

  public static Animal getInstance() {
    if (puppy == null)
      puppy = new Puppy();

    return puppy;
  }
}

템플릿 메서드 패턴 활용

// TODO

싱글톤 패턴

클라이언트가 사용하려는 객체가 클라이언트의 라이프 사이클 동안 단 하나의 인스턴스임을 보장할 수 있는 패턴. 아래와 같은 상황에서 활용될 수 있다.

  • 공유 자원 접근
  • 복수의 시스템이 하나의 자원에 접근할 때
  • 유일한 객체가 필요할 때
  • 값의 캐시가 필요할 때

예제

모든 사람이 사용할 수 있는 공용 자원인 단 한대의 TV

class Tv {
  private static Tv tv;

  private int nowChannelNo = 0;

  private Tv tv() {}

  public static Tv getInstance() {
    if (tv == null)
      tv = new Tv();

    return tv;
  }

  public void changeChannel(int channerNo) {
    this.nowChannelNo = channerNo;
  }
}

멀티쓰레드 환경에서의 싱글톤

여러 쓰레드가 위 앞선 예제의 getInstance() 메소드에 접근할 경우 if (singletone == null) 접근 시점에 따라 여러 Singletone 객체를 생성할 수 있다. 이를 방지하기 위해 바로 초기화 하는 방법동기화 방식이 있다.

// 바로 초기화 하는 방식
class Tv {
  private static Tv tv = new Tv();;

  public static Tv getInstance() {
    return tv;
  }


}

// 동기화(synchronized 사용)
class Tv {
  private static Tv tv;

  public synchronized static Tv getInstance() {
    if (tv == null)
      tv = new Tv();

    return tv;
  }
}

싱글톤 vs Statis Method

정적 변수와 메소드들로만 이루어진 정적 클래스를 구성하면 싱글톤 패턴의 클래스와 같은 역할을 할 수 있다. 하지만 인터페이스를 구현해야하는 경우 정적 메소드를 사용할 수 없다. 위의 예제의 경우 Tv를 사용할 때 interface Tv를 구현한 TvA, TvB가 있다고 할 때, 클라이언트에서 Tv를 참조하고있고, 경우에 따라 TvA의 인스턴스, TvB의 인스턴스를 사용한다고 하면 이는 정적 클래스로 구현이 불가능하다.

전략 패턴

객체의 행위를 캡슐화하여 분리한다.

예제

로봇은 움직일 수 있고 공격할 수 있다. 각 로봇의 타입에 따라서 움직이거나 공격하는 방식이 다르다.

interface Robot {
  void move();
  void attack();
}

class RobotA implements Robot {
  void move() { do("로봇 A가 걸어갑니다") }
  void attack() { do("로봇 A가 총을 사용합니다.") }
}

class RobotB implements Robot {
  void move() { do("로봇 B가 뛰어갑니다.") }
  void attack() { do("로봇 B가 화염방사기를 사용합니다.") }
}

class Client {
  main() {
    RobotA a = new RobotA();
    RobotB b = new RobotB();
    a.move()
    a.attack()
    b.move()
    b.attack()
  }
}
  • 로봇 A가 날아가게 하려면? => 아래와 같은 기존 코드 변경이 필요하다.
  • class RobotA implements Robot { void move() { // do("로봇 A가 걸어갑니다") do("로봇 A가 날아갑니다") } ... }
  • 로봇C를 추가하고 B와 똑같은 방식으로 동작하게하려면? 화염방사기가 고장나서 물대포 방사기로 변경하여야된다면? => 두 곳의 수정이 필요하다.
  • class RobotB implements Robot { ... void attack() { do("로봇 C가 물대포 방사기를 사용합니다.") } } class RobotB implemenets Robot { ... void attack() { // do("로봇 B가 화염 방사기를 사용합니다.") do("로봇 B가 물대포 방사기를 사용합니다.") } } class RobotB implemenets Robot { void move() { do("로봇 C가 뛰어갑니다.") } void attack() { // do("로봇 B가 화염 방사기를 사용합니다.") do("로봇 C가 물대포 방사기를 사용합니다.") } }

수행하는 행위를 캡슐화하여 외부로 추출하여 사용할 수 있다.

interface MoveStrategy { void move(); }
class WalkMoveStrategy implements MoveStrategy { void move() { do("걷는다") }
class FlyMoveStrategy implements MoveStrategy { void move() { do("난다") }

interface AttackStrategy { void attack(); }
class GunAttackStrategy implements AttackStrategy { void attack() { do("총 공격") }
class FireAttackStrategy implements AttackStrategy { void attack() { do("화염방사기 공격") }

interface Robot {
  void move(MoveStrategy moveStrategy);
  void attack(AttackStrategy attackStrategy);
}

class RobotA implements Robot {

  public RobotA(MoveStrategy moveStrategy, AttackStragegy attackStrategy) {
    setMoveStrategy(moveStrategy)
    setAttackStrategy(attackStrategy)
  }

  void move() { moveStrategy.move() }
  void attack() { attackStrategy.attack() }
}

class RobotB implements Robot {

  public RobotA(MoveStrategy moveStrategy, AttackStragegy attackStrategy) {
    setMoveStrategy(moveStrategy)
    setAttackStrategy(attackStrategy)
  }

  void move() { moveStrategy.move() }
  void attack() { attackStrategy.attack() }
}

MoveStrategy, AttackStrategy만 변경하여 로봇에 주입함으로써 로봇의 움직임, 공격 방식을 수정하거나 기존에 만들었던 전략을 활용 가능함으로써 재활용도 가능해졌다.

상태 패턴

객체가 상태(객체가 가질 수 있는 어떤 조건이나 상황)가 변함에 따라 수행할 수 있는 행위를 외부로 분리하여 상태의 변경에 유연하게 대처할 수 있도록 한다.

예제

OFF, 약풍이 있는 선풍기 객체를 설계한다. 여기서 OFF, 약풍은 선풍기 객체의 상태에 해당한다.

class Fan {
  private static int OFF = 0;
  private static int 약풍 = 1;
  private int 현재상태 = OFF;

  ...
  public push(int signal) {
    if (signal == OFF)
      끈다(); 현재상태=OFF;
    else if (signal == 약풍)
      약풍(); 현재상태=약풍;
  }
}

여기서 강풍이 추가된다면 아래의 변경이 필요하다. => 새로운 상태가 추가될 때마다 그 상태를 처리하기 위한 기존 클래스의 수정이 지속적으로 발생하게 된다.

class Fan {
  ...
  private static int 강풍 = 2;
  ...

  public void push(int signal) {
    ...
    else if (signal == 강풍)
      강풍(); 현재상태=강풍;
  }
}

여기서 변경되는 부분은 상태, 고정되는 부분은 선풍기가 상태에 해당하는 행위를 실행시키는 책임이다. 아래와 같이 상태를 분리할 수 있다.

interfcae State {
  State action();
}

class Off implements State { State action() { 끈다() } }
class 약풍 implements State { State action() { 약풍() } }

class Fan {
  private int 현재상태;

  public void push(State state) {
    현재상태 = state.action();
  }
}

선풍기 객체는 상태 인터페이스만 참조하고 구체적인 구현에 의존하지 않는다. 강풍이 추가된다면 선풍기 객체 Fan에 변경이 필요한 것이 아니라 새로운 상태를 추가하기만 하면 된다.

class 강풍 implements State { State action() { 강풍() } }

싱글톤과의 결합

앞선 예제에서 상태를 생성할 때 매번 새로운 인스턴스를 생성할 필요가 없을 경우 싱글톤 패턴을 적용할 수 있다.

커맨드 패턴

예제

운동 로봇은 여러 모드가 있는데 그 중 축구를 할 수 있게 하는 축구 모드가 있고 리모컨으로 조정할 수 있다.

class SoccerRobot {
  void do() { 축구() }
}

class RemoteController {
  SoccerRobot soccerRobot;

  void push() { soccerRobot.do() }
}
  • 농구 로봇이 필요하면? => 농구 로봇을 추가하고 리모컨을 수정한다 => 기존 코드(RemoteController)의 변경이 필요하다.
  • class BaseballRobot { void do() { 농구() } void push() { soccerRobot.do() } } class RemoteController { BaseballRobot baseballRobot; ... }
  • 버튼을 눌렀을 때 이전 모드에서 변경된 모드로 동작하게 한다. 이전 모드가 축구라면 농구를, 농구라면 축구를 한다.새로운 로봇이 추가되고 새로운 조건이 추가된다면? => RemoteController의 수정이 계속 요구된다.
  • class RemoteController { final int BASEBALL = 1; final int SOCCER = 2; BaseballRobot baseballRobot; SoccerRobot soccerRobot; int nowState = BASEBALL void push() { if (nowStatue == BASEBALL) nowState = SOCCER; soccerRobot.do(); else if (nowStatus == SOCCER) nowState = BASEBALL; baseballRobot.do(); } }

여기서 계속 변하는 것은 버튼을 눌렀을 때 할 행위이다. 이 행위를 캡슐화하여 외부로 추출한다.

interface Command { void execut(); }
class SoccerCommand implements Command {
  SoccerRobot soccerRobot;
  void execute() { soccerRobot.do() }
}
class BaseballCommand implements Command {
  BaseballRobot baseballRobot;
  void execute() { baseballRobot.do() }
}

class RemoteController {
  Command command;

  void push() {
    command.execute();
  }
}

리모컨의 버튼을 눌렀을 때 일어날 행위를 추가하거나 변경하고 싶다면 Command 인터페이스를 구현한 구현체만 추가하면 되고 기존 클래스의 변경이 필요하지 않다.

옵저버 패턴

예제

경마 게임에서 말이 도착하면 바로 전광판에 보여주는 클래스를 설계한다.

class ArrivalRecorder {
  List<Integer> rank;
  ScoreBoardPrinter scoreBoardPrinter;

  void crossed(Int horseNumber) {
    rank.add(horseNumber);
    ScoreBoardPrinter.update();
  }
}

class ScoreBoardPrinter {
  ArrivalRecorder recorder;

  public void update() { 화면출력(recorder.getRank()); }
}

class Client {
  main() {
    ArrivalRecorder recorder = new ArrivalRecorder();
    ScoreBoardPrinter scoreBoardPrinter = new ScoreBoardPrinter(recorder);
    recoder.scoreBoardPrinter = scoreBoardPrinter;
  }
}
  • 다른 종류의 스코어 보드를 사용한다면? 예를들어 소리로 말하는 ScoreBoardSpeaker로 변경한다면? => 기존 클래스의 변경이 필요하다.
  • class ArrivalRecorder { ... ScoreBoardSpeaker scoreBoardSpeaker; ... } class ScoreBoardSpeaker { ... }
  • 여러 종류의 스코어 보드를 사용한다면? Printer, Speaker 방식 둘 다 사용하고 싶다, 2개의 Speaker를 사용하고 싶다 => 기존 클래스의 변경이 필요하다.
  • class ArrivalRecorder { ... List<ScoreBoardSpeaker> scoreBoardSpeaker; ScoreBoardPrinter scoreBoardPrinter; ... }

변하는 것은 ArrivalRecorder가 알려줘야 할 대상, 즉 Recorder에 새로운 이벤트가 생기면 그 이벤트를 받아서 처리해야되는 대상과 그 대상을 ArrivalRecorder에 등록하는 부분.

interface Observer { abstract void update(); }

abstract class Subject {
  private List<Observer> observers = new ArrayList<Observer>();

  void attach(Observer observer) { ... }
  void detach(Observer observer) { ... }
  void notify() { 
    for (Observer observer: observers) {
      observer.update()
    }
  }
}

class ArrivalRecorder extends Subject {
  List<Integer> rank;
  void crossed(Int horseNumber) {
    rank.add(horseNumber);
    this.notify();
  }
}

class ScoreBoardPrinter implements Observer {
  void update() { 화면출력(recorder.getRank()); }
}
class ScoreBoardSpeaker implements Observer {
  void update() { 스피커출력(recorder.getRank()); }
}

새로운 출력 방식이 필요하면 Observer를 구현한 새로운 SocreBoard를 구현하고 ArrivalRecorder에 등록하면 기존 코드 수정 없이 새로운 출력 방식의 추가나 변경이 가능하도록 된다.

데코레이터 패턴

여러 기능을 구현한 클래스의 조합을 구현하고자 할 때 사용할 수 있다.

예제

스포츠를 할 수 있는 로봇이 있다. 로봇은 현재 축구 기능을 제공한다.

interface Robot {
  void do();
}

class SoccerRobot implements Robot {
  void do() {
    축구();
  }
}
  • 다른 기능을 하는 로봇이 추가된다면? 예를들어 농구를 하는 로봇, 달리기를 하는 로봇
  • 여러 기능을 조합한 로봇을 원한다면? 예를들어 농구, 축구를 하는 로봇.

각 기능별로 Robot 인터페이스를 구현한 클래스를 만들어야되고, 조합의 경우 모든 구현체에 대한 조합을 하나하나 구현해야 한다. 축구, 농구, 달리기 로봇을 조합하면 축구+농구, 축구+달리기, 농구+달라기 로봇을 구현해야한다.

각 추가 기능별로 개별 클래스를 설계하고 객체의 조합을 담당하는 클래스(데코레이터)를 만든다.

interface Robot {
  void do();
}

class Defaultrobot implements robot {
  void do() {
    기본_동작();
  }
}


abstract class RobotDecorator extends Robot {
  Robot robot;

  void do() {
    robot.do();
  }
}

class SoccerDecorator extends RobotDecorator {

  SoccerDecorator(Robot robot) {
    super(robot)
  }

  void do() {
    robot.do();
    축구();
  }
}

class BaseballDecorator extends RobotDecorator {

  BaseballDecorator(Robot robot) {
    super(robot)
  }

  void do() {
    robot.do();
    농구();
  }
}

다른 기능의 로봇이 필요할 경우 RobotDecorator를 구현한 신규 기능의 클래스를 구현하고 아래와 같이 클라이언트에서 사용할 수 있다.

class RunningDecorator extends Decorator {
  ...
}

main() {
  Robot runningRobot = new RunningDecorator(new DefaultRobot());
  runningrobot.do();
}

여러 기능을 조합한 로봇의 경우 아래와 같이 구현할 수 있다.

main() {
  Robot runningAndSoccerRobot = new RunningDecorator(
    new SoccerDecorator(
      new DefaultRobot()));
  runningAndSoccerRobot.draw();
}

템플릿 메소드 패턴

전체적으로 동일하면서 부분적으로 다른 구문으로 구성된 메소드의 코드 중복을 줄인다. 동일한 기능을 상위 클래스에서 정의하고 변경이 발생하는 부분만 하위 클래스에서 구현한다.

예제

차가 출발하려면 문이 닫혀있는지 확인해야 한다.

class Car {
  move() {
    문_확인()
    출발();
  }
}

여기서 여러 종류의 차가 있다면 아래와 같이 설계해볼 수 있다.

interface Car {
  move();
}

class CarA implements Car {
  move() {
    문_확인();
    CarA방식_출발();
  }
}

class CarB implements Car {
  move() {
    문_확인();
    CarB방식_출발();
  }
}

문_확인() 부분이 계속 중복되는 문제가 있다. 예를들어 창문_확인() 동작이 추가되어야 한다면? -> Car 구현체를 모두 찾아 변경해야 한다.
여기서 변하는 부분은 출발 전 확인하는 부분. 이 행위를 별도의 클래스로 분리한다.

abstrafct class CarTemplate {
  move() {
    문_확인();
    창문_확인();
    moveCar();
  }

  protected abstract moveCar();
}

class CarA extends CarTemplate {
  protected moveCar() {
    CarA방식_출발();
  }
}

다른 방식이 추가된다면 CarTemplate 클래스의 move() 메소드만 수정하면 된다.

컴퍼짓 패턴

객체가 서로 '부분-전체'의 조합을 가지고 전체에서 부분의 공통적인 동작들을 요구할 때 활용할 수 있다.

예제

A, B 부품으로 구성된 로봇. 각 부품 또는 전체(부품의 합)의 전력 소모 합을 제공한다.

class Robot {
  A a;
  B b;

  int getPower() {
    return a.getPower() + b.getPower() + c.getPower();
  }
}

class A {
  int getPower() {
    return 100;
  }
}

class B {
  int getPower() {
    return 50;
  }
}
  • 부품 C가 추가된다면? -> 새로운 클래스 C를 만들고 Robot의 멤버를 수정한다. 즉 기존 클래스의 수정이 필요하다.

    class C {
      int getPower() {
        return 400;
      }
    }
    
    class Robot {
      ...
      C c;
    
      int getPower() {
        return a.getPower() + b.getPower() + c.getPower();
      }
    }

추가되는 클래스를 추상화하고, 추상화된 클래스를 변경없이 다룰 수 있는 구조로 설계한다.

interface RobotPart {
  int getPower();
}

class A implements RobotPart { ... }
class B implements RobotPart { ... }
class C implements RobotPart { ... }

class Robot {
  List<RobotPart> robotParts = new ArrayList<>();

  void add(RobotPart part) {
    robotParts.add(part);
  }

  int getPower() {
    return robotParts.getPower().sum()
  }
}

Reference

'Program language > Java' 카테고리의 다른 글

run java project with h2 without installation  (0) 2022.01.22
Java Reflection  (0) 2018.04.03

+ Recent posts