www.notion.so/Linux-Container-cffb608779874506a6ac8df218105d36

컨테이너와 vm의 차이점

컨테이너

Linux Container는 linux의 가상화 기술을 사용하여 작동한다.

  • 차이점

컨테이너에서 실행되는 프로세스는 다른 모든 프로세스와 마찬가지로 호스트 운영체제 내에서 실행된다.

( 프로세스가 별도의 운영체제에서 실행되는 vm과는 다르다 )

컨테이너의 프로세스는 여전히 다른 프로세스와 격리돼 있음.

컨테이너는 더 가벼움 → 이유는 ? vm은 각각 독립적인 게스트OS를 사용하지만

컨테이너는 하나의 호스트 운영체제 내에서 실행되기 때문이다.

( vm은 구성 요소 프로세스뿐만 아니라 시스템 프로세스도 실행해야 하기 때문에 컴퓨팅 리소스가 더 필요하다 반면 컨테이너는 하나의 격리된 프로세스에 지나지 않음 )

출처: https://blog.netapp.com/blogs/containers-vs-vms/

VM은 호스트OS위의 가상화 프로세스인 하이퍼바이저를 통해 가상 머신을 실행한다.

이 가상머신내에서 실행되는 어플리케이션이 자기자신의 게스트 OS 커널에 대한 시스템 콜을 수행하면 커널은 하이퍼바이저로 호스트의 물리적 CPU에서 x86명령을 수행한다.

하이퍼바이저는 두가지 타입이 있다.

  1. 베어메탈의 하이퍼바이저는 직접 하드웨어를 제어하기 때문에 호스트OS가 없다.
    • Citrix Xen, VMWare ESX Server, MS Hyper-V등이 위 타입
  2. 호스트 운영체제에서 실행되는 하이퍼바이저
    • VMware Server, QEMU, 오라클사의 VirtualBox등이 있다.

반면 컨테이너는 호스트 OS에서 실행되는 동일한 커널에서 시스템 콜을 수행한다.

이 커널은 호스트 CPU에서 x86명령을 수행하는 유일한 커널이다.

CPU는 가상머신과 같은 방식으로 어떠한 종류의 가상화도 필요가 없다.

 

출처 https://livebook.manning.com/book/kubernetes-in-action/chapter-1/68

Vm은 각 vm이 자체 리눅스 커널을 실행해 완전한 격리를 제공

컨테이너는 모든 컨테이너가 동일한 커널을 호출한다. 하지만 보안의 위험이 있음.

하드웨어 리소스가 한정적일 경우 실행시킬 프로세스가 적다 ?

그럼 vm

많이 돌려야된다 ?

그럼 컨테이너

컨테이너는 부팅이 필요가 없다 같은 커널을 공유하기 떄문에 그냥 프로세스 실행하면 바로 실행이 시작된다.

컨테이너는 어떻게 동일한 운영체제에서 프로세스를 격리시킬수 있는것인가 ?

리눅스 네임스페이스로 각 프로세스가 시스템 ( 파일, 프로세스, 네트워크 인터페이스 , 호스트 이름 등) 에 대한 독립된 뷰만 볼수 있도록 한다.

두번째는 Cgroup으로 프로세스가 사용할 수 있는 리소스 ( CPU, 메모리, 네트워크 대역폭 등) 의 양을 설정 또는 제한 할수 있다.

NameSpace

각 리눅스 시스템은 기본적으로 초기 구동시 하나의 네임스페이스가 존재한다.

파일시스템 , 프로세스 ID , 사용자 ID , 네트워크 인ㅍ터페이스 등과 같은 모든 시스템 리소스는

하나의 네임스페이스에 속한다.

여기서 추가 네임스페이스를 생성해서 리소스를 구성할수 있다.

프로세스를 실행할때 해당 네임스페이스 중 하나에서 프로세스를 실행한다.

프로세스는 동일한 네임스페이스 내에 있는 리소스만 볼수 있다.

( 프로세스가 돌아가고있는 네임스페이스의 리소스만 볼수 있음 )

여러 종류의 네임스페이스가 있기 떄문에 프로세스는 하나의 NS에 속하는게 아니라 여러 NS에 속할수 있다.

NS의 종류

마운트, 프로세스 ID ( pid ) , 네트워크 ( net ) , 프로세스 간 통신 ( ipc )

호스트와 도메인 이름 ( uts )

  • 이 UTS는 Unix Time Sharing으로 호스트 이름과 Network Inetrnet Service 일명 nis 도메인 이름 관련 네임스페이스다.

사용자 ID ( user )

UTS는 해당 네임스페이스 내에서 실행 중인 프로세스가 사용할 호스트 이름과 도메인 이름을 결정한다.

두개의 서로 다른 UTS 네임스페이스를 한쌍의 프로세스에 각각 지정하면

서로다른 로컬 호스트이름을 보게 할수도 있다.

즉 두 프로세스를 마치 두개의 다른 시스템에서 실행중인것처럼 보이게 가능 ( 호스트 이름만 )

마찬가지로 프로세스가 속한 네트워크 네임스페이스는 실행 중인 애플리케이션의 프로세스에서 볼 수 있는 네트워크 인터페이스를 결정한다.

각 네트워크 인터페이스는 정확히 하나의 ns에 속하지만 한 ns에서 다른 ns로 이동할 수 있다.

각 컨테이너는 고유한 네트워크 ns를 사용하므로 각 컨테이너는 고유한 ns interface set를 볼수 있다.

ns를 이용해 컨테이너에서 app을 분리하는 방법을 알수 있다.

Cgroup

Cgroup의 역할은 namespace로 각 컨테이너를 격리 시킨 후 각 컨테이너가 사용할 수 있는 시스템 리소스의 양을 설정하거나 제한하는 것.

( 프로세스의 리소스사용을 제한하는 것이 cgroup )

Cgroup를 이용하여 제한을 걸면 다른 프로세스용으로 예약된 리소스를 사용할수 없다.

이는 각 프로세스가 별도의 시스템에서 실행될때와 비슷하다.

초반 도커는 LXC를 이용하여 개발

0.9버전부터는 libcontainer를 개발하여 사용

현재는 runc 사용

참고

쿠버네티스 인 액션

시작하세요 도커 쿠버

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

docker 0  (0) 2021.03.14
Docker Image  (0) 2021.03.05
docker-compose.yml 작성  (0) 2020.06.18
DOCKERFILE  (0) 2020.03.01
내가 자주쓰는 docker 명령어  (0) 2020.02.23

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

Stream

스트림이란 데이터를 운반하는데 사용되는 연결통로

스트림은 단방향 통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.

둘다 하려면 input stream과 output stream 2개의 스트림이 필요하다.

스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다.

큐와 같은 FIFO 구조로 되어있다고 생각하면 이해하기 쉽다.

Byte기반의 스트림

  • FileInputStream , FileOutputStream

    • 파일
  • ByteArrayInputStream , ByteArrayOutputStream

    • 메모리 ( Byte 배열 )
  • PipedInputStream , PipedOutputStream

    • 프로세스 ( 프로세스간 통신 )
  • AudioInputStream , AudioOutputStream

    • 오디오장치
  • 어떤 대상에 대해서 작업을 할것인지에 따라 해당 스트림을 선택해서 사용하면 된다.

입출력스트림의 부모 inputStream, outputStream

  • InputStream

    • abstraact int read()
    • int read(byte[] b)
    • int read(byte[] b, int off, int len)
  • OutputStream

    • abstract void write(int b)
    • void write(byte[] b)
    • void write(byte[] b, int off, int len)

Buffer

CPU와 I/O의 속도차이에서 나오는 비효율성을 줄이기 위해 등장

데이터를 전송하는 상호간의 장치에서 고속의 장치 ( java api 호출 ) 와 저속의 장치 ( OS레벨에서 시스템콜 ) 간의 속도차이로 인해 저속의 장치가 작업을 추리하는 동안 고속의 장치가 기다려야 하는 현상을 줄여준다.

JAVA I/O

스트림 가반의 I/O이며 기본적으로 buffer를 사용하지 않으며 blocking 상태이다.

JAVA NIO

채널 기반의 I/O , 기본적으로 buffer를 사용하며 non-blocking을 지원한다.

출처: https://velog.io/@jaden_94/13주차-항해일지-IO

InputStream 과 OutputStream

InputStream try (FileInputStream fis = new FileInputStream(new File(fileName))) { int content; // 돌면서 byte를 읽고 file의 끝에 도달하면 -1을 return한다. while ((content = fis.read()) != -1) { System.out.println((char)content); } } catch (IOException e) { e.printStackTrace(); } OutputStream try (FileOutputStream outputStream = new FileOutputStream(file, false)) { int read; byte[] bytes = new byte[DEFAULT_BUFFER_SIZE]; while ((read = inputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, read); } }

출처 https://mkyong.com/java/how-to-convert-inputstream-to-file-in-java/

  • NIO가 아닌 IO를 사용한 stream이여서 buffer를 자동으로 사용하지 않아 느리다.

Byte와 Character 스트림

  • inputStream과 outputStream은 대표적인 byteStream이다.

  • 1 byte 단위로 데이터를 입출력한다.

  • characterStream은 2바이트단위로 데이터를 전송한다.

  • 유니코드는 기본단위가 2바이트로 문자를 입출력할때 CharacterStream을 쓰는것이 적절하다.

  • characterStream은 클래스명에 Reader, Writer이 들어가 있다.

BufferReader // 리소스 폴더로부터 파일을 불러온다. InputStream is = InputStreamToReaderExample.class .getClassLoader() .getResourceAsStream("file/abc.txt"); // BufferedReader -> InputStreamReader -> InputStream // try-with-resources, auto close String line; try (BufferedReader br = new BufferedReader( new InputStreamReader(is, StandardCharsets.UTF_8))) { // read line by line while ((line = br.readLine()) != null) { System.out.println(line); } } BufferedWriter try (FileWriter writer = new FileWriter("app.log"); BufferedWriter bw = new BufferedWriter(writer)) { bw.write(content); } catch (IOException e) { System.err.format("IOException: %s%n", e); }

출처 https://mkyong.com/java/how-to-write-to-file-in-java-bufferedwriter-example/

표준 스트림 (System.in, System.out, System.err)

표준 스트림

  • 콘솔을 통해 데이터입력과 콘솔로의 데이터 출력을 의미

  • 자바에서는 표준 입출력을 위해 3가지 입출력 스트림 in,out,err를 제공한다.

  • 자바앱의 실행과 동시에 자동으로 생성되기 때문에 별도의 스트림없이 사용이 가능하다.

  • System.in

    • 콘솔로 부터 데이터를 입력받는데 사용
  • System.out

    • 콘솔로 데이터를 출력하는데 사용
  • System.err

    • 콘솔로 데이터를 출력하는데 사용

파일 읽고 쓰기

BufferReader와 BufferWriter의 예제 참고.

'백선생님 온라인스터디' 카테고리의 다른 글

12주차 애노테이션  (0) 2021.02.28
10주차 멀티스레드 프로그래밍  (0) 2021.01.31
11주차 Enum  (0) 2021.01.30
9주차 - 예외 처리  (0) 2021.01.19
8주차 - 인터페이스  (0) 2021.01.16

목표

자바의 애노테이션에 대해 학습하세요.

학습할 것 (필수)

애노테이션 정의하는 방법

public @interface WhiteTest { int count(); }

  • @Override는 애노테이션 이고 Override는 애노테이션의 타입.

애노테이션의 엘리멘트

@interface WhiteTest { TestType testType(); DateTime testDate(); } @interface DateTime { String yymmdd(); }

  • 자신이 아닌 다른 애노테이션을 엘리먼트로 가질수 있다.
  • Enum 역시 가질수 있다.

@WhiteTest ( testType = TestType.First, testDate = @DateTime(yymmdd = "160101") )

  • 애노테이션의 엘리먼트들은 반환값이 있고 매개변수는 없는 추상메서드의 형태를 가진다.

  • 상속을 통해 구현하지 않아도 된다.

  • 다만 애노테이션을 적용할 때 이 요소들의 값을 다 지정해줘야 한다.

  • 요소의 이름도 같이 적어주므로 순서는 상관없다.

  • 애노테이션의 각 엘리먼트들은 기본값을 가질수 있다.

  • 배열타입의 경우 {} 를 이용해 여러개의 값을 지정할수 있다.

  • 기본값 적용시 {}로 적용할수 있다.

java.lang.annotation.Annotation

  • 모든 애노테이션의 조상은 Annotation이다.
  • 다만 애노테이션은 상속이 허용되지 않으므로 명시적으로 선언할수는 없다.

Marker Annotation

  • 값을 지정할 필요가 없는 경우 애노테이션의 필드가 하나도 없는 경우가 있다.
  • Serializable이나 Cloneable인터페이스처럼 요소가 하나도 정의되지 않은 애노테이션을
  • 마커 애노테이션이라고 한다.

애노테이션 요소의 규칙

  1. 요소의 타입은 기본형, String, enum, 애노테이션, Classs만 허용된다.
  2. () 안에 매개변수를 선언할 수 없다.
  3. 예외를 선언할 수 없다.
  4. 요소를 타입 매개변수로 정의할 수 없다.

@interface AnnoTest { int id = 100; // 상수는 선언가능 String major(int i, int j); // 에러 String minor() throws Exception; // 에러 ArrayList<T> list(); // 에러 }

@Retention

애노테이션이 유지되는 범위를 설정한다.

@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.CLASS)

  • 3가지 범위가 존재
    • SOURCE
      • 소스파일에만 존재하고 클래스파일에는 존재하지 않음.
    • CLASS
      • 클래스 파일에 존재하며 실행시 사용불가능함 기본값
    • RUNTIME
      • 클래스 파일에 존재하며 실행시에 사용가능.

Source

@Override , @SuppressWarning 처럼 컴파일러가 사용하는 애노테이션은

유지정책이 source이다. 컴파일러가 컴파일시 해당 애노테이션의 메모리는 버림.

Runtime

실행시 리플렉션을 통해 클래스 파일에 저장된 애노테이션 정보를 읽어 처리가능

@FuntionalInterface는 @Override처럼 컴파일러가 체크해주는 애노테이션이지만

실행시에도 사용되므로 유지정책이 RUNTIME으로 되어있다.

CLASS 정책과 다르게 jvm이 로딩될때 애노테이션 정보를 같이 들고있어 애노테이션 정보를 얻을수 있다.

Class

컴파일러가 애노테이션의 정보를 클래스파일에 저장할수 있게는 하지만 클래스파일이 JVM에 로딩될때는 애노테이션의 정보가 무시되어 실행시에 애노테이션 정보를 얻을수가 없다.

CLASS가 기본정책이지만 잘 사용되지 않는 이유다.

@Target

애노테이션이 적용가능한 대상을 지정한다.

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }

  • 대상타입의 종류

@Target({ ElementType.PACKAGE, // 패키지 선언시 ElementType.TYPE, // 타입 선언시 ElementType.CONSTRUCTOR, // 생성자 선언시 ElementType.FIELD, // 멤버 변수 선언시 ElementType.METHOD, // 메소드 선언시 ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시 ElementType.LOCAL_VARIABLE, // 지역 변수 선언시 ElementType.PARAMETER, // 매개 변수 선언시 ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시 ElementType.TYPE_USE // 타입 사용시 })

출처 : https://jdm.kr/blog/216

@Documented

애노테이션의 정보가 javadoc로 작성한 문서에 포함되도록 한다.

자바에서 제공하는 기본 애노테이션중에 @Override와 @SuppressWarnings를 제외하면 다 붙어 있다.

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE, TYPE_PARAMETER, TYPE_USE}) public @interface WhiteTest { int id = 100; }

@Inherited

애노테이션이 자손 클래스에 상속되도록 한다.

@Inherited가 붙으 ㄴ애노테이션을 조상클래스에 붙이면 자손클래스도 이 애노텡션이 붙은것과 같이 인식된다.

@Repeatable

보통 하나의 대상에 하나의 애노테이션을 붙이는데 @Repeatable 애노테이션이 붙은 애노테이션은 여러번 붙일수 있다.

애노테이션 프로세서

리플렉션에서 사용하는 애노테이션이 아닌 컴파일시점에 수행된다.

컴파일시점에 hook하여 용자가 정의한 어노테이션에 대한 소스코드를 분석하고 처리한다.

애노테이션 프로세서를 사용하여 코드를 생성해주는 lombok같은것이 존재함.

'백선생님 온라인스터디' 카테고리의 다른 글

13주차 I/O  (0) 2021.03.01
10주차 멀티스레드 프로그래밍  (0) 2021.01.31
11주차 Enum  (0) 2021.01.30
9주차 - 예외 처리  (0) 2021.01.19
8주차 - 인터페이스  (0) 2021.01.16
  • 가장 추천하는 방법

    • private method는 public method 에서 extract 되어 나온것이기 때문에
    • public method를 테스트하면서 자연스럽게 테스트하는것이 좋다.
  • private의 접근지시자 변경하는 방법

    • 코드의 스코프가 넓어지는것은 클래스의 스코프도 넓어지므로 바람직하지 않다.
  • PowerMock 사용

    • junit5를 지원하지 않아서 제외
  • Refelect 사용

    • 비용이 많이든다.

    • 테스트코드가 부서지기 쉬워진다. ( 바뀌는 경우가 많다는 의미인것 같다. )

    • 부서지기 쉬운 코드란 무엇일까 ?

    1. 결합도가 높으면 조금만 수정해도 테스트가 금방 깨지는 경우
    2. 비지니스 로직 변경으로 인한 테스트 영향

잘짜야진 코드는 private 메서드를 테스트할 일이 벌어지면 안된다

 

출처

blog.benelog.net/2685835.html

  • 높은 추상화 수준, 즉 시스템 수준에서도 깨끗함을 유지하는 방법을 살펴본다.

시스템 제작과 시스템 사용을 분리하라.

  • 제작 ( Construction ) 과 사용 ( Use ) 는 아주 다르다.

  • 소프트웨어 시스템은 ( 애플리케이션 객체를 제작하고 의존성을 서로 연결하는 ) 준비 과정과 ( 준비과정 이후에 이어지는) 런티암 로직을 분리해야한다.

  • 시작 단계는 모든 애플리케이션이 풀어야할 관심사다.

  • 관심사 분리는 가장 오래되고 가장 중요한 설계 기법 중 하나

  • 초기화 지연 ( 레이지 이니셜라이제이션 ) 혹은 계산 지연 ( 레이지 에볼루션 ) 기법

    • 객체 null 체크후 null 일 경우만 객체 생성함.
    • 장점은 있다 null 을 반환하지 않는다. 객체 생성시간이 늦어져서 어플리케이션을 시작하는 시간이 빨라진다.
    • 단점
      • 메서드가 생성자와 , 생성자 인수에 의존하게 된다.
      • 테스트도 문제
        • 객체가 무거운 객체라면 단위 테스트에서 메서드를 호출하기전에 적절한 테스트 전용 객체 ( Mock Object) 를 할당해야 한다.
      • 또한 일반 런타임 로직에다 객체 생성 로직을 섞어놓은 탓에 모든 실행경로도 테스트해야 한다. ( 객체가 null인 경로와 null이 아닌 경로 )
      • 책임이 둘 ( null 일 경우 , null이 아닐 경우) 일 경우는 단일책임원칙에 위배된다.
    • 저런 기법으로 모듈성을 깨지 말자.
    • 설정 논리는 일반 실행 논리와 분리해야 모듈성이 높아진다.

메인 분리

  • 생성과 사용을 분리하는 방법중 하나
  • 생성 코드는 main이나 main이 호출하는 모듈로 옮기고 사용은 다른쪽에서한다.
  • 이러면 다른쪽은 main을 보지 않는다. ( 생성 과정을 모른다 )

팩토리

  • 객체가 생성되는 시점을 애플리케이션이 결정
  • Abstract factory 패턴을 사용

 

  • 구체적인 객체 생성 방법은 몰라도 생성되는 시점을 완벽하게 통제.
  • 생성자 인수역시 넘길수 있음.

의존성 주입

  • 사용과 제작을 분리하는 강력한 메커니즘 중 하나
  • 스프링의 IOC 그것이다.
  • 스프링은 특수 컨테이너를 사용한다.

확장

  • 점차 기능이 늘어남.

  • 처음부터 올바르게 시스템을 만들 수 있다는 말은 미신이다.

  • 새로운 스토리에 맞춰 시스템을 조정하고 확장하면 된다.

  • 이것이 반복적이고 점진적인 애자일 방식의 핵심

  • TDD와 리팩토링으로 인해 얻어지는 깨끗한 코드는 코드 수준에서 시스템을 조정하고 확장하기 쉽게 만든다.

  • 시스템과 소프트웨어는 다르다. 소프트웨어는 관심사를 적절히 분리해 관리 한다면 점진적으로 발전할수 있다.

  • 컨테이너와 비지니스 로직이 강하게 결합되어 있다면 독자적인 단위테스트가 어렵고

  • 클래스를 생성할때는 컨테이너에서 파생해야 되며 컨테이너가 요구하는 다양한 생명주기 메서드도 제공해야 한다.

  • DTO는 구조체며 메소드가 없다.

횡단 관심사

  • 영속성과 같은 관심사는 애플리케이션의 자연스러운 객체 경계를 넘나드는 경향이 있다.

  • 모듈화한 영속성 프레임워크와 모듈화한 도메인논리가 세밀한 단위로 겹친다.

  • AOP에서 관점이라는 모듈구성개념 은 특정관심사를 지원하려면 시스템에서 특징 지점들이 동작하는 방식을 일관성 있게 바꿔야 한다.

  • AOP의 진정한 가치는 시스템 동작을 간결하고 모듈화된 방식으로 명시하는 능력

자바 프록시

  • 개별 객체나 클래스에서 메서드 호출을 감싸는 경우가 좋은 예.
  • jdk에서는 동적프록시는 api만 제공
  • 클래스 프록시를 사용하려면 cglib , asm ,javassis등과 같은 바이트코드 처리 라이브러리가 필요
  • 단순한 예제에도 코드가 많아지며 제법 복잡해진다.
  • 프록시를 사용하면 깨끗한 코드를 작성하기 어렵다
  • 프록시는 AOP처럼 시스템 단위로 실행지점을 명시할수도 없다.
  • ( 프록시 패턴 )

순수 자바 AOP 프레임워크

  • 의존성이 적으면 테스트가 쉬워진다.
  • 순수자바 AOP 프레임워크는 스프링관련된 코드가 없어서 스프링과 사실상 독립적이다.
  • 아까 위에 나왔던 컨테이너와 비지니스로직의 결합이 없어진다.
  • ( 이게 자바 프록시보다 낫다는 말 )

AspecJ

  • 관심사를 관점으로 분리하는 가장 강력한 도구
  • 언어차원에서 관점을 모듈화 구성으로 지원하는 자바 언어 확장
  • 관점을 분리하는 강력하고 풍부한 도구 집합을 제공하지만 새 도구를 사용하고
  • 새문법과 사용법을 익혀야하는 단점이 존재.

테스트 주도 시스템 아키텍처 구축

  • 관점 ( 혹은 유사한 개념으로 ) 관심사를 분리하는 방식은 그 위력이 막강하다.

  • 어플리케이션 도메인 논리를 POJO로 작성할 수 있다면.

  • 즉 코드 수준에서 아키텍처 관심사를 분리할 수 있다면 진정한 테스트 주도 아키텍처 구축이 가능해진다.

  • 세계최대의 웹사이트들은 고도의 자료 캐싱, 보안 , 가상화등을 이용해 아주 높은 가용성과 성능을 효율적이고도 유연하게 달성했다.

  • 설계가 최대한 분리되어 각 추상화 수준과 범위에서 코드가 적당히 단순하기 때문이다.

  • 최선의 시스템 구조는 각기 POJO객체로 구현되는 모듈화된 관심사 영역 ( 도메인 ) 으로 구성한다.

  • 이렇게 서로다른 영역은 해당 영역코드에 최소한의 영향을 미치는 관점이나 유사한 도구를 사용해 봉합한다.

  • 이런 구조 역시 코드와 마찬가지로 테스트 주도 기법을 적용할 수 있다.

의사결정을 최적화 하라

  • 최대한 정보를 모아 가능한 마지막 순간까지 결정을 미뤄라
  • 관심사를 모듈로 분리한 POJO 시스템은 기민함을 제공한다.
  • 이런 기민함 덕택에 최신 정보에 기반해 최선의 시점에 최적의 결정을 내리기가 쉬워진다.
  • 또한 결정의 복잡성도 줄어든다.
  • ( 뭔가 만들면서 선택지를 점점 제거해간다는 느낌 )

명백한 가치가 있을때 표준을 현명하게 사용하라

  • 너무 과장되게 포장된 표준은 사용하지 않는 것이 좋다.
  • 표준을 사용하면 아이디어와 컴포넌트를 재사용하기 쉽고, 적절한 경험을 가진 사람을 구하기 쉬우며, 좋은 아이디어를 캡슐화하기 쉽고, 컴포넌트를 엮기 쉽다.
  • 하지만 표준을 만드는 시간이 너무 오래걸릴 수 있다.

시스템은 도메인 특화 언어가 필요하다.

  • DSL은 간단한 스크립트 언어나 표준 언어로 구현한 API

  • 좋은 DSL은 도메인 개념과 그 개념을 구현한 코드 사이에 존재하는 의사소통의 간극을 줄여준다.

  • 애자일 기법이 팀과 프로젝트 이해관계자 사이에 의사소통 간극을 줄여주듯이.

  • 효과적으로 사용한다면 DSL은 추상화 수준을 코드 관용구나 디자인 패턴 이상으로 끌어올린다.

  • 그래서 개발자가 적절한 추상화 수준에서 코드 의도를 표현할 수 있다.

  • DSL을 사용하면 고차원 정책에서 저차원 세부사항에 이르기까지 모든 추상화 수준과 모든 도메인을 POJO로 표현할 수 있다.

결론

  • 시스템 역시 깨끗해야 한다.

  • ( 보기 좋아야 된다는 말 , solid 원칙을 지켜야 된다는 말)

  • 도메인 논리가 흐려지면 제품 품질이 떨어진다.

  • 기민성도 떨어진다.

  • 기민성이 떨어지면 생산성이 낮아져 TDD가 제공하는 이점도 사라진다.

  • 모든 추상화 단계에서 의도는 명확히 표현해야 한다.

  • POJO를 이용해 관심사를 분리하자

  • ( Entity를 포조로 )

  • 시스템을 설계하든 개별 모듈을 설계하든, 실제로 돌아가는 가장 단순한 수단을 사용해야 한다는 사실을 명심하자.

'book > 클린코드' 카테고리의 다른 글

13장 동시성  (0) 2021.01.05
12장 - 창발성  (0) 2021.01.03
10장 - 클래스  (0) 2020.12.22
9장 - 단위 테스트  (0) 2020.12.02
8장 - 경계  (0) 2020.11.30

static method 모킹하는 방법

 

MockedStatic<TestClass> mock = mockStatic(TestClass.class);
1. TestClass클래스 타입으로 Mocking 해준다.

mock.when(() -> TestClass.getMethod).thenReturn(null);
2. 모킹한 객체로 when 사용시 직접 static method를 호출
  • 대신 의존성으로 mockito-inline 이 필요함.

 

WebMVCTest vs AutoconfigureWebMvc vs SpringBootTest 차이점

 

  • SpringBootTest는 실제와 마찬가지로 어플리케이션을 시작하며 테스트를 한다.
  • webEnvironment = WebEnvironment.RANDOM_PORT 사용시
  • 임의의 포트를 사용한다.
  • 임의의 포트를 사용하는 이유는 테스트할때 충돌을 방지하기 위함.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {

	@LocalServerPort
	private int port;

	@Autowired
	private TestRestTemplate restTemplate;

	@Test
	public void greetingShouldReturnDefaultMessage() throws Exception {
		assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
				String.class)).contains("Hello, World");
	}
}

 

  • AutoconfigureWebMvc는 서버를 시작하지 않고 아래 계층만 테스트
  • Spring은 들어오는 HTTP 요청을 처리하고 이를 컨트롤러에 전달
  • 아래와 같이 테스트를 한다면 전체 스택이 사용되며 코드는
  • 실제 HTTP 요청을 처리하는 것과 똑같은 방식으로 호출되지만 서버시작 비용이 없다.
@SpringBootTest
@AutoConfigureMockMvc
public class TestingWebApplicationTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")));
	}
}
  • WebMvcTest는 MVC쪽만 따로 테스트 할때 사용
    • 테스트 범위를 웹레이어쪽으로 스코프를 지정한다는 의미

 

@WebMvcTest
public class WebLayerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")));
	}
}

 

 

 

```
1. when().thenReturn() 은 when에 method를 담는다.
	 
	 doReturn().when()은 when에 mocking한 객체를 넣고 when에서 모킹한 객체의 메소드를 
	 직접 호출해줘야한다.
  
	 예시)
	 when(test.test()).thenReturn(null);
	 doReturn(null).when(test).test();

2. when().thenReturn()은 mock되기 전에 실제 메소드를 호출하고 
	 thenReturn으로 return 값을 지정하고 호출시 이 return 값이 나오게 한다.

	 doReturn().when()은 실제 메소드를 호출하지 않고
	 doReturn으로 return 값을 지정하고 호출시 이 return 값이 나오게 한다.
  
```

 

쿠버네티스는 기본적으로 API 요청이 오면 위와 같은 순서로 작업을 처리합니다.

그 중간에 Mutating admission 부분에 Webhook을

이용한 pod injection 예제로 pod injection 하는 부분을 알아보고

예제가 오래된 관계로 v1beta1인 예제를 v1으로 업데이트하는 부분까지 할 예정입니다.

Webhook의 작동 방식

  • 출처 mutating webhook tutorial 깃헙

MutatingWebhook은 요청이 etcd에 등록되기 전에 MutatingWebhookConfiguration 의 rule과

일치하는 request를 intercept 합니다.

MutatingWebhook은 webhook 서버에 승인을 요청하여

mutation ( injection이라던지 수정같은 작업) 을 수행합니다.

( webhook 서버는 API를 준수하는 일반 http 서버)

예제

https://github.com/morvencao/kube-mutating-webhook-tutorial

이 튜토리얼은 nginx 사이드카 컨테이너를 파드에 injection 하는 MutatingAdmissionWebhook을 빌드하고 배포하는 방법을 보여줍니다.

조건

  • git
  • go version v1.12+
  • docker version 17.03+
  • kubectl version v1.11.3+
  • kubernetes v1.11.3+ cluster with admissionregistration.k8s.io/v1beta1 API enabled
    • 처음은 v1beta1로 작업하고 추후에 v1으로 수정할 예정.

admissionregistration.k8s.io/v1beta1 API enabled 확인 방법

kubectl api-versions | grep admissionregistration.k8s.io

결과는 아래와 같이 나와야합니다.

admissionregistration.k8s.io/v1 admissionregistration.k8s.io/v1beta1

빌드

  1. Build binary

# make build

  1. Build docker image

# make build-image

  1. push docker image

# make push-image

deploy

  1. sidecar injector webhook이 배포될 sidecar-injector  namespace를 생성

# kubectl create ns sidecar-injector

  1. sidecar injector deployment에 사용될 secret 생성

    (이 시크릿은 cert와 key 정보를 가지고 있다):

# ./deployment/webhook-create-signed-cert.sh \\ --service sidecar-injector-webhook-svc \\ --secret sidecar-injector-webhook-certs \\ --namespace sidecar-injector

  1. 위에서 생성한 caBundle 값을 mutatingwebhook.yaml 에 패치를 한 결과를 mutatingwebhook-ca-bundle.yaml로 생성한다.:

# cat deployment/mutatingwebhook.yaml | \\ deployment/webhook-patch-ca-bundle.sh > \\ deployment/mutatingwebhook-ca-bundle.yaml

  1. 리소스들을 배포한다.:

# kubectl create -f deployment/nginxconfigmap.yaml # kubectl create -f deployment/configmap.yaml # kubectl create -f deployment/deployment.yaml # kubectl create -f deployment/service.yaml # kubectl create -f deployment/mutatingwebhook-ca-bundle.yaml

확인

  1. sidecar injector webhook이 잘 배포됬나 확인한다.

# kubectl -n sidecar-injector get pod NAME READY STATUS RESTARTS AGE sidecar-injector-webhook-deployment-7c8bc5f4c9-28c84 1/1 Running 0 30s # kubectl -n sidecar-injector get deploy NAME READY UP-TO-DATE AVAILABLE AGE sidecar-injector-webhook-deployment 1/1 1 1 67s

  1. injection namespace 생성 그리고  sidecar-injector=enabled로 라벨링 한다.

# kubectl create ns injection # kubectl label namespace injection sidecar-injection=enabled # kubectl get namespace -L sidecar-injection NAME STATUS AGE SIDECAR-INJECTION default Active 26m injection Active 13s enabled kube-public Active 26m kube-system Active 26m sidecar-injector Active 17m

  1. alpine app을 예제로 쿠버네티스 클러스터에 배포한다.

# kubectl run alpine --image=alpine --restart=Never -n injection --overrides='{"apiVersion":"v1","metadata":{"annotations":{"sidecar-injector-webhook.morven.me/inject":"yes"}}}' --command -- sleep infinity

  1. sidecar container가 injected 됬는지 확인한다.

# kubectl get pod NAME READY STATUS RESTARTS AGE alpine 2/2 Running 0 1m # kubectl -n injection get pod alpine -o jsonpath="{.spec.containers[*].name}" alpine sidecar-nginx

admissionregistration.k8s.io/v1beta1 를 v1으로 업그레이드 하기

webhook의 작동방식부분에서 mutatingadmissionwebhook 부분과 webhook 서버를 보면

admissionReview를 요청하고 응답받는것을 보실수 있습니다.

v1beta1버전에서는 요청에 대한 응답시 kind와 apiversion 을 넣지 않아도 응답을 정상으로 판단 했습니다.

하지만 v1버전에서는 요청에 대한 응답시 kind와 apiversion 버전을 추가시켜줘야 합니다.

https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/e72f4d5e501d61f22023efb26e16cda35a335941/cmd/webhook.go#L296

위 빈 라인에 아래와 같이 요청으로 들어온 api version 과 kind , uid를 그대로 review 응답에 넣어줍니다.

admissionReview.Response.UID = ar.Request.UID admissionReview.APIVersion = ar.APIVersion admissionReview.Kind = ar.Kind

위와 같이 할 경우 v1beta1로 요청이 들어왔을 경우 그대로 v1beta1로 응답하며

v1로 요청이 왔을 경우 그대로 v1로 응답이 갑니다.

그 후 deployment/mutatingwebhook.yaml 을 수정합니다.

apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: name: sidecar-injector-webhook-cfg labels: app: sidecar-injector webhooks: - name: sidecar-injector.morven.me clientConfig: service: name: sidecar-injector-webhook-svc namespace: sidecar-injector path: "/mutate" caBundle: ${CA_BUNDLE} rules: - operations: ["CREATE", "UPDATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] namespaceSelector: matchLabels: sidecar-injection: enabled admissionReviewVersions: ["v1"] sideEffects: None

위와 같이 수정하면 v1로 업데이트 완료입니다.

Injection 수정하기

webhook 서버에서 injection 처리 부분은 webhook.go 파일 입니다.

https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/e72f4d5e501d61f22023efb26e16cda35a335941/cmd/webhook.go#L234

webhook.go 안에 createPatch 부분이 injection 할 내용을 생성하는 부분입니다.

이 부분으로 생성 후 apiserver로 다시 요청을 응답하는 작업이 이루어집니다.

이때 이 부분도 같이 response에 담겨서 응답하게 됩니다.

createPatch 부분을 참고하셔서 수정하시면 자신만의 injection 을 만드실수 있습니다.

createPath의 추가적인 참고는 하단의 tumblr github을 참고해주세요.

https://github.com/tumblr/k8s-sidecar-injector/blob/2478960ca6686798d26319956354b48e37108c29/pkg/server/webhook.go#L448

정리

mutating webhook은 추가적인 변경이나, 검증을 쉽게 제공해줘서 go를 처음접하는 저로써도

손쉽게 수정하여 저만의 injection 을 만들수 있었습니다.

다만 v1beta1에서 v1 patch부분이나 인증서 관련 부분은 삽질이 좀 필요해서 글로 남기게 됬습니다.

인증서 부분과 webhook 소스내부 설명은 다음글에 남겨보도록 하겠습니다.

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

왜 apiversion이 변경되어 있을까  (0) 2021.06.11
컨테이너 런타임  (0) 2021.03.07
쿠버네티스의 클러스터 구성  (0) 2021.01.28
k8s 에서 spring boot jib image command 수정  (0) 2020.12.24
클러스터 구성  (0) 2020.09.13

+ Recent posts