diff --git a/.github/workflows/copy-files-from-til.yml b/.github/workflows/copy-files-from-til.yml index 12cc9b34..07ca8ec9 100644 --- a/.github/workflows/copy-files-from-til.yml +++ b/.github/workflows/copy-files-from-til.yml @@ -55,10 +55,13 @@ jobs: rm ${{ env.destination }}/README.md rm -rf TIL + - name: Set current date as env + run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + - name: Commit and push uses: stefanzweifel/git-auto-commit-action@v4 with: - commit_message: "Copy files from TIL" + commit_message: "${{ env.date }} TIL 문서 복사" repository: . branch: main file_pattern: '.' @@ -66,6 +69,5 @@ jobs: commit_options: '--no-verify --signoff' commit_user_name: rlaisqls commit_user_email: rlaisqls@gmail.com - commit_author: rlaisqls env: GITHUB_TOKEN: ${{ secrets.PAT }} diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Apache/Spark.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Apache/Spark.md" new file mode 100644 index 00000000..b3415aea --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Apache/Spark.md" @@ -0,0 +1,19 @@ +--- +title: 'Spark' +lastUpdated: '2024-03-02' +--- + +Apache Spark는 빅 데이터 워크로드에 주로 사용되는 오픈 소스 분산 처리 시스템이다. Apache Spark는 빠른 성능을 위해 인 메모리 캐싱과 최적화된 실행을 사용하며, 일반 배치 처리, 스트리밍 분석, 기계 학습, 그래프 데이터베이스 및 임시 쿼리를 지원한다. + +## 장점 + +- **빠른 성능:** Apache Spark는 방향성 비순환 그래프(DAG) 실행 엔진을 사용함으로써 데이터 변환에 대한 효율적인 쿼리 계획을 생성할 수 있다. 또한, Apache Spark는 입력, 출력 및 중간 데이터를 인 메모리에 RDD(Resilient Distributed Dataset)로 저장하므로, I/O 비용 없이 반복 또는 대화형 워크로드를 빠르게 처리하고 성능을 높일 수 있다. + +- **애플리케이션을 신속하게 개발:** Apache Spark는 기본적으로 Java, Scala 및 Python 등 애플리케이션을 구축할 수 있는 다양한 언어를 제공한다. 또한, Spark SQL 모듈을 사용하여 SQL 또는 HiveQL 쿼리를 Apache Spark에 제출할 수 있다. 애플리케이션을 실행하는 것 외에도, Apache Spark API를 Python과 대화식으로 사용하거나 클러스터의 Apache Spark 셸에서 Scala를 직접 사용할 수 있다. Zeppelin을 사용하여 데이터 탐색과 시각화를 위한 대화형 협업 노트북을 생성할 수도 있다. + +- **다양한 워크플로 생성:** Apache Spark에는 기계 학습(MLlib), 스트림 처리(Spark Streaming) 및 그래프 처리(GraphX)용 애플리케이션을 구축하는 데 도움이 되는 몇 가지 라이브러리가 포함되어 있다. 이러한 라이브러리는 Apache Spark 에코시스템과 긴밀하게 통합되며, 다양한 사용 사례를 해결하는 데 바로 활용할 수 있다. + +--- +참고 +- https://spark.apache.org/ +- https://cloud.google.com/learn/what-is-apache-spark?hl=ko \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/CustomAnnotation.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/CustomAnnotation.md" new file mode 100644 index 00000000..e141c713 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/CustomAnnotation.md" @@ -0,0 +1,125 @@ +--- +title: 'CustomAnnotation' +lastUpdated: '2024-03-02' +--- + +개발시 프레임워크를 사용하다보면 여러 Annotation들을 볼 수 있다. (ex: `@NotNull`, `@Controller`, `@Data` 등) + +이러한 어노테이션들은 라이브러리에 미리 정의되어있는 것인데, 우리가 직접 이 어노테이션을 생성하여 AOP로 기능을 부여해줄 수 있다. + +## Annotation class 정의 + +Annotation을 정의하고 싶다면, `@interface`를 클래스 키위드 뒤에 붙이면 된다. + +```java +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MyAnnotation { + String param() default "paramsPost.content"; + Class checkClazz() default ParamsPost.class; +} +``` + +코틀린에선 아래와 같이 작성할 수 있다. + +```kotlin +@Target(AnnotationTarget.METHOD) +@Retention(AnnotationRetention.RUNTIME) +annotation class MyAnnotation() { + val param: String = "paramPost.content", + val checkClazz: KClass<*> = ParamsPost.class +} +``` + +## Annotation을 위한 Annotation + +Annotation을 정의할떄, Annotation의 성질을 정해줄 수 있는 Annotation들에 대해 알아보자. + +## `@Retention` + +해당 Annotation의 정보를 어느 범위까지 유지할 것인지를 설정한다. 즉, Annotation을 언제까지 작동시킬 것인지를 정하는 것이다. + +#### RetentionPolicy의 종류 + +- **SOURCE :**
+ Annotation을 사실상 주석처럼 사용하는 것이다. 컴파일러가 컴파일할때 해당 어노테이션의 메모리를 버린다. + +- **CLASS** :
+ 컴파일러가 컴파일에서는 Annotation의 메모리를 가져가지만 실질적으로 런타임시에는 사라지게된다. 런타임시에 사라진다는 것은 리플렉션으로 선언된 Annotation 데이터를 가져올 수 없다는 뜻이다. (Default).
+ Lombok의 `@Getter`, `@Setter`처럼 컴파일시 바이트코드를 생성한 후 사라지는 경우, 이 전략을 사용한다. + +- **RUNTIME** :
+ Annotation을 런타임시에까지 사용할 수 있다. JVM이 자바 바이트코드가 담긴 class 파일에서 런타임환경을 구성하고 런타임을 종료할 때까지 메모리에 살아있다.
+ 스프링에서 빈이나 Transaction 등록하는 것과 같은 동작은 모두 어플리케이션이 시작한 후에 실행되기 때문에, 그와 관련된 어노테이션은 이 전략을 사용한다. + +```java +/** + * Contains the list of possible annotation's retentions. + * + * Determines how an annotation is stored in binary output. + */ +public enum class AnnotationRetention { + /** Annotation isn't stored in binary output */ + SOURCE, + /** Annotation is stored in binary output, but invisible for reflection */ + BINARY, + /** Annotation is stored in binary output and visible for reflection (default retention) */ + RUNTIME +} +``` + +## `@Target` + +해당 어노테이션이 사용되는 위치를 결정한다. + +List로 여러가지를 선택할 수 있으며, 선언한 어노테이션이 이 어노테이션으로 명시하지 않은 곳에 붙어있을 경우 컴파일시 에러가 발생한다. + +이름만 보면 이해할 수 있는 것들이 많기 때문에 설명은 생략하고 ENUM 파일을 읽어보자. + +```java +public enum class AnnotationTarget { + /** Class, interface or object, annotation class is also included */ + CLASS, + /** Annotation class only */ + ANNOTATION_CLASS, + /** Generic type parameter */ + TYPE_PARAMETER, + /** Property */ + PROPERTY, + /** Field, including property's backing field */ + FIELD, + /** Local variable */ + LOCAL_VARIABLE, + /** Value parameter of a function or a constructor */ + VALUE_PARAMETER, + /** Constructor only (primary or secondary) */ + CONSTRUCTOR, + /** Function (constructors are not included) */ + FUNCTION, + /** Property getter only */ + PROPERTY_GETTER, + /** Property setter only */ + PROPERTY_SETTER, + /** Type usage */ + TYPE, + /** Any expression */ + EXPRESSION, + /** File */ + FILE, + /** Type alias */ + @SinceKotlin("1.1") + TYPEALIAS +} +``` + +## `@Inherited` + +어노테이션이 붙은 클래스 뿐만 아니라 그 클래스를 상속받은 하위 클래스까지 모두 전파하도록 하는 어노테이션이다. + +## `@Repeatable` + +어노테이션을 여러번 중복해서 붙여도 괜찮다는 의미이다. + +## `@Documented` + +JavaDoc 생성 시 Document에 포함되도록하는 어노테이션이다. diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/FineGrained\354\231\200\342\200\205CoarseGrained.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/FineGrained\354\231\200\342\200\205CoarseGrained.md" new file mode 100644 index 00000000..2816a66b --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/FineGrained\354\231\200\342\200\205CoarseGrained.md" @@ -0,0 +1,21 @@ +--- +title: 'FineGrained와 CoarseGrained' +lastUpdated: '2023-12-11' +--- + + +Fine-grained는 사전적으로 "결이 고운", "미세한"이라는 의미를 가지고, Coarse-grained는 "결이 거친", "조잡한"의 의미를 가진다. Grain은 곡식 혹은 낱알을 뜻하는데, 알갱이가 거칠고 큼직큼직헌지, 곱고 세밀한지에 따라서 Coarse와 Fine으로 나누어 표현한다고 이해할 수 있다. + +# Fine-Grained +- 하나의 작업을 작은 단위의 프로세스로 나눈 뒤, 다수의 호출을 통해, 작업 결과를 생성해내는 방식 +- 예를 들어, `Do`라는 동작이 있다면 해당 함수를 `First_Do()`, `Second_Do()`로 나누어 작업 결과를 생성해냄 +- 다양한 **"Flexible System"** 상에서 유용하게 쓰일 수 있음 + +# Coarse-Grained +- 하나의 작업을 큰 단위의 프로세스로 나눈 뒤, "Single Call" 을 통해, 작업 결과를 생성해내는 방식 +- 예를 들어, `Do` 라는 동작이 있다면 단순히, `Do()`를 호출해 작업 결과를 생성해내는 방식 +- **"Distributed System"** 상에서 유용하게 쓰일 수 있음 + +--- +참고 +- https://coderanch.com/t/99845/engineering/Coarse-grained-fine-grained-objects diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/GTM.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/GTM.md" new file mode 100644 index 00000000..a8f47783 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/GTM.md" @@ -0,0 +1,73 @@ +--- +title: 'GTM' +lastUpdated: '2024-03-02' +--- + +- GTM은 ‘코드추적을 용이하게 해주는 도구이자 태그 관리 툴’이다. +- 기존에는 Google Analytics(GA), 페이스북 픽셀 등과 같은 트래킹 툴을 각각 설정해야 했다. 하지만 GTM은 트래킹을 위한 코드의 삽입, 수정, 삭제 모두를 효율적으로 관리할 수 있게 해준다. +- GTM 코드만 삽입하면, 새로운 마케팅 툴을 여러 개 추가하더라도 추가적인 코드작업 없이 손쉽게 설치 할 수 있다. + +## 핵심 용어 + +image + +### **태그** +- 데이터를 어디로 전달할 지를 정의한다. +- 데이터를 추적해 활용하는 트레이싱 툴을 등록하면 된다. + +image + + +### **트리거** + +- 어떤 경우에 실행할 지를 정의한다. 트리거 조건을 충족했을 경우 데이터가 전송된다. +- 트리거 유형은 사용자의 창 로드, 클릭, 스크롤 등이 될 수 있다. + +image + +### **변수** + +- 어떤 데이터, 값을 전달할 지를 정의한다. +- 태그 실행시마다 변경되는 값을 관리하는 데 사용된다. + +image + +## 효과 + +1. 다양한 툴 삽입가능 + - GTM은 우선 한 번 웹사이트에 심어 놓으면 개발자의 도움 없이도 다양한 툴을 테스트해볼 수 있다. +2. 다양한 데이터를 간단하게 사용 + - GA에서 수집할 수 있는 데이터 외에 다른 데이터들이 궁금해질 때 GTM을 통해 간단히 수집할 수 있다. +3. 손쉽게 반복적인 TEST 가능 + - GTM에서는 버전 관리 기능을 통해 새로운 데이터를 수집하거나 설치한 툴을 여러 번 테스트해볼 수 있고, 결과가 마음에 들지 않으면 이전으로 되돌릴 수도 있다. 즉, 원하는 정보를 얻기 위해 다양한 실험을 반복해볼 수 있다. + +## 데이터 레이어 + +- 웹사이트에서 GTM 컨테이너로 정보를 전달할 때 사용되는 자바스크립트 개체를 데이터 레이어라고 부른다. +- 홈페이지와 GTM 간의 데이터 송수신을 위한 매개체라고 할 수 있다. + +```js +// 초기화 +window.dataLayer = window.dataLayer || []; + +// 데이터 추가 +window.dataLayer.push({ + event: "이벤트명", + 변수명1: "값1", + 변수명2: "값2", + 변수명3: "값3" +}); +``` + +GTM에서는 데이터 레이어를 아래와 같은 절차로 처리한다. + +1. GTM 컨테이너가 로드됨. +2. 데이터레이어 푸시 메시지 중 처음 수신한 메시지를 가장 먼저 처리함. +3. 수신한 메시지에 이벤트가 있을 경우 해당 이벤트를 트리거로 하는 모든 태그를 실행함. +4. 메시지 처리 및 태그 실행이 완료되면 다음 수신한 메시지를 한 번에 하나씩 처리함. (선입선출) + +--- +참고 +- https://marketingplatform.google.com/intl/ko/about/tag-manager/ +- https://support.google.com/tagmanager/answer/6164391?hl=ko +- https://developers.google.com/tag-platform/tag-manager/datalayer \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/DependencyHandler.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/DependencyHandler.md" new file mode 100644 index 00000000..90e83b81 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/DependencyHandler.md" @@ -0,0 +1,69 @@ +--- +title: 'DependencyHandler' +lastUpdated: '2024-03-02' +--- + +[DependencyHandler](https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/dsl/DependencyHandler.html)는 Gradle의 종속성(Dependencies)을 생성해주는 인터페이스이다. + +```kotlin +public interface DependencyHandler extends ExtensionAware { + + @Nullable + Dependency add(String configurationName, Object dependencyNotation); + + Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure); + + void addProvider(String configurationName, Provider dependencyNotation, Action configuration); + + void addProvider(String configurationName, Provider dependencyNotation); + + void addProviderConvertible(String configurationName, ProviderConvertible dependencyNotation, Action configuration); + ... +} +``` + +그중 Dependencies를 생성할때 일반적으로 쓰이는 것은 맨 위에 있는 `add` 메서드이다. dependencies 부분에 implement를 추가하면 저 메서드로 자동으로 연결되어서 실행된다. + +(kotlinDSL을 사용하면 아래와 같은 코드로 명시적으로 이어주는 것 같다.) + +```kotlin +fun DependencyHandler.`implementation`(dependencyNotation: Any): Dependency? = + add("implementation", dependencyNotation) +``` + +dependencies에서 그냥 add 메서드를 바로 실행해줘도 종속성이 정상적으로 등록된다. + +```kotlin +dependencies { + add("implementation", "org.jetbrains.kotlin:kotlin-reflect") +} +``` + +--- + +그리고 신기하게도 + +```kotlin +fun DependencyHandler.implementationDependencies(libraries: List>) { + libraries.forEach { (dependency, type) -> + add(type.originalName, dependency) + } +} +``` + +이렇게 코드를 작성하면 + +```kotlin +dependencies { + implementationDependencies( + listOf( + "org.jetbrains.kotlin:kotlin-reflect" to IMPLEMENTATION, + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" to IMPLEMENTATION + ) + ) +} +``` + +이런식으로 호출할 수가 있는데, 이를 이용하여 아래 링크의 코드와 같이 buildSrc를 정의할 수도 있다. + +https://github.com/rlaisqls/HelloWorld/tree/master/buildSrc \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/Git\342\200\205action\342\200\205gradle\342\200\205caching.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/Git\342\200\205action\342\200\205gradle\342\200\205caching.md" new file mode 100644 index 00000000..08ccd565 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/Git\342\200\205action\342\200\205gradle\342\200\205caching.md" @@ -0,0 +1,47 @@ +--- +title: 'Git action gradle caching' +lastUpdated: '2024-03-02' +--- + +Gradle은 빌드할때 의존성 패키지들을 모두 다운받는다. 이때 Gradle은 빌드 시간과 네트워크 통신을 줄이기 위해 의존성 패키지를 캐싱해서 재사용하는 방법을 사용한다. + +하지만 Github Actions의 workflow는 매 실행하다 새로운 환경을 구축하고, 매번 새롭게 의존성 패키지들을 가지고 와야 한다. 이는 전체 빌드 시간의 증가로 이어진다. 빌드 시간의 단축을 위해서 우리는 Github Actions의 actions/cache를 사용해서 gradle의 의존성을 캐싱할 수 있다. + +```yml +- name: Gradle Caching + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + +- name: Grant execute permission for gradlew + run: chmod +x ./gradlew + shell: bash + +- name: Build with Gradle + run: ./gradlew build + shell: bash +``` + +- path : 캐시의 저장과 복원에 사용되는 runner 내 파일 경로이다. +- key : 캐시를 저장, 복원에 사용되는 키. 여러 값들을 조합해서 512자 제한으로 생성할 수 있다. +- restore-keys : 내가 설정한 key로 cache miss가 발생할때 사용할 수 있는 후보군 키들이다. + +gradle에 정의된 git action을 활용하면 아래와 같이 사용할 수 있다. + +`gradle-build-action`의 용도와 의미에 대해선 추후 별도의 문서로 작성할 예정이다. + +```yml + - name: Build Gradle + uses: gradle/gradle-build-action@v2 + with: + arguments: | + build + --build-cache + --no-daemon +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/Gradle\342\200\205LifeCycle.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/Gradle\342\200\205LifeCycle.md" new file mode 100644 index 00000000..4778561a --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/Gradle\342\200\205LifeCycle.md" @@ -0,0 +1,71 @@ +--- +title: 'Gradle LifeCycle' +lastUpdated: '2024-03-02' +--- + +Gradle은 의존성 기반의 프로그래밍용 언어이다. 이 말은 태스크를 정의하고 또한 태스크들 사이의 의존성도 정의 할 수 있다는 뜻이다. + +Gradle은 태스크들이 의존성의 순서에 따라 실행되고, 오직 한 번만 실행될 것임을 보장한다. + +Gradle은 태스크를 실행하기 전에 완전한 의존성 그래프를 구축한다. + +## 빌드 단계 + +Gradle 빌드는 3단계로 구분된다. + +```md +## Initialization +Gradle supports single and multi-project builds. During the initialization phase, Gradle determines which projects are going to take part in the build, and creates a Project instance for each of these projects. + +## Configuration +During this phase the project objects are configured. The build scripts of all projects which are part of the build are executed. + +## Execution +Gradle determines the subset of the tasks, created and configured during the configuration phase, to be executed. The subset is determined by the task name arguments passed to the gradle command and the current directory. Gradle then executes each of the selected tasks. +``` + +- **초기화 (Initialization)** : 초기화 단계에서는 어느 프로젝트를 빌드하는지 결정하고 각각에 대해 Project 객체를 생성한다. +- **구성 (Configuration)** : 빌드에 속하는 **모든 프로젝트의 빌드 스크립트**를 실행한다. 이를 통해 프로젝트 객체를 구성한다. +- **실행 (Execution)** : 구성 단계에서 생성하고 설정된 태스크 중에 실행할 것을 결정한다. 이 때 gradle 명령행에 인자로 지정한 태스크 이름과 현재 디렉토리를 기반으로 태스크를 결정하여 선택된 것들을 실행한다. + +## 초기화 (Initialization) + +그래들은 빌드를 시작하는 단계에서 해당 `settings.gradle` 파일을 기준으로 멀티 모듈로 실행할지, 혹은 싱글로 실행할지를 결정한다. 그래들이 파일을 탐색하고 판단하는 순서는 아래와 같다. + +- 현재 디렉토리와 동일한 계층 단계의 master 디렉토리에서 `settings.gradle`을 찾는다. +- 없으면, 부모 디렉토리에서 settings.gradle을 찾는다. +- 없으면, 단일 프로젝트로 빌드를 실행한다. + +`settings.gradle`가 존재하면 현재 프로젝트가 멀티 프로젝트 계층에 속하는지 판단한다. 아니라면 단일 프로젝트로 실행하고 맞다면 멀티 프로젝트로 빌드를 실행한다. + +이런 식으로 작동하는 이유는 멀티 프로젝트 일 경우 모든 멀티프로젝트 빌드 구성을 생성해야하기 때문이다. `-u` 옵션을 주면 부모 디렉토리에서 설정파일을 찾는 것을 막고 항상 단일 프로젝트로 실행한다. `settings.gradle` 파일이 있는 곳에서 `-u`는 아무 기능도 없다. + +Gradle은 빌드에 참여하는 모든 프로젝트에 대해 Project 객체를 생성한다. 각 프로젝트는 기본적으로 탑레벨 디렉토리를 이름으로 갖는다. 최상위를 제외한 모든 프로젝트는 부모 프로젝트가 있고, 자식 프로젝트를 가질 수 있다. + +Settings File을 통해 하는 작업은 아래와 같은 것들이 있다. + +- 빌드 스크립트 클래스 경로에 라이브러리를 추가한다. +- 다중 프로젝트 빌드에 참여할 프로젝트를 정의한다. + +## 구성 (Configuration) + +구성 단계에서 Gradle은 초기화 단계에서 생성된 프로젝트에 태스크 및 기타 속성을 추가한다. 구성 단계가 끝날 때까지 Gradle은 요청된 작업에 대한 전체 작업 실행 그래프를 갖게 된다. + +각 프로젝트 내에서 작업은 방향 비순환 그래프(DAG)를 형성한다. + +image + + +## 실행 (Execution) + +실행 단계에서는 task를 직접 실행한다. 구성 단계에서 생성된 태스크 실행 그래프를 사용하여 실행할 태스크를 결정하게 된다. + +작업 실행에는 라이브러리 다운로드, 코드 컴파일, 입력 읽기 및 출력 쓰기 등 빌드와 관련된 대부분의 작업이 포함된다. + + +--- + +참고 + +- https://stackoverflow.com/questions/23484960/gradle-executes-all-tasks/23485085#23485085 +- https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:build_phases \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/\353\251\200\355\213\260\353\252\250\353\223\210.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/\353\251\200\355\213\260\353\252\250\353\223\210.md" new file mode 100644 index 00000000..3f6ebb60 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Gradle/\353\251\200\355\213\260\353\252\250\353\223\210.md" @@ -0,0 +1,42 @@ +--- +title: '멀티모듈' +lastUpdated: '2024-03-02' +--- + +멀티모듈은 하나의 프로젝트를 여러개의 모듈로 구성하는 것을 말한다. 모듈은 독립적으로 운영될 수 있는 의미를 가지는 구성요소 단위이며, 다른 모듈과의 상호작용으로 애플리케이션을 구성한다. + +intelij에서는 가장 상위 프로젝트에서 마우스 오른쪽 클릭을 하고, New > Module 을 선택 후, 모듈 이름을 입력하면 프로젝트 내부에 여러개의 모듈을 만들 수 있다. + + + +멀티모듈을 생성하면 모듈별로 `build.gradle` 파일이 생기기 때문에, 각 모듈에 맞게 의존성을 설정할 수 있다. + +메인 프로젝트의 `build.gradle`에서는 모든 모듈에 대해 공통적인 설정을 적용할 수도 있다. + +``` +subprojects { + +} + +allprojects { + +} +``` + +subprojects에서는 메인 프로젝트를 제외한 나머지 모듈들에 대한 설정, allprojects는 메인을 포함한 전체 프로젝트에 대한 설정을 할 수 있다. + +모든 모듈들에서 사용되는 기본적인 설정은 메인 gradle 파일에서 해주면 된다. + +## 장점 + +멀티모듈 프로젝트를 만드는 방법에 대해 간단히 알아봤는데, 멀티모듈의 장점은 도대체 뭘까? + +### 1. 최소의존성 + +첫째는 최소의존성이다. 모듈별로 의존성을 따로 설정할 수 있기 때문에, 각 모듈은 해당 모듈에 필요한 최소한의 의존성을 가진다. + +만약 모놀리식으로 프로젝트를 개발한다면, 의존성이 굳이 필요하지 않은 부분에서도 외부 라이브러리를 사용할 수 있기 때문에 `스파게티 의존성`이 생긴다. 복잡하게 얽힌 의존성으로 점차 개발 생산성이 떨어질 수 있다. + +### 2. 명확한 추상화 경계 + +계층에 따라 분리된 모듈로 인해 추상화 레벨을 맞출 수 있다. 하나의 모듈에서 다양한 어플리케이션 레이어와 의존성을 있는 형태가 아니라, 계층화된 모듈 레이어로 분리한으로서 역할과 책임의 선을 명확하게 개발의 생산성을 향상시킬 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Intellij\342\200\205Profiling\342\200\205tools.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Intellij\342\200\205Profiling\342\200\205tools.md" new file mode 100644 index 00000000..38268d73 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Intellij\342\200\205Profiling\342\200\205tools.md" @@ -0,0 +1,44 @@ +--- +title: 'Intellij Profiling tools' +lastUpdated: '2024-03-02' +--- + +IntelliJ IDEA Ultimate를 사용하고 있다면 **Profiling tools**을 사용하여 애플리케이션에 대한 부가적인 분석 정보를 얻을 수 있다. 세부 기능으로는 애플리케이션의 실행 방식과 메모리, CPU 리소스가 할당되는 방식에 대한 분석을 제공하는 Async Profiler, 애플리케이션이 실행되는 동안 JVM에서 발생한 이벤트에 대한 정보를 수집하는 모니터링 도구인 Java Flight Recorder 등이 있다. + +또한 애플리케이션의 특정 시점의 스냅샷으로 메모리를 분석하거나(Analyze memory snapshots) 애플리케이션이 실행되는 도중에도 CPU와 메모리 현황을 실시간으로 확인할 수 있는 기능(CPU and memory live charts)들이 있다. + +## 기능 + +### Profiling Application + +Async Profiler, Java Flight Recorder 기능 등을 통해서 애플리케이션을 분석할 수 있다. CPU와 메모리를 많이 쓸수록 더 넓은 직사각형으로 표현하여 애플리케이션의 호출 트리(call tree)를 시각화하는 플레임 그래프(Flame Graph), 프로그램의 호출 스택에 대한 정보를 나타내는 호출 트리(Call Tree) 그리고 메소드의 호출을 추적하거나 특정 메서드에서 호출된 모든 메서드를 확인할 수 있는 메서드 리스트(Method List) 등의 기능을 제공한다. + +## CPU and memory live charts + +실행 중인 애플리케이션 리소스 상태를 아래와 같이 차트 형태로 실시간 확인할 수 있다. 옵션을 통해서 데이터 확인 기간도 모든 데이터, 최근 5분 등으로 범위를 변경할 수 있다. + + + +## Analyze memory snapshots + +메모리 스냅샷 기능을 통해서 힙(heap) 메모리를 사용하는 코드를 분석하고 메모리 누수를 찾는 등 애플리케이션의 성능 문제를 분석할 수도 있다. 위에서 살펴본 라이브 차트에서 “Capture Memory Snapshot” 기능을 사용하면, 해당 시점의 메모리를 덤프하여 스냅샷으로 캡처할 수 있다. + + + +캡처가 완료되면 아래와 같이 여러 정보를 분석할 수 있다. 왼쪽 프레임에서는 메모리에 할당된 각 클래스의 정보를 확인할 수 있는데 각 항목은 다음과 같다. + +- Class: 애플리케이션의 클래스 목록 +- Count: 각 클래스의 사용 횟수 +- Shallow: 객체 자체가 저장되기 위해 할당되는 메모리 크기인 Shallow size 표기 +- Retained: 객체들의 shallow size와 해당 객체에서 직간접적으로 접근 가능한 객체들의 shallow size 합인 Retained size 표기 +오른쪽 프레임에서는 다음과 같은 탭을 확인할 수 있다. + +- Biggest Objects: 리소스를 가장 많이 차지하는 객체를 순서대로 나열 +- GC Roots: 클래스 별로 그룹핑된 가비지 수집기 루트 객체와 Shallow Size, Retained Size 표기 +- Merged Paths: 클래스 별로 그룹핑된 인스턴스의 개수 등을 확인 +- Summary: 인스턴스 개수, 스택 트레이스(stack traces) 등과 같이 일반적인 정보 표기 +- Packages: 모든 객체를 패키지별로 표기 + +--- +참고 +- https://www.jetbrains.com/help/idea/profiler-intro.html diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Keytool.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Keytool.md" new file mode 100644 index 00000000..068d8f32 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Keytool.md" @@ -0,0 +1,81 @@ +--- +title: 'Keytool' +lastUpdated: '2024-03-02' +--- + +Keystore 정보를 확인하기 위해선 터미널 또는 CMD에서 아래 명령어를 실행하면 된다. + +```bash +keytool -v -list -keystore [Keystore 파일] +``` + +명령어 입력 후 비밀번호를 입력하면, 키스토어의 정보가 나오게 된다. + +## Keytool 명령어 + +```bash +-certreq [-v] [-protected] + [-alias <별명>] [-sigalg <서명 알고리즘>] + [-file ] [-keypass <키 암호>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-delete [-v] [-protected] -alias <별명> + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-export [-v] [-rfc] [-protected] + [-alias <별명>] [-file <인증서 파일>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-genkey [-v] [-protected] + [-alias <별명>] + [-keyalg <키 알고리즘>] [-keysize <키 크기>] + [-sigalg <서명 알고리즘>] [-dname <대상 이름>] + [-validity <유효일>] [-keypass <키 암호>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-help +-identitydb [-v] [-protected] + [-file <신원 데이터베이스 파일>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-import [-v] [-noprompt] [-trustcacerts] [-protected] + [-alias <별명>] + [-file <인증서 파일>] [-keypass <키 암호>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-keyclone [-v] [-protected] + [-alias <별명>] -dest <대상 별명> + [-keypass <키 암호>] [-new <새 키 암호>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-keypasswd [-v] [-alias <별명>] + [-keypass <기존 키 암호>] [-new <새 키 암호>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-list [-v | -rfc] [-protected] + [-alias <별명>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-printcert [-v] [-file <인증서 파일>] +-selfcert [-v] [-protected] + [-alias <별명>] + [-dname <대상 이름>] [-validity <유효일>] + [-keypass <키 암호>] [-sigalg <서명 알고리즘>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] ... +-storepasswd [-v] [-new <새 암호 입력>] + [-keystore ] [-storepass <암호 입력>] + [-storetype <입력 유형>] [-providerName <이름>] + [-providerClass <공급자 클래스 이름> [-providerArg <인자>]] +``` + diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/Makefile.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/Makefile.md" new file mode 100644 index 00000000..eec44762 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/Makefile.md" @@ -0,0 +1,126 @@ +--- +title: 'Makefile' +lastUpdated: '2024-03-02' +--- + +Makefiles are a simple way to organize code compilation. This tutorial does not even scratch the surface of what is possible using _make_, but is intended as a starters guide so that you can quickly and easily create your own makefiles for small to medium-sized projects. + +### A Simple Example + +Let's start off with the following three files, hellomake.c, hellofunc.c, and hellomake.h, which would represent a typical main program, some functional code in a separate file, and an include file, respectively. + +```c +//hellomake.c +#include + +int main() { + // call a function in another file + myPrintHelloMake(); + + return(0); +} + +//hellofunc.c +#include +#include + +void myPrintHelloMake(void) { + + printf("Hello makefiles!\n"); + + return; +} + +//hellomake.h +/* +example include file +*/ + +void myPrintHelloMake(void); +``` + +Normally, you would compile this collection of code by executing the following command: + +```bash +gcc -o hellomake hellomake.c hellofunc.c -I. +``` + +This compiles the two .c files and names the executable hellomake. The -I. is included so that gcc will look in the current directory (.) for the include file hellomake.h. Without a makefile, the typical approach to the test/modify/debug cycle is to use the up arrow in a terminal to go back to your last compile command so you don't have to type it each time, especially once you've added a few more .c files to the mix. + +Unfortunately, this approach to compilation has two downfalls. First, if you lose the compile command or switch computers you have to retype it from scratch, which is inefficient at best. Second, if you are only making changes to one .c file, recompiling all of them every time is also time-consuming and inefficient. So, it's time to see what we can do with a makefile. + +The simplest makefile you could create would look something like: + +```js +hellomake: hellomake.c hellofunc.c + gcc -o hellomake hellomake.c hellofunc.c -I. +``` + +If you put this rule into a file called Makefile or makefile and then type make on the command line it will execute the compile command as you have written it in the makefile. Note that make with no arguments executes the first rule in the file. Furthermore, by putting the list of files on which the command depends on the first line after the :, make knows that the rule hellomake needs to be executed if any of those files change. Immediately, you have solved problem #1 and can avoid using the up arrow repeatedly, looking for your last compile command. However, the system is still not being efficient in terms of compiling only the latest changes. + +One very important thing to note is that there is a tab before the gcc command in the makefile. There must be a tab at the beginning of any command, and make will not be happy if it's not there. + +In order to be a bit more efficient, let's try the following: + +```js +CC=gcc +CFLAGS=-I. + +hellomake: hellomake.o hellofunc.o + $(CC) -o hellomake hellomake.o hellofunc.o +``` + +This addition first creates the macro DEPS, which is the set of .h files on which the .c files depend. Then we define a rule that applies to all files ending in the .o suffix. The rule says that the .o file depends upon the .c version of the file and the .h files included in the DEPS macro. The rule then says that to generate the .o file, make needs to compile the .c file using the compiler defined in the CC macro. The -c flag says to generate the object file, the -o $@ says to put the output of the compilation in the file named on the left side of the :, the $< is the first item in the dependencies list, and the CFLAGS macro is defined as above. + +As a final simplification, let's use the special macros $@ and $^, which are the left and right sides of the :, respectively, to make the overall compilation rule more general. In the example below, all of the include files should be listed as part of the macro DEPS, and all of the object files should be listed as part of the macro OBJ. + +```js +CC=gcc +CFLAGS=-I. +DEPS = hellomake.h +OBJ = hellomake.o hellofunc.o + +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +hellomake: $(OBJ) + $(CC) -o $@ $^ $(CFLAGS) +``` + +So what if we want to start putting our .h files in an include directory, our source code in a src directory, and some local libraries in a lib directory? Also, can we somehow hide those annoying .o files that hang around all over the place? The answer, of course, is yes. The following makefile defines paths to the include and lib directories, and places the object files in an obj subdirectory within the src directory. It also has a macro defined for any libraries you want to include, such as the math library -lm. This makefile should be located in the src directory. Note that it also includes a rule for cleaning up your source and object directories if you type make clean. The .PHONY rule keeps make from doing something with a file named clean. + +```js +IDIR =../include +CC=gcc +CFLAGS=-I$(IDIR) + +ODIR=obj +LDIR =../lib + +LIBS=-lm + +_DEPS = hellomake.h +DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) + +_OBJ = hellomake.o hellofunc.o +OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) + + +$(ODIR)/%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +hellomake: $(OBJ) + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +.PHONY: clean + +clean: + rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ +``` + +So now you have a perfectly good makefile that you can modify to manage small and medium-sized software projects. You can add multiple rules to a makefile; you can even create rules that call other rules. + +--- +reference +- http://www.gnu.org/software/make/manual/make.html +- https://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest.md" new file mode 100644 index 00000000..3b59993b --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest.md" @@ -0,0 +1,297 @@ +--- +title: 'Kotest' +lastUpdated: '2024-03-02' +--- + +코틀린에서는 아래와 형태와 같은 DSL(Domain Specific Language) 스타일의 중괄호를 활용한 코드 스타일을 제공한다. 코틀린 내부에서 제공하는 Standard library 대부분도 DSL을 이용해 작성된 것을 볼 수 있다. +- [Type safe builders](https://play.kotlinlang.org/byExample/09_Kotlin_JS/06_HtmlBuilder?_gl=1*scn3we*_ga*MTExMjE5OTk1NC4xNjExNjY4NzEx*_ga_J6T75801PF*MTYyNDM2NTA0Mi4xMDguMC4xNjI0MzY1MDQyLjA.&_ga=2.54260653.40338281.1624343617-1112199954.1611668711) +- [Kotlin Standard Library](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/#kotlin.collections.List) +- [Kotlin Scope Functions (let, also, apply, run ..)](https://github.com/rlaisqls/TIL/blob/main/%EC%96%B8%EC%96%B4%E2%80%85Language/Kotlin/%EB%B2%94%EC%9C%84%E2%80%85%EC%A7%80%EC%A0%95%E2%80%85%ED%95%A8%EC%88%98.md) + + +하지만 기존에 사용하던 Junit과 AssertJ, Mockito를 사용하면 Mocking이나 Assertion 과정에서 코틀린 DSL 을 활용할 수 없다. + +기존에 JAVA에서 사용하던 `Junit`, `Assertion`, `Mockito` 등의 테스트 프레임워크 대신에, Kotest나 Mockk와 같은 도구들을 사용하면 아래처럼 코틀린 DSL과 Infix를 사용해 코틀린 스타일의 테스트 코드를 작성할 수 있다. + + + + +--- + +## Kotest + + + +[Kotest](https://github.com/kotest/kotest)는 코틀린 진영에서 가장 많이 사용되는 테스트 프레임워크아다. 코틀린 DSL을 활용해 테스트 코드를 작성할 수 있으며 아래와 같은 기능들을 포함하고 있다. + +- 다양한 테스트 레이아웃(String Spec, Describe Spec, Behavior Spec 등) 제공 +- Kotlin DSL 스타일의 Assertion 기능 제공 +- 데이터 기반 테스트로 많은 양의 매개변수도 테스트 가능 +- 모든 테스트에 대해 호출수, 병렬 처리, 시간제한, 테스트 그룹화, 조건부 비활성 등의 미세 조정 테스트 가능 +- 중첩 테스트기능 제공 +- 동적 테스트 제공 (런타임에 조건부로 테스트를 추가 가능) +- 테스트 수명주기에 맞는 다양한 콜백을 제공 + + +Kotest를 사용하기 위해서는 아래와 같은 설정 / 의존성 추가가 필요하다. +```kotlin +test { + useJUnitPlatform() +} + +dependencies { + testImplementation("io.kotest:kotest-runner-junit5:${Versions.KOTEST}") + testImplementation("io.kotest:kotest-assertions-core:${Versions.KOTEST}") +} +``` + +Kotest는 테스트를 위한 많은 레이아웃을 제공한다. + +- Annotation Spec +- Behavior Spec +- Describe Spec +- Fun Spec +- … + +모든 Spec에 대한 정보는 여기서 확인해보자. + +**Kotest Annotation Spec** + +기존 Junit 방식과 가장 유사한 방식이다. 별 다른 장점이 없는 레이아웃이지만 Junit에서 Kotest로의 마이그레이션이 필요한 상황이라면 나쁘지 않은 선택이 될 수 있다. + + +```kotlin +internal class CalculatorAnnotationSpec: AnnotationSpec() { + private val sut = Calculator() + + @Test + fun `1과 2를 더하면 3이 반환된다`() { + val result = sut.calculate("1 + 2") + result shouldBe 3 + } + + @Test + fun `식을 입력하면, 해당하는 결과값이 반환된다`() { + calculations.forAll { (expression, answer) -> + val result = sut.calculate(expression) + result shouldBe answer + } + } + + @Test + fun `입력값이 null 이거나 빈 공백 문자일 경우 IllegalArgumentException 예외를 던진다`() { + blanks.forAll { + shouldThrow { + sut.calculate(it) + } + } + } + + @Test + fun `사칙연산 기호 이외에 다른 문자가 연산자로 들어오는 경우 IllegalArgumentException 예외를 던진다 `() { + invalidInputs.forAll { + shouldThrow { + sut.calculate(it) + } + } + } + + companion object { + private val calculations = listOf( + "1 + 3 * 5" to 20.0, + "2 - 8 / 3 - 3" to -5.0, + "1 + 2 + 3 + 4 + 5" to 15.0 + ) + private val blanks = listOf("", " ", " ") + private val invalidInputs = listOf("1 & 2", "1 + 5 % 1") + } +} +``` + +**Kotest Behavior Spec** + +기존 스프링 기반 프로젝트에서 작성하던 Given, When, Then 패턴을 Kotest Behavior Spec을 활용해 간결하게 정의할 수 있다. + +```kotlin +internal class CalculatorBehaviorSpec : BehaviorSpec({ + val sut = Calculator() + + given("calculate") { + val expression = "1 + 2" + `when`("1과 2를 더하면") { + val result = sut.calculate(expression) + then("3이 반환된다") { + result shouldBe 3 + } + } + + `when`("수식을 입력하면") { + then("해당하는 결과값이 반환된다") { + calculations.forAll { (expression, answer) -> + val result = sut.calculate(expression) + + result shouldBe answer + } + } + } + + `when`("입력값이 null이거나 빈 값인 경우") { + then("IllegalArgumentException 예외를 던진다") { + blanks.forAll { + shouldThrow { + sut.calculate(it) + } + } + } + } + + `when`("사칙연산 기호 이외에 다른 연산자가 들어오는 경우") { + then("IllegalArgumentException 예외를 던진다") { + invalidInputs.forAll { + shouldThrow { + sut.calculate(it) + } + } + } + } + } +}) { + companion object { + private val calculations = listOf( + "1 + 3 * 5" to 20.0, + "2 - 8 / 3 - 3" to -5.0, + "1 + 2 + 3 + 4 + 5" to 15.0 + ) + private val blanks = listOf("", " ", " ") + private val invalidInputs = listOf("1 & 2", "1 + 5 % 1") + } +} +``` + +**Kotest Describe Spec** + +Kotest는 Describe Spec을 통해 DCI(Describe, Context, It) 패턴 형태의 레이아웃도 제공한다. + +```kotlin + +internal class CalculatorDescribeSpec : DescribeSpec({ + val sut = Calculator() + + describe("calculate") { + context("식이 주어지면") { + it("해당 식에 대한 결과값이 반환된다") { + calculations.forAll { (expression, data) -> + val result = sut.calculate(expression) + + result shouldBe data + } + } + } + + context("0으로 나누는 경우") { + it("Infinity를 반환한다") { + val result = sut.calculate("1 / 0") + + result shouldBe Double.POSITIVE_INFINITY + } + } + + context("입력값이 null이거나 공백인 경우") { + it("IllegalArgumentException 예외를 던진다") { + blanks.forAll { + shouldThrow { + sut.calculate(it) + } + } + } + } + + context("사칙연산 기호 이외에 다른 문자가 연산자로 들어오는 경우") { + it("IllegalArgumentException 예외를 던진다") { + invalidInputs.forAll { + shouldThrow { + sut.calculate(it) + } + } + } + } + } +}) { + companion object { + val calculations = listOf( + "1 + 3 * 5" to 20.0, + "2 - 8 / 3 - 3" to -5.0, + "1 + 2 + 3 + 4 + 5" to 15.0 + ) + val blanks = listOf("", " ", " ") + val invalidInputs = listOf("1 & 2", "1 + 5 % 1") + } +} +``` + +위와 같은 여러 레이아웃 중 프로젝트 상황에 가장 잘 맞는 레이아웃을 골라 사용하면 된다. + +상황에 따라 [kotest 플러그인](https://kotest.io/docs/intellij/intellij-plugin.html)을 깔아야 test 실행 버튼이 나타날 수도 있다. + +--- + +## Kotest with @SpringBootTest + +`@SpringBootTest`와 같은 통합 테스트에서도 Kotest의 테스트 레이아웃을 사용할 수 있다. + +사용을 위해서는 아래와 같은 spring extension 의존성의 추가가 필요하다. + +```kotlin +dependencies { + testImplementation("io.kotest:kotest-extensions-spring:${Versions.KOTEST}") +} +``` + +```kotlin +@SpringBootTest +internal class CalculatorSpringBootSpec : DescribeSpec() { + override fun extensions() = listOf(SpringExtension) + + @Autowired + private lateinit var calculatorService: CalculatorService + + init { + this.describe("calculate") { + context("식이 주어지면") { + it("해당 식에 대한 결과값이 반환된다") { + calculations.forAll { (expression, data) -> + val result = calculatorService.calculate(expression) + + result shouldBe data + } + } + } + } + } + + companion object { + private val calculations = listOf( + "1 + 3 * 5" to 20.0, + "2 - 8 / 3 - 3" to -5.0, + "1 + 2 + 3 + 4 + 5" to 15.0 + ) + } +} +``` + +## Kotest Isolation Mode + +Kotest는 테스트 간 격리에 대한 설정을 제공하고 있다. + +- SingleInstance – Default +- InstancePerTest +- InstancePerLeaf + +Kotest에서는 테스트 간 격리 레벨에 대해 디폴트로 SingleInstance를 설정하고 있는데, 이 경우 Mocking 등의 이유로 테스트 간 충돌이 발생할 수 있다. 따라서 테스트간 완전한 격리를 위해서는 아래와 같이 IsolationMode를 InstancePerLeaf로 지정해 사용해야 합니다. + +```kotlin +internal class CalculatorDescribeSpec : DescribeSpec({ + isolationMode = IsolationMode.InstancePerLeaf + // ... +}) +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest\342\200\205Assertions.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest\342\200\205Assertions.md" new file mode 100644 index 00000000..d0fafc81 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest\342\200\205Assertions.md" @@ -0,0 +1,54 @@ +--- +title: 'Kotest Assertions' +lastUpdated: '2024-03-02' +--- + +https://kotest.io/docs/assertions/assertions.html + +**Assertions** + +- shouldBe + +```kotlin +name shouldBe "eunbin" +// == assertThat(name).isEqualTo("eunbin") +``` + +**Inspectors** + +- forExactly + +```kotlin +mylist.forExactly(3) { + it.city shouldBe "Chicago" +} +``` + +- forAtLeast + +```kotlin +val xs = listOf("sam", "gareth", "timothy", "muhammad") + +xs.forAtLeast(2) { + it.shouldHaveMinLength(7) +} +``` + +**Exceptions** + +- shouldThrow + +```kotlin +shouldThrow { + // code in here that you expect to throw an IllegalAccessException +} +// == assertThrows { } +``` + +- shouldThrowAny + +```kotlin +val exception = shouldThrowAny { + // test here can throw any type of Throwable! +} +``` diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest\342\200\205Specs.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest\342\200\205Specs.md" new file mode 100644 index 00000000..beb6faec --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Kotest\342\200\205Specs.md" @@ -0,0 +1,270 @@ +--- +title: 'Kotest Specs' +lastUpdated: '2024-03-02' +--- + +https://kotest.io/docs/framework/testing-styles.html + +Kotest는 10가지 다른 스타일의 테스트 레이아웃을 제공한다. 그중 일부는, 다른 테스트 프레임워크와 비슷한 구조로 만들어지기도 했다. + +스타일 간에 기능적인 차이는 없다. 스레드, 태그 등 모두 동일한 유형의 구성을 허용하지만, 이는 단순히 테스트를 구성하는 방법에 대한 선호도의 문제이다. + +코테스트를 사용하려면 테스트 스타일 중 하나를 확장하는 클래스 파일을 만들어야 한다. 그런 다음 원하는 Spec (추상)클래스를 상속받고 init {} 블록 안에서 테스트 케이스를 작성하면 된다. + +|테스트 스타일|유사한 테스트 프레임워크| +|-|-| +|Fun Spec|ScalaTest| +|String Spec|A Kotest original| +|Should Spec|A Kotest original| +|Describe Spec|Javascript frameworks과 RSpec| +|Behavior Spec|BDD frameworks| +|Word Spec|ScalaTest| +|Free Spec|ScalaTest| +|Feature Spec|Cucumber| +|Expect Spec|A Kotest original| +|Annotation Spec|JUnit| + +--- + +## Fun Spec + +FunSpec 테스트는 String으로 이름을 지정한다음 테스트 자체를 람다로 호출하여 테스트를 생성할 수 있다. + +내용은 별다른 형식 없이 그냥 자유롭게 작성하고, 이름만 저런 식으로 지정해줄 수 있는 형식인 것 같다. + +```kotlin +class MyTests : FunSpec({ + test("String length should return the length of the string") { + "sammy".length shouldBe 5 + "".length shouldBe 0 + } +}) +``` + +## String Spec + +Fun Spec과 똑같은 방식이지만, `test()`로 묶어줄 필요 없이 바로 string과 람다를 써서 작성하는 Spec이다. + +```kotlin +class MyTests : StringSpec({ + "strings.length should return size of string" { + "hello".length shouldBe 5 + } +}) +``` + +추가설정은 이런 식으로 할 수 있다. + +```kotlin +class MyTests : StringSpec({ + "strings.length should return size of string".config(enabled = false, invocations = 3) { + "hello".length shouldBe 5 + } +}) +``` + +## Should Spec + +ShouldSpec은 funSpec에서 `test`만 `should`로 바뀐 것이다. + +```kotlin +class MyTests : ShouldSpec({ + should("return the length of the string") { + "sammy".length shouldBe 5 + "".length shouldBe 0 + } +}) +``` + +## Describe Spec + +Describe Spec은 DCI 패턴으로 테스트코드를 작성할 수 있는 Spec이다. + +describe에는 도메인 혹은 함수명, 어떻게 되는지를 각각 적으면 된다. + +```kotlin +class MyTests : DescribeSpec({ + + describe("score") { + it("start as zero") { + // test here + } + describe("with a strike") { + it("adds ten") { + // test here + } + it("carries strike to the next frame") { + // test here + } + } + + describe("for the opposite team") { + it("Should negate one score") { + // test here + } + } + } +}) +``` + +Docs에 예시는 위와 같이 되어있지만, Describe Spec으로는 보통 중간에 context를 껴서 이런식으로 작성하는 경우가 많다. + +JUnit5로 DCI 패턴을 쓰려면 [이렇게](https://johngrib.github.io/wiki/junit5-nested/) 해야하는데, Kotest를 쓰면 중첩테스트가 되니까 그냥 할 수 있다. + +```kotlin + describe("PASSWORD_EXP") { + context("영어 대소문자, 영어, 특수문자가 포함된 8-30자의 문자열이 주어지면"){ + val string = "Password!1" + it("참이 반환된다") { + Pattern.matches(RegexUtil.PASSWORD_EXP, string) shouldBe true + } + } + } +``` + +## Behavior Spec + +우리가 보통 아는 `given`, `when`, `then` 패턴이다. + +하지만 when이 Kotlin의 예약어이기 때문에 Behavior Spec을 사용할떄 backtick(`)으로 감싸서 사용해야한다. 그게 마음에 들지 않는 경우 Given, When, Then을 쓸 수도 있다. + +```kotlin +class MyTests : BehaviorSpec({ + given("a broomstick") { + `when`("I sit on it") { + then("I should be able to fly") { + // test code + } + } + `when`("I throw it away") { + then("it should come back") { + // test code + } + } + } +}) +``` + + +## Word Spec + +WordSpec은 context 문자열 뒤에 테스트를 키워드 should를 사용하여 테스트를 작성한다. + +```kotlin +class MyTests : WordSpec({ + "String.length" should { + "return the length of the string" { + "sammy".length shouldBe 5 + "".length shouldBe 0 + } + } +}) +``` + +또한 다른 수준의 중첩을 추가할 수 있는 When 키워드를 지원한다. + +여기에서도 역시 when은 backtick(`) 또는 대문자 변형을 사용해야 한다. + +```kotlin +class MyTests : WordSpec({ + "Hello" When { + "asked for length" should { + "return 5" { + "Hello".length shouldBe 5 + } + } + "appended to Bob" should { + "return Hello Bob" { + "Hello " + "Bob" shouldBe "Hello Bob" + } + } + } +}) +``` + +## Free Spec + +Free Spec에서는 키워드 `-`(빼기)를 사용하여 임의의 깊이 수준을 중첩할 수 있다. + +```kotlin +class MyTests : FreeSpec({ + "String.length" - { + "should return the length of the string" { + "sammy".length shouldBe 5 + "".length shouldBe 0 + } + } + "containers can be nested as deep as you want" - { + "and so we nest another container" - { + "yet another container" - { + "finally a real test" { + 1 + 1 shouldBe 2 + } + } + } + } +}) +``` + +## Feature Spec + +FeatureSpec은 feature, scenario를 사용한다. ([cucumber](https://cucumber.io/docs/gherkin/reference/)와 비슷하다) + +```kotlin +class MyTests : FeatureSpec({ + feature("the can of coke") { + scenario("should be fizzy when I shake it") { + // test here + } + scenario("and should be tasty") { + // test here + } + } +}) +``` + +## Expect Spec + +ExpectSpec은 FunSpec, ShouldSpec과 비슷하며, `expect` 키워드를 사용한다. + +```kotlin +class MyTests : ExpectSpec({ + expect("my test") { + // test here + } +}) +``` + +context 블록에 중첩하여 작성할 수도 있다. + +```kotlin +class MyTests : ExpectSpec({ + context("a calculator") { + expect("simple addition") { + // test here + } + expect("integer overflow") { + // test here + } + } +}) +``` + +## Annotation Spec + +JUnit과 동일한 방식이다. 별다른 이점은 없지만, 기존 형식을 그대로 가져올 수 있어 더 빠른 마이그레이션이 가능하다. + +```kotlin +class AnnotationSpecExample : AnnotationSpec () { + + @BeforeEach fun beforeTest () { + println ( " 각 테스트 전 " ) + } + @Test fun test1 () { + 1 shouldBe 1 + } + @Test fun test2 () { + 3 shouldBe 3 + } +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Mockk.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Mockk.md" new file mode 100644 index 00000000..d91c5c6b --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Kotlin/Mockk.md" @@ -0,0 +1,144 @@ +--- +title: 'Mockk' +lastUpdated: '2024-03-02' +--- + +![image](https://user-images.githubusercontent.com/81006587/210192366-7c1a653d-9af0-4e7d-aab1-7b54b58be4cd.png) + +Mockk는 코틀린 스타일의 Mock 프레임워크이다. + +Mockito를 사용하는 경우 코틀린 DSL 스타일을 활용할 수 없지만, Mockk를 사용하면 아래와 같이 코틀린 DSL 스타일로 Mock 테스트를 작성할 수 있다. + +```kotlin +//Mockito +given(userRepository.findById(1L).willReturn(expectedUser) + +//Mockk +every { userRepository.findById(1L) } answers { expectedUser } +``` + +Mockk의 사용을 위해서는 아래와 같은 의존성을 추가해줘야 한다. + +```kotlin +dependencies { + testImplementation("io.mockk:mockk:1.12.0") +} +``` + +### 예시 + +**Mocking** + +```kotlin +val permissionRepository = mockk() +``` + +**SpyK** + +```kotlin +val car = spyk(Car()) // or spyk() to call default constructor +``` + +**Relaxed mock** + +```kotlin +val car = mockk(relaxed = true) + +// relaxed를 설정하는 경우 permissionRepository.delete에 대해 Mocking을 하지 않은 상태에서 delete 메소드가 호출되더라도 예외가 발생하지 않습니다. +// every { permissionRepository.delete(id) } just Runs +``` + +**Answers** + +```kotlin +// answers +every { permissionRepository.save(permission) } answers { permission } + +// throws +every { permissionRepository.findByIdOrNull(id) } throws EntityNotFoundException() + +// just Runs +every { permissionRepository.delete(id) } just Runs + +// returnsMany +every { permissionRepository.save(permission) } returnsMany listOf(firstPermission, secondPermission) + +// returns +every { permissionRepository.save(permission) } returns permission +every { permissionRepository.save(permission) } returns firstPermission andThen secondPermission +``` + +**Argument matching** + +- any + +```kotlin +every { permissionRepository.save(any()) } retunrs permission +``` + +- varargs + +```kotlin +every { obj.manyMany(5, 6, *varargAll { it == 7 }) } returns 3 + +println(obj.manyMany(5, 6, 7)) // 3 +println(obj.manyMany(5, 6, 7, 7)) // 3 +println(obj.manyMany(5, 6, 7, 7, 7)) // 3 + +every { obj.manyMany(5, 6, *varargAny { nArgs > 5 }, 7) } returns 5 + +println(obj.manyMany(5, 6, 4, 5, 6, 7)) // 5 +println(obj.manyMany(5, 6, 4, 5, 6, 7, 7)) // 5 +``` + +**Verification** + +- verify + +```kotlin +verify(atLeast = 3) { car.accelerate(allAny()) } +verify(atMost = 2) { car.accelerate(fromSpeed = 10, toSpeed = or(20, 30)) } +verify(exactly = 1) { car.accelerate(fromSpeed = 10, toSpeed = 20) } +verify(exactly = 0) { car.accelerate(fromSpeed = 30, toSpeed = 10) } // means no calls were performed +``` + +- verifyAll + +```kotlin +verifyAll { + obj.sum(1, 3) + obj.sum(1, 2) + obj.sum(2, 2) +} +``` + +- verifySequnece + +```kotlin +verifySequence { + obj.sum(1, 2) + obj.sum(1, 3) + obj.sum(2, 2) +} +``` + +## SpringMockk + +Mockk에서는 `@MockBean`이나 `@SpyBean`의 기능을 직접 제공하지 않는다. + +`@MockBean`이나 `@SpyBean`의 기능을 코틀린 DSL을 활용해 사용하고 싶다면 `Ninja-Squad/springmockk` 의존성을 추가해야 합니다. + +```kotlin +testImplementation("com.ninja-squad:springmockk:3.0.1") +``` + +SpringMockk에서는 `@MockkBean`, `@SpykBean`이라는 어노테이션 및 기능을 제공한다. + +```kotlin + @MockkBean // @MockBean 대신 @MockkBean 사용 가능 + private lateinit var userRepository: UserRepository +``` + +더 많은 예시는 공식 링크에서 볼 수 있다. + +https://mockk.io/#dsl-examples diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Mock\352\263\274\342\200\205Spy.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Mock\352\263\274\342\200\205Spy.md" new file mode 100644 index 00000000..b5c5c156 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/TestCode/Mock\352\263\274\342\200\205Spy.md" @@ -0,0 +1,189 @@ +--- +title: 'Mock과 Spy' +lastUpdated: '2024-03-02' +--- + +Mockito 등의 라이브러리를 쓰다보면, Mock과 Spy를 사용하게 될 것이다. 둘 다 클래스의 **가상 객체**를 만들어서 메소드와 필드를 모의 실험하기 위해 쓰인다는 공통점이 있다. + +그렇다면 차이점은 무엇일까? + +## 비교 + +간단히 말하자면, Mock은 완벽한 가짜 객체를 만드는데 반해, Spy는 기본적으로 기존에 구현되어있는 동작을 따르고 일부 메소드만 stub 한다. + +Mock 객체의 함수를 호출했을때 그 함수에 stub된 동작이 없으면 코드나 라이브러리에 따라 문제가 생길 수 있다. strict 하게 규칙이 설정된 라이브러리(`ex. mockk`)의 경우에는 호출 즉시 예외가 던져질 수 있고, null이나 기본값 (`int = 0, String = ""...`)이 반환되도록 되어있을 수도 있다.(`ex. mockito`) + +하지만 Spy는 그렇지 않다. 내부에 구현된 메소드가 있다면 그것의 코드를 그대로 실행한다. 실제로 그 클래스의 객체가 존재하는 것과 똑같지만, 일부 메서드의 동작만 바꿔주는게 가능한 것이다. + +> stub이란, 해당 메소드(또는 필드)를 호출했을 때 반환해야 하는 값을 미리 지정하는 것을 뜻한다.
https://martinfowler.com/bliki/TestDouble.html + +--- + +아래는 mockito를 사용해 **mock과 spy를 비교**해보는 코드이다. + +```java +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class MockSpy { + + @Mock + private List mockList; + + @Spy + private List spyList = new ArrayList(); + + //Mock : add와 get은 stub되지 않은 동작이기 때문에 아무것도 하지 않고, 반환값으론 null을 return한다. + @Test + public void testMockList() { + mockList.add("test"); + assertNull(mockList.get(0)); + } + + + //Spy : add와 get은 stub되지 않은 동작이기 때문에 기존 List의 메서드 동작을 똑같이 수행한다. + @Test + public void testSpyList() { + spyList.add("test"); + assertEquals("test", spyList.get(0)); + } + + //Mock : get(100)을 호출하면 "Mock 100"을 반환하도록 설정해주었기 때문에, + // 파라미터로 100이 들어왔을때 그 값을 반환한다. + @Test + public void testMockWithStub() { + String expected = "Mock 100"; + when(mockList.get(100)).thenReturn(expected); + + assertEquals(expected, mockList.get(100)); + } + + //Spy : get(100)을 호출하면 "Mock 100"을 반환하도록 설정해주었기 때문에, + // 파라미터로 100이 들어왔을때 기존 List의 메서드 실행결과가 아닌 그 값을 반환한다. + @Test + public void testSpyWithStub() { + + String expected = "Spy 100"; + doReturn(expected).when(spyList).get(100); + + assertEquals(expected, spyList.get(100)); + } +} +``` + +--- + +mockk에서는 mock 메서드 호출에 대한 설정이 `strict`하기 때문에 어떤 메소드를 호출하고 싶다면 stub을 꼭 만들어주거나 별도의 설정을 해줘야한다. + +mockk는 Unit을 반환하는, 즉 return 값이 없는 메서드에 대해서도 stub을 필요로 하기 때문에 아래와 같은 상황에서도 예외가 던져진다. + +```kotlin + @Service +class DeleteUserUseCase( + private val documentRepository: DocumentRepository +) { + + fun execute(documentId: UUID) { + + val document = documentRepository.findByIdOrNull(documentId)?: throw DocumentNotFoundException + + documentRepository.delete(document) //void delete(T entity); + } +} +``` + +그렇기 때문에 이런 상황에서는 아래와 같이 설정해주거나 + +```kotlin + every { documentRepository.delete(document) } returns Unit + justRun { documentRepository.delete(document) } +``` + +`relaxUnitFun` 설정을 `true`로 해줘서, 반환값이 없는 메소드는 stub 없이도 실행할 수 있도록 해줘야한다. + +반환값 없는 메서드가 아니더라도, 엄격하게 Exception을 던지지 않도록 하는 `relaxed` 설정도 있다. + +```kotlin +inline fun mockk( + name: String? = null, + relaxed: Boolean = false, + vararg moreInterfaces: KClass<*>, + relaxUnitFun: Boolean = false, + block: T.() -> Unit = {} +): T = MockK.useImpl { + MockKDsl.internalMockk( + name, + relaxed, + *moreInterfaces, + relaxUnitFun = relaxUnitFun, + block = block + ) +} +``` + +relaxed 설정을 하면 + +Unit 메서드 : Unit +반환값 있는 메서드 +- 반환값이 nullable Type(`?`) : null +- 반환겂이 not nullable : 임의의 값 + +이런식으로 반환하는 것 같다. + +```kotlin +//io.mockk.impl.stub.MockKStub + protected open fun defaultAnswer(invocation: Invocation): Any? { + return stdObjectFunctions(invocation.self, invocation.method, invocation.args) { + if (shouldRelax(invocation)) { //Relax + if (invocation.method.returnsUnit) return Unit + return gatewayAccess.anyValueGenerator().anyValue( + invocation.method.returnType, + invocation.method.returnTypeNullable + ) { + childMockK(invocation.allEqMatcher(), invocation.method.returnType) + } + } else { + throw MockKException("no answer found for: ${gatewayAccess.safeToString.exec { invocation.toString() }}") + } + } + } + + private fun shouldRelax(invocation: Invocation) = when { + relaxed -> true + relaxUnitFun && + invocation.method.returnsUnit -> true + else -> false + } +``` + +```kotlin +//io.mockk.impl.instantiation.AnyValueGenerator +open class AnyValueGenerator { + open fun anyValue(cls: KClass<*>, isNullable: Boolean, orInstantiateVia: () -> Any?): Any? { + return when (cls) { + Boolean::class -> false + Byte::class -> 0.toByte() + Short::class -> 0.toShort() + Char::class -> 0.toChar() + Int::class -> 0 + Long::class -> 0L + Float::class -> 0.0F + Double::class -> 0.0 + String::class -> "" + ... + } + } +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/WAS/tomcat\342\200\205\352\265\254\354\204\261\354\232\224\354\206\214.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/WAS/tomcat\342\200\205\352\265\254\354\204\261\354\232\224\354\206\214.md" new file mode 100644 index 00000000..bf9c11b3 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/WAS/tomcat\342\200\205\352\265\254\354\204\261\354\232\224\354\206\214.md" @@ -0,0 +1,27 @@ +--- +title: 'tomcat 구성요소' +lastUpdated: '2023-02-09' +--- +### tomcat-Catalina + +Catalina는 Tomcat의 서블릿 컨테이너 이름이다. + +자바 서블릿을 호스팅 하는 환경. + +### tomcat-Coyote + +톰캣에 TCP를 통한 프로토콜을 지원한다. + +Coyote가 HTTP요청을 받으면 Catalina 서블릿 컨테이너에서 요청중에서 java웹 어플리케이션을 해석하는데, jsp에 관한 요청 일땐 Jasper가 처리한다. + +### tomcat-Jasper + +Jasper는 실제 JSP페이지의 요청을 처리하는 서블릿 엔진이다. + +Jasper는 JSP 파일을 구문 분석하여 Java 코드로 컴파일한다. 런타임에 Jasper는 JSP 파일에 대한 변경 사항을 감지하고 재컴파일한다. + +**Catalina** is Tomcat's servlet container. Catalina implements Sun Microsystems' specifications for servlet and JavaServer Pages (JSP). In Tomcat, a Realm element represents a "database" of usernames, passwords, and roles (similar to Unix groups) assigned to those users. Different implementations of Realm allow Catalina to be integrated into environments where such authentication information is already being created and maintained, and then use that information to implement Container Managed Security as described in the Servlet Specification + +**Coyote** is a Connector component for Tomcat that supports the HTTP 1.1 protocol as a web server. This allows Catalina, nominally a Java Servlet or JSP container, to also act as a plain web server that serves local files as HTTP documents. + +Coyote listens for incoming connections to the server on a specific TCP port and forwards the request to the Tomcat Engine to process the request and send back a response to the requesting client. Another Coyote Connector, Coyote JK, listens similarly but instead forwards its requests to another web server, such as Apache, using the JK protocol. This usually offers better performance. \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/WAS/\354\233\271\354\204\234\353\262\204\354\231\200\342\200\205WAS.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/WAS/\354\233\271\354\204\234\353\262\204\354\231\200\342\200\205WAS.md" new file mode 100644 index 00000000..93634301 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/WAS/\354\233\271\354\204\234\353\262\204\354\231\200\342\200\205WAS.md" @@ -0,0 +1,63 @@ +--- +title: '웹서버와 WAS' +lastUpdated: '2023-04-26' +--- +## 웹 서버(Web Server) + +웹 서버란 HTTP 프로토콜을 기반으로 클라이언트가 웹 브라우저에서 어떠한 요청을 하면 그 요청을 받아 **정적 컨텐츠**를 제공하는 서버이다. 정적 컨텐츠란 단순 HTML 문서, CSS, 이미지, 파일 등 즉시 응답 가능한 컨텐츠이다. + +이때 웹 서버가 정적 컨텐츠가 아닌 동적 컨텐츠를 요청받으면 WAS에게 해당 요청을 넘겨주고, WAS에서 처리한 결과를 클라이언트에게 전달하는 역할도 해준다. + +이러한 웹 서버에는 `Apache`, `NginX`등이 있다. + +## WAS(Web Application Server) + +WAS란 DB 조회 혹은 다양한 로직 처리를 요구하는 **동적 컨텐츠**를 제공하기 위해 만들어진 Application 서버이다. HTTP 프로토콜을 기반으로 사용자 컴퓨터나 장치에 애플리케이션을 수행해주는 미들웨어로서, 주로 데이터베이스 서버와 같이 수행된다. + +WAS는 JSP, Servlet 구동환경을 제공해주기 때문에 서블릿 컨테이너 혹은 웹 컨테이너로도 불린다. + +이러한 WAS는 웹 서버의 기능들을 구조적으로 분리하여 처리하고자 하는 목적으로 제시되었다. 분산 트랜잭션, 보안, 메시징, 쓰레드 처리 등의 기능을 처리하는 분산 환경에서 사용된다. WAS는 프로그램 실행 환경과 DB 접속 기능을 제공하고, 여러 개의 트랜잭션을 관리 가능하다. 또한 비즈니스 로직을 수행할 수 있다. + +이러한 WAS에는 `Tomcat`, `JBoss`, `WebSphere` 등이 있다. + +## 웹 서버와 WAS + +![image](https://user-images.githubusercontent.com/81006587/234428846-6fc537cd-f44c-4291-9975-0e26c61f58a7.png) + +WAS는 Web Server와 Web Container의 역할을 모두 할 수 있다. 여기서 컨테이너는 JSP, Servlet을 실행시킬 수 있는 소프트웨어를 말한다. 현재 WAS의 웹 서버도 정적인 컨텐츠를 처리하는 데 성능상 큰 차이가 없다. + +그렇다면 WAS가 웹 서버의 기능까지 모두 수행하면 되는 것일까? 무조건 그렇진 않고, 웹 서버와 WAS를 분리해야 하는 몇 이유들이 있다. + +1. 서버 부하 방지 + +- WAS와 웹 서버는 분리하여 서버의 부하를 방지해야 한다. WAS는 DB 조회나 다양한 로직을 처리하고, 단순한 정적 컨텐츠는 웹 서버에서 처리해줘야 한다. 만약 정적 컨텐츠까지 WAS가 처리한다면 부하가 커지게 되고, 수행 속도가 느려질 것이다. + +2. 보안 강화 + +- SSL에 대한 암호화, 복호화 처리에 웹 서버를 사용 가능 + +3. 여러 대의 WAS 연결 가능 + +- 로드 밸런싱을 위해 웹 서버를 사용할 수 있다. 여러 개의 서버를 사용하는 대용량 웹 어플리케이션의 경우 웹 서버와 WAS를 분리하여 무중단 운영을 위한 장애 극복에 쉽게 대응할 수 있다. + +4. 여러 웹 어플리케이션 서비스 가능 + +- 하나의 서버에서 PHP, JAVA 애플리케이션을 함께 사용할 수 있다. + +이러한 이유로 웹 서버를 WAS 앞에 두고 필요한 WAS들을 웹 서버에 플러그인 형태로 설정하면 효율적인 분산 처리가 가능하다. + +## Web Service Architecture + +웹서버와 WAS를 둔 서비스의 전체 아키텍처와 동작 순서는 아래와 같다. + +image + +1. 클라이언트의 요청을 먼저 웹 서버가 받은 다음, WAS에게 보내 관련된 Servlet을 메모리에 올린다. +2. WAS는 `web.xml`을 참조해 해당 Servlet에 대한 스레드를 생성한다. (스레드 풀 이용) +3. 이때 `HttpServletRequest`와 `HttpServletResponse` 객체를 생성해 Servlet에게 전달한다. +4. 스레드는 Servlet의 `service()` 메소드를 호출한다. +5. `service()` 메소드는 요청에 맞게 `doGet()`이나 `doPost()` 메소드를 호출한다. +6. `doGet()`이나 `doPost()` 메소드는 인자에 맞게 생성된 적절한 동적 페이지를 Response 객체에 담아 WAS에 전달한다. +7. WAS는 Response 객체를 HttpResponse 형태로 바꿔 웹 서버로 전달한다. +8. 생성된 스레드를 종료하고, `HttpServletRequest`와 `HttpServletResponse` 객체를 제거한다. + diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/authn\352\263\274\342\200\205authz.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/authn\352\263\274\342\200\205authz.md" new file mode 100644 index 00000000..1d8069fc --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/authn\352\263\274\342\200\205authz.md" @@ -0,0 +1,72 @@ +--- +title: 'authn과 authz' +lastUpdated: '2024-03-02' +--- + +정보 보안에는 인증(Authentication, authn)와 인가(Authorization, authz)라는 개념이 있다. 간단히 말하자면 authn은 정체성 또는 그 유저가 **누구인지**와 관련있는 반면, authz는 권한 또는 **누군가가 할 수 있는 것**을 다룬다. + +둘은 연관되어있지만 서로 다른 개념이고, 유저를 식별하고 접근을 관리하는(IAM)에서 중요한 요소이다. + +--- + +# Authentication(authn)이란? + +authn은 사용자의 신원을 검증하는 행위로서 보안 프로세스에서 첫 번째 단계이며, 사용자 또는 장치가 누구인지(또는 무엇인지)를 확인하는 것을 의미한다. 신분을 확인함으로써 사용자가 합법적인지 확인하고자 하는 것이며, 잘못된 사람에게 정보가 노출되지 않도록 보장한다. + +일반적인 authn 방법에는 아래와 같은 것들이 있다. + +#### username, password 입력 + +- 가장 일반적인 인증 방법 중 하나는 사용자에게 사용자 이름과 암호를 입력하도록 요청하는 것이다. 로그인 양식에 사용자 이름과 비밀번호를 입력하면, 서비스는 그러한 자격 증명을 확인하고, 사람을 유저로 인증하고, 그의 계정에 로그인할 수 있도록 한다. + +#### 다중 요소 인증(MFA) + +- 하지만 username과 password를 입력해 인증하는 방법의 단점은 비밀번호가 종종 악의적인 당사자에 의해 추측되거나 도용될 수 있다는 것이다. + +- 이떄. 인증의 추가 요소를 요구하면 사용자의 보안이 강화되며 이 개념을 MFA(다요소 인증)라고 한다. MFA를 사용할 때 공격자가 합법적인 사용자로 허위 인증하려면 암호 이상의 추가 인증절차가 필요하다. + +- MFA는 2요소 인증(2FA)으로 구현되는 경우가 가장 많다. 오늘날 많은 서비스들은 사용자들에게 그들이 발행된 토큰을 가지고 있다는 것을 증명하도록 요청함으로써 2FA를 구현한다. 토큰에는 SMS 또는 모바일 앱을 통해 사용자에게 전송되는 코드와 같은 "소프트" 토큰과 USB 키와 같은 "하드" 토큰의 두 가지 유형이 있다. 2FA 및 MFA는 생체 인증 요소(아래 설명)를 사용할 수도 있다. + +#### 공개 키 인증서 + +- [공개 키 인증](https://www.cloudflare.com/learning/ssl/how-does-public-key-encryption-work/)은 이러한 다른 형식의 인증보다 약간 더 복잡하지만 제대로 구현되면 더 안전할 수 있다. 공용 키 암호화를 사용하여 인증된 당사자가 올바른 개인 키를 가지고 있는지 확인하는 것이다. + +- 공개 키 인증의 가장 일반적인 용도는 웹 서버를 인증하는 데 사용되는 TLS(Transport Layer Security)이다. 사용자 장치는 HTTPS를 사용하는 웹 사이트를 로드할 때마다 이 유형의 인증을 수행한다. + +- 공개 키 인증은 또한 상호 인증을 위해 사용되는데, 이는 단지 클라이언트가 사용자를 인증하는 것이나 웹 서비스가 아닌 통신의 양쪽 모두가 서로를 인증할 때 사용된다. 사물인터넷(IoT) 장치와 API endpoint가 이러한 유형의 인증 방식을 쓰는 경우도 있다. + +#### 생체인증 + +- 사람을 인증하는 데에만 사용할 수 있는 생체 인증은 알려진 신체적 특성의 데이터베이스와 대조하여 신체적 특성 중 하나를 확인함으로써 누군가의 신원을 확인하는 것을 얘기한다. 얼굴 인증, 지문 인증 등이 이러한 유형의 인증의 예이다. + +--- + +# Authorization(authz)이란? + +authz는 **인증된 사용자가 수행할 수 있는 작업을 결정**한다. "권한"이라는 용어로 불리기도 한다. + +소프트웨어를 개발하는 회사에서는 사용자의 작업을 허용하거나 차단하기 위해 일종의 권한 부여 솔루션을 사용한다. 이 솔루션은 일반적으로 사용자가 누구인지에 따라 허용하거나 차단할 작업을 파악한다. 이러한 이유로 인증은 권한 부여와 밀접하게 관련되어 있다. + +사용자 권한을 결정하는 방법에는 아래와 같은 것들이 있다. + +#### RBAC(role-based access control) + +- 모든 사용자에게 하나 이상의 미리 결정된 역할이 할당되고, 각 역할에 지정된 사용 권한 집합이 제공된다. +- 다시 말해, 유저에게 일반 사용자, 관리자 등의 ROLE을 부여하여 각 ROLE의 접근 범위를 지정하는 것이다. + +#### ABAC(Attribute-based Access Control) + +- 사용자가 자신의 속성 또는 수행하려는 작업의 속성을 기반으로 사용 권한을 할당 받는다. + +#### RBAC(rule-based access control) + +- 역할에 관계없이 모든 사용자에게 적용되는 규칙 집합에 따라 허용되거나 거부된다. +- 따로 유저면 권한을 나누지 않는 방식이다. + +> role-based access control와 rule-based access control는 약칭이 똑같다... + +--- + +참고 + +https://www.cloudflare.com/ko-kr/learning/access-management/authn-vs-authz/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/mermaid\342\200\205\353\254\270\353\262\225.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/mermaid\342\200\205\353\254\270\353\262\225.md" new file mode 100644 index 00000000..127df861 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/mermaid\342\200\205\353\254\270\353\262\225.md" @@ -0,0 +1,116 @@ +--- +title: 'mermaid 문법' +lastUpdated: '2024-03-02' +--- +mermaid로 그릴 수 있는 다이어그램은 아래와 같은 것들이 있다. + +- 플로우 차트 (Flowchart) +- 시퀀스 다이어그램 (Sequence Diagram) +- 간트 차트 (Gantt Chart) +- 클래스 다이어그램 (Class Diagram) +- User Journey Diagram +- Git graph +- Entity Relationship Diagram + +## 플로우 차트(Flowcharts) + +모든 플로우 차트는 각 상태를 나타내는 Node와 Node를 이어주는 간선(Edge)으로 구성된다. 우리는 mermaid를 통해 다양한 화살표, 간선 타입, 서브 그래프로 플로우차트를 간단하게 그려낼 수 있다. + +```java +flowchart LR + A[구매] --> B; + B[유저, 파라미터, \n 어뷰징 검증] --> C; + C{client가 안드로이드} -->|Yes| E; + C -->|No| G; + E[안드로이드 Proxy 처리] --> G; + G[DB 저장] --> I; + I[응답 반환]; +``` + +## 플로우차트 선언 + +mermaid에서는 항상 그리고자 하는 다이어그램 종류를 위에 명시해주어야 한다. + +그리고 플로우차트일 경우에는 `flowchart`를 적고, 그려질 방향을 선언해준다. 아래 코드는 플로우차트를 그리고 그 방향은 왼쪽에서 오른쪽으로 향한다는 선언이다. + +```java +flowchart LR +``` + +선언할 수 있는 방향은 아래와 같다. + +- TB(= TD) : 위에서 아래로 +- BT : 아래에서 위로 +- RL : 오른쪽에서 왼쪽으로 +- LR : 왼쪽에서 오른쪽으로 + +## 노드(Node) 선언 + +아래 코드는 플로우차트에 노드를 하나 선언한 것이다. id는 해당 노드를 가리키는 값이며 `[]` 사이에 있는 값은 해당 노드에 표시될 값이다. 해당 값이 없으면 id가 노출된다. + +```java +flowchart LR + id[구매] +``` + +```java +flowchart LR + 구매 +``` + +위의 코드를 작성하게되면 아래와 같은 노드하나가 그려진다. + +노드는 다양한 형태로 선언할 수 있다. + +```java +flowchart LR + id[(Database)] + +flowchart LR + id{조건} +``` + +## 간선(Edge) 선언 + +노드와 노드를 이어주는 간선을 선언해보자. 간선은 기본적으로 `-->`를 통해서 선언할 수 있다. 이렇게 선언하면 그래프로써 화살표가 있는 간선이 그려지게 된다. 아래 코드는 Service라는 이름을가진 A 노드에서 Database라는 이름을 가지고 있는 B 노드로의 화살표 간선이 생기는 플로우차트이다. 그리고 B와 C 사이에는 방향이 없는 간선을 하나 두었다. + +```java +flowchart LR + A[Service] + B[(Database_1)] + C[(Database_2)] + A --> B --- C +``` + +간선을 다른 형태로 표시할 수도 있다. + +### 점선 화살표 + +```java +flowchart LR + C -.-> id2{box} +``` + +### 간선에 텍스트 추가 + +```java +flowchart LR + A-->|의존|B +``` + +그리고 한 노드에서 여러개의 노드와 연결하는 것도 가능하다. 아래는 A 노드를 B와 C에 연결하는 방법이다. + +```java +flowchart LR + A[Service] + B[(Database_1)] + C[(Database_2)] + A-->B + A-->C +``` + +더 자세하고 다양한 사용법은 공식 문서를 참고하자. + +- https://mermaid.js.org/syntax/flowchart.html#links-between-nodes +- https://mermaid.js.org/syntax/classDiagram.html +- https://mermaid.js.org/syntax/entityRelationshipDiagram.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/Coroutine\342\200\205vs\342\200\205Reactor.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/Coroutine\342\200\205vs\342\200\205Reactor.md" new file mode 100644 index 00000000..c5c70e14 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/Coroutine\342\200\205vs\342\200\205Reactor.md" @@ -0,0 +1,133 @@ +--- +title: 'Coroutine vs Reactor' +lastUpdated: '2024-03-02' +--- + +코틀린 코루틴과 리액티브 스트림의 구현체 중 하나인 Reactor의 차이점은 무엇이 있을까? + +아래와 같이 사용자의 정보를 받아 환영 메세지를 생성한 후 반환하는 메서드가 있을때, 이 메서드를 호출하는 코드를 각 방식으로 작성해보자. + +```kotlin +fun generateWelcome( + usernameOrIp: String, + userProfile: UserProfile?, // 프로필 정보 + block: Block?, // 차단 여부 +): WelcomeMessage = + when { + block != null -> WelcomeMessage( + "You are blocked. Reason: ${block.reason}", + WARNING + ) + else -> WelcomeMessage( + "Hello ${userProfile?.fullName ?: usernameOrIp}", + INFO + ) + } +``` + +호출 코드는, 대략적으로 이러한 흐름을 가져야한다. + +1. usernameOrIp를 인자로 받는다. +2. 인자를 사용해 UserProfile을 조회한다. +3. 인자를 사용해 block(차단 여부)를 조회한다. +4. 조회 정보를 넣어 generateWelcome 메서드를 호출한다. + +구조를 알아보기 쉽게 명령형 방식으로 작성하자면 아래와 같이 짤 수 있다. + +```kotlin +class WelcomeService { + fun welcome(usernameOrIp: String): WelcomeMessage { + val userProfile: UserProfile? = findUserProfile(usernameOrIp) + val block: Block? = findBlock(usernameOrIp) + return generateWelcome(usernameOrIp, userProfile, block) + } +} +``` + +## Reactive Streams(Reactor) Code + +```kotlin +fun welcome(usernameOrIp: String): Mono { + return userProfileRepository.findById(usernameOrIp) + .zipWith(blockRepository.findById(usernameOrIp)) + .map { tuple -> + generateWelcome(usernameOrIp, tuple.t1, tuple.t2) + } +} +``` + +`reactive repository`인 `userProfileRepository`와 `blockRepository`는 결과값을 `Mono`에 감싸서 반환하고, 두 reactive stream은 `zip` 연산자를 통해 연결되고 있다. + +괜찮은 코드인 것 같지만 이 코드는 정상적으로 작동하지 않을 수 있다. `zip` 연산자는 인자가 비어있을 때 리액티브 스트림 후속 과정을 실행하지 않고 취소한다. 그래서 사용자 프로파일이나 차단 여부 정보 중 한 가지라도 없는 경우에는 `generateWelcome()`이 실행되지 않는다. + +위에서 명령형 방식으로 짰던 코드에서는 변수 타입을 명시적으로 nullable Type(`?`)으로 지정해서 프로그래머가 인식하고 처리할 수 있도록 했는데, Reactor를 사용하면 그러한 이점이 사라진다. 항상 프로그래머가 그 가능성을 고려하여 코드를 짜야한다. null인 경우를 고려하여 코드를 수정한 코드는 아래와 같다. + +```kotlin +fun welcome(usernameOrIp: String): Mono { + return userProfileRepository.findById(usernameOrIp) + .map { Optional.of(it) } + .defaultIfEmpty(Optional.empty()) + .zipWith( + blockRepository.findById(usernameOrIp) + .map { Optional.of(it) } + .defaultIfEmpty(Optional.empty()) + ) + .map { tuple -> + generateWelcome( + usernameOrIp, tuple.t1.orElse(null), tuple.t2.orElse(null) + ) + } +} +``` + +로직이 한눈에 파악하기 어려워졌다. 코드가 수행하고자 하는 목적이 잘 드러나지 않아서, 유지보수하기에 굉장히 어려울 것이다. + +그리고 위의 코드에서도 마찬가지였는데, 도메인과 관련 없는 reactor에 의존한 용어(tuple)가 코드에 섞이는 것도 문제가 된다. 이 코드를 이해하기 위해선 reactor의 구조와 용어를 숙지하는 과정이 필요할 것이다. + +## Coroutine Code + +```kotlin +suspend fun welcome(usernameOrIp: String): WelcomeMessage { + val userProfile = userProfileRepository.findById(usernameOrIp).awaitFirstOrNull() + val block = blockRepository.findById(usernameOrIp).awaitFirstOrNull() + return generateWelcome(usernameOrIp, userProfile, block) +} +``` + +코틀린 코루틴 코드는 suspend, awaitFirstOrNull()외에는 명령형 코드와 거의 같다. 그냥 `reactive repository`를 호출해서 사용하기만 한다. + +가독성이 굉장히 좋아졌고 간단해졌다. + +## Kotlin Coroutine + Reactor Code + +`kotlinx-coroutines-reactor`를 사용하면 코틀린 코루틴과 스프링 리액터 및 웹플럭스(WebFlux)를 함께 사용할 수 있다. + +```kotlin +@RestController +class WelcomeController( + private val welcomeService: WelcomeService +) { + @GetMapping("/welcome") + suspend fun welcome(@RequestParam ip: String) = + welcomeService.welcome(ip) +} +``` + +스프링 웹플럭스는 suspend 함수 결과를 받아서 반환하는 기능을 지원하고 있어서 컨트롤러에서 suspend 함수를 사용할 수 있다. +따라서 Reactor 기반으로 작성된 코드를 코틀린 코루틴 코드로 대체할 수 있다. + +그리고 코틀린 코루틴은 리액터 타입인 Mono를 지원하고 있기 때문에 다음과 같이 섞어쓸 수 있다. + +```kotlin +fun welcome(usernameOrIp: String): Mono { + return mono { + val userProfile = userProfileRepository.findById(usernameOrIp).awaitFirstOrNull() + val block = blockRepository.findById(usernameOrIp).awaitFirstOrNull() + generateWelcome(usernameOrIp, userProfile, block) + } +} +``` + +참고 + +https://nexocode.com/blog/posts/reactive-streams-vs-coroutines/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/Flow.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/Flow.md" new file mode 100644 index 00000000..4020914f --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/Flow.md" @@ -0,0 +1,136 @@ +--- +title: 'Flow' +lastUpdated: '2024-03-02' +--- + +coroutine의 Flow는 데이터 스트림이며, 코루틴 상에서 리액티브 프로그래밍을 지원하기 위한 구성요소이다. + +`kotlinx-coroutines-core-jvm` 라이브러리의 `kotlinx.coroutines.flow` 패키지에 인터페이스로 정의되어있다. + +```kotlin +public interface Flow { + public suspend fun collect(collector: FlowCollector) +} +``` + +Flow 생성시 연산자(`map`, `filter`, `take`, `zip` 등)들이 추가되면 Flow (SafeFlow) 의 형태로 체인을 형성하게 되고 `collect()` 호출 시 루트 스트림 (최상위 업스트림) 까지 `collect()`가 연쇄적으로 호출되어 데이터가 리프 스트림(최하위 다운스트림)까지 전달되게 된다. 모든 flow operation은 같은 코루틴 안에서 순차적으로 실행된다.(Exception이 발생한 경우에 비동기적으로 `buffer`나 `flatMapMerge`로 전달하는 경우도 있다.) + +가장 많이 쓰이는 terminal operator는 collect이다. + +```kotlin +try { + flow.collect { value -> + println("Received $value") + } +} catch (e: Exception) { + println("The flow has thrown an exception: $e") +} +``` + +이 과정에서 collect() 대신 launchIn(CoroutineScope) 를 사용하여 다음과 같이 특정 코루틴 스코프에서 실행하도록 하고, onEach 를 통해 수집할 수도 있지만, 이는 현재 스코프에서 새로운 코루틴을 실행하여 Flow 를 구독하는 헬퍼일 뿐 기본적인 내용은 변하지 않는다. + +가장 많이 쓰이는 terminal operator는 collect이다. + +```kotlin +try { + flow.collect { value -> + println("Received $value") + } +} catch (e: Exception) { + println("The flow has thrown an exception: $e") +} +``` + +flow에서 중간 연산자는 flow에서 코드를 실행하지 않고 함수 자체를 일시 중단하지 않는다. 이들은 향후 실행과 신속한 복귀를 위해 일련의 작업을 설정할 뿐이다. 이것을 cold flow 프로퍼티라고도 부른다. + +flow의 Terminal operator는 `collect`, `single`, `reduce`, `toList` 등과 같은 일시중단 함수이거나, 지정된 scope에서 flow 수집을 시작하는 `leanchIn` operator이다. 이는 upstream flow에 적용되며, 모든 operation의 실행을 trigger한다. flow를 실행한다는 것을 **flow를 collecting한다**고 얘기하기도 하며 그것은 실제로 blocking없이 항상 일시 중단하는 방식으로 수행된다. Terminal operator는 전체 upstream에 속한 연산자들의 성공 여부(Exception이 throw 되었는지)에 따라 정상적으로 또는 예외적으로 완료된다. + +`Flow` interface는 해당 flow가 같은 코드 내에서 flow가 반복적으로 collected 되거나 트리거 되는 cold stream인지, 혹은 각각 다른 running source에서 다른 값을 낼 수 있는 hot stream인지에 대한 정보를 전혀 가지고 있지 않는다. 보통 flow는 cold stream를 나타내지만 [SharedFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/index.html)라는 서브타입은 hot stream을 나타낸다. 추가적으로, flow는 `stateIn`이나 `shareIn` 연산자를 통해 hot stream으로 변환되거나, produceIn 연산자를 통해 hot channel로 변환될 수 있다. + +## Flow builder + +flow를 만드는 방법으로는 아래와 같은 것들이 있다. + +- `flowOf(...)` : 고정된 값 목록으로부터 flow를 만든다. +- `asFlow()` : List 등 타입을 가진 객체를 flow로 바꾼다. +- `flow { ... }` : 빌더 함수로 순차적 호출에서 emit 함수로 임의의 flow를 구성한다. +- `channelFlow { .. }` : 빌더 함수를 통해 잠재적으로 동시 호출에서 send 함수로의 임의의 flow를 구성한다. +- `MutableStateFlow` 및 `MutableSharedFlow`는 해당 생성자 함수를 정의하여 직접 업데이트 할 수 있는 hot flow를 생성한다. + +## Flow constraints + +Flow 인터페이스의 모든 구현은 아래에 자세히 설명된 두 가지 주요 속성을 준수해야 한다. + +- context 보존 +- Exception transparency + +이런 특성은 flow를 사용하여 코드에 대한 로컬 판단을 수행하고 업스트림 flow emitter가 다운 스트림 flow collector와 별도로 개발 할 수 있는 방식으로 코드를 모듈화하는 기능을 보장한다. flow의 사용자는 flow에서 사용하는 업스트림의 구현 세부 정보를 알 필요가 없다. + +## Context preservation + +flow는 context 보존 특성을 가지고 있다. 즉, 자체적으로 실행하는 context를 캡슐화하고 다운스트림에서 전파하거나 누출하지 않으므로 특정 변환 또는 터미널 연산자의 실행 context에 대한 판단을 간단하게 만든다. + +flow의 context를 변경하는 유일한 방법은 업스트림 context를 변경하는 `flowOn` 연산자이다. + + +```kotlin +val flowA = flowOf(1, 2, 3) + .map { it + 1 } // ctxA에서 실행된다. + .flowOn(ctxA) // 업스트림 context 변경 + +// 이제 context 보존 특성을 가진 flow가 있다. - 어딘가에서 실행되지만 이 정보는 flow 자체에 캡슐화된다. + +val filtered = flowA // ctxA는 flowA에서 캡슐화된다. + .filter { it == 3 } // 아직 context가 없는 순수 연산자 + +withContext(Dispatchers.Main) { + // 캡슐화되지 않은 모든 연산자는 Main에서 실행된다. + val result = filtered.single() + myUi.text = result +} +``` + +구현의 관점에서 모든 flow 구현은 동일한 코루틴에서만 방출되어야 한다는 것을 의미한다. 이 제약 조건은 기본 flow 빌더에 의해 효과적으로 적용되며, flow의 구현이 어떤 코루틴을 시작하지 않는 경우에는 빌더를 사용해야 한다. 이를 구현하면 대부분의 개발 실수를 방지할 수 있다. + +```kotlin +val myFlow = flow { + // GlobalScope.launch { // 금지됨 + // launch (Dispatchers.IO) { // 금지됨 + // withContext(CoroutineName( "myFlow")) // 금지됨 + emit(1) // OK + coroutineScope { + emit(2) // OK - 여전히 동일한 코루틴 + } +} +``` + +flow의 수집과 방출이 여러 코루틴으로 분리되어야하는 경우 channelFlow를 사용할 수 있다. 모든 context 보존 작업을 캡슐화하여 구현의 세부 사항이 아닌 도메인 별 문제에 집중할 수 있다. channelFlow 내에서 코루틴 빌더를 조합하여 사용할 수 있다. + +동시에 emit 되거나 context jump가 발생하는 경우가 없다고 확신하는 경우, flow 빌더 대신 `coroutineScope` 또는 `supervisorScope`와 함께 사용하여 성능을 개선할 수 있다. + +## Exception transparency + +flow 구현은 다운스트림 flow에서 발생하는 예외를 포착하거나 처리하지 않는다. 구현 관점에서 보면 emit 및 emitAll의 호출이 `try { .. } catch { .. }` 블록으로 래핑되지 않아야 한다는 것을 의미한다. flow의 예외 처리는 catch 연산자로 수행되어야 하며 이 연산자는 모든 다운스트림에게 예외를 전달하는 동안 업스트림 flow에서 발생하는 예외만 catch 하도록 설계되었다. 마찬가지로 collect와 같은 터미널 연산자는 코드 또는 업스트림 flow에서 발생하는 처리되지 않는 예외를 발생시킨다. + +```kotlin +flow { emitData() } + .map { computeOne(it) } + .catch {...} // emitData 및 computeOne에서 예외 포착 + .map { computeTwo(it) } + .collect { process(it) } // 다음에서 예외 발생 처리 및 computeTwo +``` + +finally 블록에 대한 대체로 `onCompletion` 연산자에도 동일한 추론을 적용할 수 있다. + +예외 투명성의 요구 사항을 준수하지 않으면 `collect { .. }`의 예외로 인하여 코드에 대한 추론을 어렵게 만드는 이상한 동작이 발생할 수 있다. 왜냐하면 exception이 업스트림 flow에 의해 어떻게든 “caugth”되어 로컬 추론 능력을 제한할 수 있기 때문이다. + +flow는 런타임에 **예외 투명성**을 적용하여 이전 시도에서 예외가 발생된 경우 값을 emit하려는 모든 시도에서 `IllegalStateException`을 던진다. + +## Reactive streams + +Flow는 Reactive Stream과 호환되므로 `Flow.asPublisher` 및 `Publisher.asFlow`를 사용하여 `kotlin-coroutines-reactive` 모듈의 리액티브 스트림과 안전하게 상호 작용할 수 있다. + +--- +참고 +- https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/ +- https://myungpyo.medium.com/%EC%BD%94%EB%A3%A8%ED%8B%B4-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%82%B4%EB%B6%80-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-eb4d9dfebe43 \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/cold\342\200\205stream\352\263\274\342\200\205hot\342\200\205stream.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/cold\342\200\205stream\352\263\274\342\200\205hot\342\200\205stream.md" new file mode 100644 index 00000000..a73f6928 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/cold\342\200\205stream\352\263\274\342\200\205hot\342\200\205stream.md" @@ -0,0 +1,70 @@ +--- +title: 'cold stream과 hot stream' +lastUpdated: '2024-03-02' +--- + +flow에서 사용되는 개념인 cold stream과 hot stream에 대해 알아보고, 서로 비교해보자. + +우선 observable과 producer의 개념에 대해 알아보자. + +### observable + +Observable은 observer를 producer에 연결해주는 함수이다. obserable을 함수처럼 call하면 observer에 값이 전달된다고 생각하면 된다. + +### producer + +producer는 obserable의 실행 값이다. producer는 web socket이 될 수도 있고, DOM event가 될 수 있고, iterator도 될 수 있다. 보통은 `observer.next(value)`를 통해 값을 받는 것이 producer라고 생각할 수 있다. + +## cold stream + +cold stream은 스트림의 구독자마다 개별 스트림이 생성/시작되어 데이터를 전달하며 그렇기에 스트림 생성 후 구독하지 않으면 어떤 연산도 발생하지 않는다. + +producer가 구독하는 동안 만들어지고 동작한다. Cold 스크림에는 구독자가 오로지 1명 뿐이며, 해당 구독자에게만 값을 내보낸다. (따라서 Cold 스트림에는 unicast 매커니즘이 있다고 볼 수 있다.) + +누군가가 구독할 때만 값의 방출이 시작되기 때문에 lazy한 특성이 있다고도 얘기할 수 있다. + +아래는 webSocket을 listen하는 cold stream의 예시이다. + +```kotlin +// COLD +const source = new Observable((observer) => { + const socket = new WebSocket('ws://someurl'); + socket.addEventListener('message', (e) => observer.next(e)); + return () => socket.close(); +}); +``` + +일부 값을 생성하기 위해 실행될 코드 라인은 호출하는 주체에 따라 달라지지 않고, 모든 구독자에 대해 동일하다는 점에 유의해야 한다. + +## Hot Stream + +스트림이 생성되면 바로 연산이 시작되고 데이터를 전달하며 다수의 구독자가 동일한 스트림에서 데이터를 전달 받을 수 있다. + +Hot 스트림은 0개 이상의 구독자를 가질 수 있으며 동시에 모든 구독자에게 값을 내보낸다. 따라서 Hot 스트림에는 multicast 매커니즘이 있다고 볼 수 있다. + +아래는 webSocket을 listen하는 hot stream의 예시이다. + +```kotlin +const socket = new WebSocket('ws://someurl'); +const source = new Observable((observer) => { + socket.addEventListener('message', (e) => observer.next(e)); +}); +``` + +Cold stream을 쓰는 경우 producer 객체를 매번 생성해야하기 때문에 낭비가 생길 수 있다. 그렇기 때문에 상황에 맞게 producer를 묶어서 실행해야하는 경우 hot stream, 그렇지 않은 경우에는 cold stream을 선택하여 사용해야 한다. + +필요하다면 각 라이브러리별로 cold stream을 hot으로 만드는 방법이 있으니 고려해보면 좋다. + +```js +function makeHot(cold) { + const subject = new Subject(); + cold.subscribe(subject); + return new Observable((observer) => subject.subscribe(observer)); +} +``` + +--- + +참고 +- https://luukgruijs.medium.com/understanding-hot-vs-cold-observables-62d04cf92e03 +- https://myungpyo.medium.com/%EC%BD%94%EB%A3%A8%ED%8B%B4-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%82%B4%EB%B6%80-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-eb4d9dfebe43 \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Channel.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Channel.md" new file mode 100644 index 00000000..dcfa5543 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Channel.md" @@ -0,0 +1,98 @@ +--- +title: 'Channel' +lastUpdated: '2024-03-02' +--- + +Channel은 2개의 Coroutine 사이를 연결한 파이프라고 생각하면 된다. + +이 파이프는 두 Coroutine 사이에서 정보를 전송할 수 있도록 한다. + +하나의 Coroutine은 파이프를 통해서 정보를 보낼 수 있고, 다른 하나의 Coroutine은 정보를 받기위해 기다린다. + +이 채널을 통한 두 Coroutine간의 커뮤니케이션은 공유메모리가 아닌, 커뮤니케이션을 통해서 이뤄진다. + +### Thread는 어떻게 Communicate 할까 + +소프트웨어를 만들면서 Resource를 Blocking하는 작업, 예를 들면 네트워킹이나 DB를 사용하거나 아니면 계산이 필요한 작업을 할때 우리는 그 작업들을 쓰레드로 분리한다. + +이 쓰레드간에 공유할 자원이 필요할때 우리는 두개의 쓰레드가 동시에 그걸 쓰거나 읽게 하지 못하도록 자원을 [lock]()한다. 이것이 흔히 쓰레드가 커뮤니케이션하는 방식이다. 하지만 이렇게하면 데드록, 레이스 컨디션 같은 이슈가 발생할 수 있다. + +* 데드록: 교착상태라고도 하며 한정된 자원을 여러 곳에서 사용하려고 할때 발생 + +* 레이스 컨디션: 한정된 공유 자원을 여러 프로세스가 동시에 이용하기 위해 경쟁을 벌이는 현상 + +```kotlin +fun main(args: Array) = runBlocking { + val channel = Channel() + launch(coroutineContext) { + repeat(10) { i -> + delay(100) + channel.send(i) + } + channel.close() + } + launch(coroutineContext) { + for(i in chan) { + println(i) + } + } +} +``` + +## 채널 버퍼의 타입 + +Channel은 여러 버퍼 타입을 통해 Coroutine과의 커뮤니케이션의 유연성을 제공한다. + +### 1. Rendezvous (Unbuffered) + +![image](https://github.com/rlaisqls/TIL/assets/81006587/87c25464-da85-463f-ab42-df8af98f319d) + +```kotlin +val channel = Channel(capacity = Channel.RENDEZVOUS) +``` + +특별한 채널 버퍼를 설정하지 않을 시 이 타입이 기본적으로 설정된다. `Rendezvous`는 버퍼가 없다. 이것이 위 예제코드에서 본 것처럼 수신측 Coroutine과 송신측 Coroutine이 모두 가능한 상태로 "모일때까지" suspend 되는 이유다. + + +### 2. Conflated + +![image](https://github.com/rlaisqls/TIL/assets/81006587/331cff90-f0c8-4d0a-be46-42cab0fa2805) + +```kotlin +val channel = Channel(capacity = Channel.CONFLATED) +``` + +Conflate의 뜻은 `"융합하다"`, `"하나로 합치다"`이다. + +이렇게 하면 크기가 1인 고정 버퍼가 있는 채널이 생성된다. 만약에 수신하는 Coroutine이 송신하는 Coroutine을 따라잡지 못했다면, 송신하는 쪽은 새로운 값을 버퍼의 마지막 아이템에 덮어씌운다. 수신 Coroutine이 다음 값을 받을 차례가 되면, 송신 Coroutine이 보낸 마지막 값을 받는다. 즉 송신하는 쪽은 수신측 Coroutine이 가능할때까지 기다리는게 없다는 말이다. 수신측 Coroutine은 채널 버퍼에 값이 올때까지 suspend 된다. + +### 3. Buffered + +![image](https://github.com/rlaisqls/TIL/assets/81006587/229ad9aa-0860-42a4-a2a9-6e9f7672d69c) + +```kotlin +val channel = Channel(capacity = 10) +``` + +이 모드는 고정된 크기의 버퍼를 생성한다. 버퍼는 Array 형식이다. + +송신 Coroutine은 버퍼가 꽉 차있으면 새로운 값을 보내는 걸 중단한다. 수신 Coroutine은 버퍼가 빌때까지 계속해서 꺼내서 수행한다. + +### 4. Unlimited + +```kotlin +val channel = Channel(capacity = Channel.UNLIMITED) +``` + +이 모드는 이름처럼 제한 없는 크기의 버퍼를 가진다. 버퍼는 LinkedList 형식이다. + +만약에 버퍼가 소비되지 않았다면 메모리가 다 찰때까지 계속해서 아이템을 채운다. 결국엔 `OutOfMemeoryException`을 일으킬 수 있다. + +송신 Coroutine은 suspend 되지않지만, 수신 Coroutine은 버퍼가 비면 suspend 된다. + +--- + +참고 +- https://proandroiddev.com/kotlin-coroutines-channels-csp-android-db441400965f +- https://www.youtube.com/watch?v=YrrUCSi72E8&t=110s +- https://en.wikipedia.org/wiki/Communicating_sequential_processes \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205CPS.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205CPS.md" new file mode 100644 index 00000000..2c9d655e --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205CPS.md" @@ -0,0 +1,201 @@ +--- +title: 'Coroutine CPS' +lastUpdated: '2024-03-02' +--- + +Kotlin coroutines are managed at the JVM level using the concept of **"continuations."** Continuations are a programming technique that allows suspending the execution of a function at a certain point and resuming it later from where it left off. Coroutines build upon this concept to provide a high-level and efficient way to write asynchronous code. + +## Continuation + +When a code with suspend is converted to bytecode, a parameter is added to the end of the function to convert it into handing over an object called Continuation. + +```kotlin +// suspend fun createPost(token: Token, item: Item): Post { … } + ↓ +// Java/JVM +Object createPost(Token token, Item item, Continuation cont) { … } +``` + +When such a suspend function is called from the coroutine, the execution information at that time is cached by making it a `Continuation` object, and when the execution is resumed (Resume), the execution is resumed based on the stored execution information. + +```kotlin +/** + * Interface representing a continuation after a suspension point that returns a value of type `T`. + */ +@SinceKotlin("1.3") +public interface Continuation { + /** + * The context of the coroutine that corresponds to this continuation. + */ + public val context: CoroutineContext + + /** + * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the + * return value of the last suspension point. + */ + public fun resumeWith(result: Result) +} +``` + +Continuation is just a geneeric callback that any suspending function actually uses behind the scenes. You can't see it, every time you call the suspend function, it's actually called back. + +## Labels + +It also performs labeling for recognized callback points. The Kotlin compiler checks and labels the suspension points, as they require suspension points when resuming. + +```kotlin + suspen fun postItem(item: Item) { + // LABEL 0 + val token = requestToken() + // LABEL 1 + val post = createPost(token, item) + // LABEL 2 + processPost(post) + } +``` + +```kotlin +fun postItem(item: Item, cont: Continuation) { + + val sm = cont as? ThisSM ?: object : ThisSM { + fun resume(…) { + postItem(null, this) + } + } + + switch (sm.label) { + case 0: + sm.item = item + sm.label = 1 + requestToken(sm) + case 1: + createPost(token, item, sm) + … + } +} +``` + +`sm` on the above code means `state machine`, the state (the result of the operations done so far) when each function is called. + +This `state machine` is eventually **Continuation**, and Corutin operates internally as Continuation passes in a form with some information value. This style is called as **continuation-passing style (CPS)**. + +Each suspend function takes Continuation (sm in the code above) as the last parameter, so : + +- if `requestToken(sm)` is completed, `resume()` is called in `sm(continuation)`. +- The `createPost(token, item, sm)` is called again, and even when it is completed, the form of calling `resume()` to `sm(continuation)` is repeated. + +So what is `resume()` for? In the code above, `resume()` is what eventually calls itself. (`postItem(…)` Inside the postItem(…) is being recalled.) + +- For example, when the operation of suspend function `requestToken(sm)` is finished, `postItem(…)` again through `resume()` is called, which increases the Label value by one so that another case is called. In this case, internally, it is as if the suspend function is called and then the next case, and then the next case. + +## Decompile the code + +```kotlin +fun main(): Unit { + GlobalScope.launch { + val userData = fetchUserData() + val userCache = cacheUserData(userData) + updateTextView(userCache) + } +} + +suspend fun fetchUserData() = "user_name" + +suspend fun cacheUserData(user: String) = user + +fun updateTextView(user: String) = user + +``` + +Let's make the above code into Kotlin's byte code, then decompose it into Java code. + + + +```kotlin +public final class Example_nomagic_01Kt { + public static final void main() { + BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) { + int label; + + @Nullable + public final Object invokeSuspend(@NotNull Object $result) { + Object var10000; + label17: { + Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); + switch(this.label) { + case 0: + ResultKt.throwOnFailure($result); + this.label = 1; + var10000 = Example_nomagic_01Kt.fetchUserData(this); + if (var10000 == var4) { + return var4; + } + break; + case 1: + ResultKt.throwOnFailure($result); + var10000 = $result; + break; + case 2: + ResultKt.throwOnFailure($result); + var10000 = $result; + break label17; + default: + throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); + } + + String userData = (String)var10000; + this.label = 2; + var10000 = Example_nomagic_01Kt.cacheUserData(userData, this); + if (var10000 == var4) { + return var4; + } + } + + String userCache = (String)var10000; + Example_nomagic_01Kt.updateTextView(userCache); + return Unit.INSTANCE; + } + + @NotNull + public final Continuation create(@Nullable Object value, @NotNull Continuation completion) { + Intrinsics.checkNotNullParameter(completion, "completion"); + Function2 var3 = new (completion); + return var3; + } + + public final Object invoke(Object var1, Object var2) { + return (()this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE); + } + }), 3, (Object)null); + } + + // $FF: synthetic method + public static void main(String[] var0) { + main(); + } + + @Nullable + public static final Object fetchUserData(@NotNull Continuation $completion) { + return "user_name"; + } + + @Nullable + public static final Object cacheUserData(@NotNull String user, @NotNull Continuation $completion) { + return user; + } + + @NotNull + public static final String updateTextView(@NotNull String user) { + Intrinsics.checkNotNullParameter(user, "user"); + return user; + } +} +``` + +It can be seen that functions that were suspend functions were changed to general functions and Continuation was included as the last parameter. + +--- + +reference +- https://www.youtube.com/watch?v=YrrUCSi72E8&t=110s +- https://kotlinlang.org/spec/asynchronous-programming-with-coroutines.html#suspending-functions diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Delay.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Delay.md" new file mode 100644 index 00000000..a9614fcb --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Delay.md" @@ -0,0 +1,289 @@ +--- +title: 'Coroutine Delay' +lastUpdated: '2024-03-02' +--- + +아래와 같은 timeout(혹은 delay)가 내부적으로 어떻게 동작하는지 알아보자. + +```kotlin +GlobalScope.launch { + withTimeout(10) { + ... + } +} +``` + +일단 `withTimeout`의 코드를 살펴보자면 아래와 같다. + +가장 아랫부분을 보면 time이 실제로 track되는 것은 CoroutineDispatcher의 구현이라고 적혀있다. + +> Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher]. + +```kotlin +/** + * Runs a given suspending [block] of code inside a coroutine with a specified [timeout][timeMillis] and throws + * a [TimeoutCancellationException] if the timeout was exceeded. + * + * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of + * the cancellable suspending function inside the block throws a [TimeoutCancellationException]. + * + * The sibling function that does not throw an exception on timeout is [withTimeoutOrNull]. + * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. + * + * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time, + * even right before the return from inside of the timeout [block]. Keep this in mind if you open or acquire some + * resource inside the [block] that needs closing or release outside of the block. + * See the + * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources] + * section of the coroutines guide for details. + * + * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher]. + * + * @param timeMillis timeout time in milliseconds. + */ +public suspend fun withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + if (timeMillis <= 0L) throw TimeoutCancellationException("Timed out immediately") + return suspendCoroutineUninterceptedOrReturn { uCont -> + setupTimeout(TimeoutCoroutine(timeMillis, uCont), block) + } +} +``` + +
+TimeoutCoroutine + +```kotlin +private class TimeoutCoroutine( + @JvmField val time: Long, + uCont: Continuation // unintercepted continuation +) : ScopeCoroutine(uCont.context, uCont), Runnable { + + // 이 run은 runnable에서 상속받은 메서드이고, cancel하기 위해 사용된다. + override fun run() { + cancelCoroutine(TimeoutCancellationException(time, this)) + } + + override fun nameString(): String = + "${super.nameString()}(timeMillis=$time)" +} + +public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 { + final override val key: CoroutineContext.Key<*> get() = Job + + ... + public fun cancelCoroutine(cause: Throwable?): Boolean = cancelImpl(cause) + + // TimeoutCoroutine에서 run을 호출하면, 여기로 와서 적절히 cancel된다. + // cause is Throwable or ParentJob when cancelChild was invoked + // returns true is exception was handled, false otherwise + internal fun cancelImpl(cause: Any?): Boolean { + var finalState: Any? = COMPLETING_ALREADY + if (onCancelComplete) { + // make sure it is completing, if cancelMakeCompleting returns state it means it had make it + // completing and had recorded exception + finalState = cancelMakeCompleting(cause) + if (finalState === COMPLETING_WAITING_CHILDREN) return true + } + if (finalState === COMPLETING_ALREADY) { + finalState = makeCancelling(cause) + } + return when { + finalState === COMPLETING_ALREADY -> true + finalState === COMPLETING_WAITING_CHILDREN -> true + finalState === TOO_LATE_TO_CANCEL -> false + else -> { + afterCompletion(finalState) + true + } + } + } + ... +} +``` + +
+ +```kotlin +private fun setupTimeout( + coroutine: TimeoutCoroutine, + block: suspend CoroutineScope.() -> T +): Any? { + + // schedule cancellation of this coroutine on time + val cont = coroutine.uCont + val context = cont.context + coroutine.disposeOnCompletion(context.delay.invokeOnTimeout(coroutine.time, coroutine, coroutine.context)) // <-- + + // restart the block using a new coroutine with a new job, + // however, start it undispatched, because we already are in the proper context + return coroutine.startUndispatchedOrReturnIgnoreTimeout(coroutine, block) +} +``` + +`context.delay`를 가져와서 `invokeOnTimeout`를 호출한다. 그리고 block을 restart해서 새로운 job을 가지도록 만든다. (delay 후 다시 시작되어야하기 떄문) + +```kotlin +/** Returns [Delay] implementation of the given context */ +internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay + +internal actual val DefaultDelay: Delay = initializeDefaultDelay() + +private fun initializeDefaultDelay(): Delay { + // Opt-out flag + if (!defaultMainDelayOptIn) return DefaultExecutor + val main = Dispatchers.Main + /* + * When we already are working with UI and Main threads, it makes + * no sense to create a separate thread with timer that cannot be controller + * by the UI runtime. + */ + return if (main.isMissing() || main !is Delay) DefaultExecutor else main // <-- +} +``` + +`CoroutineContext.delay`는 기본적으로 `DefaultExecutor`라는 object를 가져온다. + +## DefaultExecuter + +```kotlin +internal actual object DefaultExecutor : EventLoopImplBase(), Runnable { + + override fun run() { + // TreadLocalEventLoop에 등록되어 동작한다. + // TreadLocalEventLoop는 @ThreadLocal이 달려있고, object로 선언되어있어 모든 스레드마다 각 1개씩 생성된다. + // eventLoop로 등록되므로 일정 주기마다 실행된다. (@InternalCoroutinesApi) + ThreadLocalEventLoop.setEventLoop(this) + ... + } + + // timeout일때 block을 run 시켜준다. + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = + scheduleInvokeOnTimeout(timeMillis, block) + + protected fun scheduleInvokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle { + val timeNanos = delayToNanos(timeMillis) + return if (timeNanos < MAX_DELAY_NS) { + val now = nanoTime() + DelayedRunnableTask(now + timeNanos, block).also { task -> + schedule(now, task) + } + } else { + NonDisposableHandle + } + } + + // 이 부분은 delayedQueue가 있는지 확인하고, task를 받았을때 스케줄링해주는 함수이다. + // 위를 보면 알겠지만 DelayedRunnableTask에 람다로 들어간다. + public fun schedule(now: Long, delayedTask: DelayedTask) { + when (scheduleImpl(now, delayedTask)) { + SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark() + SCHEDULE_COMPLETED -> reschedule(now, delayedTask) + SCHEDULE_DISPOSED -> {} // do nothing -- task was already disposed + else -> error("unexpected result") + } + } + private fun scheduleImpl(now: Long, delayedTask: DelayedTask): Int { + if (isCompleted) return SCHEDULE_COMPLETED + val delayedQueue = _delayed.value ?: run { + _delayed.compareAndSet(null, DelayedTaskQueue(now)) + _delayed.value!! + } + return delayedTask.scheduleTask(now, delayedQueue, this) + } + ... +} +``` + +## DelayedRunnableTask와 DelayedTask + +```kotlin + +private class DelayedRunnableTask( + nanoTime: Long, + private val block: Runnable +) : DelayedTask(nanoTime) { // <-- + override fun run() { block.run() } + override fun toString(): String = super.toString() + block.toString() +} +``` + +```kotlin + internal abstract class DelayedTask( + /** + * This field can be only modified in [scheduleTask] before putting this DelayedTask + * into heap to avoid overflow and corruption of heap data structure. + */ + @JvmField var nanoTime: Long + ) : Runnable, Comparable, DisposableHandle, ThreadSafeHeapNode { + @Volatile + private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK + + override var heap: ThreadSafeHeap<*>? + get() = _heap as? ThreadSafeHeap<*> + set(value) { + require(_heap !== DISPOSED_TASK) // this can never happen, it is always checked before adding/removing + _heap = value + } + + override var index: Int = -1 + + // EventLoop#DefaultExecutor()에서 DelayedTask(DisposableHandle)를 반환하면 이 메서드를 통해 비교되고 실행된다. + override fun compareTo(other: DelayedTask): Int { + val dTime = nanoTime - other.nanoTime + return when { + dTime > 0 -> 1 + dTime < 0 -> -1 + else -> 0 + } + } + + fun timeToExecute(now: Long): Boolean = now - nanoTime >= 0L + + // 실제로 스케줄링 하는 부분 + @Synchronized + fun scheduleTask(now: Long, delayed: DelayedTaskQueue, eventLoop: EventLoopImplBase): Int { + if (_heap === DISPOSED_TASK) return SCHEDULE_DISPOSED // don't add -- was already disposed + delayed.addLastIf(this) { firstTask -> + if (eventLoop.isCompleted) return SCHEDULE_COMPLETED // non-local return from scheduleTask + /** + * We are about to add new task and we have to make sure that [DelayedTaskQueue] + * invariant is maintained. The code in this lambda is additionally executed under + * the lock of [DelayedTaskQueue] and working with [DelayedTaskQueue.timeNow] here is thread-safe. + */ + if (firstTask == null) { + /** + * When adding the first delayed task we simply update queue's [DelayedTaskQueue.timeNow] to + * the current now time even if that means "going backwards in time". This makes the structure + * self-correcting in spite of wild jumps in `nanoTime()` measurements once all delayed tasks + * are removed from the delayed queue for execution. + */ + delayed.timeNow = now + } else { + /** + * Carefully update [DelayedTaskQueue.timeNow] so that it does not sweep past first's tasks time + * and only goes forward in time. We cannot let it go backwards in time or invariant can be + * violated for tasks that were already scheduled. + */ + val firstTime = firstTask.nanoTime + // compute min(now, firstTime) using a wrap-safe check + val minTime = if (firstTime - now >= 0) now else firstTime + // update timeNow only when going forward in time + if (minTime - delayed.timeNow > 0) delayed.timeNow = minTime + } + /** + * Here [DelayedTaskQueue.timeNow] was already modified and we have to double-check that newly added + * task does not violate [DelayedTaskQueue] invariant because of that. Note also that this scheduleTask + * function can be called to reschedule from one queue to another and this might be another reason + * where new task's time might now violate invariant. + * We correct invariant violation (if any) by simply changing this task's time to now. + */ + if (nanoTime - delayed.timeNow < 0) nanoTime = delayed.timeNow + true + } + return SCHEDULE_OK + } + } +``` diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Dispatcher.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Dispatcher.md" new file mode 100644 index 00000000..bd42fd50 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Dispatcher.md" @@ -0,0 +1,384 @@ +--- +title: 'Coroutine Dispatcher' +lastUpdated: '2024-03-02' +--- + +Coroutines always execute in some context represented by a value of the [CoroutineContext](Coroutine%E2%80%85Scope%2C%E2%80%85Context.md) type, defined in the Kotlin standard library. + +The coroutine context is a set of various elements. The main elements are the Job of the coroutine, which we've seen before, and its dispatcher, which is covered in this section. + +The coroutine context includes a coroutine dispatcher **that determines what thread or threads the corresponding coroutine uses for its execution.** The coroutine dispatcher can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined. + +It is declared as base class to be extended by all coroutine dispatcher implementations. + +
+Coroutine Dispatcher's code + +(Actually, this below code and java doc has a many information. The contents to be discussed below are described, I think read this is very useful to additional understanding.) + +**java doc** + +Base class to be extended by all coroutine dispatcher implementations. + +The following standard implementations are provided by `kotlinx.coroutines` as properties on the [Dispatchers] object: + +- [Dispatchers.Default] is used by all standard builders if no dispatcher or any other [ContinuationInterceptor] is specified in their context. It uses a common pool of shared background threads. +This is an appropriate choice for compute-intensive coroutines that consume CPU resources. +- [Dispatchers.IO] : uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive `_blocking_` operations (like file I/O and blocking socket I/O). +- [Dispatchers.Unconfined] — starts coroutine execution in the current call-frame until the first suspension, where upon the coroutine builder function returns. The coroutine will later resume in whatever thread used by the corresponding suspending function, without confining it to any specific thread or pool. The `Unconfined` dispatcher should not normally be used in code**. +- Private thread pools can be created with [newSingleThreadContext] and [newFixedThreadPoolContext]. +- An arbitrary [Executor][java.util.concurrent.Executor] can be converted to a dispatcher with the [asCoroutineDispatcher] extension function. +- This class ensures that debugging facilities in [newCoroutineContext] function work properly. + +**code** + +```kotlin +public abstract class CoroutineDispatcher : + AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { + + /** @suppress */ + @ExperimentalStdlibApi + public companion object Key : AbstractCoroutineContextKey( + ContinuationInterceptor, + { it as? CoroutineDispatcher }) + + /** + * Returns `true` if the execution of the coroutine should be performed with [dispatch] method. + * The default behavior for most dispatchers is to return `true`. + * + * If this method returns `false`, the coroutine is resumed immediately in the current thread, + * potentially forming an event-loop to prevent stack overflows. + * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation. + * + * The [context] parameter represents the context of the coroutine that is being dispatched, + * or [EmptyCoroutineContext] if a non-coroutine-specific [Runnable] is dispatched instead. + * + * A dispatcher can override this method to provide a performance optimization and avoid paying a cost of an unnecessary dispatch. + * E.g. [MainCoroutineDispatcher.immediate] checks whether we are already in the required UI thread in this method and avoids + * an additional dispatch when it is not required. + * + * While this approach can be more efficient, it is not chosen by default to provide a consistent dispatching behaviour + * so that users won't observe unexpected and non-consistent order of events by default. + * + * Coroutine builders like [launch][CoroutineScope.launch] and [async][CoroutineScope.async] accept an optional [CoroutineStart] + * parameter that allows one to optionally choose the [undispatched][CoroutineStart.UNDISPATCHED] behavior to start coroutine immediately, + * but to be resumed only in the provided dispatcher. + * + * This method should generally be exception-safe. An exception thrown from this method + * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state. + * + * @see dispatch + * @see Dispatchers.Unconfined + */ + public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true + + /** + * Creates a view of the current dispatcher that limits the parallelism to the given [value][parallelism]. + * The resulting view uses the original dispatcher for execution, but with the guarantee that + * no more than [parallelism] coroutines are executed at the same time. + * + * This method does not impose restrictions on the number of views or the total sum of parallelism values, + * each view controls its own parallelism independently with the guarantee that the effective parallelism + * of all views cannot exceed the actual parallelism of the original dispatcher. + * + * ### Limitations + * + * The default implementation of `limitedParallelism` does not support direct dispatchers, + * such as executing the given runnable in place during [dispatch] calls. + * Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct. + * For direct dispatchers, it is recommended to override this method + * and provide a domain-specific implementation or to throw an [UnsupportedOperationException]. + * + * ### Example of usage + * ``` + * private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background") + * // At most 2 threads will be processing images as it is really slow and CPU-intensive + * private val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(2) + * // At most 3 threads will be processing JSON to avoid image processing starvation + * private val jsonProcessingDispatcher = backgroundDispatcher.limitedParallelism(3) + * // At most 1 thread will be doing IO + * private val fileWriterDispatcher = backgroundDispatcher.limitedParallelism(1) + * ``` + * Note how in this example the application has an executor with 4 threads, but the total sum of all limits + * is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism. + * + * Note that this example was structured in such a way that it illustrates the parallelism guarantees. + * In practice, it is usually better to use [Dispatchers.IO] or [Dispatchers.Default] instead of creating a + * `backgroundDispatcher`. It is both possible and advised to call `limitedParallelism` on them. + */ + @ExperimentalCoroutinesApi + public open fun limitedParallelism(parallelism: Int): CoroutineDispatcher { + parallelism.checkParallelism() + return LimitedDispatcher(this, parallelism) + } + + /** + * Requests execution of a runnable [block]. + * The dispatcher guarantees that [block] will eventually execute, typically by dispatching it to a thread pool, + * using a dedicated thread, or just executing the block in place. + * The [context] parameter represents the context of the coroutine that is being dispatched, + * or [EmptyCoroutineContext] if a non-coroutine-specific [Runnable] is dispatched instead. + * Implementations may use [context] for additional context-specific information, + * such as priority, whether the dispatched coroutine can be invoked in place, + * coroutine name, and additional diagnostic elements. + * + * This method should guarantee that the given [block] will be eventually invoked, + * otherwise the system may reach a deadlock state and never leave it. + * The cancellation mechanism is transparent for [CoroutineDispatcher] and is managed by [block] internals. + * + * This method should generally be exception-safe. An exception thrown from this method + * may leave the coroutines that use this dispatcher in an inconsistent and hard-to-debug state. + * + * This method must not immediately call [block]. Doing so may result in `StackOverflowError` + * when `dispatch` is invoked repeatedly, for example when [yield] is called in a loop. + * In order to execute a block in place, it is required to return `false` from [isDispatchNeeded] + * and delegate the `dispatch` implementation to `Dispatchers.Unconfined.dispatch` in such cases. + * To support this, the coroutines machinery ensures in-place execution and forms an event-loop to + * avoid unbound recursion. + * + * @see isDispatchNeeded + * @see Dispatchers.Unconfined + */ + public abstract fun dispatch(context: CoroutineContext, block: Runnable) + + /** + * Dispatches execution of a runnable `block` onto another thread in the given `context` + * with a hint for the dispatcher that the current dispatch is triggered by a [yield] call, so that the execution of this + * continuation may be delayed in favor of already dispatched coroutines. + * + * Though the `yield` marker may be passed as a part of [context], this + * is a separate method for performance reasons. + * + * @suppress **This an internal API and should not be used from general code.** + */ + @InternalCoroutinesApi + public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = dispatch(context, block) + + /** + * Returns a continuation that wraps the provided [continuation], thus intercepting all resumptions. + * + * This method should generally be exception-safe. An exception thrown from this method + * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state. + */ + public final override fun interceptContinuation(continuation: Continuation): Continuation = + DispatchedContinuation(this, continuation) + + public final override fun releaseInterceptedContinuation(continuation: Continuation<*>) { + /* + * Unconditional cast is safe here: we only return DispatchedContinuation from `interceptContinuation`, + * any ClassCastException can only indicate compiler bug + */ + val dispatched = continuation as DispatchedContinuation<*> + dispatched.release() + } + + /** + * @suppress **Error**: Operator '+' on two CoroutineDispatcher objects is meaningless. + * CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts. + * The dispatcher to the right of `+` just replaces the dispatcher to the left. + */ + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated( + message = "Operator '+' on two CoroutineDispatcher objects is meaningless. " + + "CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " + + "The dispatcher to the right of `+` just replaces the dispatcher to the left.", + level = DeprecationLevel.ERROR + ) + public operator fun plus(other: CoroutineDispatcher): CoroutineDispatcher = other + + /** @suppress for nicer debugging */ + override fun toString(): String = "$classSimpleName@$hexAddress" +} +``` + +
+ +--- + +Try the below example + +```kotlin +fun main() = runBlocking { + launch { // context of the parent, main runBlocking coroutine + println("main runBlocking : I'm working in thread ${Thread.currentThread().name}") + } + launch(Dispatchers.Unconfined) { // not confined -- will work with main thread + println("Unconfined : I'm working in thread ${Thread.currentThread().name}") + } + launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher + println("Default : I'm working in thread ${Thread.currentThread().name}") + } + launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread + println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}") + } +} +``` + +It produces the following output (maybe in different order): + +```kotlin +Unconfined : I'm working in thread main +Default : I'm working in thread DefaultDispatcher-worker-1 +newSingleThreadContext: I'm working in thread MyOwnThread +main runBlocking : I'm working in thread main +``` + +- When `launch { ... }` is used without parameters, it inherits the context (and thus dispatcher) from the `CoroutineScope` it is being launched from. In this case, it inherits the context of the main `runBlocking` coroutine which runs in the main thread. + +- `Dispatchers.Unconfined` is a special dispatcher that also appears to run in the main thread, but it is, in fact, a different mechanism that is explained later. + +- The default dispatcher is used when no other dispatcher is explicitly specified in the scope. It is represented by `Dispatchers.Default` and uses a shared background pool of threads. + +- `newSingleThreadContext` creates a thread for the coroutine to run. A dedicated thread is a very expensive resource. In a real application it must be either released, when no longer needed, using the close function, or stored in a top-level variable and reused throughout the application. + +## Unconfined vs confined dispatcher + +The `Dispatchers.Unconfined` coroutine dispatcher starts a coroutine in the caller thread, but only until the first suspension point. The unconfined dispatcher is appropriate for coroutines which neither consume CPU time nor update any shared data confined to a specific thread. + +On the other side, the dispatcher is inherited from the outer CoroutineScope by default. The default dispatcher for the runBlocking coroutine, in particular, is confined to the invoker thread, so inheriting it has the effect of confining execution to this thread with predictable FIFO scheduling. + +```kotlin +launch(Dispatchers.Unconfined) { // not confined -- will work with main thread + println("Unconfined : I'm working in thread ${Thread.currentThread().name}") + delay(500) + println("Unconfined : After delay in thread ${Thread.currentThread().name}") +} +launch { // context of the parent, main runBlocking coroutine + println("main runBlocking: I'm working in thread ${Thread.currentThread().name}") + delay(1000) + println("main runBlocking: After delay in thread ${Thread.currentThread().name}") +} +``` + +Produces the output: + +```kotlin +Unconfined : I'm working in thread main +main runBlocking: I'm working in thread main +Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor +main runBlocking: After delay in thread main +``` + +So, the coroutine with the context inherited from `runBlocking {...}` continues to execute in the main thread, while the unconfined one resumes in the default executor thread that the delay function is using. + +> The unconfined dispatcher is an advanced mechanism that can be helpful in certain corner cases where dispatching of a coroutine for its execution later is not needed or produces undesirable side-effects, because some operation in a coroutine must be performed right away. The unconfined dispatcher should not be used in general code. + +## Debugging coroutines and threads + +Coroutines can suspend on one thread and resume on another thread. Even with a single-threaded dispatcher it might be hard to figure out what the coroutine was doing, where, and when if you don't have special tooling. + +### Debugging with IDEA + +The Coroutine Debugger of the Kotlin plugin simplifies debugging coroutines in IntelliJ IDEA. + +> Debugging works for versions 1.3.8 or later of kotlinx-coroutines-core. + +The Debug tool window contains the Coroutines tab. In this tab, you can find information about both currently running and suspended coroutines. The coroutines are grouped by the dispatcher they are running on. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/fd2f2ff4-07e6-4465-942d-b06005ac9ad7) + +With the coroutine debugger, you can: +- Check the state of each coroutine. +- See the values of local and captured variables for both running and suspended coroutines. +- See a full coroutine creation stack, as well as a call stack inside the coroutine. The stack includes all frames with variable values, even those that would be lost during standard debugging. +- Get a full report that contains the state of each coroutine and its stack. To obtain it, right-click inside the Coroutines tab, and then click **Get Coroutines Dump.** +- To start coroutine debugging, you just need to set breakpoints and run the application in debug mode. + +Learn more about coroutines debugging in the [official tutorial](https://kotlinlang.org/docs/tutorials/coroutines/debug-coroutines-with-idea.html). + +## Jumping between threads +Run the following code with the -Dkotlinx.coroutines.debug JVM option (see debug): + +```kotlin +newSingleThreadContext("Ctx1").use { ctx1 -> + newSingleThreadContext("Ctx2").use { ctx2 -> + runBlocking(ctx1) { + log("Started in ctx1") + withContext(ctx2) { + log("Working in ctx2") + } + log("Back to ctx1") + } + } +} +``` + +It demonstrates several new techniques. One is using runBlocking with an explicitly specified context, and the other one is using the withContext function to change the context of a coroutine while still staying in the same coroutine, as you can see in the output below: + +```kotlin +[Ctx1 @coroutine#1] Started in ctx1 +[Ctx2 @coroutine#1] Working in ctx2 +[Ctx1 @coroutine#1] Back to ctx1 +``` + +Note that this example also uses the use function from the Kotlin standard library to release threads created with newSingleThreadContext when they are no longer needed. + +## Chilren of a coroutine + +When a coroutine is launched in the CoroutineScope of another coroutine, it inherits its context via CoroutineScope.coroutineContext and the Job of the new coroutine becomes a child of the parent coroutine's job. When the parent coroutine is cancelled, all its children are recursively cancelled, too. + +However, this parent-child relation can be explictly overriden in one of two ways: + +1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`), then is does no inherit a `Job` from the parent scope. +2. When a different `Job` object is passed as the context for the new coroutine (as shown in the example below), then is overrides the `Job` of the parent scope. + +In both cases, the launched coroutine is not tied to the scope it was launched fron and operates independently. + +## Combining context elements + +Sometimes we need to define multiple elements for a coroutine context. We can use the + operator for that. For example, we can launch a coroutine with an explicitly specified dispatcher and an explicitly specified name at the same time: + +```kotlin +launch(Dispatchers.Default + CoroutineName("test")) { + println("I'm working in thread ${Thread.currentThread().name}") +} +``` + +The output of this code with the `-Dkotlinx.coroutines.debug` JVM option is: + +```kotlin +I'm working in thread DefaultDispatcher-worker-1 @test#2 +``` + +## Thread-local data + +Sometimes it is convenient to have an ability to pass some thread-local data to or between coroutines. However, since they are not bound to any particular thread, this will likely lead to boilerplate if done manually. + +For **ThreadLocal, the asContextElement** extension function is here for the rescue. It creates an additional context element which keeps the value of the given ThreadLocal and restores it every time the coroutine switches its context. + +```kotlin +threadLocal.set("main") +println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") +val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) { + println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") + yield() + println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") +} +job.join() +println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") +``` + +In this example we launch a new coroutine in a background thread pool using Dispatchers.Default, so it works on a different thread from the thread pool, but it still has the value of the thread local variable that we specified using threadLocal.asContextElement(value = "launch"), no matter which thread the coroutine is executed on. Thus, the output (with debug) is: + +```kotlin +Pre-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main' +Launch start, current thread: Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main], thread local value: 'launch' +After yield, current thread: Thread[DefaultDispatcher-worker-2 @coroutine#2,5,main], thread local value: 'launch' +Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: 'main' +``` + +It's easy to forget to set the corresponding context element. The thread-local variable accessed from the coroutine may then have an unexpected value, if the thread running the coroutine is different. To avoid such situations, it is recommended to use the ensurePresent method and fail-fast on improper usages. + +ThreadLocal has first-class support and can be used with any primitive `kotlinx.coroutines` provides. It has one key limitation, though: when a thread-local is mutated, a new value is not propagated to the coroutine caller (because a context element cannot track all ThreadLocal object accesses), and the updated value is lost on the next suspension. Use `withContext` to update the value of the thread-local in a coroutine, see `asContextElement` for more details. + +Alternatively, a value can be stored in a mutable box like `class Counter(var i: Int)`, which is, in turn, stored in a thread-local variable. However, in this case you are fully responsible to synchronize potentially concurrent modifications to the variable in this mutable box. + +For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries which internally use thread-locals for passing data, see the documentation of the `ThreadContextElement` interface that should be implemented. + +--- +reference + +- https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html +- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/ +- https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide +- https://medium.com/@lucianoalmeida1/an-overview-on-kotlin-coroutines-d55e123e137b \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Scope,\342\200\205Context.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Scope,\342\200\205Context.md" new file mode 100644 index 00000000..d68e5e3b --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Coroutine\342\200\205Scope,\342\200\205Context.md" @@ -0,0 +1,153 @@ +--- +title: 'Coroutine Scope, Context' +lastUpdated: '2024-03-02' +--- + +To launch a coroutine, we need to use a coroutine builder like launch or async. These builder functions are actually extensions of the `CoroutineScope` interface. So, whenever we want to launch a coroutine, we need to start it in some scope. + +```kotlin +public interface CoroutineScope { + /** + * The context of this scope. + * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope. + * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages. + * + * By convention, should contain an instance of a [job][Job] to enforce structured concurrency. + */ + public val coroutineContext: CoroutineContext +} + +public fun CoroutineScope.launch( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +): Job { + val newContext = newCoroutineContext(context) + val coroutine = if (start.isLazy) + LazyStandaloneCoroutine(newContext, block) else + StandaloneCoroutine(newContext, active = true) + coroutine.start(start, coroutine, block) + return coroutine +} +``` + +The scope **creates relationships between coroutines inside it and allows us to manage the lifecycles of these coroutines.** To manage the lifecycle, it can manage memory to prevent leak also. There are several scopes provided by the `kotlinx.coroutines` library that we can use when launching a coroutine. + +Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead. By convention, the context of a scope should contain an instance of a job to enforce the discipline of structured concurrency with propagation of cancellation. + +Every coroutine builder (like launch, async, and others) and every scoping function (like coroutineScope and withContext) provides its own scope with its own [Job instance](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html) into the inner block of code it runs. By convention, they all wait for all the coroutines inside their block to complete before completing themselves, thus enforcing the structured concurrency. It's important to prevent memory leak. + +There’s also a way to create a custom scope. Let’s have a look + +## Coroutine Context + +One of the simplest ways to run a coroutine is to use GlobalScope: + +```kotlin +GlobalScope.launch { + delay(500L) + println("Coroutine launched from GlobalScope") +} +``` + +The lifecycle of this scope is tied to the lifecycle of the whole application. This means that the scope will stop running either after all of its coroutines have been completed or when the application is stopped. + +It’s worth mentioning that coroutines launched using GlobalScope do not keep the process alive. They behave similarly to daemon threads. So, even when the application stops, some active coroutines will still be running. This can easily create resource or memory leaks. + +## runBlocking + +Another scope that comes right out of the box is runBlocking. From the name, we might guess that it creates a scope and runs a coroutine in a blocking way. This means it blocks the current thread until all childrens’ coroutines complete their executions. + +It is not recommended to use this scope because threads are expensive and will depreciate all the benefits of coroutines. + +The most suitable place for using runBlocking is the very top level of the application, which is the main function. Using it in main will ensure that the app will wait until all child jobs inside runBlocking complete. + +Another place where this scope fits nicely is in tests that access suspending functions. + +## CoroutineScope + +For all the cases when we don’t need thread blocking, we can use coroutineScope. Similarly to runBlocking, it will wait for its children to complete. But unlike runBlocking, **this scope doesn’t block the current thread but only suspends** it because coroutineScope is a suspending function. + +## Custom Coroutine Scope + +There might be cases when we need to have some specific behavior of the scope to get a different approach in managing the coroutines. To achieve that, we can implement the CoroutineScope interface and implement our custom scope for coroutine handling. + +# Coroutine Context + +Now, let’s take a look at the role of CoroutineContext here. **The context is a holder of data that is needed for the coroutine**. Basically, it’s an indexed set of elements where each element in the set has a unique key. + +The important elements of the coroutine context are the [**Job of the coroutine and the Dispatcher**](Coroutine%E2%80%85Dispatcher.md). + +Kotlin provides an easy way to add these elements to the coroutine context using the `+` operator: + +```kotlin +launch(Dispatchers.Default + Job()) { + println("Coroutine works in thread ${Thread.currentThread().name}") +} +``` + +## Job in the Context + +```kotlin +interface Job : CoroutineContext.Element +``` + +A Job of a coroutine is to handle the launched coroutine. For example, it can be used to wait for coroutine completion explicitly. + +Since Job is a part of the coroutine context, it can be accessed using the coroutineContext(Job) expression. + +Jobs can be arranged into parent-child hierarchies where cancellation of a parent leads to immediate cancellation of all its children recursively. Failure of a child with an exception other than CancellationException immediately cancels its parent and, consequently, all its other children. This behavior can be customized using [SupervisorJob](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html). + +The most basic instances of Job interface are created like this: + +- **Coroutine job** is created with launch coroutine builder. It runs a specified block of code and completes on completion of this block. +- **CompletableJob** is created with a `Job()` factory function. It is completed by calling CompletableJob.complete. + +## Coroutine Context and Dispatchers + +Another important element of the context is Dispatcher. It determines what threads the coroutine will use for its execution. + +Kotlin provides several implementations of CoroutineDispatcher that we can pass to the CoroutineContext: + +- `Dispatchers.Default` uses a shared thread pool on the JVM. By default, the number of threads is equal to the number of CPUs available on the machine. +- `Dispatchers.IO` is designed to offload blocking IO operations to a shared thread pool. +- `Dispatchers.Main` is present only on platforms that have main threads, such as Android and iOS. +- `Dispatchers.Unconfined` doesn’t change the thread and launches the coroutine in the caller thread. The important thing here is that after suspension, it resumes the coroutine in the thread that was determined by the suspending function. + + +## Switching the Context + +Sometimes, we must change the context during coroutine execution while staying in the same coroutine. We can do this **using the withContext function**. It will call the specified suspending block with a given coroutine context. The outer coroutine suspends until this block completes and returns the result: + +```kotlin +newSingleThreadContext("Context 1").use { ctx1 -> + newSingleThreadContext("Context 2").use { ctx2 -> + runBlocking(ctx1) { + println("Coroutine started in thread from ${Thread.currentThread().name}") + withContext(ctx2) { + println("Coroutine works in thread from ${Thread.currentThread().name}") + } + println("Coroutine switched back to thread from ${Thread.currentThread().name}") + } + } +} +``` + +The context of the withContext block will be the merged contexts of the coroutine and the context passed to withContext. + +## Children of a Coroutine + +When we launch a coroutine inside another coroutine, it inherits the outer coroutine’s context, and the job of the new coroutine becomes a child job of the parent coroutine’s job. Cancellation of the parent coroutine leads to cancellation of the child coroutine as well. + +We can override this parent-child relationship using one of two ways: + +- Explicitly specify a different scope when launching a new coroutine +- Pass a different Job object to the context of the new coroutine + +In both cases, the new coroutine will not be bound to the scope of the parent coroutine. It will execute independently, meaning that canceling the parent coroutine won’t affect the new coroutine. + +--- +참고 +- https://www.baeldung.com/kotlin/coroutines-scope-vs-context +- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/ +- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Integration.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Integration.md" new file mode 100644 index 00000000..aff68a4a --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/Integration.md" @@ -0,0 +1,100 @@ +--- +title: 'Integration' +lastUpdated: '2024-03-02' +--- + +```kotlin +interface Service { + fun savePost(token: Token, item: Item): Call +} + +suspend fun createPost(token: Token, item: Item): Post = + serviceInstance.savePost(token, item).await() +``` + +Let's say you want to use Coroutine with code that used futur (which corresponds to several libraries such as reactor). We are going to call 'savePost' which returns the future 'Call' from 'createPost'. + +If so, we need the conversion code as below. + +```kotlin +suspend fun Call.await(): T { + ... +} +``` + +How to implement it? + +--- + +Any kind of synchronous future has a method to install callback. For example retrofit, there's in queue. + +```kotlin +suspend fun Call.await(): T { + enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + // todo + } + override fun onFailure(call: Call, t: Throwable) { + // tode + } + }) +} +``` + +But every callback having defferent futures call is a different way. Then make different name of each function, implementation and using will complicated. + +Standard library in coroutine provides a function all suspendCorouine. That's exactly the building block that were using to implement all those ways for all dfferent kinds of future. + +```kotlin +suspend fun Call.await(): T = suspendCoroutine { cont -> + enqueue(object : Callback) { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) + cont.resume(response.body()!!) + else + cont.resumeWithException(ErrorResponse(response)) + } + override fun onFailure(call: Call, t: Throwable) { + cont.resumeWithException(t) + } + } +} +``` + +let's take a closer look at what's at that is. + +## suspendCoroutine + +```kotlin +/** + * Obtains the current continuation instance inside suspend functions and suspends + * the currently running coroutine. + * + * In this function both [Continuation.resume] and [Continuation.resumeWithException] can be used either synchronously in + * the same stack-frame where the suspension function is run or asynchronously later in the same thread or + * from a different thread of execution. Subsequent invocation of any resume function will produce an [IllegalStateException]. + */ +@SinceKotlin("1.3") +@InlineOnly +public suspend inline fun suspendCoroutine(crossinline block: (Continuation) -> Unit): T { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return suspendCoroutineUninterceptedOrReturn { c: Continuation -> + val safe = SafeContinuation(c.intercepted()) + block(safe) + safe.getOrThrow() + } +} +``` + +Inside of it is just regular lambda that doesn't have a suspend modifier. so it is kind of reverse separation to coroutine builder. It takes a lambda with a `Continuation` parameter as an argument. The function uses this `Continuation` object to suspend the current coroutine until the callback is called. Once this happens, the `Continuation` object resumes the coroutine and passes the result of the callback back to the calling code. + +**By definition, a call to any suspend function may yield control to other coroutines** within the same [coroutine dispatcher](./Coroutine%E2%80%85Dispatcher.md). So this is exactly what happens with the function that we’ve just constructed with the help of the `suspendCoroutine()` call. + +The `suspendCancellableCoroutine()` works similarly to the `suspendCoroutine()`, with the difference being that it uses a CancellableContinuation, and the produced function will react if the coroutine it runs in is canceled. + +This atually inspired by the operator that named call with current continuation in the Lisp FEM language called scheme. + +--- +reference +- https://www.youtube.com/watch?v=YrrUCSi72E8&t=110s +- https://en.wikipedia.org/wiki/Call-with-current-continuation \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/thread/\352\263\265\354\234\240\352\260\235\354\262\264\342\200\205\354\212\244\353\240\210\353\223\234\342\200\205\353\217\231\352\270\260\355\231\224.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/thread/\352\263\265\354\234\240\352\260\235\354\262\264\342\200\205\354\212\244\353\240\210\353\223\234\342\200\205\353\217\231\352\270\260\355\231\224.md" new file mode 100644 index 00000000..f9c74188 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/thread/\352\263\265\354\234\240\352\260\235\354\262\264\342\200\205\354\212\244\353\240\210\353\223\234\342\200\205\353\217\231\352\270\260\355\231\224.md" @@ -0,0 +1,207 @@ +--- +title: '공유객체 스레드 동기화' +lastUpdated: '2024-03-02' +--- + +동시에 읽고 쓰이는 공유 객체에서 스레드 동기화를 적절하게 사용하는 방법에 대해 알아보자. + +- Atomic +- Synchronized block +- 스레드 한정 +- Mutex +- Actor + +### AtomicInteger + +CAS(Compare And Swap) 알고리즘을 사용한다. + +원리를 간단하게 말하자면, 변경하려는 값과 현재 저장된 값을 비교해서 같으면 변경하고 다르면 값을 변경하지 못하게 하는 것이다. 변경하려는 값과 현재 저장된 값이 다른 경우는 중간에 다른 스레드에 의해 값이 변경된 경우로 볼 수 있기에 변경을 허용하지 않는다. + +사용법은 아래와 같다. + +```kotlin +suspend fun exampleFun(action: suspend () -> Unit) { + val n = 100 + val k = 1000 + val write = measureTimeMillis { + coroutineScope { + repeat(n) { + launch { + repeat(k) { action() } + } + } + } + } + println("$write ms동안 ${n*k}개의 일을 했습니다.") +} + +var counter = AtomicInteger(0) + +fun main() = runBlocking { + withContext(Dispatchers.Default) { + exampleFun { + counter.incrementAndGet() + } + } + println("Counter = $counter") +} + +//13 ms동안 100000개의 일을 했습니다. +//Counter = 100000 +``` + +### Synchronized block + +Synchronized는 객체가 가지는 **LOCK**의 속성을 이용해 단일 스레드만 객체에 접근하게 하는 방식이다. 아래의 예시를 보면 Counter 인스턴스(`this`)를 Lock 파라미터로 설정해주어서 간단하게 사용한다. 하지만 이방식을 사용할 때 Lock을 걸었다 풀었다 하는 과정에서 더 많은 비용을 가진다고 한다. 또한 설계를 잘못하면 Deadlock에 빠질 수 있다. + +```kotlin +class Counter { + fun plusCount() { + synchronized(this) { + counter++ + } + } +} + +suspend fun exampleFun(action: suspend () -> Unit) { + val n = 100 + val k = 1000 + val write = measureTimeMillis { + coroutineScope { + repeat(n) { + launch { + repeat(k) { action() } + } + } + } + } + println("$write ms동안 ${n * k}개의 일을 했습니다.") +} + +var counter = 0 + +fun main() = runBlocking { + withContext(Dispatchers.Default) { + val c = Counter() + exampleFun { + c.plusCount() + } + } + println("Counter = $counter") +} + +//19 ms동안 100000개의 일을 했습니다. +//Counter = 100000 +``` + +### 스레드 한정(newSingleThreadContext) + +`newSingleThreadContext`는 새로운 스레드를 만들고 그 스레드에서만 작업이 수행되도록 보장해주는 것이다. + +```kotlin +suspend fun exampleFun(action: suspend () -> Unit) { + val n = 100 + val k = 1000 + val write = measureTimeMillis { + coroutineScope { + repeat(n) { + launch { + repeat(k) { action() } + } + } + } + } + println("$write ms동안 ${n*k}개의 일을 했습니다.") +} + +var counter = 0 +val newContext = newSingleThreadContext("newContext") + +fun main() = runBlocking { + withContext(Dispatchers.Default) { + exampleFun { + withContext(newContext){ + counter++ + } + } + } + println("Counter = $counter") +} + +//673 ms동안 100000개의 일을 했습니다. +//Counter = 100000 +``` + +## [Mutex](https://github.com/rlaisqls/TIL/blob/main/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%E2%80%85Operating%E2%80%85System/%EC%9E%84%EA%B3%84%EC%98%81%EC%97%AD%EA%B3%BC%E2%80%85%EC%83%81%ED%98%B8%EB%B0%B0%EC%A0%9C.md) + +공유 상태를 수정할 때 임계 영역을 이용하게 하여, 임계영역을 동시에 접근하는 것을 허용하지 않는다. 뮤텍스는 앞서 보았던 synchronized block과 비슷하게 객체의 Lock을 이용하여 스레드 간 동기화를 처리한다. `Mutex`는 스레드를 suspend 하고 synchronized block은 스레드를 block 하기 때문에 실제 성능은 더 좋다고 한다. + +```kotlin +... +val mutex = Mutex() + +fun main() = runBlocking { + withContext(Dispatchers.Default) { + exampleFun { + mutex.withLock { + count++ + } + } + } + println("Counter = $count") +} + +//336 ms동안 100000개의 일을 했습니다. +//Counter = 100000 +``` + +## Actor(액터) + +액터가 독점적으로 자료를 가지며 그 자료를 다른 코루틴과 공유하지 않고 액터를 통해서만 접근하게 만든다. + +액터는 내부적으로 `ReceivedChannel`을 가지고 있다. 이를 통해 단일 스레드만 액터에 접근하게 하고 다른 스레드는 채널을 통해 상태를 수정하게 하여 스레드/코루틴 간의 동기화를 이루어낸다. + +이 방법을 사용하려면 우선 sealed class를 만들어야 한다. sealed class는 외부에서 확장이 불가능하고 sealed class를 상속받은 클래스, 객체는 스레드에서 채널에게 상태를 수정해달라고 요청하는 일종의 신호로 사용된다. 아래는 그 예시이다. + +```kotlin +suspend fun exampleFun(action: suspend () -> Unit) { + val n = 100 + val k = 1000 + val write = measureTimeMillis { + coroutineScope { + repeat(n) { + launch { + repeat(k) { action() } + } + } + } + } + println("$write ms동안 ${n * k}개의 일을 했습니다.") +} +sealed class CounterMsg +object IncCounter : CounterMsg() +class GetCounter(val response: CompletableDeferred) : CounterMsg() + +fun CoroutineScope.counterActor() = actor { + var counter = 0 + for (msg in channel) { + when (msg) { + is IncCounter -> counter++ + is GetCounter -> msg.response.complete(counter) + } + } +} + +fun main() = runBlocking { + val counter = counterActor() + withContext(Dispatchers.Default) { + exampleFun { + counter.send(IncCounter) + } + } + val response = CompletableDeferred() + counter.send(GetCounter(response)) + println("Counter = ${response.await()}") + counter.close() +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/\354\275\224\353\243\250\355\213\264.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/\354\275\224\353\243\250\355\213\264.md" new file mode 100644 index 00000000..f679bd91 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/coroutine/\354\275\224\353\243\250\355\213\264.md" @@ -0,0 +1,204 @@ +--- +title: '코루틴' +lastUpdated: '2024-03-02' +--- + +프로그래밍 언어에는 루틴이라는 개념이 있다. + +```kotlin +fun main() { + ... + val addedValue = plusOne(value) +} + +fun plusOne(value: Int) { + return value + 1 +} +``` + +위와 같은 코드가 있다고 했을떄, main 함수가 메인루틴이고 함수를 호출해서 이동하는 것은 서브루틴이라고 부른다. + +서브루틴은 진입하고, 빠져나오는 지점이 명확하다. 메인 루틴이 서브루틴을 호출하면, 서브루틴의 맨 처음 부분에 진입하여 return문을 만나거나 서브루틴의 닫는 괄호를 만나면 해당 서브루틴을 빠져나오게 된다. 그리고 진입점과 탈출점 사이에 쓰레드는 블락되어있다. + +우리가 보통 짜는 코드는 이렇게 동작한다. 그러나 코루틴(Coroutine)은 조금 다르다. + +코루틴 함수는 꼭 return문이나 마지막 닫는 괄호를 만나지 않더라도 언제든지 중간에 나갈 수 있고, 언제든지 다시 나갔던 그 지점으로 들어올 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/217225934-dff05b67-f83e-4234-987d-33670eba50e7.png) + +## 예시 + +```kotlin +suspend fun doSomethingUsefulOne(): Int { + delay(1000L) // pretend we are doing something useful here + return 13 +} + +suspend fun doSomethingUsefulTwo(): Int { + delay(1000L) // pretend we are doing something useful here, too + return 29 +} +``` + +> https://kotlinlang.org/docs/composing-suspending-functions.html + +두 함수가 있다. 이 두 함수는 transaction을 수행하는 등 뭔가 유용한 작업을 한 뒤 Int를 반환하는 함수인데, 동작하는데 1초가 걸린다. + +이 함수를 그냥 순서대로 실행하면 당연히 2초가 걸릴 것이다. + +```kotlin +val time = measureTimeMillis { + val one = doSomethingUsefulOne() + val two = doSomethingUsefulTwo() + println("The answer is ${one + two}") +} +println("Completed in $time ms") +``` + +```kotlin +The answer is 42 +Completed in 2017 ms +``` + +하지만 코루틴을 통해 이걸 비동기적으로 처리하면 두 함수를 1초만에 처리할 수 있게 된다. (두 함수간의 종속성이 없기 때문에 가능하다.) + +```kotlin +fun main() = runBlocking { + val time = measureTimeMillis { + val one = async { doSomethingUsefulOne() } + val two = async { doSomethingUsefulTwo() } + println("The answer is ${one.await() + two.await()}") + } + println("Completed in $time ms") // about 1000ms +} +``` + +async로 둘러싼 블록이 있으면, 다른 코루틴과 동시에 작동할 수 있는 별도의 코루틴이 생성되기 때문에 위 코드에서의 두 함수도 동시에 실행할 수 있다. + +위 코드에서는 [async](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html)를 사용했는데 개념적으로는 [launch](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html)를 사용하는 것과 동일하다. 하지만 차이점이 있다면 launch는 return값을 반환하지 않는데 비해, acyns는 `Deferred`라는 경량 반환값을 반환한다는 것이다. (이 반환값은, 비동기 처리가 끝난 미래에 실제 값이 들어오는 것으로 약속되어있는 가상의 값이다.) + +`Deferred`에 `.await()`을 사용하면 최종 결과를 얻을 수 있지만, Deferred도 `job`의 일종이기 때문에 필요한경우 취소하는 것도 가능하다. + +원하면 async를 Lazy하게 설정할 수 있다. 이렇게 설정 해놓으면 await에 의해 반환값이 요구되거나 job의 시작 기능이 호출될때만 코루틴을 시작한다. + +```kotlin +fun main() = runBlocking { + val time = measureTimeMillis { + val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() } + val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() } + // some computation + one.start() // start the first one + two.start() // start the second one + println("The answer is ${one.await() + two.await()}") + } + println("Completed in $time ms") // about 1000ms +} +``` + +## suspend 없이 일반 함수로 비동기 구현해보기 + +추천되는 방식은 아니지만, 우리는 [GlobalScope](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/)의 `async`를 활용하여, 일반 함수도 비동기적으로 동작할 수 있게 할 수 있다. + +```kotlin +// 나름 async이기 때문에 Deferred를 반환한다 +@OptIn(DelicateCoroutinesApi::class) +fun somethingUsefulOneAsync() = GlobalScope.async { + doSomethingUsefulOne() +} + +@OptIn(DelicateCoroutinesApi::class) +fun somethingUsefulTwoAsync() = GlobalScope.async { + doSomethingUsefulTwo() +} +``` + +> 이렇게 정의한 함수는 Suspending Function이 아니다. 코루틴 스코프가 아니여도 어디서든 쓰일 수 있다. 하지만, 이들은 항상 비동기적으로 (동시성을 띄며) 동작하도록 한다. + +```kotlin +// main()에 runBlocking이 없어도 된다. +fun main() { + val time = measureTimeMillis { + // 이런 식으로, 코루틴 스코프 밖에서 Deferred 객체 생성이 가능하다. + val one = somethingUsefulOneAsync() + val two = somethingUsefulTwoAsync() + + // 하지만, 결과를 받아보는 `await()` 등의 동작은 무조건 코루틴 스코프 내에서 이루어져야 한다. + // 아래 runBlocking 을 통해 13 + 29의 결과인 42가 나올 때 까지 메인 쓰레드를 블로킹하여 시간을 잰다. + runBlocking { + println("13 + 29 는 ${one.await() + two.await()} 입니다") + } + } + println("Completed in $time ms") +} +``` + +이러한 비동기 스타일의 일반 함수를 사용하는 예제는, 공식 문서에도 나와있듯이 '**다른 프로그래밍 언어에서 많이 사용되는 스타일이기 때문에 보여주기 식으로 제공**'되는 것이다. 코틀린에서는 이러한 스타일을 절대 사용하지 말 것을 권고한다. 이유는 다음과 같다. + +![image](https://user-images.githubusercontent.com/81006587/217149371-f073998f-0a5f-41db-8215-c1b089d95e64.png) + +만약 Async 스타일 함수를 호출하는 부분과 해당 함수의 Deferred 객체의 `await()`를 호출하는 부분 사이에서 어떤 에러가 발생하여 프로그램이 Exception을 throwing하고 프로그램이 중단되는 경우를 생각해보자. + +오류 핸들러가 이 Exception을 감지해서 개발자에게 로그를 보여주는 식의 동작을 할 수도 있고, 아니면 그냥 다른 동작을 시작할 수도 있다. + +하지만, 우리가 호출한 Async 함수는 이를 호출한 쪽에 에러가 떠러 중단되더라도 백그라운드상으로 계속 실행되어 있는 문제가 발생한다. 이 문제를 해결하기 위해서는 아래에서 설명하는 구조적 동시성 프로그래밍을 사용해야한다. + +## 구조적 동시성 프로그래밍 + +조금 위에서 사용했던 동시성 계산 코드를 Suspending Function으로 분리해보자. + +```kotlin +suspend fun concurrentSum(): Int = coroutineScope { + val one = async { doSomethingUsefulOne() } + val two = async { doSomethingUsefulTwo() } + one.await() + two.await() +} +``` + +이렇게 하면 concurrentSum() 내부의 자식 코루틴 스코프 둘 중 하나에게 어떠한 에러가 발생했을떄, 상위 코루틴 스코프 coroutineScope의 실행이 중단되어 모든 자식 코루틴이 종료된다, + +```kotlin +fun main() = runBlocking { + val time = measureTimeMillis { + println("13 + 29 는 ${concurrentSum()} 입니다") + } + println("Completed in $time ms") // about 100ms +} +``` + +아래의 예시를 보자. `failedConcurrentSum()` 내부에는 두 코루틴 객체가 각각 있고 두 번째 녀석은 ArithmeticException을 발생하는 녀석이다. 이 함수 자체를 try-catch 로 감쌌을 때, 어떤 결과가 나오는지 보자. + +```kotlin +fun main() = runBlocking { + try { + failedConcurrentSum() + } catch(e: ArithmeticException) { + println("Computation failed with ArithmeticException") + } +} + +suspend fun failedConcurrentSum(): Int = coroutineScope { + val one = async { + try { + delay(Long.MAX_VALUE) // Emulates very long computation + 42 + } finally { + println("First child was cancelled") + } + } + val two = async { + println("Second child throws an exception") + throw ArithmeticException() + } + one.await() + two.await() +} +``` + +```yml +Second child throws an exception +First child was cancelled +Computation failed with ArithmeticException +``` + +one 객체에 대한 동작을 수행한 다음 two를 수행했다면 긴 delay가 걸려 Exception이 던져지는 것을 볼 수 없었을 테지만, 코루틴을 통해 동시에 동작하기 때문에 two의 Exception으로 인해 catch문에 바로 걸리는 것을 볼 수 있다. + +이를 통해 coroutineScope() 안에서 오류가 발생하면 해당 코루틴 자체가 중단되어 다른 자식 코루틴도 모두 중단되고, 결국 최상위 계층까지 오류가 전파되는 사실을 알 수 있다. 따라서, 백그라운드 상으로 코루틴이 남아있는 문제는 발생하지 않는다. diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/netty\342\200\205\354\202\254\353\241\200\354\227\260\352\265\254.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/netty\342\200\205\354\202\254\353\241\200\354\227\260\352\265\254.md" new file mode 100644 index 00000000..26a94583 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/netty\342\200\205\354\202\254\353\241\200\354\227\260\352\265\254.md" @@ -0,0 +1,92 @@ +--- +title: 'netty 사례연구' +lastUpdated: '2023-02-09' +--- + +파일을 업로드하면 그 파일을 순간 공유할 수 있도록 하는 플랫폼이 있다고 해보자. 그 파일 자체는 아마존 S3에 저장된다. + +간단한 업로드 흐름을 구축하자면 아래와 같이 할 수 있다. + +1. 전체 파일을 수신한다. +2. S3로 업로드한다. +3. 이미지인 경우 섬네일을 생성한다. +4. 클라이언트 애플리케이션에 응답한다. + +여기에서는 2단계와 3단계가 병목 지점이 될 수 있다. 클라이언트에서 서버로 아무리 빠른 속도로 업로드하더라도 실제 드롭을 생성하고 클라이언트에 응답하려면 업로드가 완료된 후 파일을 S3로 업로드라고 섬네일을 생성할 때까지 오랫동안 기다려야했다. 파일이 클수록 대기 시간은 길었고, 아주 큰 파일의 경우 서버로부터 응답을 기다리다가 시간이 만료되는 경우도 있다. + +업로드 시간을 줄이기 위한 두가지 다른 방법이 고안됐다. + +- A 낙관적이지만 간단한 방법 + 1. 전체 파일을 수신한다. + 2. 파일을 로컬 파일 시스템에 저장하고 (클라이언트-서버 스트리밍) 클라이언트에는 즉시 성공했다고 보고한다. + 3. S3로 파일 업로드를 예약한다. + +이 방법의 맹점은, S3에 파일업로드가 성공했는지 성공하지 않았는지 알 수 없다는 것이다. 사용자는 업로드된 이미지의 위치를 알 수 없거나, 이미지가 실제로 있는지 없는지 모르는 임시 URL만을 가질 수 있다. + +파일을 S3로 푸시하기 전까지 임시로 제공할 시스템을 마련하는 방법이 있지만 그 방법에도 파일 손실이나 클러스터간 동기화 문제가 문제가 있어 해결하기 어렵다.. + +- B 복잡하지만 안전한 방법 + 1. 클라이언트에서 S3로 실시간으로 업로드르 파이프한다. (클라이언트-서버-S3 스트리밍) + +이 방법을 구현하려면 전체 프로세스를 세부적으로 제어해야한다. 자세히 얘기하자면 다음과 같은 기능이 필요하다. + +``` +1. 클라이언트로부터 업로드를 수신하는 동안 S3에 연결한다. +2. 클라이언트 연결에서 S3 연결로 데이터를 파이프한다. +3. 두 연결에 버퍼와 스로틀을 적용한다. + - 버퍼링은 클라이언트-서버-S3 업로드 간에 안정적인 흐름을 유지하는 데 필요하다. + - 스로틀은 서버-S3 업로드 단계가 클라이언트 -서버 업로드 단계보다 느릴 경우 과도한 메모리 소비를 예방하는 데 필요하다. +4. 문제가 발생한 경우 모든 작업을 깔끔하게 롤백한다. +``` + +개념상으로는 간단하지만 보통 웹 서버로 해결 가능한 수준의 기능이 아니다. 특히 TCP 연결에 스로틀을 적용하려면 해당 소켓에 대한 저수준 접근이 필요하다. 또한, 지연 섬네일 생성이라는 새로운 개념이 필요하므로 최종 아키텍처의 형태를 근본적으로 다시 고려해야했다. + +즉, 최상의 성능과 안정성을 가지고 있으면서도 바이트 단위의 저수준을 제어할 수 있는 유연성을 가진 기술이 필요했다. 그래서 이 경우 netty를 사용해서 문제를 해결할 수 있다. + +```java +pipelineFactory = new ChannelPipelineFactory() { + @Override + public ChannelPipeline getPipeline() throw Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("idleStateHandler", new IdleStateHandler(...)); // IdelStateHandler가 비활성 연결을 종료 + pipeline.addLast("httpServerCodec", new HttpServerCodec()); // HttpServerCodec이 오가는 데이터를 직렬화, 역직렬화 + pipeline.addLast("requestController", new RequestController(...)); // RequestController를 파이프라인에 추가 + return pipeline; + } +} +``` + +```java +public class RequestController extends IdleStateAwareChannelUpstreamHandler { + + @Override + public void channelIdle(ChannelHandlerContext ctx, IdelStateEvent e) throws Exception { + // 클라이언트에 대한 연결을 닫고 모든 항목을 롤백함 + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + if (!acquireConnectoinSlot()) { + // 허용하는 최대 서버 연결 횟수에 도달함 + // 503 service nuavaliablefh 응답하고 연결 종료 + } else { + // 연결의 요청 파이프라인을 설정함 + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + if (isDone()) return; + + if (e.getMessage() instanceof HttpRequest) { + handlerHttpRequest((HttpRequest) e.getMessage()); // 서버 요청 유효성 검사의 핵심 사항 + } else if (e.getMessage() instanceof HttpChunk) { + handleHttpChunk((HttpChunk)e,getMessage()); // 현재 요청에 대한 황성 핸들러가 청크를 수락하는 경우 청크 전달 + } + } +} +``` + +이런식으로 구현헀다고 한다. + +> 근데 여기서 클라이언트에서 서버를 거치지 않고, s3에 바로 파일을 넣을 수 있도록 하면 서버에 가는 부하는 최적으로 줄일 수 있지 않을까? \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Callback\352\263\274\342\200\205Futures.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Callback\352\263\274\342\200\205Futures.md" new file mode 100644 index 00000000..bed7e15b --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Callback\352\263\274\342\200\205Futures.md" @@ -0,0 +1,182 @@ +--- +title: 'Callback과 Futures' +lastUpdated: '2024-03-02' +--- + +어떻게 해야 JVM 위에서 비동기적인 코드를 작성할 수 있을까? Java는 asynchronous programming을 위해 두가지 모델을 제공한다. + +- **Callbacks**: return 값을 직접 가지지 않고, 비동기 처리가 끝난 후 result 값을 가져올 수 있을때 추가 callback parameter(a lambda or anonymous class)를 가져오는 Asynchronous 메서드이다. Swing의 `EventListener`와 그 구현 클래스들이 대표적인 예시이다. + +- **Futures**: `Future`를 즉시 반환하는 Asynchronous 메서드이다. The asynchronous process computes a T value, but the Future object wraps access to it. Future 값은 Callback과 마찬가지고, 실제 값을 가져오는건 비동기 처리가 끝난 뒤에야 가능하다. Future를 사용하는 예를 들자면, `Callable` 태스크를 실행하는 `ExecutorService`가 있다. + +하지만 이 두 모델이 항상 유용하게 사용되진 않는다. 두 접근법은 한계를 가지고 있다. + +## Callbacks + +`Callbacks` 코드가 늘어날 경우 가독성이 해쳐지고, 유지보수하기 힘들어진다. (Callback Hell, 콜백 지옥이라고 부르기도 한다.) + +예를 들어, UI에서 사용자가 즐겨찾기 5개, 즐겨찾기가 없는 경우에는 새 컨텐츠 제안을 띄워주는 코드를 생각해보자. 즐겨찾기 ID를 가져오고, 두 번째는 즐겨찾기 세부 정보를 가져오고, 다른 하나는 세부 정보가 포함된 제안을 제공하는 세가지 절차를 거쳐야한다. + +```java +userService.getFavorites(userId, new Callback>() { // --(1) + public void onSuccess(List list) { // --(2) + if (list.isEmpty()) { // --(3) + suggestionService.getSuggestions(new Callback>() { // --(4) + public void onSuccess(List list) { + UiUtils.submitOnUiThread(() -> { // --(5) + list.stream() + .limit(5) + .forEach(uiList::show); + }); + } + + public void onError(Throwable error) { // --(6) + UiUtils.errorPopup(error); + } + }); + } else { + list.stream() // --(7) + .limit(5) + .forEach(favId -> favoriteService.getDetails(favId, // --(8) + new Callback() { + public void onSuccess(Favorite details) { + UiUtils.submitOnUiThread(() -> uiList.show(details)); + } + + public void onError(Throwable error) { + UiUtils.errorPopup(error); + } + } + )); + } + } + + public void onError(Throwable error) { + UiUtils.errorPopup(error); + } +}); +``` + +1. 성공한 케이스와 실패한 케이스에 대한 처리를 명시하는 Callback interface를 정의한다. +2. 즐겨찾기 ID를 가져온다. +3. list가 empty인 경우 suggestionService로 이동한다. +4. suggestionService가 두번째 Callback interface를 가진다. +5. UI를 그리기 위해 UI thread에서 실행할 동작을 정의한다. +6. 각 레벨의 `onError`에서 에러 팝업 코드를 넣어줘야한다. +7. favorite ID 레벨로 돌아와서 favoriteService를 호출해준다. 결과값을 5개로 제한한다는 것을 stream으로 다시 명시해줘야한다. +8. UI를 그리기 위해 UI thread에서 실행할 동작을 또 다시 정의한다. + +코드량이 굉장히 많고 중복되는 코드가 많아서 흐름 파악이 어렵다. + +다음은 reactor를 사용해서 코드를 작성한 예시이다. + +```java +userService.getFavorites(userId) // --(1) + .flatMap(favoriteService::getDetails) // --(2) + .switchIfEmpty(suggestionService.getSuggestions()) // --(3) + .take(5) // --(4) + .publishOn(UiUtils.uiThreadScheduler()) // --(5) + .subscribe(uiList::show, UiUtils::errorPopup); // --(6) +``` + +1. favorite ID를 가져오는 flow를 시작한다. +2. Favorite의 상세 객체를 가져온다. +3. Favorite이 empty라면 `suggestionService.getSuggestions()`를 실행해서 그 결과물을 반환시킨다. +4. 5개의 element를 반환한다는 것을 딱 한번만 명시해준다. +5. UI를 그리기 위해 UI thread에서 실행할 동작을 정의한다. +6. 팝업을 띄워주는 에러 처리도 한 번만 수행한다. + +코드량이 훨씬 줄어들었고, 중요한 흐름을 알아보기 쉽다. + +## Future + +java8에서 CompletableFuture를 지원하기 시작하면서 `Future`의 사용성이 개선되었지만, 약간의 불편한 점이 여전히 있다. + +- `get()` 메서드를 호출하면 쉽게 blocking 된다. +- lazy computation을 지원하지 않는다. +- 여러 값을 가져오거나, 구체적인 에러핸들링이 필요한 상황에 대한 지원이 부족하다. + +이름과 통계를 Pair로 가져오는 예제를 보자. + +```java +CompletableFuture> ids = ifhIds(); // --(1) + +CompletableFuture> result = ids.thenComposeAsync(l -> { // --(2) + Stream> zip = + l.stream().map(i -> { + CompletableFuture nameTask = ifhName(i); + CompletableFuture statTask = ifhStat(i); + + return nameTask.thenCombineAsync(statTask, (name, stat) -> "Name " + name + " has stats " + stat); // --(3) + }); + List> combinationList = zip.collect(Collectors.toList()); + CompletableFuture[] combinationArray = combinationList.toArray(new CompletableFuture[combinationList.size()]); + + CompletableFuture allDone = CompletableFuture.allOf(combinationArray); // --(5) + return allDone.thenApply(v -> combinationList.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())); +}); + +List results = result.join(); // --(6) +assertThat(results).contains( + "Name NameJoe has stats 103", + "Name NameBart has stats 104", + "Name NameHenry has stats 105", + "Name NameNicole has stats 106", + "Name NameABSLAJNFOAJNFOANFANSF has stats 121" +); +``` + +1. id의 목록을 반환하는 `CompletableFuture`를 정의한다. +2. Future 값을 바탕으로 정보를 가져오기 위해 `thenComposeAsync`를 사용하고 map을 수행하여 각 값을 가져온다. +3. 두 값을 합쳐서 결과값을 만든다. +4. `CompletableFuture.allOf`에 array를 넣어서 모든 작업이 수행한 결과값인 Future를 반환하도록 해준다. +5. 여기서 번거로운 부분이 하나 있는데, `allOf`는 `CompletableFuture`를 반환하기 때문에 우리는 `join()`을 사용해서 값을 다시 collecting하고 `thenApply` 해줘야한다. +6. 전체 비동기 파이프라인이 trigger되면 우리는 그것이 processing 되길 기다리고, 그 값이 실제로 반환되면 assert해볼 수 있다. + +## reactor + +다음은 reactor를 사용해서 코드를 작성한 예시이다. + +```java +Flux ids = ifhrIds(); // --(1) + +Flux combinations = + ids.flatMap(id -> { // --(2) + Mono nameTask = ifhrName(id); + Mono statTask = ifhrStat(id); + + return nameTask.zipWith(statTask, (name, stat) -> "Name " + name + " has stats " + stat); // --(3) + }); + +Mono> result = combinations.collectList(); + +List results = result.block(); // --(4) +assertThat(results).containsExactly( + "Name NameJoe has stats 103", + "Name NameBart has stats 104", + "Name NameHenry has stats 105", + "Name NameNicole has stats 106", + "Name NameABSLAJNFOAJNFOANFANSF has stats 121" +); +``` + +1. 이번엔 ids를 `Flux`의 형태로 가져온다. +2. flatMap call 안에서 각 정보를 비동기적으로 가져온다. +3. 두 값을 합쳐서 결과값을 만든다. +4. 실제 production 코드라면 `Flux`를 추가로 결합하거나 구독해서 사용했겠지만, 여기선 List를 `Mono`로 묶어서 `blocking`한 다음 테스트해주었다. + +코드량이 훨씬 줄어들었고, 중요한 흐름이 더 잘 명시된다. + +## 결론 + +Callback과 Future를 사용한 코드를 살펴보았고, 그 코드를 reactor에서 어떻게 간소화할 수 있는지 알게 되었다. + +reactor가 저 동작들을 어떻게 추상화하고 처리하는지에 대해서 더 알아보고 싶다는 생각이 들었다. + +--- + +참고 + +- https://projectreactor.io/docs/core/release/reference/#_from_imperative_to_reactive_programming \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Reactor.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Reactor.md" new file mode 100644 index 00000000..887b8129 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Reactor.md" @@ -0,0 +1,74 @@ +--- +title: 'Reactor' +lastUpdated: '2024-03-02' +--- + +Reactor란 Pivotal의 오픈소스 프로젝트로, JVM 환경에서 동작하는 non-blocking reactive 라이브러리로서 non-blocking IPC(Inter-Process Commumication)을 지원한다. + +## Reactive Programing + +> 반응형 프로그래밍(reactive programming)은 데이터 스트림과 변화의 전파와 관련된 선언적 프로그래밍 패러다임이다. 이 패러다임을 사용하면 정적 또는 동적 데이터 스트림을 쉽게 표현할 수 있다. In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change. With this paradigm, it's possible to express static (e.g., arrays) or dynamic (e.g., event emitters) data streams with ease. + +Reactive Programming 패러다임은 객체지향 언어에서는 보통 옵저버 패턴으로 표현되며 Reactive Stream은 Publisher와 Subscriber 구조로 되어있다. + +스트림에서 새로 사용 가능한 값이 올 때 Publisher는 Subscriber에게 알려줄 수 있다 (push라고도 한다). 이렇게 Publisher가 Push 해주는 것은 Reactive의 핵심이다. 그리고 Push된 값을 적용시키는 연산자를 명령형 대신 선언형으로 표현하는데, Reactive Programming에서는 값을 조작시키는 것애 대한 제어 흐름을 나타내는 것 보다는 연산의 논리를 표현하는 것에 초점을 두기 때문이다. + +그리고 Publisher 값을 푸쉬하는 것 외에도 에러가 발생하거나 정상적으로 완료했을 때에 대한 핸들링 로직도 정의할 수 있다. Publisher는 Subscriber에게 새로운 값을 푸쉬할 수도 있지만 에러를 보내거나 더 이상 보낼 데이터가 없을 때 완료 신호를 보낼 수도 있다. 에러나 완료 신호를 받는다면 시퀀스는 종료된다. 이러한 특성 때문에 Reactive Stream은 다양한 스트림을 표현할 수 있다. + +- 값이 없는 스트림 (바로 완료 신호를 줌) +- 오직 하나의 값(하나의 값을 주고 완료 신호)이 있는 스트림 +- 유한개의 값(여러 개 보내고 완료 신호)이 있는 스트림 +- 무한한(완료 신호를 주지 않는 경우) 스트림 + +https://tech.kakao.com/2018/05/29/reactor-programming/ + +## 목표 + +Reactor는 다음과 같은 목표를 이루기 위해 노력한다. + +### Composability and Readability + +Composability는 이전 task의 결과를 사용하여 그 결과를 다음 task에 사용할 수 있도록 하는 여러가지 비동기 task들을 조율하는 능력을 의미한다. 그리고 이 몇가지 task들을 fork-join 하는 형식으로 실행시킬 수 있다. 그리고 비동기 task들을 개별적인 컴포넌트로써 재사용할 수 있다. + +여러 task들을 조율하는 능력은 코드의 가독성과 유지능력과 밀접한 관계를 이룬다. 비동기 프로세스의 개수와 복잡성이 모두 증가하면 코드를 읽기 어려워지고, 유지하는데 어려움이 발생한다. (콜백 지옥이라거나..) Reactor는 코드로 추상 프로세스를 구성할 수 있고, 모든 task들이 동일한 수준으로 유지될 수 있도록 풍부한 구성 옵션들을 제공한다. + +### The Assembly Line Analogy + +Reactor를 사용해 짜여진 코드를 실행하는 것은 데이터를 조립라인에 통과시키는 것으로 생각할 수 있다. 최초의 raw data는 source(Publisher)로 들어가서 처리가 완료된 데이터는 consumer(Subscriber)로 전달(push)된다. + +최초의 데이터는 여러가지 변형과정을 거치거나 여러 조각으로 분해되거나 여러 조각을 함께 모으는 작업을 거칠 수도 있다. 하나의 지점에 결함이나 문제가 발생하는 경우에 문제가 발생한 워크스테이션에서 데이터의 흐름을 제한하기 위해 upstream에 신호를 보낼 수도 있다. + +### Operator + +Reactor에서 Operator는 조립라인의 워크스테이션이다. 각 Operator는 Publisher에게 데이터 처리 과정을 더하고 이전 스텝의 Publisher를 새로운 인스턴스로 감싼다. 따라서 전체의 체인들은 연결되어 데이터는 처음 Publisher에서 시작되고, 각 링크에 의해 변형되어 체인 아래로 내려간다. 결국 Subscriber는 모든 체인을 통과하고 나온 결과를 받는다. (여기서 주의해야 할 점은 Subscriber가 Publisher를 구독하지 않으면 아무 일도 발생하지 않는다는 것이다.) + +Reactive Stream 사양은 operator를 지정하지 않지만, Reactor와 같은 reactive 라이브러리의 장점 중 하나는 operator가 제공하는 풍부한 표현 방식이다. 이런 표현방식은 단순 변환과 필터링을 포함하여 여러 복잡한 orchestration과 오류 처리 등 여러가지를 표현할 수 있다. + +### Nothing Happens Until You subscribe() + +Reactor에서 Publisher 체인을 작성하면 그냥 단순히 비동기 프로세스를 만들어 놓을 뿐, Publisher를 선언만 했을 때는 기본적으로 데이터가 Publisher를 통과하여 처리되지 않는다. Subscriber가 구독을 해야 Publisher를 Subscriber에 연결하여 전체 chain에서 데이터 흐름을 유발한다. 이는 upstream으로 전파되는 subscriber의 단일 요청 신호에 의해 내부적으로 처리되고, 다시 Publisher에게 전달된다. + +### Backpressure + +upstream에 신호를 전파하는 것은 backpressure를 개발하여 사용할 수 있는데, backpressure는 조립라인에서 워크스테이션이 upstream 워크스테이션보다 더 느리게 처리될 때 전송되는 피드백 신호와 비슷하다. + +Subscriber가 unbound mode에서 작업하고 source가 도달 가능한 가장 빠른 속도로 푸쉬하도록 하거나 요청 메커니즘을 사용하여 약 n개의 요소를 처리할 준비가 되어있다는 신호를 source에게 보낼 수 있는데, Reactive Stream 스펙에서 정의되는 실제 메커니즘은 이와 유사하다. + +중간 operator는 전송 중인 요청을 변경할 수도 있다. 예를 들어, 데이터를 10개 묶음으로 그룹화하는 `buffer operator`를 생각해보자. subscriber가 하나의 버퍼를 요청한다면, source가 10개의 데이터를 생산하는 것이 허용된다. 일부 oprator는 `request(1)` round-trip을 방지하고 요청되기 전에 요소를 생산하는 것이 비용이 많이 들지 않으면 prefetching 전략을 구현한다. + +이는 push 모델을 **push-pull 하이브리드 모델**로 변형하는데, 요소가 이미 사용 가능하다면 downstream은 n개의 요소를 upstream으로부터 받아올 수 있다. 하지만, 요소가 당장 사용가능하지 않다면 생산될 때 마다 upstream에서 요소들을 푸쉬 해준다. + +### Hot vs Cold + +Rx Reactive 라이브러리는 Hot과 Cold라는 두 가지의 리액티브 시퀀스를 구별한다. 이러한 구분은 주로 reactive stream이 subscriber에게 어떻게 반응하는지와 관련된다. + +- Cold sequence는 각 subscriber에게 데이터 소스를 포함하여 시퀀스가 새로 시작한다. 예를 들어, source가 HTTP 통신을 사용한다면, 새로운 HTTP 요청이 각 구독마다 새로 만들어진다. +- Hot sequence는 각 subscriber에 대해 시퀀스가 처음부터 시작되지 않는다. 대신에, 나중에 들어온 subscriber는 구독 후 방출되는 신호를 수신한다. 그러나, Hot reactive stream은 전체 또는 일부 방출된 이력을 캐싱하거나 재현할 수 있다. Hot sequence는 아무도 구독하지 않은 경우에도 Hot sequence가 방출될 수 있다. + +source가 생산해내는 데이터의 특징에 따라 Hot과 Cold 중 어떤걸 사용해야 할지 생각을 하면 좋을 것 같다. + +--- +참고 +- https://projectreactor.io/ +- https://github.com/reactor/reactor-core +- https://projectreactor.io/docs/core/3.5.6-SNAPSHOT/reference/index.html diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Reactor\342\200\205Pattern\352\263\274\342\200\205event\342\200\205loop.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Reactor\342\200\205Pattern\352\263\274\342\200\205event\342\200\205loop.md" new file mode 100644 index 00000000..88407a8d --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\353\271\204\353\217\231\352\270\260/reactor/Reactor\342\200\205Pattern\352\263\274\342\200\205event\342\200\205loop.md" @@ -0,0 +1,192 @@ +--- +title: 'Reactor Pattern과 event loop' +lastUpdated: '2024-03-02' +--- + +Reactor 패턴은 동시에 들어오는 여러 종류의 이벤트를 처리하기 위한 동시성을 다루는 디자인 패턴 중 하나이다. Reactor 패턴은 관리하는 리소스에서 이벤트가 발생할 때까지 대기하다가 이벤트가 발생하면 해당 이벤트를 처리할 수 있는 핸들러(`handler`)에게 디스패치(`dispatch`)하는 방식으로 이벤트에 반응하며, '이벤트 핸들링(event handling)', event loop 패턴이라고도 부른다. + +Reactor 패턴은 크게 Reactor와 핸들러로 구성된다. + +|name|description| +|-|-| +|Reactor|무한 반복문을 실행해 이벤트가 발생할 때까지 대기하다가 이벤트가 발생하면 처리할 수 있는 핸들러에게 디스패치한다. 이벤트 루프라고도 부흔다.| +|Handler|이벤트를 받아 필요한 비즈니스 로직을 수행한다.| + +간단한 동작을 나타내는 예제 코드를 확인해보자. + +물론 세부적인 구현은 상황에 맞게 변경할 수 있다. 세부 구현 내용에 초점을 맞추기보다는 리소스에서 발생한 이벤트를 처리하기까지의 과정과, 그 과정에서 Reactor와 핸들러가 어떤 역할을 하는지 이해해보자. + +```java +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.util.Set; + +public class Reactor implements Runnable { + final Selector selector; + final ServerSocketChannel serverSocketChannel; + + Reactor(int port) throws IOException { + selector = Selector.open(); + + serverSocketChannel = ServerSocketChannel.open(); + serverSocketChannel.socket().bind(new InetSocketAddress(port)); + serverSocketChannel.configureBlocking(false); + SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + + // Attach a handler to handle when an event occurs in ServerSocketChannel. + selectionKey.attach(new AcceptHandler(selector, serverSocketChannel)); + } + + public void run() { + try { + while (true) { + // Selector에서 이벤트가 발생하기까지 대기하다가 + // 이벤트가 발생하는 경우 적절한 핸들러에서 처리할 수 있도록 dispatch한다. + selector.select(); + Set selected = selector.selectedKeys(); + for (SelectionKey selectionKey : selected) { + dispatch(selectionKey); + } + selected.clear(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + void dispatch(SelectionKey selectionKey) { + Handler handler = (Handler) selectionKey.attachment(); + handler.handle(); + } +} +``` + +```java +public interface Handler { + void handle(); +} +``` + +```java +import java.io.IOException; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +class AcceptHandler implements Handler { + final Selector selector; + final ServerSocketChannel serverSocketChannel; + + AcceptHandler(Selector selector, ServerSocketChannel serverSocketChannel) { + this.selector = selector; + this.serverSocketChannel = serverSocketChannel; + } + + @Override + public void handle() { + try { + final SocketChannel socketChannel = serverSocketChannel.accept(); + if (socketChannel != null) { + new EchoHandler(selector, socketChannel); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } +} +``` + +```java +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +public class EchoHandler implements Handler { + static final int READING = 0, SENDING = 1; + + final SocketChannel socketChannel; + final SelectionKey selectionKey; + final ByteBuffer buffer = ByteBuffer.allocate(256); + int state = READING; + + EchoHandler(Selector selector, SocketChannel socketChannel) throws IOException { + this.socketChannel = socketChannel; + this.socketChannel.configureBlocking(false); + // Attach a handler to handle when an event occurs in SocketChannel. + selectionKey = this.socketChannel.register(selector, SelectionKey.OP_READ); + selectionKey.attach(this); + selector.wakeup(); + } + + @Override + public void handle() { + try { + if (state == READING) { + read(); + } else if (state == SENDING) { + send(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + void read() throws IOException { + int readCount = socketChannel.read(buffer); + if (readCount > 0) { + buffer.flip(); + } + selectionKey.interestOps(SelectionKey.OP_WRITE); + state = SENDING; + } + + void send() throws IOException { + socketChannel.write(buffer); + buffer.clear(); + selectionKey.interestOps(SelectionKey.OP_READ); + state = READING; + } +} +``` + +## 다양한 event loop 구현체 + +### Netty + +> Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server. + +Netty는 비동기식 이벤트 기반 네트워크 애플리케이션 프레임워크이다. Netty 자체로도 많이 사용하지만 고성능 네트워크 처리를 위해 [정말 많은 프레임워크나 라이브러리](https://netty.io/wiki/related-projects.html)에서 사용되고 있다. 이와 같은 Netty도 기본적으로는 지금까지 살펴본 Java NIO의 Selector와 Reactor 패턴을 기반으로 구현돼 있다. + +### node.js + +> Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. + +JavaScript 런타임 환경을 제공하는 Node.js에도 이벤트 루프가 있다. 정확히는 Node.js에서 사용(참고)하는 libuv 라이브러리가 이벤트 루프를 제공한다. libuv는 비동기식 I/O를 멀티 플랫폼 환경에서 제공할 수 있도록 만든 라이브러리이다. 내부적으로 멀티플렉싱을 위해 epoll, kqueue, IOCP(input/output completion port)를 사용하고 있다. + +### Redis + +> The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker. + +Redis는 이벤트 처리를 위해 자체적으로 이벤트 루프(참고)를 구현해서 사용하는 싱글 스레드 애플리케이션이다. + +```c +typedef struct aeEventLoop{ + int maxfd; + long long timeEventNextId; + aeFileEvent events[AE_SETSIZE]; /* Registered events */ + aeFiredEvent fired[AE_SETSIZE]; /* Fired events */ + aeTimeEvent *timeEventHead; + int stop; + void *apidata; /* This is used for polling API specific data */ + aeBeforeSleepProc *beforesleep; +} aeEventLoop; +``` + +--- +참고 +- https://en.wikipedia.org/wiki/Reactor_pattern \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/AES&IV.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/AES&IV.md" new file mode 100644 index 00000000..b0a2f81e --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/AES&IV.md" @@ -0,0 +1,124 @@ +--- +title: 'AES&IV' +lastUpdated: '2023-05-03' +--- +In AES, For The Same Plaintext, Will We Always Get Same Ciphertext (for the same IV and Key)? + +You will perhaps know that in ECB (Electronic Code Book) mode in AES, you will always get the same ciphertext for the same key. As this is a block cipher, the padding will change the end characters, but as long as we have 16 bytes then the first block will be the same. But what about the modes that use an IV? Well, let’s look at a stream cipher mode: GCM, and see what happens when we use the same key and the same IV. + +So we will create the following Golang cod. + +In this case, we just convert a password into an encryption key, and then just set an IV to all zeros, along with the same salt used with the key generation. If we try “Testing 123” and a password of “qwerty123”, we get : + +```yml +Message: Testing 123 +Cipher: 80f8087e75d6875d56198a082820fb3dc6c0ba7e4bac4f697094e2 +Key: 2f7ffec39904ee5b61a73d881f6d2f36c27d2a60a42d828b52b6409dc13d1318 +Nonce: 000000000000000000000000 +Decrypted: Testing 123 +``` + +If we use the same encryption key and IV, and encrypt “Testing 1234”, we get : + +```yml +Message: Testing 1234 +Cipher: 80f8087e75d6875d56198acf54928581167bca942c9baa407511d4f6 +Key: 2f7ffec39904ee5b61a73d881f6d2f36c27d2a60a42d828b52b6409dc13d1318 +Nonce: 000000000000000000000000 +Decrypted: Testing 1234 +``` + +If we use the same encryption key and IV, and encrypt “Testing 12354”, we get : + +```yml +Message: Testing 12345 +Cipher: 80f8087e75d6875d56198acfa267d902af38c544c7cfb088b51b8b7442 +Key: 2f7ffec39904ee5b61a73d881f6d2f36c27d2a60a42d828b52b6409dc13d1318 +Nonce: 000000000000000000000000 +Decrypted: Testing 12345 +``` + +And so we see “The following is the Golang code : + +```go +package main +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "fmt" + "os" + "golang.org/x/crypto/pbkdf2" +) + +func main() { + passwd := "qwerty" + nonce := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + salt := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + pt := "" + argCount := len(os.Args[1:]) + + if argCount > 0 { + pt = (os.Args[1]) + } + + key := pbkdf2.Key([]byte(passwd), salt, 10000, 32, sha256.New) + block, err := aes.NewCipher(key) + if err != nil { + panic(err.Error()) + } + + plaintext := []byte(pt) + aesgcm, err := cipher.NewGCM(block) + if err != nil { + panic(err.Error()) + } + + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + fmt.Printf("Message:\t%s\n", pt) + fmt.Printf("Cipher:\t\t%x\n", ciphertext) + fmt.Printf("Key:\t\t%x\n", key) + fmt.Printf("Nonce:\t\t%x\n", nonce) + + plain, _ := aesgcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + panic(err.Error()) + } + fmt.Printf("Decrypted:\t%s\n", plain) +} +``` + +If we try “Testing 123” and a password of “qwerty123” : + +```yml +Message: Testing 123 +Cipher: 80f8087e75d6875d56198a082820fb3dc6c0ba7e4bac4f697094e2 +Key: 2f7ffec39904ee5b61a73d881f6d2f36c27d2a60a42d828b52b6409dc13d1318 +Nonce: 000000000000000000000000 +Decrypted: Testing 123 +``` + +If we use the same encryption key and IV, and encrypt “Testing 1234”, we get : + +```yml +Message: Testing 1234 +Cipher: 80f8087e75d6875d56198acf54928581167bca942c9baa407511d4f6 +Key: 2f7ffec39904ee5b61a73d881f6d2f36c27d2a60a42d828b52b6409dc13d1318 +Nonce: 000000000000000000000000 +Decrypted: Testing 1234 +``` + +If we use the same encryption key and IV, and encrypt “Testing 12354”, we get : + +```yml +Message: Testing 12345 +Cipher: 80f8087e75d6875d56198acfa267d902af38c544c7cfb088b51b8b7442 +Key: 2f7ffec39904ee5b61a73d881f6d2f36c27d2a60a42d828b52b6409dc13d1318 +Nonce: 000000000000000000000000 +Decrypted: Testing 12345 +``` + +And so we see `“80f8087e75d6875d56198”` for each of the cipher, and basically that maps to “Testing 123”. The ciphering of “4” is thus: “ac”, and “5” is “fa”. + +And so we can see that we get **the same out for our ciphering, for the same key and the same IV for each of the modes.** + diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/Certificate\342\200\205formats.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/Certificate\342\200\205formats.md" new file mode 100644 index 00000000..99413e38 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/Certificate\342\200\205formats.md" @@ -0,0 +1,63 @@ +--- +title: 'Certificate formats' +lastUpdated: '2024-03-02' +--- + +SSL has been around for long enough you'd think that there would be agreed upon container formats. Many formats are using for implement it. But in the end, all of these are different ways to encode Abstract Syntax Notation 1 (ASN.1) formatted data — which happens to be the format x509 certificates are defined in — in machine-readable ways. + +### `.csr` + +> This is a Certificate Signing Request. + +Some applications can generate these for submission to certificate-authorities. The actual format is PKCS10 which is defined in RFC 2986. It includes some/all of the key details of the requested certificate such as subject, organization, state, whatnot, as well as the public key of the certificate to get signed. These get signed by the CA and a certificate is returned. The returned certificate is the public certificate (which includes the public key but not the private key), which itself can be in a couple of formats. + +### `.pem` + +> PEM on it's own isn't a certificate, it's just a way of encoding data. X.509 certificates are one type of data that is commonly encoded using PEM. + +PEM is a X.509 certificate (whose structure is defined using ASN.1), encoded using the ASN.1 DER (distinguished encoding rules), then run through Base64 encoding and stuck between plain-text anchor lines (BEGIN CERTIFICATE and END CERTIFICATE). + +Defined in RFC 1422 (part of a series from 1421 through 1424) this is a container format that may include just the public certificate (such as with Apache installs, and CA certificate files /etc/ssl/certs), or may include an entire certificate chain including public key, private key, and root certificates. + +Confusingly, it may also encode a CSR as the PKCS10 format can be translated into PEM. The name is from Privacy Enhanced Mail (PEM), a failed method for secure email but the container format it used lives on, and is a base64 translation of the x509 ASN.1 keys. + +### `.key` + +This is a (usually) PEM formatted file containing just the private-key of a specific certificate and is merely a conventional name and not a standardized one. In Apache installs, this frequently resides in /etc/ssl/private. The rights on these files are very important, and some programs will refuse to load these certificates if they are set wrong. + +### `.pkcs12` `.pfx` `.p12` + +Originally defined by RSA in the Public-Key Cryptography Standards (abbreviated PKCS), the "12" variant was originally enhanced by Microsoft, and later submitted as RFC 7292. This is a password-protected container format that contains both public and private certificate pairs. + +Unlike `.pem` files, this container is fully encrypted. Openssl can turn this into a `.pem` file with both public and private keys: `openssl pkcs12 -in file-to-convert.p12 -out converted-file.pem -nodes` + +A few other formats that show up from time to time: + +### `.der` +A way to encode ASN.1 syntax in binary, a .pem file is just a Base64 encoded .der file. OpenSSL can convert these to .pem (openssl x509 -inform der -in to-convert.der -out converted.pem). Windows sees these as Certificate files. By default, Windows will export certificates as .DER formatted files with a different extension. Like... + +### `.cert` `.cer` `.crt` + +A `.pem` (or rarely `.der`) formatted file with a different extension, one that is recognized by Windows Explorer as a certificate, which .pem is not. + +### .p7b .keystore + +Defined in RFC 2315 as PKCS number 7, this is a format used by Windows for certificate interchange. Java understands these natively, and often uses .keystore as an extension instead. Unlike `.pem` style certificates, this format has a defined way to include certification-path certificates. + +### `.crl` + +A certificate revocation list. Certificate Authorities produce these as a way to de-authorize certificates before expiration. You can sometimes download them from CA websites. + +--- + +In summary, there are four different ways to present certificates and their components: + +- **PEM** - Governed by RFCs, used preferentially by open-source software because it is text-based and therefore less prone to translation/transmission errors. It can have a variety of extensions (`.pem`, `.key`, `.cer`, `.cert`, more) +- **PKCS7** - An open standard used by Java and supported by Windows. Does not contain private key material. +- **PKCS12** - A Microsoft private standard that was later defined in an RFC that provides enhanced security versus the plain-text PEM format. This can contain private key and certificate chain material. Its used preferentially by Windows systems, and can be freely converted to PEM format through use of openssl. +- **DER** - The parent format of PEM. It's useful to think of it as a binary version of the base64-encoded PEM file. Not routinely used very much outside of Windows. + +--- +reference +- https://stackoverflow.com/questions/991758/openssl-pem-key +- https://gist.github.com/kirilkirkov/4c73da883088b6ff7420c49af1561b2b diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/Cipher.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/Cipher.md" new file mode 100644 index 00000000..11b9c9bd --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/Cipher.md" @@ -0,0 +1,199 @@ +--- +title: 'Cipher' +lastUpdated: '2024-03-02' +--- + +> https://www.baeldung.com/java-cipher-class + +Simply put, encryption is the process of **encoding a message** such that only authorized users can understand or access it. + +The message, referred to as plaintext, is encrypted using an encryption algorithm – a cipher – generating ciphertext that can only be read by authorized users via decryption. + +In this article, we describe in detail the core Cipher class, which provides cryptographic encryption and decryption functionality in Java. + +## Cipher Class + +**Java Cryptography Extension(JCE)** is the part of the Java Cryptography Architecture(JCA) that provides an application with cryptographic ciphers for data encryption and decryption as well as hashing of private data. + +The `javax.crypto.Cipher` class forms the core of the JCE framework, providing the functionality for encryption and decryption. + +```java +public class Cipher { + + private static final Debug debug = + Debug.getInstance("jca", "Cipher"); + + private static final Debug pdebug = + Debug.getInstance("provider", "Provider"); + private static final boolean skipDebug = + Debug.isOn("engine=") && !Debug.isOn("cipher"); + + /** + * Constant used to initialize cipher to encryption mode. + */ + public static final int ENCRYPT_MODE = 1; + + /** + * Constant used to initialize cipher to decryption mode. + */ + public static final int DECRYPT_MODE = 2; + ... +} +``` + +## Cipher Instatntiation + +To instantiate a Cipher object, we call the static getInstance method, passing the name of the requested transformation. Optionally, the name of a provider may be specified. + +Let's write an example class illustrating the instantiation of a Cipher: + +```java +public class Encryptor { + + public byte[] encryptMessage(byte[] message, byte[] keyBytes) + throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + //... + } +} +``` + +The transformation AES/ECB/PKCS5Padding tells the getInstance method to instantiate the Cipher object as an AES cipher with ECB mode of operation and PKCS5 padding scheme. + +We can also instantiate the Cipher object by specifying only the algorithm in the transformation: + +```java +Cipher cipher = Cipher.getInstance("AES"); +``` + +In this case, Java will use provider-specific default values for the mode and padding scheme. + +Note that getInstance will throw a NoSuchAlgorithmException if the transformation is null, empty, or in an invalid format, or if the provider doesn't support it. Also it will throw a NoSuchPaddingException if the transformation contains an unsupported padding scheme. + + +## Thread-Safety + +The Cipher class is a stateful one without any form of internal synchronization. As a matter of fact, methods like `init()` or `update()` will change the internal state of a particular Cipher instance. + +Therefore, the Cipher class is not thread-safe. So we should create one Cipher instance per encryption/decryption need. + +> reference: https://www.baeldung.com/java-cipher-class + +## Keys + +The Key interface represents keys for cryptographic operations. Keys are opaque containers that hold an encoded key, the key's encoding format, and its cryptographic algorithm. + +Keys are generally obtained through key generators, certificates, or key specifications using a key factory. + +Let's create a symmetric Key from the supplied key bytes: + +```java +SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); +``` + +## Cipher Initialization + +We call the `init()` method to **initialize the Cipher object with a Key** or **Certificate and an `opmode` indicating the operation mode of the cipher.** + +Optionally, we can pass in a source of randomness. By default, a SecureRandom implementation of the highest-priority installed provider is used. Otherwise, it'll use a `system-provided` source. We can specify a set of algorithm-specific parameters optionally. For example, we can pass an `IvParameterSpec` to specify an initialization vector. + +Here are the available cipher operation modes: + +- **ENCRYPT_MODE:** initialize cipher object to encryption mode +- **DECRYPT_MODE:** initialize cipher object to decryption mode +- **WRAP_MODE:** initialize cipher object to key-wrapping mode +- **UNWRAP_MODE:** initialize cipher object to key-unwrapping mode + +Let's initialize the Cipher object: + +```java +Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); +SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); +cipher.init(Cipher.ENCRYPT_MODE, secretKey); +// ... +``` + +Now, the init method throws an InvalidKeyException if the supplied key is inappropriate for initializing the cipher, like when a key length/encoding is invalid. + +It's also thrown when the cipher requires certain algorithm parameters that cannot be determined from the key, or if the key has a key size that exceeds the maximum allowable key size (determined from the configured JCE jurisdiction policy files). + +Let's look at an example using a Certificate: + +```java +public byte[] encryptMessage(byte[] message, Certificate certificate) + throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException { + + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, certificate); + // ... +} +``` + +The Cipher object gets the public key for data encryption from the certificate by calling the getPublicKey method. + +## Encryption and Decryption + +After initializing the Cipher object, we call the `doFinal()` method to perform the encryption or decryption operation. This method returns a byte array containing the encrypted or decrypted message. + +The `doFinal()` method also resets the Cipher object to the state it was in when previously initialized via a call to `init()` method, making the Cipher object available to encrypt or decrypt additional messages. + +Let's call doFinal in our encryptMessage method: + +```java +public byte[] encryptMessage(byte[] message, byte[] keyBytes) + throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, + BadPaddingException, IllegalBlockSizeException { + + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(message); +} +``` + +To perform a decrypt operation, we change the opmode to DECRYPT_MODE: + +```java +public byte[] decryptMessage(byte[] encryptedMessage, byte[] keyBytes) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, + BadPaddingException, IllegalBlockSizeException { + + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey secretKey = new SecretKeySpec(keyBytes, "AES"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(encryptedMessage); +} +``` + +## exmaple code + +In this test, we use AES encryption algorithm with a 128-bit key and assert that the decrypted result is equal to the original message text: + +```java +@Test +public void whenIsEncryptedAndDecrypted_thenDecryptedEqualsOriginal() + throws Exception { + + String encryptionKeyString = "thisisa128bitkey"; + String originalMessage = "This is a secret message"; + byte[] encryptionKeyBytes = encryptionKeyString.getBytes(); + + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey secretKey = new SecretKeySpec(encryptionKeyBytes, "AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + byte[] encryptedMessageBytes = cipher.doFinal(message.getBytes()); + + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + byte[] decryptedMessageBytes = cipher.doFinal(encryptedMessageBytes); + assertThat(originalMessage).isEqualTo(new String(decryptedMessageBytes)); +} +``` + +## Conclusion + +In this article, we discussed the Cipher class and presented usage examples. More details on the Cipher class and the JCE Framework can be found in the [class documentation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/crypto/Cipher.html) and the [Java Cryptography Architecture (JCA) Reference Guide](https://docs.oracle.com/javase/9/security/java-cryptography-architecture-jca-reference-guide.htm) + +Implementation of all these examples and code snippets can be found over on GitHub. This is a Maven-based project, so it should be easy to import and run as it is. + diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/DB\342\200\205\354\225\224\355\230\270\355\231\224.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/DB\342\200\205\354\225\224\355\230\270\355\231\224.md" new file mode 100644 index 00000000..172cd557 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\225\224\355\230\270\355\231\224/DB\342\200\205\354\225\224\355\230\270\355\231\224.md" @@ -0,0 +1,54 @@ +--- +title: 'DB 암호화' +lastUpdated: '2024-03-02' +--- + +DB 암호화 기술을 적용할 때는 운영 환경에 적합한 기술을 도입하는 것이 중요하다. DB를 암호화 할 떄에 고려해야하는 부분들에 대해 알아보자. + +### 암호화 대상 및 범위 분석 + +암호화 대상은 국내 법·규정에서 규정한 개인정보 및 그 외 중요 데이터를 우선적으로 선정해야 한다. + +**[금융정보원 DB 암호화 기술 가이드]** + +![image](https://user-images.githubusercontent.com/81006587/235272328-50788a72-0153-4c2b-8157-f38a470d4e76.png) + +### 검증된 암호화 알고리즘 적용 + +DB의 중요 데이터를 암호화하는데 적용되는 알고리즘은 양방향 알고리즘과 단방향 알고리즘이 있다. 개인정보는 양뱡항 알고리즘으로 암호화해야 하며, 비밀번호는 복호화할 수 없는 단방향 알고리즘으로 암호화한다. + +검증되지 않은 알고리즘을 사용하는 경우, 암호문이 해독될 수 있어 위험하므로, 국내외 암호연구기관이 권장하는 다음의 암호 알고리즘을 사용해야 한다. + +**[대칭형 알고리즘]** + +|알고리즘|설명| +|-|-| +|DES|가장 널리 사용되는 암호화 알고리즘이다. 주로 파일이나 패킷을 암호화할 때 많이 사용되지만, 64bit 입력 블록과 56bit의 짧은 암호키를 사용하기 때문에 보안이 강력하진 않다. 따라서 매우 중요한 보안이 아닌 경우 자주 사용되다.| +|3-DES|DES를 3번 반복해서 암호화하는 방식이다. 보안성이 향상되지만 그만큼 성능은 떨어진다.| +|AES|미국 NIST에서 표준화한 알고리즘이다. 128bit 입력 블록을 적용하여 보안성이 좋다.| +|SEED|KISA 주관으로 ETRI와 함꼐 국내에서 만들어진 알고리즘이다. AES처럼 128bit 입력 블록을 사용하여 국제 표준에 부합한다.| +|ARIA|NSRI에서 만든 알고리즘으로, 비밀키 규격이 AES와 동일하다. 공공에서 사용할 목적으로 만들어졌다.| +|MASK|기존의 알고리즘은 Charset을 고려하지 않아 문자가 깨지거나 길이가 블록단위로 패딩되는 문제가 생겼는데, 이러한 문제점을 보안하기 위해 개발됐다. ANSI와 Unicode를 상호분석하여 Charset이 깨지지 않도록 한다.| + +**[비대칭형 알고리즘]** + +|알고리즘|설명| +|-|-| +|RSA|공개키 암호체계의 세계적인 표준이고 매우 널리 사용되고 있는 알고리즘이다. 개인키나 공개키로 암호화하는 것에 모두 수학적으로 안전하다. 더 큰 소수를 사용하여 보안을 높일 수 있다.| +|DSA|NIST가 공포한 디지털 서명방법이다. DSA는 전자 서명을 통해 인증되며 기밀성 유지에는 사용되지 않는다.| + +### 안전한 키 관리 + +DB 암·복호화에 사용되는 키는 두가지이다. + +- 암·복호화키: DB 데이터를 암·복호화 +- 마스터키: 암·복호화키를 암호화 + +대칭키 방식에서는 암호화 키가 노출되면 암호문을 복호화할 수 있으므로, 키를 안전하게 관리하는 것이 무엇보다 중요하다. 키는 아래 7단계로 관리한다. + +``` +키 생성 → 키 분배 → 키 저장 → 키 사용 → 키 백업/복구 → 키 교체 → 키 폐기 +``` + +이 외에 암·복호화 서버 부하 분산 방안, 다양한 DBMS 및 OS 지원 여부, 암복호화 권한 및 접근통제 지원 여부 등 성능과 운영, 보안 관련 정책들을 마련하는 것도 중요하다. + diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\354\247\201\353\240\254\355\231\224\342\200\205serialVersionUID.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\247\201\353\240\254\355\231\224\342\200\205serialVersionUID.md" new file mode 100644 index 00000000..feabcb57 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\354\247\201\353\240\254\355\231\224\342\200\205serialVersionUID.md" @@ -0,0 +1,44 @@ +--- +title: '직렬화 serialVersionUID' +lastUpdated: '2024-03-02' +--- + +가끔 자바코드에서 이런 코드를 발견할 때가 있다. + +```java + private static final long serialVersionUID = 3487495895819393L; +``` + +이 `serialVersionUID`라는 long 타입의 상수는 직렬화할 객체의 버전이 바뀌었는지 식별하기 위한 클래스의 고유 식별자이다. + +직렬화와 역직렬화 할때 이 값이 동일하면 동일한 클래스의 데이터라는 것을 확인할 수 있고, 그렇지 않은 경우에는 다르거나 바뀐 클래스라고 생각하여 오류를 throw할 수 있다. + +자바 스펙에 따르면, serialVersion을 명시적으로 선언해놓지 않았을 땐 직렬화 런타임이 기본 serialVersion을 클래스의 기본 해쉬값을 가지고 자동으로 계산해준다고 한다. + +> 하지만 JVM에 의한 default serialVersionUID 계산은 클래스의 세부 사항을 매우 민감하게 반영하기 때문에 약간의 변경사항만 있어도 deserialization 과정에서 InvalidClassException이 생길 수 있다. 그렇기 때문에 클래스 호환 문제를 어느정도 직접 관리할 수 있다면 이 값을 고정된 값으로 정의해주는게 좋다. + + +java의 ObjectStreamClass에 가보면 이런 식으로 데이터를 저장하는 변수가 정의되어있는 것을 볼 수 있다. + +```java + private volatile Long suid; +``` + +```java + private static Long getDeclaredSUID(Class cl) { + try { + Field f = cl.getDeclaredField("serialVersionUID"); + int mask = Modifier.STATIC | Modifier.FINAL; + if ((f.getModifiers() & mask) == mask) { + f.setAccessible(true); + return f.getLong(null); + } + } catch (Exception ex) { + } + return null; + } +``` + +스프링 Web을 쓸때도 똑같이 역직렬화/직렬화 과정을 거치긴 하지만 웹 통신을 할때는 byte를 주거나 받기만 하고, 본인이 직렬화한 데이터를 다시 역직렬화할 일은 없기 때문에 serialVersionUID에 대한 걱정은 하지 않아도 된다. + +하지만 데이터를 Kafka에 직렬화 해서 넣어놓고 받는 로직을 쓴다거나 하면 클래스 변경에 유의하거나 uid 값을 정의하기를 고려해보는 것이 좋을 것 같다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/HashedWheelTimer.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/HashedWheelTimer.md" new file mode 100644 index 00000000..0c7dfd13 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/HashedWheelTimer.md" @@ -0,0 +1,302 @@ +--- +title: 'HashedWheelTimer' +lastUpdated: '2024-03-02' +--- + +Timers are important for failure recovery, rate based flow control, scheduling algorithms, controlling packet lifetime in networks. And Efficient timer algorithms are required to reduce the overall interrupt overhead + +- Timer maintenance high if + - Processor interrupted every clock tick + - Fine granularity timers are used + - outstanding timers is high + +## Model & Performance Measure + +Therefore, we will look at several ways of implementing timers. And we're going to look at Hashed WheelTimer's approach. First, define the features and methods that the timer should have, and how it compares performance. + +**Routines in the model** + - Client Invoked : + The interface provided by the timer facility is as follows: + ```js + START_TIMER(Interval, Timer_ID, Expiry_Action) + Start a timer lasting Interval, identified by Timer_ID, and when expired, perform Expiry_Action + + STOP_TIMER(Timer_ID) + Stop the timer identified by Timer_ID, and clean it up + ``` + Now for all the schemes we will assume we have some source of clock ticks, probably hardware, but this could also be some other software interface. Whatever resolution these clock ticks are (every second, every millisecond, or every microsecond) are the “atomic”, individisble clock ticks we will construct our timer from. For our calculations we will assume the granularity of our timer is T clock ticks.
Then to evaluate our timer implementation we will also consider two more operations: + + - Timer tick invoked : + ```js + PER_TICK_BOOKKEEPING + For every T clock ticks we will have to check for expired timers. This is how much work we have to do when we check for expired timers. + + EXPIRY_PROCESSING + This is the routine that does the Expiry_Action from the START_TIMER call. + ``` + +- **Performance Measure** + - Space : Memory used by the data structures + - Latency : Time required to begin and end any of the routines mentioned above + +## Several ways of implementing timers + +### 1. Straightforward + +image + +The first approach is the most straightforward. `START_TIMER` allocates a timer struct that stores the expiry action and the interval of the timer. `PER_TICK_BOOKKEEPING` goes through all the timers and decrements the interval by one tick, and if the timer has counted down to zero, it calls the expiry action. + +It’s fast for all operations except `PER_TICK_BOOKKEEPING` and to quote the paper is only appropriate if + +- There are only a few outstanding timers. +- Most timers are stopped within a few ticks of the clock. +- `PER_TICK_BOOKKEEPING` is done with suitable performance by special-purpose hardware. + +- START_TIMER = O(1) +- STOP_TIMER = O(1) +- PER_TICK_BOOKKEEPING = O(n) + +### 2. Ordered List / Timer Queues + +image + +Instead of having `PER_TICK_BOOKKEEPING` operate on every timer, this scheme has it operate on **just one**. + +It stores the absolute expiry time for timers instead of how much remaining time it has left. It then keeps the timers in a sorted list, ordered by their expiry time, with the lowest in the front, so the head of the list is the timer that will expire first. + +Now `START_TIMER` has to perform possibly O(n) work to insert the timer into the sorted list, but `PER_TICK_BOOKKEEPING` is vastly improved. On each tick, the timer only increments the current time, and compares it with the head of the list. If the timer is expired, it calls `EXPIRY_PROCESSING` and removes it from the list, continuing until the head of the list is an unexpired timer. + +This is the best performance we can get. We only do constant work per tick, and then the necessary work when timers are expired. + +- START_TIMER = O(n) +- STOP_TIMER = O(1) +- PER_TICK_BOOKKEEPING = O(1) + +### 3. Tree-based Algorithms + +image + +There is a great deal of similarity between trying to manage which timer has the smallest expiration time and sorting. The difference between timers and classical sorting is that we don’t know all of the elements to start with, as more will come at some later point. The authors call this modified version, “dyanmic sorting.” + +Using that lens, we can view the ordered list in number 2 as simply insertion sort, which takes `O(n)` work per item. However this isn’t the best and we can do better, usually via quicksort or merge sort. Quicksort doesn’t necessarily translate well to the problem, but the authors suggest using a balanced binary search tree in place of a list and using that to keep track of the timers. In this case our `START_TIMER` costs only `O(log(n))` and everything else is the same. + +- START_TIMER = O(log n) +- STOP_TIMER = O(1) +- PER_TICK_BOOKKEEPING = O(1) + +### 4. Simple Timing Wheel + +In the simulation of digital circuits, it is often sufficient to consider event scheduling at time instants that are multiples of the clock interval, say `c`. Then, after the program processes an event, it increments the clock variable by `c` until it finds any outstanding events at the current time. It then executes the events. + +The idea is that given we are at time `t` we’ll have a timing wheel that consists of an array of lists. The array is of size `N` and each index `i` in the array holds a list of timers that will expire at time `t + i`. This allows us to schedule events up to `N` clock ticks away. For events beyond that, the timing wheel also has an overflow list of timers that expire at a time later than `t + N - 1`. + +`START_TIMER` will add the timer either to the appropriate list in the array, or into the overflow list. In the common case, `PER_TICK_BOOKKEEPING` increments the current time index, checks the current time index in the array for expired timers and performs the expiry action as necessary. Every `N` clock ticks, `PER_TICK_BOOKKEEPING` will need to reset the current time index to 0 and “rotate” the timing wheel, moving items from the overflow list into the appropriate list in the array. + +The downside to this approach is that as we approach `N` clock ticks, right before we’ll need to perform a rotation, it’s more and more likely that timers will be added to the overflow list. One known solution at the time was to rotate the timing wheel half way through, every `N/2` ticks. This reduces the severity of the problem, but doesn’t quite do away with it.v + +image + +Simple Timing Wheel is the simple implementation of that idea. + +- Keep a large timing wheel +- A curser in the timing wheel moves one location every time unit (just like a seconds hand in the clock) +- If the timer interval is within a rotation from the current curser position then put the timer in the corresponding location +- Requires exponential amount of memory + +We create a timing wheel consisting of an array of `N` slots. The array is a circular buffer indexed by the current time `i by i mod N`. `START_TIMER` for a timer with interval j gets placed into the list located at index `(i + j) mod N`. + +`PER_TICK_BOOKKEEPING` only has to increment the current time i and inspect the list at index i mod N and perform expiry actions as necessary. This is ideal in that START_TIMER does only O(1) work and `PER_TICK_BOOKKEEPING` does only `O(1)` extra work, besides the necessary work of handling expiry actions of expired timers. + +- START_TIMER = O(1) +- STOP_TIMER = O(1) +- PER_TICK_BOOKKEEPING = O(1) + +The downside to this solution is that we can only provide timers of a max interval `N`. And if we decide to grow `N`, then we in turn must grow the size of our timing wheel, meaning that if our clock resolution is one millisecond, and we wish to have a max interval of one hour, we’ll need a 3.6 million element timing wheel. This can quickly become prohibitively expensive in terms of memory usage. + +### 5. Hashed Timing Wheel + +we have a fixed amount of memory to store these timers, instead of having a memory slot for each possible timer expiration date, we can have each slot represent many possible expiration dates. Then to figure out which expiration dates correspond to which slots, we use a hash function to hopefully evenly distribute our expiration dates over all slots. + +This is in fact what `Hashed Timing Wheel` does. Using a power of 2 array size, and a bit mask, The lower bits, lb, index into the array at point `(i + lb) mod N` where `i` is the current time and N is the array size. Then the higher order bits are stored into the list at that index. + +image + +> In Figure 9, let the table size be 256 and the timer be a 32 bit timer. The remainder on division is the last 8 bits. Let the value of the last 8 bits be 20. Then the timer index is 10 `(Curent Time Pointer) + 20 (remainder) = 30`. The 24 high order bits are then inserted into a list that is pointed to by the 30th element.
[Hashed and Hierarchical Timing Wheels: Data Structures or the Efficient Implementation of a Timer Facility, 1987](http://www.cs.columbia.edu/~nahum/w6998/papers/sosp87-timing-wheels.pdf) + +image + +
+ +- Say wheel has 8 ticks +- Timer value = 17 +- Make 2 rounds of wheel + 1 more tick +- Schedule the timer in the bucket “1” +- Keep the # rounds with the timer +- At the expiry processing if the `# rounds > 0` then reinsert the timer + +- Sorted Lists in each bucket + - The list in each bucket can be insertion sorted + - Hence `START_TIMER` takes `O(n)` time in the worst case + - If `n < WheelSize` then average `O(1)` +- Unsorted list in each bucket + - List can be kept unsorted to avoid worst case `O(n)` latency for START_TIMER + - However worst case `PER_TICK_BOOKKEEPING = O(n)` + - Again, if `n < WheelSize then average O(1)` + +
+ +Hashed Timing Wheel data structure is better as it holds lock only on that sec value of the list. But to efficiently define timers that can specify a larger time, a hierarchical structure can be used. + +image + +
+ +- START_TIMER = O(m) where m is the number of wheels. The bucket value on each wheel needs to be calculated +- STOP_TIMER = O(1) +- PER_TICK_BOOKKEEPING = O(1) on avg. + +
+ +## Comparison + +||START_TIMER|STOP_TIMER|PER_TICK| +|-|-|-|-| +|Straight Fwd|O(1)|O(1)|O(n)| +|Sequential List|O(n)|O(1)|O(1)| +|Tree Based|O(log(n))|O(1)|O(1)| +|Simple Wheel|O(1)|O(1)|O(1)| +|Hashed Wheel (sorted)|O(n) worst case
O(1) avg|O(1)|O(1)| +|Hashed Wheel (unsorted)|O(1)|O(1)|O(n) worst case
O(1) avg| +|Hierarchical Wheels|O(m)|O(1)|O(1)| + +# HashedWheelTimer + +`HashedWheelTimer` is a Class which effecient timer implementation of netty, a timer optimized for approximeted I/O timeout scheduling. + +As described with 'approximated', this timer does not execute the scheduled TimerTask on time. HashedWheelTimer, on every tick, will check if there are any TimerTasks behind the schedule and execute them. + +When adding a new task, hold a lock on the list and add it. +For every tick, the Timeout manager hold a lock and scan through the List, decrement toExpire time for the tasks and expire tasks what need to expire. + +Based on explaination in front, + +- HashedWheelTimer maintains a data structure called 'wheel'. To put simply, a wheel is a hash table of TimerTasks whose hash function is 'dead line of the task'. When we add a new task, we compute taskToExpire mod 60, go to that key, hold the lock, take the list out and add tuple (counter, task) to the list where the counter is the timeToExpire / 60. + +- For every sec, we go the key for that second and hold the lock on the list and scan through the list, expire all the task which have counter 0 and decrease the counter of rest of the task tuples. + +It's wheel is maintain of inner class `HashedWheelBucket`'s array. `HashedWheelBucket` is a linked list of `HashedWheelTimeout` that contains information about each added timeout. + +```java + private static HashedWheelBucket[] createWheel(int ticksPerWheel) { + //ticksPerWheel may not be greater than 2^30 + checkInRange(ticksPerWheel, 1, 1073741824, "ticksPerWheel"); + + ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel); + HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel]; + for (int i = 0; i < wheel.length; i ++) { + wheel[i] = new HashedWheelBucket(); + } + return wheel; + } +``` + +You can increase or decrease the accuracy of the execution timing by specifying smaller or larger tick duration in the constructor. In most network applications, I/O timeout does not need to be accurate. Therefore, the default tick duration is 100 milliseconds and you will not need to try different configurations in most cases. + +And **HashedWheelTimer creates a new thread whenever it is instantiated and started**. Therefore, you should make sure to create only one instance and share it across your application. One of the common mistakes, that makes your application unresponsive, is to create a new instance for every connection. + +```java + public HashedWheelTimer( + ThreadFactory threadFactory, + long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection, + long maxPendingTimeouts, Executor taskExecutor) { + + ... + workerThread = threadFactory.newThread(worker); + ... + } +``` + +The generated worker thread acts as a timer by moving the cursor and checking each timeout. + +```java + private final class Worker implements Runnable { + ... + @Override + public void run() { + // Initialize the startTime. + startTime = System.nanoTime(); + if (startTime == 0) { + // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized. + startTime = 1; + } + + // Notify the other threads waiting for the initialization at start(). + startTimeInitialized.countDown(); + + do { + final long deadline = waitForNextTick(); + if (deadline > 0) { + int idx = (int) (tick & mask); + processCancelledTasks(); + HashedWheelBucket bucket = + wheel[idx]; + transferTimeoutsToBuckets(); + bucket.expireTimeouts(deadline); + tick++; + } + } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED); + + // Fill the unprocessedTimeouts so we can return them from stop() method. + for (HashedWheelBucket bucket: wheel) { + bucket.clearTimeouts(unprocessedTimeouts); + } + for (;;) { + HashedWheelTimeout timeout = timeouts.poll(); + if (timeout == null) { + break; + } + if (!timeout.isCancelled()) { + unprocessedTimeouts.add(timeout); + } + } + processCancelledTasks(); + } + ... + } +``` + +```java + public void expireTimeouts(long deadline) { + HashedWheelTimeout timeout = head; + + // process all timeouts + while (timeout != null) { + HashedWheelTimeout next = timeout.next; + if (timeout.remainingRounds <= 0) { + next = remove(timeout); + if (timeout.deadline <= deadline) { + timeout.expire(); + } else { + // The timeout was placed into a wrong slot. This should never happen. + throw new IllegalStateException(String.format( + "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline)); + } + } else if (timeout.isCancelled()) { + next = remove(timeout); + } else { + timeout.remainingRounds --; + } + timeout = next; + } + } +``` + +--- +reference + +- https://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt +- https://cseweb.ucsd.edu/users/varghese/PAPERS/twheel.ps.Z +- https://medium.com/@raghavan99o/hashed-timing-wheel-2192b5ec8082 +- http://www.cs.columbia.edu/~nahum/w6998/papers/sosp87-timing-wheels.pdf +- https://paulcavallaro.com/blog/hashed-and-hierarchical-timing-wheels/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\342\200\205server\342\200\205\354\230\210\354\240\234.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\342\200\205server\342\200\205\354\230\210\354\240\234.md" new file mode 100644 index 00000000..4a6659e9 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\342\200\205server\342\200\205\354\230\210\354\240\234.md" @@ -0,0 +1,135 @@ +--- +title: 'netty server 예제' +lastUpdated: '2024-03-02' +--- + +클라이언트의 입력을 그대로 응답으로 돌려주는 서버를 Netty 프레임워크로 구현하고 이해해보자. + +### 메인 클래스 + +```kotlin +class EchoServer { + + private val allChannels: ChannelGroup = DefaultChannelGroup("server", GlobalEventExecutor.INSTANCE) + private var bossEventLoopGroup: EventLoopGroup? = null + private var workerEventLoopGroup: EventLoopGroup? = null + + fun startServer() { + + // (1) + bossEventLoopGroup = NioEventLoopGroup(1, DefaultThreadFactory("boss")) + workerEventLoopGroup = NioEventLoopGroup(1, DefaultThreadFactory("worker")) + + // (2) + val bootstrap = ServerBootstrap() + bootstrap.group(bossEventLoopGroup, workerEventLoopGroup) + + // Channel 생성시 사용할 클래스 (NIO 소켓을 이용한 채널) + bootstrap.channel(NioServerSocketChannel::class.java) + + // accept 되어 생성되는 TCP Channel 설정 + bootstrap.childOption(ChannelOption.TCP_NODELAY, true) + bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true) + + // (3) + // Client Request를 처리할 Handler 등록 + bootstrap.childHandler(EchoServerInitializer()) + + // (4) + try { + // Channel 생성후 기다림 + val bindFuture = bootstrap.bind(InetSocketAddress(SERVER_PORT)).sync() + val channel: Channel = bindFuture.channel() + allChannels.add(channel) + + // Channel이 닫힐 때까지 대기 + bindFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + throw RuntimeException(e) + } finally { + close() + } + } + + private fun close() { + allChannels.close().awaitUninterruptibly() + workerEventLoopGroup!!.shutdownGracefully().awaitUninterruptibly() + bossEventLoopGroup!!.shutdownGracefully().awaitUninterruptibly() + } + + companion object { + private const val SERVER_PORT = 8080 + + @Throws(Exception::class) + @JvmStatic + fun main(args: Array) { + EchoServer().startServer() + } + } +} +``` + +netty TCP 서버를 생성하기 위해서는 다음 과정을 수행해야한다. + +1. EventLoopGroup을 생성 + - `NIO` 기반의 `EventLoop`를 생성해서, 비동기처리할 수 있도록 헀다. `bossEventLoopGroup`은 서버 소켓을 listen하고, `workerEventLoopGroup`은 만들어진 Channel에서 넘어온 이벤트를 처리하는 역할을 할 것이다. 각각 1개의 쓰레드를 할당해줬다. + +2. ServerBootstrap을 생성하고 설정 + - netty 서버를 생성하기 위한 헬퍼 클래스인 ServerBootstrap 인스턴스를 만들어준다. + - 우선 만들어둔 EventLoopGroup을 `group()` 메서드로 세팅해주고, 채널을 생성할 때 NIO 소켓을 이용한 채널을 생성하도록 `channel()` 메소드에 `NioServerSocketChannel.class`를 인자로 넘겨준다. + - 그리고 TCP 설정을 `childOption()`으로 설정해준다. `TCP_NODELAY`, `SO_KEEPALIVE` 설정이 이 서버 소켓으로 연결되어 생성되는 connection에 적용될 것이다. + +3. ChannelInitializer 생성 + - 채널 파이프라인을 설정하기 위해 `EchoServerInitializer` 객체를 할당한다. 서버 소켓에 연결이 들어오면 이 객체가 호출되어 소켓 채널을 초기화해준다. + +4. 서버 시작 + - 마지막으로 bootstap의 `bind()` 메서드로 서버 소켓에 포트를 바인딩한다. `sync()` 메서드를 호출해서 바인딩이 완료될 때까지 기다리고, 서버가 시작된다. + +### 서버의 채널 파이프라인을 정의하는 클래스 + +```java +class EchoServerInitializer : ChannelInitializer() { + + @Throws(Exception::class) + override fun initChannel(ch: SocketChannel) { + val pipeline: ChannelPipeline = ch.pipeline() + pipeline.addLast(LineBasedFrameDecoder(65536)) + pipeline.addLast(StringDecoder()) + pipeline.addLast(StringEncoder()) + pipeline.addLast(EchoServerHandler()) + } +} +``` + +`initChannel()` 메서드의 역할은 채널 파이프라인을 만들어주는 것이다. TCP 연결이 accept 되면 이 파이프라인을 따라 각 핸들러에 해당하는 동작이 수행된다. + +inbound와 Outboud 핸들러가 섞여있는 것을 볼 수 있는데, 채널에 이벤트(메시지)가 발생하면 소켓 채널에서 읽어들이는 것인지 소켓 채널로 쓰는 것인지에 따라서 파이프라인의 핸들러가 수행된다. + +이 파이프라인에서는 `LineBasedFrameDecoder()`를 통해 네트워크에서 전송되는 바이트 값을 읽어 라인 문자열로 만들어주고, 필요하다면 디코딩을 한 다음 `EchoServerHandler`를 호출해준다. 이후 `write()`가 되면 `StringEncoder()`를 통해 네트워크 너머로 데이터를 전송하게 된다. + +채널이 생성될때마다 호출되는 메서드이기 때문에, 이 코드에서는 각 핸들러 메서드의 객체가 매번 새로 생성된다. 원한다면 핸들러 객체를 싱글톤으로 해서 공유하도록 할 수 있다. (물론 그렇게 하면 클래스를 무상태로 설계해야한다.) + +마지막에 추가한 `EchoServerHandler`는 우리가 정의할 클래스이고, 나머지는 네티에 정의되어있는 코덱이다. (`io.netty.handler.codec`) + +### 클라이언트로부터 메시지를 받았을때 처리할 클래스 + +```kotlin +class EchoServerHandler : ChannelInboundHandlerAdapter() { + + override fun channelRead( + ctx: ChannelHandlerContext, + msg: Any + ) { + val message = msg as String + val channel = ctx.channel() + channel.writeAndFlush("Response : '$message' received\n") + if ("quit" == message) { + ctx.close() + } + } +} +``` + +전달받은 msg를 가지고 원하는 값으로 변환해서 `writeAndFlush()` 해주면 클라이언트에게 그 데이터를 그대로 반환한다. + + \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\342\200\205\353\251\224\354\213\234\354\247\200\342\200\205\354\240\204\354\206\241\342\200\205\355\235\220\353\246\204.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\342\200\205\353\251\224\354\213\234\354\247\200\342\200\205\354\240\204\354\206\241\342\200\205\355\235\220\353\246\204.md" new file mode 100644 index 00000000..b80027d9 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\342\200\205\353\251\224\354\213\234\354\247\200\342\200\205\354\240\204\354\206\241\342\200\205\355\235\220\353\246\204.md" @@ -0,0 +1,89 @@ +--- +title: 'netty 메시지 전송 흐름' +lastUpdated: '2024-03-02' +--- + + + +1. `Channel`을 통해 메시지 전송을 요청한다. + +```java +Channel channel = ... +channel.writeAndFlush(message); +``` + +2. Channel은 `ChannelPipeline`으로 메시지를 전달한다. + +- ChannelPipeline은 기본적으로 `TailContext`와 `HeadContext`를 가진다. (Pipeine의 시작과 끝이라 할 수 있다.) +- Tail과 Head 사이에는 사용자가 등록한 ChannelHandlerContext가 체인 구조로 연결되고, 전달된 메시지가 체인을 따라 Outbound 방향으로 흘러간다. + +3. ChannelPipeline이 메시지가 각각의 Handler를 거칠 때 마다, **Handler에게 바인딩된 EventExecutor 쓰레드**와 현재 **메시지 전송을 요청하고 있는 쓰레드**가 동일한지 체크한다. + +- 만약 서로 다른 쓰레드라면(Handler의 EventExecutor가 아니라면) **메시지를 Queue에 삽입하고 그대로 실행을 반환한다.** +- Queue에 쌓인 메시지는 이후에 EventExecutor에 의해 비동기적으로 처리되게 된다. +- Pipeline의 첫 ChannelHandlerContext에서는 **항상** 요청 쓰레드와 EventExecutor가 다르게 되고 메시지가 Queue에 쌓인다. + +```java +abstract class AbstractChannelHandlerContext ... { + private void write(Object msg, boolean flush, ChannelPromise promise) { + ... + EventExecutor executor = next.executor(); + if (executor.inEventLoop()) { + if (flush) { + next.invokeWriteAndFlush(m, promise); + } else { + next.invokeWrite(m, promise); + } + } else { + final WriteTask task = WriteTask.newInstance(next, m, promise, flush); + if (!safeExecute(executor, task, promise, m, !flush)) { + task.cancel(); + } + } + ... + } +} +``` + +4. 만약 사용자가 별도의 EventExecutor를 설정하지 않았다면(기본 설정) 모든 Handler는 Channel의 EventLoop 쓰레드를 공유해서 사용하게 된다. 그러으로 Pipeline의 Tail 외에는 Queue에 메시지가 버퍼링되는 일이 일어나지 않는다. +- 반면에 사용자가 특정 Handler의 EventExecutor를 설정해주었다면, Executor가 달라지는 Handler에서는 Queue에 메시지가 버퍼링 된 후 서로 다른 EventExecutor에 의해 메시지가 비동기적으로 처리되게 된다. + +```java +abstract class AbstractChannelHandlerContext ... { + public EventExecutor executor() { + if (executor == null) { + return channel().eventLoop(); + } else { + return executor; + } + } +} +``` + + + +5. Pipeline을 통과한 메시지는 다시 Channel로 전달된다. + +6. Netty Channel은 내부적으로 NIO 채널을 통해 네트워크로 메시지를 전송한다. + +```java +public class NioSocketChannel ... { + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + SocketChannel ch = javaChannel(); + ... + ByteBuffer buffer = nioBuffers[0]; + int attemptedBytes = buffer.remaining(); + final int localWrittenBytes = ch.write(buffer); + if (localWrittenBytes <= 0) { + incompleteWrite(true); + return; + } + ... + } +} +``` + +--- +참고 +- https://github.com/netty/netty +- [네티 인 액션 : Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발](https://m.yes24.com/Goods/Detail/25662949) diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\354\235\230\342\200\205thread\342\200\205\353\252\250\353\215\270.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\354\235\230\342\200\205thread\342\200\205\353\252\250\353\215\270.md" new file mode 100644 index 00000000..4ebc3603 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/netty\354\235\230\342\200\205thread\342\200\205\353\252\250\353\215\270.md" @@ -0,0 +1,130 @@ +--- +title: 'netty의 thread 모델' +lastUpdated: '2024-03-02' +--- + +이벤트 루프란 이벤트를 실행하기 위한 무한루프 스레드를 말한다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/513a5ad9-3cde-47ec-ad2b-a13b6730e24e) + +위의 그림과 같이 객체에서 발생한 이벤트는 이벤트 큐에 입력되고 이벤트 루프는 이벤트 큐에 입력된 이벤트가 있을 때 해당 이벤트를 꺼내서 이벤트를 실행한다. 이것이 이벤트 루프의 기본 개념이다. 이벤트 루프는 지원하는 스레드 종류에 따라서 단일 스레드 이벤트 루프와 다중 스레드 이벤트 루프로 나뉜다. 이것을 [reactor pattern](../%EB%B9%84%EB%8F%99%EA%B8%B0/reactor/Reactor%20Pattern%EA%B3%BC%E2%80%85event%E2%80%85loop.md)이라 부르기도 한다. + +## single thread event loop + +현재는 다중 코어나 CPU를 장착한 시스템이 일반적이므로 최신 애플리케이션은 시스템 리소스를 효율적으로 활용하기 위해 정교한 멀티스레딩 기법을 이용하는 경우가 많다. 자바 초창기의 멀티스레딩 체계는 동시 작업 단위를 실행하기 위해 필요할 때마다 새로운 스레드를 만들고 시작하는 기초적인 수준이었기 때문에 부하가 심한 상황에서는 성능 저하가 심했다. 다행히 자바 5에는 Thread 캐싱과 재사용을 통해 성능을 크게 개선한 스레드 풀을 지원하는 Executor API가 도입됐다. + +싱글 스레드 이벤트 루프는 말 그대로 이벤트를 처리하는 스레드가 하나인 상태를 이야기 한다. 설명하자면 다음과 같다. + +- 요청된 작업(Runnable의 구현)을 실행하기 위해 풀의 가용 리스트에서 Thread하나를 선택해 할당한다. +- 작업이 완료되면 Thread가 리스트로 반환되고 재사용할 수 있게 된다. + +스레드를 풀링하고 재사용하는 방식은 작업별로 스레드를 생성하고 삭제하는 방식보다 분명히 개선된 것이지만, 컨텍스트 전환 비용이 아예 사라진 것은 아니다. 이 비용은 스레드의 수가 증가하면 명백하게 드러나고 부하가 심한 상황에서는 심각한 문제가 된다. 또한 애플리케이션의 동시성 요건이나 전반적인 복잡성 때문에 프로젝트의 수명주기 동안 다른 스레드 관련 문제가 발생할 수 있다. + +## multi thread event loop + +다중 스레드 이벤트 루프는 이벤트를 처리하는 스레드가 여러개인 모델이다. 단일 스레드 이벤트 루프에 비해서 프레임워크의 구현이 복잡하지만, 이벤트 루프 스레드들이 이벤트 메서드를 병렬로 수행하므로 멀티 코어 CPU를 효율적으로 사용한다. 단점으로는 여러 이벤트 루프 스레드가 이벤트 큐 하나에 접근하므로 여러 스레드가 자원 하나를 공유할 때 발생하는 스레드 경합이 발생할 수 있고, 이벤트들이 병렬로 처리되므로 이벤트의 발생 순서와 실행 순서가 일치하지 않게 된다는 것이 있다. + +Netty에서는 멀티 스레드 이벤트 루프의 단점인 발생 순서와 실행 순서가 일치하지 않는다는 문제를 아래와 같은 방법으로 해결한다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/d8d9948d-7387-4568-a4eb-2c8d5e29ccc2) + +- Netty의 이벤트는 Channel에서 발생한다. +- 각각의 이벤트 루프 객체는 개인의 이벤트 큐를 가지고 있다. +- Netty Channel은 하나의 이벤트 루프에 등록된다. +- 하나의 이벤트 루프 스레드에는 여러 채널이 등록될 수 있다. +- Channel의 라이프 사이클 동안 모든 동작은 하나의 thread에서 처리되게 된다. + +멀티 스레드 이벤트 모델에서 이벤트의 실행 순서가 일치하지 않는 이유는 루프들이 이벤트 큐를 공유하기 때문이다. Netty는 이벤트 루프 스레드마다 개인의 이벤트 큐를 가짐으로써, 해당 이벤트를 처리하는 스레드가 지정하도록 하기 때문에 공유된 하나의 이벤트 큐에 스레드들이 접근하지 않게 된다. + +## EventLoop 인터페이스 + +연결의 수명기간 동안 발생하는 이벤트를 처리하는 작업을 실행하는 것은 네트워킹 프레임워크의 기본 기능이다. 이를 나타내는 프로그래밍 구조를 이벤트 루프(event loop)라고 하는데, 네티에서도 `io.netty.channel.EventLoop` 인터페이스에 이를 적용했다. + +네티의 EventLoop는 동시성과 네트워킹의 두 가지 기본 API를 활용해 설계됐다. + +1. `io.netty.util.concurrent` 패키지는 JDK 패키지인 `java.util.concurrent`에 기반을 두는 스레드 실행자를 제공한다. +2. `io.netty.channel` 패키지의 클래스는 Channel이벤트와 인터페이스를 수행하기 위해 이러한 API를 확장한다. + +이 모델에서 EventLoop는 변경되지 않는 Thread 하나로 움직이며, 작업을 EventLoop구현으로 직접 제출해 즉시 또는 예약 실행할 수 있다. 구성과 사용 가능한 코어에 따라서는 리소스 활용을 최적화하기 위해 여러 EventLoop가 생성되고 여러 Channel에 서비스를 제공하기 위해 단일 EventLoop가 할당되는 경우도 있다. + +네티의 EventLoop는 변경되지 않는 Thread 하나로 움직이며, 작업(Runnable 또는 Callable)을 EventLoop 구현으로 직접 제출해 즉시 또는 예약 실행할 수 있따. 구성과 사용 가능한 코어에 따라서는 리소스 활용을 최적화하기 위해 여러 EventLoop가 생성되고, 여러 Channel에 서비스를 제공하기 위해 단일 EventLoop가 할당되는 경우도 있다. + +네티의 EventLoop는 `ScheduledExecutorService`를 확장하며, `parent()` 라는 메서드 하나만 정의한다. 이 메서드는 다음 코드에 나오는 것처럼 현재 EventLoop구현 인스턴스가 속한 EventLoopGroup의 참조를 반환하기 위한 것이다. + +```java +public interface EventGroup extends EventExecutor, EventLoopGroup { + @Override + EventLoopGroup parent(); +} +``` + +네티4의 모든 입출력 작업과 이벤트는 EventLoop에 할당된 Thread에 의해 처리된다. + +## EventLoop를 이용한 작업 스케줄링 + +`ScheduledExecutorService`구현은 풀 관리 작업의 일부로 스레드가 추가로 생성되는 등의 한계점을 가지고 있으며, 이 때문에 많은 작업을 예약할 경우 병목 현상이 발생할 수 있다. + +**EventLoop를 이용한 작업 예약** + +```java +Channel ch = ...; +ScheduledFuture future = ch.eventLoop().schedule( + new Runnable() { + @Override + public void run() + { + System.out.println("60 seconds later"); + } + }, 60, TimeUnit.SECONDS +); +``` + +**EventLoop를 이용한 반복 작업 예약** + +```java +Channel ch = ... +ScheduledFuture future = ch.eventLoop().scheduleAtFixedRate( + new Runnable() { + @Override + public void run() + { + System.out.println("Run every 60 seconds"); + } + }, 60, 60, TimeUnit.Seconds +); +``` + +**SchedueldFuture를 이용한 작업 취소** + +```java +ScheduledFuture future = ch.eventLoop().scheduleAtFixedRate(...); +boolean mayInterruptIfRunning = false; +future.cancel(mayInterruptIfRunngin); +``` + +# 스레드 관리 + +네티 스레딩 모델이 탁월한 성능을 내는 데는 현재 실행중인 Thread의 ID를 확인하는 기능, 즉 Thread가 현재 Chanenl과 해당 EventLoop에 할당된 것인지 확인하는 기능이 중요한 역할을 한다.(EventLoop는 수명주기 동안 Channel하나의 모든 이벤트를 처리한다.) + +호출 Thread가 EventLoop에 속하는 경우 해당 코드 블록이 실행되며, 그렇지 않으면 EventLoop이 나중에 실행하기 위해 작업을 예약하고 내부 큐에 넣는다. EventLoop는 다음 해당 이벤트를 처리할 떄 큐에 있는 항목을 실행한다. Thread가 ChannelHandler를 도익화 하지 않고도 Chanel과 직접 상호작용할 수 있는 것은 이런 작동 방식 때문이다. + +장기 실행 작업은 실행 큐에 넣지 않아야 하며, 그렇지 않으면 동일한 스레드에서 다른 작업을 실행할 수 없게 된다. 블로킹 호출을 해야하거나 장기 실행 작업을 실행해야 하는 경우 전용 EventExecutor를 사용하느 것이 좋다. + +### EventLoop와 스레드 할당 + +Channel에 이벤트와 입출력을 지원하는 EventLoop는 EventLoopGroup에 포함된다. EventLoop가 생성 및 할당되는 방법은 전송의 구현에 따라 다르다. + +- 비동기 전송 + - 비동기 구현은 적은 수의 EventLoop를 이용하며, 현재 모델에서는 이를 여러 Channel에서 공유할 수 있다. 덕분에 Channel마다 Thread를 할당하지 않고 최소한의 Thread로 다수의 Chanenl을 지원할 수 있다. + - Channel은 EventLoop가 할당되면 할당된 EventLoop를 수명주기 동안 이용한다. 덕분에 ChannelHandler 구현에서 동기화와 스레드 안정성에 대해 걱정할 필요가 없다. + - 또한 ThreadLocal 이용을 위한 EventLoop 할당의 영향도 알아야 한다. 일반적으로 EventLoop하나가 둘 이상의 Channel에 이용되므로 **ThreadLocal은 연결된 모든 Channel에서 동일하다.** 즉, 상태 추적과 같은 기능을 구현하는 데는 적합하지 않지만 상태 비저장 환경에서는 여러 Channel에서 대규모 객체 또는 고비용 객체나 이벤트를 공유하는데 유용할 수 있다. +- 블로킹 전송 + - 각 Channel에 한 EventLoop가 할당된다. 그러나 이전과 마찬가지로, 각 Channel의 입출력 이벤트는 한 Thread에 의해 처리된다. + +--- +참고 +- [네티 인 액션 : Netty를 이용한 자바 기반의 고성능 서버 & 클라이언트 개발](https://m.yes24.com/Goods/Detail/25662949) +- https://netty.io/wiki/thread-model.html +- https://medium.com/@akhaku/netty-data-model-threading-and-gotchas-cab820e4815a +- https://livebook.manning.com/book/netty-in-action/chapter-7/27 +- https://shortstories.gitbook.io/studybook/netty/c774_bca4_d2b8_baa8_b378 \ No newline at end of file diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/webFlux\354\231\200\342\200\205netty.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/webFlux\354\231\200\342\200\205netty.md" new file mode 100644 index 00000000..87809cde --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/netty/webFlux\354\231\200\342\200\205netty.md" @@ -0,0 +1,39 @@ +--- +title: 'webFlux와 netty' +lastUpdated: '2024-03-02' +--- + +Spring MVC는 기본적으로 블럭킹이고 동기방식을 사용한다. 비동기 처리 기능이 스프링 프레임워크 3에서 추가되어 지원된다고 하지만, 서블릿은 응답을 기다리는 동안 pool의 스레드들은 여전히 지연시킬 수 있기 때문에 전체 stack이 reactive 해야 하는 요구를 충족시킬 수 없다. + +이러한 요구사항에 맞추어 스프링 프레임워크5에 도입된 대안적인 모듈이 바로 WebFlux이고, 웹 요청을 reactive 하게 다루는 데에 초점이 맞추어져 있다. + +기존의 서블릿 기반의 Spring Boot는 Tomcat을 기반으로 동작한다. 반면 Spring Boot WebFlux는 여러 가지를 고를 수 있는데, Default로 Netty를 사용한다. Netty를 사용하는 이유는 tomcat은 요청 당 하나의 스레드가 동작하는 반면, netty는 1개의 이벤트를 받는 스레드와 다수의 worker 스레드로 동작하여 보다 효율적으로 데이터를 처리할 수 있기 때문이다. + +![image](https://user-images.githubusercontent.com/81006587/227113309-0bea32bc-6fd4-4733-bef3-e077f6cb8edc.png) + +netty는 channel에서 발생하는 이벤트를 EventLoop가 처리하는 구조로 동작한다. 이벤트 루프란 이벤트를 실행하기 위한 무한루프 스레드를 뜻한다. + +위의 이미지와 같이 객체에서 발생한 이벤트는 이벤트 큐에 push 되고 이벤트 루프는 이벤트 큐에 입력된 이벤트가 있을 때 해당 이벤트를 꺼내서 실행한다. + +이벤트 루프는 지원하는 스레드의 종류에 따라서 단일 스레드와 다중 이벤트 루프로 나누어지게 된다. 또한 이벤트 루프가 처리한 이벤트의 결과를 돌려주는 방식에 따라 [Callback과 Futures 패턴](../%EB%B9%84%EB%8F%99%EA%B8%B0/reactor/Callback%EA%B3%BC%E2%80%85Futures.md)으로 나누어지게 되고, netty는 이 두 가지 패턴을 모두 지원한다. + +## 싱글 스레드 + +싱글 스레드 이벤트 루프는 말 그대로 이벤트를 처리하는 스레드가 하나인 상태를 이야기 한다. 단일 스레드로 동작하기 때문에 예측 가능한 동작을 보장하고, 이벤트 루프의 구현이 단순하다는 장점이 있다. 하지만 하나의 처리시간이 긴 작업이 들어오면 전체 작업 지연시간이 늘어난다는 단점이 있다. 또한 단일 스레드이기 때문에 멀티 코어 cpu환경에서 cpu자원을 효율적으로 사용할 수 없다. + +## 멀티 스레드 + +멀티 스레드 이벤트 루프는 이벤트를 처리하는 스레드가 여러 개인 모델이다. 싱글 스레드 이벤트 루프에 비해 프레임워크 구현이 복잡하지만, 스레드들이 이벤트 메서드를 병렬로 수행하기 때문에 멀티 코어 cpu의 자원을 효율적으로 사용할 수 있다. 하지만 여러 이벤트 스레드가 하나의 이벤트 큐에 접근하기 때문에 동시성 문제가 발생할 수 있다. 또한 이벤트들이 병렬로 처리되기 때문에 이벤트의 발생순서와 실행 순서가 일치하지 않다. + +Netty에서는 멀티 스레드 이벤트 루프의 단점인 발생 순서와 실행 순서가 일치하지 않는다는 문제를 아래와 같은 방법으로 해결한다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/8a3c4208-21a9-42a7-a159-f5433db3d6a2) + +- Netty의 이벤트는 Channel에서 발생한다. +- 각각의 이벤트 루프 객체는 개인의 이벤트 큐를 가지고 있다. +- Netty Channel은 하나의 이벤트 루프에 등록된다. +- 하나의 이벤트 루프 스레드에는 여러 채널이 등록될 수 있다. + +멀티 스레드 이벤트 모델에서 이벤트의 실행 순서가 일치하지 않는 이유는 루프들이 이벤트 큐를 공유하기 때문에 발생하게 된다. 따라서 Netty는 이벤트 큐를 이벤트 루프 스레드의 내부에 둠으로써 실행 순서의 불일치 원인을 제거하게 된다. + +즉 이벤트 루프 스레드마다 개인의 이벤트 큐를 가짐으로써, 해당 이벤트를 처리하는 스레드가 지정되어 있기 때문에 공유된 하나의 이벤트 큐에 스레드들이 접근하지 않게 된다. diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/node.js/module.exports\354\231\200\342\200\205exports.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/node.js/module.exports\354\231\200\342\200\205exports.md" new file mode 100644 index 00000000..64d639ce --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/node.js/module.exports\354\231\200\342\200\205exports.md" @@ -0,0 +1,94 @@ +--- +title: 'module.exports와 exports' +lastUpdated: '2024-03-02' +--- + +모듈이란 관련된 코드들을 하나의 코드 단위로 캡슐화 하는 것을 말한다. + +`greeting.js` 라는 파일이 있다. 이 파일은 두개의 함수를 포함하고 있다. + +```js +// greetings.js +sayHelloInEnglish = function() { + return "Hello"; +}; + +sayHelloInSpanish = function() { + return "Hola"; +}; +``` + +## 모듈 추출하기(exporting) + +`gretting.js`의 코드를 다른 파일에서 사용해보자. + +우선 외부에서 사용할 함수에 아래와 같이 export 설정을 해주어야 한다. + +```js +// greetings.js + +exports.sayHelloInEnglish = function() { + return "HELLO"; +}; +exports.sayHelloInSpanish = function() { + return "Hola"; +}; +``` + +위의 코드에서 `exports`를 `module.exports`로 묶어주는 것으로 사용할 수도 있으며 의미는 같다. + +```js +module.exports = { + sayHelloInEnglish: function() { + return "HELLO"; + }, + + sayHelloInSpanish: function() { + return "Hola"; + } +}; +``` + +## 모듈 사용하기 + +`main.js` 라는 새로운 파일에서 `greeting.js` 의 메소드를 사용 할 수 있도록 import 해보자. + +require 키워드를 사용해 `main.js`에서 `greetings.js`를 require 한다. + +```js +// main.js +var greetings = require("./greetings.js"); +``` + + +이제 `main.js` 에서 `greeting.js` 의 값과 메소드에 접근할 수 있다. + +```js +// main.js +var greetings = require("./greetings.js"); + +// "Hello" +greetings.sayHelloInEnglish(); + +// "Hola" +greetings.sayHelloInSpanish(); +``` + +require 키워드는 `object`를 반환한다. 그리고 `module.exports` 와 `exports` 는 call by reference 로 동일한 객체를 바라보고 있고, 리턴되는 값은 항상 `module.exports` 이다. + +모듈은 기본적으로 객체이고, 이 객체를 `module.exports`, `exports` 모두 바라보고 있는데, 최종적으로 return 되는 것은 무조건 `module.exports` 라는 것이다. + +아래는 `express.Router()`가 리턴한 “객체”에 일부 프로퍼티를 수정한 뒤, 이 객체 자체를 모듈로 return 하고있는 코드이다. + +```js +var express = require('express'); +var router = express.Router(); + +/* GET home page. */ +router.get('/', function(req, res) { + res.render('index', { title: 'Express' }); +}); + +module.exports = router; +``` + diff --git "a/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/node.js/puppeteer.md" "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/node.js/puppeteer.md" new file mode 100644 index 00000000..eadc6920 --- /dev/null +++ "b/src/content/docs/TIL/\352\260\234\353\260\234/\355\224\204\353\240\210\354\236\204\354\233\214\355\201\254/node.js/puppeteer.md" @@ -0,0 +1,138 @@ +--- +title: 'puppeteer' +lastUpdated: '2024-03-02' +--- + +Puppeteer는 Headless Chrome 혹은 Chromium 를 제어하도록 도와주는 라이브러리이다. + +Puppeteer는 Node 6 이상 에서 동작하며, Chrome 혹은 Chromium의 DevTools 프로토콜을 통해 각 Chrome 혹은 Chromium의 API를 제어한다. + +## Headless Browser + +Headless Browser는 일반적으로 사용자가 사용하는 GUI 에서 동작하는 것이 아닌 CLI(Command Line interface) 에서 작동하는 브라우저이다. 백그라운드에서 동작하며, 일반적인 브라우저와 같이 웹페이지에 접속하여 HTML, CSS로 DOM Tree와 CSSOM Tree를 만들고, JS 엔진을 구동한다. + +유일한 차이점은 만든 화면을 사용자에게 보여주지 않는다는 점이다. + +일반 브라우저와 큰 차이가 없기 때문에 보여주는 화면이 없이도, 화면 테스트나 스크린샷을 찍는것 등 다양한 기능 동작이 가능하며, 사용자가 실제 사용하는 환경과 비슷하게 테스트가 가능하다. + +puppeteer 에서는 Chrome 혹은 Chromium의 렌더링 엔진을 사용하여, Headless Browser 환경을 구성하였다. Chrome의 렌더링 엔진이 지원하는 최신 스펙의 HTML, CSS, JS 등 렌더링 엔진이 만들 수 있는 모든 화면을 만들어 낼 수 있다. 또한 여러 ifream 이나 popup 으로 이루어진, 복잡한 화면을 제어하는 것이 가능하며, 최근 ES6로 작성된 SPA 화면들도 렌더링 및 제어가 가능하다. + +## 구조 + +Puppeteer API는 계층적이다. + +- puppeteer는 하나의 Browser 를 갖는다. +- 하나의 Browser는 여러 BrowserContext 를 가질 수 있다. +- 하나의 BrowserContext 여러 Page 를 가질 수 있고, Serviceworker 와 Session 을 가질 수 있다. +- 하나의 Page는 여러 Frame 을가질 수 있다. +- 하나의 Frame은 여러 Context 를 가질 수 있다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/04151a62-c124-4d03-87e6-a5422a01b1ab) + +## BrowserContext + +puppeteer로 Browser가 시작되면 default BrowserContext가 생성된다. 이 default BrowserContext는 Browser의 생명주기와 같다. + +Browser는 여러 개의 BrowserContext를 가질 수 있고 `Browser.createIncognitoBrowserContext()` 으로 시크릿 브라우저 컨텍스트를 만들 수 있다. + +`window.open` 호출로 생성된 팝업은 이전 페이지의 BrowserContext에 속한다. + +`Browser.newPage()`로 생성된 페이지는 default BrowserContext에 포함된다. + +## 예시 + +아래는 example 페이지에 접속하여 스크린 샷을 찍는 예제이다. + +```js +// https://developers.google.com/web/tools/puppeteer/get-started +(async () => { + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + await page.goto('https://example.com'); + await page.screenshot({path: 'example.png'}); + await browser.close(); +})(); +``` + +대부분의 메소드들은 `Promise` 를 반환한다. + +`puppeteer.launch()` 메서드를 통해 browser를 생성할 수 있다. 이때, 옵션을 전달하여 브라우저를 제어할 수 있다. + +- **headless:** 기본값은 true 이고, false로 설정하면, 브라우저가 실제로 실행된다. +- **defaultViewport:** 기본값은 800x600 이고, 화면이 노출될 사이즈를 지정할 수 있다. +- **devtools:** 기본값은 false 이고, true 로 설정하면, 브라우저에 Devtools 가 열린다. + +이 외에도 많은 옵션이 제공된다. + +생성된 browser로 `browser.newPage()` 메서드를 통해 page 를 생성할 수 있고 생성된 page로 실제 페이지를 조작 및 제어 할 수 있다. + +`page.goto` 메서드로 페이지를 이동할 수 있다. + +## pdf + +링크에서 페이지를 읽어와 pdf buffer를 반환하는 예제이다. +```js +const puppeteer = require('puppeteer'); + +(async () => { + + // Create a browser instance + const browser = await puppeteer.launch(); + + // Create a new page + const page = await browser.newPage(); + + // Website URL to export as pdf + const website_url = 'https://host'; + + // Open URL in current page + await page.goto(website_url, { waitUntil: 'networkidle0' }); + + //To reflect CSS used for screens instead of print + await page.emulateMediaType('screen'); + +// Downlaod the PDF + const pdf = await page.pdf({ + path: 'result.pdf', + margin: { top: '100px', right: '50px', bottom: '100px', left: '50px' }, + printBackground: true, + format: 'A4', + }); + + // Close the browser instance + await browser.close(); +})(); +``` + +html로 페이지를 만들어 pdf를 출력하는 예제이다. +```js +const puppeteer = require('puppeteer'); +const fs = require('fs'); + +(async () => { + + // Create a browser instance + const browser = await puppeteer.launch(); + + // Create a new page + const page = await browser.newPage(); + + //Get HTML content from HTML file + const html = fs.readFileSync('sample.html', 'utf-8'); + await page.setContent(html, { waitUntil: 'domcontentloaded' }); + + // To reflect CSS used for screens instead of print + await page.emulateMediaType('screen'); + + // Downlaod the PDF + const pdf = await page.pdf({ + path: 'result.pdf', + margin: { top: '100px', right: '50px', bottom: '100px', left: '50px' }, + printBackground: true, + format: 'A4', + }); + + // Close the browser instance + await browser.close(); +})(); +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/Exponential\342\200\205Backoff\342\200\205And\342\200\205Jitter.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/Exponential\342\200\205Backoff\342\200\205And\342\200\205Jitter.md" new file mode 100644 index 00000000..94c8ddd5 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/Exponential\342\200\205Backoff\342\200\205And\342\200\205Jitter.md" @@ -0,0 +1,79 @@ +--- +title: 'Exponential Backoff And Jitter' +lastUpdated: '2024-03-02' +--- + +### Introducing OCC +Optimistic concurrency control(OCC) is a time-honored way for multiple writers to safely modify a single object without losing writes. OCC has three nice properties: it will always make progress as long as the underlying store is available, it’s easy to understand, and it’s easy to implement. + +while OCC is guaranteed to make progress, it can stil perform quite poorly under high contention. The simplest of this contiention cases is when a whole log of clients start at the same time, and try to update the same database row. With one client guarenteed to succeed every round, the time to complete all the updates grows linearly with contention. + +With N clients contending, the total amount of work done by the system increases with N2. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/cb23c3d2-cc47-4d84-9271-7a817818d349) + +### Adding Backoff + +The problem here is that N clients compete in the first rount, N-1 in the second round, and so on. Having every client compete in every roun dis wateful. Slowing clients down may hel, and the classic way to slow clients down is capped exponential backoff. Capped exponential backoff means that clients multiply their backoff by a constant after each attempt, up to some maximum value. In our case, after each unsuccessful attempt, clients sleep for: + +```c +sleep = min(cap, base * 2 ** attempt) +``` + +Running the simulation again shows that backoff helps a small amount, but doesn't solve the problem. Client work has only been reduced slightly. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/16cd8173-d354-4626-8896-163814323794) + +The best way to see the problem is to look at the times these exponentially backed-off calls happen. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/5235d62a-6fa6-40f6-a826-50a45c1ebf7c) + +The obvious that the exponential backoff is working, in that the calls are happening less and less frequently. The problem also stands out: there are still clusters of calls. Instead of reducing the number of clients competing in every round, we've just introduced times when no client is competing. Contention hasn't been reduced much, although the natural variance in network delay has introduced some spreading. + +### Adding Jitter + +The solution isn't to remove backoff. It's to add jitter. Initially, jitter may apper to be a counter-intuitive idea: trying to improve the performancd of a system by adding randomness. The time series above makes a great case for jitter - we want to spread out the spikes to an approximately constant rate. Adding jitter is a small change to the sleep function. + +```c +sleep = random_between(0, min(cap, base * ** attempt)) +``` + +The time series looks a whole lot better. The gaps are gone, and beyond the initial spike, there's an approximately constant rate of calls. It's also had a great effect on the total number of calls. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/19ae5986-3024-4122-a4a6-a2641fae7368) + +In the case with 100 contending clients, we've reduced our call count by more than half. We've also significantly improved the time to completion, when compared to un-jittered exponential backoff. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/7e64a0ca-c7da-4c63-8d6e-1e0b16496021) + +There are a few ways to implement the timed backoff loops. Let's call the algorithm above "Full Jitter", and consider two alternatives. The first alternative is "Equal Jitter", where we always keep some of the back off and jitter by a smaller amount: + +```c +tmp = min(cap, base * 2 ** attempt) +sleep = temp / 2 + random_between(0, tmp / 2) +``` + +Ter intuition behind this one is that it prevents very short sleeps, always keeping some of the slow down from the backoff. A second alternative is "Decorrelated Jitter", which is similar to "Full Jitter", but we also increase the maximum jitter based on the last random value. + +```c +sleep = min(cap, random_bwtween(base, sleep * 3)) +``` + +Which approach do you think is best? + +Looking at the amount of client work, the number of calls is approximately the same for “Full” and “Equal” jitter, and higher for “Decorrelated”. Both cut down work substantially relative to both the no-jitter approaches. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/5fe5e0a3-d198-4731-9db9-7943f50a7d34) + +The no-jitter exponential backoff approach is the clear loser. It not only takes more work, but also takes more time than the jittered approaches. In fact, it takes so much more time we have to leave it off the graph to get a good comparison of the other methods. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/4d5cd304-2511-4997-b997-eed15f8f6243) + + +Of the jittered approaches, “Equal Jitter” is the loser. It does slightly more work than “Full Jitter”, and takes much longer. The decision between “Decorrelated Jitter” and “Full Jitter” is less clear. The “Full Jitter” approach uses less work, but slightly more time. Both approaches, though, present a substantial decrease in client work and server load. + +It’s worth noting that none of these approaches fundamentally change the N2 nature of the work to be done, but do substantially reduce work at reasonable levels of contention. The return on implementation complexity of using jittered backoff is huge, and it should be considered a standard approach for remote clients. + +--- +reference +- https://github.com/aws-samples/aws-arch-backoff-simulator/blob/master/src/backoff_simulator.py diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/CRC.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/CRC.md" new file mode 100644 index 00000000..76a4c29b --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/CRC.md" @@ -0,0 +1,39 @@ +--- +title: 'CRC' +lastUpdated: '2024-03-02' +--- + +- CRC는 네트워크 등을 통하여 데이터를 전송할 때 전송된 데이터에 오류가 있는지를 확인하기 위한 체크값을 결정하는 방식을 말한다. + +- 송신측에서는 CRC값을 데이터에 붙인 코드워드를 전송하며, 수신측에서는 수신된 코드워드에서 CRC값을 이용하여 에러를 발견한다. +- 오류 제어를 위한 후진 오류 수정(BEC, Backward Error Correction) 방식 중 오류 검출 방식이다. +- CRC는 이진법 기반의 하드웨어에서 구현하기 쉽고, 데이터 전송 과정에서 발생하는 오류들을 검출하는 데 탁월하다. +- 하지만 CRC의 구조 때문에 의도적으로 주어진 CRC 값을 갖는 다른 데이터를 만들기가 쉽고, 따라서 데이터 무결성을 검사하는 데는 사용될 수 없다. 이런 용도로는 MD5 등의 함수들이 사용된다. + +### 코드워드 계산 과정 + +- 주어진 데이터 + - 데이터워드: 01101011 + - Divisor: 100000111 // CRC-8 = x8 + x2 + x + 1 + +- 계산 과정 + + image + +- 전송 데이터 + - 코드워드 = 01101011 + 00010110 + - 전송데이터 = 0110101100010110 + +### 에러 검증 과정 + +- 주어진 데이터 + - 수신된 데이터 = 0110101100010110 + - Divisor: 100000111 // CRC-8 = x8 + x2 + x + 1 + +- 검증 과정 + + image + +- 검증 결과 + - Divisor 나누기 값이 0이므로 정상 데이터 수신 + - 수신된 데이터의 데이터워드(01101011)를 추출하여 목적에 맞게 사용 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/Ethernet\352\263\274\342\200\205TokenRing.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/Ethernet\352\263\274\342\200\205TokenRing.md" new file mode 100644 index 00000000..9eabcd0c --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/Ethernet\352\263\274\342\200\205TokenRing.md" @@ -0,0 +1,22 @@ +--- +title: 'Ethernet과 TokenRing' +lastUpdated: '2023-09-20' +--- + +# 📡 Ethernet +

이더넷(Ethernet)이란 CSMA/CD라는 프로토콜을 사용해 네트워크에 연결된 각 기기들이 하나의 전송 매체를 통해 인터넷 망에 접속해 통신할 수 있도록 하는 네트워크 방식이다.

+

CSMA/CD란 Carrier Sense Multiple Access / Collision Detection의 약자로, 다중 접속(콜리전)이 생겼을때 그것을 감지해 대기 후 재전송하는 것을 뜻한다. 즉, 네트워크 상의 데이터 신호들을 감지하다 두 개 이상의 PC가 서로 데이터를 보내다가 충돌이 발생하면 임의의 시간동안 기다린 후 다시 데이터를 보낸다. 따라서 CSMA/CD라는 프로토콜을 사용하는 이더넷이라는 네트워킹 방식은 네트워크상에 하나의 데이터만 오고 갈 수 있다는 특징이 있다.

+

과거에 쓰이던 Token ring을 대체하여, 현재는 LAN, MAN 및 WAN에서 가장 많이 쓰이는 방식이다.

+
+ + + +
+ +# 📡 Token ring +

Token ring 이란 네트워크 안에서 링 속에서 토큰을 가진 하나의 장치만이 네트워크에 데이터를 실어 보낼 수 있는 방식이다. 토큰 링에는 여러 컴퓨터가 연결되어있고, 토큰을 가지고 있는 컴퓨터가 데이터를 다 보냈거나 전송할 데이터가 없을 경우 옆 PC에게 토큰을 전달하며 전송매체 하나로 각 기기들이 모두 인터넷 망에 접속해있는 것과 같은 구조를 구현한다.

+

토큰을 가지고 있는 컴퓨터만 요청할 수 있기 때문에 콜리전이 발생하지 않지만, 요청할 일이 생겨도 토큰을 넘겨받을 때는 까지 무조건 대기해야 하기 때문에 지연이 생긴다는 단점이 있다.

+ +
+ + diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/L2\342\200\205\354\212\244\354\234\204\354\271\230\354\231\200\342\200\205STP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/L2\342\200\205\354\212\244\354\234\204\354\271\230\354\231\200\342\200\205STP.md" new file mode 100644 index 00000000..31cd50e5 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/L2\342\200\205\354\212\244\354\234\204\354\271\230\354\231\200\342\200\205STP.md" @@ -0,0 +1,210 @@ +--- +title: 'L2 스위치와 STP' +lastUpdated: '2024-03-02' +--- + +--- + +## 개요 + +image + + +- **스위치**: 같은 네트워크 내에서 통신을 중재하는 L2 장비 +- 이더넷을 구성하기 위해 사용되는 경우 이더넷 스위치라고도 불린다 +- 패킷 경합을 없애서 여러 단말이 동시에 효율적으로 통신할 수 있게 함 +- 구조가 간단하고 가격이 저렴함. 신뢰성과 성능이 높음. + +## **MAC 주소 테이블** + +--- + +image + + +- 여러 NIC와 포트로 연결됨 +- 각 NIC의 MAC을 기억해놓고, 어느 호스트가 특정 MAC 주소와 연결하고 싶다 요청하면 연결해줌 +- 허브는 한 포트로 신호가 들어오면 같은 신호를 다른 모든 포트로 전달함 + - 반면 스위치는 플러딩할 때만 모든 포트로 전달하고, 나머지의 경우에는 정보를 **MAC 주소 Table**에 저장해서 필요한 포트에만 전달하기 때문에 속도가 더 빠름 + +## 동작 과정 + +--- + +1. **Address learning:**  +이더넷 프레임이 수신되면, source MAC 주소를 읽어서 수신 port 번호와 함께 MAC Table에 기록한다. +2. **Flooding:** +Destination MAC address가 MAC Table에 등록되어 있지 않은 Unicast 프레임(Unknown Unicast)이거나, ARP Request와 같은 브로드캐스트인 경우, 수신 port를 제외한 다른 모든 port로 프레임을 전송한다. + + 허브에서 데이터를 전송하는 것도 Flooding이라 부른다. + +3. **Filtering:** +Destination MAC address가 MAC Table에 등록되어 있고, 등록되어 있는 port 번호가 프레임이 수신된 port 번호와 동일한 경우 해당 프레임이 포워딩 당하지 않도록 차단한다. +4. **Forwarding:** +Destination MAC address가 MAC Table에 등록되어 있고, 등록되어 있는 port 번호가 프레임이 수신된 port 번호와 동일하지 않은 Unicast인 경우 등록되어 있는 port로 프레임을 전송한다. +5. **Aging:** +MAC Table에 Entry가 등록될때 Timer도 같이 start 되며, 해당 Entry의 MAC address를 source MAC으로 하는 프레임이 수신되면 Timer가 reset 되어 다시 시작된다. + + Timer가 경과되면 해당 Entry는 MAC Table에서 삭제된다. TTL 같은 개념이다 + +# VLAN + +--- + +## 개요 + +- **VLAN**: 하나의 물리 스위치에서 여러개의 가상 네트워크를 만드는 것 +- 옛날에는 스위치가 고가였기 때문에, 한 스위치를 분할해 사용하고자 하는 목적으로 생긴 개념이다. +- 물리적으로 스위치를 분리할 때 보다 장비를 효율적으로 사용 +- 물리적 구성과 상관 없이 네트워크를 분리할 수 있다. +- 한 대의 스위치에 연결되더라도 VLAN으로 분리된 단말 간에는 3계층 통신이 필요하다. + +image + + +## 종류 + +--- + +- **포트 기반 VLAN** + - 어떤 단말이 접속하든지 스위치의 포트를 기준으로 VLAN을 할당한다. + - 일반적으로 언급하는 대부분의 VLAN은 이 포트기반 VLAN을 뜻한다. +- **주소 기반 VLAN** + - 스위치의 고정 포트가 아닌, 단말의 MAC 주소를 기준으로 VLAN을 할당한다. + - 단말이 연결되면 단말의 MAC 주소를 인식한 스위치가 해당 포트를 지정된 VLAN으로 변경한다. + - 단말에 따라 VLAN 정보가 바뀔 수 있어 다이나믹 VLAN이라고도 부른다. + +## 스위치 간 연결 + +--- + +- 태그 기능이 없는 VLAN 네트워크에서 여러 스위치를 연결하려면 VLAN의 갯수 만큼 포트를 사용해야한다. + + image + + +- VLAN이 더 많아지면 위와 같은 구조는 비효율적이다. +- 그렇기 때문에, 여러 스위치를 연결하기 위해선 Tagged Port를 사용한다. + - 태그 포트는 통신할 때 이더넷 프레임에 VLAN 정보를 같이 담아 보낸다. + - 태그 포트로 트래픽이 들어오면 태그를 벗겨내면서 해당 VLAN으로 패킷을 전송한다. + - 이 경우 일반적인 포트는 Untagged Port, 또는 Access Port라고 부른다. + + image + + + +## Loop + +--- + +- IT 환경에서는 SPoF(Single Point of Failure: 단일 장애점)로 인한 장애를 피하기 위해 노력한다. +- 만약 네트워크를 스위치 하나로 구성하면, 그 스위치에 장애가 발생했을 때 전체 네트워크에 장애가 발생한다. 그러나 이런 SPoF를 피하기 위하기 위해 스위치 두 대 이상으로 네트워크를 구성하면 패킷이 루프되어 네트워크를 마비시킬 수 있다. +- 루프의 원인은 크게 두가지가 있다. + - **브로드캐스트 스톰 (Broadcast Storm)** + - 루프 구조로 연결된 상태에서 브로드캐스트를 발생시키면 스위치는 이 패킷을 모든 포트로 플러딩한다. 플러딩된 패킷은 다른 스위치로도 보내지고 이 패킷을 받은 스위치는 패킷이 유입된 포트를 제외한 모든 포트로 다시 플러딩한다. + - 이렇게 계속해서 브로드캐스트가 반복되는 것을 브로드캐스트 스톰이라 한다. + + image + + + - ****스위치 MAC 러닝 중복 (Port Flapping)**** + - 스위치는 출발지 MAC 주소를 학습하는데, 직접 전달되는 패킷과 스위치를 돌아 들어간 패킷 간의 포트가 다르면 MAC 주소를 정상적으로 학습할 수 없다. + - MAC 주소 테이블에서는 하나의 MAC 주소에 대해 하나의 포트만 학습할 수 있으므로 동일한 MAC 주소가 여러 포트에서 학습되면 MAC 테이블이 반복 갱신되어 문제가 생긴다. + - 브로드캐스트 스톰과 달리 동적 라우팅이나 잘못된 설정 등 통제 가능한 원인으로 인해 생긴다. ([참고](https://www.manageengine.com/network-monitoring/tech-topics/route-flapping.html#common)) + + image + + + +## STP(Spanning Tree Protocol) + +--- + +STP는 스위치가 연결된 구조를 학습하고, 통신할 수 있는 최소 경로의 포트만 남기고 나머지를 block하여 루프를 없앤다. 이름에서 알 수 있듯 스패닝 트리 알고리즘을 활용한다. + +**참고를 위한 스패닝 트리 사진** +image + + + + +### **BPDU(Bridge Protocol Data Unit)** + +- 스패닝 트리 프로토콜을 이용해 루프를 예방하려면 전체 스위치가 어떻게 연결되는지 알아야 한다. 이를 위해 BPDU라는 프로토콜이 사용된다. +- 2초마다 한번씩 정보를 보내 확인한다. + - 만약 한 port에 문제가 생겨도, 트리를 다시 파악하여 재구성한다. +- Configuration BPDU에는 `Bridge ID`, `Root Bridge ID`, `Port ID`, 경로, Timer 등의 정보가 있다. + +### **STP 동작과정** + +- 모든 스위치는 처음에 자신을 루트로 인식해 BPDU를 통해 2초마다 자신이 루트임을 광고한다 +1. 루트를 선정한다. + - 브릿지 ID가 더 적은 스위치가 있으면 그 스위치를 루트 스위치로 인식한다. +2. 루트가 아닌 스위치는 Root에서 온 BPDU를 받은 포트를 `Root port`로 선정한다. + - Root port는 루트 브릿지로 가는 경로가 가장 짧은 포트이다. +3. 스위치와 스위치가 연결되는 포드는 하나의 `Designated port`를 선정한다. + - Root Bridge의 BPDU를 다른 스위치들에게 전달하기 위해 지정된 포트이다. + - Root-Bridge는 모든 포트가 DP로 설정이 되어 Forward 상태가 된다. + - Root port도 아니고 Designated port도 아닌 포트는 대체(Alternate) 포트로 지정되고, Blocking 상태가 된다. + +최종적으로 아래와 같은 형태가 되어서, 스위치 사이에 loop가 생기지 않게 된다. + +image + + +### **스위치 포트 상태** + +스패닝 트리를 구성할 때 각 스위치의 상태는 아래와 같이 바뀐다. + +- **Blocking** (20초) + - 패킷 데이터를 차단한 상태로 상대방이 보내는 BPDU를 기다린다. + - Max Age 기간 동안 상대방 스위치에서 BPDU를 받지 못했거나 후순위 BPDU를 받았을 떄 포트는 리스닝 상태로 변경된다. +- **Listening** (15초) + - 해당 포트가 전송 상태로 변경되는 것을 결정하고 준비하는 단계이다. 이 상태부터 자신의 BPDU 정보를 상대방에게 전송하기 시작한다. +- **Learning** (15초) + - 러닝 상태는 해당 포트를 포워딩하기로 결정하고 실제로 패킷 포워딩이 일어날 때 스위치가 곧바로 동작하도록 MAC 주소를 러닝하는 단계이다. +- **Forwarding** + - 정상적인 통신이 가능해 패킷을 포워딩하는 단계이다. + +스패닝 트리 프로토콜은 loop를 예방하기 위해 BPDU가 전달되는 시간을 고려하여 각 상태에서 충분히 대기한다. 그렇기 때문에 스위치에 신규로 장비를 붙이면 (해당 장비가 스위치가 아니더라도 검증을 위해) BPDU를 일정 시간 이상 기다린다. + +## 향상된 STP + +--- + +### RSTP (Rapid Spanning Tree Protocol) + +- STP에서 백업 경로를 활성화하는데 시간이 너무 오래 걸리는 문제를 해결하기 위해 개발된 프로토콜 +- STP는 토폴로지가 변경되면 말단에서 루트까지 보고하는 과정을 거쳤기에 느렸다. 하지만 RSTP에선 토폴로지 변경이 일어난 스위치 자신이 모든 네트워크에 토폴로지 변경을 직접 전파할 수 있다. (RSTP는 2~3초면 모든 변경을 전파할 수 있다) + + image + + +- 기본적인 구성과 동작 방식은 STP와 같지만 BPDU 메시지 형식을 더 다양하게 사용한다 + + +### MST (Multiple Spanning Tree) + +- STP, RSTP에서는 VLAN의 개수와 상관 없이 트리는 1개를 사용했다. (CST, Common Spanning Tree) + - VLAN이 여러개인 경우, 해당 방식에선 루프가 생기는 토폴로지에서 한 개의 포트와 회선만 쓰니 더 비효율적이다. 또한 VLAN마다 최적의 경로가 다를 수 있는데 포트를 사용할 수 없어 멀리 돌아야하는 경우가 생긴다. +- 이 문제를 해결하기 위해 PVST(Per Vlan Spanning Tree)가 개발되었고, VLAN마다 별도의 Block port를 지정해 네트워크 로드를 셰어링할 수 있게 되었다. + - 그러나 PVST는 모든 VLAN마다 별도의 스패닝 트리를 유지해야하므로 더 많은 부담이 되었고, 이런 단점을 보완하기 위해 MST가 개발됐다. +- MST는 **여러개의 VLAN**을 **리전**이라는 단위로 묶어 사용한다. + - CST 보다는 효율적이고, PVST 보다는 오버헤드가 적다. + +--- + +### 여담 + +> **스위치의 IP주소** +> 스위치는 2계층 장비여서 MAC 주소만 이해할 수 있다. 스위치 동작에 IP는 필요 없지만 일정 규모 이상의 네트워크에서 쓰이는 스위치는 대부분 관리목적으로 IP 주소가 할당된다. +> 스위치 구조는 크게 관리용 Control Plan과 패킷을 포워딩하는 Data plane으로 나뉘는데, IP는 컨트롤 플레인에 할당되게 된다. + +--- + +**참고** + +- https://pgono.tistory.com/57 +- [https://net-study.club/entry/스패닝-트리-프로토콜STP-Spanning-Tree-Protocol](https://net-study.club/entry/%EC%8A%A4%ED%8C%A8%EB%8B%9D-%ED%8A%B8%EB%A6%AC-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9CSTP-Spanning-Tree-Protocol) +- [**https://en.wikipedia.org/wiki/Spanning_Tree_Protocol**](https://en.wikipedia.org/wiki/Spanning_Tree_Protocol) +- [**https://www.youtube.com/watch?v=Iw12n7vfUlg**](https://www.youtube.com/watch?v=Iw12n7vfUlg) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/MTU.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/MTU.md" new file mode 100644 index 00000000..c9b42ce7 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/MTU.md" @@ -0,0 +1,49 @@ +--- +title: 'MTU' +lastUpdated: '2024-03-02' +--- + +- 데이터링크에서 하나의 프레임 또는 패킷에 담아 운반 가능한 최대 크기 +- MTU란 TCP/IP 네트워크 등과 같은 패킷 또는 프레임 기반의 네트워크에서 전송될 수 있는 최대 크기의 패킷 또는 프레임을 말한다. +- 한 번에 전송할 수 있는 최대 전송량(Byte)인 MTU 값은 매체에 따라 달라진다. +- Ethernet 환경이라면 MTU 기본값은 1500, FDDI 인 경우 4000, X.25는 576, Gigabit MTU는 9000 정도 등 매체 특성에 따라 한 번에 전송량이 결정된다. +- 상위 계층(즉, 물리적, 데이터링크, 네트워크, 인터넷, 애플리케이션 중 네트워크 계층 이상 계층을 말함)의 데이터(헤더 포함된 전체 사이즈)의 수용 가능한 최대 크기로도 생각할 수 있다. +- 따라서, 상위 계층 프로토콜(네트워크 계층 이상)은 하위 계층인 데이터링크에서의 MTU에서 맞추어야 합니다. 그래서 IP 단편화 등을 시행할 수밖에 없다. +- 기본적인 MTU 1500을 초과하는 것은 "Jumbo Frame"이라고 불린다. +- Offical Maxtimun MTU 값은 65535이다. + +## 2계층 (Data-Link Layer) 네트워크에서 종류별 MTU 권고값 + +- DIX Ethernet : 1500 bytes +- 802.3 Ethernet : 1492 byte + +## 3계층 (IP Layer)에서 MTU 권고값 + +- IPv4에서 MTU 최소 권고값은 576byte이다. (RFC 791에서 IP 패킷 구조상으로 볼 때는 68~65,535바이트 범위로써 가능하나, 수신 처리 가능한 MTU 최소값은 576바이트로 권고) +- IPv6에서 MTU 최소 권고값은 1280 bytes이다. + +## MTU값 계산 + +- MTU는 Ethernet프레임을 제외한 IP datagram의 최대 크기를 의미한다. +- 즉, MTU가 1500이라고 할 때 IP Header의 크기 20byte 와 TCP Header의 크기 20byte를 제외하면 실제 사용자 data는 최대 1460까지 하나의 패킷으로 전송될 수 있다. +- Window 계열에서는 PC의 기본 MTU가 1500으로 설정되어 있으며 레지스터리에 특정 값을 적어주지 않으면 자신의 MTU값을 1500으로 설정한다. +그러나 Win2000부터 Media의 특성을 인식하여 dynamic하게 MTU를 설정하게 된다. + +## 운영체제별 MTU 확인 방법 + +- 윈도우 : `netsh interface ip show interface` +- Linux : `ifconfig` + +## MSS(Maximum Segment Size) + +- MSS는 Maximum Segment size의 약어로 TCP상에서의 전송할 수 있는 사용자 데이터의 최대 크기이다. +- MSS값은 기본적으로 설정된 MTU 값에 의해 결정된다. +- 예를 들어 Ethernet일 경우 MTU 1500에 IP 헤더크기 20byte TCP 헤더 크기 20byte를 제외한 1460이 MSS 값이다. + +```bash +MSS = MTU – IP Header의 크기(최소 20byte) – TCP Header의 크기(최소 20byte) +``` + +--- +참고 +- http://ktword.co.kr/test/view/view.php?nav=2&opt=&m_temp1=638&id=484 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/NIC.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/NIC.md" new file mode 100644 index 00000000..1d0368e7 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/NIC.md" @@ -0,0 +1,41 @@ +--- +title: 'NIC' +lastUpdated: '2024-03-02' +--- + +- NIC(Network Interface Card, Controller)는 컴퓨터를 네트워크에 연결하기 위한 하드웨어 장치이다. + +image + +- NIC는 이더넷 또는 Wi-Fi와 같은 특정 물리 계층 및 데이터 링크 계층 표준을 사용하여 통신하는데 필요한 전자 회로를 구현한다. 동일한 LAN(local area network) 상의 컴퓨터 간의 통신 및 IP(Internet Protocol)와 같은 라우팅 가능한 프로토콜을 통한 대규모 네트워크 통신을 가능하게 한다. 또, 케이블을 사용하거나 무선으로 컴퓨터 네트워크를 통해 컴퓨터가 통신할 수 있게 해준다. + +- L2 장비로서 네트워킹 매체에 대한 물리적 액세스를 제공하고, 고유하게 할당된 MAC 주소를 사용하여 하위 수준의 주소 지정 시스템을 제공한다. + +## 역할 + +- **직렬화(Serialization)** + - NIC는 외부 케이블에서 전송되는 전기적 신호를 데이터 신호 형태로, 또는 데이터 신호 형태를 전기적 신호 형태로 변환해준다. + +- **MAC 주소** + - NIC는 MAC 주소를 가지고 있다. 받은 패킷의 도착지 주소가 자신의 MAC 주소가 아니면 폐기하고 자신의 주소가 맞으면 시스템 내부에서 처리할 수 있도록 전달한다. + +- **흐름 제어(Flow Control)** + - 패킷 기반 네트워크에서는 다양한 통신이 하나의 채널을 이용하므로 이미 통신 중인 데이터 처리 때문에 새로운 데이터를 받지 못할 수 있다. 이런 현상으로 인한 데이터 유실 방지를 위해 데이터를 받지 못할 때는 상대방에서 통신 중지를 요청하는 등, 흐름 제어를 수행한다. + +## 다양한 NIC + +### 고대역폭, 이더넷 스위치 기능을 내장한 NIC + +일반 PC에서는 1GbE NIC가 많이 사용되지만, 서버나 네트워크 장비는 높은 신뢰도와 대역폭을 위해 광케이블을 이용한 인터페이스 카드를 사용하고 있다. + +NIC가 점점 진화하면서 단순히 높은 대역폭을 제공할 뿐만 아니라 높은 대역폭 처리로 인해 CPU에 부하가 걸리지 않도록 패킷 생성, 전송을 CPU 도움 없이 독자적으로 처리하기도 한다. 일반적으로 10G 이상의 NCI는 다양한 패킷 생성, 수신을 자신이 혼자 처리한다. + +일부 NIC는 L3 스위치 기능이 내장되어 있어 가상화 서버들끼리 연결하는 vSwich를 가송하는 기능도 함께 제공한다. 이런 다양한 기능이 제공되고 높은 대역폭을 처리하는 NIC는 서버 가상화와 네트워크 장비를 가상화하는 NFV(Network Function Virtualization)에 사용된다. + +### Multifunction NIC + +네트워크 전송 기술로 이더넷만 사용되는 것은 아니다. 스토리지와 서버를 연결하는 Storage Area Network(SAN) 구성용 Fibre Channel 표준이 있고 이더넷에서 스토리지 네트워크를 구성하기 위한 iSCSI 프로토콜도 있다. 또한 슈퍼 컴퓨터와 같이 여러 대의 서버를 묶어 고성능 클러스터링을 구현할 수 있는 HPC 네트워크에는 인피니밴드 기술도 사용된다. + +--- +참고 +- https://en.wikipedia.org/wiki/Network_interface_controller \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\353\215\260\354\235\264\355\204\260\353\247\201\355\201\254\342\200\205\354\240\234\354\226\264(DLC).md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\353\215\260\354\235\264\355\204\260\353\247\201\355\201\254\342\200\205\354\240\234\354\226\264(DLC).md" new file mode 100644 index 00000000..16c80940 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\353\215\260\354\235\264\355\204\260\353\247\201\355\201\254\342\200\205\354\240\234\354\226\264(DLC).md" @@ -0,0 +1,55 @@ +--- +title: '데이터링크 제어(DLC)' +lastUpdated: '2024-03-02' +--- + +데이터 링크 제어(DLC, DataLink Control)는 두 인접한 노드간의 통신의 프레임 짜기, 오류검출 작업 절차를 포함하는 데이터링크층의 부계층이다. + +## 프레임 짜기 + +데이터 링크 층은 비트들을 프레임으로 만들어서 각 프레임이 다른 프레임과 구분되도록 해야한다. 데이터링크층의 프레임 짜기(framing)는 송신자와 수진자의 주소를 추가하여 한 방신지에서 한 목적지로 가는 메시지를 구분한다. 목적지 주소를 패킷이 가야 할 곳을 규정한다. 송신자 주소는 수신자가 수신에 대해 확인응답하는 데 필요하다. + +### 프레임 크기 + +프레임은 고정 길이나 가변 길이가 될 수 있다. 고정 길이 프레임 짜기(fixed-size-framing), 그렇지 않고 크기가 때에따라 바뀌는 방식을 가변 길이 프레임 짜기(variable-size-framing)라고 한다. 고정 길이는 크기 자체가 경계 역할을 하여 경계가 필요없지만 가변에서는 구분해줄 방법이 필요하다. + +경계를 구분하는 방법으로는 문자 중심, 비트 중심 두 방법이 있다. + +- 문자 중심 framing + - 데이터를 문자로 다룬다. + - 8비트의 flag를 프레임의 시작과 끝에 추가한다. + - 텍스트만 교환할 때에는 인기있었지만 요즘은 그래프, 오디오, 비디오 등 다양한 데이터가 있다 보니 플래그로 사용하는 패턴이 정보에 들어있어 오인하는 경우가 발생하기도 한다. + - 이를 방지하기 위해 바이트 채우기(byte stuffing) 또는 escape character를 사용한다. + > 바이트 채우기는 텍스트에 플래그나 탈출 문자가 있을 때 마다 여분의 1바이트를 추가하는 처리이다. +- 비트 중심 프로토콜 + - 데이터를 bit로 다룬다. + - 문자 중심과 동일하게 8비트의 flag를 프레임의 시작과 끝에 추가한다. (보통은 01111110을 플래그로 사용한다.) + - 오인 문제를 해결하기 위해 비트 채우기를 사용한다. + > 비트채우기는 0 뒤에 연속되는 5개의 1이 있게 되면 0을 추가로 채워 수신자가 데이터 속의 01111110을 플래그로 오인하지 않도록 한다. + +## 오류제어 + +- 간섭: 예측할 수 없는 변경 +- 단일 비트 오류: 주어진 데이터 단위 중 하나의 비트만이 역으로 변경되는 오류 +- 폭주오류: 데이터에서 2개 이상의 연속적인 비트들이 역으로 바뀌는 오류 + +### 오류 검출 + +- **블록 부호화** + - 메시지를 dataword라는 k 비트의 블록으로 나누고, 각 블록에 r개의 중복 비트를 더한 결과를 codeword로 정의 + - 블록 부호화로 오류를 찾아내는 법 + - 다음 두 조건이 맞으면 수신자는 원래의 코드워드가 바뀐 것을 알 수 있다. + 1. 수신자는 유효 코드워드의 목록을 가지고 찾을 수 있다. + 2. 원래의 코드워드가 무효 코드워드로 바뀌었다. + +- **패리티 검사** + - 특정 비트가 홀수개인지 짝수개인지 나타내는 추가 비트를 넣어서 오류 검출 + - 단순 패리티 검사 코드는 홀수 개의 오류만 검출한다. (오류가 짝수개면 맞는 것으로 간주) + +- **순환 중복 검사(Cyclic Redundancy Check)** + - LAN이나 WAN에서 널리 사용된다. + - 미리 나눌 수를 정하고 나머지를 정보로 코드워드 구성 + +--- +참고 +- [데이터통신과 네트워킹 TCP/IP 프로토콜 기반](https://product.kyobobook.co.kr/detail/S000001693780) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\353\247\244\354\262\264\342\200\205\354\240\221\352\267\274\342\200\205\354\240\234\354\226\264(MAC).md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\353\247\244\354\262\264\342\200\205\354\240\221\352\267\274\342\200\205\354\240\234\354\226\264(MAC).md" new file mode 100644 index 00000000..0dc7f5e1 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\353\247\244\354\262\264\342\200\205\354\240\221\352\267\274\342\200\205\354\240\234\354\226\264(MAC).md" @@ -0,0 +1,91 @@ +--- +title: '매체 접근 제어(MAC)' +lastUpdated: '2024-03-02' +--- + +- 공유 링크를 사용할 떄 링크에 접근하는 것을 조율하기 위한 데이터링크 계층의 다중 접근 프로토콜 +- 동시에 각 노드가 링크에 대한 접근을 확보했다는 것을 확정하고, 충돌을 방지한다 +- 크게 임의접근 방식, 제어 접근 방식, 채널화 방식으로 총 세가지가 있다. + +## 임의 접근 방식 + +다수 노드가 공유 매체에서 프레임을 전송하기 위해 스케줄링 및 전송 순서 없이 충돌 감지 및 회피, 충돌 발생 시 재전송 하는 방식이다. (일단 보내고 생각) + +각 노드는 다른 노드의 간섭을 받지 않고 공유 매체에 접근할 권한을 보유한다. + +- **ALOHA** + + image + + - 타 노드와 무관하게 프레임 전송, 충돌 허용 + - 프레임 충돌 시 전송 실패 프레임 재전송 + - 취약시간: 충돌이 발생할 수 있는 시간 + - pure ALOHA: 언제든지 전송 + - slotted ALOHA: 타임슬롯 시작시 전송 + +- **CSMA** + - 먼저 사용중인지 아닌지 보고 전송 + - 지속방식: 채널이 사용중일 때 어떻게 해야할까? + - 1 지속 방식: 사용 안하는거 감지하면 즉시 재전송 + - 비지속 방식: 사용 안하는거 감지하면 즉시 재전송 + 사용중이면 기다렸다가 다시 감지 + - p 지속 방식: 사용 안하는거 감지하면 p 확률로 재전송 + - **CSMA/CD** + + image + + - 프레임 전송 전 공유 매체 사용 여부 체크 + - 프레임 전송 후 충돌 발생 여부 체크 + - 전송이 끝나기 전에 감지해서 충돌 발생 사실을 더 빠르게 알릴 수 있음 + - 이더넷, LAN에서 많이 사용 + - ALOHA와 차이점 + - 지속과정 + - 프레임 전송(다 보내고 아는 것이 아님, 보내면서 충돌인지 계속 확인) + - 충돌 신호 + + - **CSMA/CA** + + image + + - IEEE802.11 표준, 무선 LAN에서 사용 → 충돌 감지 어려워 검출이 아니라 회피해야함 + - 프레임 전송 전 공유 매체 사용 여부 체크 + - IFS 이후 매체가 idle 인 경우 프레임 전송 + - 매체가 사용중인 경우 Backoff time 대기 + +## 제어 접근 방식 + +- **폴링** + - 지국 중 하나가 주국(primary station)이 되고 다른 지국들은 종국(secondary station)이 되는 접속 형태에서 동작 + - 종국으로 가는 데이터도 모두 주국을 통해서 전달 + - 주국이 링크를 제어하며, 종국은 그 지시에 따름 + - 폴: NCK(난 몰름), ACK(나 데이터 있음) + - 주국이 종국으로부터 전송을 요청하는데 사용 + - 선택: SEL + - 주국이 송신할 것이 있을 때 사용 + - 예정된 전송을 위해 주국은 종국의 준비 상태에 대한 확인 응답을 대기 + - 주국은 장치의 주소를 한 필드에 포함하고 선택 프레임(SEL)을 만들어 전송 + + image + +- **토큰 전달** + - 토큰을 가진 지국이 데이터 송신할 권한을 가짐 + - 토큰 전달 접근 방법에서의 논리적 링과 물리적 형상 + +## 채널화 방식 + +링크의 가용 대역폭을 지국들 사이에서 시간적으로, 주파수상으로, 또는 코딩을 통해 나누어서 다중 접근할 수 있도록 채널화한다. + +- **주파수 분할 다중 접근(FDMA, frequency division multiple access)** + - 사용 사능한 대역폭은 모든 지국들에 의해 공유 + - 각 지국들은 할당된 대역을 사용하여 데이터를 전송 + - 각각의 대역은 특정 지국을 위해 예약되어 있음 +- **시간 분할 다중 접근(TDMA, time-division multiple access)** + - 지국들이 시간상에서 채널을 공유 + - 각 지국은 자신이 데이터를 전송할 수 있는 타임 슬롯을 할당 받음 + - 각 지국은 할당받은 타임 슬롯에 자신의 데이터를 전송 +- **코드 분할 다중 접근(CDMA, code-division multiple access)** + - 링크이 전체 대역폭을 하나의 채널에서 점유 + - 모든 지국들은 시분할 없이 동시에 데이터를 송신 가능 + +--- +참고 +- [데이터통신과 네트워킹 TCP/IP 프로토콜 기반](https://product.kyobobook.co.kr/detail/S000001693780) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\354\225\204\353\202\240\353\241\234\352\267\270,\342\200\205\353\224\224\354\247\200\355\204\270\342\200\205\354\213\240\355\230\270\354\231\200\342\200\205\354\240\204\354\206\241.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\354\225\204\353\202\240\353\241\234\352\267\270,\342\200\205\353\224\224\354\247\200\355\204\270\342\200\205\354\213\240\355\230\270\354\231\200\342\200\205\354\240\204\354\206\241.md" new file mode 100644 index 00000000..a38e3f22 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L1\342\200\205network\342\200\205access\342\200\205layer/\354\225\204\353\202\240\353\241\234\352\267\270,\342\200\205\353\224\224\354\247\200\355\204\270\342\200\205\354\213\240\355\230\270\354\231\200\342\200\205\354\240\204\354\206\241.md" @@ -0,0 +1,114 @@ +--- +title: '아날로그, 디지털 신호와 전송' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/6a6ec1dc-b675-470d-b85c-148c29ffb7d2) + +- **아날로그 신호** + - 정현파: 단순 아날로그 신호 + - 최대진폭(전압), 주파수(Hz), 위상(시각 0시에 대한 위치) 세가지 특성으로 나타낼 수 있음 + - 파장: 주기나 전파속도로 구함 + - 복합신호: 여러개의 단순 정현파로 이뤄짐 + - 대역폭: 복합 신호에 포함된 주파수의 범위 (최고 주파수와 최저 주파수의 차이) +- **디지털 신호**: 한정된 준위를 가짐 + - bit rate: 대부분의 디지털 신호는 비주기적이어서 주기나 주파수를 이용할 수 없음 + - 대신 1초 동안 전송된 비트의 수를 나타내는 비트율을 bps(Mbps)로 표현 + - 비트 길이: 한 비트가 전송매체를 통해 차지하는 길이 (파장과 비슷) + - 디지털 신호의 전송 + - 데이터 통신에선 비주기 디지털 신호를 주로 다룸 + - 기저대역(베이스밴드): 디지털 신호를 아날로그로 바꾸지 않고 채널로 바로 전송 + - 광대역(브로드밴드): 디지털 신호를 아날로그 신호로 변경하여 전송 +- **전송 장애**: 신호가 매체를 통해 전송될 때 장애 발생 가능. + - 감쇠와 증폭 (dB 단위로 비교) + - 감쇠 attenuation: 에너지 손실 (저항 때문) + - 증폭 amplification: 다시 키워줌 + - 일그러짐 distortion: 신호의 모양이나 형태 변화 + - 잡음: 잡음 +- **데이터 전송률** + - 데이터 전송률은 아래 세가지에 따라 결정 + - 가역대역폭 + - 사용 가능한 신호준위 + - 채널의 품질 + - 잡음이 없는 채널은 **나이퀴스트 비트율**(nyquist bit rate)이 이론상 최대전송률임 + - 잡음이 있는 채널의 최대 전송률을 **섀넌 용량**(shannon capacity)이라고 정의하고 공식으로 구함 + - 섀넌 용량은 상한 값을 알려주고 나이퀴스트 공식은 몇 개의 신호 준위가 필요한지를 알려준다. + +### 디지털 전송 + +- 회선 부호화(Line coding): 디지털 데이터 to 디지털 신호 + - 블록 부호화(Block coding): 동기화 및 회선 부호화 향상을 위한 규칙 (mB|nB 부호화) + +- 펄스 코드 변조(PCM): 아날로그 데이터 to 디지털 신호 + + image + + - 표본화(Sampling): 아날로그 신호를 매 Ts 마다 채집한다. + - 양자화(Quantizing): 채집한 신호를 계수화(단계별 구분)하여 펄스로 간주한다. + - 부호화(Encoding): 계수화된 펄스 값을 비트 스트림으로 부호화한다. + +### 아날로그 전송 + +디지털 전송은 low-pass channel(0부터 시작하는 채널)이 필요하고, 아날로그 전송은 bandpass chanel(0부터 시작하지 않는 채널)이 필요하다. 그렇기 때문에 디지털 데이터를 아날로그로 전송하기 위해선 변환 과정이 필요하다. + +- **디지털-대-아날로그 변환**: 디지털 데이터 to low-pass channel + - 진폭 편이 변조(ASK, Amplitude Shift Keying) + - 주파수 편이 변조(FSK, Frequency Shift Keying) + - 위상 편이 변조(PSK, Phase Shift Keying) + - 최근엔 ASK와 FSK를 혼합한 QAM이 널리 사용됨 + +- **아날로그-대-아날로그 변환**: low-pass channel to bandpass channel + - 진폭 변조(AM) + - 주파수 변조(FM) + - 위상 변조 (PM) + +### 다중화 +: 단일 링크를 통하여 여러 개의 신호를 동시에 전송 + +- **Multiplexer:** 데이터 스트림을 1개의 스트림으로 조합 +- **Demultiplexer:** 스트림을 각 요소별로 분리하여 각각을 해당 회선으로 보냄 +- 링크는 물리적인 경로를, 채널은 주어진 장치 쌍 사이의 전송을 위한 하나의 경로를 말한다. + 즉, 하나의 경로에 여러개의 채널이 있을 수 있다. + +- **FDM (주파수 분할 다중화, 가로로 자름)** + - 채널들은 신호가 겹치지 않게 하기 위해 사용하지 않는 대역폭(보호대역) 만큼 서로 떨어져있어야 한다. + - 아날로그 다중화 기술 +- **TDM (시분할 다중화, 세로로 자름)** + - 대역폭의 일부를 공유하는 대신에 시간을 공유함. + - 동기식 : 모든 프레임을 각 장치별로 똑같이 n 등분하여 보냄 + - 비동기식(통계적) : 데이터가 있는 것만 보냄. 목적지가 어딘지 알려주는 정보가 필요함 + + + +### 복습 + +- 주파수 영역 도면에서 수평선은 주파수를 나타낸다 +- 아날로그 데이터는 연속적이고 연속적인 값을 갖는다 +- 디지털 데이터는 이산 상태를 가지며 이산 값을 갖는다 +- 아날로그 신호는 시각 간격에서 무한개 수의 값을 가질 수 있다 +- 디지털 신호는 시간 간격에서 제한된 수의 값만 가질 수 있다 +- 주파수와 주기는 서로 역이다 +- 위상은 시간 0에 대한 상대적인 파형의 위치를 나타낸다 +- 주파수가 높아짐에 따라 주기는 감소한다 +- 감쇠는 신호가 전송 매체의 저항 때문에 신호의 강도를 잃어버리는 전송 장애의 한 종류이다. +- 무잡음 채널에서는 나이퀴스트 비트 전송률 공식이 최대 비트 전송률을 결정한다. +- 잡음 있는 채널에서 최대 비트 전송률을 알기 위해서는 섀넌 용량을 사용해야 한다. +- 감쇠, 일그러짐, 잡음은 신호를 망가뜨릴 수 있다. +- 디지털-대-디지털 변환은 회선 부호화, 블록 부호화 및 뒤섞기 기술을 사용한다. +- 블록 부호화는 수신자 측에서 동기화와 오류 검출하는 것을 돕는다. +- PCM은 아날로그-대-디지털 변환의 예이다. +- 아날로그 신호를 디지털 데이터로 변환하는 가장 일반적인 기법은 PCM이다. +- PCM의 첫 번째 단계는 표본 채집 +- FCM + - 아날로그 신호를 전송하는 다중화 기술 + - 각 신호를 다른 주파수로 이동시킴 +- TDM + - 디지털 신호를 전송하는 다중화 기술 + - 동기식 및 통계적 기법으로 나눌 수 있음 + - 동기식 TDM에서 각 입력 연결은 데이터를 전송하지 안는 경우에도 출력을 할당받는다 + - 통계적 TDM에서는 대역폭 효율을 높이기 위해 틈새가 동적으로 할당된다. +- 다중화는 단일 데이터 링크를 사용하여 여러 신호를 동시에 전송하는 것을 허용하는 기술 + +--- +참고 +- [데이터통신과 네트워킹 TCP/IP 프로토콜 기반](https://product.kyobobook.co.kr/detail/S000001693780) diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/CIDR.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/CIDR.md" new file mode 100644 index 00000000..c53bfd73 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/CIDR.md" @@ -0,0 +1,36 @@ +--- +title: 'CIDR' +lastUpdated: '2024-03-02' +--- + +- 인터넷에 연결되는 모든 컴퓨터, 서버 및 최종 사용자 디바이스에는 IP 주소라는 고유한 번호가 연결되어 있으며, 디바이스는 이러한 IP 주소를 사용하여 서로 찾고 통신한다. + +- 1990년대 초반까지 IP 주소는 클래스 기반 주소 지정 시스템을 사용하여 할당되었다. 주소의 전체 길이는 고정되었으며 네트워크 및 호스트 부분에 할당되는 비트 수도 고정되었다. + +- 즉, A, B, C, D, E 등으로 나뉘던 Class는 CIDR가 나오기 전 사용했던 네트워크 구분 체계이다. CIDR가 나오면서 Class 체계보다 더 유연하게 IP주소를 여러 네트워크 영역으로 나눌 수 있게 되었다. + +- 클래스 없는 주소 또는 **Classless Inter-Domain Routing(CIDR)** 주소는 가변 길이 서브넷 마스킹(VLSM)을 사용하여 IP 주소의 네트워크와 호스트 주소 비트 간의 비율을 변경한다. 서브넷 마스크는 호스트 주소를 0으로 변환하여 IP 주소의 네트워크 주소 값을 반환하는 식별자 집합이다. + +- 네트워크 관리자는 VLSM 시퀀스를 통해 IP 주소 공간을 다양한 크기의 서브넷으로 나눌 수 있다. 각 서브넷은 유연한 수의 호스트와 제한된 수의 IP 주소를 포함할 수 있다. CIDR IP 주소는 네트워크 주소 접두사 비트 수를 나타내는 접미사 값을 일반 IP 주소에 추가한다. + +- 예를 들어 `192.0.2.0/24`는 처음 24비트 또는 `192.0.2`가 네트워크 주소인 IPv4 CIDR 주소이다. + +## Subbet masks + +서브넷 마스크는 IP 주소와 비슷한 방식으로 접두어의 길이를 표시하는 방법이다. 즉 32비트 길이의, 접두어 길이만큼의 1로 시작해서, 나머지는 0으로 채우는 것이고, 4개의 숫자 형태로 표현하는 것이다. 서브넷 마스크는 접두어 길이와 동일한 정보를 나타내지만, CIDR보다 먼저 개발되었다. + +CIDR는 IP 주소를 필요에 맞게 서브넷에 할당하기 위해서 일반적으로 네트워크에 사용되는 방식이 아닌, **가변길이 서브넷 마스크(variable length subnet masks, VLSM)**를 사용한다. 그러므로 네트워크/호스트 구분은 주소영역의 어느 부분을 경계로라도 일어날 수 있다. 이 과정은 일부분의 주소가 더욱 작은 부분의 주소로 분해되는 것처럼 계속해서 반복적으로 일어날 수도 있다. 이러면 더욱 작은 부분으로 분해하기 위해서 더 많은 부분을 가리는 마스크가 필요하다. + +CIDR/VLSM 네트워크 주소는 공공 인터넷을 비롯하여 기타 대형 사설 네트워크에서도 널리 사용되고 있다. LAN을 이용하는 환경은 보통 특별한 사설 RFC 1918 주소체계를 이용하여 식별되기 때문이다. + +## private IP range + +class 구분은 없다지만 private IP 범위는 여전히 통용되어 사용되고 있다. + +- **Class A:** 10.0.0.0/8 (10.0.0.0–10.255.255.255) +- **Class B:** 172.16.0.0/12 (172.16.0.0–172.31.255.255) +- **Class C:** 192.168.0.0/16(192.168.0.0–192.168.255.255) + +--- +참고 +- https://yonghyunlee.gitlab.io/temp_post/network-2/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/ICMP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/ICMP.md" new file mode 100644 index 00000000..aa953a78 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/ICMP.md" @@ -0,0 +1,19 @@ +--- +title: 'ICMP' +lastUpdated: '2024-03-02' +--- + +- Internet Control Message Protocol (ICMP) is a network layer protocol used by network devices to diagnose network communication problems. + +- Typically, ICMP protocols are used by network devices such as routers. + +- ICMP is primarily used to determine whether data reaches the intended target in a timely manner. Ping applications that use ICMP packets are used by network administrators to test network hardware devices such as computers, printers, and routers. Ping is typically used to verify that the device is functioning and to track the time it takes for messages to return from the source device to the destination and to return to the source. + +- The main purpose of ICMP is **to report errors**. When the two devices are connected over the Internet, ICMP generates an error to share with the device it sends if the data does not reach its intended destination. For example, if a data packet is too large for a router, the router drops the packet and returns the ICMP message to the original source of the data. + +- The secondary use of the ICMP protocol is **to perform network diagnostics**. Both commonly used terminal utilities traceRoute and ping operate using ICMP. The traceroute utility is used to display the routing path between two Internet devices, which is the actual physical path of the connected router that must pass before the request reaches the destination. The journey between one router and another is called a 'hop', and traceroute also reports the time required for each hop during the journey. This can be useful for determining the cause of network delays.- + +--- +reference +- https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol +- https://www.cloudflare.com/ko-kr/learning/ddos/glossary/internet-control-message-protocol-icmp/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IP.md" new file mode 100644 index 00000000..7cc044bb --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IP.md" @@ -0,0 +1,40 @@ +--- +title: 'IP' +lastUpdated: '2024-03-02' +--- +

IP는 송신 호스트와 수신 호스트가 네트워크에서 정보(패킷)를 주고받는 데 사용하는 정보 위주의 규약이다. OSI 네트워크 계층에서 호스트의 주소지정과 패킷 분할 및 조립 기능을 담당한다.

+

IP에서는 컴퓨터 네트워크에서 장치들이 서로를 인식하고 통신을 하기 위해 IP주소를 배정한 후, 그 주소를 사용해 데이터를 전송할 대상을 특정한다.

+ +## IP의 특징 +- ### 비연결성 + 패킷을 받을 대상과 연결을 확립하여 데이터를 전송하지 않는다. 패킷을 받을 대상이 없거나, 서비스 불능 상태여도 패킷 전송 + +- ### 비신뢰성(unreliability) + IP는 전송 흐름에 관여하지 않기 때문에 보낸 정보가 제대로 갔는지 보장하지 않는다. 전송 과정에서 패킷이 손상되거나 패킷의 순서가 바뀐 경우, 또는 패킷이 사라진 경우에도 그 에러를 검출하거나 수정하지 않는다. + +- ### 프로그램 구분 X + IP는 호스트에 대해서만 식별하기 때문에, IP단계에서는 같은 호스트에서 여러개의 프로그램을 돌려서 여러 서버와 통신하는 경우에 수신한 패킷을 프로그램별로 구분할 수 없다. + +이 점을 보완하기 위해 주로 상위계층의 프로토콜인 TCP/UDP 등의 프로토콜을 함께 사용한다. + +## IPv4와 IPv6 +### IPv4 +

IP의 4번째 버전으로, 32비트의 주소를 사용한다. 보통 0~255 사이의 십진수 넷을 쓰고 '.'으로 구분하여 나타낸다. 0.0.0.0부터 255.255.255.255까지 약 43억개의 IP주소가 존재한다. 점으로 나뉜 8비트의 단위를 옥텟(Octet)이라고 부르기도 한다.

+

ex) 223.130.195.200

+

IP주소는 Network ID와 Host ID로 나뉘어있으며, 서브넷 마스크를 통해 두 부분을 구분할 수 있다. (서브넷 마스크에서 1인 부분은 Network ID, 0인 부분은 Host ID임)

+ + - ### IPv4클래스 + Ipv4는 사용 목적에 따라 ip를 적절히 할당하기 위해서 용도별로 사용할 수 있는 클래스가 나뉘어져 있다. + + |클래스|첫번째 옥텟|사용 목적|기본 서브넷 마스크| + |-|-|-|-| + |A|0xxx xxxx (0~127)|대형 기관 (대륙 간)|255.0.0.0| + |B|10xx xxxx (128~191)|중형 기관 (국가 간)|255.255.0.0| + |C|110x xxxx (192~223)|소형 기관 (기업 간)|255.255.255.0| + |D|111x xxxx (224~239)|그룹 통신, 멀티캐스트용|X| + |E|1111 xxxx (240~254)|연구/개발용|X| + +### IPv6 +

Ipv4 주소가 고갈되는 문제를 해결하기 위하여 새로 개발된 128비트 체계의 인터넷 프로토콜이다. IPv6 주소는 16비트 단위 8개로 구분하며, 각 단위는 16진수로 변환되어 콜론(:)으로 구분하여 표기한다. 0:0:0:0:0:0:0:0부터 FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF까지 약 340간(43억의 4승) 개의 IP주소가 존재한다.

+

ex) 2001:0db8:85a3:08d3:1319:8a2e:0370:7334

+

IPv4보다 IP주소 갯수가 훨씬 많을 뿐만 아니라 보안이나 서비스 품질 면에서도 더 우수하고, PnP 등 여러 기능을 더 지원하기 때문에 교체가 이뤄지고 있다. 현재까지는 IPv4와 IPv4가 공존하고 있기 때문에 NAT(Network Address Translator)로 변환하여 사용하고 있지만, 언젠가는 IPv6이 IPv4를 완전히 대체하게 될 것이다.

diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IPAM.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IPAM.md" new file mode 100644 index 00000000..adffd605 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IPAM.md" @@ -0,0 +1,41 @@ +--- +title: 'IPAM' +lastUpdated: '2024-03-02' +--- + +**IP Address Management (IPAM)** is an integrated suite of tools to enable end-to-end planning, deploying, managing and monitoring of your IP address infrastructure, with a rich user experience. IPAM automatically discovers IP address infrastructure servers and Domain Name System (DNS) servers on your network and enables you to manage them from a central interface. + +image + +
+ +IPAM brings a centralized repository used to build an inventory of the networks, subnets and IP addresses (private and public). It allows administrators to maintain accurate and current records of IP assignments and available addresses. Typical records are: + +- Free/assigned IP address space +- Status of each IP address +- Hostname associated with each IP address +- Hardware associated with each IP address +- Size of subnets and current users + +
+ +image + +## Key Benefits + +1. Easier administration + - for delegation of address space management; improved visibility over IP resources cross-platforms; automation of DNS-DHCP configurations and automatic real-time updates when a host connects/disconnects from TCP/IP network (if the IPAM is integrated with DNS and DHCP servers); built-in reports. + +2. Enhanced reliability + - lower risk of misconfigurations by avoiding overlapping subnets and duplicate IP addresses, and by enforcing FQDN; reduced network service downtime; faster troubleshooting. + +3. Reduced complexity + - single centralized repository containing IP Golden Records; address space related information fetched from RIR and maintained in IPAM; single interface/tool for the administrator to manage both private and public address plans; network discovery to gather info about hosts connected, VRFs etc.; IPv6 management capability. + +4. Ensured integrity + - integration with DNS/DHCP/RIR allows IPAM to be updated with “A” Resource Records and lease information; policy rules can be enforced. + +--- +reference +- https://efficientip.com/glossary/ipam/ + diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IPSec.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IPSec.md" new file mode 100644 index 00000000..10e4ff3a --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IPSec.md" @@ -0,0 +1,83 @@ +--- +title: 'IPSec' +lastUpdated: '2024-03-02' +--- + +IPSec IP계층(네트워크 계층)을 안전하게 보호하기 위한 기법이다. 대부분의 네트워크 응용프로그램은 IP 계층을 사용하기 때문에 IP계층에서 동작하는 보안, 즉, 페킷에 대한 보안을 제공하는 IP Security(IPSec)가 필요하다. + +## 모드 + +IPSec에는 두 가지 모드가 있는데, IP의 내용(payload)만을 보호하느냐, 아니면 헤더까지 모두 보호하느냐에 따라서 전송 모드(Transport Mode), 후자는 터널 모드(Tunnel Model)로 나뉜다. + +### 전송 모드(Transport Mode) + +전송모드는 전송 계층와 네트워크 계층 사이에 전달되는 payload를 보호한다. 중간에 IPSec 계층이 있기 때문에 IPSec 헤더가 붙고, 이후에 네트워크 계층에서는 이것이 모두 상위층에서 보낸 데이터(payload)로 취급이 되므로 IP 헤더가 붙고 아래 계층으로 전달된다. + +image + +전송모드는 host-to-host(end-to-end)간 데이터 보호가 필요할때 사용된다. 아래는 전송모드의 데이터 전송 흐름을 보여준다. + +왼쪽 컴퓨터(host)는 IPSec을 적용하여 데이터를 보낸다. 네트워크를 통해서 오른쪽 컴퓨터로 데이터가 도착한다. + +이 사이에서 다른 사람이 데이터를 가져가도 IPSec로 보호 되어있으므로 볼 수 없고, 라우터를 거쳐 종점에 도착했을 떄의 두 당사자만 데이터를 확인할 수 있다. 그래서 tls와 유사하게 종단 간의 보호(End-To-End Protection, E2EP)가 이루어 질 수 있다. 다만 보호가 이뤄지는 계층이 다르다는 점이 차이이다. + +image + +### 터널 모드(Tunnel Mode) + +터널 모드의 IPSec은 IP 헤더를 포함한 IP 계층의 모든 것을 보호한다. IP 헤더까지 완전히 보호하고 IPSec의 헤더를 추가하였으니 기존의 IP 헤더를 볼 수 없어서 새로운 IP 헤더가 추가된다. + +image + +이 IPSec 헤더와 새로운 헤더는 종단이 아닌 그 중간자가, 대부분의 경우 라우터가 추가해준다. + +아래는 그 흐름을 보여준다. 전송모드와는 다르게 호스트 A는 별다른 IPSec의 조취를 취하지 않는다. 하지만 Router A에서 IPSec을 적용하고 새로운 IP 헤더를 추가한다. + +이 헤더에는 목적지 라우터의 주소가 있어서 Router B로 보낸다. Router B는 이후에 적절한 조치를 취하고 새 IP 헤더와 IPSec 헤더를 제거한 후 Host B에게 전달한다. + +마치 RouterA, RouterB가 터널 역할을 하는 것과 같다. 터널 모드는 주로 종단-종단 간 통신이 아닌 경우에 사용된다. (두개의 라우터간, 호스트와 라우터간, 라우터와 호스트간) + +image + +## 프로토콜 + +IPSec은 또 두가지 보안 프로토콜을 제공한다. + +- **AH(Authentication Header Protocol)** - 인증에 대해서만 검사하는 인증헤더 프로토콜 +- **ESP(Encapsulating Security Payload)** - 페이로드 전체를 보호하여 기밀성을 제공하는 보안 페이로드 캡슐화 + +### AH(Authentication Header) + +발신지 호스트를 인증하고 IP 패킷의 무결성을 보장한다. 인증을 위해서 해시함수와 대칭키가 사용되어 Message Digest를 생성하고 헤더에 삽입한다. AH는 인증과 무결성을 보장하지만 비밀은 보장해주지 않는다. + +image + +- **Next Header** : IPSec 다음에 오는 페이로드의. TCP인지 UDP인지 또는 ICMP인지 의미한다. +- **Payload Length** : 인증헤더의 길이. +- **Security Parameter Index** : 32bit 보안 매개변수 색인(SPI) 필드, Security Association에 대한 식별자 +- **Sequence Number** : 32bit 순서번호 (replay attack을 방지) +- **Authentication Data** : 헤더를 포함하여 전체 페킷에 대한 데이터를 인증 데이터로 만든다. 이때 IP 헤더의 변경될 수 있는 데이터는 제외된다. + +### ESP(Encapsulating Security Payload) + +AH가 데이터의 기밀성을 보장할 수 없지만 ESP는 기밀성을 보장할 수 있다. 또한 AH가 보장하는 IP패킷의 무결성 등 AH가 제공하는 서비스를 모두 보장할 수 있다. + +image + +ESP 헤더 구성 대부분은 AH의 필드와 유사하다. + +AH와는 다르게 인증데이터가 IP헤더를 포함하지 않는다. ESP 헤더까지만 인증데이터로 만들고 ESP Trailer에 붙이게 된다. + +|Services|AH|ESP| +|-|-|-| +|Access Control|O|O| +|Message Authentication\n(Message Integrity)|O|O| +|Confidentiality|X|O| +|Replay Attack Protection|O|O| +|Entity Authentication\n(Data Source Authentication)|O|O| + +--- +reference +- https://aws.amazon.com/ko/what-is/ipsec/ +- https://www.cloudflare.com/ko-kr/learning/network-layer/what-is-ipsec/ +- https://en.wikipedia.org/wiki/IPsec \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IP\342\200\205\353\215\260\354\235\264\355\204\260\352\267\270\353\236\250\352\263\274\342\200\205\353\213\250\355\216\270\355\231\224.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IP\342\200\205\353\215\260\354\235\264\355\204\260\352\267\270\353\236\250\352\263\274\342\200\205\353\213\250\355\216\270\355\231\224.md" new file mode 100644 index 00000000..7eab385e --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/IP\342\200\205\353\215\260\354\235\264\355\204\260\352\267\270\353\236\250\352\263\274\342\200\205\353\213\250\355\216\270\355\231\224.md" @@ -0,0 +1,67 @@ +--- +title: 'IP 데이터그램과 단편화' +lastUpdated: '2024-03-02' +--- + +IP 데이터그램은 아래와 같은 형태로 구성되어있다. + +image + +- `VERS` + - 4비트로 데이터그램의 IP 프로토콜 버전을 명시한다. 다른 버전의 IP는 다른 데이터그램 형식을 사용하기 때문에 라우터는 버전 번호를 확인하여 데이터그램의 나머지 부분을 어떻게 해석하는지 결정한다. + +- `HLEN` + - IPv4 데이터그램은 헤더에 가변 길이의 옵션을 포함하기 때문에 4비트의 헤더 길이를 통해 IP 데이터그램에서 실제 페이로드가 시작하는 곳을 명시해준다. 대부분 IPv4 데이터그램은 옵션을 포함하지 않아서 데이터그램의 헤더는 20바이트가 통상적이다. + +- `SERVICE TYPE` + - 서비스 타입 비트는 서로 다른 유형의 IP 데이터그램을 구별한다. + +- `TOTAL LENGTH` + - 바이트로 계산한 IP 데이터그램(헤더와 데이터)의 전체 길이이다. 이 필드의 크기는 16비트이므로 IP 데이터그램의 이론상 최대 길이는 65,536(2^16)바이트이다. + +- `IDENTIFICATION`, `FLAG`, `FRAGMENT OFFSET`: 세 필드는 IP 단편화와 관계되어있다. 아래에서 더 자세히 알아보자! + +- `TTL(Time-To-Live)` + - 이 필드는 네트워크에서 데이터그램이 무한히 순환하지 않도록 한다. + +- `TYPE`(상위 계층 프로토콜) + - 이 필드는 일반적으로 IP 데이터그램이 최종 목적지에 도달했을 때만 사용한다. 이 필드값은 IP 데이터그램에서 데이터 부분이 전달될 목적지의 전송 계층의 특정 프로토콜을 명시한다. + +- `HEADER CHECKSUM` + - 헤더 체크섬은 라우터가 수신한 IP 데이터그램의 비트 오류를 탐지할 수 있도록 돕는다. + +- `SOURCE IP ADDRESS`, `DESTINATION IP ADDRESS` + - 출발지가 데이터그램을 생성할 때, 자신의 IP 주소를 출발지 IP 주소 필드에 삽입하고 목적지 IP 주소를 목적지 IP 주소 필드에 삽입합니다. + +- `IP OPTIONS` + - 옵션 필드는 IP 헤더를 확장한다. + +## 단편화 + +- Ipv4 데이터그램의 단편화는 **모든 링크 계층 프로토콜들이 같은 크기의 네트워크 계층 패킷을 전달할 수 없기** 때문에 필요하다. + +- 어떤 프로토콜은 아주 많은 양의 데이터를 보내야할 수 있다. 그러나 링크 계층 프레임이 전달할 수 있는 최대 데이터 양은 제한되어있다(MTU). + +- 그렇기 때문에 IP 데이터그램의 페이로드를 두 개 이상의 더 작은 IP 데이터그램으로 분할하고, 각각의 더 작아진 IP 데이터그램을 별로의 링크 계층 프레임으로 캡슐화하여 출력 링크로 보낸다. + +- 다시 말해, 만약 MTU 이상의 크기로 데이터가 전송된다면 패킷이 MTU 크기에 맞춰져서 분할하게 되는데 이것을 **단편화**(Fragmentation)라고 하고, 나눠진 작은 데이터그램 각각을 **단편**(Fragment)이라고 부른다. + +- `IDENTIFICATION`, `FLAG`, `FRAGMENT OFFSET`은 데이터그램에서 단편화에 관련된 정보를 저장하는 필드이다. + +- `IDENTIFICATION` + - 패킷이 단편화 된 각각의 조각을 구분하기 위해 할당된 식별자이다. +- `FLAG` + - 단편의 특성을 나타내는 3비트의 플래그들이다. 순서대로 아래와 같은 의미이다. + 1. 미사용 (항상 0) + 2. Don't Fragment + 이 값이 1이면 단편이 불가능하다는 뜻 + 3. More Fragment + 이 값이 1이면 뒤에 단편이 더 있다는 뜻 +- `FRAGMENT OFFSET` + - 분할된 패킷을 수신측에서 다시 재조합할때 패킷들의 순서를 파악하기 위해 사용된다. + - 8 바이트 단위(2 워드)로 최초 분열 조각으로부터의 Offset을 나타낸다. + - ex) 첫 단편 옵셋 : 0, 둘째 단편 옵셋 : 첫 단편 크기 만큼 (8 바이트 단위) + +--- +참고 +- http://www.ktword.co.kr/test/view/view.php?m_temp1=5236 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/NAT.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/NAT.md" new file mode 100644 index 00000000..940b12c3 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/NAT.md" @@ -0,0 +1,89 @@ +--- +title: 'NAT' +lastUpdated: '2024-03-02' +--- + +2011년 2월, 인터넷 주소 관리기구인 IANA는 더 이상의 IPv4 할당이 없을 것이라고 선언했다. IPv4는 약 43억 개의 한정된 주소를 사용할 수 있는데 반해 인터넷의 수요가 빠르게 증가하여 각 대륙에 할당한 IPv4가 동이 나버려 더 이상 할당할 수 없게 된 것이다. + +IPv6가 조금씩 상용화 되고 있긴 하지만, 이상하게도 우린 아직도 IPv4를 원활하게 사용하고 있다. 많지 않은 수의 IPv4로 현재까지 별 탈 없이 인터넷을 사용할 수 있게 된 것은 Private Network(이하 사설망) 덕분이라고 볼 수 있다. + +## Private Network(사설망)의 탄생 + +> 사설망 또는 프라이빗 네트워크(private network)는 인터넷 어드레싱 아키텍처에서 사설 IP 주소 공간을 이용하는 네트워크이며 RFC 1918과 RFC 4193 표준을 준수한다. 이러한 주소는 가정, 사무실, 기업 랜에 쓰인다. + +Private Network(사설망)는 IPv4 중 특정 대역을 공인 인터넷이 아닌 가정, 기업 등의 한정된 공간에 사용한 네트워크를 의미한다. 사설망에 소속된 IP인 사설 IP 대역은 다음과 같으며 오로지 **사설망**(내부망)에서만 사용 가능하기 때문에 공인망(외부망, 인터넷)에선 사용할 수 없다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/10e7225c-d27e-42a2-818d-d21edbc7e66e) + +이 사설 IP는 사설망에만 해당한다면 어디에서나 사용할 수 있다. 일반적으로 집에서 사용하는 컴퓨터, IPTV, 휴대폰, 플레이스테이션 등은 공유기가 할당해주는 사설 IP를 사용하고, 기업도 스위치나 라우터, 방화벽과 같은 네트워크 장비 혹은 비슷한 장비에 사설 IP와 서브넷 마스크를 지정하고 게이트웨이(사설 IP 할당)로 사용하며 이에 연결된 컴퓨터에 사설 IP를 할당한다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/8e636e94-69f3-4af0-8107-cae6fd427c3e) + +이렇게 사설망과 공인망이 사용하는 IP에 따라 분리되면서 공인망과 사설망의 경계에서 별도의 조치가 필요해졌다. 사설망에서 공인 인터넷으로 나가고자 할 때 자신의 출발지 IP(Source IP)를 사설 IP 그대로 쓸 수 없기 때문이다. 그렇기에 사설 IP를 공인 IP로 변환해야한다. + +IP를 변환하는 것은 사설망과 공인망의 통신에서만 필요한 것이 아니다. 자사의 사설망(내부망)과 전용 회선(Leased Line)을 통해 대외사의 사설망(내부망)을 연결할 경우, 이 경우의 통신에서도 IP를 변환해야 한다. 자신의 실제 IP를 노출시키지 않아야 하거나 반대편 기업의 실제 IP로 목적지 IP를 변환하여야 할 필요가 있을 때 사용한다. 이에 IP를 변환하기 위한 방법을 고안한 것이 바로 Network Address Translation(NAT)이다. + +## NAT이란? + +> 네트워크 주소 변환(영어: network address translation, 줄여서 NAT)은 컴퓨터 네트워킹에서 쓰이는 용어로써, IP 패킷의 TCP/UDP 포트 숫자와 소스 및 목적지의 IP 주소 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고받는 기술을 말한다. + +Network Address Translation(이하 NAT)는 IP 주소 혹은 IP 패킷의 TCP/UDP Port 숫자를 변환 및 재기록하여 네트워크 트래픽을 주고받는 기술을 의미한다. 지금까지 설명한 내용을 적용해보자면 사설망에서 공인망으로, 공인망에서 사설망으로 통신하고자 할 때 공인망/사설망에서 사용하는 IP로 변환하는 것을 의미한다고 볼 수 있다. + +여기서 IP 주소뿐만 아니라 IP 패킷의 TCP/UDP Port 숫자를 변환한다고 말한 이유는 실제로 NAT 의미가 IP 주소뿐만 아니라 Port까지 변환시켜 사용하는 것을 포함하기 때문이다. 이를 Port Address Translation(이하 PAT 또는 NAPT)라고 부른다. NAT와 PAT의 예시를 각각 살펴보자. + +image + +사용자 1(`10.10.10.10/24`)이 공유기를 통해 공인망에 존재하는 웹 서버(`125.209.22.142:80`)에 접속하려고 한다. 사용자 1은 사설 IP를 보유하고 있기 때문에 공인망으로 나아가기 위해서는 자신의 사설 IP를 공인 IP로 반드시 변환(NAT)해야 한다. 그리고 NAT Device(이하 NAT 장비, 공유기 등)가 이를 수행해준다. + +1. 사용자가 웹 서버에 접속하기 위해 NAT 장비(Gateway)에 패킷을 보내는데 IP/Port 정보이다. + +2. 이를 받아든 NAT 장비가 자신에게 허용된 규칙을 확인하고 공인망의 웹서버에게 보내기 위해 사용자의 사설 IP를 자신의 공인 IP로 변환하여 웹서버에게 전달한다. 정확히 말하면 공인망에 맞닿아 있는 자신의 인터페이스 IP로 변환하는 것이다. + +3. 웹서버가 사용자가 보낸 요청을 처리하고 응답을 사용자에게 보낸다. 목적지에서 출발지로 패킷을 다시 보내는 것이다. + +4. 응답 패킷을 받은 NAT 장비가 과거 사용자가 보낸 요청에 대한 응답임을 기억(Stateful)한다. 그리고 목적지 IP를 공인 IP에서 사용자의 실제 사설 IP로 변환하여 전달한다. + +여기서 문제가 하나 발생한다. NAT 장비에 할당된 공인 IP는 하나이지만 사용자는 2명이다. 사용자 1이 자신의 출발 포트를 9999로 지정하여 NAT 장비에 전송했음을 위 과정을 통해 알 수 있었다. 그런데 동시에 사용자 2도 자신의 출발 포트를 9999로 설정하여 전송한다면 어떻게 될까? 패킷이 공인망으로 나아갈 땐 문제가 없겠지만 되돌아올 때 문제가 발생할 것이다. 왜냐하면 목적지가 공인 IP이고 포트는 9999인데 이게 사용자 1인지 사용자 2인지 구분할 방법이 없기 때문이다. + +이에 사용되는 것이 바로 **PAT(Port Address Translation)**이다. 사용자 1과 사용자 2로부터 패킷을 전달받아 사용자의 IP에 대해 NAT 장비가 NAT를 실시할 때 출발지 포트를 임의로 변경하는 것이다. 예를 들어 사용자 1의 출발지 포트를 `10000`으로 바꾸고 사용자 2의 출발지 포트를 `20000`으로 바꾼다면, 공인 IP는 하나이지만 사용자마다 포트로 구분할 수 있으니 문제가 해결된다. + +위 그림을 보면 사용자 1에게 받은 패킷은 출발지 포트를 `10000`로 변환하여 구분한 것을 알 수 있다. 그리고 패킷이 되돌아 올 때 변경된 목적지 포트를 보고 포트 `10000`은 사용자 1임을 구별할 수 있게 될 것이다. + +image + +NAT에는 목적지의 IP 변경도 존재한다. L4 스위치의 목적지 IP NAT가 가장 대표적이다. + +image + +## Session Table & Stateful + +NAT를 수행하는 네트워크 장비의 종류는 매우 다양하다. 주로 관문 역할(Gateway)을 하는 네트워크 장비가 주로 NAT를 수행한다. 가정에서는 공유기가 내부망과 공인망의 경계에서 NAT를 실시하며, 기업에서는 방화벽, VPN, L4 스위치 등이 이 역할을 좀 더 많이 수행한다. 공인망에 노출되는 관문에 해당하는 장비인만큼 보안 기능을 곁들인 장비가 맡는 것이다. + +NAT를 수행하는 장비들은 자신에게 설정된 규칙(Rule)에 따라 허용/거부를 판단하고, NAT를 실시하고 이를 기록해둔다. 이를 수행하는 장비들을 보통 Session 장비라고 부르며 NAT를 실시한 내역을 기록한 테이블을 Session Table이라고 부른다. 위의 예시 또한 세션 테이블을 생성한다. + +image + +위에서 설명했던 예시와 같은 상황에서 사용자 1의 세션 테이블에 어떠한 IP와 어떠한 Port로 NAT/PAT되어있는지 기록되어있는 모습이다. 공인 IP가 1개라 사용자별로 출발지 포트를 구분하여 기록되었다. + +image + +위 사진은 L4 스위치를 거쳐 실제 서버로 Request가 유입되면서 목적지인 실제 서버의 사설 IP로 NAT된 것이 세션 테이블에 반영이 되어있는 모습이다. + +보통 세션 장비에 정해진 Rule(이하 규칙)에 의해 허용된 IP만이 NAT를 실시할 수 있고 세션 테이블에 이름을 올릴 수 있게 된다. 주로 방화벽과 같은 장비가 이러한 작업을 수행한다. 그리고 테이블에 기록된 IP는 규칙에 의해 나가거나/들어온 뒤 다시 들어오거나/나갈 수 있다. 즉 규칙에 의해 한 번 허용이 된 패킷(Request)은 반대 방향(Response)에 대한 정책을 별도로 수립할 필요 없이 테이블에 기록된 세션을 보고 네트워크 장비가 통과시킨다는 것을 의미한다. 이러한 특성을 Stateful이라고 얘기한다. + +## NAT의 용어와 종류 + +NAT는 어느 관점에서 보느냐에 따라 부르는 용어가 달라진다. + +- IP와 IP를 일대일 방식으로 변환하면 **Static NAT** +- IP Pool과 IP 1개 혹은 IP Pool을 다대다 방식으로 변환하면 **Dynamic NAT** +- 포트까지 같이 변환하면 **Newtork Address Port Translation(NAPT)** +- Source IP를 변환하면 **Source IP NAT(SNAT)** +- Destination IP를 변환하면 Destination **IP NAT(DNAT)** + +--- +참고 +- https://www.stevenjlee.net/2020/07/11/%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-nat-network-address-translation-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A3%BC%EC%86%8C-%EB%B3%80%ED%99%98/ +- https://en.wikipedia.org/wiki/Network_address_translation +- https://learn.microsoft.com/ko-kr/azure/rtos/netx-duo/netx-duo-nat/chapter1 +- https://archive.md/20130103041130/http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/rzajw/rzajwstatic.htm \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/secondary\342\200\205IP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/secondary\342\200\205IP.md" new file mode 100644 index 00000000..fd405b34 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/secondary\342\200\205IP.md" @@ -0,0 +1,24 @@ +--- +title: 'secondary IP' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/d6e7d951-3092-4281-8661-65bcd7206e92) + +- Secondary IP란 현재 서브넷에 의해 나뉘어져있는 네트워크 대역에 추가적인 호스트가 필요할때 + 전체적인 구성을 변경하지 않고도 확장이 가능하게 하는 기술이다. + +- 예를 들어 10.0.0.0/24 의 네트워크 인터페이스를 가진 라우터내의 호스트가 200명이 있고 + 갑작스럽게 추가적으로 200명의 호스트가 더 필요해졌을때 해당 네트워크 인터페이스에 `10.0.1.0/24` 라는 Secondary IP 대역을 부여하면 `10.0.1.0/24` 대역까지 같은 네트워크 대역으로 인식시킬 수 있다. + +## 장점 + +1. 가용성(내결함성 향상) + + - Host의 입장에서 `10.0.0.0/24`대의 NIC 한 개와 `10.0.1.0/24`대의 NIC 한 개를 달고 있을 때 + 하나의 네트워크 카드가 망가지더라도 다른 네트워크 카드로 통신이 가능하므로 가용성이 향상된다. + - 물론 라우터 자체의 NIC가 망가진다면 그 측면에서는 가용성이 향상된것은 아니다. + +2. 확장성 + + - 네트워크 구조를 바꾸지 않아도 Secondary IP만을 부여함으로써 쉽게 확장이 가능하다 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/ECMP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/ECMP.md" new file mode 100644 index 00000000..d8a2ccf3 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/ECMP.md" @@ -0,0 +1,47 @@ +--- +title: 'ECMP' +lastUpdated: '2024-03-02' +--- + + + +ECMP는 하나의 목적지로 패킷 라우팅을 수행하면서 여러 개의 경로를 선택하는 라우팅 기법이다. 이름처럼 같은 Cost를 가진 여러 경로에 트래픽을 분산시킨다. 같은 Cost로 판정되려면 static, OSPF, BGP 에서 다음의 속성이 같아야 한다. + +- Destination Subnet +- Distance +- Metric +- Priority + +ECMP는 다음 홉에 대한 선택을 단일 라우터로 국한시킬 수 있기 때문에 대부분의 라우팅 프로토콜과 결합하여 사용할 수 있다. + + +### 트래픽 분배 방법 + +트래픽을 분배하는 방법에 대한 설정은 다음과 같다. + +image + +- source IP (default) + - source IP 기반으로 경로 선택. 동일한 source IP의 경우 동일한 경로로 통신 +- source-destination IP + - 동일한 source IP와 destination IP 쌍(pair)에 대해 동일한 경로로 통신 +- weighted + - Route 또는 interface weight 설정에 따라 분배 (가중치가 높을수록 선택 가능성 높음) +- usage (spillover) + - 설정한 임계치에 도달할 때까지 하나의 경로만 사용하고 임계치에 도달하면 다음 경로 사용 + +세션 단위로 분배를 하게 되며, ECMP route중 하나에서 fail이 발생하면 자동으로 routing table에서 삭제되고 트래픽은 남아있는 경로로 통신하게 된다. 라우팅 fail에 대한 별도의 설정은 필요 없다. + +### 장점 +- 다중 경로를 통해 트래픽을 분산시킴으로써 대역폭이 증가할 수 있다. + +### 단점 +- TCP, path MTU discovery와 같은 인터넷 프로토콜의 동작에 혼란을 가져온다. +- 여러 개의 경로를 통해 전송되던 데이터 흐름이 어느 순간 하나의 경로로 수렴할 경우, 대역폭의 증가없이 트래픽 경로의 복잡도만 증가한다. +- 시스템의 물리적인 토폴로지와 논리적 토폴로지가 다를 경우 (VLAN가 적용되었거나 ATM, MPLS와 같은 virtual circuit-based 구조를 가진 시스템 등), 다른 라우팅 프로토콜과 제대로 연동되지 않을 수 있다. + +--- +참고 +- https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing +- https://ebt-forti.tistory.com/465 +- https://docs.vmware.com/kr/VMware-NSX/4.1/administration/GUID-443B6B0D-F179-429E-83F3-E136038332E0.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\235\274\354\232\260\355\204\260.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\235\274\354\232\260\355\204\260.md" new file mode 100644 index 00000000..522a072b --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\235\274\354\232\260\355\204\260.md" @@ -0,0 +1,113 @@ +--- +title: '라우터' +lastUpdated: '2024-03-02' +--- + +- 라우터는 경로를 지정해주는 장비이다. +- 들어오는 패킷의 목적지 IP 주소를 확인하고 자신이 가진 경로(Route) 정보를 이용해 패킷을 최적의 경로로 포워딩한다. + +image + +- **Hop-by-Hop 라우팅**: 라우터는 인접한 라우터까지만 경로를 지정하면 인접 라우터(Next-hop)에서 최적의 경로를 다시 파악한 후 라우터로 패킷을 포워딩한다. +- Next-hop을 지정할 때는 세 가지의 방법을 사용할 수 있다. + - 다음 라우터의 IP을 지정 + - 라우터의 나가는 인터페이스를 지정 + - : 상대방 넥스트 홉 라우터의 IP주소를 몰라도 MAC주소 정보를 알아낼 수 있을 때만 사용. + - 라우터의 나가는 인터페이스와 다음 라우터의 IP를 동시에 지정 + - : 이 경우 VLAN 인터페이스와 같은 논리적인 인터페이스를 사용할 수 있다. + +## 라우팅 테이블 + +image + +- 라우터의 역할은 라우팅 뿐만이 아니라, 다양한 경로 정보를 데이터베이스화하고 최선의 경로 정보를 수집하는 것이 포함된다. + +- 라우터가 수집한 Raw data는 토폴로지 테이블이라고 하고 이 경로 정보 중 최적의 경로를 저장하는 테이블을 라우팅 테이블이라고 한다. + +- 라우터는 목적지 주소와 라우팅 테이블을 비교해 포워딩 경로를 결정한다. 그렇기 때문에 라우터가 정상적으로 동작하려면 복잡하고 많은 경로 정보를 충분히 수집하여 라우팅 테이블을 적절히 유지해야 한다. 자신이 분명히 알고 있는 주소가 아닌 목적지를 가진 패킷이 들어오면 해당 패킷을 버린다. + +- 라우팅 테이블에는 다음과 같은 정보가 포함된다. + - 목적지 주소 + - Next-hop 주소, 나가는 로컬 인터페이스 (선택 가능) + +- PBR(Policy-Based Routing) 기능을 사용해 패킷의 출발지 주소로 라우팅하도록 설정할 수 있다. + - 라우터 정책과 관련된 별도 설정이 필요하다. + - 다른 라우터로의 전파가 어렵고 일반적이지 않은 별도 동작이 필요하다. + + +### 라우팅, 스위칭 우선순위 + +- 토폴로지 테이블에서 우선순위는 경로 정보를 받은 방법과 거리를 기준으로 정한다. + +- 경로 정보를 받는 방법은 아래와 같은 분류들이 있다. + - 내가 갖고 있는 네트워크 (Direct Connected) + - 내가 경로를 직접 지정한 네트워크 (Static Routing) + - 경로를 전달받은 네트워크 (Dynamic Routing) + +- 필요에 따라 관리자가 우선순위인 AD(Administrative Distance)를 조정해 경로를 바꿀 수 있다. 숫자가 작을수록 우선순위가 높다. + |우선순위|기본 디스턴스| + |-|-| + |0|Connected Interface| + |1|Static Route| + |20|External BGP| + |110|OSPF| + |115|IS-IS| + |120|RIP| + |200|Internal BGP| + |255|Unknown| + +- 가중치 값이 동일한 경우에는 Cost 값으로 우선순위를 정한다. Cost 값은 거리를 나타내는 값으로, 라우팅 프로토콜마다 기준이 다르다. + - RIP은 흡수, OSPF는 대역폭(Bandwidth), EIGRP는 다양한 값들을 연산해 나온 값으로 코스트를 지정한다. + +- Cost도 같은 경우엔 ECMP(Equal-Cost Multi-Path) 기능으로 동일한 코스트 값을 가진 경로 값 정보를 모두 활용해 트래픽을 분산한다. + +## 라우팅 + +- **Direct Connected** + - IP 주소와 서브넷 마스크로 네트워크 주소를 사용해 얻는 경로 정보를 Direct Connected라고 부른다. + - 인터페이스에 IP를 설정하면 자동 생성되는 정보이므로 정보를 강제로 지울 수 없고, 해당 네트워크 설정을 삭제하거나 네트워크 인터페이스가 비활성화되어야만 사라진다. + - 목적지가 다이렉트 커넥티드라면 라우터는 L2(ARP 요청을 직접 보내는)으로 목적지에 도달한다. + - 외부 네트워크와 통신하려면 다이렉트 커넥티드 외에 스태틱 라우팅이나 다이나믹 라우팅에서 얻은 원격지 네트워크에 대한 적절한 라우팅 정보가 필요하다. + - 반대로 외부 네트워크 주소가 있더라도 다이렉트 커넥티드 정보가 잘못되면 통신이 불가능하다. + ⇒ 외부 네트워크로 나가는 첫 번쨰 길목이 다이렉트 커넥티드이기 때문이다. + +- **Static Routing** + - 관리자가 목적지 네트워크와 넥스트 홉을 라우터에 직접 지정해 경로 정보를 입력하는 것을 Static Routing이라고 한다. + - 라우터 너머의 다른 라우터의 상태 정보를 파악할 수 없어 라우터 사이의 회선이나 라우터에 장애가 발생했을 때 대응이 불가능하다. + - 연결된 인터페이스 정보가 삭제되거나 비활성화되면 연관된 정보가 자동 삭제된다. + - 물리 인터페이스가 비활성화되어도 논리 인터페이스는 함께 비활성화되지 않아 라우팅 테이블에 남아있을 수 있다. + - 스태틱 라우팅 정보를 기입하는 명령어는 다음과 같다. + ```js + ip route NETWORK NETMAST NEXTHOP // 네트워크 장비가 시스코인 경우 + route add -net NETWORK /Prefix gw NEXTHOP // 서버 운영체제가 리눅스인 경우 + ``` + - 대상이 `0.0.0.0` Static routing을 디폴트 라우팅이라고 한다. 인터넷으로 향하는 경로나 자신에게 경로 정보가 없는 경우 디폴트를 참조하게 된다. + +- **Dynamic Routing** + - 라우터끼리 자신이 알고 있는 경로 정보나 링크 상태 정보를 교환해 정보를 학습하고, 장애가 발생하면 감지하여 대체 경로로 패킷을 포워딩하는 방식이다. + - 라우터끼리 정보를 교환해 경로 정보를 최신으로 유지할 수 있는 다이나믹 라우터는 RIP, OSPF, IS-IS IGRP, EIGRP, BGP같은 다양한 프로토콜이 있다. + + + + - Dynamic Routing 프로토콜을 분류하는 기준은 여러개가 있다. + - 역할별 분류는 다음과 같다. + - **IGP(Interior Gateway Protocol)**: AS 내부에서 사용하는 라우팅 프로토콜 + - **EGP(Exterior Gateway Protocol)**: AS간의 통신에서 사용하는 라우팅 프로토콜 + - 동작원리별 분류는 다음과 같다. + - **디스턴스 벡터(Distance Vector)**: 인접한 라우터에서 경로 정보(계산된 라우팅 테이블)를 받는 라우팅 프로토콜 + - 인접 장비끼리만 통신하기 때문에 멀리 떨어져있는 경로 정보를 얻고 동기화되는 데 시간이 오래 걸린다. + - **링크 스테이트(Link-State)**: 네트워크 망에 속한 장비가 보내준 링크 상태 정보를 기반으로 네트워크 맵을 그리는 라우팅 프로토콜 + - SPF(Shortest Path First) 알고리즘으로 최단 경로 트리를 만든다. + - 네트워크 변화를 빨리 감지하고 최적화하기 위해 네트워크를 Area 단위로 분리하고, 분리된 Area 안에서만 정보를 교환한다. + - 대부분 IGP로 OSPF(링크 스테이트 라우팅 프로토콜)와 BGP가 많이 사용된다. EGP로는 대부분 BGP(디스턴스 벡터 프로토콜)가 사용된다. + +## 스위칭 + +- 패킷이 들어와 라우팅 테이블을 참조하고 최적의 경로를 찾아 라우터 외부로 포워딩하는 작업을 스위칭이라고 한다. + +- 들어온 패킷의 목적지가 라우팅 테이블에 있는 정보와 완벽히 일치하지 않는 경우에는 Longest Prefix Match 기법을 이용해 갖고 있는 경로 정보 중 가장 가까운 경로를 선택한다. + - 이런 과정은 라우터에 부하를 많이 주기 때문에 목적지 IP, 출발지와 목적지 IP, 포트번호 등을 캐싱하여 최적화할 수 있다. + +--- +참고 +- https://geek-university.com/directly-connected-routes/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\235\274\354\232\260\355\214\205\342\200\205\354\225\214\352\263\240\353\246\254\354\246\230.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\235\274\354\232\260\355\214\205\342\200\205\354\225\214\352\263\240\353\246\254\354\246\230.md" new file mode 100644 index 00000000..31f4ba9f --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\235\274\354\232\260\355\214\205\342\200\205\354\225\214\352\263\240\353\246\254\354\246\230.md" @@ -0,0 +1,88 @@ +--- +title: '라우팅 알고리즘' +lastUpdated: '2023-10-21' +--- +## **라우팅 알고리즘** + +- Dynamic Routing을 구현하는 알고리즘들에 대해 자세히 알아보자 +- **Distance-Vector** **Routing Argorithm** + - 인접 라우터와 정보 공유하여 목적지까지의 거리와 방향을 결정하는 라우팅 프로토콜 알고리즘 + - 벨만-포드(Bellman-Ford) 알고리즘 사용 + + image + + - https://ko.wikipedia.org/wiki/거리_벡터_라우팅_프로토콜 + + ```bash + dist[v]<=dist[u]+w(u,v) + ``` + + - **Count to Infinity** 문제가 일어날 수 있음 + - **수평 분할:** C가 A에 도달하는 최소 경로에서 B를 거쳤다는 것을 안다면, 이 정보를 다시 B에게 광고할 필요가 없다. 경로 정보를 받아온 노드에는 다시 정보를 보내지 않도록 하는 것이 수평 분할 방식이다. + + 만약 타이머를 써서 테이블의 오래된 정보를 삭제한다면, 타이머 때문에 경로가 사라진 건지(이 경우 갱신 필요) 수평 분할 정책으로 인해 소식이 안 오는건지 구분이 불가능 + + - **포이즌 리버스:** 포이즌 리버스는 수평분할에서 일어나는 문제를 해결할 수 있다. C가 A에 대한 정보를 광고하되, 만약 송신자가 노드 A인 경우 거리값을 무한대로 설정해서 값이 사용되지 않도록 한다. + + - 장점 + - 구현이 간단하고 쉽다. + - 작은 네트워크에서 잘 작동한다. + - 라우터가 자신의 이웃에 대한 정보만 알면 되므로 라우팅 테이블이 비교적 작다. + - 단점 + - 컨버전스 시간(모든 라우터가 전체 네트워크 경로를 합의하는 데 걸리는 시간)이 길 수 있다. + - 확장성이 제한적이다. + - 큰 네트워크에서는 잘 동작하지 않을 수 있다. + + - 대표 프로토콜: RIP, IGRP + +- **Link-State Routing Argorithm** + - 링크 상태 정보를 모든 라우터에 전달하여 최단 경로 트리를 구성하는 라우팅 프로토콜 알고리즘 + - 다익스트라(Dijkstra) 알고리즘 사용 + - **동작과정** + 1. 이웃을 파악하기 위해 hello 패킷을 보낸다. + 응답이 오면 이웃이 있는 것이고 없으면 존재하지 않는 것이다. + 2. 각 라우터마다의 cost를 측정하기 위해서 ping 메세지를 보낸다. + 3. cost 정보를 뿌리기 위한 link state 패킷을 만든다. + + image + + ```bash + distance[a][c]>distance[a][b]+distance[b][c]가 성립한다면 + distance[a][c]=distance[a][b]+distance[b][c]; + ``` + + 4. 만든 패킷을 도메인 내의 다른 라우터들에게 브로드캐스트 한다.(Flooding) + 5. 다른 노드들도 모두 브로드캐스트하므로 받은 정보를 통해 + 다익스트라 알고리즘을 돌려 shortest path를 구성한다. + - 대표 프로토콜: OSPF(Open Shortest Path First) + +- **Path-Vector Routing Argorithm** + - 자신의 패킷이 통과하는 것을 어떤 라우터를 지나는 것을 금지하고 싶은 경우가 있을 수 있다. + - (라우터가 충분한 보안을 제공하지 못하거나, 정책때문에 특정 지역을 피해야 하는 경우) + - 그러나 위에서 얘기한 Distance-Vector와 Link-State 라우팅은 최소비용을 목표로 하기 때문에 이런 정책을 설정할 수 없다. + - ISP 사이에 패킷의 경로를 사용하기 위해 설계되었다. + - 발신자가 경로에 적용한 규칙을 사용하여 경로를 결정한다. + - 스패닝 트리를 사용하여 경로를 결정한다. (최소비용 X, 지정한 규칙에 따라) + + image + + - **동작 과정** + - 노드가 부팅될 때 이웃으로부터 얻는 정보 기반으로 경로 벡터를 작성한다. + - greeting 메시지를 근접 라우터에 전송하여 이웃에 대한 정보를 수집한다. + - 이웃으로부터 경로 벡터를 전달받아 벨만-포드 방정식과 비슷한 방정식을 사용하여 갱신된다. + 최소비용을 찾는 대신 규칙에 맞는 최선의 경로를 선택하도록 한다. + + - 장점 + - 전체 네트워크에 대한 정확한 정보를 가지고 있으므로 최적의 경로를 결정하는데 더 효과적이다. + - 컨버전스 시간이 짧다. + - 복잡하고 큰 네트워크에서도 잘 동작합니다. + - 단점 + - 각 라우터가 전체 네트워크에 대한 정보를 유지해야 하므로 메모리 사용량과 CPU 사용량이 많다. + - 구현 및 관리가 복잡할 수 있으며, 초기 설정 비용이 비쌀 수 있다. + +- 라우팅에서는 대부분 IGP로 OSPF(Link-State Routing)와 BGP가 많이 사용된다. EGP로는 대부분 BGP(Distance-Vector)가 사용된다. + +--- +참고 +- [데이터 통신과 네트워킹 6판](https://product.kyobobook.co.kr/detail/S000001693780) +- https://ko.wikipedia.org/wiki/거리_벡터_라우팅_프로토콜 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\251\200\355\213\260\354\272\220\354\212\244\355\212\270\342\200\205\353\235\274\354\232\260\355\214\205.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\251\200\355\213\260\354\272\220\354\212\244\355\212\270\342\200\205\353\235\274\354\232\260\355\214\205.md" new file mode 100644 index 00000000..e42e5338 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\353\235\274\354\232\260\355\204\260/\353\251\200\355\213\260\354\272\220\354\212\244\355\212\270\342\200\205\353\235\274\354\232\260\355\214\205.md" @@ -0,0 +1,64 @@ +--- +title: '멀티캐스트 라우팅' +lastUpdated: '2024-03-02' +--- + +- 유니캐스팅: 하나의 발신지와 목적지(1:1) +- 멀티캐스팅: 하나의 발신지와 목적지 그롬(1:n) + +## DVMRP(Distance Vector Multicast Routing Protocol) + +- 거리-벡터 멀티캐스트 라우팅 프로토콜 +- 유니캐스트 라우팅에 사용되는 RIP를 기반으로 멀티캐스팅이 가능하도록 확장한 것이다. +- DVMRP는 발신지 기반 트리 기법을 사용한다. 멀티캐스트 패킷을 수신할 각 라우터는 먼저 다음과 같은 세 가지 단계를 거쳐 발신지 기반 멀티캐스트 트리를 생성해야 한다. + +--- + +1. 라우터는 RPF(Reverse Path Forwarding)로 발신지와 자신 사이의 최적 트리를 생성하는 시뮬레이션을 한다. + - Multicast에서 모든 노드에 대한 최적 트리를 찾기는 복잡하다. + - 대신, 각 하위 노드에서 멀티캐스트 지점에 대한 최적 경로를 찾으면 그것을 뒤집은 것을 합친 정보로 최적 트리를 만들 수 있을 것이다. + +2. 라우터는 RPB(Reverse Path Broadcasting)을 사용하여 발신지가 자신이고, 인터넷 상의 모든 네트워크를 트리의 구성원으로 하는 브로드캐스트(스패닝) 트리를 생성한다. + - 라우터가 발신지로부터 수신한 복사본 중 단 하나의 복사본만을 전송하고 나머지는 삭제하는 알고리즘이다. + - 하나의 라우터를 부모 라우터로 지정하여, 담당 네트워크의 부모 라우터가 아닌 라우터가 멀티캐스트 패킷을 수신하면 패킷을 폐기한다. + - RPB는 RPF 알고리즘을 통해 생성된 그래프로부터 브로드 캐스트 트리를 실제로 생성한 뒤 그래프에서 순환의 원인이 되는 링크를 삭제한다. + - 만약 부모 선택을 위해 최단경로를 기준으로 사용하면 발신지가 루트, 네트워크 각각은 리프 노드가 되는 최단경로 트리를 가지게 된다. + +3. 라우터는 PRM(Reverse Path Multicasting)을 사용하여 그룹의 회원이 없는 네트워크의 끝부분(트리의 구성원 부분)을 잘라냄으로써 멀티캐스트 트리를 구성한다. + - 리프 노드 레벨에서 라우터들은 IGMP 프로토콜을 이용하여 구성원들의 정보를 수집한 네트워크의 연결을 실행한다. + - 네트워크의 부모 라우터는 발신지 라우터로부터 얻은 역 최단경로 트리를 이용하여 정보를 상단에 퍼트인다. + - 라우터가 그룹 구성원에 관련된 모든 정보를 수신하면 어느 인터페이스가 제거되어야 할지 알 수 있다. + +image + +--- + +## MOSPF(Multicast Open Shortest Path First) + +- MOSPF는 유니캐스트 라우팅에 사용되는 OSPF 프로토콜의 확장 형태이며, 발신지 기반 트리 기법을 사용하여 멀티캐스트 한다. +- 각 라우터는 최단경로 트리를 생성하기 위한 LSDB를 가진다. + +라우터는 다음과 같은 과정을 통하여 발신지 S로부터 수신하는 멀티캐스트 패킷을 전달하고, 목적지 G까지 전송한다(그룹 내의 수신자). + +1. 라우터는 다익스트라 알고리즘으로 최상위 노드로부터 최단거리 경로트리를 S에서 생성하고, 인터넷 내의 모든 목적지를 리프 노드로 사용한다. + - 트리의 루트는 패킷의 발신지 주소 내에 정의된 패킷의 발신지가 된다. + - 인터넷 전체 토폴로지 정보를 가진 LSDB가 있기에 이러한 트리를 만들 수 있다. + - 각 발신지에 대해 각각 다른 트리를 생성해야한다. + +2. 라우터는 첫 번째 단계에서 생성된 최단 경로 트리에서 자기 자신을 찾는다. + - 즉, 라우터는 자기 자신을 서브트리의 루트로써 최단경로 서브트리에 생성한다. + +3. 최단경로 서브트리에서 라우터는 서브트리의 루트로서, 모든 네트워크는 리프노드로서 브로드캐스트한다. + - 라우터는 DVMRP에서와 유사하게 브로트캐스트 트리의 노드를 제거하여 멀티캐스트 트리로 변경한다. + - 리프 레벨의 정보를 찾기 위해 IGMP를 사용한다. + - MOSPF는 멤버가 모든 라우터에 새로운 링크상태 갱신 패킷을 추가하도록 하였기 때문에, 정보를 주고받아 브로트캐스트 트리를 멀티캐스트 트리를 만들 수 있다. +4. 라우터는 멀티캐스트 트리의 가지에 해당하는 인터페이스로부터 전달받은 패킷을 전송할 수 있다. + - 멀티캐스트 패킷의 사본은 활성화된 멤버가 있는 그룹의 네트워크에 모두 도달해야한다. + +image + +--- +참고 +- [데이터 통신과 네트워킹 6판](https://product.kyobobook.co.kr/detail/S000001693780) +- https://www.slideshare.net/SubhajitSahu/distance-vector-multicast-routing-protocol-dvmrp-combined-presentation +- https://www.cs.emory.edu/~cheung/Courses/558/Syllabus/14-Multicast/RPB.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\354\204\234\353\270\214\353\204\267.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\354\204\234\353\270\214\353\204\267.md" new file mode 100644 index 00000000..b9638e06 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L2\342\200\205internet\342\200\205layer/\354\204\234\353\270\214\353\204\267.md" @@ -0,0 +1,62 @@ +--- +title: '서브넷' +lastUpdated: '2024-03-02' +--- + +- 흔히 사용되는 IPv4 주소 체계는 클래스를 나누어 IP를 할당한다. +- 하지만 이 방식은 매우 비효율적이다. 예를 들어 어떤 기관에 A 클래스를 할당한다고 하면 16,777,214개의 호스트를 할당할 수 있게 되는데, 이 기관이 100개의 호스트를 할당할 때 16,777,114개의 호스트는 그대로 낭비된다. +- 이러한 비효율성을 해결하기 위해 네트워크 장치들의 수에 따라 효율적으로 사용할 수 있는 서브넷(subnet)이 등장하게 되었다. + +- 서브넷은 IP 주소에서 네트워크 영역을 부분적으로 나눈 부분 네트워크를 뜻한다. 이러한 서브넷을 만들 때 사용되는 것이 바로 서브넷 마스크이다. +- 즉, 서브넷 마스크는 IP 주소 체계의 Network ID와 Host ID를 분리하는 역할을 한다. + +image + +### 서브넷 마스크(subnet mask) + +서브넷팅을 자세히 알아보기 전에 기본 서브넷 마스크(Default Subnet mask)에 대해 알아보자. + +image + +- 각 클래스마다 기본 서브넷 마스크는 위 표와 같다(D, E클래스는 사용하지 않음). 이러한 기본 서브넷 마스크를 이용하면 IP 주소의 Network ID와 Host ID를 구분할 수 있다. +- IP주소에 서브넷 마스크를 AND 연산하면 Network ID가 된다. + +- 예를 들어 C클래스인 `192.168.32.0` 이라는 IP주소가 있다고 하자. + - C클래스의 기본 서브넷 마스크는 `255.255.255.0` 이므로 AND연산을 하면 `192.168.32.0`이 나오고 이것이 바로 **Network ID**이다. + - 이때 서브넷 마스크의 Network ID부분은 1이 연속적으로 있어야 하고 Host ID부분은 0이 연속적으로 있어야 한다. + +- 예시의 IP주소를 보면 `192.168.32.0/24` 처럼 `/24` 같은 표시가 붙어있는 것을 확인할 수 있다. + - 이러한 표기법을 슬래시 표기법이라 부른다. + - 이것은 서브넷 마스크의 bit 수(왼쪽에서부터 1의 개수)를 나타낸다. + - 즉 `/24`는 해당 IP의 서브넷 마스크의 왼쪽에서부터 24개가 1이라는 것을 의미한다. + +### 서브넷팅(subnetting) + +- 서브넷팅은 IP 주소 낭비를 방지하기 위해 원본 네트워크를 여러개의 서브넷으로 분리하는 과정을 뜻한다. +- 서브넷 마스크의 bit 수를 증가시키는 것이라고 생각하면 된다. +- 서브넷마스크의 bit수를 1씩 증가시키면 할당할 수 있는 네트워크가 2배수로 증가하고 호스트 수는 2배수로 감소한다. + +**예제** + +- C클래스인 `192.168.32.0/24`를 서브넷 마스크의 bit수를 1 증가시켜서 `192.168.32.0/25`로 변경하는 상황을 살펴보자. + + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/10abe6de-49ba-4465-b1a4-be5c0a07663e) + +- `192.168.32.0/24`는 원래 하나의 네트워크였다. 이때 할당 가능한 host의 수는 `2^8-2 = 254`개이다. + - 여기서 2개를 빼는 이유는 첫번째 주소인 192.168.32.0은 Network Address로 쓰이고 마지막 주소인 192.168.32.255는 Broadcast로 쓰이기 때문에 호스트에 할당할 수 없기 때문이다. + +- 이 때 서브넷 마스크의 bit 수를 1 증가시켜서(서브넷팅) `192.168.32.0/25`로 변경하게 되면 Network ID부분을 나타내는 부분이 24비트에서 25비트로 증가하고 Host ID를 나타내는 부분이 8개 비트에서 7개 비트로 줄어든다. + - 즉 할당 가능한 네트워크 수가 2개로 증가하고 각 네트워크(서브넷)당 할당가능한 호스트수는 2^7-2 = 126개로 줄어든다. 또한 서브넷 마스크가 `255.255.255.128`로 변한 것을 확인할 수 있다. + +- 정리하자면 다음과 같다. + + - 192.168.32.0 : 서브넷1의 Network Address + - 192.168.32.1 ~ 192.168.32.126 : 서브넷1의 host 할당 가능한 부분 + - 192.168.32.127 : 서브넷1의 Broadcast Address + - 192.168.32.128 : 서브넷2의 NetworkAddress + - 192.168.32.129 ~ 192.168.32.254 : 서브넷2의 host 할당 가능한 부분 + - 192.168.32.255 : 서브넷2의 Broadcast Address + +--- +reference +- https://code-lab1.tistory.com/34 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/4\352\263\204\354\270\265\342\200\205\354\236\245\353\271\204.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/4\352\263\204\354\270\265\342\200\205\354\236\245\353\271\204.md" new file mode 100644 index 00000000..471228a5 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/4\352\263\204\354\270\265\342\200\205\354\236\245\353\271\204.md" @@ -0,0 +1,69 @@ +--- +title: '4계층 장비' +lastUpdated: '2024-03-02' +--- + +- 4계층 장비는 TCP와 같이 4계층 헤더에 있는 정보를 기반으로 동작한다. +- 4계층 이상에서 동작하는 로드 밸런서, 방화벽과 같은 장비를 세션 장비라 부른다. + +- 세션 장비에서 고려해야할 요소는 다음과 같다. + - **세션 테이블** + - 세션 장비는 세션 테이블 기반으로 운영된다. + - 세션 정보를 저장, 확인하는 작업 전반을 이해해야 한다. + - 세션 정보는 세션 테이블에 남아 있는 라이프타임이 존재한다. + + image + + - **Symmetric(대칭)경로 요구** + - Inbound와 Outbound 경로가 일치해야 한다. + - 세션 동기화 또는 터널링(경로 보정)을 통해 해결할 수 있다. + + image + + - **정보 변경(로드 밸런서의 경우)** + - IP주소가 변경되며 확장된 1.7 로드 밸런서(ADC)는 애플리케이션 프로토콜 정보도 변경된다. + +이 밖에도 세션 장비의 여러 요소가 서비스에 영향을 미치기 때문에 네트워크 중간 위치에 세션을 기반으로 동작하는 방화벽, NAT, 로드밸런서와 같은 장비가 있다면 네트워크 인프라 뿐 아니라 시스템 설계와 애플리케이션 개발에도 세션 장비 고려가 필요하다. + +### 로드 밸런서 + +- 로드밸런서 - 서버나 장비의 부하를 분산하기 위해 사용하는 장비 +- 4계층 이상에서 동작하면서 트래픽을 분산해주며 IP주소, 4계층 정보, 애플리케이션 정보를 확인 및 수정해주는 기능이 있다. 그래서 가장 많이 쓰이는 곳이 웹 서버 부하 분산이다. +- 이러한 로드밸런서는 웹이나 애플리케이션 뿐아니라 FWLB(FireWall Load Bancing: 방화벽 로드밸런싱), VPNLB(VPN Load Balancing: VPN 로드 밸런싱)와 같이 다양한 서비스를 위해 사용될 수 있다. +- 로드밸런서는 동작하는 계층에 따라 4계층과 7계층으로 나뉜다. + +### L4 스위치 + +- 용어 그대로 4계층에서 동작하면서 로드 밸런서 기능이 있는 스위치이다. +- 내부 동작 방식은 4계층 로드밸런서지만 외형은 스위치처럼 여러 포트를 가지고 있다. +- 다양한 네트워크 구성이 가능한 스위치형 로드 밸런서가 가장 대중화되어있다. +- 부하 분산, 성능 최적화, 리다이렉션 기능을 제공한다. + image + +- L4스위치가 동작하려면 다음 4가지를 설정해야 한다. + - 가상 서버(Virtual Server) : 사용자가 바라보는 실제 서비스 + - 가상 IP(Virtual IP) : 사용자가 접근해야 하는 서비스 IP 주소 + - 리얼 서버(Real Server) : 실제 서비스를 수행하는 서버 + - 리얼 IP(Real IP) : 실제 서버의 IP + +사용자는 L4스위치의 가상 IP를 목적지로 서비스를 요청하고 L4스위치가 목적지로 설정된 가상 IP를 리얼 IP로 다시 변경해 보내준다. 이 과정에서 부하를 어떤 방식으로 분산할지 결정할 수 있다. + +### ADC(Application Delivery Controller) + +- ADC(Application Delivery Controller)는 애플리케이션 계층에서 동작하는 로드 밸런서다. +- 애플리케이션 프로토콜의 헤더와 내용을 이해하고 동작한다. + - 부하 분산, 정보 수정, 정보 필터링이 가능하다. +- ADC는 이런 동작을 위해 프록시로 동작한다. +- 대부분의 ADC는 L4스위치의 기능을 포함하고 있으며, 다음과 같은 기능을 제공 및 수행한다. + - 로드 밸런싱 기능 + - 페일 오버(Failover, 장애극복 기능) + - 리다이렉션(Redirection) +- 애플리케이션 프로토콜을 이해하고 최적화하는 기능도 제공한다. + - 캐싱(Caching), 압축(Compression), 콘텐츠 변환 및 재작성, 인코딩 변환 +- 플러그인 형태로 보안 강화기능을 추가로 제공해서 다음과같은 기능을 제공한다. + - WAF(Web Application Firewall) 기능 + - HTML, XML 검증 및 변환 수행 + +--- +참고 +- [IT 엔지니어를 위한 네트워크 입문](https://m.yes24.com/Goods/Detail/93997435) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/SO_REUSEADDR.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/SO_REUSEADDR.md" new file mode 100644 index 00000000..f9f7d461 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/SO_REUSEADDR.md" @@ -0,0 +1,34 @@ +--- +title: 'SO_REUSEADDR' +lastUpdated: '2024-03-02' +--- + +> SO_REUSEADDR Specifies that the rules used in validating addresses supplied to bind() should allow reuse of local addresses, if this is supported by the protocol. This option takes an int value. This is a Boolean option + +`SO_REUSEADDR`를 true로 설정한다는 것은, TIME_WAIT 상태의 주소에 바인딩할 수 있도록 한다는 의미이다. 특정 포트의 소켓이 사용 중인 경우(TIME_WAIT 상태)라도 커널에 사용 가능이라고 알려서 reuse할 수 있도록 하는 것이다. 정말 간단하게 예를 들자면, 내 컴퓨터에 8080 포트가 이미 열려있더라도 같은 8080 포트를 listen하는 process를 또 시작할 수 있게 한다는 의미이다. + +이 옵션은 어떤 애플리케이션을 빠르게 종료했다가 다시 시작해야하는 경우에 특히 유용하다. + +TCP 연결의 마지막 과정은 필요한 데이터를 모두 전송했으니, 더 이상 패킷을 전송하지 않겠다! 라고 약속하는 `FIN` 패킷(보통 서버가 보냄)과 `FIN_ACK` 패킷(보통 클라이언트가 보냄_을 전송하는 것이다. 그렇기 때문에 요청을 처리할 서버 애플리케이션이 내려갔다고 하더라도, `FIN_ACK`를 받기 위해 커널이 해당 소켓(포트)을 잠시 점유하고 있어야 한다. + +이 때 만약 애플리케이션을 닫은 다음 바로 다시 시작하려고 하면, 해당 port가 이미 binding 되어있다는 이유로 실패하게 될 것이다. 이렇게 되면 이전에 있던 애플리케이션이 내려갔지만, 소켓을 점유하고 있는 동안은 사용자의 요청을 전혀 처리할 수 없게 된다. 하지만 `SO_REUSEADDR`를 설정하면 포트를 동일하게 사용할 수 있기 때문에 새 애플리케이션을 조금 더 빨리 시작할 수 있다. + +이 밖에도 두 개 이상의 IP 주소를 갖는 호스트에서 IP 주소별로 서버를 운용할 경우에도 이 옵션을 사용하면 사용 중인 포트에 대해서도 소켓을 성공적으로 주소에 연결(bind)할 수 있다. 멀티캐스팅 응용 프로그램이 동일한 포트를 사용할 때도 이 옵션을 활용한다. + +### 주의할 점 + +SO_REUSE ADDR을 설정하는 것은, 약간의 모호함을 동반한다. TCP packet의 header가 완전히 unique한 것이 아니어서 그 패킷이 이미 죽은 listener를 향한 패킷인지, 아니면 새 listene를 향한 메시지인지 완전히 구분하는 것이 힘들기 때문이다. + +보통은 패킷이 동일한 연결에 속한것인지 확인하기 위해 `4-Tuple`(송신/수신 IP 주소, 포트번호)를 사용하지만, 중복 패킷이 오거나 요청이 같은 튜플로 여러개 들어오면 식별이 불가능해져서 일부 정보가 제대로 처리되지 못할 수도 있다. + +결국 TIME_WAIT(애플리케이션은 죽었지만 포트를 점유중인 상태) 기간동안 기다리느냐, 혹은 일부 데이터를 lost할 가능성을 감당하느냐 사이의 선택이 되는데, 대부분의 서버 프로그램은 수신 연결을 놓치지 않도록 서버를 즉시 복구하는 것이 좋다고 판단하고 이러한 위험을 감수하는 경우가 많다고는 한다. (패킷이 손실되는 경우가 매우 드물기도 하다.) + +Nginx Blue/Green 배포같은거 사용하면 port reuse 설정을 할 필요 없이 더 효율적인 무중단 배포를 할 수 있곘지만, 패킷 구분에 있어서는 동일한 문제가 발생하긴 할 것 같다. + + +--- + +참고 + +- http://www.unixguide.net/network/socketfaq/4.11.shtml +- https://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/SSH\342\200\205config.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/SSH\342\200\205config.md" new file mode 100644 index 00000000..0823dddf --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/SSH\342\200\205config.md" @@ -0,0 +1,37 @@ +--- +title: 'SSH config' +lastUpdated: '2024-03-02' +--- + +ssh 명령어로 항상 같은 서버에 접속할 때, 매번 pem, host를 지정하는 것이 귀찮다면 설정에 등록해서 사용해보자 + +```bash +sudo vi /etc/ssh/ssh_config +``` + +이런식으로 밑에 추가해주면 된다. + +```bash +# dev +Host dev + HostName x.x.x.x + User ec2-user + IdentityFile /user/rlaisqls/... + +# ops +Host ops + HostName x.x.x.x + User ec2-user + IdentityFile /user/rlaisqls/... +``` + +- Host: 이름 +- HostName: 연결될 서버 호스트 명 (미 설정시 Host값이 HostName으로 사용됨) +- User: 네트워크 커넥션에 사용되는 계정 명 +- IdentityFile: 키 파일 위치 + +이런식으로 바로 접속해줄 수 있다. + +```bash +$ ssh dev +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/Sticky\342\200\205Session\352\263\274\342\200\205Session\342\200\205Clustering.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/Sticky\342\200\205Session\352\263\274\342\200\205Session\342\200\205Clustering.md" new file mode 100644 index 00000000..d005ad11 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/Sticky\342\200\205Session\352\263\274\342\200\205Session\342\200\205Clustering.md" @@ -0,0 +1,29 @@ +--- +title: 'Sticky Session과 Session Clustering' +lastUpdated: '2024-03-02' +--- + +Load balancing이란 대용량 트래픽을 장애 없이 처리하기 위해 여러 대의 서버에 적절히 트래픽을 분배하는 것이다. 이러한 서비스에서는 세션 관리에 문제가 생길 수 있다. + +예를 들어, 로그인 정보를 한 서버의 세션에 저장했는데 다음 요청을 다른 서버에 보내는 경우에 로그인 정보가 사라지는 문제가 생긴다. 이러한 문제를 해결하기 위해 특정 클라이언트의 요청을 처음 처리한 서버로만 보내는 것을 **Sticky Session**이라 말한다. + +일반적으로 특정 서버로 요청 처리를 고정시키는 방법은 Cookie를 사용하거나 클라이언트의 IP tracking 하는 방식이 있다. AWS ELB는 cookie를 사용하여 Http Response에 cookie를 이용해서 해당 클라이언트가 가야하는 EC2 instance의 정보를 저장해두고, 그걸 활용하여 특정 EC2로 요청을 고정한다. + +Sticky Session의 단점은 이러한 것들이 있다. +- 로드밸런싱이 잘 동작하지 않을 수 있다 +- 특정 서버만 과부하가 올 수 있다. +- 특정 서버 Fail시 해당 서버에 붙어 있는 세션들이 소실될 수 있다. + +## Session Clustering + +LB에서 세션문제를 해결하기 위한 다른 방법으론 Session Clustering이 있다. Session Clustering은 여러 서버의 세션을 묵어서 하나의 클러스터로 관리하는 것이다. + +하나의 서버에서 fail이 발생하면 해당 WAS가 들고 있던 세션은 다른 WAS로 이동되어 그 WAS가 해당 세션을 관리하게 된다. + +하지만 이 방식은 scale out 관점에서 새로운 서버가 하나 뜰 때마다 기존에 존재하던 WAS에 새로운 서버의 IP/Port를 입력해서 클러스터링 해줘야 하는 단점이 있다. + +그렇기 때문에 Session server를 Redis로 따로 두고 관리하는 방식도 있다. Redis Session 서버의 중요성이 올라가고, 해당 세션 서버가 죽는 순간 모든 세션이 사라지기 때문에 이 Redis 서버의 다중화도 고려해보아야 한다. + +--- +참고 +- https://aws.amazon.com/ko/blogs/aws/new-elastic-load-balancing-feature-sticky-sessions/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/TCP\354\231\200\342\200\205UDP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/TCP\354\231\200\342\200\205UDP.md" new file mode 100644 index 00000000..b39bf730 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/TCP\354\231\200\342\200\205UDP.md" @@ -0,0 +1,60 @@ +--- +title: 'TCP와 UDP' +lastUpdated: '2024-03-02' +--- +

TCP는 네트워크 계층 중 전송 계층에서 사용하는 프로토콜로서, 장치들 사이에 논리적인 접속을 성립(establish)하기 위하여 연결을 설정하여 신뢰성을 보장하는 연결지향적 프로토콜이다. TCP는 흐름제어, 혼잡제어 등을 통해 네트워크에 연결된 컴퓨터에서 실행되는 프로그램 간에 일련의 옥텟(데이터, 메세지, 세그먼트라는 블록 단위)을 안정적으로, 순서대로, 에러없이 교환할 수 있게 한다.

+ +## TCP의 특징 + ### 연결지향 + - TCP는 통신 과정에서 클라이언트와 서버간에 3-way handshaking으르 통해 연결을 확립한 후에 전송을 시작하기 때문에 두 호스트가 논리적으로 연결되어있음을 보장할 수 있다. + ### 흐름제어(Flow control) + - 흐름제어는 송신측과 수신측의 데이터 처리 속도 차이를 해결하기 위한 흐름제어를 수행한다. 수신자가 데이터를 받을 수 있는 Window의 사이즈를 지정해 수신자가 전송 속도를 제어할 수 있는 매커니즘을 제공하여 송신하는 쪽에서 데이터를 빠르게 전송할 수 있더라도 수신하는 곳에서 문제가 일어나는 것을 방지한다. + ### 혼잡제어(Congestion control) + - 송신측의 데이터 전달과 네트워크의 데이터 처리 속도 차이를 해결하기 위한 혼잡제어를 수행한다. 한 라우터에 데이터가 몰려 지연이 생길때, 호스트들이 계속해서 데이터를 재전송하는 경우 혼잡이 더 가중되기 때문에 네트워크의 혼잡을 피하기 위해 송신측에서 보내는 데이터의 전송속도를 강제로 줄이는 작업을 의미한다. + ### 신뢰성이 높은 전송(Reliable transmission) + - ACK 값이 정상적으로 전송되는 경우에만 연결이 확립된 것으로 간주하여 신뢰성 높은 전송이 이뤄질 수 있도록 한다. + ### 전이중, 일대일(Unicast) 통신 + - 전이중 (Full-Duplex)
+ 전송이 양방향으로 동시에 일어날 수 있다. + - 일대일(Unicast)
+ 각 연결이 정확히 2개의 종단점을 가지고 있다. + +## TCP 헤더 + + + +|필드|내용| +|-|-| +|발신지, 목적지의 포트 번호|송,수신 프로세스에 할당되는 포트 주소| +|시퀀스 번호(Sequence Number)|연속된 데이터의 순서 번호| +|응답 번호(ACK Number)|수신 프로세스가 제대로 수신한 바이트의 수를 응답하기 위해 사용| +|데이터 오프셋(Data Offset)|데이터가 시작되는 위치가 어디부터인지를 표시 | +|예약 필드(Reserved)|나중을 위해 예약해놓은 필드| +|제어 비트(Flag Bit)|SYN, ACK, FIN 등의 제어 번호, 3-way handshaking에 사용| +|윈도우 크기(Window)|수신 윈도우의 버퍼 크기를 지정할 때 사용(흐름제어를 위함)| +|체크섬(Checksum)|송신하는 중에 발생할 수 있는 오류를 검출하기 위한 값| +|긴급 포인터(Urgent Pointer)| URG 플래그 비트가 1인 경우 이 포인터에 있는 데이터를 우선처리함| + + +# UDP(User Datagram Protocol) +

UDP는 전송계층의 비연결지향적 프로토콜이다. 비연결지향적이란 데이터를 주고받을 때 연결 절차를 거치지 않고 발신자가 일방적으로 데이터를 발신하는 방식을 의미한다. 연결 과정이 없기 때문에 TCP보다는 빠른 전송을 할 수 있지만 데이터 전달의 신뢰성은 떨어진다.

+ +## UDP의 특징 + ### 비연결지향 + - 수신자가 데이터를 수신했는지 확인하거나 통신 과정에서의 에러를 검출하는 과정 없이 일방적으로 데이터를 전송한다. + ### 낮은 신뢰성 + - 중간에 패킷이 유실되었거나 순서가 섞여도 에러를 검출하고 복구하는 작업을 수행하지 않는다. + ### 빠른 전송 + - 확인 절차가 없고 전송하는 데이터의 약이 적기 때문에 데이터를 빠르게 전송할 수 있다. + ### 일대다(Broadcast), 다대다(Multicast) 통신 + - 신뢰성은 적지만 데이터를 빠르게 전송할 수 있다는 특징 때문에 브로드캐스트와 멀티캐스트에 주로 이용된다. + + +## UDP 헤더 + + +|필드|내용| +|-|-| +|발신지, 목적지의 포트 번호|송,수신 프로세스에 할당되는 포트 주소| +|데이터 길이(Total Length)|전송하는 데이터의 길이| +|체크섬(Checksum)|송신하는 중에 발생할 수 있는 오류를 검출하기 위한 값| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/\354\240\204\354\206\241\352\263\204\354\270\265\342\200\205\355\224\204\353\241\234\355\206\240\354\275\234.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/\354\240\204\354\206\241\352\263\204\354\270\265\342\200\205\355\224\204\353\241\234\355\206\240\354\275\234.md" new file mode 100644 index 00000000..ee22e175 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L3\342\200\205transport\342\200\205layer/\354\240\204\354\206\241\352\263\204\354\270\265\342\200\205\355\224\204\353\241\234\355\206\240\354\275\234.md" @@ -0,0 +1,42 @@ +--- +title: '전송계층 프로토콜' +lastUpdated: '2024-03-02' +--- + +전송계층(Transport Layer)에서는 데이터 전송 보장(Reliable data transfer)을 위해 다양한 방식의 프로토콜을 사용하고 있다. + +그 중 Stop-And-Wait, Go-Back-N, Selective Repeat 프로토콜에 대해 알아보자. + +## Stop-And-Wait(정지 대기 방식) + +- 컴퓨터 네트워크 설정에서 재전송을 기반으로 하는 신뢰적인 데이터 전송 프로토콜중 하나가 ARQ(Automatic Repeat Request)프로토콜인데, Stop-And-Wait는 이 ARQ방식의 일종이다. +- 송신측 A가 B에게 1개의 프레임을 송신하게 되면 B는 해당 프레임의 에러 유무를 판단하여 A에게 ACK혹은 NAK를 보내게 된다. +- Stop-And-Wait 방식의 경우 구현 방식이 단순하며 송신측내에 양쪽의 슬라이딩 윈도우가 1칸이라는 특징이 있다. +- 하지만 송신측이 ACK혹은 NAK를 받을 때까지 다음 프레임을 받을 수 없으므로 전송효율이 떨어진다. + + image + +## Go-Back-N + +- 송신자가 확인응답을 기다리지 않고 여러 패킷을 전송할 수 있다. +- n번 패킷에 대한 ACK는, `0`번 부터 `n-1`번까지의 패킷을 모두 잘 받았으니 `n`번 패킷을 보내달라는 의미이다. +- 송신자 슬라이딩 윈도우의 최대 크기는 sequence number의 bit가 `m` bit일 때 `(2^m)-1`이다. (수신자의 버퍼는 여전히 1이다.) +- 위의 그림의 경우 Windows Size가 7이므로 0~6까지 7개의 프레임을 보낼 수 있으며 2, 3, 4를 보내고 있는 와중에 0과 1에 대한 ACK가 왔기 때문에 Window size에 맞게 옆으로 두 칸 더 늘린 모습을 확인할 수 있다. +- 패킷 하나의 오류 때문에 많은 패킷들을 재전송하는 경우가 발생할 수 있다. + + image + +## Selective Repeat ARQ (선택적 방법) + +- Selective Repeat ARQ는 수신자에게 오류가 발생된 수신 패킷만을 다시 전송하여 불필요한 재전송을 피한다. +- `n`번 패킷에 대한 ACK는 `n`번 패킷을 잘 받았다는 의미이다. +- `n`번 패킷에 대한 NAK는 `n`번 패킷을 받지 못했다는 의미이다. +- 송,수신자 슬라이딩 윈도우의 최대 크기는 sequence number의 bit가 `m` bit일 때 `2^(m-1)`이다. +- 데이터 한 개가 유실이 되었는데 다음 데이터가 올 경우 GBN은 그냥 버리지만 Selective Repeat는 이를 저장해놓을 수 있다. + + image + +--- +참고 +- [데이터통신과 네트워킹: TCP/IP 프로토콜 기반 6판](https://product.kyobobook.co.kr/detail/S000001693780) +- https://blog.naver.com/no5100/220735525591 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/ACME.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/ACME.md" new file mode 100644 index 00000000..39276a57 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/ACME.md" @@ -0,0 +1,84 @@ +--- +title: 'ACME' +lastUpdated: '2024-03-02' +--- + +ACME is a protocol that makes it possible to automate the issuance and renewal of certificates, all withour human interaction. + +The Internet Sercurity Research Group(ISRG) initially designed the ACME protocol for its own certificate service, [Let's Encrypt](https://letsencrypt.org/), a free and open certificate authority (CA) that provides domain validated (DV) certificates at no charge. Today various other CAs, PKI vendors, and browsers support ACME to support different types of certificates. + +### How does the protocol work? + +By leveragin ACME, organitations can streamline and automate otherwise time-consuming processes, such as CSR goneration, domain ownership verification, certificate issuance, and installation. + +ACME is primatly used to obtain DV certificates. This is because DV certificates donot require advanced verification. Only the existence the domain is validatessd, which requires no thman intervention. + +The protocol can also be used to obtain higher-value certifications, such as organization validated (OV) and extended validation (EV), but these cases require additional support mechanisms alongside the ACME agent. + +The objective of ther ACME protocol is to set up an HTTPS server and automate the provisioning of trusted certificates and eliminate any error-prone manual transactions. To use the protocol, an ACME client and ECME server are needed, which communicate with JSON messages over a secure HTTPS connection. + +- The client runs on any server or device that requires a trusted SSL/TLS certificate. It is used to request certificate management actions, such a s issuance or revocation. +- The server runs at a Certificate Authority (CA), like Let's Encrypt, and respond to the requests of authorized clients. + +There are many different ACME client implementations available for the protocol. It is designed to allow businesses to choose the CA they want, as long as the CA supports ACME. + +Let's Encrypt recomments using the certbot client, because it's easy to use, it works on many OS, and it has helpful documentation. + +### Setting up an ACME client + +Once you've selected a client, the next step is to install it on the domain/server where the certificates need to be deployed. ACME clients can tun in alost any programming language and environment, and the setup process consistes of just 5 straightforward steps to complete: + +1. The client prompts to enter the domain to be managed. +2. The client offers a list of Certificate Authorities (CA) supporting the protocol. +3. The client contacts the selected CA and generates an authorization key pair. +4. The CA issues DNS or HTTPS challenges for the client to demonstrate control over their domain(s). +5. The CA sends a nonce – a randomly generated number – for the agent to sign with its private key to demonstrate ownership of the key pair. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/f919f6b5-08fa-4a0b-8ba9-5dce34ca0bfb) + +### Using ACME to deploy and manage certificates + +Issuing and renewing certificates using the ACME protocol is simple. The client simply sends certificate management requests and signs them with the authorized key pair. + +**Issuance/renewal:** a web server with the ACME agent installed generates a CSR, sends it to the CA, and the CA issues it. The process for issuance and renewal works similarly: + +1. The agent sends to the CA a Certificate Signing Request (CSR) requesting the issuance of a certificate for the authorized domain with a specified public key. +2. The CSR is signed with the corresponding private key and the authorized key for the domain. +3. When the CA receives the request, it verifies both signatures, issues a certificate for the authorized domain with the public key from the CSR, and returns it to the agent. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/e592874d-fdad-43ec-8d7c-0c0a6e2684dc) + +**Revocation:** to revoke a certificate, the agent signs the revocation request with the authorized key pair for the domain, and the CA validates the request. The CA then publishes the revocation information through CRLs or OCSP to prevent the acceptance of the revoked certificate. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/0da73283-e653-4db3-a346-3facd687329b) + +### Solving Challenges + +In order for the ACME CA server to verity that a client owns the domains, a certificate is being requested for, the client must complete "challenges". This is to ensure clients are unable to request certificates for domains they do not own and as a result, fraudulently impersonate another's site. As detailed in the [RFC8555](https://tools.ietf.org/html/rfc8555), cert-manager offers two challenge validations - HTTP01 and DNS01 challenges. + +**HTTP01** challenges are completed by presenting a computed key, that sould be present at a HTTP URL endpoint and is routable over the internet. This URL will use the domain name requested for the certificate. Once the ACME server is able to get this key from this URL over the internet, the ACME server can validate you are the owner of this domain. When a HTTP01 challenge is created, cert-manager will automatically configure your cluster ingress to route traffic for this URL to a small web server that presents this key. + +- Advantages: + - Automate easily without additional knowledge of domain configuration. + - Hosting providers can issue certificates for the CNAME domain. + - Works with commercial web servers. +- Disadvantages: + - When an ISP blocks port 80 it doesn't work (rarely happens on some home ISPs). + - Let's Encrypt cannot use this task to issue wildcard certificates. + - If you have multiple Web servers, you must ensure that files are available on all servers. + +**DNS01** challenges are complated by providing a computed key that is present at a DNS TXT record. Onve this TXT record has been propagated across the internet, the ACME server cna successfully retrive this key via a DNS lookup and can validate that the client owns the domain for the requested certificate. With the correct permissions, cert-manager will automatically present this TXT record for your given DNS provider. + +- Advantages: + - You can use this way to issue a certificate that contains the wildcard domain name. + - It works well with multiple web servers. +- Disadvantages: + - Maintaining API credentials on a web server is risky. + - The DNS provider might not provide the API. + - The DNS API may not provide information about propagation time. + +--- +reference +- https://datatracker.ietf.org/doc/html/rfc8555 +- https://www.keyfactor.com/blog/what-is-acme-protocol-and-how-does-it-work/ +- https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/CDN.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/CDN.md" new file mode 100644 index 00000000..36e7d430 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/CDN.md" @@ -0,0 +1,54 @@ +--- +title: 'CDN' +lastUpdated: '2024-03-02' +--- + +CDN은 지리적으로 분산된 여러 개의 서버이다. **네트워크 설계로 인해 발생하는 통신 대기, 지연시간을 줄이는 것**을 목적으로 한다. + +CDN은 사용자가 전세계에 있더라도 가까운 CDN 서버에 요청을 보내 빠르게 응답을 받을 수 있고, 한 서버에 장애가 생겼을떄 다른 서버를 사용하여 서비스를 그대로 응답받을 수도록 한다. 또한, 클라이언트와 웹 사이트 서버 간에 중간 서버를 두어 효율성을 높이고, 클라이언트-서버 통신의 일부를 관리한다. 웹 서버에 대한 웹 트래픽을 줄이고, 대역폭 소비를 줄이며, 애플리케이션의 사용자 환경을 개선한다. + +## 장점 + +- 페이지 로드 시간 단축 + 페이지 로드 시간이 너무 느리면 웹 사이트 트래픽이 감소할 수 있다. CDN은 반송률을 줄이고 사용자가 사이트에서 보내는 시간을 늘릴 수 있다. + +- 대역폭 비용 절감 + 웹 사이트 요청은 높은 비용의 네트워크 대역폭을 사용한다. **캐싱 및 기타 최적화**를 통해 CDN은 오리진 서버가 제공해야 하는 데이터의 양을 줄여 웹 사이트 소유자의 호스팅 비용을 절감할 수 있다. + +- 콘텐츠 가용성 제고 + 한 번에 너무 많은 방문자가 방문하거나 네트워크 하드웨어 오류가 발생하면 웹 사이트가 중단될 수 있다. CDN 서비스는 더 많은 웹 트래픽을 처리하고 웹 서버의 로드를 줄일 수 있다. 또한 하나 이상의 CDN 서버가 오프라인으로 전환되면 다른 운영 서버가 해당 서버를 대체하여 서비스가 중단되지 않도록 할 수 있다. + +- 웹 사이트 보안 강화 + 분산 서비스 거부(DDoS) 공격은 대량의 가짜 트래픽을 웹 사이트로 전송하여 애플리케이션이 작동 중지되도록 만드는 것이 못적인데, CDN은 여러 중간 서버 간에 로드를 분산하여 오리진 서버에 미치는 영향을 줄임으로써 이러한 트래픽 급증을 처리할 수 있다. + +## 작동 + +콘텐츠 전송 네트워크(CDN)는 여러 지리적 위치에 접속 지점(POP) 또는 CDN 엣지 서버 그룹을 설정하는 방식으로 작동한다. 지리적으로 분산된 이 네트워크는 캐싱, 동적 가속 및 엣지 로직 계산의 원리를 기반으로 움직인다. + +### 캐싱 + +캐싱은 더 빠른 데이터 액세스를 위해 동일한 데이터의 여러 복사본을 저장하는 프로세스이다. 컴퓨팅에서 캐싱의 원리는 모든 유형의 메모리 및 스토리지 관리에 적용된다. CDN 기술에서 이 용어는 네트워크의 여러 서버에 정적 웹 사이트 콘텐츠를 저장하는 프로세스를 의미한다. CDN에서 캐싱은 다음과 같이 작동한다. + +1. 지리적으로 멀리 떨어진 웹 사이트 방문자가 사이트에서 정적 웹 콘텐츠를 요청한다. +2. 요청이 웹 애플리케이션 서버 또는 오리진 서버에 도달한다. 오리진 서버는 원격 방문자에게 응답을 보낸다. 또한 해당 방문자와 지리적으로 가장 가까운 CDN POP에 응답 복사본을 보낸다. +3. CDN POP 서버는 복사본을 캐싱된 파일로 저장한다. +4. 다음에 해당 방문자 또는 해당 위치에 있는 다른 방문자가 동일한 요청을 하면, 오리진 서버가 아닌 캐싱 서버가 응답을 보냅니다. + +### 동적 가속 + +동적 가속은 웹 애플리케이션과 클라이언트 사이의 중개 CDN 서버로 인해 발생하는 동적 웹 콘텐츠 요청에 대한 서버 응답 시간을 단축하는 것이다. 사용자 요청이 있을 때마다 콘텐츠가 변경될 수 있기 때문에 동적 웹 콘텐츠에서는 캐싱이 제대로 작동하지 않는다. CDN 서버는 모든 동적 요청에 대해 오리진 서버와 다시 연결해야 하지만 자신과 오리진 서버 간의 연결을 최적화하여 프로세스를 가속화한다. + +클라이언트가 인터넷을 통해 웹 서버로 직접 동적 요청을 보내는 경우 네트워크 지연 시간으로 인해 요청이 손실되거나 지연될 수 있다. 반면, 근처의 CDN 서버가 요청을 오리진 서버로 전달할 경우, 신뢰할 수 있는 지속적인 연결이 이미 설정되었을 것이다. 다음과 같은 기능들을 통해 이들 간의 연결을 더욱 최적화할 수 있다. + +- 지능형 라우팅 알고리즘 +- 오리진에 대한 지리적 근접성 + +### 엣지 로직 계산 + +CDN Edge 서버가 클라이언트와 서버 간의 통신을 단순화하는 논리적 계산을 수행하도록 프로그래밍할 수 있다. 예를 들어 이 서버는 다음을 수행할 수 있다. + +- 사용자 요청을 검사하고 캐싱 동작을 수정한다. +- 잘못된 사용자 요청을 확인하고 처리한다. +- 응답하기 전에 콘텐츠를 수정하거나 최적화한한다. + +웹 서버와 네트워크 Edge 간에 애플리케이션 로직을 배포하면 개발자가 오리진 서버의 컴퓨팅 요구 사항을 오프로드하고 웹 사이트 성능을 높일 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/DHCP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/DHCP.md" new file mode 100644 index 00000000..f5a04aa1 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/DHCP.md" @@ -0,0 +1,99 @@ +--- +title: 'DHCP' +lastUpdated: '2024-03-02' +--- + +The **Dynamic Host Configuration Protocol (DHCP)** is a network management protocol used on Internet Protocol (IP) networks for automatically assigning IP addresses and other communication parameters to devices connected to the network using a client–server architecture. + +The technology eliminates the need for individually configuring network devices manually, and consists of two network components, a centrally installed network DHCP server and client instances of the protocol stack on each computer or device. When connected to the network, and periodically thereafter, a client requests a set of parameters from the server using DHCP. + +DHCP services exist for networks running Internet Protocol version 4 (IPv4), as well as version 6 (IPv6). The IPv6 version of the DHCP protocol is commonly called DHCPv6. + +## History + +The **Reverse Address Resolution Protocol (RARP)** was defined in 1984 for the configuration of simple devices, such as diskless workstations, with a suitable IP address. Acting in the data link layer, **it made implementation difficult on many server platforms**. It required that a server be present on each individual network link. + +**DHCP** was first defined in October 1993. It is based on [BOOTP](https://en.wikipedia.org/wiki/Bootstrap_Protocol), but can dynamically allocate IP addresses from a pool and **reclaim them when they are no longer in use**. It can also be used to deliver a wide range of extra configuration parameters to IP clients, including platform-specific parameters. + +## Terms + +- DHCP client + - A DHCP client is an Internet host using DHCP to obtain configuration parameters such as a network address. +- DHCP server + - A DHCP server is an Internet host that returns configuration parameters to DHCP clients. +- BOOTP relay agent + - A BOOTP relay agent or relay agent is an Internet host or router that passes DHCP messages between DHCP clients and DHCP servers. DHCP is designed to use the same relay agent behavior as specified in the BOOTP protocol specification. + - When a DHCP request enters the router by setting up a DHCP Relay Agent at the router stage, the router can convert it to unicast and send packets to the DHCP server. +- binding + - A binding is a collection of configuration parameters, including at least an IP address, associated with or "bound to" a DHCP client. Bindings are managed by DHCP servers. + +## Methods + +Depending on implementation, the DHCP server may have three methods of allocating IP addresses: + +- **Dynamic allocation** + A network administrator reserves a range of IP addresses for DHCP, and each DHCP client on the LAN is configured to request an IP address from the DHCP server during network initialization. The request-and-grant process uses a lease concept with a controllable time period, allowing the DHCP server to reclaim and then reallocate IP addresses that are not renewed. +- **Automatic allocation** + The DHCP server permanently assigns an IP address to a requesting client from a range defined by an administrator. This is like dynamic allocation, but the DHCP server keeps a table of past IP address assignments, so that it can preferentially assign to a client the same IP address that the client previously had. +- **Manual allocation** + - This method is also variously called static DHCP allocation, fixed address allocation, reservation, and MAC/IP address binding. An administrator maps a unique identifier (a client id or MAC address) for each client to an IP address, which is offered to the requesting client. DHCP servers may be configured to fall back to other methods if this fails. + +## Lease + +DHCP's IP allocation is called lease. This lease has a term, which literally refers to the period during which that IP address can be used. That is, at the end of the lease period, the IP address is returned to the DHCP address pool. The lease period is basically 8 days, and you can find and set an appropriate value depending on the location. + +DHCP lease IP by four steps as below description. + +### Discover (c->s) + +- The DHCP client broadcasts a `DHCPDISCOVER` message on the network subnet using the destination address `255.255.255.255` (limited broadcast) or the specific subnet broadcast address (directed broadcast). + +- A DHCP client may also request its last known IP address. If the client remains connected to the same network, the server may grant the request. Otherwise, it depends whether the server is set up as authoritative or not. An authoritative server denies the request, causing the client to issue a new request. A non-authoritative server simply ignores the request, leading to an implementation-dependent timeout for the client to expire the request and ask for a new IP address. + +### Offer (c<-s) + +- When a DHCP server receives a `DHCPDISCOVER` message from a client, which is an IP address lease request, the DHCP server reserves an IP address for the client and makes a lease offer by sending a DHCPOFFER message to the client. + +- This message contains the client's client id (traditionally a MAC address), the IP address that the server is offering, the subnet mask, the lease duration, and the IP address of the DHCP server making the offer. + +- The DHCP server may also take notice of the hardware-level MAC address in the underlying transport layer: according to current RFCs the transport layer MAC address may be used if no client ID is provided in the DHCP packet. + +### Request (c->s) + +- In response to the DHCP offer, the client replies with a `DHCPREQUEST` message, broadcast to the server, a requesting the offered address. A client can receive DHCP offers from multiple servers, but it will accept only one DHCP offer. Before claiming an IP address, the client will broadcast an ARP request, in order to find if there is another host present in the network with the proposed IP address. If there is no reply, this address does not conflict with that of another host, so it is free to be used. + +- The client must send the server identification option in the `DHCPREQUEST` message, indicating the server whose offer the client has selected.[8]: Section 3.1, Item 3  When other DHCP servers receive this message, they withdraw any offers that they have made to the client and return their offered IP address to the pool of available addresses. + +### Acknowledgement (c<-s) + +- When the DHCP server receives the `DHCPREQUEST` message from the client, the configuration process enters its final phase. The acknowledgement phase involves sending a `DHCPACK` packet to the client. + +- This packet includes the lease duration and any other configuration information that the client might have requested. At this point, the IP configuration process is completed. + +- The protocol expects the DHCP client to configure its network interface with the negotiated parameters. + +- After the client obtains an IP address, it should probe the newly received address[8]: sec. 2.2  (e.g. with ARP Address Resolution Protocol) to prevent address conflicts caused by overlapping address pools of DHCP servers. If this probe finds another computer using that address, the computer should send DHCPDECLINE, broadcast, to the server. + +## Renewal + +When a device leases an IP address from a DHCP (Dynamic Host Configuration Protocol) server, it is assigned a lease term, which is the duration for which it can use that IP address. At the end of the lease term, the device is typically required to return the IP address to the DHCP server. + +Returning and releasing the IP address can generate broadcast traffic on the network. This is because the device needs to inform other devices on the network that it is no longer using that IP address, allowing them to update their network configurations accordingly. And renewal process is to allow devices to continue using the same IP address if possible, minimizing disruptions caused by IP address changes. + +Normally, DHCP leases can be renewed before they expire. Typically, a device will attempt to renew its lease when it reaches the halfway point of the lease term. If the renewal is successful, the lease term is reset to its full duration. + +However, if the renewal process fails due to a power-off or another reason, the device will continue using the IP address until approximately 87.5% of the lease term has passed. At this point, the device will make another attempt to renew the lease. The purpose of retrying the renewal at this stage is to ensure that the device has enough time to communicate with the DHCP server and update its lease before the lease actually expires. + +## Realease + +When the lease term ends or if the IP address is no longer needed, it should be returned to the DHCP server, which is known as releasing the IP address. Both the DHCP server and the client keep track of the lease duration, so even if the lease expires and the client is not connected, the server will send the IP address back to the address pool, making it available for reassignment. + +In situations where internet connectivity is not working despite a properly connected Ethernet cable or when the IP address displayed by the `ipconfig` command is in the `169.254.x.x` range with a subnet mask of `255.255.0.0`, the recommended troubleshooting step is to first enter the `ipconfig /release` command in the command prompt and then enter `ipconfig /renew`. The `ipconfig /release` command releases the IP address back to the DHCP server, while `ipconfig /renew` either creates a new lease if no IP address is currently assigned or renews the existing lease if an IP address is already assigned. This process encourages the client to obtain a new IP address if everything is functioning properly. + +If the release process is not followed, DHCP becomes less advantageous compared to using a static IP address, as it introduces more disadvantages. Therefore, the release process is considered more important than the lease creation/renewal process in DHCP environments. + +--- +reference +- https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol +- https://nordvpn.com/blog/dhcp/ +- https://datatracker.ietf.org/doc/html/rfc2131 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/DNS\342\200\205\353\240\210\354\275\224\353\223\234\354\234\240\355\230\225.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/DNS\342\200\205\353\240\210\354\275\224\353\223\234\354\234\240\355\230\225.md" new file mode 100644 index 00000000..dc2a5f50 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/DNS\342\200\205\353\240\210\354\275\224\353\223\234\354\234\240\355\230\225.md" @@ -0,0 +1,15 @@ +--- +title: 'DNS 레코드유형' +lastUpdated: '2024-03-02' +--- + +|레코드|설명| +|-|-| +|A(Host)
주소/호스트 레코드|정규화된 도메인 이름/호스트명(FQDN)을 IPv4에 연결한다.| +|AAAA
주소 레코드|호스트를 IPv6에 연결한다.| +|CNAME(Canonical NAME)
별칭 레코드|실제 호스트명(A레코드)과 연결되는 별칭,별명을 정의 한다.| +|MX(Mail Exchange)
메일 교환 레코드|메일 서버에 도달할 수 있는 라우팅정보(메일서버)를 제공한다.| +|SRV(SeRVice)
서비스 위치 레코드|비슷한 TCP/IP 서비스를 제공하는 다수의 서버 위치 정보를 제공한다.| +|PTR(PoinTeR)
포인터 리소스 레코드|다른 DNS 레코드를 가리킴, 역방향 조회에서 A레코드를 가리킬때 사용한다.| +|SOA(Start Of Authority)
권한시작 레코드|존(Zone)의 기본 속성 정보, DNS영역의 주 DNS 서버를 정의하며 일련번호를 통해 영역의 변경사항을 기록한다. 또한 보조영역의 새로고침 및 다시시도 간격등을 정의하고, 영역의 기본 TTL 값을 정의한다.| +|NS(Name Server)
네임 서버 레코드|영역을 풀이할 수 있는 DNS서버의 목록을 가지고 있다.| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/FTP\342\200\205Active,\342\200\205Passive\342\200\205mode.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/FTP\342\200\205Active,\342\200\205Passive\342\200\205mode.md" new file mode 100644 index 00000000..b76213d6 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/FTP\342\200\205Active,\342\200\205Passive\342\200\205mode.md" @@ -0,0 +1,41 @@ +--- +title: 'FTP Active, Passive mode' +lastUpdated: '2024-03-02' +--- + +image + +FTP는 파일을 전송하기 위해 두 개의 세션을 사용한다. 컨트롤 프로토콜과 데이터 프로토콜이 완전히 분리되어있고, 통신 방법이 다른 두가지 모드를 가지고 있다. + +### Active 모드 + +FTP의 기본적인 구동 방식은 Active 모드이다. 컨트롤 프로토콜을 클라이언트에서 서버로 통신을 시작하고, 데이터 프로토콜은 서버에서 클라이언트 쪽으로 데이터를 푸시하는 방식이다. + +image + +Active 모드의 작동 흐름은 다음과 같다. + +1. 클라이언트가 FTP 서버에 접속. 클라이언트는 1023번 이상의 TCP 포트를, 서버는 21번 포트 사용 +2. 클라이언트가 1025번 포트로 데이터를 수신하겠다 알림 +3. 서버는 송신하겠다고 응답 +4. 서버에서 데이터를 보냄 + +Active 모드를 사용할 때 중간에 방화벽이나 세션 장비가 있으면 동작 방식에 맞춰 반댓방향의 방화벽을 열어줘야 한다. + +특히 NAT 환경인 경우, FTP가 동작하는 프로토콜을 모두 이해할 수 있는 별도 기능을 동작시켜야 한다. 이 기능을 ALG(Application Layer Gateway)라고 한다. ALG가 동작하는 방화벽은 FTP 명령어를 이해하고 반대 방향으로 시작하는 데이터 세션을 인지해 방화벽과 NAT를 자동으로 동작시켜준다. + +### Passive 모드 + +Passive 모드는 Acive 모드의 단점을 보완하기 위해 만들어졌다. Active 모드의 가장 큰 문제는 컨트롤 프로토콜과 데이터 프로토콜의 방향이 반대라는 것 이었다. Passive 모드는 컨트롤, 데이터 프로토콜이 분리되어 있는 것은 같지만 클라이언트에서 서버쪽으로 데이터를 요청해 다운받도록 동작한다. + +image + +Passive 모드의 작동 흐름은 다음과 같다. + +1. 클라이언트가 FTP 서버에 접속. 클라이언트는 1023번 이상의 TCP 포트를, 서버는 21번 포트 사용(Active 모드와 동일) +2. 클라이언트가 Passive 모드를 사용하겠다고 알림 +3. 서버는 클라이언트에 데이터 수신에 사용할 포트를 알림 2024번 포트를 사용해 수신하겠다고 응답 +4. 클라이언트에서 서버에 데이터를 요청, 2번 과정에서 알려준 2023번 포트에 요청 +5. 데이터 전송 + +Passive 모드에서 클라이언트 쪽에 방화벽이나 세션 장비가 있을 경우, 특별한 작업 없이 동작할 수 있다는 장점이 있지만 서버 쪽에 방화벽이 있으면 데이터 다운로드를 위한 추가 포트를 열어줘야 한다. FTP 모드에서 사용하는 데이터 포트의 범위를 설정할 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/SMTP\354\235\230\342\200\205\353\263\264\354\225\210\342\200\205\354\213\234\354\212\244\355\205\234.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/SMTP\354\235\230\342\200\205\353\263\264\354\225\210\342\200\205\354\213\234\354\212\244\355\205\234.md" new file mode 100644 index 00000000..aa3b2d82 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/SMTP\354\235\230\342\200\205\353\263\264\354\225\210\342\200\205\354\213\234\354\212\244\355\205\234.md" @@ -0,0 +1,104 @@ +--- +title: 'SMTP의 보안 시스템' +lastUpdated: '2024-03-02' +--- + +- SMTP는 MARC, DKIM, SPF 등 세 가지 방법으로 이메일에 대한 보안을 지킨다. 함께 사용하면 스팸 발송자, 피싱 공격자 등 권한이 없는 당사자가 소유하지 않은 도메인을 대신하여 이메일을 보내는 것을 막을 수 있다. + +- SPF, DKIM, DMARC를 올바르게 설정하지 않은 도메인에서는 이메일이 스팸으로 격리되거나 수신자에게 전달되지 않을 수 있다. 또한 스팸 발송자가 자신을 사칭할 위험도 있다. + +- DKIM, SPF, DMARC 레코드는 모두 DNS TXT 레코드로 저장된다. + +## 1. SPF (Sender Policy Framework) + +- SPF는 누군가(google.com)로부터 메일이 발송되었을 때, 이 메일의 발송지(111.111.111.111)가 진짜 해당 도메인인 google.com으로부터 발송된 메일인지 확인하는 시스템이다. +- 만약 이 주소가 진짜라면 google.com은 DNS 서버에 '이 IP로 보낸 것은 저(google.com)입니다.' 라고 등록한다. 이를 SPF record (혹은 TXT record)라고 한다. +- 즉, 특정 도메인이 DNS 서버에 자신의 메일 서버를 등록시키고, 메일 수신자가 발송 서버를 검증할 수 있도록 만든 것이다. + +- 어떤 스팸 업자가 구글의 이름을 사칭해서 발송 도메인을 spam@google.com이라고 위조했다고 하자. SPF가 없을 때, 대다수의 사용자들은 이 스팸 메일을 받아도 "구글에서 보낸 것이니 믿을만 하겠지?" 라고 생각해서 열어보게 될 것이다. +- 이를 방지하기 위해서 수신 메일 서버들은 어떤 송신 메일 서버로부터 어떤 메일을 수신했을 때, 이 도메인에 해당 송신 메일 서버가 유효한 서버인지 검증하는 것이다. google은 DNS 서버에 자신이 허가한 메일 송신 서버를 등록해둠으로서 스팸업자들이 자신을 사칭하는 것을 예방할 수 있다. + +image + +``` +v=spf1 a mx include:spf.mtasv.net include:_spf.google.com include:cmail1.com ~all +``` + +|항목|입력값 및 설명| +|-|-| +|`v`|SPF 버전을 나타낸다. (e.g. `spf1`)| +|`include`|include 뒤에 붙은 도메인의 SPF 레코드에 있는 IP 주소와 도메인을 이 도메인의 인증된 발신자 목록에 추가한다. (e.g. `_spf.google.com`, `amazonses.com`, `servers.mcsv.net`| +|`~all`|도메인의 인증된 발신자 목록에 없는 모든 IP 주소와 도메인에 대해 소프트 페일(Soft Fail)을 설정한다는 의미이다. 수신 서버에게 이메일을 거부하거나 스팸으로 처리할 것을 권장하지만 강제하지는 않는다. 다른 정책으로는 `-all` (메일을 받지 않고 SPF 검증에 실패했다고 표시), `?all` (메일을 받되 SPF 검증과 관계없이 처리), `+all` (모든 메일을 받고 SPF 검증에 성공했다고 표시) 등이 있다.| + +- **발신자별 SPF 레코드** + + |발신자|레코드|용도| + |-|-|-| + |Google Workspace|`_spf.google.com`|Gmail 클라이언트에서 메일 발송| + |Amazon SES|`amazonses.com`|Amazon SES에서 메일 발송| + |MailChimp|`servers.mcsv.net`|MailChimp에서 메일 발송 + |Atlassian|`_spf.atlassian.net`|Atlassian에서 메일 발송| + +### 2. DomainKeys Indetified Mail (DKIM) + +- DKIM은 메일이 전송 중에 다른 사람(해커 등)에 의해서 변조되지 않았는지를 검증하는 절차이다. +- 도메인 사용자는 DKIM을 사용하여 수신자가 메일을 받았을 때 이 메일이 변조되지 않았다는 것을 확인하고, 이를 증명해야한다. +- DKIM은 공개키/사설키를 사용한다. 도메인이 메일을 발송할 때, 발송 서버는 사설키로 해시값을 만들고 이를 헤더에 넣어 발송하고, 메일 수신 서버가 메일을 받으면 발송자의 도메인의 DNS에 있는 공개키로 복호화한다. 복호화한 해시값을 확인하여 메일이 중간에 변조되었는지를 확인할 수 있다. +- DKIM 레코드 이름은 다음 형식을 따른다. + ``` + [selector]._domainkey.[domain] + ``` + - `selector`는 도메인에서 사용하는 이메일 서비스 공급자가 발급한 특수 값이다. selector는 DKIM 헤더에 포함되어 이메일 서버가 DNS에서 필요한 DKIM 조회를 수행할 수 있도록 한다. + - `_domainkey`는 모든 DKIM 레코드 이름에 포함된다. + - `domain`은 이메일 도메인 이름이다. + +- DKIM 레코드 값은 아래와 같은 구조이다. + +``` +pm._domainkey.domain.com IN TXT +k=rsa\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOCTHqIIQhGNISLchxDvv2X8NfkW7MEHGmtawoUgVUb8V1vXhGikCwYNqFR5swP6UCxCutX81B3+5SCDJ3rMYcu3tC/E9hd1phV+cjftSFLeJ+xe+3xwK+V18kM46kBPYvcZ/38USzMBa0XqDYw7LuMGmYf3gA/yJhaexYXa/PYwIDAQAB +``` + +- `v=DKIM1`은 이 TXT 레코드가 DKIM으로 해석되어야 한다는 의미이고, `p` 뒤에 오는 값은 공개 키를 나타낸다. + +### 3. DMARC (Domain-based Message Authentication, Reporting and Conformance) + +- DMARC는 spoofing (발신자 정보를 위조하는 것)을 예방하기 위해 만들어진 보안 방법이다. DMARC는 위에서 소개한 SPF와 DKIM에 종합보고서인 Reporting을 추가한 방식이다. +- DMARC를 채택한다면 일반적으로 하루에 한 번 종합 보고서;`Aggregate reports` 를 받게 된다. 이 보고서는 XML 파일로 보내지며 해당 도메인으로부터 보내진 (혹은 보내졌다고 위조된) 메일들이 DMARC 인증 절차를 통과했는지를 알려준다. 이를 통해 발신측은 정상 메시지 중 인증 실패 비율이나 얼마나 많은 사칭 메일이 발송되고 있는가를 파악할 수 있게 한다. +- SPF 와 DKIM은 좋은 인증 방식이지만 각각에는 허점이 있다. SPF는 중간에 메일이 변조되어서 피싱 메일로 바뀌어도 이를 검증할 수 없고, DKIM의 경우는 해당 메일 자체가 피싱 사이트에서 왔어도 검증할 수 없다. 그래서 DMARC 는 이 둘을 모두 사용하여 1. 메일이 제대로 된 곳에서 왔는지 2. 메일이 위/변조되지 않았는지를 검증한다. + +- DMARC 레코드 값은 아래와 같은 구조이다. + +``` +v=DMARC1; p=none; aspf=r; adkim=r; rua=mailto:report@example.com +``` + +|항목|입력값 및 설명|비고| +|-|-|-| +|`v`|반드시 가장 먼저 선언되어야 함
‘DMARC1’로 입력|필수| +|`p`|반드시 v 다음에 선언되어야 함
수신 서버에서 DMARC로 인증되지 않은 메일에 대한 처리 방법
- none : 아무런 조치를 하지 않고 메일을 수신한다.
- quarantine : 스팸메일함으로 수신한다.
- reject : 수신을 차단하고, 반송처리한다.|필수| +|sp|하위 도메인에서 전송된 메일에 대한 정책
- none : 아무런 조치를 하지 않고 메일을 수신한다.
- quarantine : 스팸메일함으로 수신한다.
- reject : 수신을 차단하고, 반송처리한다.|| +|`aspf`|메일 정보와 SPF 서명의 문자열 일치 여부 설정
- s : 모두 일치해야한다.
- r : 부분일치를 허용한다.|| +|`adkim`|메일 정보와 DKIM 서명의 문자열 일치 여부를 설정
- s : 모두 일치해야한다.
- r : 부분일치를 허용한다.|| +|`rua`|해당 도메인의 DMARC 처리 보고서를 수신할 이메일 주소이다. 메일 주소 앞에 ‘mailto:’를 입력한다. 쉼표(,) 를 연결하여 여러 이메일 주소를 지정할 수 있다.|| + +### 이메일이 SPF, DKIM, DMARC를 통과했는지 확인하는 방법 + +- "세부 정보 표시" 또는 "원본 표시" 등 옵션으로 헤더를 포함한 이메일의 전체 버전을 확인해보면 헤더에 SPF, DKIM, DMARC의 결과 추가된 것을 볼 수 있다. + + ``` + arc=pass (i=1 spf=pass spfdomain=example.com dkim=pass + dkdomain=example.com dmarc=pass fromdomain=example.com); + ``` + +- "pass"라는 단어가 표시되면 이메일이 인증 검사를 통과했다는 뜻이다. 예를 들어 "spf=pass"는 이메일이 SPF에 실패하지 않았으며 도메인의 SPF 레코드에 나열된 IP 주소를 가진 인증된 서버에서 전송되었음을 의미한다. +- 위의 예시에서는 이메일이 SPF, DKIM, DMARC 세 가지를 모두 통과했으며 메일 서버에서는 이 이메일이 사칭자가 아닌 실제 example.com에서 보낸 것임을 확인할 수 있다. + +--- +참고 +- https://en.wikipedia.org/wiki/Sender_Policy_Framework +- https://postmarkapp.com/guides/spf +- https://postmarkapp.com/guides/dkim +- https://en.wikipedia.org/wiki/DMARC +- https://help.worksmobile.com/kr/administrator/service/mail/advanced-setting/what-is-dmarc/ +- https://www.cloudflare.com/ko-kr/learning/dns/dns-records/dns-dkim-record/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/HTTP.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/HTTP.md" new file mode 100644 index 00000000..6a659217 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/HTTP.md" @@ -0,0 +1,68 @@ +--- +title: 'HTTP' +lastUpdated: '2024-03-02' +--- + +HTTP는 웹 상에서 하이퍼텍스트 등의 정보를 주고받는 데 쓰이는 프로토콜로, 서버와 클라이언트의 사이에서 어떻게 메시지를 교환할지를 정해 놓은 규칙이다. HTTP는 요청(Request)와 응답(Response)로 구성되어 있고, 클라이언트가 요청을 하면 서버가 응답을 하는 구조로 되어 있다. + +## HTTP 메서드 + - ### GET + - 리소스를 조회하는 메서드이다. + - 서버에 전달하고 싶은 데이터는 query(쿼리스트링)를 통해 전달할 수 있다. + - 캐싱이 가능하다. + - ### POST + - 요청한 데이터를 처리하는 메서드이다. + - 메시지 바디를 통해 서버로 요청 데이터 전달한다. (들어온 데이터를 처리하는 모든 기능을 수행할 수 있다.) + - 대상 리소스가 가지는 의미에 따라 요청에 포함된 표현을 자유롭게 처리하도록 요청한다. 리소스를 생성하는 것 부터 단순히 요청 데이터를 처리하는 작업까지 넒은 범위의 프로세스를 의미할 수 있다. + - ### PUT + - 입력된 데이터로 리소스를 대체하고, 리소스가 없으면 생성한다 + - 클라이언트가 리소스 위치를 알고 URI를 지정해야한다. + - 일부만 넣으면 나머지는 null이 된다. + - ### DELETE + - 리소스를 삭제한다. + - ### PATCH + - 리소스를 부분적으로 변경한다. + + +## HTTP 메서드의 속성 + - ### 안전 safe + 호출해도 리소스를 변경하지 않는다. + - ### 멱등 idempotent + 몇 번 호출하든 결과가 같다. + f(f(x)) = f(x) + - GET: 한 번 조회하든, 두 번 조회하든 같은 결과가 조회된다. + - PUT: 결과를 대체한다. 같은 요청을 하면 남는건 똑같다. + - DELETE: 결과를 삭제한다. 똑같이 지워진다. + - POST: 멱등이 아니다. 두 번 호출하면 같은 함수가 중복해서 발생 할 수 있다. + 멱등이라면 자동 복구 메커니즘에 활용할 수 있음 (막혔을때 여러번 시도) + - ### 캐시 가능 cacheable + - 웹브라우저에 임시 저장 (앞주머니) + - GET, HEAD는 캐시로 사용 (POST, PATCH는 본문 내용까지 캐시 키로 고려해야 하는데, 구현이 쉽지 않음) + +- |HTTP 메소드|RFC|요청BODY|응답BODY|안전|멱등|캐시가능| + |------|:----:|:---:|:---:|:---:|:---:|:---:| + |GET|RFC7231|X|O|O|O|O| + |POST|RFC7231|O|O|X|X|O| + |PUT|RFC7231|O|O|X|O|X| + |DELETE|RFC7231|X|O|X|O|X| + |PATCH|RFC5789|O|O|X|X|O| + +## HTTP 상태코드 +HTTP 상태 코드(HTTP Status Code)는 클라이언트의 요청에 대한 서버에서 설정해주는 응답(Response) 정보이다. + +### 200번대: 성공 + - 200 OK + - 201 CREATED 리소스를 성공적으로 생성했음 + - 204 NO CONTENT 성공했으나 응답 본문에 데이터가 없음 + +### 400번대: 클라이언트 에러 +400번대 상태 코드는 대부분 클라이언트의 코드가 잘못된 경우이다. 유효하지 않은 자원을 요청했거나 요청이나 권한이 잘못된 경우 발생한다. + + - 400 BAD REQUEST 잘못된 요청 + - 401 UNAUTHORIZED 인증 정보가 없거나 잘못됨 + - 403 FORBIDDEN 인증은 됐으나 권한이 없음 + - 404 NOT FOUND 요청한 URL이나 리소스에 대한 정보를 찾을 수 없음 + - 405 METHOD NOT ALLOWED 허용되지 않은 요청 메서드 + - 409 CONFLICT 사용자가 요청하는 리소스가 서버의 리소스와 충돌됨 +### 500번대: 서버 에러 + - 500 INTERNAL SERVER ERROR 서버 내부에서 에러가 발생함 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/HTTP\342\200\205Options.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/HTTP\342\200\205Options.md" new file mode 100644 index 00000000..7938ab2a --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/HTTP\342\200\205Options.md" @@ -0,0 +1,51 @@ +--- +title: 'HTTP Options' +lastUpdated: '2024-03-02' +--- + +일반적인 브라우저를 사용하여 통신한 내용을 들여다보면, 본 요청 이전에 Request Method가 OPTIONS인 요청이 있는것을 볼 수 있다. + +통신 Type도 XMLHttpRequest (XHR) 가 아닌 preflight으로 되어있다. + +이 요청은 무슨 용도의 요청일까? + +![image](https://user-images.githubusercontent.com/81006587/212473281-0d417a0d-096b-4f79-99b2-d983e6b656fa.png) + +## Preflight: Request Method: OPTIONS + +preflight인 OPTIONS 요청은 서버와 브라우저가 통신하기 위한 통신 옵션을 확인하기 위해 사용한다. + +서버가 어떤 method, header, content type을 지원하는지를 알 수 있다. + +브라우저가 요청할 메서드와 헤더를 허용하는지 미리 확인한 후, 서버가 지원할 경우에 통신한다. 좀 더 효율적으로 통신할 수 있다. + +터미널에서 확인해볼 수 있다. + +``` +curl -X OPTIONS https://API서버 -i +``` + +라는 요청을 서버에 보내면 다음과 같은 응답이 나온다. + +``` +HTTP/1.1 204 No Content +... +Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE +... +``` + +본 정보는 없고, 서버에서 허용하는 메소드나 Origin에 대한 정보만 헤더에 담겨서 온다. + +다시말해, 먼저 Options요청을 보낸 뒤 응답 정보를 사용 가능한지 파악하고 서버의 "허가"가 떨어지면 실제 요청을 보내도록 요구하는 것이다. 또한 서버는 클라이언트에게 요청에 "인증정보"(쿠키, HTTP 인증)를 함께 보내야 한다고 알려줄 수도 있다. + +허용되지 않는 요청의 경우, 405(Method Not Allowed) 에러를 발생시키고 실제 요청은 전송하지 않게된다. + +## 발생 조건 + +preflight은 보안을 위한 절차이며, 아래와 같은 경우에 발생하게 된다. + +1. GET, HEAD, POST 요청이 아닌 경우 +2. Custom HTTP Header가 존재하는 경우 + - 유저 에이전트가 자동으로 설정 한 헤더 (ex. Connection, User-Agent (en-US), Fetch 명세에서 “forbidden header name”으로 정의한 헤더), “CORS-safelisted request-header”로 정의한 헤더(ex. Accept, Accept-Language, Content-Language, Content-Type) 등등... +3. Content-Type이 `application/x-www-form-urlencoded`, `multipart/form-data, text/plain`이 아닌 경우 + diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/keep\342\200\205alive.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/keep\342\200\205alive.md" new file mode 100644 index 00000000..29889dbb --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/http/keep\342\200\205alive.md" @@ -0,0 +1,67 @@ +--- +title: 'keep alive' +lastUpdated: '2024-03-02' +--- + +image + +## Persistent Connection + +site locality란 웹에서 특정 페이지를 보여주기 위해 서버에 연속적으로 이미지 request를 보내는 것 처럼, 서버에 연속적으로 동일한 클라이언트가 여러 요청을 보낼 가능성이 높은 경우를 의미한다. + +site localiry가 높은 경우엔, 요청이 처리된 후에도 connection을 유지하는 persistent connection을 통해 통신 효율을 높일 수 있다. (connection을 위한 절차가 생략되므로) + +그 외에도 아래와 같은 장점이 있다. + +1. 네트워크 혼잡 감소: TCP, SSL/TCP connection request 수가 줄어들기 때문 +2. 네트워크 비용 감소: 여러 개의 connection으로 하나의 client요청을 serving 하는 것보다는 한 개의 connection으로 client요청을 serving하는게 더 효율적이다. +3. latency감소: 3-way handshake을 맺으면서 필요한 round-trip이 줄어들기 때문에 그만큼 latency가 감소한다. + +## HTTP keep alive + +HTTP keep-alive는 위에서 설명한 persistent connection을 맺는 기법 중 하나이다. 하나의 TCP connection을 활용해서 여러개의 HTTP request/response를 주고받을 수 있도록 해준다. + +Keep-Alive 옵션은 HTTP1.0부터 지원한다. 단, HTTP/1.0에서는 무조건 Keep-Alive 헤더를 명시적으로 추가하여 사용해야 했던 것과 달리 HTTP/1.1부터는 Keep-Alive 연결이 기본적으로 활성화되어 있어 별도의 헤더를 추가하지 않아도 연결을 유지할 수 있게 되었다. + +## keep-alive 옵션 사용 방법 + +keep-alive 옵션을 통해 persistent connection을 맺기 위해서는 HTTP header에 아래와 같이 입력해주어야 한다. 만약 서버에서 keep-alive connection을 지원하는 경우에는 동일한 헤더를 response에 담아 보내주고, 지원하지 않으면 헤더에 담아 보내주지 않는다. 만약 서버의 응답에 해당 헤더가 없을 경우 client는 지원하지 않는다고 가정하고 connection을 재사용하지 않는다. + +```bash +HTTP/1.1 200 OK +Connection: Keep-Alive +Keep-Alive: timeout=5, max=1000 +``` + +- max (MaxKeepAliveRequests): keep-alive connection을 통해서 주고받을 수 있는 request의 최대 갯수. 이 수보다 더 많은 요청을 주고 받을 경우에는 connection은 close된다. +- timeout (KeepAlivetimeout): 커넥션이 idle한 채로 얼마동안 유지될 것인가를 의미한다. 이 시간이 지날 동안 request가 없을 경우에 connection은 close된다. + +keep-alive를 사용할 때는 아래와 같은 사항에 유의해야한다. + +- persistent한 connection을 유지하기 위해서는 클라이언트 측에서 모든 요청에 위에 언급한 헤더를 담아 보내야 한다. 만약 한 요청이라도 생략될 경우 서버는 연결을 close한다. +- connection이 언제든 close 될 수 있기 때문에 클라이언트에서 retry 로직을 준비해두어야 한다. +- 정확한 Content-length를 사용해야 한다. 하나의 connection을 계속해서 재사용해야 하는데, 특정 요청의 종료를 판단할 수 없기 때문이다. +- Connection 헤더를 지원하지 않는 proxy에는 사용할 수 없다. + +## proxy 문제: blind relays + +서버와 클라이언트가 proxy없이 직접 통신할 경우에는 keep-alive 옵션이 정상 동작할 수 있지만, 만약 blind relay, 즉 keep-alive 옵션을 지원하지 않는 proxy는 Connection header를 이해하지 못하고 그냥 extension header로 인식하여 제대로 동작하지 않는다. + +image + +위 사진을 살펴보자. + +- (b)단계에 blind relay proxy가 server측에 HTTP Connection Keep Alive header를 보낼 경우에, 서버는 proxy가 keep-alive를 지원하는 걸로 착각하게 된다. + +- 따라서 proxy와 헤더에 입력된 규칙으로 통신을 시도한다. 그리고 proxy는 서버가 보낸 header를 그대로 client에게 전달은 하지만 keep-alive 옵션을 이해하지 못하기 때문에, client서버가 connection을 close하기를 대기한다. + +- 하지만 client는 response에서 keep-alive 관련 헤더가 넘어왔기 때문에 persistent connection이 맺어진 줄 알고 close하지 않게된다. 따라서 이 때 proxy가 connection이 close 될 때까지 hang에 걸리게 된다. + +- client는 동일한 conenction에 request를 보내지만 proxy는 이미 close된 connection이기 때문에 해당 요청을 무시한다.이에 따라 client나 서버가 설정한 timeout이 발생할 때까지 hang이 발생한다. + +따라서 `HTTP/1.1`부터는 proxy에서 Persistent Connection 관련 header를 전달하지 않는다. persistent connection을 지원하는 proxy에서는 대안으로 Proxy Connection 헤더를 활용하여 proxy에서 자체적으로 keep-alive를 사용한다. Keep-Alive 연결이 기본적으로 활성화되는 이유가 이 때문이다. + +--- +참고 +- https://flylib.com/books/en/1.2.1.88/1/ +- http://www.w3.org/Protocols/rfc2616/rfc2616.txt \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/java\353\241\234\342\200\205\352\260\204\353\213\250\355\225\234\342\200\205socket\342\200\205\355\224\204\353\241\234\352\267\270\353\236\250\353\247\214\353\223\244\352\270\260.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/java\353\241\234\342\200\205\352\260\204\353\213\250\355\225\234\342\200\205socket\342\200\205\355\224\204\353\241\234\352\267\270\353\236\250\353\247\214\353\223\244\352\270\260.md" new file mode 100644 index 00000000..3ed1abba --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/java\353\241\234\342\200\205\352\260\204\353\213\250\355\225\234\342\200\205socket\342\200\205\355\224\204\353\241\234\352\267\270\353\236\250\353\247\214\353\223\244\352\270\260.md" @@ -0,0 +1,122 @@ +--- +title: 'java로 간단한 socket 프로그램만들기' +lastUpdated: '2024-03-02' +--- + +`java.net`의 Socket class와 Thread를 이용해 간단한 socket 통신 프로그램을 만들어보자. + +## socket client + +```java +public class ServerMain { + + public static void main(String[] args) { + + // ServerSocket 클래스로 객체를 생성해준다. port는 6000으로 설정해주었다. + try (ServerSocket ss = new ServerSocket(6000)) { + + // accept() 는 클라이언트가 들어오는 것을 대기 하는 역할을 한다. + // 클라이언트가 설정해준 포트(6000)로 연결을 시도한다면 accept 메소드는 대기를 풀고, 클라이언트와 연결시키는 Socket 클래스를 생성하여 반환한다. + Socket sc = ss.accept(); + + // input과 output 작업을 수행할 스레드를 별도로 정의하여 실행시킨다. + Thread inputThread = new InputThread(sc); + Thread outputThread = new OutputThread(sc); + + inputThread.start(); + outputThread.start(); + + } catch (IOException e) { + e.printStackTrace(); + } + } +} +``` + +```java +public class ClientMain { + + public static void main(String[] args) { + + try { + + // Socket 객체를 생성하여 연결을 시도한다. + // 연결할 IP 주소와 Port 번호를 매개변수로 넘겨 주어서, 해당 주소로 연결을 시도하게 한다. + Socket sc = new Socket("127.0.0.1", 6000); + + // 연결이 완료 되었다면 여기에서도 마찬가지로 input과 output 작업을 수행할 스레드를 별도로 정의하여 실행시킨다 + Thread inputThread = new InputThread(sc); + Thread outputThread = new OutputThread(sc); + + inputThread.start(); + outputThread.start(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +``` + +## Input / Output Thread + +```java +public class OutputThread extends Thread { + + private final Socket sc; + private final Scanner scanner; + + public OutputThread(Socket sc) { + this.sc = sc; + this.scanner = new Scanner(System.in); + } + + @Override + public void run() { + try { + OutputStream os = sc.getOutputStream(); + PrintWriter pw = new PrintWriter(os, true); + while (true) { + // Scanner로 들어온 값을 읽어서 PrintWriter로 출력한다. + pw.println(scanner.nextLine()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + this.scanner.close(); + } + } +} +``` + +```java +public class InputThread extends Thread { + + private final Socket sc; + + public InputThread(Socket sc) { + this.sc = sc; + } + + @Override + public void run() { + try { + InputStream is = sc.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + while (true) { + // InputStream을 BufferedReader로 읽어서 출력한다. + System.out.println(br.readLine()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +``` + +## 결과 + +Server나 Client 중 한 쪽에서 메시지를 입력하면 서로 잘 전송되는 것을 볼 수 있다. + +image +image diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/SNI.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/SNI.md" new file mode 100644 index 00000000..8827ec96 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/SNI.md" @@ -0,0 +1,69 @@ +--- +title: 'SNI' +lastUpdated: '2024-03-02' +--- + +- SNI is somewhat like mailing a package to an apartment building instead of to a house. When mailing something to someone's house, the street address alone is enough to get the package to the right person. + - But when a package goes to an apartment building, it needs the apartment number in addition to the street address; otherwise, the package might not go to the right person or might not be delivered at all. + +- Many web servers are more like apartment buildings than houses: They host several domain names, and so the IP address alone is not enough to indicate which domain a user is trying to reach. +- This can result in the server showing the wrong SSL certificate, which prevents or terminates an HTTPS connection – just like a package can't be delivered to an address if the correct person doesn't sign for it. + +- When multiple websites are hosted on one server and share a single IP address, and each website has its own SSL certificate, the server may not know which SSL certificate to show when a client device tries to securely connect to one of the websites. +- This is because the SSL/TLS handshake occurs before the client device indicates over HTTP which website it's connecting to. + +- Server Name Indication (SNI) is designed to solve this problem. **SNI is an extension for the TLS protocol** (formerly known as the SSL protocol), which is used in HTTPS. + - It's included in the TLS/SSL handshake process in order to ensure that client devices are able to see the correct SSL certificate for the website they are trying to reach. + - The extension makes it possible to specify the hostname, or domain name, of the website during the TLS handshake, instead of when the HTTP connection opens after the handshake. + - More simply put, SNI makes it possible for a user device to open a secure connection with `https://www.example.com` even if that website is hosted in the same place (same IP address) as `https://www.something.com`, `https://www.another-website.com`, and `https://www.example.io`. + +- SNI prevents what's known as a "common name mismatch error": when a client (user) device reaches the right IP address for a website, but the name on the SSL certificate doesn't match the name of the website. + - Often this kind of error results in a "Your connection is not private" error message in the user's browser. + +- SNI was added as an extension to TLS/SSL in 2003; it was not originally a part of the protocol. Almost all browsers, operating systems, and web servers support it, with the exception of some of the very oldest browsers and operating systems that are still in use. + +## What is a server name? + +- Although SNI stands for Server Name Indication, what SNI actually "indicates" is a website's hostname, or domain name, which can be separate from the name of the web server that is actually hosting the domain. +- In fact, it is common for multiple domains to be hosted on one server – in which case they are called virtual hostnames. + +- A server name is simply the name of a computer. + - For web servers this name is typically not visible to end users unless the server hosts only one domain and the server name is equivalent to the domain name. + +## What does the TLS SNI extension do? + +- Often a web server is responsible for multiple hostnames – or domain names (which are the human-readable names of websites). + - Each hostname will have its own SSL certificate if the websites use HTTPS. + +- The problem is, all these hostnames on one server are at the same IP address. + - This isn't a problem over HTTP, because as soon as a TCP connection is opened the client will indicate which website they're trying to reach in an HTTP request. + +- But in HTTPS, a TLS handshake takes place first, before the HTTP conversation can begin (HTTPS still uses HTTP – it just encrypts the HTTP messages). Without SNI, then, there is no way for the client to indicate to the server which hostname they're talking to. + - As a result, the server may produce the SSL certificate for the wrong hostname. If the name on the SSL certificate does not match the name the client is trying to reach, the client browser returns an error and usually terminates the connection. + +- SNI adds the domain name to the TLS handshake process, so that the TLS process reaches the right domain name and receives the correct SSL certificate, enabling the rest of the TLS handshake to proceed as normal. + +- Specifically, SNI includes the hostname in the Client Hello message, or the very first step of a TLS handshake. + +## What is a (virtual) hostname? + +- A **hostname** is the name of a device that connects to a network. In the context of the Internet, a domain name, or the name of a website, is a type of hostname. Both are separate from the IP address associated with the domain name. + +- A **virtual hostname** is a hostname that doesn't have its own IP address and is hosted on a server along with other hostnames. It is "virtual" in that it doesn't have a dedicated physical server, just as virtual reality exists only digitally, not in the physical world. + +## What is encrypted SNI (ESNI)? + +- Encrypted SNI (ESNI) adds on to the SNI extension by encrypting the SNI part of the Client Hello. +- This prevents anyone snooping between the client and server from being able to see which certificate the client is requesting, further protecting and securing the client. +- Cloudflare and Mozilla Firefox launched support for ESNI in 2018. + +## What happens if a user's browser does not support SNI? + +- In this rare case, the user will likely be unable to reach certain websites, and the user's browser will return an error message like "Your connection is not private." + +- The vast majority of browsers and operating systems support SNI. Only very old versions of Internet Explorer, old versions of the BlackBerry operating system, and other outdated software versions do not support SNI. + +--- +reference +- https://www.cloudflare.com/learning/ssl/what-is-sni/ +- https://en.wikipedia.org/wiki/Server_Name_Indication \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/TLS.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/TLS.md" new file mode 100644 index 00000000..ed382e27 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/TLS.md" @@ -0,0 +1,108 @@ +--- +title: 'TLS' +lastUpdated: '2024-03-02' +--- + +HTTP는 Hypertext Transfer Protocol의 약어이다. Hypertext 즉, HTML을 전송하는 통신 프로토콜을 의미하는 것이다. + +그리고, HTTPS의 약자는 Hypertext Transfer Protocol Secure이다. 간단하게 말하자면 HTML통신 규약이긴한데 그게 안전하게 이루어진다는 것이다. HTTPS에는 TLS라는 프로토콜이 함께 사용되어, 사용자의 인증 과정을 추가로 거치게 된다. + +![image](https://user-images.githubusercontent.com/81006587/216963905-420674e8-3330-4adb-b93e-4f0425edd095.png) + +## TLS + +TLS는 Transport Layer Security의 약자로, 컴퓨터 네트워크를 통해 통신 보안을 제공하도록 설계된 암호화 프로토콜이다. 통신을 하는 과정에서 도청, 간섭, 위조를 막을 수 있도록 암호 코드를 교환하고 인증하는 절차를 거치도록 한다.TLS 위에서 HTTP가 동작하면 HTTPS가 되고, FTP가 동작하면 SFTP가 된다. (즉, 꼭 HTTPS만을 위한 것은 아니다.) + +[TLS](https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations)에도 여러 구현이 있다. + +HTTPS를 얘기할떄 SSL라는 용어를 사용하기도 한다. 원래 처음 HTTPS가 만들어졌을때는 Netscape Communications가 개발한 SSL(Secure Sockets Layer)가 사용되었었는데, IETF(Internet Engineering Task Force)에서 표준으로 TLS를 다시 정의하면서 이제는 사용되지 않게 된 구버전이다. 하지만 TLS가 SSL을 계승받아서 만들어진 것이니 개념은 거의 비슷하다고 볼 수 있다. + +## 장단점 + +공개키 암호화 방식과 대칭키 암호화 방식을 같이 사용하여 덕분에 보안상 안전하다는 장점이 있지만, 인증서 유지 비용이 들고 암호화, 복호화 과정 때문에 HTTP에 비해서 느리다는 단점이 있다. + +## 보안적 특성 + +패킷이 암호화되어 송수신 되므로 정보탈취 부분에서는 강하다. 기밀성이 우선이므로 **스니핑 공격(sniffing attack)**에서 뛰어난 보안성을 보인다. 다만 암호화된 패킷이 클라이언트 PC 또는 서버로 전송되기 때문에 송수신자간 데이터 교환이 일어나는 일에 대해서는 무력해진다. **개인정보유출, 기밀정보유출, DDoS, APT, 악성코드 공격**이 발생할 경우 무력화된다. + +## 인증서 + +TLS 통신을 하려면 인증서가 필요한데, 이 인증서는 공인된 CA로부터 발급 받아야한다. 그 인증서는 공개키와 비밀키, 서명과 Finger Print 등등의 인증 정보를 가지고 있다. + +자세한 절차는 [문서](TLS 인증서 발급 절차를 이해해보자.md)에서 볼 수 있다. + +# 보안 인증 과정 + +서버가 CA에게 인증서를 발급받았다고 가정했을떄, 그 서버에게 클라이언트가 요청을 보내면 어떤 통신 과정을 거치는지 살펴보자. + + + +### 1. Client : Client Hello + +Client가 서버에 접속할때 Server에게 몇가지 데이터를 먼저 알려준다. + +- random : 클라이언트는 32바이트 난수값을 전달해서 전달한다. 이 랜덤값은 나중에 비밀 데이터(master secret)를 위해 사용된다. + +- Session ID : 매번 연결할 때마다 Handshake 과정을 진행하는 것은 비효율적이니 최초 한번 전체 Handshake 과정을 진행하고 Session ID를 가진다. 후에는 이 Session ID를 사용해서 위 과정을 반복해서 진행하지 않는다. (앞으로의 통신에도 계속해서 Session ID값이 포함될 것이다.) + + + +- cipher suite : 클라이언트가 지원가능한 키 교환 알고리즘, 대칭키 암호 알고리즘, 해시 알고리즘 목록을 알려준다. 이렇게 전체 목록을 주면 서버는 최적의 알고리즘을 선택한다. + +`TLS_RSA_WITH_AES_128_GCM_SHA256` <- 이런식으로 데이터를 전달하는데, 키 교환 알고리즘은 RSA, 대칭키 알고리즘은 AES_128 GCM방식을 사용하고 Hash 알고리즘으로는 SHA256을 사용한다는 의미이다. + +암호화 알고리즘으로 쓰이는 것들은 아래와 같은 것들이 있다. + +- 키교환: RSA, Diffie-Hellman, ECDH, SRP, PSK +- 대칭키 암호: RC4, 트리플 DES, AES, IDEA, DES, ARIA, ChaCha20, Camellia (SSL에서는 RC2) +- 해시 함수: TLS에서는 HMAC-MD5 또는 HMAC-SHA. (SSL에서는 MD5와 SHA) + +### 2. Server : Server Hello + +TLS Version, 암호화 방식(Client가 보낸 암호화 방식 중에 서버가 사용 가능한 암호화 방식을 선택), Server Random Data(서버에서 생성한 난수, 대칭키를 만들 때 사용), SessionID(유효한 Session ID)를 전달한다. + +### 3. Server : Server Certificate + +서버의 인증서를 클라이언트에게 보내는 단계로, 필요에 따라 CA의 Certificate도 함께 전송한다. + +> 클라이언트는 이 패킷을 통해 서버의 인증서가 무결한지 검증한다. + +### 4. Server : Server ket exchange (선택) + +키교환에 추가 정보가 필요하면 이때, 전송한다. 예를 들면, 알고리즘을 Diffie-Hellman으로 사용해서 소수, 원시근 등 값이 필요한 경우 등이 있다. + +### 5. Server : Certificate request (선택) + +서버 역시 클라이언트를 인증할때 인증서를 요청할 수 있지만, 받지 않을 수도 있다. + +### 6. Server : Server Hello Done + +서버가 클라이언트에게 보낼 메시지를 모두 보냈다. + +### 7. Client : Certificate (선택) + +서버가 인증서를 요청했으면 전송하고, 아니면 생략한다. + +### 8. Client : Client Key Exchange + +우선 서버에서 보낸 인증서가 무결한지 확인해본다. OS나 브라우저는 기본적으로 CA 리스트를 가지고 있다. (Mac에는 keyChain, 브라우저는 소스코드) 그렇기 떄문에 서버의 공개키와 CA 공개키를 비교하면 서버가 보낸 인증서가 정상적인지 검증할 수 있다. + +CA는 서버에게 인증서를 발급해줄때 제출받은 서버의 공개키에 CA의 공개키 정보를 암호화하여 넣어놓는다. 그러면 클라이언트는 서버의 인증서를 받은 다음, 알고있는 CA의 공개키를 해시해서 해당 값과 일치하는지 비교하면 해당 인증서가 유효한지를 확인할 수 있다. + +인증서를 확인한 다음에는 클라이언트가 생성한 랜덤값과 서버에서 보내준 랜덤값을 합쳐 pre-master secret를 만든다. 이 값을 사용해서 세션에 사용될 키를 생성하게 되는데, 그 키가 바로 대칭키이다. + +중요한 정보이기 때문에 아까 받았던 인증서 안에 있는 서버의 공개키로 암호화해서 전송한다. + +### 9. Certificate verify (선택) + +클라이언트에 대한 Certificate request를 받았다면 보낸 인증서에 대한 개인키를 가지고 있다는 것을 증명한다. (handshake과정에서 주고 받은 메시지 + master secret을 조합한 hash값에 개인키로 디지털 서명하여 전송한다.) + +### 10. Server & Client : Change Cipher Spec + +이제부터 전송되는 모든 패킷은 협상된 알고리즘과 키를 이용하여 암호화 하겠다 알리고 끝낸다. 이제 이 뒤로 통신되는 모든 데이터는 대칭키로 암호화해서, 서버와 클라이언트끼리만 해독할 수 있도록 만든다. + +# 결론 + +TLS는 이러한 과정을 거쳐서 통신 보안을 유지한다. 암호 코드를 교환하고 인증하는 절차가 있으니 통신 과정에서의 도청, 간섭, 위조를 막을 수 있다. (중간에 패킷을 탈취하거나 해도, 대칭키가 없으면 해독이 불가능하기 떄문) + +뭔가 복잡해보였는데, 자세히 들여다보면 막 어렵진 않은 것 같다. 현재 https는 거의 모든 인터넷에서 사용되고 있는 프로토콜이기 때문에 이렇게 공부해놓으면 나중에 응용할 일이 꼭 생기지 않을까? 싶은 마음이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/TLS\342\200\205\354\235\270\354\246\235\354\204\234\342\200\205\353\260\234\352\270\211\342\200\205\354\240\210\354\260\250\353\245\274\342\200\205\354\235\264\355\225\264\355\225\264\353\263\264\354\236\220.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/TLS\342\200\205\354\235\270\354\246\235\354\204\234\342\200\205\353\260\234\352\270\211\342\200\205\354\240\210\354\260\250\353\245\274\342\200\205\354\235\264\355\225\264\355\225\264\353\263\264\354\236\220.md" new file mode 100644 index 00000000..3e175766 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/L4\342\200\205appplication\342\200\205layer/tls/TLS\342\200\205\354\235\270\354\246\235\354\204\234\342\200\205\353\260\234\352\270\211\342\200\205\354\240\210\354\260\250\353\245\274\342\200\205\354\235\264\355\225\264\355\225\264\353\263\264\354\236\220.md" @@ -0,0 +1,41 @@ +--- +title: 'TLS 인증서 발급 절차를 이해해보자' +lastUpdated: '2024-03-02' +--- + +[TLS](TLS.md) 보안 인증 과정을 거치기 전에, 서버는 CA (Certificate Authority) 기관에서 인증서를 발급받아야한다. CA는 신뢰성이 엄격하게 공인된 기업들만 할 수 있다고 한다. + +CA에서 인증서를 발급 받으려면 아래와 같은 과정을 거쳐야한다. + + + +- 먼저, 발급 받고자 하는 기관은 자신의 사이트 정보(도메인 등)과 공개키를 CA에게 제출한다. +- 그러면 CA는 검증을 걸친 후 발급 받고자 하는 기관의 공개 키를 해시한다. (SHA-256 등..) +- 이렇게 해시한 값을 **Finger Print**(지문)이라고 한다. + +Screenshot 2023-02-06 at 22 10 44 + +이제 이 지문을 CA의 비밀키로 암호화 하고, 인증서의 발급자 서명으로 등록한다. 이렇게 서명된 것을 **디지털 서명(Digital Signing)이라고 한다. + +Screenshot 2023-02-06 at 22 13 47 + +이제 CA는 서버에게 이 디지털 서명, 발급자 정보 등등이 등록되어 있는 인증서를 발급해 준다. + +이러한 방식처럼, 상위 인증 기관이 하위 인증서가 포함하고 있는 공개키 (인증서)를 상위 기관의 비밀키로 암호화 하여 상호 보증하게 되는 것을 인증서 체인(Certificate Chain) 이라고 한다. + +내가 발급받는 CA 기관이 Root CA가 아니라면, 이 CA 기관마저 또 상위 CA에게 인증서를 발급받은 것이다. + + + +보통 3단계에 걸쳐서 인증서 체인이 일어나는데, 구글(*.google.com)의 인증서를 보면 + +- `*.google.com`은 `GTS CA 1C3`의 비밀키로 암호화 되어있고, +- `GTS CA 1C3`는 `GRS Root R1`의 비밀키로 암호화 되어있다는 것을 알 수 있다. + +`GRS Root R1`는 상위 인증기관이 없는 Root CA이기 때문에 Self-Signed 되어있다. (Self-Signed는 자신의 인증서를 해시한 후, CA가 아닌 자신의 비밀키로 암호화 하여 서명으로 등록하는 것이다!) + +### CA 인증 없이 인증서를 생성할 수 있을까? + +신뢰성은 떨어지겠지만 가능은 하다. CA 인증과 상관 없이 발행하는 인증서를 **사설 인증서**라고 하고, 이 사설 인증서는 Root CA처럼 Self-Signed 되어 있다. + +이제 인증서를 발급받은 서버는, 클라이언트와 [TLS 통신](TLS.md)을 할 수 있다 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/OSI\342\200\2057Layer.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/OSI\342\200\2057Layer.md" new file mode 100644 index 00000000..780d1b9b --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/OSI\342\200\2057Layer.md" @@ -0,0 +1,54 @@ +--- +title: 'OSI 7Layer' +lastUpdated: '2024-03-02' +--- +OSI 모형은 국제표준화기구(ISO)에서 개발한 모델로, 컴퓨터 네트워크 프로토콜 디자인과 통신을 계층으로 나누어 설명한 것이다. 일반적으로 OSI 7 계층(OSI 7 Layer)이라고 한다. 분산된 이기종 시스템간의 네트워크 상호호환을 위한 표준 아키텍처를 정의하여, 통신을 하기 위한 업무를 계층별로 분할하고 분업할 수 있다는 점에서 의의를 가진다. + + + +
+ +# 계층 기능 + +## 1. 물리 계층(Physical Layer) + - 네트워크 데이터가 전송되기 위한 기본적인 하드웨어 기술을 담당한다. 다양한 특징의 하드웨어 기술이 접목되어 있기에 OSI 아키텍처에서 가장 복잡한 계층으로 간주된다. 리피터, 네트워크 허브, 모뎀 등의 장비가 물리계층에 속하며 비트단위의 데이터를 다룬다. + +## 2. 데이터 링크 계층(Date Link Layer) + - 장치 간 신호를 전달하는 물리 계층을 이용하여 네트워크 상의 주변 장치들 간 데이터를 전송한다. 포인트 투 포인트(Point to Point) 간 신뢰성있는 전송을 보장하기 위한 계층이다. 즉, 네트워크 위의 두 개체가 데이터를 주고받는 과정에서 오류를 잡아내는 것이 목적이다. 네트워크 브릿지나 스위치 등이 이 계층에서 동작하며, 직접 이어진 곳에만 연결할 수 있다. + + - ### 대표 프로토콜 + [Ethernet, Token ring](./L1%E2%80%85network%E2%80%85access%E2%80%85layer/Ethernet%EA%B3%BC%E2%80%85TokenRing.md), PPP + +## 3. 네트워크 계층(Network Layer) + - 여러개의 노드를 거쳐 패킷을 최종 수신대상에게 전달(End-To-End)하기 위한 경로 설정을 담당한다. 호스트를 식별하고 라우팅 등의 패킷포워딩을 수행하여 패킷이 목적지에 도달할 수 있도록 한다. + - ### 대표 프로토콜 + IP, DHCP, ARP, IGMP, ICMP + +## 4. 전송 계층(Transport Layer) + - 종단간 연결의 신뢰성과 유효성을 보장한다. 양 끝단의 사용자들이 통신하는 과정에서 생기는 오류를 검출, 복구하고 흐름을 제어하는 일을 담당한다. 프로세스를 특정하여 데이터를 전송하기 위해서 Port 번호를 사용하며, 주로 세그먼트(Segment) 라는 데이터 단위를 사용한다. + - ### 대표 프로토콜 + TCP, UDP + +## 5. 세션 계층(Session Layer) + - 장치간의 연결을 관리 및 종료하고 체크포인팅과 유휴, 재시작 과정 등을 수행하며 호스트가 정상적으로 통신할 수 있도록 하는 계층이다. 통신을 하기 위한 세션을 확립/유지/중단하는 등의 역할을 담당한다. + - ### 대표 프로토콜 + Telnet, SSH (telnet과 SSH는 7계층으로 분류되기도 함 ) + + +## 6. 표현 계층(Presentation Layer) + - 송·수신해야하는 데이터를 암·복호화 하는 일을 담당한다. 이 계층의 대표적인 프로토콜로는 ASCII, EBCDID, MPEG, JPEG 등이 있지만 응용계층과 구분하지 않고 수행하는 경우도 있다. + +## 7. 응용 계층(Application Layer) + - 사용자가 네트워크에 접근할 수 있는 응용 프로세스를 제공하는 계층이다. 네트워크 활동의 기반이 되는 인터페이스를 보여주고 사용자와 직접 상호작용할 수 있도록 한다. + - ### 대표 프로토콜 + HTTP, SMTP, FTP, DNS + +
+
+
+ +--- + +더 알아보기
+OSI 계층별 프로토콜 예시
https://en.wikipedia.org/wiki/List_of_network_protocols_(OSI_model)
+5-7 Layer 계층 구분
https://www.reddit.com/r/ccna/comments/5umh4m/comment/ddy5blj/?utm_source=share&utm_medium=web2x&context=3
https://networkengineering.stackexchange.com/questions/29622/how-we-can-assume-which-network-protocol-is-working-in-which-osi-layer/30493#30493 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/Switch.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/Switch.md" new file mode 100644 index 00000000..4eda58e3 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/Switch.md" @@ -0,0 +1,88 @@ +--- +title: 'Switch' +lastUpdated: '2024-03-02' +--- + +네트워크 스위치는 받은 패킷을 어느쪽으로 보낼지 결정하는 역할을 하는 하드웨어이다. + +스위치는 어떤 정보를 기준으로 스위칭을 하느냐에 따라 L1, L2, L3, L4, L7 스위치 등 다양한 종류가 있다. + +## L1 + +허브를 L1 스위치라고 부르기도 한다. + +## L2 + +L2 스위치는 스위치 패킷이 왔을 때, 그 패킷의 목적지가 어디인지를 본 후에 해당 목적지로 보내주는 역할을 수행한다. 2계층 주소인 MAC 주소를 기반으로 스위칭한다. + +허브가 한 포트로 신호가 들어오면 같은 신호를 다른 모든 포트로 전달하는 것에 비해서, 스위치는 신호를 필요로 하는 포트에만 신호를 전달하기 때문에 속도가 빠르다. + +스위치가 이 기능을 수행하기 위해서는 지나가는 트래픽의 목적지를 정확히 알아야 하니, 자신과 연결된 장비들의 맥 주소와 그 장비가 연결되어 있는 포트 번호를 기억해야 한다. 그리고서 자신이 아는 MAC 주소로 데이터가 오면 알고 있던 포트로 데이터를 전달한다. + +여기서 **아는 MAC**들은 MAC Table이라는 곳에 저장되고, Switch가 이 MAC Table을 생성하고 참조하여 수신되는 프레임을 목적지로 전송하는 과정을 **Transparent Bridging**이라고 한다. (IEEE 802.1D에 표준으로 정의되어 있다.) + +그 과정은 아래와 같다. + +1. **Learning:** 이더넷 프레임이 수신되면, source MAC address를 읽어서 수신 port 번호와 함께 MAC Table에 기록한다. + +2. **Flooding:** Destination MAC address가 MAC Table에 등록되어 있지 않은 Unicast 프레임(Unknown Unicast)이거나, ARP Request와 같은 브로드캐스트인 경우, 수신 port를 제외한 다른 모든 port로 프레임을 전송한다. 허브에서 데이터를 전송하는 것도 Flooding이라 부른다. + +3. **Filtering:** Destination MAC address가 MAC Table에 등록되어 있고, 등록되어 있는 port 번호가 프레임이 수신된 port 번호와 동일한 경우 해당 프레임이 포워딩 당하지 않도록 차단한다. + +4. **Forwarding:** Destination MAC address가 MAC Table에 등록되어 있고, 등록되어 있는 port 번호가 프레임이 수신된 port 번호와 동일하지 않은 Unicast인 경우 등록되어 있는 port로 프레임을 전송한다. + +5. **Aging:** MAC Table에 Entry가 등록될때 Timer도 같이 start 되며, 해당 Entry의 MAC address를 source MAC으로 하는 프레임이 수신되면 Timer가 reset 되어 다시 시작된다. + +Timer가 경과되면 해당 Entry는 MAC Table에서 삭제된다. TTL 같은 개념이다 + + + +패킷 정보를 읽어서 MAC 주소를 판단하는 것도 아래와 같은 여러 방식들이 있는데, 주로 cut-through를 사용한다고 한다. + +|방식|설명| +|-|-| +|cut-through|수신된 frame의 목적지 주소만 확인 후 forwarding| +|store-and-forwarding|수신 frame 전체 수신 및 체크 후 forwarding| +|Fragment Free|Frame 앞 64byte만 읽고 에러 처리, 포워딩| + +장점 +- 구조가 간단하고 가격이 저렴하지만, 신뢰성과 성능이 높다. +- CPU에 의한 소프트웨어 처리가 아닌 ASIC 하드웨어에 의한 수행으로 인해 속도가 빠르고, 네트워크를 미세하게 분할하기 때문에 실사용자에게 대역폭을 최대한 할당할 수 있다. +- 특정 세그먼트로의 트래픽을 고립 시켜 전체 가용 대역폭을 증가시킨다. +- LAN 스위치에 의한 가상 LAN 기능을 구현할 수 있다. + +단점 +- 브로트캐스트 패킷에 의해 성능 저하가 발생할 수 있다. +- 라우팅 및 상위 레이어 프로토콜을 이용한 스위칭이 불가능하다. + +보통 스위치라고 하면 이 L2 스위치를 일컫는 것이라 생각하면 된다. + +## L3 + +L3 스위치는 자신에게 온 패킷의 목적지가 외부에 존재하는 IP일 경우 그 패킷을 외부에 연결된 라우터로 보내줄 수 있다. 라우터 기능도 탑재되어 라우터와의 경계가 모호한 편이다. + +L3 스위치로 포트간 패킷 스위칭을 위해 패킷의 IP나 IPX 주소를 읽어서 스위칭을 하며 통신 경로를 한번만 설정한다. 해당 프로토콜을 쓰는 패킷에 대해 스위칭이 가능하고, IP나 IPX 주소가 OSI 7계층 중 3계층에 해당함으로 L3 스위치라 한다. L2 스위치에 라우팅을 추가하고 고성능 하드웨어를 대부분 기초로 하였기 때문에 기본구성은 L2와 동일하다. + +장점 +- 브로드캐스트 트래픽으로 인한 성능 저하가 방지된다. +- 트래픽을 체크할 수 있다. +- 가상 랜, 라우터 등의 많은 부가 기능들을 가진다. + +단점 +- 특정 프로토콜을 이용해야 스위칭이 가능하다. + +## L4 + +L4 스위치는 웹 트래픽, FTP 트래픽과 같이 정해진 서비스 포트를 보고 트래픽을 스위칭해주는 장비이다. L3와 같이 프로토콜을 기반으로 하며, 어플리케이션별로 우선 순위를 두어 스위칭이 가능하다. + +여러대의 서버를 1대처럼 묶을 수 있는 부하 분산 (Load Balancing) 기능 또한 제공한다. + +## L7 + +L7 스위치는 데이터 안의 실제 내용까지 조회해 보고 특정 문자열이나 특정 명령을 기준으로 트래픽을 스위칭한다. + +HTTP의 URL, 또는 FTP의 파일명, 쿠키 정보, 특정 바이러스의 패턴 등을 분석하여 보안에 더 유리하고 정교한 로드 밸런싱이 가능해진다. 해커의 공격을 막는 웹 방화벽, 보안 스위치 등이 여기에 포함될 수 있다. + +--- +참고 +- http://wiki.hash.kr/index.php/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC_%EC%8A%A4%EC%9C%84%EC%B9%98 diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/bandwidth.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/bandwidth.md" new file mode 100644 index 00000000..9307eb81 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/bandwidth.md" @@ -0,0 +1,36 @@ +--- +title: 'bandwidth' +lastUpdated: '2024-03-02' +--- + +> Bandwidth is the amount of data that can be transmitted in a specified amount of time. + +Bandwidth is a measure of how much information a network, a group of two or more devices that communicate between themselves, can transfer. Data moves from A to B just as water flows through pipes from a supply point to our faucets. The volume that's transported varies, impacting how effectively a transmission medium, such as an internet connection, operates. + +Internet service providers (ISPs) typically denote bandwidth speeds in millions of bits per second (Bps), or megabits (Mbps), and billions of Bps, or gigabits (Gbps). Generally speaking, the higher the bandwidth, the quicker a device downloads information from the internet, including emails or streamed movies. + +- Bandwidth is the data transfer capacity of a network in bits per second (Bps). +- Bandwidth is a measure of how much information a network can transfer. +- The volume of data that can be transported varies, impacting how effectively transmission medium, such ass an internet connection, operates. +- Internet service providers(ISPs) typically denote bandwidth speeds in million of bits per second (Bps), or megabits (Mbps), and billions of Bps, or gigabits (Gbps). +- Generally speaking, the higher the bandwidth, the quicker your devices download information from the internet. + +### Recording Bandwidth + +Any device can measure the bandwidth it has at any given time. Special websites or the Internet Service Provider can calculate the bandwidth by sending a file through the connection and then waiting for the information to return. + +### Bandwidth Requirements + +The amount of bandwidth required to surf the web seamlessly depends on the task the user wishes to undertake. + +For instance, an instant messaging conversation may use 1,000 bits, or one kilobit, per second in bandwidth. A voice-over internet conversation, in which someone's voice transmits through internet connections, typically uses more than 1 Mbps to 3 megabits per second. + +Moving further up the scale, standard-definition video takes 1 Mbps, while 4k needs at least 15 Mbps. The highest available, 8k, requires speeds of nearly 100 Mbps for seamless viewing. + +### What Is a Good Bandwidth? + +A good bandwidth allows you to transmit and receive the amount of data you need without overloading your connection's capacity. A medium business might require at least 25 Mbps down and 3 Mbps up speeds, while a smaller business could use less. However, the faster your bandwidth is, the better it is for you and your customers. + +--- +reference +- https://www.investopedia.com/terms/b/bandwidth.asp#:~:text=our%20editorial%20policies-,What%20Is%20Bandwidth%3F,at%20a%20point%20in%20time. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/error/Context\342\200\205deadline\342\200\205exceeded.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/error/Context\342\200\205deadline\342\200\205exceeded.md" new file mode 100644 index 00000000..5cbde4a1 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/error/Context\342\200\205deadline\342\200\205exceeded.md" @@ -0,0 +1,55 @@ +--- +title: 'Context deadline exceeded' +lastUpdated: '2024-03-02' +--- + +When a `context is canceled` or `its deadline is exceeded`, all operations associated with that **context are terminated, and the corresponding functions return with an error**. The error message "context deadline exceeded" indicates that the operation took longer than the deadline specified in the context. + +In the example below, a context with a deadline of 2 seconds is created using context.WithTimeout. However, the performOperation function intentionally simulates a long-running operation that takes 3 seconds to complete. As a result, when the operation is executed with the context, the context deadline exceeded error is returned. + +```go +package main + +import ( + "context" + "fmt" + "time" +) + +func main() { + // Create a context with a deadline of 2 seconds. + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // Simulate a long-running operation that takes more than 2 seconds. + time.Sleep(3 * time.Second) + + // Perform some operation with the context. + if err := performOperation(ctx); err != nil { + fmt.Println("Error:", err) + } +} + +func performOperation(ctx context.Context) error { + select { + case <-ctx.Done(): + // The context deadline has been exceeded. + return ctx.Err() + default: + // Perform the operation + fmt.Println("Operation completed successfully") + return nil + } +} +``` + +## Solutions: + +Before being able to solve the problem, you need to determine what is actually failing. + +- **Investigate Network connectivity** + - Can required systems communicate in general? Are there any firewalls in place that can be preventing communication? If in the cloud, are the instances in the same VPC/Network? Do the instances belong to the correct/expected Security Groups? +- **Resource Contention** + - Are we asking too much of the underlying provisioned infrastructure? What is our CPU/Memory usage? +- **Slow I/O** + - Are we using an external storage backend? Are we seeing I/O wait? Are we in the cloud? Do we have enough IOPS provisioned for our storage? \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\352\260\200\354\203\201\355\231\224\342\200\205\352\270\260\354\210\240.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\352\260\200\354\203\201\355\231\224\342\200\205\352\270\260\354\210\240.md" new file mode 100644 index 00000000..6592db76 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\352\260\200\354\203\201\355\231\224\342\200\205\352\270\260\354\210\240.md" @@ -0,0 +1,102 @@ +--- +title: '가상화 기술' +lastUpdated: '2024-03-02' +--- + +> 가상화는 컴퓨터에서 컴퓨터 리소스의 추상화를 일컫는 광범위한 용어이다. "물리적인 컴퓨터 리소스의 특징을 다른 시스템, 응용 프로그램, 최종 사용자들이 리소스와 상호 작용하는 방식으로부터 감추는 기술"로 정의할 수 있다. 이것은 다중 논리 리소스로서의 기능을 하는 것처럼 보이는 서버, 운영 체제, 응용 프로그램, 또는 저장 장치와 같은 하나의 단일 물리 리소스를 만들어 낸다. 아니면 단일 논리 리소스처럼 보이는 저장 장치나 서버와 같은 여러 개의 물리적 리소스를 만들어 낼 수 있다. +> 출처: 위키백과 + +- 네트워크에서는 다양한 가상화 기술이 사용되고 있다. +- 가상화 기술을 이용하면 리소스를 더 효율적으로 사용할 수 있고 운영 비용이나 도입 비용을 줄일 수 있다. +- 기존 레거시 환경의 문제점을 해결할 수도 있다. +- 그러나 다른 기술과 마찬가지로, 부적절한 상황에 쓰이면 악영향을 주기에 현재 인프라에 꼭 필요한 상황인지 판단해야한다. + +- 가상화 기술은 크게 두가지로 나눌 수 있다. + +- **여러 개의 물리 장비를 하나의 논리 장비로 합치는 기술** + - 다수의 장비를 하나처럼 관리하여 관리 부하가 줄어든다. + - 여러개의 물리 스위치를 묶어 논리스위치를 만들면 이중화 경로를 효율적으로 사용하고 루프 문제를 제거할 수 있다. (페일오버 시간이 짧음) + - LACP와 MC-LAG이 이 범주에 속한다. + + +- **하나의 물리 장비를 여러 개의 논리 장비로 나누는 기술** + - 스위치에서 설명한 VLAN 기술이 이 범주에 속한다. + - VM(ex. VMware의 ESXi)과 같이 하나의 물리서버에 여러개의 가상 서버를 구성하는 기술도 이 범주에 속한다. + - 관리 포인트가 감소하고, 자원활용률이 증가한다. + - 전체 장비 물량이 줄어 도입 비용과 운영 비용이 절감된다. (Capex & Opex) + - 비교적 성능 저하가 있을 수 있기에 용량 산정에 신경써야한다. + +## 벤더별 장비 가상화 기술 + +각 벤더별로 제공하는 가상화 기술들에 대해 알아보자. + +### 하나의 논리 장비로 만드는 가상화 + +- **Cisco Systems** + - **VSS(Virtual Switching System)**: + - VSL(Virtual Switching Link)로 장비를 연결해 하나의 가상 스위치를 만든다. 시스템에서 운영할 수 있는 대역폭을 확장하고 가용성을 증대시킨다. + - **StackWise/FlexStack**: + - VSS는 최대 두개의 장비를 하나의 가상 스위치로 구성할 수 있지만, StackWise나 FlexStack은 최대 8~9개의 스위치를 하나의 가상 스위치로 구성할 수 있다. + - 스택 구성을 위해 일반 포트가 아닌 스위치 후면의 스택 구성용 모듈이나 케이블을 이용해 데이지 체인(Daisy Chain) 형태로 구성한다. + - **FEX(Fabric Extender)**: + - FEX는 하나의 스위치를 다른 장비의 모듈 형태로 구성하는 기술이다. + - 하나의 스위치가 다른 스위치의 모듈 형태로 구성되어, 상단 스위치의 운영체제를 그대로 사용한다. + + image + +- **주니퍼** + - **가상 섀시(Virtual Chassis)**: + - 가상 섀시는 EX와 QFX 시리즈 스위치에서 지원되는 가상화 기술이다. 최대 10개의 스위치를 링 형태의 토폴로지로 연결해 하나의 장비처럼 관리한다. + - **VCF** + - 가상 섀시와 마찬가지로 EX와 QFX 시리즈 스위치에서 지원되는 가상화 기술이다. + - 스파인-리프 형태로 디자인하여 최대 20대의 장비를 패브릭 멤버로 구성할 수 있다. + - 16개 랙 규모의 PoD(Point of Delivery) 사이즈에 적합하다. + + image + + - **Junos Fusion** + - VCF보다 대규모에 적용될 수 있다. 유형에 따라 3가지 아키텍처로 나뉘며 각 기술에 따라 지원되는 장비 모델이 다르다. + - Junos Fusion Provider Edge + - Junos Fusion Data Center + - Junos Fusion Enterprise + +- **익스트림(Extreme)** + - **VCS(Virtual Cluster Switching)** + - 최대 48개의 VCS 패브릭을 한 대의 가상 스위치로 만들어 관리할 수 있다. + - 스위치 간 ISL(Inter-Switch Link)를 구성하는 것 만으로 클러스터에 연결 가능하다. + - 쉬운 Scale-Out을 지원한다. + + image + +- **HP Networking** + - **IRF(Interligent Resilent Fabric)** + - 다른 벤더가 하나의 가상 스위치로 구성할 수 있는 제품이 제한적인 반면 HF IRF는 모든 제품에서 가상화는 지원한다. + - 다만 IRF로 구성할 때는 동일한 모델끼리 구성해야한다. + +### 여러 개의 논리 장비로 만드는 가상화 + +- **Cisco Systems** + - **VDC** + - VDC 기능으로 하나의 물리 스위치를 8개의 논리 스위치로 나눠 사용할 수 있다. + - 전체 VDC를 관리할 수 있는 별도의 관리 VDC도 지원한다. + - VDC별 데이터 트래픽은 완전치 분리되므로 VDC 간 통신시에는 물리 케이블 등 일반적인 구성이 필요하다. + +- **F5** + - **vCMP(Virtual Clusted Multi Processing)** + - 장비를 Guest 장비로 나누어 개별적으로 자원을 할당할 수 있다. + - 가상화 장비 간 다른 버전의 운영체제를 사용할 수도 있다. + + image + + +- **포티넷** + - **포티게이트** + - VDOM 가상화 기술을 사용해 물리 장비를 여러 대의 논리장비로 나눈다. + - VRF(Virtuual Routing and Forwarding)의 중간 수준의 가상화 기능을 제공한다. + - 라우팅 뿐만 아니라 전체 단위로 시스템 전체를 가상화한다. + +--- +참고 +- https://ko.wikipedia.org/wiki/%EA%B0%80%EC%83%81%ED%99%94 +- https://www.packetmischief.ca/2012/08/28/what-the-fex-is-a-fex-anyways/ +- https://www.juniper.net/documentation/us/en/software/junos/virtual-chassis-fabric/topics/concept/vcf-overview.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254.md" new file mode 100644 index 00000000..a523377a --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254.md" @@ -0,0 +1,61 @@ +--- +title: '네트워크' +lastUpdated: '2023-06-16' +--- +## 네트워크 + +**네트워크:** 서로 다른 컴퓨터끼리 데이터 주고 받기 + +**프로토콜:** 네트워크 통신을 하기 위한 규칙 + +데이터를 주는 사람과 받는 사람은 서로 약속해놓은 방식(프로토콜)으로 통신 + +--- + +**MAC:** 기기 주소 (장치별로 다 다름, 장비 식별용) + +**IP:** 어느 네트워크의 어느 컴퓨터인지를 식별하는 주소 + +네트워크 번호(Network Part)와 컴퓨터 번호(Host Part)를 조합하여 만들어짐 + +네트워크가 바뀌면 IP도 바뀌기 때문에 **동적**이다 + +- MAC이 고유한 주소면 IP를 또 사용할 필요가 없는 것 아닌가? + + IP는 ***라우팅*** 하기에 적합하게 설계된 형태로, 주로 Area code를 포함하고 있음 + + ex) 만약에 편지를 주소가 아닌 주민등록번호로 보낸다면 어떻게 될까? + + +**ARP:** 보통 network 통신에서는 IP를 목적지로 정함 + +→ 하지만 IP는 동적이기 때문에 실제 기기로 데이터를 보내는 마지막 지점에선 MAC 필요 + +ARP 프로토콜이 IP와 MAC 주소를 매칭시킨 ARP Table을 가지고 매칭시켜줌 + +(모든 단말이 각각 가지고 있음) + +--- + +**스위치:** 받은 패킷을 어느쪽으로 보낼지 결정하는 역할을 하는 하드웨어. MAC 주소를 기준으로 함 + +****라우터:**** 네트워크 내에서 데이터를 전송할 때 최적의 경로를 선택하는 하드웨어. IP를 기준으로 함 + +- 하나의 라우터가 결정하는 것은 경로의 일부 +- 다음 라우터로 정보 쏴줌 +- 라우팅 테이블에 다음 라우터의 IP 주소를 가지고 있음 + +image + + +최종 목적지는 IP로 지정했지만 라우터와 라우터간 이동 할때는 다음 라우터의 MAC주소를 기준으로 쏜다 + +중간과정에서 IP 주소를 MAC 주소로 변환하는 ARP를 또 사용함 + + +image + + +패킷을 다른 컴퓨터로 보내는 과정을 간단하게 표현하면 아래 그림과 같다 + +image diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\353\263\264\354\225\210.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\353\263\264\354\225\210.md" new file mode 100644 index 00000000..6f37bd0f --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\353\263\264\354\225\210.md" @@ -0,0 +1,166 @@ +--- +title: '네트워크 보안' +lastUpdated: '2024-03-02' +--- + +## 정보 보안 + +IT에서 다루는 정보 보안은 "다양한 위협으로부터 보안을 보호하는 것"을 뜻한다. + +3대 보안 정의는 다음과 같다. + +- 기밀성(Confidentiality) + - 인가되지 않은 사용자가 정보를 보지 못하게 하는 작업이다. + - 대표적인 기밀성은 암호화 작업이다. +- 무결성(Integriality) + - 정확하고 완전한 정보 유지에 필요한 모든 작업을 말한다. 누군가가 정보를 고의로 훼손하거나 중간에 특정 이유로 변경이 가해졌을 때, 그것을 파악해 잘못된 정보가 전달되거나 유지되지 못하게 하는 것이 무결성이다. + - IT의 대표적인 무결성 기술은 MD5, SHA와 같은 Hash 함수를 이용해 변경 여부를 파악하는 것이다. +- 가용성(Availability) + - 정보가 필요할 때, 접근을 허락하는 일련의 작업이다. 우리가 유지하는 정보에 대해 사고날 염려 없이 온전한 상태를 유지하는 것도 정보보안의 일부분이다. + +## 네트워크 보안 개념 + +### **네트워크 분류** +- **Trust network**: 외부로부터 보호받아야 할 네트워크 +- **Untrust network**: 신뢰할 수 없는 외부 네트워크 +- **DMZ(DeMilitarized Zone) network**: 외부 사용자에게 개방해야 하는 서비스 네트워크 + - 일반적으로 인터넷에 공개되는 서비스를 이 네트워크에 배치한다. + +### **네트워크 보안 구분** +이 구분에 따라 요구되는 성능과 기능이 다르기 때문에 보안 장비를 구분할 때도 사용된다. +- **Internet Secure Gateway**: Trust(또는 DMZ) 네트워크에서 Untrust 네트워크로의 통신을 통제 + - 인터넷으로 나갈 때는 인터넷에 수많은 서비스가 있으므로 그에 대한 정보와 요청 패킷을 적절히 인식하고 필터링하는 기능이 필요하다. + - SWG(Secure Web Gateway), Web Filter, Application Control, Sandbox와 같은 다양한 서비스나 네트워크 장비가 포함된다. +- **Data Center Secure Gateway**: Untrust 네트워크에서 trust(또는 DMZ)로의 통신을 통제 + - 상대적으로 고성능이 필요하고 외부의 직접적인 공격을 막아야 하므로 인터넷 관련 정보보다 공격 관련 정보가 더 중요하다. + - IPS, DCSG(DataCenter Secure Gateway), WAF(Web Application Fire Wall), Anti-DDoS 등의 장비가 이 분류에 속한다. + +### **네트워크 보안 정책 수립에 따른 분류** + +네트워크 보안 장비는 화이트리스트, 블랙리스트 중 하나만 목표로 할 때도 있다. 하지만 대부분의 장비는 수립 정책에 따라 화이트리스트 기법과 블랙리스트 기법 모두 사용할 수 있다. + +- **화이트리스트**: 방어에 문제가 없는 통신만 허용하는 방식 + - IP와 통신 정보에 대해 명확히 아는 경우에 많이 사용된다. + - 정보를 확실히 알고 세부적으로 통제해야하므로 많은 관리가 필요하다. +- **블랙리스트**: 공격이라고 판단되거나 문제가 있었던 IP, 패킷 리스트를 차단하는 방식 + - IPS, 안티바이러스, WAF들이 보통 블랙리스트 기반의 방어 기법을 제공한다. + +### **탐지 에러 타입** + +- IPS나 안티 바이러스와 같은 네트워크 장비에서는 공격 DB에 따라 공격과 악성 코드를 구분해 방어한다. +- 하지만 DB를 아무리 정교하게 만들어고 공격으로 탐지하지 못하거나 공격이 아닌데도 공격으로 감지해 패킷을 드롭시킬 때가 있다. 블랙리스트 기반 데이터베이스를 이용한 방어는 이런 문제점이 있으므로 장비 도입시 정교한 튜닝이 필요하다. +- 공격을 탐지할 때 예상한 결과가 나오는 것을 정상 탐지라고 하고, 예상과 다른 결과가 나오는 것을 오탐지, 미탐지라고 한다. 예상했던 상황과 탐지 결과에 따라 4가지 경우가 나타날 수 있다. + image + +## 보안 솔루션의 종류 + +해커들의 공격이 발전함에 따라 보안 진영에서도 다양한 개발이 진행되고 있다. 새 장비가 출시되면 기존 보안 장비에 새로운 보안 솔루션을 함께 쓰는 형태가 되므로 보안은 타 IT 분야보다 복잡한 형태를 띈다. 위치, 역할에 따라 다양한 장비가 있다. + +image + +### DDoS 방어 + +- DoS 공격은 'Denial of Service' 공격의 약자로, 다양한 방법으로 공격 목표에 서비스 부하를 가해 정상적인 서비스를 방해하는 기법이다. 요청이 한 출발지에서 온다면 IP 기반으로 방어할 수 있지만, 다수의 봇을 이용해 분산 공격을 수행하면 해당 방어를 회피하면서 더 강력하게 공격할 수 있다. + +- DDoS 장비는 회선 사용량 이상의 트래픽을 발생시켜 회선 사용을 방해하는 Volumetric Attack을 막기 위해, 회선을 공급해주는 ISP나 그 경계에서 공격을 완화한다. + +image + +- **DDos 공격 방식** + - **볼류메트릭 공격**: 대용량의 트래픽을 사용해 공격 대상의 대역폭을 포화시키는 공격 + - 좀비 PC나 증폭기술을 사용해 생성된다. + - 이 공격은 회선 사업자 차원에서 미리 방어되어야 하므로 DDos 방어를 위한 서비스 가입이나 회선 사업자의 도움이 별도로 필요하다. 혹은 Clodu DDos 솔루션을 통해 서비스 네트워크로 트래픽이 직접 도달하지 못하도록 조치해야 한다. + - **프로토콜 공격**: 3, 4계층 프로토콜 스택의 취약점을 악용해 대상을 액세스할 수 있게 만드는 공격 + - 공격 대상 장비의 CPU, 메모리 자원을 고갈시켜 정상적인 서비스가 불가능하게 한다. + - **애플리케이션 공격**: 7계층 프로토콜 스택의 약점을 악용하는 공격. + - 가장 정교한 공격이라 식별, 완화에 까다롭다. + - 애플리케이션 프로토콜 자체의 취약점이나 서비스를 제공하는 플랫폼의 취약점을 악용하는 경우가 많다. + +### VPN + +- 터널링 프로토콜로 감싸 가상 네트워크를 만들어 통신. 패킷 암호화 및 인증을 통해, 해커나 기관의 감청을 방지할 수 있다. + +- VPN 형태 + 1. Host to Host 통신 보호 + - 두 호스트간에 직접 VPN 터널을 연결한다. + 2. Network to Network 통신 보호 + - 본사-지사와 같이 특정 네트워크를 가진 두 종단을 연결 + - IPSEC 프로토콜 스택이 많이 사용된다 + 3. Host가 Network로 접근할 때 보호 + - 모바일 사용자가 인터넷망을 통해 사내망으로 연결 + - IPSEC과 SSL 프로토콜이 범용적으로 사용된다 + +### 방화벽 + +- 방화벽은 4계층에서 동작하는 패킷 필터링 장비이다. 3, 4계층 정보를 기반으로 정책을 세워 해당 정책과 매치되는 패킷이 방화벽을 통과하면 그 패킷을 Allow 혹은 Deny할 수 있다. + +- 3,4 계층 헤더 중 필터링 시 참고하는 5가지 주요필드(Source IP, Destination IP, Protocol No, Source Port, Destination Port)를 5-Tuple이라 부른다. + +- 방화벽은 TCP 컨트롤 플래그에 따라 동작 방식이 변하거나 시퀀스가 ACK 번호가 갑자기 변경되는 것을 인지해 세션 탈취 공격을 일부 방어할 수 있다. + +- 방화벽은 상태 기반 엔진(SPI)을 가지고 있어야 한다. 통신의 양방향성을 고려해서, 세션에 존재하는 패킷은 그냥 내보낸다. 세션 로깅을 통해 통신 문제를 디버깅할 수도 있다. + +- ASIC이나 FPGA와 같은 전용 칩으로 가속하면 대용량 처리가 가능하다. + +### IDS(Intrusion Detection Systen), IPS(Intrusion Prevention System) + +- IDS와 IPS는 방화벽에서 방어할 수 없는 다양한 애플리케이션 공격을 방어하는 장비이다. + - **IDS, 침입 탐지 시스템**: 공격자가 시스템을 해킹할 때 탐지하는 역할을 함 + - 공격에 개입하거나 방어하지 않는다 + - 패킷을 복제하여 탐지한다. + - **IPS, 침입 방지 시스템**: 공격이 발견되면 직접 차단한다. + - 트래픽이 지나가는 인라인상에 배치된다. + - Host 기반 IPS: 엔드포인트 보안 + - Network 기반 IPS: 네트워크 보안 + +- 과거에는 IDS와 IPS를 구분했지만 최근에는 애플리케이션 공격을 방어하는 장비를 IPS로 통칭한다. + +- 방화벽은 3, 4계층만 담당하기 때문에 한계가 있는 반면, IPS는 3~7 계층을 탐지, 방어한다. + +- IDS와 IPS는 사전에 공격 데이터베이스(Signature)를 제조사나 위협 인텔리전스(Threat Intelligence) 서비스 업체로부터 받는다. 이후 IPS 장비에 인입된 패킷이 보유한 공격 데이터베이스에 해당하는 공격일 떄, 차단하거나 모니터링한 후 관리자에게 알람을 보내 공격 시도를 알린다. + +- IPS는 오탐이 많이 발생하므로 환경에 맞는 튜닝 작업이 필요하고, 관리인력이 계속해서 최적화해주어야 한다. IPS의 기능을 향상시켜 문제점을 해결한 NGIPS(Next Generation IPS) 개념의 장비가 출시되고 있기도 하다. + +- 최근에는 데이터 센터 영역의 보안을 위해 방화벽과 IPS 장비를 통합한 DCSG 장비 시장이 커지고 있다. + +- **동작방식** + - **패턴 매칭 방식(Signature 방식)**: 기존 공격이나 취약점을 통해 공격 방식에 대한 데이터베이스를 습득하고 그 내용으로 공격을 파악한다. + - 최신 공격 방식을 공격 데이터베이스에 빠르게 반영하는 것이 중요하다. + - **Anoramly 공격 방어**: 화이트리스트 기반의 방어기법 + - **프로파일 Anormaly**: 관리자가 정해놓은 기준이나 IPS가 모니터링해 정한 기준과 다른 행위가 일어나면 공격으로 판단한다. + - **프로토콜 Anormaly**: 프로토콜이 Known port가 아닌 port로 요청할 때 파악하여 제어한다. + +### WAF + +- WAF는 HTTP, HTTPS처럼 웹 서버에서 동작하는 웹 프로토콜의 공격을 방어하는 전용 보안 장비이다. IDS/IPS보다 범용성이 떨어지지만 웹 프로토콜에 대해선 더 세밀히 방해한다. + +- WAF는 다양한 형태의 장비나 소프트웨어로 제공된다. + + - 전용 네트워크 장비 + - 웹 서버의 플러그인 + - ADC 플러그인 + - 프록시 장비 플러그인 + +- WAF는 IPS 회피 공격을 막을 수 있다. WAF는 프록시 서버와 같이 패킷을 데이터 형태로 조합해 처리하기 때문에 회피하기 어렵다. + +- 민감한 데이터가 유출될 때, 그 정보만 제거해 보내는 등의 처리가 가능하다. + +### 샌드박스 + +- 보호된 영역 내에서 프로그램을 동작시키는 것으로, 외부 요인에 의해 악영향이 미치는 것을 방지하는 보안 모델. 이 모델에서는 외부로부터 받은 프로그램을 보호된 영역, 즉 '상자'안에 가두고 나서 동작시킨다. '상자'는 다른 파일이나 프로세스로부터는 격리되어 내부에서 외부를 조작하는 것이 금지되어 있다. + +- 즉, 외부에서 바이러스 감염이나 해킹 등의 도구로 사용이 의심되는 파일들을 받게 되면 샌드박스 기능을 가진 솔루션은 이 자료를 독립된 가상 공간으로 이동시키고 거기서 나름의 검사 등을 수행한다. 이 때 그 자료가 악의적인 의도를 가진 자료라 하더라도 가상으로 독립된 공간에서 발견된 것이므로 처리나 대처가 용이하다. + +### NAC(Network Access Control) + +- NAC는 네트워크에 접속하는 장치들을 제어하기 위해 개발되었다. 인가된 사용자만 내부망에 접속할 수 있고 인가받기 전이나 승인에 실패한 사용자는 접속할 수 없도록 제어하는 기술이다. + +### 접근 통제 + +- 서버나 데이터베이스에 대한 직접적인 접근을 막고 작업 추적 및 감사를 할 수 있는 접근 통제 솔루션이다. +- Agent 기반, Agentless 등 여러 구현 방법이 있지만 대부분 Bastion Host 기반으로 구현된다. 서버 접근을 위한 모든 통신이 Bastion Host를 항상 거치도록 하는 것이다. + +--- +참고 +- [IT 엔지니어를 위한 네트워크 입문](https://m.yes24.com/Goods/Detail/93997435) +- http://word.tta.or.kr/dictionary/dictionaryView.do?subject=Sandbox#:~:text=TTA%EC%A0%95%EB%B3%B4%ED%86%B5%EC%8B%A0%EC%9A%A9%EC%96%B4%EC%82%AC%EC%A0%84&text=%EB%B3%B4%ED%98%B8%EB%90%9C%20%EC%98%81%EC%97%AD%20%EB%82%B4%EC%97%90%EC%84%9C,%EB%A7%90%EC%9D%B4%20%EC%96%B4%EC%9B%90%EC%9D%B4%EB%9D%BC%EA%B3%A0%20%EC%95%8C%EB%A0%A4%EC%A0%B8%20%EC%9E%88%EB%8B%A4. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\354\271\250\355\225\264.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\354\271\250\355\225\264.md" new file mode 100644 index 00000000..7300eb78 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\354\271\250\355\225\264.md" @@ -0,0 +1,97 @@ +--- +title: '네트워크 침해' +lastUpdated: '2024-03-02' +--- + +### 스니핑 + +한 서브 네트워크 내에서 전송되는 패킷의 내용을 임의로 확인하는 공격 + +중요한 데이터는 SSL와 같은 암호화 통신 방식을 사용함으로써 대응한다. + +### 스푸핑 + +네트워크 서비스 혹은 패킷 정보를 임의로 변경하여 공격에 사용하는 기법 + +IP 주소, DNS 주소, MAC 주소 등의 정보를 변조하여 공격의 탐지 및 역추적이 어렵다. + + - **IP 스푸핑** + - TCP/IP 구조의 취약점을 악용, 공격자가 자신의 IP를 변조해 IP 기반 인증 등의 서비스를 무력화한다. + - IP 기반 인증을 최소화하고 TCP 시퀀스 번호를 랜덤으로 지정해 대응한다. + + - **ARP 스푸핑** + - ARP 프로토콜의 취약점을 이용, IP-MAC 매핑 정보를 브로드캐스트해 ARP 테이블의 정보를 변조한다. + - `arp -s ip mac` 명령어로 정적 ARP 매핑을 등록한다. + + - **DNS 스푸핑** + - DNS 요청에 위조된 정보를 응답 패킷으로 보낸다. + +### Dos + +Dos 공격은 공격 대상의 시스템 및 네트워크 취약점 등을 공격하여 부하를 발생시켜 정상적인 서비스를 불가능하게 만드는 가용성 침해 공격이다. + + - **Ping of Death** + - ICMP Echo 패킷을 크게 전송하여 부하를 유도한다. + + - **Teardrop Attack IP** + - fragmentation에 따라 패킷의 오프셋을 임의로 변조해 과부하를 유도한다. + + - **TCP Syn Flooding** + - Syn 요청을 대량으로 보내고, 응답 패킷을 전송하지 않는다. + + - **UDP Flooding** + - 대량의 UDP 패킷을 전송해 자원을 소모시킨다. + + - **Land Attack** + - 발신자와 수신자 IP 주소를 대상의 IP로 패킷을 보낸다. + + - **Smurt Attack** + - ICMP Rechest 패킷을 브로드캐스트를 통해 공격 대상의 ip 주소로 ICMP Echo Reply 패킷을 보낸다. + + - **Mail Bomb** + - 동일한 이메일 주소를 대상으로 대량의 메일을 동시에 발송하는 기법 + + - **NTP 증폭 공격** + - NTP 서비스의 monist 요청 방식을 악용해 적은 공격 패킷이 증폭되어 공격 대상으로 보내진다. + +### DDoS + +공격을 위한 에이전트를 분산된 시스템에 배치한 후 DoS 공격을 동시에 실행하는 공격 기법이다. + +공격자는 C&C 서버에 공격을 명령한다. 그러면 C&C 서버 내의 Zombie PC들이 일제히 공격을 시작한다. + +DDoS 공격을 위해서는 다음의 다양한 도구들이 사용될 수 있다. + +- **Stacheldraht** + - Linux 시스템용 DDoS의 에이전트 역할을 하는 악성코드. + +- **TFN** + - 공격자 시스템과 마스터 시스템간 연결을 평문으로 해, 브로드캐스트 공격에 사용된다. + +- **TFN 2K** + - TFN의 발전된 형태로 UDP, TCP, ICMP를 복합적으로 사용하고 포트도 임의로 결정하여 암호화 사용. + - 지정된 TCP 포트에 백도어 실행 가능 + +- **Trinoo** + - UDP Flooding DDoS를 할 수 있는 통합 도구 + +### Dos, DDoS 공격의 대응 방안 + +- 침입차단시스템을 이용해 패킷 및 포트 필터링 수행 +- 침입탐지시스템을 이용해 공격 탐지 +- 로드 밸런싱을 통한 대용량 트래픽 분산 처리 +- 불필요한 서비스 제거 및 포트 닫기 + +### 해킹 프로그램을 이용한 공격 + +1. 무작위 공격 + - 사전 파일을 기반으로 임의의 정보를 무작위로 대입하여 인증을 우회 + - 대표적인 공격 도구로는 John the Ripper가 있다. + - 입력 회수를 제한하면 공격에 대응할 수 있다. +2. 기타 + - **Prorat**: 해킹 대상 시스템에 원격 접속 및 제어해 해킹을 수행하는 프로그램 + - **Netbus**: 네트워크를 통해 윈도우 컴퓨터를 원격으로 제어하는 소프트웨어 + - **Back orifice**: 윈도우 TCP/IP 기능으로 시스템끼리 원격 제어 + - **랜섬웨어**: 시스템을 잠그거나 데이터를 암호화한 후 금전을 요구하는 악성 프로그램 + - **트로이목마**: 정상적인 프로그램으로 가장했으나 내부에 악성 코드를 가진 프로그램 + - **웜바이러스**: 스스로 실행되는 악성 소프트웨어로 자기 복제와 다른 프로그램으로 전파된다. diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\241\234\353\223\234\353\260\270\353\237\260\354\204\234.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\241\234\353\223\234\353\260\270\353\237\260\354\204\234.md" new file mode 100644 index 00000000..6a6f0352 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\241\234\353\223\234\353\260\270\353\237\260\354\204\234.md" @@ -0,0 +1,156 @@ +--- +title: '로드밸런서' +lastUpdated: '2024-03-02' +--- + +## 부하 분산 +- 서비스 규모가 커질 때, 물리, 가상 서버 한 대로는 모든 서비스를 수용할 수 없게 된다. +- 서비스 가용성을 높이기 위해서 보통 하나의 서비스를 두 대 이상의 서버로 구성하는데, 각 서버 IP 주소가 다르기 때문에 사용자가 서비스를 호출할 때는 어떤 IP 로 서비스를 요청할 지 결정해야 한다. +- 이런 문제점 해결을 위해서, L4, L7 로드밸런서를 사용한다. + + image +- 로드 밸런서는 부하를 다수의 장비에 분산시키기 위해 가상 IP 주소를 갖게 된다. 사용자가 VIP 로 서비스를 요청하게 되면, 해당 VIP에 연결된 실제 서버의 IP로 해당 요청을 전달한다. + +## 헬스 체크 + +- 로드 밸런서에서는 주기적으로 부하 분산을 하는 각 서버의 서비스를 주기적으로 헬스 체크해 정상적인 서비스로만 트래픽이 갈 수 있도록 체크한다. + +- **헬스 체크 방식** + - **ICMP** + - 서버에 대해 ICMP(ping)로 헬스 체크를 수행하는 방법이다. + - 서버가 살아 있는지 여부만 확인할 수 있다. + - **TCP 서비스 포트** + - 가장 기본적인 헬스 체크 방법은 로드 밸런서에 설정된 서버의 서비스 포트를 확인하는 것. + - 실제 서비스 포트에 3 way handshake 후, FIN을 보내 헬스 체크를 종료한다. + - **TCP 서비스 포트 : Half Open** + - 일반 TCP 서비스 포트로 인한 부하를 줄이거나, 빨리 헬스 체크 세션을 끊기 위해서 사용하는 방식이다. + - SYN 을 보내고, SYN,ACK 를 받은 후 ACK 대신 RST 를 보내 세션을 끊는다. → 3 way handshake 과정을 절반으로 줄임 + - **HTTP 상태 코드** + - 웹서비스를 할 때, 서비스 포트까지는 TCP 로 정상적으로 열리지만, 웹 서비스에 대한 응답을 정상적으로 해주지 못하는 경우가 있다. + - 이 때 로드 밸런서 헬스 체크 방식 중 HTTP 상태 코드를 확인하는 방식으로, 3 way handshake 를 거치고, HTTP 를 요청해 200번으로 응답하는지 체크할 수 있다. + - **콘텐츠 확인(문자열 확인)** + - 로드 밸런서에서 서버로 콘텐츠를 요청하고 응답 받은 내용을 확인하여 지정된 콘텐츠가 정상적으로 응답했는지 여부를 확인하는 헬스 체크 방법. + - 보통 특정 웹페이지를 호출해 사전에 지정한 문자열이 해당 웹페이지 내에 포함되어 있는 지를 체크하는 기능. + +## 부하 분산 알고리즘 + +- **라운드 로빈** + - 특별한 규칙 없이 현재 구성된 장비에 순차적으로 돌아가면서 트래픽을 분산한다. +- **최소 접속 방식** + - 서버가 가진 세션 부하를 확인해 현재 세션이 가장 적게 연결된 장비로 서비스 요청을 보내는 방식 + - 로드 밸런서는 서비스 요청을 각 장비로 보낼 때 마다 저장하는 세션 테이블로 현재 세션 수를 알아낸다. +- **해시** + - 서버 부하를 고려하지 않고 클라이언트가 같은 서버에 지속적으로 접속하도록 하기 위해서 사용하는 방식이다. + - 알고리즘 계산에는 주로 출발지 IP, 목적지 IP, 출발지 포트, 목적지 포트가 사용된다. + +## 로드 밸런서 구성 방식 + +- **원암(One-Arm) 구성** + - 부하 분산을 수행하는 트래픽에 대해서만 로드 밸런서를 경유한다. + - LACP이나 분리된 VLAN을 사용하는 경우처럼 같은 다수의 인터페이스로 연결되었더라도 스위치 옆에 있는 구성이라면 동일하게 원암 구성이라 부른다. + + image + +- **인라인(Inline) 구성** + - 모든 트래픽에 대해서 로드 밸런서를 경유한다. + + image + +> 원암 구성과 인라인 구성을 구분하는 것은 물리적 구성 기준이 아니다. 원암 구성과 비슷하게 생겼더라도 모든 트래픽이 로드밸런서를 경유하게 설정되었다면 인라인 구성으로 구분한다. + +## 로드 밸런서 동작 모드 + +### Transparent Mode(L2 모드) + +- 로드 밸런서가 2계층 스위치처럼 동작하는 구성, 서비스하기 위해 사용하는 VIP 주소와 실제 서버가 동일한 네트워크를 사용한다. +- VIP와 실제 서버가 동일한 네트워크를 사용하기 때문에, 로드 밸런서 도입으로 인한 IP 네트워크를 재설계하지 않아도 된다. (손쉽게 구성 가능) +- 트래픽이 로드 밸런서를 거치더라도 부하 분산 서비스를 받는 트래픽에 대해서만 4계층 이상의 기능을 수행한다. +- 원암 / 인라인 구조 모두에서 사용할 수 있다. + +- **동작 흐름** + + image + + - **요청** + 1. 사용자가 L4의 VIP로 요청을 전송한다. + 2. L4에서 Real Server로 전송 시, Destination IP를 VIP에서 Real Server IP로 NAT한다. + 3. 동일한 네트워크 대역이므로 Destination MAC 주소만 변경된다. + - **응답** + 1. Real Server에서 서비스 응답 시, Destination IP가 다른 대역의 IP이므로 Gateway (Router)로 전송한다. + 2. Real Server에서 L4를 거치면서 Source IP를 VIP로 NAT 수행한다. + 3. 동일한 네트워크 대역이므로 MAC 주소는 변경되지 않는다. + +### Router Mode + +- 로드 밸런서가 라우팅 역할을 수행하는 모드. +- LB를 기준으로 Client Side와 Server Side가 서로 다른 네트워크로 분리된다. +- 서버 네트워크를 사설로 구성해 서버에 접속하는 것을 막을 수 있어서 보안을 강화해준다. + +- **동작 흐름** + + image + + - **요청** + 1. 사용자가 L4의 VIP로 요청을 전송한다. + 2. L4에서 Real Server로 전송 시, Destination IP를 VIP에서 Real Server IP로 NAT한다. + 3. 네트워크 대역이 변경되었으므로, Source와 Destination MAC 주소 모두 변경된다. + - **응답** + 1. Real Server에서 서비스 응답 시, Destination IP가 다른 대역의 IP이므로 Gateway (L4)로 전송한다. + 2. L4에서 Source IP를 VIP로 NAT한다. + +### DSR(Direct Server Return) 모드 + +- 요청이 로드 밸런서를 통해서 서버로 유입된 이후, 서버가 사용자에게 직접 응답하는 모드이다. +- 로드 밸런서에서 실제 서버까지의 통신이 L2 / L3 인지에 따라서 두 종류로 구분된다. + - **L2 DRS** : 실제 서버의 네트워크 대역을 로드 밸런서가 가진 경우 + - **L3 DRS** : 실제 서버의 네트워크 대역을 로드 밸런서가 가지지 않은 경우 + +- DRS 모드는 요청 트래픽만 로드 밸런서를 통하기 때문에, 로드 밸런서 자체의 전체 트래픽은 감소한다. 반면, 응답이 로드 밸런서를 경유하지 않기 때문에 문제 발생 시 확인이 어렵다. +- 로드밸런서의 VIP를 서버의 루프백 인터페이스에 설정해주어야 작동한다. + +- **동작 흐름** + + image + + - **요청** + 1. 로드 밸런서 VIP 주소로 서비스를 요청한다. + 2. 목적지 IP를 그대로 유지하고 목적지 MAC 주소만 실제 MAC 주소로 변경한다. + 3. 서버 + - **응답** + 1. 출발지 IP를 서버의 인터페이스 주소가 아닌, 사용자가 요청했던 VIP 주소로 설정해 패킷을 전송한다. 응답 과정에 로드밸런서가 개입하지는 않는다. + +## 로드 밸런서 유의사항 + +- 사용자가 서비스 IP(로드 밸런서 VIP)로 요청하면 로드 밸런서에서는 실제 서버의 IP 주소로 Destination NAT 한 후 서버로 전달한다. +- 서버는 다시 사용자에게 응답할 때 게이트웨이 장비인 L3 스위치를 통해 응답하는데 인라인 구성에서는 로드 밸런서를 통과하지만 원암 구성에서는 로드 밸런서를 거치지 않고 바로 응답한다. +- 요청 IP와 응답 IP가 매칭되지 않아서 클라이언트 입장에서는 패킷을 폐기하게 된다. +- 이 문제는 로드 밸런서를 거치면서 변경된 IP 가 재응답할 때, 로드 밸런서를 경유하면서 원래의 IP 바꾸어 응답해야 하지만 원암 구조에서는 응답 트래픽이 로드 밸런서를 경유하지 않아서 발생한다. +- 해결방법 + - 게이트웨이를 로드 밸런서로 설정 + - 서버가 동일 네트워크가 아닌 다른 네트워크의 목적지로 가려면 게이트웨이를 통과해야 한다 + - 때문에, 게이트웨이를 로드 밸런서로 설정하게 되면, 외부 사용자의 호출에 대한 응답이 항상 로드 밸런서를 통하므로 정상적으로 응답할 수 있게 된다. + - 물론 부하 분산을 사용하지 않는 서버는 기존과 동일하게 게이트웨이를 L3 스위치로 설정하면 로드 밸런서를 경유하지 않으므로 여전히 로드 밸런서의 부하 감소효과를 가져올 수 있다. + - Source NAT 사용 + - 사용자의 서비스 요청에 대해 로드 밸런서가 실제 서버로 가기 위해 수행하는 Destination NAT 뿐만 아니라, 출발지 IP 주소를 로드 밸런서가 가진 IP 로 함께 변경한다. (Source NAT) + - 이 경우 서비스를 호출할 때와 응답할 때 모두 Source/Destination NAT 을 함께 수행하게 된다. + - DSR 모드 + - DSR 모드는 사용자의 서비스 요청 트래픽에 대해 별도의 Destination NAT 을 수행 하지 않고, 실제 서버로 서비스 응답 패킷을 전송한다. + - 각 서버에는 서비스 IP 정보가 루프백(클라이언트가 목적지로 설정한 VIP) 인터페이스에 설정되어 있으며 서비스에 응답할 때 루프백에 설정된 서비스 IP 주소를 출발지로 응답한다. + +**동일 네트워크 내에서 서비스 IP(VIP) 호출** + - 로드 밸런서를 통해 동일 네트워크에 있는 서버1, 서버2 가 있다고 가정해보자. +- 문제 상황 + - 서버2에서 서버1의 서비스 IP 호출을 위해서 로드 밸런서로 서비스 요청을 한다. + - 로드 밸런서는 목적지 IP 인 서비스 IP 주소를 서버1의 IP 주소로 변환해 서버1로 전달한다. + - 서비스 요청을 받은 서버1은 서비스를 호출한 출발지 IP 를 확인하는데, 이 때 출발지 IP 가 동일 네트워크인 것을 확인하고 로드 밸런서를 통해서가 아닌 직접 응답한다. + - 서버2에서는 요청한 IP 가 아닌 다른 IP 주소로 응답이 오기 때문에, 해당 패킷은 폐기되고, 정상적인 서비스가 이루어지지 않는다. + - ⇒ 원암 / 인라인 모두에서 발생할 수 있는 문제이다. + +- 해결 방안 + - Source NAT 사용 + - DSR 모드 + +--- +참고 +- https://gugbab2.tistory.com/64 +- https://www.haproxy.com/blog/layer-4-load-balancing-direct-server-return-mode diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\270\214\353\235\274\354\232\260\354\240\200\354\227\220\342\200\205url\354\235\204\342\200\205\354\236\205\353\240\245\355\225\230\353\251\264\342\200\205\354\226\264\353\226\244\354\235\274\354\235\264\342\200\205\354\203\235\352\270\270\352\271\214\357\274\237.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\270\214\353\235\274\354\232\260\354\240\200\354\227\220\342\200\205url\354\235\204\342\200\205\354\236\205\353\240\245\355\225\230\353\251\264\342\200\205\354\226\264\353\226\244\354\235\274\354\235\264\342\200\205\354\203\235\352\270\270\352\271\214\357\274\237.md" new file mode 100644 index 00000000..87085889 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\353\270\214\353\235\274\354\232\260\354\240\200\354\227\220\342\200\205url\354\235\204\342\200\205\354\236\205\353\240\245\355\225\230\353\251\264\342\200\205\354\226\264\353\226\244\354\235\274\354\235\264\342\200\205\354\203\235\352\270\270\352\271\214\357\274\237.md" @@ -0,0 +1,103 @@ +--- +title: '브라우저에 url을 입력하면 어떤일이 생길까?' +lastUpdated: '2023-06-26' +--- +## 📡 브라우저에 url을 입력하면 어떤일이 벌어질까? + +### 1. 브라우저 주소창에 google.com을 입력한다. + +### 2. 웹 브라우저가 도메인의 IP 주소를 조회한다. + +- DNS(Domain Name System)는 웹 사이트의 이름(URL)을 가지고 있는 데이터베이스와 같다. 인터넷의 모든 URL에는 고유한 IP 주소가 할당되어 있으며, IP 주소는 액세스 요청 웹 사이트의 서버를 호스트하는 컴퓨터에 속한다. DNS는 영어로된 URL과 IP를 연결해주는 시스템이다. + +- 예를 들어, www.google.com의 IP주소는 http://209.85.227.104이다. 따라서 원하는 경우 브라우저에서 https://142.250.196.110 를 입력해도 www.google.com에 접속할 수 있다. + +> nslookup google.com로 IP주소를 확인할 수 있다. + +- DNS의 목적은 사람들이 쉽게 사이트 주소를 찾을 수 있도록 도와주는 것이다. 만약 DNS가 없다면 google.com과 같이 도메인 주소가 아닌, 142.250.196.110 라는 ip 주소를 외워야한다. DNS는 자동으로 URL과 IP 주소를 매핑해주기 떄문에, 쉽게 원하는 사이트에 접속할 수 있다. + +#### 캐시 확인 + +- DNS 기록을 찾기 위해, 브라우저는 우선 네개의 캐시를 확인한다. + +1. 브라우저 캐시를 확인한다. 브라우저는 이전에 방문했던 DNS 기록을 일정 기간동한안 저장하고 있다. + +2. OS 캐시를 확인한다. 브라우저 캐시에 원하는 DNS 레코드가 없다면, 브라우저가 내 컴퓨터 OS에 시스템 호출(i.e., 윈도우에서 `gethostname`)을 통해 DNS 기록을 가져온다. (OS도 DNS 레코드 캐시를 저장하고 있다.) + +3. 라우터 캐시를 확인한다. 만약 컴퓨터에도 원하는 DNS 레코드가 없다면, 라우터에 저장되어있는 DNS 캐시를 확인한다. + +4. ISP 캐시를 확인한다. ISP(Internet Service Provider)는 **DNS 서버**를 가지고 있는데, 해당 서버에서 DNS 기록 캐시를 검색할 수 있다. + +- DNS 캐시는 네트워크 트래픽을 규제하고 데이터 전송 시간을 개선하는 데 꼭 필요한 정보이기 때문에 여러 단계에 걸쳐 캐싱되어있다. + +#### DNS 쿼리 + +- 만약 요청한 URL이 캐시에 없다면, ISP의 DNS 서버가 DNS 쿼리로 서버의 IP주소를 찾는다. + +- DNS 쿼리의 목적은 웹 사이트에 대한 올바른 IP 주소를 찾을 때까지 인터넷에서 여러 DNS 서버를 검색하는 것이다. 필요한 IP 주소를 찾거나, 찾을 수 없다는 오류 응답을 반환할 때까지 한 DNS 서버에서 다른 DNS 서버로 검색이 반복적으로 계속되기 때문에 이 유형의 검색을 재귀적 질의(Recursive Query)라고 한다. + +- 이러한 상황에서, 우리는 ISP의 DNS 서버를 DNS 리커서(DNS Recursor)라고 부르는데, DNS 리커서는 인터넷의 다른 DNS 서버에 답변을 요청하여 의도된 도메인 이름의 적절한 IP 주소를 찾는 일을 담당한다. 다른 DNS 서버는 웹사이트 도메인 이름의 도메인 아키텍처를 기반으로 DNS 검색을 수행하므로 네임 서버(Name Server)라고 한다. + +- DNS 리커서가 루트 네임 서버(Root Name Server)에 연결하면, 최상위 도메인에 따라 해당하는 도메인 네임 서버로 리디렉션한다. 하위 네임 서버의 DNS 기록에서 찾는 URL과 일치하는 IP 주소를 찾으면 DNS 리커서를 거쳐 브라우저로 반환된다. + +- 위와 같은 요청(Request)은 내용 및 IP 주소(DNS 리커서의 IP 주소)와 같은 정보가 패킷에 담겨진 형태로 전송된다. 이 패킷은 올바를 DNS 서버에 도단하기 전에 클라이언트와 서버 사이의 여러 네트워킹 장비를 통해 이동한다. 이 장비들은 **라우팅 테이블**을 사용하여 패킷이 못적지에 도달할 수 있는 가장 빠른 방법을 알아낸다. + +- 올바른 경로를 통해 DNS 서버에 도달하면 IP 주소를 가져온 후 브라우저로 돌아간다. + +### 3. 브라우저가 해당 서버와 TCP 연결을 시작한다. + +- 인터넷에 연결된 웹 브라우저 요청 패킷은 일반적으로 TCP/IP(Transmission Control Protocol/Internet Protocol)라고 하는 전송 제어 프로토콜이 사용된다. TCP 연결은 TCP/IP 3-way handshake라는 연결 과정을 통해 이뤄진다. 클라이언트와 서버가 SYN(synchronize: 연결 요청) 및 ACK(acknowledgement: 승인) 메시지를 교환하여 연결을 설정하는 3단계 프로세스이다. + +1. 클라이언트는 인터넷을 통해 서버에 SYN 패킷을 보내 새 연결이 가능한지 여부를 묻는다. + +2. 서버에 새 연결을 수락할 수 있는 열린 포트가 있는 경우, SYN/ACK 패킷을 사용하여 SYN 패킷의 ACK(승인)으로 응답한다. + +3. 클라이언트는 서버로부터 SYN/ACK 패킷을 수신하고 ACK 패킷을 전송하여 승인한다. + +### 4. 웹 브라우저가 HTTP 요청을 서버로 전송한다. + +- 웹 브라우저가 서버에 연결되면, HTTP(s) 프로토콜에 대한 통신 규칙을 따른다. HTTP 요청에는 요청 라인, 헤더(또는 요청에 대한 메타데이터) 및 본문이 포함되며, 클라이언트가 서버에 원하는 작업을 요청하지 위한 정보가 들어간다. + +- 요청 라인에는 다음이 포함된다. + - GET, POST, PUT, PATCH, DELETE 또는 몇 가지 다른 HTTP 동사 중 하나인 요청 메서드 + - 요청된 리소스를 가리키는 경로 + - 통신할 HTTP 버전 + + + +- HTTPS를 사용하는 경우 주고 받는 데이터의 암호화를 위한 **TLS (Transport Layer Security)** 핸드셰이크라는 추가 과정을 수행한다. + +### 5. 서버가 요청을 처리하고 응답(response)을 보낸다. + +- 서버에는 웹 서버(예: Apache, IIS)가 포함되어 있는데, 이는 브라우저로부터 요청을 수신하고, 해당 내용을 request handler에 전달하여 응답을 읽고 생성하는 역할을 한다. Request handler는 요청, 요청의 헤더 및 쿠키를 읽고 필요한 경우 서버의 정보를 업데이트하는 프로그램이다(NET, PHP, Ruby, ASP 등으로 작성됨). 그런 다음 response를 특정 포맷으로(JSON, XML, HTML)으로 작성한다. + +### 7. 서버가 HTTP 응답을 보낸다. + +- 서버 응답에는 요청한 웹 페이지와 함께 상태 코드(status code), 압축 유형(Content-Encoding), 페이지 캐싱 방법(Cache-Control), 설정할 쿠키, 개인 정보 등이 포함 된다. + +- 서버의 HTTP 응답 예시이다: + +```bash +Server: nginx/1.18.0 (Ubuntu) +Date: Mon, 26 Jun 2023 05:47:16 GMT +Content-Type: application/json;charset=UTF-8 +Content-Length: 64 +Connection: keep-alive +Vary: Origin +Vary: Access-Control-Request-Method +Vary: Access-Control-Request-Headers +X-Content-Type-Options: nosniff +X-XSS-Protection: 1; mode=block +Cache-Control: no-cache, no-store, max-age=0, must-revalidate +Pragma: no-cache +Expires: 0 +X-Frame-Options: DENY + +{"message":"Hello"} +``` + +### 8. 브라우저가 HTML 컨텐츠를 보여준다. + +- 브라우저는 응답받은 HTML을 화면에 단계별로 표시한다. 첫째, HTML 골격을 렌더링한다. 그런 다음 HTML 태그를 확인하고 이미지, CSS 스타일시트, 자바스크립트 파일 등과 같은 웹 페이지의 추가 요소에 대한 GET 요청을 보낸다. 정적 파일(Static File)은 브라우저에서 캐싱되므로 다음에 페이지를 방문할 때 다시 가져올 필요가 없다. + +- 드디어, google.com 페이지가 브라우저에 나타난다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\354\235\264\354\244\221\355\231\224.md" "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\354\235\264\354\244\221\355\231\224.md" new file mode 100644 index 00000000..a9560553 --- /dev/null +++ "b/src/content/docs/TIL/\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205Network/\354\235\264\354\244\221\355\231\224.md" @@ -0,0 +1,106 @@ +--- +title: '이중화' +lastUpdated: '2024-03-02' +--- + +### 이중화의 목적 + +- 장애가 발생하더라도, 이중화 된 다른 인프라를 통해서 서비스가 지속되도록 해준다. (SPoF 방지) +- 액티브-스탠바이가 아닌, 액티브-액티브로 구성할 때는, 이중화 된 인프라에서 서비스 요청을 동시에 처리할 수 있기에, 처리 가능 용량이 늘어난다. +- 다만, 이렇게 증가된 인프라를 기준으로 서비스를 운영하다보면, 특정 지점에 장애가 발생했을 때 인프라 용량이 절반으로 떨어져 정상적인 서비스 운영이 어렵다. +- 따라서 인프라 이중화를 구성할 때는, 이중화 된 인프라 중 일부에서 장애가 발생하더라도 정상적인 서비스에 문제가 없도록 용량을 산정해 설계해야 한다. + +### LACP + +- 두 개의 물리 인터페이스가 논리 인터페이스를 구성하는 프로토콜 +- 1990 년대 중반까지는 각 벤더별로 장비 간 대역폭을 늘리기 위해 독자적인 방법을 구현했지만, 독자적인 방법으로는 다른 장비와 연결할 때 호환성 문제가 발생해 IEEE 에서 상호 호환 가능한 연결 계층 표준화를 시작했다. 이 표준화가 바로 LACP(Link Aggregation Control Protocol)이다. +- 링크 사용률이 좋아지고 장애를 빠르게 회복할 수 있다. +- LACP로 구성되는 물리 인터페이스들 간의 속도가 동일해야 한다. + +- **동작 방식** + - LACP를 통해 장비 간 논리 인터페이스를 구성하기 위해 LACPDU(LACP Data Unit) 라는 프레임을 사용한다. + - LACP 가 연결되려면, LACP 를 구성하는 두 개 이상의 물리 인터페이스가 서로 다른 장비에 연결되어 있으면 안된다. → LACP 를 구성하는 물리 인터페이스들은 하나에 장비에만 연결되어야 한다. + - LACPDU 에는 LACP 를 구성하기 위한 출발지 주소, 목적지 주소, 타입, 서브 타입, 버전 정보를 매 초마다 주고받는다. + +- **LACP 모드** + - **액티브** : LACPDU를 먼저 송신하고 상대방이 LACP 로 구성된 경우 LACP 구성 + - **패시브** : LACPDU를 먼저 송신하지 않지만, LACPDU를 수신 받으면 응답해 LACP 구성 + - 모든 인터페이스가 패시브 상태라면 LACPDU를 송신하지 않아서, LACP 연결이 되지 않는다. + +단방향이라도 LACPDU를 받아 정상적인 LACPDU를 교환하면 LACP가 구성된다. + +## **MC-LAG** + - LACP를 구성할 때, LACPDU를 주고 받는 장비 상호 간 구성이 1:1 이어야 한다. + - 그래서 본딩이나 티밍(다중의 물리 MAC 주소를 하나의 MAC 주소로 묶는다)과 같은 이중화 구성을 할 때, 각 랜카드 별로 물리 MAC 주소를 사용하지 않고 여러 개의 물리 MAC 주소 중 하나를 Primary MAC 주소로 사용한다. + - 서버에서 인터페이스(랜카드)를 두 개 이상 구성하더라도 상단 스위치가 한대로 구성된 경우, 상단 스위치에서 장애가 발생하면 서버는 통신이 불가능해진다. + - SPoF 구성을 피하기 위해서 서버의 인터페이스(랜카드)를 서로 다른 스위치로 연결해야 한다. + - 서로 다른 스위치로 이중화 구성을 하면 두 스위치 간 MAC 주소가 달라 LACP를 사용할 수 없다. + - 따라서 서버에서도 본딩과 티밍 모드를 액세스-스탠바이로 구성해 사용한다. + - MC-LAG(Multi-Chassis Link Aggregation Group) 기술로 서로 다른 스위치 간의 실제 MAC 주소 대신 가상 MAC 주소를 만들어 논리 인터페이스로 LACP 를 구성할 수 있다. + - MC-LAG 방식은 2개 이상의 랜카드 중 하나의 Primary MAC 을 정하는 방식에서 발전한 방식이다. + +- **MC-LAG 구성 요소** + - **피어(peer) 장비**: MC-LAG 를 구성하는 장비 + - **MC-LAG 도메인**: 두 Peer 장비를 하나의 논리 장비로 구성하기 위한 영역 ID 이다. + - Peer 장비는 이 영역 ID 를 통해서 상대방 장비가 Peer 를 맺으려는 장비인지 판단한다. + - **피어 링크(Peer-Link)**: MC-LAG 을 구성하는 두 Peer 장비 간의 데이터 트래픽을 전송하는 인터링크이다. + +image + +- **MC-LAG 구성하는 방법** + 1. 피어들을 하나의 도메인으로 구성하고 각 피어에 동일한 도메인 ID 값을 설정한다. + 2. 피어는 피어 간 데이터 트래픽 전송을 위해 피어 링크를 구성한다. + - MC-LAG 관련 제어 패킷을 주고받기 위해서 인터 링크를 사용할 지, 별도의 경로(L3 인터페이스)를 사용할 지 둘 중 하나를 선택하여 설정할 수 있다. + + image + - MC-LAG을 구성하기 위한 협상이 정상적으로 완료되면 두 대의 장비는 하나의 MC-LAG 도메인으로 묶이고 인터페이스 이중화 구성에 사용할 가상 MAC 주소를 피어간 동일하게 생성한다. + 3. 제어 패킷을 통해 MC-LAG을 구성하기 위한 협상이 정상적으로 완료되면 두 대의 장비가 하나의 MC-LAG 도메인으로 묶이고 인터페이스 이중화 구성에 사용할 MAC 주소를 생성한다. + + image + +- **MC-LAG이 설정 된 스위치가 LACP를 통해 이중화를 구성 하는 방법** + - 두 장비 간 LACP를 구성할 때는 각 장비의 MAC 주소가 출발지 MAC 주소가 된다. + - 하지만 MC-LAG 를 이용해 LACP 를 구성할 때는 각 장비 개별 MAC 주소가 아닌 가상 MAC 주소를 사용해 LACPDU를 전송한다. + - 그러므로 서로 다른 MAC 주소에서 통신을 하지만 MC-LAG 와 연결된 장비는 MC-LAG 피어들이 동일한 MAC 주소로 보이게 되고, 서로 다른 장비로도 LACP를 통한 이중화 구성을 할 수 있다. + + image + + +- **MC-LAG 을 이용한 디자인** + + 1. MC-LAG로 서버를 연결하면 스위치를 물리적으로 이중화하면서 액티브-액티브 구성으로 연결할 수 있다. + + image + + 2. 스위치간의 MC-LAG을 이용하면 서로 다른 장비를 하나의 장비처럼 인식시킬 수 있어 루프 구조가 사라지므로 STP에 의한 차단 포트 없이 모든 포트를 사용할 수 있다. + + image + + 3. 상하단을 모두 MC-LAG로 구성하는 디자인도 만들 수 있다. + + image + +## 게이트웨이 이중화 + +- Active-Passive, Active-Active 구성 중 하나를 설정할 수 있다. +- 애니캐스트 게이트웨이 기술을 적용하면 각 위치에 같은 주소를 가지는 게이트웨이가 여러 개 동작할 수 있다. + +### FHRP(First Hop Redundancy Protocol) + +- FHRP는 외부 네트워크와 통신하기 위해 사용되는 게이트웨이 장비를 두 대 이상 구성할 수 있게 하는 기술이다. + +- FHRP 그룹 내의 장비가 동일한 가상 IP와 가상 MAC 주소를 갖도록 설정하고, 우선순위에 따라 어떤 장비가 액티브 역할을 할 지를 정한다. + +- **VRRP 동작 방식** + - 게이트웨이 이중화를 구현한 표준 프로토콜인 VRRP의 동작 방식에 대해 알아보자. + - 다음은 스위치 A와 B를 이용해 VRRP 설정이 된 장비 구성도이다. + - 그룹 10에 대한 스위치 A의 우선순위 값을 110으로 설정한다고 가정하자. 스위치 B는 기본값인 100으로 설정된다. + image + - VRRP 설정이 끝나면 마스터를 선출하기 위해 장비간에 1초 간격으로 Hello 패킷을 주고받는다. 우선순위를 비교해 액티브가 될 마스터 장비를 선정한다. 패킷을 3회 이상 수신하지 못하면 자신이 마스터 장비가 된다. 위 구성에서는 스위치 A가 마스터가 된다. (Hello 패킷은 VRRP를 위해 예약된 멀티캐스트 IP인 `224.0.0.18`를 사용한다.) + - 마스터로 선출된 스위치 A는 VRRP에서 선언한 가상 IP와 가상 MAC 주소를 갖게 된다. ARP 테이블과 MAC 테이블을 보면 해당 가상 IP와 가상 MAC이 스위치 A에서 광고되는 것을 볼 수 있다. 하단 장비는 IP를 게이트웨이로 설정하고 스위치 A가 게이트웨이가 된다. + image + - 스위치 A의 인터페이스가 죽거나 장비에 장애가 발생하면 스위치 B가 마스터 역할을 가져간다. + +--- +참고 +- https://egstory.net/edge-study/tech-lesson/829/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/API\342\200\205Gateway.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/API\342\200\205Gateway.md" new file mode 100644 index 00000000..d30deee1 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/API\342\200\205Gateway.md" @@ -0,0 +1,43 @@ +--- +title: 'API Gateway' +lastUpdated: '2024-03-02' +--- + +API Gateway is a fully managed service for developers that makes it easy to build, publish, manage, and secure entire APIs. With a few clicks in the AWS Management Console, you can create an API that acts as a “front door” for applications to access data, business logic, or functionality from your back-end services, such as workloads running on EC2 code running on AWS Lambda, or any web application. + +--- + +- Amazon API Gateway handles all the tasks involved in accepting and processing up to hundreds of thousands of concurrent API calls, including traffic management, authorization and access control, monitoring, and API version management. + +- Amazon API Gateway has no minimum fees or startup costs. You pay only for the API calls you receive and the amount of data transferred out. + +- API Gateway does the following for your APIs: + - Exposes HTTP(S) endpoints for RESTful functionality + - Uses serverless functionality to connect to Lambda & DynamoDB + - Can send each API endpoint to a different target + - Runs cheaply and efficiently + - Scales readily and effortlessly + - Can throttle requests to prevent attacks + - Track and control usage via an API key + - Can be version controlled + - Can be connected to CloudWatch for monitoring and observability + +- Since API Gateway can function with AWS Lambda, you can run your APIs and code without needing to maintain servers. + +- Amazon API Gateway provides throttling at multiple levels including global and by a service call. + - In software, athrottling process, or a throttling controller as it is sometimes called, is a process responsible for regulating the rate at which application processing is conducted, either staticalyy or dynamically. + - Throttling limits can be set for standard rates and bursts. For example, API owners can set a rate limit of 1000 requests per second for a specific method in their REST APIs, and also configure Amazon API Gateway to handle a burst of 2000 requests per second for a few seconds. + - Amazon API Gateway tracks the number of requests per second. Any requests over the limit will receive a 429 HTTP response. The client SDKs generated by Amazon API Gateway retry calls automatically when met with this response. + +- You can add caching to API calls by provisioning an Amazon API Gateway cache and specifying its size in gigagytes. The cache is procisioned for a specific stage of your APIs. This improves performance and reduces the traffic sent to your backend. Cache settings allow you yo control the way the cache key is built and the TTL of the data stored for each methos. Amazon API Gateway also exposes management APIs that help you invalidate the cache for each stage. + +- You can enable API caching for improving latency and reducing I/O for your endpoint. + +- When caching for a particular API stage (version controlled version), you cache responses for a particular TTL in seconds. + +- API Gateway supports AWS Certificate Manager and can make use of free TLS/SSL certificates. + +- With API Gateway, there are two kinds of API calls: + - Calls to the API Gateway API to create, mdify, delete, or deploy REST APIs. These are logged in CloudTrail. + - API calls set up by the developers to deliver their custom functionality: These are not logged in CloudTrail. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/AWS\342\200\205cloud\342\200\205computing.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/AWS\342\200\205cloud\342\200\205computing.md" new file mode 100644 index 00000000..c28b6beb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/AWS\342\200\205cloud\342\200\205computing.md" @@ -0,0 +1,50 @@ +--- +title: 'AWS cloud computing' +lastUpdated: '2024-03-02' +--- + +In 2006, Amazon Web Services(AWS) began offering IT infrastructure services(cloud computing). Ont of the key benefits of cloud computing is the opportunity to replace upfront capital infrastructure expenses with low variable costs that scale with your business. With the cloud, businesses no longer need to plan for and procuure servers and other IT infrastructure weeks or month in advance. Instead, they can instantly spin up hundreads or thousands of servers in minutes and deliver result faster. + +Today, AWS procides a highly reliable, scalable, low-cost infrastructure platform in the cloud that powers hundreads of thousands of businesses in 190 countries around the world. + +## Cloud computing + +Cloud computing is the on-demand delivery of compute power, database, storage, applications, and other IT resources through a cloud services platform via the internet with pay-as-you-go pricing. With cloud computing, you don't need to make large upfront investments in hardware and spend a log of time on the heaby lifting of managing that hardware. Instead, you can provision exactly the right type and size of computinng resources you need to power you newest bright idea or operate you IT department. + +Cloud computing provides a simple way to access servers, storage, databases and a broad set of application services over the internet. . A cloud services platform such as Amazon Web Services owns and maintains the network-connected hardware required for these application services, while you provision and use what you need via a web application. + +## Types of cloud computing + +As cloud computing has grown in popularity, several different models and deployment strategies have emerged to help meet specific needs of different users. Each type of cloud service and deployment method provides you with different levels of control, flexibility, and management. Understanding the differences between Infrastructure as a Service, Platform as a Service, and Software as a Service, as well as what deployment strategies you can use, can help you decide what set of services is right for your needs. + +### Cloud computing models + +- **Infrastructure as a Service (IaaS)** + IaaS is a step away from on-premises infrastructure. It’s a pay-as-you-go service where a third party provides you with infrastructure services, like storage and virtualization, as you need them, via a cloud, through the internet. + As the user, you are responsible for the operating system and any data, applications, middleware, and runtimes, but a provider gives you access to, and management of, the network, servers, virtualization, and storage you need. + You don’t have to maintain or update your own on-site datacenter because the provider does it for you. Instead, you access and control the infrastructure via an application programming interface (API) or dashboard. + +- **Platform-as-a-service (PaaS)** + Paas is another step further from full, on-premise infrastructure management. It is where a provider hosts the hardware and software on its own infrastructure and delivers this platform to the user as an integrated solution, solution stack, or service through an internet connection. + A few examples of PaaS are AWS Elastic Beanstalk, Heroku, and Red Hat OpenShift. + +- **Software-as-a-service (SaaS)** + SaaS, also known as cloud application services, is the most comprehensive form of cloud computing services, delivering an entire application that is managed by a provider, via a web browser. + Software updates, bug fixes, and general software maintenance are handled by the provider and the user connects to the app via a dashboard or API. There’s no installation of the software on individual machines and group access to the program is smoother and more reliable. + +### Cloud computing deployment models + +- **Cloud** + A cloud-based application is fully deployed in the cloud and all parts of the application run in the cloud. Applications in the cloud have either been created in the cloud or have been migrated from an existing infrastructure to take advantage of the benefits of cloud computing. Cloud-based applications can be built on low-level infrastructure pieces or can use higher level services that provide abstraction from the management, architecting, and scaling requirements of core infrastructure. + +- **Hybrid** + A hybrid deployment is a way to connect infrastructure and applications between cloud-based resources and existing resources that are not located in the cloud. The most common method of hybrid deployment is between the cloud and existing on-premises infrastructure to extend, and grow, an organization's infrastructure into the cloud while connecting cloud resources to the internal system. For more information on how AWS can help you with your hybrid deployment, visit our Hybrid Cloud with AWS page. + +- **On-premises** + The deployment of resources on-premises, using virtualization and resource management tools, is sometimes called the “private cloud.” On-premises deployment doesn’t provide many of the benefits of cloud computing but is sometimes sought for its ability to provide dedicated resources. In most cases this deployment model is the same as legacy IT infrastructure while using application management and virtualization technologies to try and increase resource utilization. For more information on how AWS can help, refer to Use case: Cloud services on-premises + +--- +reference +- https://docs.aws.amazon.com/wellarchitected/latest/framework/wellarchitected-framework.pdf +- https://docs.aws.amazon.com/whitepapers/latest/introduction-aws-security/security-of-the-aws-infrastructure.html +- https://www.redhat.com/en/topics/cloud-computing/iaas-vs-paas-vs-saas \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Account/Tasks\342\200\205that\342\200\205require\342\200\205root\342\200\205user\342\200\205credentials.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Account/Tasks\342\200\205that\342\200\205require\342\200\205root\342\200\205user\342\200\205credentials.md" new file mode 100644 index 00000000..f023aa1a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Account/Tasks\342\200\205that\342\200\205require\342\200\205root\342\200\205user\342\200\205credentials.md" @@ -0,0 +1,33 @@ +--- +title: 'Tasks that require root user credentials' +lastUpdated: '2024-03-02' +--- + +- **Change your account settings.** This includes the account name, email address, root user password, and root user access keys. Other account settings, such as contact information, payment currency preference, and AWS Regions, don't require root user credentials. + +- **Restore IAM user permissions.** If the only IAM administrator accidentally revokes their own permissions, you can sign in as the root user to edit policies and restore those permissions. + +- **Activate IAM access to the Billing and Cost Management console.** + +- **View certain tax invoices.** An IAM user with the `aws-portal:ViewBilling` permission can view and download VAT invoices from AWS Europe, but not AWS Inc. or Amazon Internet Services Private Limited (AISPL). + +- **Close your AWS account.** + +- **Register as a seller in the Reserved Instance Marketplace.** + +- **Configure an Amazon S3 bucket to enable MFA (multi-factor authentication).** + +- **Edit or delete an Amazon Simple Queue Service (Amazon SQS) resource policy that denies all principals.** + +- **Edit or delete an Amazon Simple Storage Service (Amazon S3) bucket policy that denies all principals.** + +- **Sign up for AWS GovCloud (US).** + +- **Request AWS GovCloud (US) account root user access keys from AWS Support.** + +- **In the event that an AWS Key Management Service key becomes unmanageable, you can recover it by contacting AWS Support as the root user.** + +--- +reference +- https://docs.aws.amazon.com/accounts/latest/reference/root-user-tasks.html +- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Athena.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Athena.md" new file mode 100644 index 00000000..68bd6721 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Athena.md" @@ -0,0 +1,20 @@ +--- +title: 'Athena' +lastUpdated: '2024-03-02' +--- + +- Amazon Athena is a serverless, interactive analytics service built on open-source frameworks, supporting open-table and file formats. + +- Athena provides a simplified, flexible way to analyze petabytes of data where it lives. + +- Analyze data or build applications from an Amazon Simple Storage Service (S3) data lake and 30 data sources, including on-premises data sources or other cloud systems using SQL or Python. + +- Athena is built on open-source Trino and Presto engines and Apache Spark frameworks, with no provisioning or configuration effort required. + +- Amazon Athena for SQL can be accessed through the AWS Management Console, AWS SDK and CLI, or Athena's ODBC or JDBC driver. You can programmatically run queries, add tables, or partitions using the ODBC or JDBC driver. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/b46e7e45-8038-44d0-85d1-dfc2f95c9a17) + +--- +reference +- https://aws.amazon.com/athena/faqs/?nc=sn&loc=6 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/EMR.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/EMR.md" new file mode 100644 index 00000000..5642c0fc --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/EMR.md" @@ -0,0 +1,36 @@ +--- +title: 'EMR' +lastUpdated: '2024-03-02' +--- + +Amazon EMR (previously called Amazon Elastic MapReduce) is a managed cluster platform that simplifies running big data frameworks, such as Apache Hadoop and Apache Spark, on AWS to process and analyze vast amounts of data. + +[MapReduce](https://en.wikipedia.org/wiki/MapReduce) is a programming model and an associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster, EMR is provide feature to build big data processing workload too. + +Using these frameworks and related open-source projects, you can process data for analytics purposes and business intelligence workloads. Amazon EMR also lets you transform and move large amounts of data into and out of other AWS data stores and databases, such as Amazon Simple Storage Service (Amazon S3) and Amazon DynamoDB. + +In a nutshell, EMR building and operating big data environments and applications. Related EMR features include easy provisioning, managed scaling, and reconfiguring of clusters, and EMR Studio for collaborative development. EMR lets you focus on transforming and analyzing your data without having to worry about managing compute capacity or open-source applications, and saves you money. + +## Architecture + +Amazon EMR service architecture consists of several layers, each of which provides certain capabilities and functionality to the cluster. + +1. **Storage layer:** This layer includes the different file systems that are used with your cluster. There are several different types of storage options as follows. + +2. **Cluster resource management layer:** The resource management layer is responsible for managing cluster resources and scheduling the jobs for processing data. + +3. **Data processing frameworks layer:** This layer is the engine used to process and analyze data. There are many frameworks available that run on YARN or have their own resource management. + Different frameworks are available for different kinds of processing needs, such as batch, interactive, in-memory, streaming, and so on. The framework that you choose depends on your use case. + This impacts the languages and interfaces available from the application layer, which is the layer used to interact with the data you want to process. The main processing frameworks available for Amazon EMR are Hadoop MapReduce and Spark. + +--- + +- Amazon EMR supports many applications, such as Hive, Pig, and the Spark Streaming library to provide capabilities such as using higher-level languages to create processing workloads, leveraging machine learning algorithms, making stream processing applications, and building data warehouses. In addition, Amazon EMR also supports open-source projects that have their own cluster management functionality instead of using YARN. + +- You can set up CloudWatch alerts to notify you of changes in your infrastructure and take actions immediately. If you use Kubernetes, you can also use EMR to submit your workloads to Amazon EKS clusters. Whether you use EC2 or EKS, you benefit from EMR’s optimized runtimes which speed your analysis and save both time and money. + +--- +reference + +- https://aws.amazon.com/emr/features/?nc=sn&loc=2&dn=1 +- https://aws.amazon.com/emr/faqs/?nc=sn&loc=5 diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Glue.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Glue.md" new file mode 100644 index 00000000..9c5d411a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Glue.md" @@ -0,0 +1,70 @@ +--- +title: 'Glue' +lastUpdated: '2024-03-02' +--- + +Amazon Glue is a serverless data integration service that makes it easy to discover, prepare, and combine data for analytics, machine learning, and application development. + +AWS Glue uses other AWS services to orchestrate your ETL (extract, transform, and load) jobs to build data warehouses and data lakes and generate output streams. AWS Glue calls API operations to transform your data, create runtime logs, store your job logic, and create notifications to help you monitor your job runs. The AWS Glue console connects these services into a managed application, so you can focus on creating and monitoring your ETL work. The console performs administrative and job development operations on your behalf. You supply credentials and other properties to AWS Glue to access your data sources and write to your data targets. + +AWS Glue takes care of provisioning and managing the resources that are required to run your workload. You don't need to create the infrastructure for an ETL tool because AWS Glue does it for you. When resources are required, to reduce startup time, AWS Glue uses an instance from its warm pool of instances to run your workload. + +With AWS Glue, you create jobs using table definitions in your Data Catalog. **Jobs consist of scripts that contain the programming logic that performs the transformation**. You use triggers to initiate jobs either on a schedule or as a result of a specified event. You determine where your target data resides and which source data populates your target. With your input, AWS Glue generates the code that's required to transform your data from source to target. You can also provide scripts in the AWS Glue console or API to process your data. + +### Data sources and destinations + +AWS Glue for Spark allows you to read and write data from multiple systems and databases including: + +- Amazon S3 +- Amazon DynamoDB +- Amazon Redshift +- Amazon Relational Database Service (Amazon RDS) +- Third-party JDBC-accessible databases +- MongoDB and Amazon DocumentDB (with MongoDB compatibility) +- Other marketplace connectors and Apache Spark plugins + +### Data streams + +AWS Glue for Spark can stream data from the following systems: + +- Amazon Kinesis Data Streams +- Apache Kafka + +## Amazon Glue vs. Amazon EMR + +- Amazon Glue works on top of the Apache Spark environment to provide a scale-out execution environment for your data transformation jobs. Amazon Glue infers, evolves, and monitors your ETL jobs to greatly simplify the process of creating and maintaining jobs. + +- [Amazon EMR](EMR.md) provides you with direct access to your Hadoop environment, affording you lower-level access and greater flexibility in using tools beyond Spark. + +## Glue Schema Registry + +Amazon Glue Schema Registry, a serverless feature of Amazon Glue, enables you to validate and control the evolution of streaming data using schemas registered in Apache Avro and JSON Schema data formats, at no additional charge. + +Through Apache-licensed serializers and deserializers, the Schema Registry integrates with: +- Java applications developed for Apache Kafka +- Amazon Managed Streaming for Apache Kafka (MSK) +- Amazon Kinesis Data Streams +- Apache Flink +- Amazon Kinesis Data Analytics for Apache Flink +- Amazon Lambda + +When data streaming applications are integrated with the Schema Registry, you can improve data quality and safeguard against unexpected changes using compatibility checks that govern schema evolution. Additionally, you can create or update Amazon Glue tables and partitions using Apache Avro schemas stored within the registry. + +--- + +- With AWS Glue DataBrew, you can explore and experiment with data directly from your data lake, data warehouses, and databases, including Amazon S3, Amazon Redshift, AWS Lake Formation, Amazon Aurora, and Amazon Relational Database Service (RDS). You can choose from over 250 prebuilt transformations in DataBrew to automate data preparation tasks such as filtering anomalies, standardizing formats, and correcting invalid values. + + +- Amazon Glue monitors job event metrics and errors, and pushes all notifications to Amazon CloudWatch. With Amazon CloudWatch, you can configure a host of actions that can be triggered based on specific notifications from Amazon Glue. For example, if you get an error or a success notification from Glue, you can trigger an Amazon Lambda function. Glue also provides default retry behavior that will retry all failures three times before sending out an error notification. + +- Data integration is the process of preparing and combining data for analytics, machine learning, and application development. It involves multiple tasks, such as discovering and extracting data from various sources; enriching, cleaning, normalizing, and combining data; and loading and organizing data in databases, data warehouses, and data lakes. These tasks are often handled by different types of users that each use different products. + +- Amazon Glue provides both visual and code-based interfaces to make data integration easier. Users can easily find and access data using the Amazon Glue Data Catalog. Data engineers and ETL (extract, transform, and load) developers can create and run ETL workflows. Data analysts and data scientists can use Amazon Glue DataBrew to visually enrich, clean, and normalize data without writing code. + +- Amazon Glue provides all of the capabilities needed for data integration so that you can start analyzing your data and putting it to use in minutes instead of months. + +--- + +reference +- https://docs.aws.amazon.com/ko_kr/glue/latest/dg/what-is-glue.html +- https://docs.aws.amazon.com/ko_kr/glue/latest/dg/how-it-works.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Kinesis.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Kinesis.md" new file mode 100644 index 00000000..921f6af1 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Kinesis.md" @@ -0,0 +1,37 @@ +--- +title: 'Kinesis' +lastUpdated: '2024-03-02' +--- + +Amazon Kinesis makes it easy to collect, process, and analyze real-time, streaming data so you can get timely insights and reacy quickly to new information. With Amazon Kinesis, you can ingest real-time data such as video, audio, application logs, website clickstreams, and IoT telemetry data for machine learning, analytics, and other applications. Amazon Kinesis enables you to process and analyze daata as it arrives and respond instantly instread of having to wait until all your data is collected before the processing can begin. + +--- + +- Amazon Kinesis makes it easy to load and analyze the large volumes of data entering AWS. + +- Kinisis is used for processing real-time data streams (data that is generated continuously) from devices constantly sending data into AWS so that said data can be collected and analyzed. + +- It is a fully managed service that automatically scales to match the throuput of your data before loading it, minimizing the amount of storage used at the destination and increading security. + +- There are three different types of Kinesis: + - **Kinesis Streams** + - Kinesis Streams works where the data producers stream their data into Kinesis Streams which can retain the data from one day up until 7 days. Once inside Kinesis Streams, the data is contained within shards. + - Kinesis Streams can continously capture and store terabytes of data per hour from hundreds of thousands of sources such as website clickstreams, financial transactinos, cocial media feeds, IT logs, and location-tracking events. For example: puchase requests from a large online store like Amazon, stock prices, Netflix content, Twitch content, online gaming data, Uber positioning and directions, etc. + + - **Kinesis Firehose** + - Amazon Kinesis Firehose is the easiesy way to load streaming data into data stores and analytics tools. When data is streamed into Kinesis Firehose, there is no persistent storage there to hold onto it. The data has to be analyzed as it comes in so it's optional to have Lambda functinos inside your Kinesis Firehose. Once processed, you send the data elsewhere. + - Kinesis Firehose can capture, transform, and load streaming data into Amazon S3, Amazon Redshift, Amazon Elasticsearch Service, and Splunk, enabling near real-time analytics with existing business intellijgence tools and dashboards you're already using today. + + - **Kinesis Analytics** + - Kinesis Analytics work with both Kinesis Streams and Kinesis Firehose and can analyze data on the fly. The data within Kinesis Analytics also gets sent elsewhere once it is finished processing. It analyzes your data inside of the Kinesis service itselt. + +- artition keys are used with Kinesis so you can organize data by shard. This way, input from a particular device can be assigned a key that will limit its destination to a specific shard. + +- Partition keys are useful if you would like to maintain order within your shard. + +- Consumers, or the EC2 instances that read from Kinesis Streams, can go inside the shards to analyze what is in there. Once finished analyzing or parsing the data, the consumers can then pass on the data to a number of places for storage like a DB or S3. + +- The total capacity of a Kinesis stream is the sum of data within its constituent shards. + +- You can always increase the write capacity assigned to your shard table. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Lake\342\200\205Formation.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Lake\342\200\205Formation.md" new file mode 100644 index 00000000..97197ba1 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Analytics/Lake\342\200\205Formation.md" @@ -0,0 +1,31 @@ +--- +title: 'Lake Formation' +lastUpdated: '2024-03-02' +--- + +[AWS Lake Formation](https://aws.amazon.com/lake-formation/) is fully managed service that helps you build, secure, and manage data lakes, and provide access control for data in the data lake. + +image + +## Granular access permissions + +Customers acress lines of business (LOBs) need a way to manage granular access permissions for different users at the table and column level. Lake Formation helps you manage fine-grained access for internal and external customers from a ventralized location and in a scalable way. + +you can manage granular permissions on datasets shared between AWS accounts using Lake Formation. + +Our use case assumes you’re using AWS Organizations to manage your AWS accounts. The user of Account A in one organizational unit (OU1) grants access to users of Account B in OU2. You can use this same approach when not using Organizations, such as when you only have a few accounts. + +The following diagram illustrates the fine-grained access control of datasets in the data lake. + +- The data lake is available in the Account A. +- The data lake administrator of Account A provides fine-grained access for Account B. + +The diagram also shows that a user of Account B provides column-level access of the Account A data lake table to another user in Account B. + +image + +--- +reference +- https://aws.amazon.com/ko/blogs/big-data/manage-fine-grained-access-control-using-aws-lake-formation/ +- https://aws.amazon.com/ko/lake-formation/ +- https://docs.aws.amazon.com/lake-formation/latest/dg/what-is-lake-formation.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/ASG\342\200\205Lifecycle\342\200\205Hook.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/ASG\342\200\205Lifecycle\342\200\205Hook.md" new file mode 100644 index 00000000..217990ea --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/ASG\342\200\205Lifecycle\342\200\205Hook.md" @@ -0,0 +1,105 @@ +--- +title: 'ASG Lifecycle Hook' +lastUpdated: '2024-03-02' +--- + +ASG를 통해 생성되는 인스턴스들은 아래와 같은 Lifecycle을 가지고 있다. + + + +https://docs.aws.amazon.com/autoscaling/ec2/userguide/lifecycle-hooks.html + +- ASG가 인스턴스를 증가시키는 이벤트는 Scale out 이벤트이며, 이 때는 `Pending` 상태가 된다. +- 인스턴스가 부트스트랩 과정을 마치고 나면 `InService` 상태가 된다. +- 인스턴스가 축소되는 이벤트는 Scale in 이벤트이며, `Terminating` 상태를 거쳐 `Terminated` 상태가 된다. + +ASG의 Lifecycle Hook은 이 과정 중 Scale in과 Scale out 과정에 설정할 수 있다. 먼저 Scale out 이벤트에 걸게 된다면 인스턴스는 `Pending` 이후에 `Pending:Wait` 상태와 `Pending:Proceed` 상태를 거치게 되고, Scale in 이벤트에 걸게 된다면 인스턴스는 `Terminating:Wait` 상태와 `Terminating:Proceed` 상태를 거치게 된다. + +그렇기 때문에 인스턴스가 생성된 후 부트 스트랩 이후에 뭔가 추가적으로 작업해야 할 것이 있다면 `EC2_INSTANCE_LAUNCHING` 이벤트에, 인스턴스가 삭제될 때 추가적으로 작업해야 할 것이 있다면 `EC2_INSTANCE_TERMINATING` 이벤트에 Lifecycle Hook을 설정해주면 된다. + +## ASG Lifecycle Hook의 처리 순서 + +ASG의 Lifecycle Hook 이벤트는 Cloudwatch Event Bridge, SQS, SNS 등을 통해서 전달받을 수 있다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/6fb2cd39-44db-4e70-be30-3d17e9f99aab) + +ASG에서 설정한 Lifecycle 이벤트가 발생하면 Cloudwatch Event Bridge (이하 Event Bridge)를 통해 이벤트가 생성되고 Lambda 함수가 실행된다. 실행된 Lambda 함수는 SSM의 Run Command를 이용해서 Lifecycle 이벤트의 대상이 된 인스턴스에 주어진 명령을 실행한다. 그리고 정상적으로 실행이 완료되면 Lambda 함수가 ASG 에게 Lifecycle Hook에 의해 진행된 작업이 완료되었음을 알려준다. + +## 실습 + +EC2_INSTANCE_TERMINATING 이벤트에 Lifecycle Hook을 설정해서 인스턴스가 삭제되기 전에 애플리케이션이 확실하게 종료될 수 있도록 구성해보자. + +### Lambda + +이벤트시 실행 된 Lambda 코드를 짜보자. AWS 세션을 획득한 후 EventBridge를 통해 전달받은 이벤트를 확인하고 애플리케이션을 중지시키도록 SSM의 Run Command를 호출 한 뒤 Lifecycle Hook Action이 완료되었음을 알리는 중심 코드이다. + + + +또 핵심이 되는 부분 중에 하나는 EventBridge를 통해 전달받은 이벤트를 함수 내부에서 사용할 수 있도록 구조체로 변경하는 부분이다. 그리고 이 이벤트는 TerminationDetail 이라는 구조체로 정의되어 있다. + + + +이렇게 이벤트의 내용을 구조체로 변환한 다음 `stopApplication` 이라는 함수를 호출한다. 이 함수는 아래와 같이 구성되어 있다. + +`stopApplication` 함수는 크게 두 부분으로 나뉘어 있다. 먼저 SSM RunCommand를 호출하는 부분과 호출된 RunCommand가 정상적으로 종료되었는지를 확인하는 부분이다. + + + +ASG의 Lifecycle 대상이 된 인스턴스 ID와 실행해야 하는 명령을 인자로 넘겨주어서 AWS-RunShellScript 방식으로 RunCommand를 실행한다. 이때 넘겨주는 명령은 `systemctl stop application` 이라는 명령이다. 그럼 인스턴스는 종료되기 전에 `systemctl stop application` 이라는 명령을 실행하고, 애플리케이션을 정상적으로 종료할 수 있게 된다. + + + +그리고 생성된 RunCommand가 정상적으로 종료되었는지를 확인한다. RunCommand가 실행되고 API를 통해 상태를 확인하기 위해서는 약간의 시간이 소요되기 때문에 강제로 Sleep을 주어 텀을 줘야한다. + +### serverless를 이용한 배포 + +Lambda 함수를 만들었으니 [serverless](https://www.serverless.com/) 프레임워크를 사용해 배포해 보자. + + + +함수의 이름은 asg-termination-handler라고 지어준다. `serverless.yml` 파일의 핵심은 EventBridge를 정의하기 위한 하단 events 부분이다. event 하단에 있는 `source`와 `detail-type`은 이미 정의되어 있는 부분이기 때문에 동일하게 맞춰 주어야 한다. + +만약 Scale in이 아닌 Scale out시에 걸고 싶으면 detail-type을 EC2 Instance-launch Lifecycle Action로 변경해 주면 된다. 또한 Lifecycle Hook을 처리하기 위한 Lambda 함수는 일반적인 Lambda 함수 실행 권한 외에도 ASG와 SSM과 관련된 권한이 더 필요하다. 그래서 롤을 먼저 만들어 준 다음에 `serverless.yml` 파일에 role을 명시해서 추가해 준다. + + + +이후 serverless 프레임워크로 배포하면 Lifecycle Hook 이벤트를 받아서 처리하기 위한 준비는 끝난다. + +### ASG에 Lifecycle Hook 설정하기 + +이제 마지막으로 ASG에 Lifecycle Hook 을 설정한다. AWS 콘솔에서 EC2 -> Auto Scaling Gropus -> Instance Management 탭에 들어가면 아래와 같이 Lifecycle hooks를 설정할 수 있는 콘솔이 나온다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/43e55c6e-ab3b-4f58-b19d-be00eb528573) + +Create lifecycle hook 버튼을 클릭하면 아래와 같은 입력창이 나온다. 우리가 구현한 것은 Scale in 시의 작업이기 때문에 이름은 자유롭게 지어주되 Lifecycle transition만 Instance terminate로 설정해준다. + + + +Heartbeat timeout은 Lifecycle Hook Action 의 유지 시간인데, 앞에서 언급했던 것처럼 completion 처리를 별도로 해주지 않으면 액션 처리를 위한 람다 함수가 종료되고도 Heartbeat timeout에 설정한 시간만큼 기다리게 된다. 만약 Lifecycle Hook 이벤트를 받은 후 진행하는 작업이 오래 걸린다면 충분히 크게 주어서 안정적으로 진행되도록 해주어야 한다. 이 값의 최대 값은 7200초이다. + +설정이 완료된 후 ASG를 통해서 Scale in 해 보면 아래와 같이 인스턴스들이 `Terminating:Waiting` 상태를 거쳐 `Terminating:Proceed` 상태로 변경되는 것을 볼 수 있다. + +image + +
+ +image + +
+ +image + +그리고 SSM의 RunCommand 콘솔로 가보면 아래와 같이 정상적으로 RunCommand가 실행된 것도 볼 수 있다. + +image + +## 마무리 + +Lifecycle Hook은 ASG에 의해 인스턴스가 늘어나거나 줄어들 때 이벤트를 발생시켜서 사용자가 별도의 원하는 작업을 수행할 수 있게 해준다. Heartbeat timeout 이내에 가능한 작업이라면 인스턴스의 데이터를 다른 곳으로 옮긴다거나 하는 형태의 복잡한 작업도 가능하다. + +ASG의 기본 기능으로만으로는 유연한 인프라를 구성하는 것에 부족함을 느낀다면 Lifecycle Hook을 사용해보자. + +--- +참고 +- https://docs.aws.amazon.com/autoscaling/ec2/userguide/lifecycle-hooks.html +- https://docs.aws.amazon.com/autoscaling/ec2/userguide/configuring-lifecycle-hook-notifications.html) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Auto\342\200\205Scaling.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Auto\342\200\205Scaling.md" new file mode 100644 index 00000000..77172d6b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Auto\342\200\205Scaling.md" @@ -0,0 +1,53 @@ +--- +title: 'Auto Scaling' +lastUpdated: '2024-03-02' +--- + +AWS Auto Scaling lets you build scaling plans that automate how groups of different resources respond the changes in demand. You can optimize availability, costs, or a balance of both. AWS Auto Scaling automatically creates all of the scaling policies and sets targets for you based on your prefenence. + +## componets + + - **Groups**: These are logical components. A webserver group of EC2 instances, a database group of RDS instances, etc. + - **Configuration Templates**: Groups use a template to configure and launch new instances to better match the scaling needs. You can specify imformation for the new instances like the AMI to use, the instance type, security groups, block deviced to associate with the instances, and more. + - **Scaling Options**: Scaling Options provides serveral ways for you to scale your Auto Scaling groups. You can base the scaling trigger on the occurrence of a specified condition or on a schedule. + +## Scaling options + +Amazon EC2 Auto Scaling provides several ways for you to scale your Auto Scaling group. + +- **Maintain current instance levels at all times:** You can configure your Auto Scaling group to maintain a specified number of running instances at all times. + To maintain the current instance levels, Amazon EC2 Auto Scaling performs a periodic health check on running instances within an Auto Scaling group. When Amazon EC2 Auto Scaling finds an unhealthy instance, it terminates that instance and launches a new one. + +- **Scale manually:** Manual scaling is the most basic way to scale your resources, where you specify only the change in the maximum, minimum, or desired capacity of your Auto Scaling group. + +- **Scale based on a schedule:** Scailing by schedule means that scaling actions are performed automatically as a function of time and date. This is useful when you know exactly when to increase or decrease the number of instances in your group, simply because the need arises on a predictable schedule. + +- **Scale based on demand:** A more advanced way to scale your resources, using dynamic scaling, lets you define a scaling policy that dynamically resizes your Auto Scaling group to meet changes in demand. + For example, let's say that you have a web application that currently runs on two instances and you want the CPU utilization of the Auto Scaling group to stay at around 50 percent when the load on the application changes. This method is useful for scaling in response to changing conditions, when you don't know when those conditions will change. + +- **Use predictive scaling:** You can also combine predictive scaling and dynamic scaling (proactive and reactive approaches, respectively) to scale your EC2 capacity faster + Use predictive scaling to increase the number of EC2 instances in your Auto Scaling group in advance of daily and weekly patterns in traffic flows. + +## Dynamic scaling + +- **Target tracking scaling:** Increase and decrease the current capacity of the group based on a Amazon CloudWatch metric and a target value. It works similar to the way that your thermostat maintains the temperature of your home—you select a temperature and the thermostat does the rest. + - If you use a target tracking scaling policy based on a custom Amazon SQS queue metric, dynamic scaling can adjust to the demand curve of your application more effectively. + +- **Step scaling:** Increase and decrease the current capacity of the group based on a set of scaling adjustments, known as step adjustments, that vary based on the size of the alarm breach. + +- **Simple scaling:** Increase and decrease the current capacity of the group based on a single scaling adjustment, with a cooldown period between each scaling activity. + +--- + +- The following image highlights the state of an Auto scaling group. The orrange squares represent active instances. The dotted squares represent potential instances that can will be spun up whenever necessary. The minimum nuber, the maximum number, and the desired capacity of instances are all entirely configurable. + + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/39c77da7-5bbf-4b2c-a9a2-c4aefc659d1b) + +- Auto Scaling allows you to suspend and then resume one or more of the Auto Scaling processes in your Auto Scaling Group. This can be very useful when you want to investigate a problem in you application without triggering the Auto Scaling process when making changes. + +- You cannot modify a launch configuration after you've created it. If you want to change the launch configuration for an Auto Scaling group, you must create a new launch configuration and update your Auto Scaling group to inherit this new launch configuration. + +--- +reference +- https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html +- https://aws.amazon.com/autoscaling/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Auto\342\200\205Scaling\342\200\205termination\342\200\205policies.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Auto\342\200\205Scaling\342\200\205termination\342\200\205policies.md" new file mode 100644 index 00000000..52510ac3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Auto\342\200\205Scaling\342\200\205termination\342\200\205policies.md" @@ -0,0 +1,54 @@ +--- +title: 'Auto Scaling termination policies' +lastUpdated: '2024-03-02' +--- + +When Amazon EC2 Auto Scaling terminates instances, it attempts to maintain balance across the Availability Zones that are used by your Auto Scaling group. Maintaining balance across Availability Zones takes precedence over termination policies. + +If one Availability Zone has more instances than the other Availability Zones that are used by the group, Amazon EC2 Auto Scaling applies the termination policies to the instances from the imbalanced Availability Zone. If the Availability Zones used by the group are balanced, Amazon EC2 Auto Scaling applies the termination policies across all of the Availability Zones for the group. + +The default termination policy applies multiple termination criteria before selecting an instance to terminate. When Amazon EC2 Auto Scaling terminates instances, it first determines which Availability Zones have the most instances, and it finds at least one instance that is not protected from scale in. Within the selected Availability Zone, the following default termination policy behavior applies: + +1. Determine whether any of the instances eligible for termination use the oldest launch template or launch configuration: + - **[For Auto Scaling groups that use a launch template]** + - Determine whether any of the instances use the oldest launch template, unless there are instances that use a launch configuration. Amazon EC2 Auto Scaling terminates instances that use a launch configuration before it terminates instances that use a launch template. + - **[For Auto Scaling groups that use a launch configuration]** + - Determine whether any of the instances use the oldest launch configuration. + - After applying the preceding criteria, if there are multiple unprotected instances to terminate, determine which instances are closest to the next billing hour. If there are multiple unprotected instances closest to the next billing hour, terminate one of these instances at random. + - Note that terminating the instance closest to the next billing hour helps you maximize the use of your instances that have an hourly charge. Alternatively, if your Auto Scaling group uses Amazon Linux, Windows, or Ubuntu, your EC2 usage is billed in one-second increments. For more information, see Amazon EC2 pricing +2. After applying the preceding criteria, if there are multiple unprotected instances to terminate, determine which instances are closest to the next billing hour. + - If there are multiple unprotected instances closest to the next billing hour, terminate one of these instances at random. + +## Use different termination policies + +To specify the termination criteria to apply before Amazon EC2 Auto Scaling chooses an instance for termination, you can choose from any of the following predefined termination policies: + +1. `Default` + - Terminate instances according to the default termination policy. This policy is useful when you want your Spot allocation strategy evaluated before any other policy, so that every time your Spot instances are terminated or replaced, you continue to make use of Spot Instances in the optimal pools. + - It is also useful, for example, when you want to move off launch configurations and start using launch templates. + +2. `AllocationStrategy` + - Terminate instances in the Auto Scaling group to align the remaining instances to the allocation strategy for the type of instance that is terminating (either a Spot Instance or an On-Demand Instance). + - This policy is useful when your preferred instance types have changed. + - If the Spot allocation strategy is `lowest-price`, you can gradually rebalance the distribution of Spot Instances across your N lowest priced Spot pools. + - If the Spot allocation strategy is `capacity-optimized`, you can gradually rebalance the distribution of Spot Instances across Spot pools where there is more available Spot capacity. + - You can also gradually replace On-Demand Instances of a lower priority type with On-Demand Instances of a higher priority type. + +3. `OldestLaunchTemplate` + - Terminate instances that have the oldest launch template. With this policy, instances that use the noncurrent launch template are terminated first, followed by instances that use the oldest version of the current launch template. This policy is useful when you're updating a group and phasing out the instances from a previous configuration. + +4. `OldestLaunchConfiguration` + - Terminate instances that have the oldest launch configuration. This policy is useful when you're updating a group and phasing out the instances from a previous configuration. With this policy, instances that use the noncurrent launch configuration are terminated first. + +5. `ClosestToNextInstanceHour` + - Terminate instances that are closest to the next billing hour. This policy helps you maximize the use of your instances that have an hourly charge. (Only instances that use Amazon Linux, Windows, or Ubuntu are billed in one-second increments.) + +6. `NewestInstance` + - Terminate the newest instance in the group. This policy is useful when you're testing a new launch configuration but don't want to keep it in production. + +7. `OldestInstance` + - Terminate the oldest instance in the group. This option is useful when you're upgrading the instances in the Auto Scaling group to a new EC2 instance type. You can gradually replace instances of the old type with instances of the new type. + +--- +reference +- https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-termination-policies.html diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Scaling\342\200\205cooldowns.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Scaling\342\200\205cooldowns.md" new file mode 100644 index 00000000..ad073fd8 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/Scaling\342\200\205cooldowns.md" @@ -0,0 +1,31 @@ +--- +title: 'Scaling cooldowns' +lastUpdated: '2024-03-02' +--- + +- After your Auto Scaling group launches or terminates instances, it waits for a cooldown period to end before any further scaling activities initiated by simple scaling policies can start. + +- The intention of the cooldown period is **to prevent your Auto Scaling group from launching or terminating additional instances before the effects of previous activities are visible.** + +- Suppose, for example, that a simple scaling policy for CPU utilization recommends launching two instances. EC2 Auto Scaling launches two instances and then pauses the scaling activities until the cooldown period ends. + After the cooldown period ends any scaling activities initiated by simple scaling policies can resume. If CPU utilization breaches the alarm high threshold again, the Auto Scaling group scales out again, and the cooldowm period takes effect again. + +## Considerations + +The follwing considerations apply when working with simple scaling policies and scaling cooldowns: + +- Target tracking and step scaling policies can initiate a scale-out activity immediarely without waiting for the cooldown period to end. Instead, whenever your Auto Scalign group launched instances, the indivisual instances have a warm-up period. For more information, see [Set the default instance warmup for an Auto Scaling group](https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html). + +- When a scheduled action starts at the scheduled time, it can also initiate a scaling activity immediatly without waiting for the cooldown period to end. + +- If an instance becomes unhealthy, EC2 Auto Scaling does not wait for the cooldown period to end before replacing the unhealthy instance. + +- When multiple instances launch or terminate, the cooldown period (either the default cooldown or the scaling policy-specific cooldown) takes effect starting when the last instance finished launching or terminating. + +- When you manually scale your Auto Scaling group, the default is not to wait for a cooldown to end. However, you can override this behavior and honor the default cooldown when you use the AWS CLI or an SDK to manually scale. + +- By default, Elastic Load Balancing waits 300 seconds to complete the deregistration (connection draining) process. If the group is behind an Elastic Load Balancing load balancer, it will wait for the terminating instances to deregister before starting the cooldown period. + +--- +reference +- https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scaling-cooldowns.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/State\342\200\205Change\342\200\205Event.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/State\342\200\205Change\342\200\205Event.md" new file mode 100644 index 00000000..60f02062 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Auto\342\200\205Scaling/State\342\200\205Change\342\200\205Event.md" @@ -0,0 +1,37 @@ +--- +title: 'State Change Event' +lastUpdated: '2024-03-02' +--- + +Amazon EC2 sends an `EC2 Instance State-change Notification` event to Amazon EventBridge when the state of an instance changes. + +The following is example data for this evnet. In this example, the instance entered the `pending` state. + +```json +{ + "id":"7bf73129-1428-4cd3-a780-95db273d1602", + "detail-type":"EC2 Instance State-change Notification", + "source":"aws.ec2", + "account":"123456789012", + "time":"2021-11-11T21:29:54Z", + "region":"us-east-1", + "resources":[ + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" + ], + "detail":{ + "instance-id":"i-abcd1111", + "state":"pending" + } +} +``` + +The possible values for `state` are: + +- `pending` +- `running` +- `stopping` +- `stopped` +- `shutting-down` +- `terminated` + +When you launch or start an instance, it enters the `pending` state and then the `running` state. When you stop an instance, it enters the `stopping` state and then the stopped state. When you terminate an instance, it enters the `shutting-down` state and then the `terminated` state. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/EC2.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/EC2.md" new file mode 100644 index 00000000..9eea6fa7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/EC2.md" @@ -0,0 +1,148 @@ +--- +title: 'EC2' +lastUpdated: '2024-03-02' +--- + +EC2 spins up resizable server instances that can sacle up an down quickly. An instance is a virtual server in the cloud. With Amazon EC2, you can set up and configure the operating system and appplications that run on your instance. + +Its configuration at launch is a live copy of Amazon Machine Image (AMI) that you specify when you launched the instance. + +EC2 has an extemely reduced time frame for provisioning and booting new instances and EX2 enwures that you pay as you go, pay for what you use, pay less as you use more, and pay even less when you reserve capacity. When your EC2 instance is running, you are only charged on CPU, memory, storage, ans networking. When it is stopped, you are only charged for EBS storage. + +## Key Details + +- You can launch different types of instances from a single AMI. An instance type essentially determines the hardware of the host computer used for your instance. Each instance type offers different compute and memory capabilities. You should select an instance type based on the amount of memory and computing power that you need for the applicatio or software that you plan to run on top of the instance. + +- You can lanch multiple instances of an AMI, as shown in the follwing figure:
+ ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/8805dad9-5b2e-4017-a9ca-bd5efb754c8b) + +- You have the option of using dedicated tenancy with your instance. This means that with an AWS data center, you have exclusive access to physical hardware. Dedicated tenancy ensures that your EC2 instances are run on hardware specific to your account. + Naturally, this option incurs a high cost, but is makes sense if you work with technology that has a strict licensing policy. + + +- With EC2 VM Import, you can import existing VMs into AWS as long as those hosts use VMware ESX, CMware Workstation, Microsoft Hyper-V, or Citrix Xen vietualization formats. + +- When you launch a new EC2 instance, EC2 attempts to place the instance in such a way that all of your VMs are spread out across different hardware to limit failure to a single location. You can use placement groups to influence the placement of a group of interdependent instances that meet the needs of your workload. + +- When you launch an instance in Amazon EC2, you have the option of passing user data to the instance when the instance starts. This user data can be used to run common automated configuration tasks or scripts. For example, you can pass a bash script that ensures htop is installed on the new EC2 host and is always active. + +- By default, the public IP address of an EC2 Instance is released when the instance is stopped even if its stopped temporarliy. Therefor, it is best to refer to an instance by its external DNS hostname. If you require a persistent public IP address that can be associated to the same instancee, use an Elestic IP address which is basically a static IP address instead. + +- If you have requirements to self-manage a SQL database, EC2 can be a solid alternative to RDS. To ensure high availability, remember to have at least one other EC2 Instance in a separate Availability zone so even if a DB instance goes down, the other(s) will still be available. + +- A golden image is simply an AMI that you have fully customized to your liking with all necessary software/data/configuration details set and ready to go once. This personal AMI can then be the source from which you launch new instances. + +- Instance status checks check the health of the running EC2 server, systems status check monitor the health of the underlying hypervisor. If you ever notice a systems status issue, just stop the instance and start it again (no need to reboot) as the VM will start up again on a new hypervisor. + +## Instance Pricing + +- **On-Demand instances** are based on a fixed rate by the hour or second. As the name implies, you can start an On-Demand instance whenever you need one and can stop it when you no longer need it. Ther is no requirement for a long-term commitment. + +- **Reserved instance** ensure that you keep exclusive use of an instance on 1 or 3 year contract terms. The long-term commitment procides significantly reduced discounts at the hourly rate. + +- **Spot instances** take advantage of Amazon's excess capacity and work in an interesting manner. In order to use them, you must financially bid for access. + +## Standard Reserved vs. Converible Reserved vs. Scheduled Reserved + +- **Standard Reserved Instances** have inflexible reservations that are discounted at 75% off of On-Demand instances. Standard Reserved Instances cannot be moved between regions. You can choose if a Reserved Instance applies to either a specific Availability Zone, or an Entire Resion, but you cannot change the region. + +- **Converible Reserved Instances** are instances that are discounted at 54% off of On-Demand instances, but you can also modify the instance type at any point. For example, you suspect that ofter a few months your CM might need to change from general purpose to memory optimized, but you aren't sure just yet. So if you think that in the future you might need to change your VM type or upgrade you VMs capacity, choose Convertible Reserved Instances. There is no downgrading instance type with this option though. + +- **Scheduled Reserved Instances** are reserved according to a specified timeline that you set. For example, you might use Scheduled Reserved Instances if you run education software that only needs to be available during school hours. This option allows you to better match your your needed capacity with a recurring schedule so that you can save money. + +## EC2 Instance Lifecycle + +The following table highlights the many instance states that a VM can be in at a given time. + +|Instance state|Description|Billing| +|-|-|-| +|`pending`|The instance is preparing to enter the running state. An instance enters the pending state when it launches for the first time, or when it is started after being in the stopped state.|Not billed| +|`running`|The instance is running and ready for use.|Billed| +|`stopping`|The instance is preparing to be stopped or stop-hibernated.|Not billed if preparing to stop. Billed if preparing to hibernate| +|`stopped`|The instance is shut down and cannot be used. The instance can be started at any time.|Not billed| +|`shutting-down`|The instance is preparing to be terminated.|Not billed| +|`terminated`|The instance has been permanently deleted and cannot be started.|Not billed| + +Note: Reserved Instances that are terminated are billed until the end of their term. + +## EC2 Security + +- When you deploy an Amazon EC2 instance, you are responsible for management of the guest operating system (including updates and security patches), any application software or utilitied installed on the instances, and the configuration of the AWS-provided firewall (called a security group) on each instance. + +- With EC2, termination protection of the instance is disabled by default. This means that you do not have a safe-quard in place from accidentally terminating your instance. You must turn this feature on if tou want that extra bit of protection. + +- Amazon EC2 uses public–key cryptography to encrypt and decrypt login information. Public–key cryptography uses a public key to encrypt a piece of data, such as a password, and the recipient uses their private key to decrypt the data. The public and private keys are known as a key pair. + +- You can encrypt your root device volume which is where you install the underlying OS. You can do this during creation time of the instance or with third-party tools like bit locker. Of course, additional or secondary EBS vulumed are also enxryptable as well. + +- By default, an EC2 instance with an attached EBS root volume will be deleted together when the instance is terminated. However, any additional or secondary EBS volume that is also attached to the same instance will be preserved. This is because the root EBS volume is for OS installations and other low-level settings. This rule can be modified, bur it is usually easier to boot a new instance with a fresh root device volume than make use of an old one. + +## Placement Groups + +- When you launch a new EC2 instance, the EC2 service attempts to place the instance in such a way that all of your instances are spread out across underlying hardware to minimize correlated failures. You can use placement groups to influence the placement of a group of interdependent instances to meet the needs of your workload. + Placement groups balance the tradeoff between risk tolerance and network performance when it comed to your fleet of EC2 instances. The more you care about risk, the more isolated you want your instances to be from each other. The more you care about performance, the more conjoined you want your instances to be with each other. + +- There are three different types of EC2 placement groups: + 1. Clustered Placement Groups + - Clustered Placement Grouping is when you put all of your EC2 instances in a single availability zone. This is recommended for applications that need the lowest latency possible and require the highest neywork throughput. + - Only certain instances can be launched into this group (compute optimized, GPU optimized, storage optimized, and memory optimized).
+ ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/05388d12-24f0-425c-8c15-a2e6d6da00f2) + + 2. Spread Placement Groups + - Spread Placement Grouping is when you put each individual EC2 instance on top of its own distinct hardware so that failure is isolated. + - Your VMs live on separate racks, with separate network inputs and separate power requirements. Spread placement groups are recommended for applications that have a small number of critical instances that should be kept separate from each other.
+ ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/ef0d7b55-c74b-4b49-9457-17b51f9a49b2) + + 3. Partitioned Placement Groups + - Partitioned Placement Grouping is similar to Spread placement grouping, but differs because you can have multiple EC2 instances within a single partition. Failure instead is isolated to a partition (say 3 or 4 instances instead of 1), yet you enjoy the benefits of close proximity for improved network performance. + - With this placement group, you have multiple instances livingtogether on the same hardware inside of different availity zones acress one or more regions. + - If you would like a balance of risk tolerance and network performance, use Partitioned Placement Groups.
+ ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/19af9966-f14a-4509-a324-9a17ad0ad624) + +- Each placement group name within your AWS must be unique + +- You can move an existing instance into a placement group provided that it is in a stopped state. You can move the instance via the CLI or an AWS SDK, but not the console. You can also take a snapshot of the existing instance, convert it into an AMI, and launch it into the placement group where you desire it to be. + +## AWS Nitro System + +The AWS Nitro System is the underlying platform for the latest generation of EC2 instances that enables AWS to innovate faster, further reduce cost for our customers, and deliver added benefits like increased security and new instance types. With the latest set of enhancements to the Nitro system, all new C5/C5d/C5n, M5/M5d/M5n/M5dn, R5/R5d/R5n/R5dn, and P3dn instances now support 36% higher EBS-optimized instance bandwidth, up to 19 Gbps. Also, 6, 9, and 12 TB Amazon EC2 High Memory instances can now support 19 Gbps of EBS-optimized instance bandwidth, a 36% increase from 14 Gbps. + +This performance increase enables you to speed up sections of your workflows dependent on EBS-optimized instance performance. For storage intensive workloads, you will have an opportunity to use smaller instance sizes and still meet your EBS-optimized instance performance requirement, thereby saving costs. With this performance increase, you will be able to handle unplanned spikes in EBS-optimized instance demand without any impact to your application performance. + +## Instance Familiy + +Amazon EC2 provides a variety of instance types so you can choose the type that best meets your requirements. Instance types are named based on their family, generation, additional capabilities, and size. The first position of the instance type name indicates the instance family, for example c. The second position indicates the instance generation, for example 5. The remaining letters before the period indicate additional capabilities, such as instance store volumes. After the period (.) is the instance size, such as small or 4xlarge, or metal for bare metal instances. + +#### Instance families + +- C – Compute +- D – Dense storage +- F – FPGA +- G – GPU +- Hpc – High performance computing +- I – I/O +- Inf – AWS Inferentia +- M – Most scenarios +- P – GPU +- R – Random access memory +- T – Turbo +- Trn – AWS Tranium +- U – Ultra-high memory +- VT – Video transcoding +- X – Extra-large memory + +#### Additional capabilities + +- a – AMD processors +- g – AWS Graviton processors +- i – Intel processors +- d – Instance store volumes +- n – Network and EBS optimized +- e – Extra storage or memory +- z – High performance + +--- +reference +- https://aws.amazon.com/ec2/faqs/ +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html +- https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/WindowsGuide/instance-types.html diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/EC2\342\200\205Fleet.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/EC2\342\200\205Fleet.md" new file mode 100644 index 00000000..32b3c5c4 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/EC2\342\200\205Fleet.md" @@ -0,0 +1,51 @@ +--- +title: 'EC2 Fleet' +lastUpdated: '2024-03-02' +--- + +An EC2 Fleet contains the configuration information to launch a fleet—or group—of instances. In a single API call, a fleet can launch multiple instance types across multiple Availability Zones, using the On-Demand Instance, Reserved Instance, and Spot Instance purchasing options together. Using EC2 Fleet, you can: + +- Define separate On-Demand and Spot capacity targets and the maximum amount you’re willing to pay per hour + +- Specify the instance types that work best for your applications + +- Specify how Amazon EC2 should distribute your fleet capacity within each purchasing option + +You can also set a maximum amount per hour that you’re willing to pay for your fleet, and EC2 Fleet launches instances until it reaches the maximum amount. + +When the maximum amount you're willing to pay is reached, the fleet stops launching instances even if it hasn’t met the target capacity. + +The EC2 Fleet attempts to launch the number of instances that are required to meet the target capacity specified in your request. If you specified a total maximum price per hour, it fulfills the capacity until it reaches the maximum amount that you’re willing to pay. The fleet can also attempt to maintain its target Spot capacity + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/9e284444-791b-409d-90a0-6c3f6e44b308) + +You can specify an unlimited number of instance types per EC2 Fleet. Those instance types can be provisioned using both On-Demand and Spot purchasing options. You can also specify multiple Availability Zones, specify different maximum Spot prices for each instance, and choose additional Spot options for each fleet. Amazon EC2 uses the specified options to provision capacity when the fleet launches. + +While the fleet is running, if Amazon EC2 reclaims a Spot Instance because of a price increase or instance failure, EC2 Fleet can try to replace the instances with any of the instance types that you specify. This makes it easier to regain capacity during a spike in Spot pricing. You can develop a flexible and elastic resourcing strategy for each fleet. For example, within specific fleets, your primary capacity can be On-Demand supplemented with less-expensive Spot capacity if available. + +If you have Reserved Instances and you specify On-Demand Instances in your fleet, EC2 Fleet uses your Reserved Instances. For example, if your fleet specifies an On-Demand Instance as c4.large, and you have Reserved Instances for c4.large, you receive the Reserved Instance pricing. + +There is no additional charge for using EC2 Fleet. You pay only for the EC2 instances that the fleet launches for you. + +--- + +## Spot Fleet vs. Spot Instances + +- The Spot Fleet selects the Spot Instance pools that meet your needs and launches Spot Instances to meet the target capacity for the fleet. By default, Spot Fleets are set to maintain target capacity by launching replacement instances after Spot Instances in the fleet are terminated. + +- A spot instance generates a single request for a specific instance in a specific available area. Instead of requesting a single instance type, you can use a spotlet to request different instance types that meet your requirements. If the CPU and RAM are close enough for heavy workloads, it's okay to have many instance types. + +- This allows you to use spotplits to distribute instance costs across zones and instance types. In addition to the low disruption rates already mentioned, spot fleets can greatly enhance the system. You can also run an on-demand cluster to provide additional protection capacity. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/7a0e1e6a-eb39-49b7-bbf0-3a28d5fa4ef3) + +- With spot fleets, you can also apply a custom weighting to each instance type. Weighting tells the spot fleet request what total capacity we care about. As a simple example, say we would like a total capacity of 10GB of RAM, and we select two instance types, one that has 2GB and one that has 4GB of RAM. + + + + +--- + +reference +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-fleet.html +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-fleet.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Elastic\342\200\205Fabric\342\200\205Adapter.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Elastic\342\200\205Fabric\342\200\205Adapter.md" new file mode 100644 index 00000000..683846b2 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Elastic\342\200\205Fabric\342\200\205Adapter.md" @@ -0,0 +1,12 @@ +--- +title: 'Elastic Fabric Adapter' +lastUpdated: '2024-03-02' +--- + +An Elastic Fabric Adapter (EFA) is a network device that you can attach to your Amazon EC2 instance to accelerate High Performance Computing (HPC) and machine learning applications. EFA enables you to achieve the application performance of an on-premises HPC cluster, with the scalability, flexibility, and elasticity provided by the AWS Cloud. + +EFAs provide lower and more consistent latency and higher throughput than the TCP transport traditionally used in cloud-based HPC systems. It enhances the performance of inter-instance communication that is critical for scaling HPC and machine learning applications. It is optimized to work on the existing AWS network infrastructure and it can scale depending on application requirements. + +EFAs integrate with Libfabric 1.7.0 and later and it supports Open MPI 3.1.3 and later and Intel MPI 2019 Update 5 and later for HPC applications, and Nvidia Collective Communications Library (NCCL) for machine learning applications. + +image diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Instance\342\200\205Store.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Instance\342\200\205Store.md" new file mode 100644 index 00000000..d7ea49ee --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Instance\342\200\205Store.md" @@ -0,0 +1,38 @@ +--- +title: 'Instance Store' +lastUpdated: '2024-03-02' +--- + +AWS Instance Store는 Amazon Elastic Compute Cloud (EC2)에서 제공하는 임시 블록 수준 스토리지 서비스이다. EC2 인스턴스에 직접 연결된 로컬 디스크로, EC2 인스턴스가 실행 중인 동안에만 데이터를 유지한다. + +Instance Store는 인스턴스와 함께 묶여 있어 저렴하면서도 빠른 I/O 처리량을 제공한다. 그러므로 인스턴스 스토어를 사용하여 저지연 및 높은 처리량 작업을 수행할 수 있다. (대규모 데이터베이스, 데이터 웨어하우스, 로그 분석, 캐싱 등) + +하지만 Instance Store는 휘발성이므로, 인스턴스가 종료되거나 재부팅되면 데이터가 영구적으로 삭제된다. 그러므로 임시 데이터나 캐시와 같이 데이터의 지속성이 필요하지 않은 작업에 적합하다. + +AWS Instance Store는 EC2 인스턴스에 연결된 로컬 디스크로서 저렴하고 높은 성능을 제공한다. 데이터의 지속성이 필요하지 않은 작업에 이상적이며, 대용량 데이터베이스, 데이터 웨어하우스, 로그 분석, 캐싱 등에 사용될 수 있다. 그러나 데이터의 지속성과 백업이 필요한 경우에는 영구 스토리지 솔루션인 Amazon EBS를 고려해야 한다. + +--- + +image + +--- + +- The engineering team at a startup is evaluating the most optimal block storage volume type for the EC2 instances hosting its flagship application. The storage volume should support very low latency but it does not need to persist the data when the instance terminates. As a solutions architect, you have proposed using Instance Store volumes to meet these requirements. + Which of the following would you identify as the key characteristics of the Instance Store volumes? (Select two) + +- **You can't detach an instance store volume from one instance and attach it to a different instance** + - You can specify instance store volumes for an instance only when you launch it. You can't detach an instance store volume from one instance and attach it to a different instance. The data in an instance store persists only during the lifetime of its associated instance. If an instance reboots (intentionally or unintentionally), data in the instance store persists. + +- **If you create an AMI from an instance, the data on its instance store volumes isn't preserved** + - If you create an AMI from an instance, the data on its instance store volumes isn't preserved and isn't present on the instance store volumes of the instances that you launch from the AMI. + +Incorrect options: + +- Instance store is reset when you stop or terminate an instance. Instance store data is preserved during hibernation + - **When you stop, hibernate, or terminate an instance, every block of storage in the instance store is reset.** Therefore, this option is incorrect. + +- You can specify instance store volumes for an instance when you launch or restart it + - **You can specify instance store volumes for an instance only when you launch it.** + +- An instance store is a network storage type + - **An instance store provides temporary block-level storage for your instance.** This storage is located on disks that are physically attached to the host computer. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/RI\354\231\200\342\200\205Saving\342\200\205plan.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/RI\354\231\200\342\200\205Saving\342\200\205plan.md" new file mode 100644 index 00000000..fe04b893 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/RI\354\231\200\342\200\205Saving\342\200\205plan.md" @@ -0,0 +1,31 @@ +--- +title: 'RI와 Saving plan' +lastUpdated: '2024-03-02' +--- + +image + +### RI + +예약 인스턴스(Reserved Instance)라고 한다. 1년 혹은 3년 동안 EC2 혹은 RDS 인스턴스의 특정 유형을 예약하여 사용하는 방법이다. + +예를 들어, `t2.micro` 인스턴스 3개를 1년치 예약한다고 하자. 그럼 1년 동안은 실행 중인 t2.micro 인스턴스 3개에 대해 추가 비용 없이 사용할 수 있다(이미 비용을 지불했기 때문). 즉, 기존 t2.micro 인스턴스를 삭제하고 새로운 t2.micro 인스턴스를 생성해도 새로운 인스턴스는 자동으로 예약 인스턴스에 포함된다. + +예약 인스턴스는 3가지 결제 방식을 지원한다. +1. 전체 선결제(All Upfront) - 비용을 모두 선결제, 가장 저렴하다. +2. 부분 선결제(Partial Upfront) - 일부는 선결제, 일부는 선결제 없음(No Upfront)보다 저렴한 가격에 매월 시간당 사용료 지불 +3. 선결제 없음(No Upfront) - 온디맨드보다 저렴한 가격에 매월 시간당 사용료 지불 + +### Savings Plan + +RI와 비슷하게 1년 혹은 3년 동안 EC2 인스턴스의 특정 유형(ex. t2.micro)을 예약하여 사용하는 방법이다. 그러나 Savings Plan은 특정 기간에 특정 인스턴스 유형(t2.micro)을 예약하는 것이 아닌 특정 인스턴스 패밀리(t2)를 예약할 수 있다. 인스턴스 패밀리(t2) 안에서 여러 인스터스 크기(micro, small, large, ...)를 사용할 수 있어서 RI 보다 유연하다. + +Savings Plan은 세 가지 종류를 제공한다. +1. Compute Savings Plans - 온디맨드 가격의 최대 66%, 여러 지역에 걸쳐 적용 가능, 인스턴스 패밀리에 제한이 없다. +2. EC2 Instance Savings Plans - 온디맨드 가격에 최대 72%, 단일 지역에 적용 가능, 인스턴스 패밀리가 고정이다.(크기는 변경 가능) +3. SageMaker Savings Plans - AWS SageMaker 서비스 사용에 적용된다. + +--- +참고 +- https://repost.aws/ko/knowledge-center/savings-plans-considerations +- https://aws.amazon.com/ko/savingsplans/compute-pricing/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Spot\342\200\205Instance.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Spot\342\200\205Instance.md" new file mode 100644 index 00000000..486ed2ac --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/EC2/Spot\342\200\205Instance.md" @@ -0,0 +1,31 @@ +--- +title: 'Spot Instance' +lastUpdated: '2023-09-26' +--- +image + +## Spot Instance + +A Spot Instance is a type of AWS EC2 instance that allows you to use spare Amazon EC2 computing capacity at a significantly reduced cost compared to on-demand instances. You bid for this unused capacity, and when your bid meets or exceeds the current Spot price, your instance runs. + +However, your Spot Instance can be interrupted and terminated if the Spot price goes higher than your bid or if Amazon needs the capacity back. + +image + +## Spot Block + +Spot blocks are designed not to be interrupted and will run continuously for the duration you select, independent of Spot market price. + +In rare situations, Spot blocks may be interrupted due to Amazon Web Services capacity needs. In these cases, we will provide a two-minute warning before we terminate your instance, and you will not be charged for the affected instance(s). + +## Spot fleet + +A Spot Fleet is a collection or fleet of Spot Instances and/or Spot Blocks. It allows you to request a combination of instance types, across multiple Availability Zones, and can help in managing capacity, costs, and performance based on your application's needs. + +Spot Fleets can automatically request Spot Instances or Spot Blocks to maintain the desired capacity within the defined constraints and budget. They can also fall back to on-demand instances if Spot capacity is not available within your specified criteria. + +--- +reference +- https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/using-spot-instances.html +- https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/spot-requests.html +- https://aws.amazon.com/ko/blogs/aws/new-ec2-spot-blocks-for-defined-duration-workloads/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/ECS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/ECS.md" new file mode 100644 index 00000000..62db3cb7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/ECS.md" @@ -0,0 +1,68 @@ +--- +title: 'ECS' +lastUpdated: '2024-03-02' +--- + +Amazon Elastic Container Service (Amazon ECS) is a fully managed container orchestration service that helps you easily deploy, manage, and scale containerized applications. As a fully managed service, Amazon ECS comes with AWS configuration and operational best practices built-in. It's integrated with both AWS and third-party tools, such as Amazon Elastic Container Registry and Docker. This integration makes it easier for teams to focus on building the applications, not the environment. You can run and scale your container workloads across AWS Regions in the cloud, and on-premises, without the complexity of managing a control plane. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/205771e3-3e76-43d9-98b7-b9f3ae710c20) + +## ECS 요소 + +### 컨테이너(container) 및 이미지(image) + +ECS에서 애플리케이션을 배포하려면 애플리케이션 구성 요소가 컨테이너에서 실행되도록 구축되어야 한다. 일반적으로 Dockerfile로 빌드 되는 이미지가 이에 해당한다. + +### 작업 정의(Task definition) + +애플리케이션이 ECS에서 실행되도록 준비하려면 작업 정의를 생성한다. + +작업 정의는 애플리케이션을 구성하는 하나 이상의 컨테이너를 설명하는 JSON 파일이다. 작업 정의는 JSON 파일에 사용할 컨테이너, 사용할 시작 유형, 개방할 포트, 컨테이너에 사용할 볼륨 등의 다양한 파라미터가 포함된다. + +Fargate 시작 유형 + NginX 웹 서버를 실행하는 단일 컨테이너가 포함된 작업 정의의 예제는 아래와 같다. + +```json +{ + "family": "webserver", + "containerDefinitions": [ + { + "name": "web", + "image": "nginx", + "memory": "100", + "cpu": "99" + } + ], + "requiresCompatibilities": ["FARGATE"], + "networkMode": "awsvpc", + "memory": "512", + "cpu": "256" +} +``` + +### 작업(Task) 및 예약(Scheduling) + +작업은 클러스터 내 작업 정의를 인스턴스화하는 것이다. ECS에서 애플리케이션에 대한 작업 정의를 생성 하면 클러스터에서 실행 할 작업 수를 지정할 수 있다. 각 작업은 자체 격리 경계를 포함하여 메모리, CPU, 기본 커널, 네트워크 인터페이스를 다른 작업과 공유하지 않는다. + +ECS 작업 스케줄러는 클러스터 내에 작업을 배치하는 일을 한다. 다양한 예약 옵션을 지정할 수 있다. + +### 서비스(Service) + +ECS 클러스터에서 지정된 수의 작업 정의 인스턴스를 동시에 실행하고 관리할 수 있다. 어떤 이유로 작업이 실패 또는 중지되는 경우 서비스 스케줄러가 작업 정의의 다른 인스턴스를 시작하여 이를 대체하고 사용되는 일정 전략에 따라 원하는 작업 수를 유지한다. + +### 클러스터(Cluster) + +ECS를 사용하여 작업을 실행하면 Resource의 논리적 그룹인 클러스터에 작업을 배치하는 것이다. + +Fargate 시작 유형의 경우, ECS에서 클러스터 리소스를 관리한다. (ECS가 관리하는 EC2 인스턴스) EC2 시작 유형을 사용하면 클러스터는 사용자가 관리하는 컨테이너 인스턴스의 그룹이 된다. + +### 컨테이너 에이전트(Container Agent) + +컨테이너 에이전트는 ECS 클러스터의 각 인프라 Resource에서 실행된다. Resource에서 현재 실행 중인 작업과 Resource 사용률에 대한 정보를 ECS에 전송하고, ECS로부터 요청을 수신할 때마다 작업을 시작 또는 중지한다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/a6682ef5-09c0-4dbf-83fb-44eef1d7c06d) + +--- +reference +- https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/Welcome.html +- https://aws.amazon.com/ko/ecs/ +- https://aws.amazon.com/ko/ecs/faqs/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/ECS\342\200\205Getting\342\200\205started\342\200\205on\342\200\205Fargate.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/ECS\342\200\205Getting\342\200\205started\342\200\205on\342\200\205Fargate.md" new file mode 100644 index 00000000..c781d46d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/ECS\342\200\205Getting\342\200\205started\342\200\205on\342\200\205Fargate.md" @@ -0,0 +1,147 @@ +--- +title: 'ECS Getting started on Fargate' +lastUpdated: '2024-03-02' +--- + +Amazon Elastic Container Service (Amazon ECS) is a highly scalable, fast, container management service that makes it easy to run, stop, and manage your containers. You can host your containers on a serverless infrastructure that is managed by Amazon ECS by launching your services or tasks on AWS Fargate. + +Let's start to build ECS using linux container on Fargate + +## Step 1: Create the cluster + +Create a cluster that uses the default VPC. + +Before you begin, assign the appropriate IAM permission. For more information, see Cluster examples. + +1. Open the console at https://console.aws.amazon.com/ecs/v2. +2. From the navigation bar, select the Region to use. +3. In the navigation pane, choose Clusters. +4. On the Clusters page, choose Create cluster. +5. Under Cluster configuration, for Cluster name, enter a unique name. +6. (Optional) To turn on Container Insights, expand Monitoring, and then turn on Use Container Insights. +7. (Optional) To help identify your cluster, expand Tags, and then configure your tags. + [Add a tag] Choose Add tag and do the following: + - For Key, enter the key name. + - For Value, enter the key value. + [Remove a tag] Choose Remove to the right of the tag’s Key and Value. +8. Choose Create. + +## Stap 2: Create task definition + +A task definition is like a blueprint for your application. Each time you launch a task in Amazon ECS, you specify a task definition. + +The service then know which Docker image to use for containers. how many containers to use in the task, and the resource allocation for each container. + +1. In the navigation pane, choose Task Definitions. + +2. Choose Create new Task Definition, Create new revision with JSON. + +3. Copy and paste the following example task definition into the box and then choose Save. + +```json +{ + "family": "sample-fargate", + "networkMode": "awsvpc", + "containerDefinitions": [ + { + "name": "fargate-app", + "image": "public.ecr.aws/docker/library/httpd:latest", + "portMappings": [ + { + "containerPort": 80, + "hostPort": 80, + "protocol": "tcp" + } + ], + "essential": true, + "entryPoint": [ + "sh", + "-c" + ], + "command": [ + "/bin/sh -c \"echo ' Amazon ECS Sample App

Amazon ECS Sample App

Congratulations!

Your application is now running on a container in Amazon ECS.

' > /usr/local/apache2/htdocs/index.html && httpd-foreground\"" + ] + } + ], + "requiresCompatibilities": [ + "FARGATE" + ], + "cpu": "256", + "memory": "512" +} +``` + +Let's look parameters on detail. + +- **`name`**: string, required, maximum of 255 characters, numbers, hyphens, and lines + +- **`image`**: String, required, image used to start the container. Images uploaded to Docker Hub are available by default. +When a new operation starts, the ECS container agent fetches the latest version of the designated image and tag to use in the container. (At this point, the job that is already running does not reflect the image update.) + +**Memory** +There are hard and soft restrictions on how to limit the amount of memory in a container. + +- **`memory`**: integer, non-required, amount of memory in MiB of the container to be hard limited. If the amount of memory is limited by the hard limit, the container stops when the specified memory is exceeded. + +- **`memoryReservation`**: integer, non-required, soft The amount of memory in the container to be restricted (MiB). If the amount of memory is limited by soft limitations. + For example, if a container typically uses `128Mi`B of memory, but in some cases, its usage surges to `256MiB` for a short period of time, you can set the memory reservation to `128MiB` and the memory hard limit to `300MiB`. + +- **`portMapping`**: Object array, non-essential, allows containers to access ports in host container instances to send and receive traffic. For job definitions using awsvpc network mode, only the container port should be specified. The hostPort should be empty or equal to the containerPort. This maps to the `-p` option in Docker run. + +- **`ContainerPort`**: integer, required when using portMappings, container port number bound to the host port. If you use the container of the job as the Farge startup type, you must specify the exposed port using the container port. + +- **`protocol`**: string, non-required, protocol used for port mapping ( tcp | udp ); default is tcp. + +Define job definition parameters. + +- **`family`**: Name of string, required, task definition, name to be versioned. The first job definition registered in a specific family is assigned a number 1, and thereafter, it is assigned a number sequentially. + +- **`taskRoleArn`**: string, non-required, specify IAM roles allowed by job definition + +- **`networkMode`**: A character string, not required, docker networking mode to be used for the container of the job. Valid values are none, bridge, awsvpc, host. + - For none, the container is not connected to the external network. + - For bridge, the task uses a docker base virtual network running on each container instance. (Default) + - If host, maps the container port directly to the EC2 instance network interface. + - The awsvpc network mode is required when using the Fargate start type. + +## Step 3: Create the service + +Create a service using the task definition. + +1. In the navigation pane, choose Clusters, and then select the cluster you created in Step 1. +2. From the Services tab, choose Create. +3. Under Deployment configuration, specify how your application is deployes. + 1. For Task definition, choose the task definition you created in Step 2. + 2. For Service name, enter a name for your service. + 3. For Desired tasks, enter 1. +4. Under Networking, you can create a new security group or choose an existing security group for your task. +5. Choose Create. + +## Step 4: View you service + +1. Open the console at https://console.aws.amazon.com/ecs/v2. +2. In the navigation pane, choose Clusters. +3. Choose the cluster where you ran the service. +4. In the Services tab, under Service name, choose the service you created in Step 3. +5. Choose the Tasks tab, and then choose the task in your service. +6. On the task page, in the Configuration section, under Public IP, choose Open address. The screenshot below is the expected output. + +image + +## Step 5: Clean up + +When you are finished using an Amazon ECS cluster, you should clean up the resources associated with it to avoid incurring charges for resources that you are not using. + +Some Amazon ECS resources, such as tasks, services, clusters, and container instances, are cleaned up using the Amazon ECS console. Other resources, such as Amazon EC2 instances, Elastic Load Balancing load balancers, and Auto Scaling groups, must be cleaned up manually in the Amazon EC2 console or by deleting the AWS CloudFormation stack that created them. + +1. In the navigation pane, choose Clusters. +2. On the Clusters page, select the cluster cluster you created for this tutorial. +3. Choose the Services tab. +4. Select the service, and then choose Delete. +5. At the confirmation prompt, enter delete and then choose Delete. + Wait until the service is deleted. +6. Choose Delete Cluster. At the confirmation prompt, enter delete `cluster-name`, and then choose Delete. Deleting the cluster cleans up the associated resources that were created with the cluster, including Auto Scaling groups, VPCs, or load balancers. + +--- +reference +- https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/getting-started-fargate.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Elastic\342\200\205Beanstalk.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Elastic\342\200\205Beanstalk.md" new file mode 100644 index 00000000..316d05b9 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Elastic\342\200\205Beanstalk.md" @@ -0,0 +1,20 @@ +--- +title: 'Elastic Beanstalk' +lastUpdated: '2024-03-02' +--- + +Elastic Beanstalk is another way to script out your provisioning process by deploying existing applications to the cloud. ElasticBeanstalk is aimed toward developers who know very little about the cloud and want the simplest way of deploying their code. + +--- + +- Just upload your application and Elastic Beanstalk will take care of the underlying infrastructure. + +- Elastic Beanstalk has capacity provisioning, meaning you can use it with autoscaling from the get-go. ElasticBeanstalk applies updates to your application by having a duplicate ready with the already updated version. This duplicate is then swapped with the original. This is done as a preventative measure in case your updated application fails. If the app does fail, ElasticBeanstalk will switch back to the original copy with the older version and there will be no downtime experienced by the users who are using your application. + +- You can use Elastic Beanstalk to even host Docker as Elastic Beanstalk supports the deployment of web applications from containers. With Docker containers, you can define your own runtime environment, your own platform, programming language, and any application dependencies (such as package managers or tools) that aren't supported by other platforms. + +- ElasticBeanstalk makes it easy to deploy Docker as Docker containers are already self-contained and include all the configuration information and software required to run. + +--- +reference +- https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Fargate.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Fargate.md" new file mode 100644 index 00000000..d401195b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Fargate.md" @@ -0,0 +1,17 @@ +--- +title: 'Fargate' +lastUpdated: '2024-03-02' +--- + +- Fargate is a servreless compute engine for containers. + +- The Fargate launch type allows you to run your containerized applications without the need to procision and manage the backend infrastructure. Just register your task definition and Fargate launches the container for you. + +- It works with both Amazon Elastic Container Service (ECS) and Amazon Elastic Kubernetes Service (EKS) + +- Fargate makes it easy for you to focus on building your applications. It removes the need to procision and manage servers, lets you specify and pay for resources per application, and improves security through application isolation by design. + +--- +reference +- https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/userguide/what-is-fargate.html +- https://aws.amazon.com/ko/fargate/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Lambda.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Lambda.md" new file mode 100644 index 00000000..033a8386 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Computing/Lambda.md" @@ -0,0 +1,56 @@ +--- +title: 'Lambda' +lastUpdated: '2024-03-02' +--- + +AWS LAmbda leys you run code without provisioning or managing servers. You pay only for the compute time you consume. With Lambda, you can run code for virtually any type of application or backend service - all with zero administration. You upload your code and Lambda takes care of everything required to run and scale your code with high availability. You can set up your code to be automatically triggered from other AWS services or be called directly from any web or mobile app. + +--- + +- Lambda is a compute service where you upload your code as a function and AWS provisions the necessary details underneath the function so that the function executes successfully. + +- AWS Lambda is the ultimate abstraction layer. You only worry about code, AWS does everything else. + +- Lambda supports Go, Python, C#, PowerShell, Node.js, and Java + +- Each Lambda function maps to one request. Lambda scales horizontally automatically. + +- Lambda is priced on the number of requests and the first one million are free. Each million afterwards is $0.20. + +- Lambda is also priced on the runtime of your code, rounded up to the nearest 100mb, and the amount of memory your code allocates. + +- Lambda works globally. + +- Lambda functions can trigger other Lambda functions. + +- You can use Lambda as an event-driven service that executes based on changes in your AWS ecosystem. + +- You can also use Lambda as a handler in response to HTTP events via API calls over the AWS SDK or API Gateway. + +- When you create or update Lambda functions that use environment variables, AWS Lambda encrypts them using the AWS Key Management Service. When your Lambda function is invoked, those values are decrypted and made available to the Lambda code. + +- The first time you create or update Lambda functions that use environment variables in a region, a default service key is created for you automatically within AWS KMS. This key is used to encrypt environment variables. However, if you wish to use encryption helpers and use KMS to encrypt environment variables after your Lambda function is created, you must create your own AWS KMS key and choose it instead of the default key. + +- To enable your Lambda function to access resources inside a private VPC, you must provide additional VPC-specific configuration information that includes VPC subnet IDs and security group IDs. AWS Lambda uses this information to set up elastic network interfaces (ENIs) that enable your function to connect securely to other resources within a private VPC. + +- AWS X-Ray allows you to debug your Lambda function in case of unexpected behavior. + +- **Lambda can run up to 15 minuates per execution** + +## Lambda@Edge + +- You can use Lambda@Edge to allow your Lambda functions to customize the content that CloudFront delivers. + +- It adds compute capacity to your CloudFront edge locations and allows you to execute the functions in AWS locations closer to your application's viewers. The functions run tin response to CloudFront events, withour provisioning or managing servers. You can use Lambda functions to change CloudFront requests and responses at the following points: + - After CloudFront receives a request from a viewer (viewer request) + - Before CloudFront forwards the request to the origin (origin request) + - After CloudFront receives the response from the origin (origin response) + - Before CloudFront forwards the response to the viewer (viewer response) + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/54e7a0eb-ffa9-4fa2-a3b3-9fb70b9e7562) + +- You'd use Lambda@Edge to simplify and reduce origin infrastructure. + +--- +reference +- https://aws.amazon.com/lambda/ +- https://aws.amazon.com/lambda/features/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/Aurora.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/Aurora.md" new file mode 100644 index 00000000..81fee900 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/Aurora.md" @@ -0,0 +1,52 @@ +--- +title: 'Aurora' +lastUpdated: '2024-03-02' +--- + +Aurora is the AWS flagship DB known to combine the perfoemance and availability of traditional enterprise databases with the siplicity and cost-effectiveness of open source databases. It is a MySQL/PostgreSQL-compatible RDBMS that provides the security, availability, and reliability of commercial databases at 1/10th the cost of competitors. + +It is far more effective as an AWS database due to the 5x and 3x performance multipliers for MySQL and PostgreSQl respectively. + +--- + +- In case of an infrastructure failure, Aurora performs an automatic failover to a replica of its own. + +- Amazon Aurora typically involves a cluster of DB instances instead of a single instance. Each connection is handled by a specific DB instance. When you connect to an Aurora cluster, the host name and port that you specify point to an intermediate handler called an endpoint. Aurora uses the endpoint mechanism to abtracy these cnnections. Thus, you don't have to hard code all the host names or write your own logix for load-balancing and rerouting connections when some DB instances aren't available. + +- By default, there are 2 copies in a minimum of 3 availability zones for 6 total copies of all of your Aurora data. This makes it possible for it to handel the potential loss of up to 2 copis of your data without impacting write availability and up to 3 copies of yout data without impacting read availability. + +- Aurora replication differs from RDS replicas in the sense that it is possible for Aurora's replicas to be both a standby as part of a multi-AZ configuration as well as a target for read traffic. In RDS, the multi-AZ standby cannot be configured to be a read endpoint and only read replicas can serve that function. + +- With Aurora replication, you can have up to fifteen copies. If you want downstream MySQL or PostgreSQL as you replicated copies, then you can only have 5 or 1. + +## Aurora Serverless: + +- Aurora Serverless is a simple, on-demand, autoscaling configuration for the MySQL/PostgreSQl-compatible editions of Aurora. With Aurora Serverless, your instance automatically scales up or down and starts on or off based on your application usage. The use cases for this service are infrequent, intermittent, and unpredictable workloads. + +- This also makes it possible cheaper because you only pay per invocation + +- With Aurora Serverless, you simply create a database endpoint, optionally specify the desired database capacity range, and connect your applications. + +- It removes the complexity of managing database instances and capacity. The database will automatically start up, shut down, and scale to match your application's needs. It will seamlessly scale compute and memory capacity as needed, with no disruption to client connections. + +## Cluster Endpoint + +- Using cluster endpoints, you map each connection to the appropriate instance or group of instances based on your use case. + +- You can connect to cluster endpoints associated with different roles or jobs across your Aurora DB. This is because different instances or groups of instances perform different functions. + For example, to perform DDL statements you can connect to the primary instance. To perform queries, you can connect to the reader endpoint, with Aurora automatically performing load-balancing among all the Aurora Replicas behind the reader endpoint. For diagnosis or tuning, you can connect to a different endpoint to examine details. + +- Since the entryway for your DB Instance remains the same after a failover, your application can resume database operation without the need for manual administrative intervention for any of your endpoints. + +## Reader Endpoints + +- Aurora Reader endpoints are a subset of the above idea of cluster endpoints. Use the reader endpoint for read operations, such as queries. By processing those statements on the read-only Aurora Replicas, this endpoint reduces the overhead on the primary instance. + +- It also helps the cluster to scale the capacity to handle simultaneous SELECT queries, proportional to the number of Aurora Replicas in the cluster. Each Aurora DB cluster has one reader endpoint. + +- If the cluster contains one or more Aurora Replicas, the reader endpoint load-balances each connection request among the Aurora Replicas. In that case, you can only perform read-only statements such as SELECT in that session. If the cluster only contains a primary instance and no Aurora Replicas, the reader endpoint connects to the primary instance directly. In that case, you can perform write operations through the endpoint. + +--- +reference +- https://aws.amazon.com/rds/aurora/ +- https://aws.amazon.com/rds/aurora/features/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/DynamoDB.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/DynamoDB.md" new file mode 100644 index 00000000..7c621d30 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/DynamoDB.md" @@ -0,0 +1,169 @@ +--- +title: 'DynamoDB' +lastUpdated: '2024-03-02' +--- + +Amazon DynamoDB is a key-balue and document database that delivers single-digit milisecond performance at any scal. It's a fully managed, multiregion, millisecond performance at any scale. It's a fully managed, multiregion, muultimaster, durable non-SQL database, It comes with build-in security, backup and restore, and in-memory caching for internet-scale applications. + +--- + +- The main components of DynamoDB are: + - a collection which serves as the foundational table + - a document which is equivalent to a row in a SQL database + - key-value pairs which are the fields within the document or row + +- The convenience of non-relational DBs is that each row can look entirely differnt based on your usecase. There doesn't need to be uniformity. For example, if you need a new column for a particular entry you don't also need to ensure that that column exists for the other entries. + +- DynamoDB supports both document and key-value based models. It is a great fit for mobile, web, gaming, ad-tech, IoT, etc. + +- DynamoDB is stored via SSD which is why it is so fast. + +- It is spread across 3 geographically distinct data centers. + +- The default consistency model is Eventually Consistent Reads, but there are also Strongly Consistent Reads. + +- The differnce between the two consistency models is the one second rule. + With Eventual Consistent Reads, all copies off data are usually identical within one second after a write operation. A repeated read after a short period of time should return the updated data. + However, if you need to read updated data within or less than a second and this needs to be a guarantee, then strongly consistent reads are your best bet. + +- If you face a scenario that the schema, or the structure of your data, to change frequently, then you have to pick a database which procides a non-rigid and flexible way of adding or removing new types of data. This is a classic example of choosing between a relational database and non-relational (NoSQL) database. In this scenario, pick DynamoDB. + +- A relational database system does not scale well for the following readons: + - It normalizes data and stores it on multiple tables that require multiple queries to write to disk. + - It generally incurs the performance costs of an ACID-compliant transaction system. + - It uses expensive joins to reassemble required views of query results. + +- High cardinality is good for DynamoDB I/O performance. The more distinct your partition ket calues are, the better. It make it so that the requests sent will be spread acress the partitioned space. + +- DynamoDB makes use of parallel processing to achieve predictable performance. You can visualize each pertition or node as an independent DB server of fixed size with each partition or node responsible for a defined block of data. In SQL terminology, this conceppt is known as sharding but of course DynamoDB is not a SQL-based DB. With DynamoDB, data is dtored on SSD. + +## DynamoDB Accelerator (DAX) + +image + +- Amazon DynamoDB Accelerator (DAX) is a fully managed, highly available, in-memory cache that can reduce Amazon DynamoDB response times from millisecond to microseconds, even at millions of requests per second. + +- With DAX, your applications remain fast and responsice, even when unprecedented request volumes come your way. There is no tuning requires. + +- DAX lets you scale on-demand out to a ten-node cluster, giving you milions of requests per second. And is does more than just increase read performance by having write through cache. This improves write performance as well. + +- Just like DynamoDB, DAX is fully managed. You no loger need to worry about management tasks such as hardware or software provisioning, setup and configuration, software patching, operaiting, a reliable, distributed cache cluster, or relication data over multiple instances as you scale. + This means there is no need for developers to manage the caching logic. DAX is completely compatible with existing DynamoDB API calls. + +- DAX is designed for HA so in the event of a failure of one AZ, it will fail over to one of its replicas in another AZ. This is also managed automatically. + +## DynamoDB Streams + +- A DynamoDB stream is an ordered flow of information about changes to items in an Amazon DynamoDB table. When you enable a stream on a table, DynamoDB captures information about every modification to data items in the table. + +- Amazon DynamoDB is integrated with AWS Lambda so that you can create triggers_piece of code that automatically respond to events in DynamoDB Streams. + Immediately after an item in the table is modified, a new record appears in the table's stream. AWS Lambda polls the stream and invokes your Lambda function synchronously when it detects new stream records. The Lambda function can perform any actions you specify, such as sending a notification or initiating a workflow. + +- Whenever an application creates, updates, or deletes items in the table, DynamoDB Streams writes a stream record with the primary key attribute(s) of the items that were modified. A stream record contains information about a data modification to a single item in a DynamoDB table. You can configure the stream so that the stream records capture additional information, such as the "before" and "after" images of modified items. + +## Read/write capacity mode + +Amazon DynamoDB has two read/write capacity modes for processing reads and writes on your tables: + +- On-demand +- Provisioned (default, free-tier eligible) + +The read/write capacity mode controls how you are charged for read and write throughput and how you manage capacity. You can set the read/write capacity mode when creating a table or you can change it later. + +When you switch a table from provisioned capacity mode to on-demand capacity mode, DynamoDB makes several changes to the structure of your table and partitions. **This process can take several minutes**. During the switching period, your table delivers throughput that is consistent with the previously provisioned write capacity unit and read capacity unit amounts. When switching from on-demand capacity mode back to provisioned capacity mode, your table delivers throughput consistent with the previous peak reached when the table was set to on-demand capacity mode. + +Consider the following when you update a table from on-demand to provisioned mode: + +- If you're using the AWS CLI or AWS SDK, choose the right provisioned capacity settings of your table and global secondary indexes by using Amazon CloudWatch to look at your historical consumption (ConsumedWriteCapacityUnits and ConsumedReadCapacityUnits metrics) to determine the new throughput settings. +- If you are switching from on-demand mode back to provisioned mode, make sure to set the initial provisioned units high enough to handle your table or index capacity during the transition. + +### On-demand mode + +Amazon DynamoDB on-demand is a flexible billing option capable of serving thousands of requests per second without capacity planning. DynamoDB on-demand offers pay-per-request pricing for read and write requests so that you pay only for what you use. + +When you choose on-demand mode, DynamoDB instantly accommodates your workloads as they ramp up or down to any previously reached traffic level. + +If a workload’s traffic level hits a new peak, DynamoDB adapts rapidly to accommodate the workload. Tables that use on-demand mode deliver the same single-digit millisecond latency, service-level agreement (SLA) commitment, and security that DynamoDB already offers. You can choose on-demand for both new and existing tables and you can continue using the existing DynamoDB APIs without changing code. + +On-demand mode is a good option if any of the following are true: + +- You create new tables with unknown workloads. +- You have unpredictable application traffic. +- You prefer the ease of paying for only what you use. + +The request rate is only limited by the DynamoDB throughput default table quotas, but it can be raised upon request. For more information, see [Throughput default quotas.](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html#default-limits-throughput) + +Tables can be switched to on-demand mode once every 24 hours. Creating a table as on-demand also starts this 24-hour period. Tables can be returned to provisioned capacity mode at any time. + +**Read request units and write request units** + +- For on-demand mode tables, you don't need to specify how much read and write throughput you expect your application to perform. DynamoDB charges you for the **reads and writes that your application performs on your tables in terms of read request units and write request units.** + +- DynamoDB read requests can be either strongly consistent, eventually consistent, or transactional. + + - A strongly consistent read request of an item up to 4 KB requires one read request unit. + - An eventually consistent read request of an item up to 4 KB requires one-half read request unit. + - A transactional read request of an item up to 4 KB requires two read request units. + +- If you need to read an item that is larger than 4 KB, DynamoDB needs additional read request units. The total number of read request units required depends on the item size, and whether you want an eventually consistent or strongly consistent read. + +- For example, if your item size is 8 KB, you require 2 read request units to sustain one strongly consistent read, 1 read request unit if you choose eventually consistent reads, or 4 read request units for a transactional read request. + +- For more information, see [here](https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html). + +**Peak traffic and scaling properties** + +- DynamoDB tables using on-demand capacity mode automatically adapt to your application’s traffic volume. On-demand capacity mode instantly accommodates up to double the previous peak traffic on a table. + +- For example, if your application’s traffic pattern varies between 25,000 and 50,000 strongly consistent reads per second where 50,000 reads per second is the previous traffic peak, on-demand capacity mode instantly accommodates sustained traffic of up to 100,000 reads per second. If your application sustains traffic of 100,000 reads per second, that peak becomes your new previous peak, enabling subsequent traffic to reach up to 200,000 reads per second. + +- If you need more than double your previous peak on table, DynamoDB automatically allocates more capacity as your traffic volume increases to help ensure that your workload does not experience throttling. + +- However, throttling can occur if you exceed double your previous peak within 30 minutes. For example, if your application’s traffic pattern varies between 25,000 and 50,000 strongly consistent reads per second where 50,000 reads per second is the previously reached traffic peak, DynamoDB recommends spacing your traffic growth over at least 30 minutes before driving more than 100,000 reads per second. + +**Pre-warming a table for on-demand capacity mode** + +- With on-demand capacity mode, the requests can burst up to double the previous peak on the table. Note that throttling can occur if the requests spikes to more than double the default capacity or the previously achieved peak request rate within 30 minutes. One solution is to pre-warm the tables to the anticipated peak capacity of the spike. + +- To pre-warm the table, follow these steps: + + 1. Make sure to check your account limits and confirm that you can reach the desired capacity in provisioned mode. + + 2. If you're pre-warming a table that already exists, or a new table in on-demand mode, start this process at least 24 hours before the anticipated peak. You can only switch between on-demand and provisioned mode once per 24 hours. + + 3. To pre-warm a table that's currently in on-demand mode, switch it to provisioned mode and wait 24 hours. Then go to the next step. + If you want to pre-warm a new table that's in provisioned mode, or has already been in provisioned mode for 24 hours, you can proceed to the next step without waiting. + + 4. Set the table's write throughput to the desired peak value, and keep it there for several minutes. You will incur cost from this high volume of throughput until you switch back to on-demand. + + 5. Switch to On-Demand capacity mode. This should sustain the provisioned throughput capacity values. + +### Provisioned mode + +If you choose provisioned mode, you specify the number of reads and writes per second that you require for your application. You can use auto scaling to adjust your table’s provisioned capacity automatically in response to traffic changes. + +This helps you govern your DynamoDB use to stay at or below a defined request rate in order to obtain cost predictability. + +Provisioned mode is a good option if any of the following are true: + +- You have predictable application traffic. +- You run applications whose traffic is consistent or ramps gradually. +- You can forecast capacity requirements to control costs. + +**Read capacity units and write capacity units** + +- If your application reads or writes larger items (up to the DynamoDB maximum item size of 400 KB), it will consume more capacity units. + +- For example, suppose that you create a provisioned table with 6 read capacity units and 6 write capacity units. With these settings, your application could do the following: + - Perform strongly consistent reads of up to 24 KB per second (4 KB × 6 read capacity units). + - Perform eventually consistent reads of up to 48 KB per second (twice as much read throughput). + - Perform transactional read requests of up to 12 KB per second. + - Write up to 6 KB per second (1 KB × 6 write capacity units). + - Perform transactional write requests of up to 3 KB per second. + +--- +reference +- https://docs.aws.amazon.com/pdfs/AWSEC2/latest/UserGuide/ec2-ug.pdf#AmazonEBS +- https://aws.amazon.com/ko/dynamodb/dax/ +- https://www.daddyprogrammer.org/post/13990/dynamodb-stream/ +- https://youtu.be/DdXK1XYJduw \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/EFS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/EFS.md" new file mode 100644 index 00000000..f3729007 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/EFS.md" @@ -0,0 +1,29 @@ +--- +title: 'EFS' +lastUpdated: '2024-03-02' +--- + +EFS provides a simple and fully managed elastic NFS file system for use within AWS. EFS automatically and instantly scales your file system storage capacity up or down as you add or remove files withour disrupting you application. + +--- + +- In EFS, storage capacity is elastic (grows and shrinks automatically) and its size shanges based on adding or removing files. + +- While EVS mounts one EBS volume to one instance, you can attach one EFS volume acress muliple EC2 instances. + +- The EC2 instances communicate to the remote file system using the NFSv4 protocol. This makes it required to open up the NFS port for our security group (EC2 firewall rules) to allow inbound traffic on the port. + +- Within an EFS volume, the mount target state will let you know what instances are available for mounting/ + +- With EFS, you only pay for th storage that you use so you pay as you go. No pre-provisioning required. + +- EFS can scale up to the petabyte and van support thousands of concurrent NFS connections. + +- Data is stored across multiple AZs in a region and EFS ensures read after write consistency. + +- It is best for file storage that is accessed by a fleet of servers rather than just one server. + +--- +reference +- https://aws.amazon.com/efs/ +- https://aws.amazon.com/ko/efs/faq/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/RDS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/RDS.md" new file mode 100644 index 00000000..402e06de --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/RDS.md" @@ -0,0 +1,64 @@ +--- +title: 'RDS' +lastUpdated: '2024-03-02' +--- + +RDS is managed service that makes it easy to set up, operate and scale a relational database in AWS. It provides cost-efficient and resizable capacity while automating or outsourcing time-consuming adming administration tasks such as hardware provisioning, database setup, patching and backups. + +- RDS comes in six different flavors: + - SQL Server + - Oracle + - MySQL Server + - PostgreSQL + - MariaDB + - Aurora +- Think of RDS as the DB engine which various DBs sit on top of. +- RDS has two key features when scaling out: + - Read replication for improved performance + - Multi-AZ for high availablity + +- In the database world, Online Transaction Processing (OLTP) differs from Online Analytical Processing (OLAP) in terms of the type of querying that you would do. OLTP serves up data for business logic that ultimately composes the core functioning of you platform or application. OLAP is to gain insights into the data that you have stored in order to make better strategic decisions as a company. + +- RDS runs on virtual machine, but you do not have access to those machins. You cannot SSH into an RDS instance so therefore you cannot patch the OS. This means that AWS is responsible for th security and maintenance of RDS. You can provision an EC2 instance as a database if you need or want to manage the underlying server yourselt, but not with a RDS engine. + +- SQS queues can be used to store pending database writes if you applicatio is struggling under a high write load. These writes can then be added to the database when the database is ready to process them. Adding more IOPS will also help, but this alone will not wholly eliminate the chance of writes being lost. A queue however ensures that writes to the DB do not become lost. + +## RDS Multi-AZ + +- Disaster recovery in AWS always looks to ensure standby copies of resources are maintained in a seperate geographical area. This way, if a diater (natural disaster, political conflict, etc.) ever struck where your original resources are, the copies would be unaffected. + +- When you provision a Multi-DB Instance Amazon RDS auttomatically creates a primary DB instance and synchronously replicates the data to a standby instance in a differnt AZ. Each AZ runs on its own physically distinct, independent infrastructure, and is engineered to be highly reliable. + +- With a Multi-AZ configuration, EC2 connects to its RDS data store using a DNS address macked as a connection string. If the primary DB fails, Multi-AZ is smart enough to detext that failure and automatically update the DNS address to point at the secondary. No manual intervention is required and AWS takes care of swapping the IP address in DNS. + +- Multi-AZ feature allows for high availability across availability zones and not regions. + +- During a failover, the recovered former primary becomes the new secondary and the promoted secondary becomes primary. Once the original DB is recovered, there will be a sync process kucked off where the two DBs mirror each other one to sync up on the new data that the failed former primary might have missed out on. + +- You can force a failover for a Multi-AZ setup by rebooting the primary instance. + +- With a Multi-AZ RDS configuration, backups are taken from the standby. + +## RDS Read Replicas + +- Read Replication is exclusively used for perfoemance enhancement. + +- With a Read Replica configuration, EC2 connects to the RDS backend using a DNS address and every write that is received by the master satabase is also passed onto a DB secondary so that it becomes a perfect copy of the master because the secondary DBs can be queried for the same data. + +- However, if the master DB were to fail, there is no automatic failover. You would have to manually create a new connection string to sync with one of the read replicas so that it becomes a master on its own. Then you'd have to update your EC2 instances to point at the read replica. You can have up to five copies of your master DB with read replication. + +- You can promote read replicas to be their very own production database if needed. + +- Each Read Replica will have its own DNS endpoint. + +- Automated backups must be enabled in order to use read replicas. + +- You can have read replicas with Multi-AZ turned on or have the read replica in an entirely separate region. You can even have read replicas of read replicas, but watch out for latency or replication lag. + The caveat for Read Replicas is that they are subject to small amounts of replication lag. This is because they might be missing some of the latest transactions as they are not updated as quickly as primaries. Application designers need to consider which queries have tolerance to slightly stale data. Those queries should be executed on the read replica, while those demanding completely up-to-date data should run on the primary node. + + +--- +reference +- https://aws.amazon.com/ko/rds/ +- https://aws.amazon.com/ko/rds/features/ +- https://aws.amazon.com/ko/rds/faqs/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/RDS\342\200\205proxy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/RDS\342\200\205proxy.md" new file mode 100644 index 00000000..9e428bab --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/RDS\342\200\205proxy.md" @@ -0,0 +1,20 @@ +--- +title: 'RDS proxy' +lastUpdated: '2024-03-02' +--- + +- By using Amazon RDS Proxy, you can allow your applications to pool and share database connections to improve their ability to scale. + +- RDS Proxy **establishes a database connection pool and reuses connections in this pool**. This approach avoids the memory and CPU overhead of opening a new database connection each time. + +## Advantages + +- RDS Proxy makes applications more resilient to database failures by automatically connecting to a standby DB instance while preserving application connections. + +- By using RDS Proxy, you can also enforce AWS Identity and Access Management (IAM) authentication for databases, and securely store credentials in AWS Secrets Manager. And you can handle unpredictable surges in database traffic. Otherwise, these surges might cause issues due to oversubscribing connections or creating new connections at a fast rate. + +- You can reduce the overhead to process credentials and establish a secure connection for each new connection. RDS Proxy can handle some of that work on behalf of the database. + +--- +reference +- https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/Redshift.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/Redshift.md" new file mode 100644 index 00000000..03c4bb96 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Database/Redshift.md" @@ -0,0 +1,48 @@ +--- +title: 'Redshift' +lastUpdated: '2024-03-02' +--- + +Amazon Redshift is a fully managed, petabyte-scale data warehouse service in the cloud. The Amazon Redshify service manages all of the work of setting up, operating, and scaling a data warehouse. These tasks include provisioning capacity, monitoring and backing up the cluster, and applying patches and upgrades to the Amazon Redshift engine. + +--- + +- An Amazon Redshift cluster is a set of nodes which consistes of a leader node and one or more compute nodes. The type and number of compute nodes that you need depecds on the size of your data, the number of queries you will execute, and the query execution performance that you need. + +- Redshify is used for business intelligence and pulls in very large and complex datasets to perform complex queries in order to gather insights from the data. + +- It fits the usecase of Online Analytical Processing (OLAP). Redshift is a pworful technology for data discovery including capabilities for almost limitless report viewing, complex analytical calculations, and predictive "what if" scenario (budget, forecast, etc.) planning. + +- Depending on your data warehoudsing needs you can start with a small single-node cluster and easily scale up to a leager, multi-node cluster as your requiremets change. You can add or remove compute nodes to the cluster withour any interruption to the service. + +- If you intend to keep your cluster running for a year or longer, you can save money by reserving compute nodes for a one-year or three-year period. + +- Redshift is able to achieve efficiency despite the many parts and pieces in its architecture through using columnar compression of data stores that contain similar data. + In addition, Redshift does not require indexes or materialized views which means it can be relatively smaller in size compared to an OLTP database containing the same amount of information. Finally, when loading data into a Redshift table, Redshift will automatically down sample the data and pick the most appropriate compression scheme. + +- Redshift is encrypted in transit using SSL and is encrypted at rest using AES-256. By default, Redshift will manage all keys, but you can do so too via AWS CloudHSM or AWS KMS. + +## Redshift Spectrum: + +- Amazon Redshift Spectrum is used to run queries against exabytes of unstructured data in Amazon S3, with no loading or ETL required. + +- Redshift Spectrum queries employ massive parallelism to execute very fast against large datasets. Much of the processing occurs in the Redshift Spectrum layer, and most of the data remains in Amazon S3. + +- Redshift Spectrum queries use much less of your cluster's processing capacity than other queries. + +- The cluster and the data files in Amazon S3 must be in the same AWS Region. + +- External S3 tables are read-only. You can't perform insert, update, or delete operations on external tables. + +## Redshift Enhanced VPC Routing: + +- When you use Amazon Redshift Enhanced VPC Routing, Redshift forces all traffic (such as COPY and UNLOAD traffic) between your cluster and your data repositories through your Amazon VPC. + +- If Enhanced VPC Routing is not enabled, Amazon Redshift routes traffic through the Internet, including traffic to other services within the AWS network. + +- By using Enhanced VPC Routing, you can use standard VPC features, such as VPC security groups, network access control lists (ACLs), VPC endpoints, VPC endpoint policies, internet gateways, and Domain Name System (DNS) servers.] + +--- +reference +- https://aws.amazon.com/redshift/?nc1=h_ls +- https://aws.amazon.com/redshift/features/?nc=sn&loc=2&dn=1 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudFormation.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudFormation.md" new file mode 100644 index 00000000..2e26bb08 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudFormation.md" @@ -0,0 +1,34 @@ +--- +title: 'CloudFormation' +lastUpdated: '2024-03-02' +--- + +CloudFormation is an automated tool for provisioning entire cloud-based environments. It is similar to Terraform where you cofify the instructions for what you want to have inside your application setup (X many web servers of Y type with a Z type DB on the backend, etc). It makes it a lot easier to just describe what you want in markup and have AWS do the actual provisioning work involved. + +--- + +- The main usecase for CloudFormation is for advanced setups and production environments as it is complex ans has many robust features. +- CloudFormation templates can be used to create, update, and delete infrastructure. +- The templates are written in YAML or JSON +- A full CloudFormation setup is called a stack. +- Once a template is created, AWS will make the corresponding stack. This is the living and active representation of said template. One template can create an infinite number of stacks. +- The Resources field is the only mandatory field when creating a CloudFormation template +- Rollback triggers allow you to monitor the creation of the stack as it's built. If an error occurs, you can trigger a rollback as the name implies. +- [AWS Quick Starts is composed of many high-quality CloudFormation stacks designed by AWS engineers.](https://aws.amazon.com/quickstart/?quickstart-all.sort-by=item.additionalFields.updateDate&quickstart-all.sort-order=desc) +- An example template that would spin up an EC2 instance + ```yml + Resources: + Type: 'AWS::EC2::Instance' + Properties: + ImageId: !Ref LatestAmiId + Instance Type: !Ref Instance Type + KeyName: !Ref Keyname + ``` + +- For any Logical Resources in the stack, CloudFormation will make a corresponding Physical Resources in your AWS account. It is CloudFormation’s job to keep the logical and physical resources in sync. +- A template can be updated and then used to update the same stack. + + +--- +reference +- https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudTrail.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudTrail.md" new file mode 100644 index 00000000..8e3e549f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudTrail.md" @@ -0,0 +1,34 @@ +--- +title: 'CloudTrail' +lastUpdated: '2024-03-02' +--- + +AWS CloudTrail is a service that enables governance, compliance, operational auditing, and risk auditing of your AWS account. With it, you can log, continously monitor, and retain account activity related to actions acress your AWS infrastructure. CloudTrail provieds event history of your AWS account activity including actions taken through the AWS Management Console, AWS SDKs, command line tools API calls, and other AWS services, It is a regional service, but you can configure Cloud Trail to collect trails in all regions. + +--- + +- CloudTrail Events stores the last 90 days of events in its Event History. This is enabled by default and is no additional cost. + This event history simplifies security analysis, resource change tracking, and troubleshooting. + +- There are two types of events that can be logged in CloudTrail: management events and data events. + +- Management events provide information about management operations that are performed on resources in your AWS account. + +- Think of Management events as things normally done by people when they are in AWS. Examples: + - a user sign in + - a policy changed + - a newly created security configuration + - a logging rule deletion + +- Data events provide information about the resource operations performed on or in a resource. + +- Think of Data events as things normally done by software when hitting various AWS endpoints. Examples: + - S3 object-level API activity + - Lambda function execution activity + +- By default, CloudTrail logs management events, but not data events. And CloudTrail Events log files are encrypted using Amazon S3 server-side encryption (SSE). + You can also choose to encrypt your log files with an KMS key. As these logs are stored in S3, you can define Amazon S3 lifecycle rules to archive or delete log files automatically. If you want notifications about log file delivery and validation, you can set up Amazon SNS notifications. + +--- +reference +- https://aws.amazon.com/cloudtrail/features/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudWatch.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudWatch.md" new file mode 100644 index 00000000..a02ba5f7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Management\342\200\205and\342\200\205governance/CloudWatch.md" @@ -0,0 +1,79 @@ +--- +title: 'CloudWatch' +lastUpdated: '2024-03-02' +--- + +Amazon CloudWatch is a monitoring and observablility service. It provides you with data and actionable insights to monitor your applications, respond to system-wide performmance changes, optimize resource utilization, and get a unified view of operational health. + +--- + +- CloudWitch collects monitoring and operational data in the form of logs, metrics, and events. + +- You can use CloudWatch to detext anomalus behavior in you environments. set alarms, visualize logs and metrics side by side, take automated actions, troubleshoot issues, and discover insights to keep your applications running smoothly. + +- Within the compute domain, CloudWitch can inform tou about the health of EC2 instances, Autoscaling Groups, Elestic Load Balancers, and Routh53 Health Checks. Within the storage and content delivery domain, CloudWatch can inform you about the health of EBS Volumes, Storage Gareways, and CloudFront. + +- With regards to EC2, CloudWaich can only monitor host level metrics such as CPU, network, disk, and status checks for insights like th ehealth of the underlying hypervisor. + +- CloudWatch is NOT FloudTrail so it is important to know that only CloudTrail can monitor AWS access for security and auditing reasons. CloudWaich is all about performance. CloudTrail is all about auditing. + +- CloudWatch with EC2 will monitor events every 5 minutes by default, but you can have 1 minute intervals if you use Detailed Monitoring. + +- You can customize your CloudWaich dashboards for insights + +- There is a multi-platform CloudWatch agent which can be installed on both Linux and Windows-based instances. This agent enables you to select the metrics to be collected, including sub-resource metrics such as per-CPU core. You can use this single agent to collect both system metrics and log files from Amazon EC2 instances and on-premises servers. + +- The following metrics are not collected from EC2 instances via CloudWatch: + - Memory utilization + - Disk swap utilization + - Disk space utilization + - Page file utilization + - Log collection + +- CloudWatch's key purpose: + - Collect metrics + - Collect logs + - Collect events + - Create alarms + - Create dashboards + +## CloudWatch Logs: + +- You can use Amazon CloudWatch Logs to monitor, store, and access your log files from Amazon EC2 instances, AWS CloudTrail, Amazon Route 53, and other sources. You can then retrieve the associated log data from CloudWatch Logs. + +- It helps you centralize the logs from all of your systems, applications, and AWS services that you use, in a single, highly scalable service. + +- You can create log groups so that you join logical units of CloudWatch Logs together. + +- You can stream custom log files for further insights. + +## CloudWatch Events: + +- Amazon CloudWatch Events delivers a near real-time stream of system events that describe changes in AWS resources. + +- You can use events to trigger lambdas for example while using alarms to inform you that something went wrong. + +## CloudWatch Alarms: + +- CloudWatch alarms send notifications or automatically make changes to the resources you are monitoring based on rules that you define. + +- For example, you can create custom CloudWatch alarms which will trigger notifications such as surpassing a set billing threshold. + +- CloudWatch alarms have two states of either ok or alarm + +## CloudWatch Metrics: + +- CloudWatch Metrics represent a time-ordered set of data points. + These basically are a variable you can monitor over time to help tell if everything is okay, e.g. Hourly CPU Utilization. + +- CloudWatch Metrics allows you to track high resolution metrics at sub-minute intervals all the way down to per second. + +## CloudWatch Dashboards: + +- CloudWatch dashboards are customizable home pages in the CloudWatch console that you can use to monitor your resources in a single view + +- These dashboards integrate with CloudWatch Metrics and CloudWatch Alarms to create customized views of the metrics and alarms for your AWS resources. + +--- +reference +- https://aws.amazon.com/cloudwatch/faqs/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Bastion\342\200\205Host.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Bastion\342\200\205Host.md" new file mode 100644 index 00000000..eb342ef6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Bastion\342\200\205Host.md" @@ -0,0 +1,12 @@ +--- +title: 'Bastion Host' +lastUpdated: '2024-03-02' +--- + +AWS 리소스 구성 후 관리자는 VPC 외부에서 Private Subnet에 직접 접근할 수 없다. + +Private Subnet의 Private한 특성을 지키기 위해 Public Subnet인 인스턴스를 하나 만들어서 그 인스턴스를 통해 접근하는 방법을 많이 쓰는데, 이러한 host를 Bastion Host라고 부른다. 쉽게 말하면 내부와 외부 네트워크 사이에서 일종의 게이트 역할을 수행하는 호스트이다. + +관리자가 Bastion Host으로 SSH 연결을 한 후 Bastion Host에서 Private Subnet의 Host에 SSH 연결을 하는 형태로 Private Subnet에 접근할 수 있다. SSH 연결을 수행해야 하기 때문에 Bastion Host 또한 Public Subnet에 위치하는 EC2 Insatnce이다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/775149ad-dd6e-4823-9440-f8bf9793716f) diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/CloudFront.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/CloudFront.md" new file mode 100644 index 00000000..c7aa56f0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/CloudFront.md" @@ -0,0 +1,107 @@ +--- +title: 'CloudFront' +lastUpdated: '2024-03-02' +--- + +The AWS CDN service is called CloudFront. It serves up cached content and assets for the increased global performance of you application. + +**The main components of ClouFront:** +- **adge locations:** cache endpoint +- **the origin:** original sourve of truth to be cached such as an EC2 instance, an S3 bucket, an Elastic Load Balancer or a Route 53 config +- **distribution:** the arrangement of edge locations from the origin or basically the network itself + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/8db91220-f050-4243-ba27-cf5d398fbc0b) + +### To Use CloudFront... + +To use Amazon CloudFront, you: +- For static files, store the definitive versions of your files in one or more origin servers. These could be Amazon S3 buckets. For you dynamically generated content that is personalized or customized,. + +- Register your origin servers with Amazon CloudFront through a simple API call. Thsi call will return a `cloudfront.net` domain name that you can use to distribute content from your origin servers via the Amazon CloudFront service. For instance, you can register the Amazon S3 bucket `bucketname.s3.amazonaws.com` for all your dynamic content. Then, using the API or the AWS Management Console, you can create an Amazon CloudFront distribution that might return `abc123.cloudfront.net` as the distribution domain name. + +- include the `cloudfront` domain name, or a CNAME alias that you create, in your web application, media player, or website. Each request made using the `cloudfront.net` domain name (or the CNAME you set-up) is routed to the edge location best suited to deliver the content with the highest performance. The edge location will attempt to serve the request with a local copy of the file. If a local copy is not available, Amazon CloudFront will get a copy from the origin. This copy is then available at that edge location for future requests. + +## Performance + +Amazon CloudFront employs a global network of edge locations and regional edge caches that cache copies of your content close to your viewers. Amazon CloudFront ensures that end-user requestsare served by the closest edge location. + +As a result, viewer requests travel a short distance, improving performance for your viewers. For files not cached at the edge locations and the regional edge caches, Amazon CloudFront keeps persistent connections with your origin servers so that those files can be fetched from the origin servers as quickly as possible. + +Finally, Amazon CloudFront uses additional optimizations - e.g. wider TCP initial congestion window - to provide higher performance while delivering your content to viewers. + +## Key Details + +- When content is cached, it is done for a certain limit called the Time To Live(TTL) Which is always in seconds. + +- If needed, CloudFront can serve up entire websites including dynamic, static, streaming and interactive content. + +- Requests are always routes and cached in the nearest edge location for the user, thus propagation th CDN nodes and guaranteeing best performance for future requests. + +- There are two differnt types of distributions: + - **Web Distribution:** web sites, normal cached items, etc + - **RTMP:** streaming content, adobe, etc + +- Edge locations are not just read only, They can be written to which will then return the write value back to the origin. + +- Cached content can be manually invalidated or cleared beyond the TTL, but this does incur a cost. + +- You can invalidate the distribution of certain objects or entire directories so that content is loaded directly from the origin every time. Invalidation content is also hepful when debugging if content pulled from the origin seems correct, but pulling that same content from an edge location seems incorrect. + +- You can set up a failover for the origin by creating an origin group with two origins inside. One origin will act as the primary and the other as the secondary. CloudFront will automatically switch between the two when the primary origin fails. + +- Amazon CloudFront delivers your content from each edge location and offers a Dedicated IP Custom SSL feature. SNI Custrom SSL works with most modern browsers. + +- If you run PCI or HIPAA-compliant workloads and need to log usage data, you can do the following: + - Enable CloudFront access logs. + - Capture requests that are sent to the CloudFront API. + +- An Origin Access Identity (OAI) is used for charing private content via CloudFront. The OAI is a virtual user that will be used to give your CloudFront distribution permission to fetch a private object from your origin. (e.q. S3 bucket). + +- You can set origin groups and configuring specific origin failover options. When any of the following occur: + - The primary origin returns an HTTP status code that you’ve configured for failover + - CloudFront fails to connect to the primary origin + - The response from the primary origin takes too long (times out) + Then CloudFront routes the request to the secondary origin in the origin group. + +### With S3 + +Amazon CloudFront with an S3 bucket as the origin can help speed up both uploads and downloads for video files. + +When you configure CloudFront with an S3 bucket as the origin, CloudFront acts as a content delivery network (CDN) that caches your video files in edge locations around the world. This means that when a user requests a video file, CloudFront serves it from the edge location nearest to the user, reducing the distance and network latency between the user and the file. + +For downloads, CloudFront can significantly improve the performance by delivering the video files from the nearest edge location, resulting in faster download times. Users can benefit from reduced latency and improved data transfer speeds, particularly when accessing the files from geographically distant locations. + +For uploads, CloudFront can also help in certain scenarios. When you configure CloudFront with an S3 bucket as the origin, CloudFront can act as a proxy for the upload process. Instead of directly uploading the video file to the S3 bucket, the file can be uploaded to the CloudFront edge location nearest to the user. From there, CloudFront can route the upload request to the origin S3 bucket. + +This approach can be beneficial in cases where the user and the S3 bucket are geographically distant. Uploading the file to the nearby CloudFront edge location can reduce the upload latency, as the data has a shorter distance to travel. However, it's worth noting that CloudFront is primarily designed for content delivery and may not provide significant improvements for all upload scenarios, especially when dealing with larger file sizes or specific network conditions. + +Overall, utilizing CloudFront with an S3 bucket as the origin can improve both upload and download performance for video files by leveraging its global edge locations and caching capabilities. + +### CloudFront Signed URLs and Signed Cookies + +- CloudFront signed URLs and signed cookies provide the same basic functionality: they allow you to control who can access your content. These features exist because many companies that distribute content via the internet want to resrict access to documents, businesss data, media streams, or content that is intended for selected users. As an example, users who have paid a fee should be able to access private content that users on the free tier shouldn't. + +- If you want to serve private content through CloudFront and you're trying to decide whether to use signed URLs or signed cookies, consider the follosing + - Use signed URLs for the gollowing cases: + - You want to use an RTMP distribution. Signed cookies aren't supported for RTMP distributions. + - You want to restrict access to individual files, for example, an installation download for your application. + - Your users are using a client (for example, a custom HTTP client) that doesn't support cookies. + - Use signed cookies for the following cases: + - You want to provide access to multiple restricted files. For example, all of the files for a video in HLS format or all of the files in the paid users' area of a website. + - You don't want to change your current URLs. + +## Origin Shield + +Origin Shield is a centralized caching layer that helps increase your cache hit ratio to reduce the load on your origin. Origin Shield also decreases tour origin operating costs by collapsing request across regions so as few as on request goes to your origin per object. When enabled, CloudFront will route all origin fetches through Origin Shield and only make a request to your origin if the content is not already stored in Origin Shield's cache. + +Origin Shield is ideal for **workloads with viewers that are spread across different geographical regions** or **workloads that involve just-in-time packaging** for video streaming, on-the-fly image handling, or similar processes. + +Using Origin Shield in front of your origin will reduce the number of redundant origin fetches by first checking its central cache and only making a consolidated origin fetch for content not already in Origin Shield’s cache. Similarly, Origin Shield can be used in a multi-CDN architecture to reduce the number of duplicate origin fetches across CDNs by positioning Amazon CloudFront as the origin to other CDNs. + +Amazon CloudFront offers Origin Shield in AWS Resions where CloudFront has a [regional cache](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/HowCloudFrontWorks.html#CloudFrontRegionaledgecaches). When you enable Origin Shield, you should choose the AWS Region for Origin Shield that has the lowest latency to you origin. You can use Origin Shield with origins that are in an AWS Region, ans with origins that are not in AWS. + +--- +reference +- https://aws.amazon.com/cloudfront/faqs/?nc1=h_ls +- https://github.com/keenanromain/AWS-SAA-C02-Study-Guide#simple-storage-service-s3 +- https://puterism.com/deploy-with-s3-and-cloudfront/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Direct\342\200\205Connect\342\200\205Gateway.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Direct\342\200\205Connect\342\200\205Gateway.md" new file mode 100644 index 00000000..4293932d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Direct\342\200\205Connect\342\200\205Gateway.md" @@ -0,0 +1,50 @@ +--- +title: 'Direct Connect Gateway' +lastUpdated: '2024-03-02' +--- + +Use AWS Direct Connect gateway to connect your VPCs. You associate an AWS Direct Connect gateway with either of the following gateways: + +- A transit gateway when you have multiple VPCs in the same Region +- A virtual private gateway + +You can also use a virtual private gateway to **extend your Local Zone**. This configuration allows the VPC associated with the Local Zone to connect to a Direct Connect gateway. The Direct Connect gateway connects to a Direct Connect location in a Region. The on-premises data center has a Direct Connect connection to the Direct Connect location. + +A Direct Connect gateway is a globally available resource. You can connect to any Region globally using a Direct Connect gateway. + +Customers using Direct Connect with VPCs that currently bypass a parent AZ will not be able to migrate their Direct Connect connections or virtual interfaces. + + +### Senarios + +The follwing describe senario where you can use a Direct Connect gateway. + +- A Direct Connect gateway does not allow gateway associations that are on the same Direct Connect gateway to send traffic to each other (for example, a virtual pricate gateway to another virtual pricate gateway). + +- An exception to this rule, is when a supernet is advertised across two or more VPCs, which have their attached virtual private gateways(VGWs) associated to the same Direct Connect gateway and on the same virtual interface. + +- In this case, VPCs can communicate with each other via the Direct Connect endpoint. For example, if you advertise a supernet (for example, `10.0.0.0/8` or `0.0.0.0/0`) that overlaps with the VPCs attached to a Direct Connect gateway (for example, `10.0.0.0/24` or `10.0.1.0/24`), and on the same virtual interface, then from your on-premises network. the VPCs can communicate with each other. + +- If you want to block VPC-to-VPC communication within a Direct Connect gateway, do the follwing: + + 1. **Set up security groups** on the instances and other resources in the VPC to block traffic between VPCs, also using this as part of the default security group in the VPC. + + 2. **Avoid advertising a supernet from your on-premises network that overlaps with your VPCs.** Instead you can advertise more specific routes from your on-premises network that do not overlap with your VPCs. + + 3. **Provision a single Direct Connect Gateway for each VPC** that you want to connect to your on-premises network instead of using the same Direct Connect Gateway for multiple VPCs. + For example, instead of using a single Direct Connect Gateway for your development and production VPCs, use separate Direct Connect Gateways for each of these VPCs. + +- A Direct Connect gateway does not prevent traffic from being sent from one gateway association back to the gateway association itself (for example when you have an on-premises supernet route that contains the prefixed from the gateway association). + +- If you have a configuration with multiple VPCs connected to transit gateways associated to same Direct Connect gateway, the VPCs could communicate. To prevent the VPCs from communicating, associate a route table with the VPC attachments that have the blackhole option set. + +image + +- In the above diagram, the Direct Connect gateway enables you to use your AWS Direct Connect connection in the US East (N. Virginia) Region to access VPCs in your account in both the US East (N. Virginia) and US West (N. California) Regions. + +- Each VPC has a virtual private gateway that connects to the Direct Connect gateway using a virtual private gateway association. The Direct Connect gateway uses a private virtual interface for the connection to the AWS Direct Connect location. There is an AWS Direct Connect connection from the location to the customer data center. + +--- +reference +- https://docs.aws.amazon.com/directconnect/latest/UserGuide/direct-connect-gateways-intro.html +- https://docs.aws.amazon.com/vpc/latest/userguide/Extend_VPCs.html#access-local-zone \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/EC2\342\200\205Instance\342\200\205Connect\342\200\205Endpoint.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/EC2\342\200\205Instance\342\200\205Connect\342\200\205Endpoint.md" new file mode 100644 index 00000000..00628840 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/EC2\342\200\205Instance\342\200\205Connect\342\200\205Endpoint.md" @@ -0,0 +1,130 @@ +--- +title: 'EC2 Instance Connect Endpoint' +lastUpdated: '2024-03-02' +--- + +Imagine trying to connect to an EC2 instance within your VPC over the Internet. Typically, you’d first have to connect to a bastion host with a public IP address that your administrator set up over an IGW in your VPC, and then use port forwarding to reach your destination. + +[EC2 Instance Connect (EIC) Endpoint](https://aws.amazon.com/about-aws/whats-new/2023/06/amazon-ec2-instance-connect-ssh-rdp-public-ip-address/) is allows you to connect securely to you instances and other VPC resources from the Internet. With EIC Endpoint, **you no longer need an IGW** in your VPC, a public IP address on your resource, a bastion host, or any agent to connect to your resources. EIC Endpoint combines identity-based and network-based access controls, providing the isolation, control, and logging needed to meet your organization's security requirements. + +As a bonus, your organization administrator is also relieved of the **operational overhead** of maintaining and patching bastion hosts for connectivity. EIC Endpoint works with the [AWS Management Console](https://aws.amazon.com/console/) and [AWS CLI](https://aws.amazon.com/cli/). Futhermore, it gives you the flexibility to continue using your favorite tools, such as PuTTY and OpenSSH. + +Let's take a quick look at how EIC Endpoint works and the security measures it adopts. Then we'll finish by learning how to create an EIC Endpoint and use it to SSH from the Internet to an instance. + +## EIC Endpoint product overview + +EIC Endpoint is an **identity-aware TCP proxy**. + +It has two modes: first, AWS CLI client is used to create a secure, WebSocket tunnel from your workstation to the endpoint with your AWS Identity and Access Management (IAM) credentials. Once you’ve established a tunnel, you point your preferred client at your loopback address (127.0.0.1 or localhost) and connect as usual. + +Second, when not using the AWS CLI, the Console gives you secure and seamless access to resources inside your VPC. Authentication and authorization is evaluated before traffic reaches the VPC. The following figure shows an illustration of a user connecting via an EIC Endpoint: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/e587769d-1fec-4560-94d5-378442eb8371) + +### flexibility + +EIC Endpoints provide a high degree of flexibility. + +1. They don't require you VPC to have direct Internet connectivity using an IGW ar NAT gateway. +2. No agent is needed on the resource you wich to connect to, allowing for easy remote administration of resources which may not support agents, like third-party applience. +3. They preserve existing workflows, enabling you to continue using your preferred client software on your local workstation to connect and manage your resources. +4. IAM and [Security Groups](./security/Security Groups.md) can be used to control access, which we discuss in more detail in the next section. + +### key services to help manage access + +Prior to the launch of EIC Endpoints, AWS offered two key services to help manage access from public address space into a VPC more carefully. + +1. [EC2 Instance Connect](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Connect-using-EC2-Instance-Connect.html), which provides a mechanism that uses IAM credentials to push ephemeal SSH keys to an instance, making long-lived keys unnecessary. You can use EC2 Instance Connect with EIC Endpoints, combining the two capabilities to give you ephemeral-key-based SSH to your instances without exposure to the public Internet. + +2. As an alternative to EC2 instance Connect and EIC Endpoint based connectivity, AWS also offers [Systems Manager Session Manager (SSM)](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html), which provides **agent-based** connectivity to instances. SSM uses **IAM** for authentication and authorization, and is ideal for environments where an agent can be configured to run. + +Given that EIC Endpoint enables access to private resources from public IP space, let’s review the security controls and capabilities in more detail before discussing creating your first EIC Endpoint. + +## Security capabilities and controls + +Many AWS customers remotely managing resource inside their VPCs from the Internet still use either public IP addresses on the relevant resources, or at best a bastion host appreach combined with long-lived SSH Keys. Using public IPs can be locked down somewhat using IGW routes and/or security groups. + +However, in a dynamic environment those controls can be hard to manage. As a result, careful management of long-lived SSH keys remains **the only layer of defense**, which isn't great since we all know that these controls sometimes fail, and so defense-in-depth is important. Although bastion hosts can help, they increase the operational overhead of managing, patching, and maintaining infrastructure significantly. + +### IAM authorization + +IAM authorization is required to **create** EIC Endpoint and also to **establish a connection** via the endpoint's secure tunneling technology. + +Along with identity-based access controls goberning who, how, when, and how long used can connect, more traditional network access controls like security groups can also be used. Security groups associated with your VPC resources can be used to grant/deny access. Whether it's IAM policies or security groups, the default behavior is to deny traffic unless it is explicitly allowed. + +### Privilege and access management + +EIC Endpoint meets impotant security requirements in terms of **separation of privileges** for the control plane and data plane. An administrator with full EC2 IAM privileges can create and control EIC Endpoints (that control plane). However, they cannot use those endpoints without also having EC2 Instance Connect IAM privileges (the data plane). + +Conversely, DevOps engineers who may need to use EIC Endpint to tunnel into VPC resources do not require control-plane privileges to do so. In all cases, IAM principals using an EIC Endpoint must be part of the same AWS account (either directly or by cross-account role assumption). + +Security administrators and auditors have a **centralized view** of endpoint activity ad all API calls for configuring and connecting via the EIC Endpoint API are recorded in AWS CloudTrail. Records of data-plane connections include the IAM principal making the request, their source IP address, the requested destication IP address, and the destination port. See the following figure for an example CloudTrail entry. + + + +## Getting started + +### Creating your EIC Endpoint + +Only one endpoint is required per VPC. To create or modify an endpoint and connect to a resource, a user must have the required IAM permissions, and any security groups associated with your VPC resources must have a rule to allow connectivity. Refer to the following resources for more details on configuring security groups and sample IAM permissions. + +The AWS CLI or Console can be used to create an EIC Endpoint, and we demonstrate the AWS CLI in the following. To create an EIC Endpoint using the Console, refer to the documentation. + +### Creating an EIC Endpoint with the AWS CLI + +To create an EIC Endpoint with the AWS CLI, run the following command, replacing [SUBNET] with your subnet ID and [SG-ID] with your security group ID: + +```bash +aws ec2 create-instance-connect-endpoint \ + --subnet-id [SUBNET] \ + --security-group-id [SG-ID] +``` + +After creating an EIC Endpoint using the AWS CLI or Console, and granting the user IAM permission to create a tunnel, a connection can be established. Now we discuss how to connect to Linux instances using SSH. However, note that you can also use the OpenTunnel API to connect to instances via RDP. + +### Connecting to your Linux Instance using SSH + +With your EIC Endpoint set up in your VPC subnet, you can connect using SSH. Traditionally, access to an EC2 instance using SSH was controlled by key pairs and network access controls. + +With EIC Endpoint, an additional layer of control is enabled through IAM policy, leading to an enhanced security posture for remote access. We describe two methods to connect via SSH in the following. + +### One-click command + +To further reduce the operational burden of creating and rotating SSH keys, you can use the new `ec2-instance-connect ssh` command from the AWS CLI. With this new command, we generate ephemeral keys for you to connect to your instance. Note that this command requires use of the OpenSSH client and the latest version of the AWS CLI. To use this command and connect, you need IAM permissions as detailed [here](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/permissions-for-ec2-instance-connect-endpoint.html). + +![image](https://github.com/rlaisqls/TIL/assets/81006587/59625e87-e15a-4837-b842-079167c889a5) + +To test connecting to your instance from the AWS CLI, you can run the following command where [INSTANCE] is the instance ID of your EC2 instance: + +```bash +aws ec2-instance-connect ssh --instance-id [INSTANCE] +``` + +Note that you can still use long-lived SSH credentials to connect if you must maintain existing workflows, which we will show in the following. However, note that dynamic, frequently rotated credentials are generally safer. + +### Open-tunnel command + +You can also connect using SSH with standard tooling or using the proxy command. To establish a private tunnel (TCP proxy) to the instance, you must run one AWS CLI command, which you can see in the following figure: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/952bf24a-365c-4cd7-b7c3-9d3c507b2ed8) + +You can run the following command to test connectivity, where [INSTANCE] is the instance ID of your EC2 instance and [SSH-KEY] is the location and name of your SSH key. For guidance on the use of SSH keys, refer to our documentation on [Amazon EC2 key pairs and Linux instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html). + +```bash +ssh ec2-user@[INSTANCE] \ + -i [SSH-KEY] \ + -o ProxyCommand='aws ec2-instance-connect open-tunnel \ + --instance-id %h' +``` + +Once we have our EIC Endpoint configured, we can SSH into our EC2 instances without a public IP or IGW using the AWS CLI. + +## Conclusion + +EIC Endpoint provides a secure solution to connect to your instances via SSH or RDP in private subnets without IGWs, public IPs, agents, and bastion hosts. By configuring an EIC Endpoint for your VPC, you can securely connect using your existing client tools or the Console/AWS CLI. + +--- +reference +- https://aws.amazon.com/ko/blogs/compute/secure-connectivity-from-public-to-private-introducing-ec2-instance-connect-endpoint-june-13-2023/ +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Connect-using-EC2-Instance-Connect-Endpoint.html + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/ENI.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/ENI.md" new file mode 100644 index 00000000..7df8f725 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/ENI.md" @@ -0,0 +1,101 @@ +--- +title: 'ENI' +lastUpdated: '2024-03-02' +--- + +An elastic network interface is a networking component that represents a **virtual network card**. + +It can include the following attributes: + +- A primary private IPv4 address from the IPv4 address range of your VPC +- One or more secondary private IPv4 addresses from the IPv4 address range of your VPC +- One Elastic IP address (IPv4) per private IPv4 address +- One public IPv4 address +- One or more IPv6 addresses +- One or more security groups +- A MAC address +- A source/destination check flag +- A description + +## Network interface basis + +You can create a network interface, attach is to an instance, detach it from an instance and attach it to another instance. The attributes of a network interface follow it as it's attached or detacehd from an instance and reattached to another instance. When you move a network interface from one instance to another, network traffic is redirect to the new instance + +### primary network interface + +Each instance has a default network interface, called the primary network interface. You cannot detach a primary network interface from an instance. You can create an dattach additional network interfaces. + +If you attach two or more network interfaces from the same subnet to an instance, you might encounter networking issues such as asymmetric routing. If possible, use a secondary private IPv4 address on the primary network interface instead. + +### public IPv4 addresses for network interfaces + +In a VPC, all subnets have a mofifiable attribute that determines whether network interfaces created in that subnet (and therefore instances launched into that subnet) are assigned a public IPv4 address. The public IPv4 address is assigned from Amazon's pool of public IPv4 addresses. When you launch an instance, the IP address is assigned to the primary network interface that's ceated. + +When you create a network interface, it inherits the public IPv4 addressing attribute from the subnet. If you later modify the public IPv4 addressing attribute of the subnet, the network interface keeps the setting that was in effect when it was created. If you launch an instance and specify an existing network interface as the primary network interface, the public IPv4 address attribute is determined by this network interface. + +### Elastic IP addresses for network interface + +If you have an Elastic IP address, you can associate it with one of the private IPv4 addresses for the network interface. You can associate one Elastic IP address with each private IPv4 address. + +If you disassociate an Elastic IP address from a network interface, you can release it back to the address pool. This is the only way to associate an Elastic IP address with an instance in a different subnet or VPC, as network interfaces are specific to subnets. + +### Prefix Delegation + +A Prefix Delegation prefix is a reserved private IPv4 or IPv6 CIDR range that you allocate for automatic or manual assignment to network interfaces that are associated with an instance. By using Delegated Presixes, you can launch services faster by assigning a range of IP addresses as a single prefix. + +### Termination behavior + +You can set the termination behavior for a network interface that's attached to an instance. You can specify whether the network interface should be automatically deleted when you terminate the instance to which it's attached. + +### Source/destination checking + +You can enable or disable source/destination checks, which ensure that the instance is either the source or the destination of any traffic that it receives. Source/destinatino checks are enabled by default. You must disable source/destination checks if the instance runs services such as network address translation, routing, or firewalls. + +### Monitoring IP traffic + +You can enable a VPC flow log on your network interface to capture information about the IP traffic going to and from a network interfac. After you've created a flow log, you can view and retrieve its data in Amazon CloudWatch Logs. For more information. + +## Network cards + +Instances with multiple network cards provide higher network performance, including bandwidth capabilities above 100 Gbps and improved packet rate performance. Each network interfave is attaches to a network card. The primary network interface must be assigned to network card index 0. + +If you enable Elastic Fabric Adapter (EFA) when you launch an instance that supports multiple network cars, all network cards are available. You can assign up to one EFA per network card. An EFA counts as a network interface. + +## Key Details + +- ENI is used mainly for low-budget, high-availabliity network solutions. + +- However, if used mainly for low-budget, high-availability network solutions + +- Enahced Networking ENI uses single root I/O virtualization to provide high-performance networking capabilities on supported instance types. SR-IOV provides higher I/O and lower throughput and it ensures higher bandwidth, higher packet per second (PPS) performance, and consistently lower inter-instance latensies. SR-IOV does this by dedication the interface to a single instance and effectively bypassing parts of the Hypervisor which allows for better performance. + +- Adding more ENIs won't necessarily speed up your network throughput, but Enhanced Networking ENI will. + +- There is no extra charge for using Enhanced Networking ENI and the better network performance it provides. The only downside is that Enhanced Networking ENI is not available on all EC2 instance families types. + +- You can attach a network interface to an EC2 instance in the following ways: + - When it's running (hot attach) + - When it's stopped (warm attach) + - When the instance is being launched (cold attach) + +- If an EC2 instance fails with ENI properly configured, you (or more likely, the code running on your behalf) can attach the network interface to a hot standby instance. Because ENI interfaces maintain their own private IP addresses, Elastic IP addresses, and MAC address, network traffic wil begin to flow to the standby instance as soon as you attach the network interface on the replcement instance. Users will experience a brief loss of connectivity between the tine the instance fails and the time that the network interface is attached to the standby instance, but no changes to the VPC route table or you DNS server are required. + +- For instances that work with Machine Learning an dHigh Performance Computing, use EFA(Elastic Fabrip Adaptor). EFAs accelerate the work required from the above use cases. EFA provides lower and more consistent latency and higher throughput than the TCP transport traditionally used in cloud-based High Performance Computing systems. + +- EFA can also use OS-bypass (on linux only) that will enable ML and HPC applications to interface with the EFA directly, rather than be normally routed to it through the OS. This gives it a huge performance increase. + +- You can enable a VPC flow log on you network interface to capture information about the IP traffic going to and from a network interface. + +## AWS Hyperplane + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/5d7cc6be-a467-4544-b1d0-712d86aa16af) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/09c0681f-0600-4eb6-b84b-66abd3aa0bc9) + +> What’s changing
Starting today, we’re changing the way that your functions connect to your VPCs. AWS Hyperplane, the Network Function Virtualization platform used for Network Load Balancer and NAT Gateway, has supported inter-VPC connectivity for offerings like AWS PrivateLink, and we are now leveraging Hyperplane to provide NAT capabilities from the Lambda VPC to customer VPCs.
The Hyperplane ENI is a managed network resource that the Lambda service controls, allowing multiple execution environments to securely access resources inside of VPCs in your account. Instead of the previous solution of mapping network interfaces in your VPC directly to Lambda execution environments, network interfaces in your VPC are mapped to the Hyperplane ENI and the functions connect using it. + +Hyperplane is Load Balancing Service which used in AWS internal service. It based on S3 API's Load Balancer. It is used in API Gateway's VPC Link, NLB (Network Load Balancer), NAT Gateway, VPC Lambda etc. + +--- +reference +- https://speakerdeck.com/twkiiim/amazon-vpc-deep-dive-eni-reul-almyeon-vpc-ga-boinda \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Global\342\200\205Accelerator.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Global\342\200\205Accelerator.md" new file mode 100644 index 00000000..e72a888e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Global\342\200\205Accelerator.md" @@ -0,0 +1,73 @@ +--- +title: 'Global Accelerator' +lastUpdated: '2024-03-02' +--- + +AWS Global Accelerator is a networking service that helps you improve the availability and performance of the applications that you offer to your global users. AWS Global Accelerator is easy to set up, configure, and manage. It provides static IP addresses that provide a fixed entry point to your applications and eliminate the complexity of managing specific IP addresses for different AWS Regions and Availability Zones. + +AWS Global Accelerator utilizes the Amazon global network, allowing you to improve the performance of your applications by lowering first byte latency (the round trip time for a packet to go from a client to your endpoint and back again) and jitter (the variation of latency), and increasing throughput (amount of data transferred in a second) as compared to the public internet. + +By using AWS Global Accelerator, you can: + +- Associate the static IP addresses provided by AWS Global Accelerator to regional AWS resources or endpoints, such as Network Load Balancers, Application Load Balancers, EC2 Instances, and Elastic IP addresses. The IP addresses are anycast from AWS edge locations so they provide onboarding to the AWS global network close to your users. + +- Easily move endpoints between Availability Zones or AWS Regions without needing to update your DNS configuration or change client-facing applications. + +- Dial traffic up or down for a specific AWS Region by configuring a traffic dial percentage for your endpoint groups. This is especially useful for testing performance and releasing updates. + +- Control the proportion of traffic directed to each endpoint within an endpoint group by assigning weights across the endpoints. + +## Benefits + +- **Instant regional failover:** + - AWS Global Accelerator automatically checks the health of your applications and routes user traffic only to healthy application endpoints. If the health status changes or you make configuration updates, AWS Global Accelerator reacts instantaneously to route your users to the next available endpoint. + +- **High availability:** + - AWS Global Accelerator has a fault-isolating design that increases the availability of your application. + - When you create an accelerator, you are allocated two IPv4 static IP addresses that are serviced by independent network zones. Similar to Availability Zones, these network zones are isolated units with their own physical infrastructure and serve static IP addresses from a unique IP subnet. + - If one static IP address becomes unavailable due to IP address blocking or unreachable networks, AWS Global Accelerator provides fault tolerance to client applications by rerouting to a healthy static IP address from the other isolated network zone. + +- **No variability around clients that cache IP addresses:** + - Some client devices and internet resolvers cache DNS answers for long periods of time. So when you make a configuration update, or there’s an application failure or change in your routing preference, you don’t know how long it will take before all of your users receive updated IP addresses. + - With AWS Global Accelerator, you don’t have to rely on the IP address caching settings of client devices. Change propagation takes a matter of seconds, which reduces your application downtime. + +- **Improved performance:** + - AWS Global Accelerator ingresses traffic from the edge location that is closest to your end clients through anycast static IP addresses. Then traffic traverses the congestion-free and redundant AWS global network, which optimizes the path to your application that is running in an AWS Region. AWS Global Accelerator chooses the optimal AWS Region based on the geography of end clients, which reduces first-byte latency and improves performance by as much as 60%. + +--- + +Related question in SAA dump: + +- A company uses Application Load Balancers (ALBs) in multiple AWS Regions. The ALBs receive inconsistent traffic that varies throughout the year. The engineering team at the company needs to allow the IP addresses of the ALBs in the on-premises firewall to enable connectivity. + Which of the following represents the MOST scalable solution with minimal configuration changes? + +- Answer: **Set up AWS Global Accelerator. Register the ALBs in different Regions to the Global Accelerator. Configure the on-premises firewall's rule to allow static IP addresses associated with the Global Accelerator** + + - AWS Global Accelerator is a networking service that helps you improve the availability and performance of the applications that you offer to your global users. AWS Global Accelerator is easy to set up, configure, and manage. It provides static IP addresses that provide a fixed entry point to your applications and eliminate the complexity of managing specific IP addresses for different AWS Regions and Availability Zones. + + - Associate the static IP addresses provided by AWS Global Accelerator to regional AWS resources or endpoints, such as Network Load Balancers, Application Load Balancers, EC2 Instances, and Elastic IP addresses. The IP addresses are anycast from AWS edge locations so they provide onboarding to the AWS global network close to your users. + + - Simplified and resilient traffic routing for multi-Region applications using Global Accelerator: + +--- + +- An application with global users across AWS Regions had suffered an issue when the Elastic Load Balancer (ELB) in a Region malfunctioned thereby taking down the traffic with it. The manual intervention cost the company significant time and resulted in major revenue loss. + What should a solutions architect recommend to reduce internet latency and add automatic failover across AWS Regions? + +- Answer: **Set up AWS Global Accelerator and add endpoints to cater to users in different geographic locations** + + - As your application architecture grows, so does the complexity, with longer user-facing IP lists and more nuanced traffic routing logic. AWS Global Accelerator solves this by providing you with two static IPs that are anycast from our globally distributed edge locations, giving you a single entry point to your application, regardless of how many AWS Regions it’s deployed in. This allows you to add or remove origins, Availability Zones or Regions without reducing your application availability. Your traffic routing is managed manually, or in console with endpoint traffic dials and weights. If your application endpoint has a failure or availability issue, AWS Global Accelerator will automatically redirect your new connections to a healthy endpoint within seconds. + + - By using AWS Global Accelerator, you can: + + 1. Associate the static IP addresses provided by AWS Global Accelerator to regional AWS resources or endpoints, such as Network Load Balancers, Application Load Balancers, EC2 Instances, and Elastic IP addresses. The IP addresses are anycast from AWS edge locations so they provide onboarding to the AWS global network close to your users. + + 2. Easily move endpoints between Availability Zones or AWS Regions without needing to update your DNS configuration or change client-facing applications. + + 3. Dial traffic up or down for a specific AWS Region by configuring a traffic dial percentage for your endpoint groups. This is especially useful for testing performance and releasing updates. + + 4. Control the proportion of traffic directed to each endpoint within an endpoint group by assigning weights across the endpoints. + +- Incorrect: Set up an Amazon Route 53 geoproximity routing policy to route traffic + - Geoproximity routing lets Amazon Route 53 route traffic to your resources based on the geographic location of your users and your resources. + - Unlike Global Accelerator, managing and routing to different instances, ELBs and other AWS resources will become an operational overhead as the resource count reaches into the hundreds. With inbuilt features like Static anycast IP addresses, fault tolerance using network zones, Global performance-based routing, TCP Termination at the Edge - Global Accelerator is the right choice for multi-region, low latency use cases. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/NAT\342\200\205gateway\342\200\205&\342\200\205NAT\342\200\205instance.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/NAT\342\200\205gateway\342\200\205&\342\200\205NAT\342\200\205instance.md" new file mode 100644 index 00000000..8bb6eccb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/NAT\342\200\205gateway\342\200\205&\342\200\205NAT\342\200\205instance.md" @@ -0,0 +1,45 @@ +--- +title: 'NAT gateway & NAT instance' +lastUpdated: '2024-03-02' +--- + +|속성| NAT 게이트웨이|NAT 인스턴스| +|-|-|-| +|가용성| 고가용성. 각 가용 영역의 NAT 게이트웨이는 중복적으로 구현됩니다. 각 가용 영역에 하나의 NAT 게이트웨이를 만들어 아키텍처가 영역에 종속되지 않도록 합니다. |스크립트를 사용하여 인스턴스 간의 장애 조치를 관리합니다.| +|대역폭| 최대 100Gbps까지 확장합니다.|인스턴스 유형의 대역폭에 따라 다릅니다.| +|유지 관리| AWS에서 관리합니다. 유지 관리 작업을 수행할 필요가 없습니다.|사용자가 관리합니다(예: 인스턴스에 소프트웨어 업데이트 또는 운영 체제 패치 설치).| +|성능| 소프트웨어가 NAT 트래픽 처리에 최적화되어 있습니다. |NAT를 수행하도록 구성된 일반 AMI입니다.| +|비용| 사용하는 NAT 게이트웨이 수, 사용 기간, NAT 게이트웨이를 통해 보내는 데이터의 양에 따라 요금이 청구됩니다.|사용하는 NAT 인스턴스 수, 사용 기간, 인스턴스 유형과 크기에 따라 요금이 청구됩니다.| +|유형 및 크기| 균일하게 제공되므로, 유형 또는 크기를 결정할 필요가 없습니다.|예상 워크로드에 따라 적합한 인스턴스 유형과 크기를 선택합니다.| +|퍼블릭 IP 주소| 생성할 때 퍼블릭 NAT 게이트웨이와 연결할 탄력적 IP 주소를 선택합니다.|탄력적 IP 주소 또는 퍼블릭 IP 주소를 NAT 인스턴스와 함께 사용합니다. 새 탄력적 IP 주소를 인스턴스와 연결하여 언제든지 퍼블릭 IP 주소를 변경할 수 있습니다.| +|프라이빗 IP 주소| 게이트웨이를 만들 때 서브넷의 IP 주소 범위에서 자동으로 선택됩니다.|인스턴스를 시작할 때 서브넷의 IP 주소 범위에서 특정 프라이빗 IP 주소를 할당합니다. +|보안 그룹| 보안 그룹을 NAT 게이트웨이와 연결할 수 없습니다.|보안 그룹을 NAT 게이트웨이 기반 리소스와 연결하여 인바운드 및 아웃바운드 트래픽을 제어할 수 있습니다. NAT 인스턴스 뒤의 리소스 및 NAT 인스턴스와 연결하여 인바운드 및 아웃바운드 트래픽을 제어합니다.| +|네트워크 ACL| 네트워크 ACL을 사용하여 NAT 게이트웨이가 위치하고 있는 서브넷에서 보내고 받는 트래픽을 제어합니다.|네트워크 ACL을 사용하여 NAT 인스턴스가 위치하고 있는 서브넷에서 보내고 받는 트래픽을 제어합니다.| +|흐름 로그| 흐름 로그를 사용하여 트래픽을 캡처합니다.|흐름 로그를 사용하여 트래픽을 캡처합니다.| +|Port forwarding| 지원하지 않음.|포트 전달을 지원하려면 구성을 수동으로 사용자 지정합니다.| +|Bastion 서버| 지원하지 않음.|Bastion 서버로 사용합니다| +|트래픽 지표| NAT 게이트웨이에 대한 CloudWatch 지표를 확인합니다.|인스턴스에 대한 CloudWatch 지표를 확인합니다.| +|제한 시간 초과 동작| 연결 제한 시간이 초과하면 NAT 게이트웨이는 연결을 계속하려고 하는 NAT 게이트웨이 뒤의 리소스로 RST 패킷을 반환합니다 (FIN 패킷을 보내지 않음).| 연결 제한 시간이 초과하면 NAT 인스턴스는 NAT 인스턴스 뒤의 리소스로 FIN 패킷을 전송하여 연결을 닫습니다.| +|IP 조각화 |UDP 프로토콜에서 IP 조각화된 패킷의 전달을 지원합니다.
TCP 및 ICMP 프로토콜에 대해서는 조각화를 지원하지 않습니다. 이러한 프로토콜의 조각화된 패킷은 삭제됩니다.|UDP, TCP 및 ICMP 프로토콜에 대해 IP 조각화된 패킷의 재수집을 지원합니다.| + +## NAT 인스턴스에서 NAT 게이트웨이로 마이그레이션 + +이미 NAT 인스턴스를 사용하는 경우 이를 NAT 게이트웨이로 대체하는 것이 좋습니다. NAT 인스턴스와 동일한 서브넷에 NAT 게이트웨이를 만든 다음, NAT 인스턴스를 가리키는 라우팅 테이블의 기존 경로를 NAT 게이트웨이를 가리키는 경로로 대체할 수 있습니다. 현재 NAT 인스턴스에 사용하는 것과 동일한 탄력적 IP 주소를 NAT 게이트웨이에 사용하려는 경우에도 먼저 NAT 인스턴스의 탄력적 IP 주소를 연결 해제하고 NAT 게이트웨이를 만들 때 이 주소를 게이트웨이에 연결해야 합니다. + +NAT 인스턴스에서 NAT 게이트웨이로 라우팅을 변경하거나 NAT 인스턴스에서 탄력적 IP 주소의 연결을 해제하면 현재 연결이 끊어지고 연결을 다시 설정해야 합니다. 중요한 작업(또는 NAT 인스턴스를 통해 작동하는 기타 작업)이 실행 중이지 않은지 확인합니다. + +--- + +- The DevOps team at an IT company is provisioning a two-tier application in a VPC with a public subnet and a private subnet. The team wants to use either a NAT instance or a NAT gateway in the public subnet to enable instances in the private subnet to initiate outbound IPv4 traffic to the internet but needs some technical assistance in terms of the configuration options available for the NAT instance and the NAT gateway. + As a solutions architect, which of the following options would you identify as CORRECT? (Select three) + +- NAT instance can be used as a bastion server + +- Security Groups can be associated with a NAT instance + +- NAT instance supports port forwarding + +--- +reference +- https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/vpc-nat-comparison.html +- https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/VPC_NAT_Instance.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Route53.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Route53.md" new file mode 100644 index 00000000..e33d081b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Route53.md" @@ -0,0 +1,68 @@ +--- +title: 'Route53' +lastUpdated: '2024-03-02' +--- + +Amazon Route 53 is a highly available and scalable Domain Name System(DNS) service. You can use Route 53 to perform three main functions in any combination: domain registration, DNS routing, and health checking. + +--- + +- DNS is used to map human-readable domain names into an internet protocol address similarly to how phone books map company names with phone numbers. + +- When you buy a domain name, every DNS address starts with an SOA(Start of Authority) record. The SOA record stores information about the name of the server that kicked off the the transfer of ownership, the administrator who will now use the domain, the current metadata available, and the default number of seconds or TTL. + +- NS records, or Name Server records, are used by the Top Level Domain hosts(`.org`, `.com`, `.uk`, etc.) to direct traffic to the Content servers. The Content DNS servers contain the authoritative DNS records. + +- Browsers talk to the Top Level Domains whenever they are queried and encounter domain name that they do not recognize. + 1. Browsers will ask for the authoritative DNS records associated with the domain. + 2. Because the Top Level Domain contains NS records, the TLD can in turn query the Name Servers for their own SOA + 3. Within the SOA, there will be the requested informaion. + 4. Once this information is collected, it will then be returned all the way back to the original browser asking for it. + In summary: Browser -> TLD -> NS -> SOA -> DNS record. The pipeline reverses when the correct DNS record is found + +- Autoritative name servers store DNS record information, usually a DNS hosting provider or domain register like GoDaddy that offers both DNS registration and hosting. + +- There are multitude of DNS records for Route53. Here are some of the more common ones: + - **A records:** These are the fundamental type of DNS record. The `A` in A records stands for `address`. These records are used by a computer to directly pair a domain name to an IP address. IPv4 and IPv6 are both supported with `AAAA` referring to the IPv6 version. + A: URL -> IPv4
AAAA: IRL -> IPv6 + - **CName records:** Also referred to as the Canonical Name. These records are used to resolve one domain name to another domain name. For example, the domain of the mobile version of a website may be a CName from the domain of the browser bersion of that same website rather than a separate IP address. This would allow mobile users who visit the site and to receive the mobile version. + CNAME: URL -> URL + - **Alias records:** These records are used to map your domains to AWS resources such ad load balancers, CDN endpoints, and S3 buckets. Alias records function similarly to CNames in the sense that you map don domain to another. + The key differnce though is that by pointing your Alias record at a service rather than domain name, you have the abiliry to freely change your domain names if needed and not have to worry about what records might be mapped to it. Alias records give tou dynamic functionality. + One other major difference between CNames and Alias records is that a CName cannot be used for the naked domain name (the apex record in your entire DNS configuration / the primary record to be used). CNames must always be secondary records that can map to another secondary record or the apex record. The primary must always be of type Alias or A Record in order to work. + Alias: URL -> AWS Resource + - PTR records: These records are the opposite of an A record PTR records map an IP to a domain and they are used in reverse DNS lookups as a way to obbtain the domain name of an IP address. + PTR: IPv4 -> URL + +- Due to the dynamix nature of Alias records, they are often recommended for most usecases and should be used when it is possible to. + +- TTL is the length that a DNS record is cached on either the resolcing servers or the userd own cache so that a fresher mapping of IP to domain can be retrieved. Time to live is measured in seconds and the lower the TTL the faster DNS changes propagate across the internet. Most providers, for example, have a TTL that lasts 48 hours. + +- Route53 health checks can be used for any AWS endpoint that can be accessed via the Internet. This makes it an ideal option for monitoring the health of your AWS endpoints. + +## Route53 Routing Policies + +- When you create a record, you choose a routing policy, which determins how Amazon Reoute 53 responsd to DBS queries. The routing policies available are: + - Simple Routing + - Weighted Routing + - Latency-based Routing + - Failover Routing + - Geolocation Routing + - Geo-proximity Routing + - Multivalue Answer Routing + +- **Simple Routing** is used when you just need a single record in your DNS with either one or more IP addresses behind the record in case you want to balance load. If you specify multiple values in a Simple Routing polivy, Route53 returns a random IP from the options available. + +- **Weighted Routing** is used when you want to split your traffic based on assigned weights. For example, if you want 80% of your traffic to go to one AZ and the rest to goto another, use Weighted Routing. This policy is very useful for testing feature changes and due to the traffic splitting characteristics, it can double as a means to perform blue-grean deployments. When creating Whighted Routing, you need to specify a new record for each IP address. You cannot group the various IPs under one record like with Simpele Routing. + +- **Latency-base Routing**, as the name implies, is based on setting up routing based on what would be the lowest latency for a given user. To use latency-based routing, you must create a latency resource record set in the same region as the corresponding EC2 or ELB resource receiving the traffix. When Route53 receives a query for your site, it selects the record set that gives the user the quickest speed. When creating Latency-based Routing, you need to specify a end record for each IP. + +- **Failover Routing** is used when you want to configure an active-passive failover set up. Route53 will monitor the health of your primary so that it can failover when needed. You can also manually set up health checks to monitor all endpoints if you want more detailed ruls. + +- **Geolocation Routing** lets you choose where traffic will be sent based on the geographic location of your users. + +- **Geo-proximity Routing** lets you choose where traffic will be sent based on the geographic location of your users and your resources. You can choose to route more or less traffic based on a specified weight which is referred to as a bias. This bias either expands or shrinks the availability of a geographic region which makes it easy to shift traffic from resources in one location to resources in another. + To use this routing method, you must enable Route53 traffic flow. If you want to control global traffic, use Geo-proximity routing. If you want traffic to stay in a local region, use Geolocation routing. + +- **Multivalue Routing** is pretty much the same as Simple Routing, but Multivalue Routing allows you to put health checks on each record set. This ensures then that only a healthy IP will be randomly returned rather than any IP. + \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Transit\342\200\205Gateway.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Transit\342\200\205Gateway.md" new file mode 100644 index 00000000..42263ad2 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/Transit\342\200\205Gateway.md" @@ -0,0 +1,55 @@ +--- +title: 'Transit Gateway' +lastUpdated: '2024-03-02' +--- + +AWS Transit Gateway connects your Amazon Virtual Private Clouds (VPCs) and on-premises networks through a central hub. This connection simplifies your network and puts an end to complex peering relationships. Transit Gateway acts as a highly scalable cloud router—each new connection is made only once. + + + +## Use cases + +- **Deliver applications around the world:** Build, deploy, and manage applications across thousands of Amazon VPCs without having to manage peering connections or update routing tables. + +- **Rapidly move to global scale:** Share VPCs, Domain Name System (DNS), Microsoft Active Directory, and IPS/IDS across Regions with inter-Region peering. + +- **Smoothly respond to spikes in demand:** Quickly add Amazon VPCs, AWS accounts, virtual private networking (VPN) capacity, or AWS Direct Connect gateways to meet unexpected demand. + +- **Host multicast applications on AWS:** Host multicast applications that scale based on demand, without the need to buy and maintain custom hardware. + +## vs. VPC peering + +AWS VPC Peering connection is a networking connection between two VPCs that enables you to route traffic between them privately. Instances in either VPC can communicate with each other as if they are within the same network. + +AWS Transit Gateway is a fully managed service that connects VPCs and On-Premises networks through a central hub without relying on numerous point-to-point connections or Transit VPC. + +You can attach all your hybrid connectivity (VPN and Direct Connect connections) to a single Transit Gateway instance, consolidating and controlling your organization’s entire AWS routing configuration in one place. + +VPC has low cost since you need to pay only for data transfer, however transit gateway has 2 times more cost since provide more complex features as simplify network management architecture, reduce operational overhead, and centrally manage external connectivity at scale. Below is a summary of the characteristics of each service. + +|Service|Advantages|Disadvantages| +|-|-|-| +|VPC peering|- Low cost since you need to pay only for data transfer.
- No bandwidth limit.|- Complex at scale. Each new VPC increases the complexity of the network. Harder to maintain route tables compared to TGW.
- No transit routing.
- Maximum 125 peering connections per VPC.| +|Transit Gateway|- Simplified management of VPC connections. Each spoke VPC only needs to connect to the TGW to gain access to other connected VPCs.
- Supports more VPCs compared to VPC peering.
- TGW Route Tables per attachment allow for fine-grained routing.|- Additional hop introduces some latency.
- Extra cost of hourly charge per attachment in addition to data fees.| + +**Choose VPC Peering if:** +- Number of VPCs to be connected is lower (~<10). +- You need multiple VPC's connectivity to On-premises. +- You want to minimize data transfer costs when significant volumes of data transfer across regions, VPC Peering is cost-effective. +- Need for low latency. +- You need high throughput. Network bandwidth requirement is more than 50 Gbps. + +**Choose Transit Gateway if:** +- You need VPC connectivity at scale. Number of VPCs to be connected is higher (~>10) or scale in the future as the business grows. +- You need network-level segmentation. (possible with multiple TGW route tables) +- You need multiple VPCs connectivity to On-premises. + +image +image + +--- + +reference +- https://aws.amazon.com/transit-gateway/?nc1=h_ls +- https://medium.com/awesome-cloud/aws-difference-between-vpc-peering-and-transit-gateway-comparison-aws-vpc-peering-vs-aws-transit-gateway-3640a464be2d +- https://dev.classmethod.jp/articles/different-from-vpc-peering-and-transit-gateway/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC.md" new file mode 100644 index 00000000..20344031 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC.md" @@ -0,0 +1,110 @@ +--- +title: 'VPC' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/a61614ad-4b4e-4533-ba3d-434ae438978d) + +가상 프라이빗 클라우드(VPC)는 퍼블릭 클라우드 내에서 호스팅되는 안전하고 격리된 프라이빗 클라우드이다. + +VPC를 사용하면 정의한 논리적으로 격리된 가상 네트워크에서 AWS 리소스를 효율적으로 관리할 수 있다. VPC별로 네트워크를 구성하거나 각각의 VPC에 따라 다르게 네트워크 설정을 줄 수 있다. 또한 각각의 VPC는 완전히 독립된 네트워크처럼 작동한다. + +아래 그림은 VPC의 예시이다. VPC에는 리전의 각 가용성 영역에 하나의 서브넷이 있고, 각 서브넷에 EC2 인스턴스가 있고, VPC의 리소스와 인터넷 간의 통신을 허용하는 인터넷 게이트웨이가 있는 구조이다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/2546ee1d-2f1d-48c0-b996-31d039d27e58) + +쉽게 예시를 들자면, 퍼블릭 클라우드를 붐비는 레스토랑으로, 가상 프라이빗 클라우드를 붐비는 레스토랑의 예약된 테이블로 생각해볼 수 있다. 식당이 사람들로 가득 차 있어서 내부가 혼잡해도 '예약석'이라고 표시된 테이블은 예약한 사람만 앉을 수 있다. 마찬가지로 퍼블릭 클라우드는 컴퓨팅 리소스에 액세스하는 다양한 클라우드 고객으로 가득 차 있지만, VPC는 이러한 리소스 중 일부를 한 고객만 사용할 수 있도록 예약한다. + +## VPC를 구축하는 과정 + +### 1. ip 예약 +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/b09d481e-bdf6-4318-982f-404bc697ad01) + +VPC를 구축하기위해서는 VPC의 IP범위를 RFC1918이라는 사설 아이피대역에 맞추어 구축해야한다. + +VPC에서 사용하는 사설 아이피 대역은 아래와 같다. + +- `10.0.0.0 ~ 10.255.255.255`(10/8 prefix) +- `172.16.0.0 ~ 172.31.255.255`(182.16/12 prefix) +- `192.168.0.0 ~ 192.168.255.255`(192.168/16 prefix) + +한번 설정된 아이피대역은 수정할 수 없으며 각 VPC는 하나의 리전에 종속된다. 각각의 VPC는 완전히 독립적이기때문에 만약 VPC간 통신을 원한다면 VPC 피어링 서비스를 고려해볼 수 있다. + +### 2. 서브넷 설정 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/28fd0a05-3f4e-421a-bded-32e1567fe2ee) + +서브넷이란 앞서 설정한 VPC를 잘게 쪼개는 과정이라고 생각할 수 있다. VPC 단 안에서 더 많은 네트워크 망을 구성하기 위해 설정하는 단계이다. 서브넷은 VPC안에 있는 VPC보다 더 작은 단위이기때문에 서브넷마스크가 더 높아지고, 아이피범위가 더 작아진다. 서브넷을 나누는 이유는 더 많은 네트워크망을 만들기 위해서이다. + +서브넷은 가용 영역이라고 하는 Availability Zone(AZ) 여러 개에 걸쳐서 설정할 수 없으며 하나의 가용 영역 안에서 존재해야 한다는 특징을 가지고 있다. + +이렇게 생성한 서브넷 안에 AWS의 여러 리소스들을 위치시킬 수 있다. + +### 3. Route 설정 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/5a8df9db-5680-4d20-8023-0e785a61aebb) + +네트워크 요청이 발생하면 데이터는 라우터로 향하게 되고 라우팅 테이블에 따라 네트워크 요청이 작동된다. 이때 **라우팅 테이블**이란 네트워크 트래픽을 어디로 전송할지 결정하는 데 사용되는 규직 집합, 즉, 목적지에 대한 이정표라고 할 수 있다. + +기본적으로 VPC에 기본 Route table이 존재하지만 서브넷마다 다른 Route table을 할당할 수도 있다. +또한, 하나의 Route table을 여러 서브넷과 연결하는 것도 가능하다. + +위의 그림은 각각의 서브넷에 Route table을 설정한 모습이다. + +- Subnet1의 Route table은 `10.0.0.0/16`에 해당하는 네트워크 트래픽을 로컬로 향하도록 설정되어 있다. 반대로 말하면 그 이외의 트래픽은 허용되지 않는다는 것을 의미한다. +- Subnet2의 Route table은 `10.0.0.0/16`에 해당하는 네트워크 트래픽은 로컬로 보내지지만 그 외의 모든 트래픽(`0.0.0.0/0`)에 대해서는 인터넷과 연결시켜주는 관문이라고 할 수 있는 인터넷 게이트웨이(Internet Gateway)로 향하도록 설정한 모습이다. + +이때 인터넷 게이트웨이와 연결하는 Route table을 갖는 서브넷을 `Public Subnet`이라 하고, 인터넷 게이트웨이와 연결하는 Route table을 갖지 않는 서브넷을 `Private Subnet`이라고 한다. + +### 4. 네트워크 ACL과 보안그룹 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/cea2eb8e-b6c6-4ef2-a898-03022065e596) + +**Network Access Control List(NACL)**과 **Security Group(보안 그룹)**은 방화벽과 같은 역할을 하며, 이 둘을 통해 인바운드 트래픽과 아웃바운드 트래픽 보안 정책을 설정할 수 있다. + +Stateful한 방식으로 동작하는 보안그룹은 모든 허용을 차단하도록 기본설정 되어있으며 필요한 설정은 허용해주어야 한다. 서브넷이나 각각의 인스턴스에도 적용할 수 있다. + +반면, 네트워크 ACL은 Stateless하게 작동하며, 기본이 open이고 불필요한 트래픽을 막도록 설정해야한다. 서브넷단위로 적용되며 리소스별로는 설정할 수 없다. NACL과 Security Group이 충돌하면 Security Group가 더 높은 우선순위를 갖는다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/c8b98048-75e3-4913-9881-6ddcbdfb5367) + +### 5. Private 서브넷의 인터넷 연결 + +Private 서브넷이 인터넷과 통신하기 위해서는 Private 서브넷에서 외부로 요청하는 아웃바운드 트래픽을 받아 소스 IP 주소를 변환해 인터넷 게이트웨이로 트래픽을 보내는 **NAT 서비스**가 필요하다. + +NAT 서비스를 구현하는 방법은 크게 두 가지가 있다. 하나는 AWS의 NAT Gateway를 이용하는 방법이 있고, 다른 하나는 EC2를 NAT용으로 사용하는 것이다. + +NAT Gateway 또는 NAT 인스턴스는 Public 서브넷에서 동작해야 하며, Route table이 Private 서브넷에서 외부로 요청하는 아웃바운드 트래픽을 받을 수 있도록 설정한다. + +### 6. VPN 추가 연결 옵션 + +#### (1) 다른 VPC와 연결 (VPC Peering Connection) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/f7638f72-5175-451c-b8f1-794fa7602d8a) + +다른 VPC와 연결하기 위해서는 두 VPC 간에 트래픽을 라우팅할 수 있도록 서로 다른 VPC 간의 네트워크를 이어줘야하는데, 이것을 VPC Peering이라 부른다. + +이렇게 묶어주면 서로 다른 VPC의 인스턴스에서 동일한 네트워크에 속한 것처럼 통신이 가능하다. + +다른 Region, 다른 AWS 계정의 VPC Peering 연결도 가능하지만, 연결할 VPC 간의 IP 범위가 겹치는 것은 불가능하다. + +VPC Peering 연결 순서는 아래와 같다. + +``` +피어링 요청 → 피어링 요청 수락 → Route table 및 Security group 내 피어링 경로 업데이트 +``` + +#### (2) On-Premise와 연결 (VPN과 DX) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/7787fa30-0bdc-485a-92c7-2f178de23252) + +기존 온프레미스와의 연결을 통해 하이브리드 환경을 구성하는 것도 가능하다. + +- **AWS Client VPN :** VPN 소프트웨어 클라이언트를 사용하여 사용자를 AWS 또는 온프레미스 리소스에 연결하는 방식이다. +- **AWS Site-to-Site VPN :** 데이터 센터와 AWS Cloud(VPN Gateway / Transit Gateway) 사이에 암호화된 보안 연결을 생성하는 방식이다. Site-to-Site VPN 연결은 VPC에 추가된 가상 프라이빗 게이트웨이와, 데이터 센터에 위치하는 고객 게이트웨이로 구성된다. +- **AWS Direct Connect :** AWS Cloud 환경으로 인터넷이 아닌 전용 네트워크 연결을 생성하는 방식이다. (※ AWS 리소스에 대한 최단 경로) + +--- +출처 +- https://aws.amazon.com/ko/vpc/ +- https://blog.kico.co.kr/2022/03/08/aws-vpc/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC\342\200\205Mapping\342\200\205Service.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC\342\200\205Mapping\342\200\205Service.md" new file mode 100644 index 00000000..afba4f46 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC\342\200\205Mapping\342\200\205Service.md" @@ -0,0 +1,100 @@ +--- +title: 'VPC Mapping Service' +lastUpdated: '2024-03-02' +--- + +AWS VPC를 이용해서 가상 네트워크를 만들면 아래와 같은 구성이 된다. + +image + +물리 Host 내에 다수의 VPC가 존재할 수 있고, 각 VPC 간에는 독립적인 구성이 가능하다. 각 VPC는 서로 다른 IP 대역(CIDR)를 사용하는 것 뿐 아니라, 내부 IP를 같은 값으로 지정할 수도 있다. + +하나의 VPC는 여러 물리 Host로 나뉘어 위치하기도 한다. 서로 다른 Host에 위치한 `ZIGI-VM1`과 `ZIGI-VM3`은 논리적으로 같은 네트워크이기 때문에 서로 간의 통신이 가능하다. + +근데 물리적으로 서로 다른 `ZIGI-VPC1`과 `ZIGI-VM3`은 어떻게 통신이 가능할까? + +바로 아래과 같이 VPC에 대한 정보는 Encapsulation하고, 통신하고자 하는 물리 Host IP 정보를 같이 담아 전송하게 된다. + +image + +그럼 이러한 VPC Encapsulation, 물리 Host IP 정보는 어떻게 알 수 있을까? 이러한 정보를 얻기 위해서 Mapping Service라는 것이 있다. + +image + +Mapping Service를 이용해서 내가 통신하고자 하는 목적지 VM이 어느 Host에 있는지 알 수 있다. re:Invent에서 다뤄졌던 내용에서는 Mapping Service를 아래와 같이 설명한다. + +> The mapping service
- A distributed web service that handles mappings between customers VPC routes and IPs and physical destinations on the wire.
- To support microsecond-scale latencies, mappings are cached where they are used, and pro-actively invalidated when they change. + +이러한 정보를 Host에서 얻어 통신하기 위해서 각 물리 호스트에는 가상 라우터가 존재한다. 예전에는 이런 가상 라우터가 Hypervisor 내에 SW적으로 있었고, 현재는 Nitro Card(H/W)에서 그 역할을 담당한다고 한다. + +지금까지의 내용을 정리하면 아래와 같다. + +image + +## VPC Encap. + +VPC Encap 패킷의 가장 바깥쪽에는 실제 물리 호스트를 식별하기 위한 정보가 붙고, Encap시에 VPC와 ENI에 대한 정보가 포함된다. + +또, Mapping Service에는 VPC, Resource ID, ENI IP, ENI MAC, Host IP 등의 정보가 들어간다. 하나의 Host에 다수의 VPC가 들어있기 때문에 어떤 VPC인지를 구분하기 위한 VPC 구분자와 VPC 내에서 통신하고자 하는 Resource에 대한 ID 값(ENI ID), 그리고 Instance와 같은 서비스 내에서 통신하기 위해 필요한 Elastic Network Interface의 IP 주소, MAC 주소, 실제 물리 Host 간의 통신을 위한 물리 Host IP 주소이다. + +Mapping Service가 갖고 있는 정보를 포함해서 앞의 구성을 다시 그려보면 아래와 같다. + +이러한 Mapping Service를 통한 Encapsulation, Decapsulation의 과정은 사용자가 볼 수 없는 뒤에서 일어나는 일이기 때문에 내용을 볼 수 없지만, 추측할 수 있는 예시가 하나 있다. + +--- + +다음과 같이 `ZIGI-VPC1`에 `ZIGI-VM1`과 `ZIGI-VM2`가 있다. + +image + +서로 통신이 이뤄진 적이 없었다는 가정 하에, ZIGI-VM1(148)에서 ZIGI-VM2(185)로 Ping을 보내고, 응답을 받는다. 해당 통신 과정의 패킷을 `ZIGI-VM1`과 `ZIGI-VM2`에서 `tcpdump`로 확인해보자. + +```bash +$ ping -c 1 10.0.0.185 +PING 10.0.0.185 (10.0.0.185) 56(84) bytes of data. +64 bytes from 10.0.0.185: icmp_seq=1 ttl=255 time=0.910 ms + +--- 10.0.0.185 ping statistics --- +1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.910/0.910/0.910/0.000 ms +``` + +동일 네트워크에서는 통신하고자 하는 목적지 MAC 주소를 모를 경우에 ARP를 이용해서 목적지 IP 주소에 대한 MAC 주소를 확인하게 된다. + +`ZIGI-VM1(148)`에서 `ZIGI-BM2(185)`에 대한 목적지 MAC 주소를 모르기 때문에 IP 주소 `10.0.0.185`에 대한 ARP Request를 보내고, ARP Reply를 받은 이후에 ICMP Request를 보낸다. 이는 일반적인 통신 흐름이다. + +```bash +# ZIGI-CM1(10.0.0.148)의 TCP Dump +$ tcpdump -nn -i -eth0 tcmp or arp +tcpdump: verbose output suppressed, use -v or -vv for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes +11:38:38.933687 ARP, Request who-has 10.0.0.185 tell 10.0.0.148, length 28 +11:38:38.933937 ARP, Reply 10.0.0.185 is-at 02:93:0e:03:bb:88, length 28 +11:38:38.933944 IP 10.0.0.148 > 10.0.0.185: ICMP echo request, id 2746, seq 1, length 64 +11:38:38.934584 IP 10.0.0.185 > 10.0.0.148: ICMP echo reply, id 2746, seq 1, length 64 +``` + +`ZIGI-VM1`에서 확인한 통신 흐름으로 보면, `ZIGI-VM2`에서도 ARP Request를 수신 받은 이후에 ARP Reply를 보내고, `ZIGI-VM1`에서 ICMP Request를 수신한 다음 ICMP Replay를 보내야 합니다. + +하지만, `ZIGI-VM2(185)`에서 확인해 보면, ICMP에 대한 Request가 먼저 수신된다. 이후에 `ZIGI-VM1(148)`의 IP 주소인 `10.0.0.148`에 대한 ARP Request를 보내고 ARP Reply를 받고 마지막으로 `ZIGI-VM1`에서 보낸 ICMP에 대한 Reply를 보낸다. + +```bash +# ZIGI-VM2(10.0.0.185)의 TCP Dump +$ tcpdump -nn -i -eth0 tcmp or arp +tcpdump: verbose output suppressed, use -v or -vv for full protocol decode +listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes +11:38:38.934318 IP 10.0.0.148.148 > 10.0.0.185: ICMP echo request, id 2746, seq 1, length 64 +11:38:38.934349 ARP, Request who-has 10.0.0.148 tell 10.0.0.185, length 28 +11:38:38.934447 ARP, Reply 10.0.0.148 is-at 02:00:5e:ad:58:40, length 28 +11:28:28.934452 IP 10.0.0.185 > 10.0.0.148: ICMP echo reply, id 2746, seq 1, length 64 +``` + +Amazon VPC에서는 Broadcast가 지원되지 않기 때문에, Broadcast를 사용하는 기존 On-Premises의 ARP 동작 방식이 그대로 사용되지 않는다. ARP Request에 대해서 Virtual Router에서 Mapping Service에 대신 요청하고 응답을 받아서 ARP Reply를 보내는 것이다. + +이를 정리하면 다음과 같다. + +image + +--- +참고 +- https://medium.com/spaceapetech/what-the-arp-is-going-on-b4bc0e73e4d4 +- https://www.reddit.com/r/aws/comments/av8fi7/how_does_arp_works_in_aws_network/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC\342\200\205endpoint.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC\342\200\205endpoint.md" new file mode 100644 index 00000000..9d3c3d7a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPC\342\200\205endpoint.md" @@ -0,0 +1,31 @@ +--- +title: 'VPC endpoint' +lastUpdated: '2024-03-02' +--- + +A VPC endpoint enables customers to privately connect to supported AWS services and VPC endpoint services powered by AWS PrivateLink. Amazon VPC instances do not require public IP addresses to communicate with resources of the service. Traffic between an Amazon VPC and a service does not leave the Amazon network. + +VPC endpoints are virtual devices. They are horizontally scaled, redundant, and highly available Amazon VPC components that allow communication between instances in an Amazon VPC and services without imposing availability risks or bandwidth constraints on network traffic. There are two types of VPC endpoints: + +### Interface endpoints + +Interface endpoints enable connectivity to services over AWS PrivateLink. These services include some AWS managed services, services hosted by other AWS customers and partners in their own Amazon VPCs (referred to as endpoint services), and supported AWS Marketplace partner services. The owner of a service is a serviceprovider. The principal dreating the interface endpoint and using that service is a service consumer. + +An interface endpoint is a collection of one or more elastic network interfaces with a private IP address that serves as an entry point for traffic destined to a supported service. + +### Gateway endpoints + +A gateway endpoint targets specific IP routes in an Amazon VPC rout table, in the form of a prefix-list, used for traffic destined to Amazon DynamoDB or S3. Gateway endpoints do not enable AWS PrivateLink. + +--- + +Instances in an Amazon VPC do not require public IP addresses to communicate with VPC endpoints, as interface endpoints use local IP addresses within the consumer Amazon VPC. Gateway endpoints are destinations that are reachable from within an Amazon VPC through prefix-lists within the Amazon VPC’s route table. Refer to the following figure, which shows connectivity to AWS services using VPC endpoints. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/a46fa3fe-0747-44bb-b304-67179f3449af) + + +--- +reference +- https://docs.aws.amazon.com/whitepapers/latest/aws-privatelink/what-are-vpc-endpoints.html +- https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/vpc-endpoints-dynamodb.html +- https://tech.cloud.nongshim.co.kr/2023/03/16/%EC%86%8C%EA%B0%9C-vpc-endpoint%EB%9E%80/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPN.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPN.md" new file mode 100644 index 00000000..8a2c184c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPN.md" @@ -0,0 +1,28 @@ +--- +title: 'VPN' +lastUpdated: '2024-03-02' +--- + +- VPCs can also serve as a bridge between your corporate data center and the WAS cloud. With a VPC Virtual Private Network (VPN), your NPC bocomes an extension of your ono-prem environment. + +- Naturally, your instances that you launch in your VPC can't communicate with your own on-premise servers. You can allow the access by first: + - attaching a virtual private gateway to the VPC. + - creating a custom route table for the connection. + - updating your security group rules to allow traffic from the connection. + - creating the managed VPN connection itself. + +- To bring up VPN connection, you must also define a customer gateway resource in AWS, which provides AWS information abour your customer gateway device. Ans you have to set up an Internet-routable IP address of the customer gateway's external interface. + +- A customer gateway is a physical device or software application on th on-premise side of the VPN connection. + +- Although the term "VPN connection" is a general concept, a VPN connection for AWS always refers to the connection between your VPC and your own network. AWS supports Internet Protocal security (IPsec) VPN connections. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/372f3d84-ea05-4c1c-a486-64252ad657a2) + + +- The above VPC has an attached virtual private gateway (note: not an internet gateway) and there is a remote network that includes a customer gateway which you must configure to enable the VPN connection. You set up the routing so that any traffic from the VPC bound for your network is routed to the virtual private gateway. + +- In summary, VPNs connect your on-prem with your VPC over the internet. + +--- + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPN\342\200\205Options.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPN\342\200\205Options.md" new file mode 100644 index 00000000..f681851d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/VPN\342\200\205Options.md" @@ -0,0 +1,35 @@ +--- +title: 'VPN Options' +lastUpdated: '2024-03-02' +--- + +### AWS Site-to-Site VPN + +- You can create an IPsec VPN connection between your VPC and your remote network. +- On the AWS side of the Site-to-Site VPN connection, a virtual private gateway or transit gateway provides two VPN endpoints (tunnels) for automatic failover. +- You configure your customer gateway device on the remote side of the Site-to-Site VPN connection. For more information, see the AWS Site-to-Site VPN User Guide. + +### AWS Client VPN + +- AWS Client VPN is a managed client-based VPN service that enables you to securely access your AWS resources or your on-premises network. +- With AWS Client VPN, you configure an endpoint to which your users can connect to establish a secure TLS VPN session. +- This enables clients to access resources in AWS or on-premises from any location using an OpenVPN-based VPN client. For more information, see the AWS Client VPN Administrator Guide. + +### AWS VPN CloudHub + +- If you have more than one remote network (for example, multiple branch offices), you can create multiple AWS Site-to-Site VPN connections via your virtual private gateway to enable communication between these networks. +- For more information, see [Providing secure communication between sites using VPN CloudHub](https://docs.aws.amazon.com/vpn/latest/s2svpn/VPN_CloudHub.html) in the AWS Site-to-Site VPN User Guide. + +### Third party software VPN appliance + +- You can create a VPN connection to your remote network by using an Amazon EC2 instance in your VPC that's running a third party software VPN appliance. +- AWS does not provide or maintain third party software VPN appliances; however, you can choose from a range of products provided by partners and open source communities. Find third party software VPN appliances on the AWS Marketplace. + +--- + +image + +--- +reference +- https://docs.aws.amazon.com/vpc/latest/userguide/vpn-connections.html +- https://aws.amazon.com/ko/blogs/korea/improving-security-architecture-controls-for-wfh/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/Application\342\200\205LoadBalancer\342\200\205components.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/Application\342\200\205LoadBalancer\342\200\205components.md" new file mode 100644 index 00000000..83d77ee8 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/Application\342\200\205LoadBalancer\342\200\205components.md" @@ -0,0 +1,29 @@ +--- +title: 'Application LoadBalancer components' +lastUpdated: '2024-03-02' +--- + +A load balancer serves as the single point of contact for clients. The load balancer distributes incoming application traffic across multiple targets, such as EC2 instances, in multiple Availability Zones. This increases the availability of your application. You add one or more listeners to your load balancer. + +**A listener** checks for connection requests from clients, using the protocol and port that you configure. The rules that you define for a listener determine how the load balancer routes requests to its registered targets. Each rule consists of a priority, one or more actions, and one or more conditions. When the conditions for a rule are met, then its actions are performed. You must define a default rule for each listener, and you can optionally define additional rules. + +**Each target group** routes requests to one or more registered targets, such as EC2 instances, using the protocol and port number that you specify. You can register a target with multiple target groups. You can configure health checks on a per target group basis. Health checks are performed on all targets registered to a target group that is specified in a listener rule for your load balancer. + +The following diagram illustrates the basic components. Notice that each listener contains a default rule, and one listener contains another rule that routes requests to a different target group. One target is registered with two target groups. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/5c10632a-359b-4493-a8d3-7e2515e1b2d0) + +--- + +### Why can't ALB have a fixed IP allocation + +When AWS creates ALB, EC2 is created in the corresponding subnet. The EC2 is managed by AWS, so we can't recognize it, but ALB's load balancing works internally in EC2. + +> The ENI bound to EC2, which cannot be seen, can be found in the ENI menu. + +Depending on the load on ALB, ALBEC2 automatically scales in and out. Fixed IP allocation is not possible for scaling load balancers for flexible traffic processing. + +--- +reference +- https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html +- https://repost.aws/ko/knowledge-center/alb-static-ip \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/Connection\342\200\205Draining.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/Connection\342\200\205Draining.md" new file mode 100644 index 00000000..d9911c9b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/Connection\342\200\205Draining.md" @@ -0,0 +1,17 @@ +--- +title: 'Connection Draining' +lastUpdated: '2024-03-02' +--- + +Connection Draining is a feature provided by Elastic Load Balancer that allows in-flight requests to complete before terminating an unhealthy instance. + +When an instance becomes unhealthy, the ELB stops sending new requests to that instance but allows existing requests to complete within a specified timeout period. This helps prevent the loss of in-flight requests and provides a smooth transition when instances are taken out of service. + +By enabling Connection Draining on your ELB, you ensure that requests in progress are given time to complete before terminating an unhealthy instance. This helps maintain the availability and reliability of your flagship application by minimizing disruptions caused by instances going out of service. + +image + + +--- +reference +- https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/config-conn-drain.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/ELB.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/ELB.md" new file mode 100644 index 00000000..053d40ec --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/elb/ELB.md" @@ -0,0 +1,79 @@ +--- +title: 'ELB' +lastUpdated: '2024-03-02' +--- + +Elastic Load Balancing automatically distributes incoming application traffic acress multiple targets, such as Amazon EC2 instances, Docker containers, IP addresses, and Lambda functions. It can handel the varying load of your application traffic in a single Availability Zone or acress multiple Availability Zones. Elastic Load Balancing offers three types of load balancers that all feature the high cailability, automatic scaling, and robust security necessary to make your applications fault tolerant. + +--- + +- Load balancers can be internet facing or application internal. + +- To route domain traffic to an ELS load balancer, use Amazon Route 53 to create an Alias record that points to your load balancer. An Alias record is preferable over a CName, but both can work. + +- ELBs do not have predefined IPv4 addresses; you must resolve them with DNS instead. Your load balanver will never have its own IP by default, but you can create a static IP for a network load balancer because network LBs are for high performance purposes. + +- Instances behind the ELB are reported as `InService` or `OutOfService`. When an EC2 instance behind an ELB fails a health check, the ELB stops sending traffic to that instance. + +- A dual stack configuration for a load balancer means load balancing over IPv4 and IPv6 + +- In AWS, there are three types of LBs: + - Application LBs + - Network LBs + - Classic LBs + +- **Application LBs** are best suited for HTTP(S) traffic and thy balance load on layer 7 OSI. They are intelligent enough to be application aware and Application Load Balancers also support path-based routing, host-based routing and support for containerized applications. As an example, if you change your web browser's language into French, an Application LB has visibility of the metadata it receives from your browser which contains details about the language you use. + To optimize your browsing experience, it will then route you to the French-language servers on the backend behind the LB. You can also create advanced request routing, moving traffic into specific servers based on rules that you set yourself for specific cases. + - If you need flexible application management and TLS termination then you should use the Application Load Balancer. + +- **Network LBs** are best suited for TCP traffic where performance is required and they balance load on layer 4. They are capable of managing millions of requests per second while maintaining extremely low ltency. + - If extreme performance and a static IP is needed for your application then you should use the Network Load Balancer. + +- **Classic LBs** are the legacy ELB product and they balance either on HTTPS(S) or TCP, but not both. Even though they are the oldest LBs, they still support features like sticky sessions ans X-Forwarded-For headers. + - If your application is built within the EC2 Classic network then you sould use the Classic Load Balancer. + +- The lifecycle of a request to view a eabsite behind an ELB: + 1. The browser requests the IP address for the load balancer from DBS. + 2. DNS provides the IP. + 3. With the IP at hand, your browser then makes an HTTP request for an HTML page from the Load Balancer. + 4. AWS perimeter devices checks and verifies your request befor passing it onto the LB. + 5. The LB finds an active webserver to pass on the HTTP request. + 6. The browser returns the HTML file it requested and renders the graphical representation of it on the screen. + +- Load balancers are a regional service. They do not balance load acress diffent regions. You must provision a new ELB in each region that you operate out of. + +- If your application stops responding, you'll recive a 504 error when hitting your load balancer. This means the application is having issues and the error could habe bubbled up to the load balancer from the services behind it. It does not necessatily mewn's a problem with the LB itself. + +## Advanced Features + +- To enable IPv6 DNS resolution, you need to create a second DNS resource record so that the ALIAS AAAA record resolves to the load balancer along with the IPv4 record. + +- The X-Forwarded-For header, via the Proxy Protocol, is simply the idea for load balancers to forward the requester's IP address along with the actual request for information from the servers behind the LBs. Normally the servers behind the LBs only see that the IP sending it traffic belongs to the Load Balancer. They usually have no idea about the true origin of the request as they only know about the computer (the LB) that asks them to do something. But sometimes we may want to route the original IP to the backend servers for specific usecases and have the LB's IP address ignored. The X-Forwarded-For header makes this possible. + +- Sticky Sessions bind a given user to a specific instance throughout the duration of their stay on the application or website. This means all of their interactions with the application will be directed to the same host each time. If you need local disk for your application to work, sticky sessions are great as users are guaranteed cnsistent access to the same ephemeral storage on a particular instance. The downside of sticky sessions is that, if done improperly, it can defeat the purpose of load balancing. All traffic could hypothetivally be bound to the same instance instead of being evenly distributed. + +- Path Patterns create a listener with rules to forward requests based on the URL path set within those user requests. This method, known as path-based routing. ensures that traffic can be specifically directed to multiple back-end services. For example. with Path Patterns you can route general requests to one target group and requests to render images to another target group. So the URL, "www.example.com" will be forswwarded to a server that is used for general content while "www.example.com/photes" will be forwarded to another server that renders images. + +## Cross Zone Load Balancing + +- Cross Zone load balancing guarantees even distribution acress AZs tathe than just withing a single AZ. + +- If Cross Zone load balancing is disabled, Each load balancer nede distributes requests evenly acress the registered instances in its Availability Zone only. + +- Cross Zone load balancing reduces the need to maintain equivalent numbers of instances in each enabled Availability Zone, and improves your application's ability to handle the loss of one or more instances. + +- However, it is still recommend that you maintain approximately equivalent numbers of instance in each enabled Availability Zone for higher fault tolerance. + +- For environments where clients cache DNS lookups, incoming requests might favor one of the Availability Zones. Using Cross Zone load balancing, this imbalance in the request load is spread acress all available instances in the region instead. + +## Security + +- ELS supports SSL/TLS & HTTPS termination. Termination at load balancer is desired because decryption is resource and CPU intensive. Putting the decryption burden on the load balancer enables the EC2 instances to spend their processing power on application tests, which helps improve overall performance. + +- Elastic Load Balanvers (along with CloudFront) support Perfect Forward Secrecy. This is a feature that procides additional safeguards against the eavesdropping of encrypted data in transit through the use of a uniquely random session key. This is done by ensuring that the in-use part of an encryptoin system automatically and frequently changes the keys it uses to envrypt and decrypt information. So if this latest key is compromised at all, it will only expose a small portion of the user's recent data. + +- Classic Load Balancers donot support Server Name Indication (SNI). SNI allows the server (the LB in this case) to safely host multiple TLS Certificates for multiple sites all under a single IP address (the Alias record or CName record in this case). To allow SNI, you have to use an Application Load Balancer instead or use it with a CloudFront web distribution. + +--- +reference +- https://aws.amazon.com/ko/blogs/aws/new-elastic-load-balancing-feature-sticky-sessions/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/NACLs.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/NACLs.md" new file mode 100644 index 00000000..f35f9ceb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/NACLs.md" @@ -0,0 +1,35 @@ +--- +title: 'NACLs' +lastUpdated: '2024-03-02' +--- + +A network access control list (ACL) allows or denies specific inbound or outbound traffic at the **subnet level**. You can use the default network ACL for your VPC, or you can create a custom network ACL for your VPC with rules that are similar to the rules for your security groups in order to add an additional layer of security to your VPC. + +The following diagram shows a VPC with two subnets. Each subnet has a network ACL. When traffic enters the VPC (for example, from a peered VPC, VPN connection, or the internet), the router sends the traffic to its destination. + +Network ACL A determines which traffic destined for subnet 1 is allowed to enter subnet 1, and which traffic destined for a location outside subnet 1 is allowed to leave subnet 1. Similarly, network ACL B determines which traffic is allowed to enter and leave subnet 2. + +image + +## Security Group vs. NACLs + +The following table summarizes the basic differences between security groups and network ACLs. + +|Security group|Network ACL| +|-|-| +|Operates at the instance level|Operates at the subnet level| +|Applies to an instance only if it is associated with the instance|Applies to all instances deployed in the associated subnet (providing an additional layer of defense if security group rules are too permissive)| +|Supports allow rules only|Supports allow rules and deny rules| +|Evaluates all rules before deciding whether to allow traffic|Evaluates rules in order, starting with the lowest numbered rule, when deciding whether to allow traffic| +|Stateful: Return traffic is allowed, regardless of the rules|Stateless: Return traffic must be explicitly allowed by the rules| + +The following diagram illustrates the layers of security provided by security groups and network ACLs. For example, traffic from an internet gateway is routed to the appropriate subnet using the routes in the routing table. + +The rules of the network ACL that is associated with the subnet control which traffic is allowed to the subnet. The rules of the security group that is associated with an instance control which traffic is allowed to the instance. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/aa73d7b6-0970-4f49-b5f4-00efe30e8463) + +--- +reference +- https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html +- https://www.fugue.co/blog/cloud-network-security-101-aws-security-groups-vs-nacls \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/Security\342\200\205Groups.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/Security\342\200\205Groups.md" new file mode 100644 index 00000000..e878a4a5 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/Security\342\200\205Groups.md" @@ -0,0 +1,33 @@ +--- +title: 'Security Groups' +lastUpdated: '2024-03-02' +--- + +Security Groups are used to control access (SSH, HTTP, RDP, ect.) with EC2. +They act as a virtual firewall for your instances to control inbound and outbound traffic When you launch an instance in a VPC, you can assign up to five security groups to the instance an security groups act at the instance level, not the subnet level. + +--- + +- Security Groups control inbound and outbound traffic for your instances (they act as a Firewall for EC2 Instances) while NACLs control inbound and outbound traffic for your subnets (they act as a Firewall for Subnets). Security Groups usually control the list of ports that are allowed to be used by your EC2 instances and the NACLs control which network or list of IP addresses can connect to your whole VPC. + +- Every time you make a change to a security group, that change occurs immediately + +- Whenever you create an inbound rule, an outbound rule is created immediately. This is because Security Groups are **stateful**. This means that when you create an ingress rule for a sercurity group, a corresponding egress rule is created to match it. This is in contrast with NACLs which are stateless and require manual intervention for creating both inbound and outbound rules. + +- Security Group rules are based on ALLOWs and there is no concept of DENY when in comes to Security Groups. This means you cannot explicitly deny or blacklist specific ports via Security Groups, you can only implicitly deny then by excluding then in you ALLOWs list + Because of this, everything is blocked by default. You must go in and intentionally allow access for certain ports. If you need to block specific IP addresses, use NACLs instead + +- Security groups are specific to a single VPC, so you can't share a Security Group between multiple VPCs. However, you can copy a Security Group to create a new Security Group with the same ruls in another VPC for the same AWS Account. + +- Security Groups are regional and can span AZs, but can't be cress-regional. + +- Outbound rules exist if you need to connect your server to a different service such as an API endpoint or a DB backend. You need to enable the ALLOW rule for the correct port though so that traffic can leave EC2 and enter the other AWS service. + +- You can attach multiple security groups to one EC2 instance and you can have multiple EC2 instances under the umbrella of one security group. + +- You can specify the source of you security groups (basically who is allowed to bypass the virtual firewall) to be a single **/32** IP address, an IP range, or even a separate security group. + +--- +reference +- https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html +- https://docs.aws.amazon.com/vpc/latest/userguide/security-groups.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/WAF.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/WAF.md" new file mode 100644 index 00000000..29f37819 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Netwoking/security/WAF.md" @@ -0,0 +1,35 @@ +--- +title: 'WAF' +lastUpdated: '2024-03-02' +--- + +AWS WAF is a web application thet let you allow or block the HTTP(s) requests that are bound for CloudFront, API Gateway, Application Load Balancers, EC2, and other Layer 7 entry points into you AWS Environment. AWS WAF gives you control over how traffic reaches your applications by enabling you to create security rules that block common attack patterns, such as SQL injection or cross-site scripting, and rules that filter out specific traffic patterns that you can define, WAF's default rule-set addresses issues like the OWASP top 10 security risks and is regularly updated whenever new vulnerabilities are discovered. + +--- + +- As mentioned above WAF operates as a Layer 7 firewall. This grants it the ability to monitor grabular web-based confitions like URL query string parameters. This level of detail helps to detext both foul play and honest issues with the requests getting passed onto your AWS envirionment. + +- With WAF, you can set confitions such as which IP addresses are allowed to make what kind of requests or access what kind of content. + +- Bades off of these conditions, the corresponding endpoint will either allow the request by serving the requested content or return an HTTP 403 Forbidden status. + +- At the simplest level, AWS WAF lets you choose one of the following behaviors: + - **Allow all request except the ones that you specify**: This is useful when you want CloudFront or an Application Load Balancer to serve content for a public website, but you also want to block requests from attackers. + - **Block all requests except the ones that you specify**: This is useful when you want to serve content for a restricted website whose users are readily identifiable by properties in web request, such as IP addresses that they use to brow to the website. + - **Count the requests that match the properties that you specify**: When you want to allow or block requests based on new properties in web requests, tou first can configure AWS WAF to count the requests that match those properties withour allowing or blocking those requests. This lets you confirm that you didn't accidentally configure AWS WAF to block all the traffic to your website. When you're confident that you specified the correct properties, you can change the behavior to allow or block requests. + +## WAF Protection Capabilities + +- The different request characteristics that can be used to limit access: + - The IP address that a request originates from + - The country that a request originates from + - The values found in the request headers + - Any strings that appear in the request (either specific strings or strings that match a regex pattern) + - The length of the request + - Any presence of SQL code (likely a SQL injection attempt) + - Any presence of a script (likely a cross-site scripting attempt) + +- You can also use NACLs to block malicious IP addresses, prevent SQL injections/XSS, and block requests from specific contries. However, it is good form to practice defense in depth. + +- Denying or blocking malicious users at the WAF level has the the added advantage of protecting your AWS ecosystem at its outermost border. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Region\352\263\274\342\200\205Zone.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Region\352\263\274\342\200\205Zone.md" new file mode 100644 index 00000000..2173625d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Region\352\263\274\342\200\205Zone.md" @@ -0,0 +1,72 @@ +--- +title: 'Region과 Zone' +lastUpdated: '2024-03-02' +--- + +AWS resouce는 전세계의 여러 위치에서 호스팅되고 있다. 그리고 내부적으로도 여러 영역으로 나뉘어있다. 그 중 Availability Zone, Local Zone, Wavelength Zones에 대해 알아보자. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/ad0d86d7-b794-47bd-9cb8-fac0c50abe0f) + +## Availability Zone + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/f7d32e0e-3b29-44d1-b7b0-5a88825de4c5) + +각 Region에는 **Availability Zone**이라고 하는 여러 개의 격리된 위치가 있다. Availability Zone은 Region 내의 서버를 분리 시켜놔서, 일부분이 피해를 입어도 동작시키기 위해 구분해놓은 IDC라고 생각할 수 있다. + +ELB를 이용해서 서로 다른 AZ에서도 같은 서비스를 사용가능하게끔 트래픽을 분배시켜준다. 이러한 특징을 가용성이 높다고 표현한다. + +### AZ IDs + +리소스가 Availability Zone에 분산되도록 하기 위해, AWS는 각 계정에 Availability Zone을 독립적으로 매핑한다. 내 계정에서는 A, B, C로 보이지만 다른 사람의 계정에서는 B, A, C와 같은 식으로 이름이 다르게 붙여져서, 사용자는 `us-east-1a`와 같은 코드를 통해 실제로 어떤 AZ에 저장되어있는지 알 수 없다. + +실제 리소스의 AZ 위치를 알고싶다면 식별자인 **AZ ID**를 이용해야한다. 리소스의 physical location이 정해진 시점에 확인할 수 있다. AZ 코드와 다르게 AZ ID는 무조건 하나의 Zone을 가리킨다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/71a69e38-dd99-4dd2-a675-9ac936a6fbed) + +```bash +# 특정 region의 az 확인 +aws ec2 describe-availability-zones --region region-name +# 모든 region의 az 확인 +aws ec2 describe-availability-zones --all-availability-zones +``` + +## Local Zone + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/e6e3f52e-33ae-4e10-a961-ce952a431421) + +[Local Zone](https://aws.amazon.com/ko/about-aws/global-infrastructure/localzones/features/#:~:text=AWS%20Local%20Zones%EB%8A%94%20%EC%BB%B4%ED%93%A8%ED%8C%85,AWS%20%EC%9D%B8%ED%94%84%EB%9D%BC%20%EB%B0%B0%ED%8F%AC%20%EC%9C%A0%ED%98%95%EC%9E%85%EB%8B%88%EB%8B%A4.)은 사용자에게 근접한 지역에서 서비스를 제공할 수 있도록 하는 유형이다. + +EC2 인스턴스를 시작할때 Local Zone에서 서브넷을 선택하면 Local Zone은 인터넷에 대한 자체 연결을 가지며 AWS Direct Connect를 지원한다. 따라서 Local Zone에서 생성된 리소스가 매우 짧은 지연 시간의 통신으로 로컬 사용자에게 제공할 수 있다는 이점이 있다. + +Local Zone은 지역 코드와 위치를 나타내는 식별자(`us-west2-lax-la`)로 표시된다. + +## Wavelength Zones + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/2718b928-dc35-4b42-8a2a-f1d912d7e2f0) + +Wavelength는 모바일 기기나 유저의 요청에 엄청나게 낮은 latency로 응답할 수 있도록 한다. Wavelength Zone는 Wavelength infrastructure가 배포되는 캐리어 위치의 격리 구역이다. + +Region에서 VPC를 만들면 VPC에 연결되어있는 Wavelength zone에 subnet이 만들어진다. Wavelength Zone 외에도 VPC와 연결된 모든 Availability Zone 및 Local Zone에 리소스를 생성할 수 있다. + +Carrier gateway는 carrier network로부터 온 특정 위치의 inbound 트래픽이나, 서버에서 전송하는 outbound 트래픽을 허용하는 역할을 한다. 외부에서 Wavelength Zone에 접근하려면 무조건 carrier gateway를 거쳐야한다. Carrier gateway는 Wavelength Zone 안에 있는 subnet을 포함한 VPC에서만 사용할 수 있다. 여러 Wavelength zone을 묶어주고 서로 원격통신할 수 있도록 해준다. + +> Wavelength deploys standard AWS compute and storage services to the edge of telecommunication carriers' 5G networks. Developers can extend a virtual private cloud (VPC) to one or more Wavelength Zones, and then use AWS resources like Amazon EC2 instances to run applications that require ultra-low latency and a connection to AWS services in the Region. + +image + +## Outposts + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/43d365e3-1d1f-4518-b748-64e69b750c61) + +Outpost는 사용자의 premise 서버를 AWS의 서비스나 API를 사용하여 확장할 수 있게 하는 기능이다. + +AWS Outposts는 AWS 관리 인프라에 대한 로컬 액세스를 제공하여 고객이 AWS에 서버를 둔 것과 동일한 프로그래밍 인터페이스를 사용하여 사내에서 애플리케이션을 구축하고 실행할 수 있게 한다. 그리고 지연 시간과 로컬 데이터 처리 요구사항을 줄이기 위해 AWS의 로컬 컴퓨팅 및 스토리지 리소스를 사용할 수 있도록 지원한다. + +아래 그림은 `us-west-2`의 Availability zone 두개와 Outpost가 같은 VPC에 묶여있는 모습이다. Outpost는 사용자의 on-premise 데이터 센터이고, VPC에 있는 각 영역은 하나의 subnet을 가진다. + +--- +참고 +- https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html +- https://medium.com/@ranadheerraju11/what-are-regions-availability-zones-and-local-zones-428f9b739763 +- https://docs.aws.amazon.com/wavelength/index.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\230\244\353\213\265\353\205\270\355\212\270\342\200\2051~3.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\230\244\353\213\265\353\205\270\355\212\270\342\200\2051~3.md" new file mode 100644 index 00000000..d32aa8e6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\230\244\353\213\265\353\205\270\355\212\270\342\200\2051~3.md" @@ -0,0 +1,755 @@ +--- +title: 'SAA 오답노트 1~3' +lastUpdated: '2023-10-12' +--- +- The founder has provisioned an EC2 instance 1A which is running in region A. Later, he takes a snapshot of the instance 1A and then creates a new AMI in region A from this snapshot. This AMI is then copied into another region B. The founder provisions an instance 1B in region B using this new AMI in region B. + At this point in time, what entities exist in region B? + +- Keyword: AMI + +- Answer: 1 EC2 instance, 1 AMI and 1 snapshot exist in region B + - An Amazon Machine Image (AMI) provides the information required to launch an instance. You must specify an AMI when you launch an instance. + - When the new AMI is copied from region A into region B, **it automatically creates a snapshot in region B because AMIs are based on the underlying snapshots.** Further, an instance is created from this AMI in region B. Hence, we have 1 EC2 instance, 1 AMI and 1 snapshot in region B. + +--- + +- A company uses Amazon S3 buckets for storing sensitive customer data. The company has defined different retention periods for different objects present in the Amazon S3 buckets, based on the compliance requirements. But, the retention rules do not seem to work as expected. + Which of the following options represent a valid configuration for setting up retention periods for objects in Amazon S3 buckets? (Select two) + +- Keyword: S3 + +- Answer: + - **When you apply a retention periond to an object version ecplictly, you specify a `Retain Until Date` for the object version** + - You can place a retention period on an object version either explictly or through a bucket default setting. When you apply a retention period to an object version expliciyly, you specify a `Retain Until Date` for the object version. Amazon S3 stores the Retain Until Date setting in the object version's metadata and protects the object version until the retention period expires. + - **Defferent versions of a single object can have different retention mades and periods.** + - Like all other Object Lock settings, retention periods apply to individual object versions. Defferent versions of a single object can have different retention modes and periods. + - For example, suppose that you have an object that is 15 days into a 30-day retention period, and you PUT an object into S3 with the same name and a 60-day retention period. In this case, your PUT succeeds, and S3 creates a new cersion of the object with a 60-day retention period. The olderversion maintains its original retention period and becomes deletable in 15 days. + +--- + +- Can you identify those storage volume types that CANNOT be used as boot volumes while creating the instances? (Select two) + +- Keyword: EBS + +- Answer: + - **Throughput Optimized HDD (st1)** + - **Cold HDD (sc1)** + +- The EBS volume types fall into two categories: + +- SSD-backed volumes optimized for transactional workloads involving frequent read/write operations with small I/O size, where the dominant performance attribute is IOPS. + +- HDD-backed volumes optimized for large streaming workloads where throughput (measured in MiB/s) is a better performance measure than IOPS. + +- Throughput Optimized HDD (st1) and Cold HDD (sc1) volume types CANNOT be used as a boot volume, so these two options are correct. + + +- https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lock-overview.html +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html +--- + +- A gaming company uses Amazon Aurora as its primary database service. The company has now deployed 5 multi-AZ read replicas to increase the read throughput and for use as failover target. The replicas have been assigned the following failover priority tiers and corresponding instance sizes are given in parentheses: tier-1 (16TB), tier-1 (32TB), tier-10 (16TB), tier-15 (16TB), tier-15 (32TB). + In the event of a failover, Amazon Aurora will promote which of the following read replicas? + +- Keyword: EBS + +- Answer: **Tier-1 (32TB)** + - For Amazon Aurora, each Read Replica is associated with a priority tier (0-15). In the event of a failover, Amazon Aurora will promote the Read Replica that has the highest priority (the lowest numbered tier). + - If two or more Aurora Replicas share the same priority, then Amazon RDS promotes the replica **that is largest in size**. If two or more Aurora Replicas share the same priority and size, then Amazon Aurora promotes an arbitrary replica in the same promotion tier. + - Therefore, for this problem statement, the Tier-1 (32TB) replica will be promoted. + +--- + +- An IT company wants to review its security best-practices after an incident was reported where a new developer on the team was assigned full access to DynamoDB. The developer accidentally deleted a couple of tables from the production environment while building out a new feature. + Which is the MOST effective way to address this issue so that such incidents do not recur? + +- Keyword: permissions boundary + +- Answer: **Use permissions boundary to control the maximum permissions employees can grant to the IAM principals.** + - A permissions boundary can be used to control the maximum permissions employees can grant to the IAM principals (that is, users and roles) that they create and manage. As the IAM administrator, you can define one or more permissions boundaries using managed policies and allow your employee to create a principal with this boundary. The employee can then attach a permissions policy to this principal. However, the effective permissions of the principal are the intersection of the permissions boundary and permissions policy. As a result, the new principal cannot exceed the boundary that you defined. Therefore, using the permissions boundary offers the right solution for this use-case. + +- Permission Boundary Example: + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/ee05fc00-8110-407a-a8a9-6cdfeb5589d4) + +--- + +- A large financial institution operates an on-premises data center with hundreds of PB of data managed on **Microsoft’s Distributed File System (DFS)**. The CTO wants the organization to transition into a hybrid cloud environment and run data-intensive analytics workloads that support DFS. + Which of the following AWS services can facilitate the migration of these workloads? + +- Keyword: FSx + +- Answer: **Amazon FSx for Windows File Server** + - Amazon FSx for Windows File Server provides fully managed, highly reliable file storage that is accessible over the industry-standard Service Message Block (SMB) protocol. It is built on Windows Server, delivering a wide range of administrative features such as user quotas, end-user file restore, and Microsoft Active Directory (AD) integration. + - Amazon FSx supports the use of Microsoft’s Distributed File System (DFS) to organize shares into a single folder structure up to hundreds of PB in size. So this option is correct. + - wrong answer: Amazon FSx for Lustre makes it easy and cost-effective to launch and run the world’s most popular high-performance file system. It is used for workloads such as machine learning, high-performance computing (HPC), video processing, and financial modeling. Amazon FSx enables you to use Lustre file systems for any workload where storage speed matters. FSx for Lustre does not support Microsoft’s Distributed File System (DFS), so this option is incorrect. + +--- + +- An IT security consultancy is working on a solution to **protect data stored in S3 from any malicious activity as well as check for any vulnerabilities on EC2 instances.** + As a solutions architect, which of the following solutions would you suggest to help address the given requirement? + +- Keyword: GuardDuty + +- Answer: Use **Amazon GuardDuty to monitor any malicious activity** on data stored in S3. Use security assessments provided by **Amazon Inspector to check for vulnerabilities** on EC2 instances + +- Amazon GuardDuty offers threat detection that enables you to continuously monitor and protect your AWS accounts, workloads, and data stored in Amazon S3. GuardDuty analyzes continuous streams of meta-data generated from your account and network activity found in AWS CloudTrail Events, Amazon VPC Flow Logs, and DNS Logs. It also uses integrated threat intelligence such as known malicious IP addresses, anomaly detection, and machine learning to identify threats more accurately. + +- How GuardDuty works: + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/9edace56-7058-4b52-af83-3d28dbab1f92) + +- Amazon Inspector security assessments help you check for unintended network accessibility of your Amazon EC2 instances and for vulnerabilities on those EC2 instances. Amazon Inspector assessments are offered to you as pre-defined rules packages mapped to common security best practices and vulnerability definitions. + +--- + +- A file-hosting service uses Amazon S3 under the hood to power its storage offerings. Currently all the customer files are uploaded directly under a single S3 bucket. The engineering team has started seeing scalability issues where customer file uploads have started failing during the peak access hours with more than 5000 requests per second. + Which of the following is the MOST resource efficient and cost-optimal way of addressing this issue? + +- Keyword: S3 + +- Answer: Change the application architecture to create **customer-specific custom prefixes** within the single bucket and then upload the daily files into those prefixed locations + - Amazon Simple Storage Service (Amazon S3) is an object storage service that offers industry-leading scalability, data availability, security, and performance. Your applications can easily achieve thousands of transactions per second in request performance when uploading and retrieving storage from Amazon S3. Amazon S3 automatically scales to high request rates. For example, your application can achieve at least 3,500 `PUT`/`COPY`/`POST`/`DELETE` or 5,500 GET/HEAD requests per second per prefix in a bucket. + + - There are no limits to the number of prefixes in a bucket. You can increase your read or write performance by parallelizing reads. For example, if you create 10 prefixes in an Amazon S3 bucket to parallelize reads, you could scale your read performance to 55,000 read requests per second. Please see this example for more clarity on prefixes: if you have a file f1 stored in an S3 object path like so `s3://your_bucket_name/folder1/sub_folder_1/f1`, then `/folder1/sub_folder_1/`` becomes the prefix for file f1. + + - Some data lake applications on Amazon S3 scan millions or billions of objects for queries that run over petabytes of data. These data lake applications achieve single-instance transfer rates that maximize the network interface used for their Amazon EC2 instance, which can be up to 100 Gb/s on a single instance. These applications then aggregate throughput across multiple instances to get multiple terabits per second. Therefore creating customer-specific custom prefixes within the single bucket and then uploading the daily files into those prefixed locations is the BEST solution for the given constraints. + + - https://docs.aws.amazon.com/AmazonS3/latest/dev/optimizing-performance.html + +--- + +- A retail company uses Amazon EC2 instances, API Gateway, Amazon RDS, Elastic Load Balancer and CloudFront services. To improve the security of these services, the Risk Advisory group has suggested a feasibility check for using the Amazon GuardDuty service. + Which of the following would you identify as data sources supported by GuardDuty? + +- Keyword: Guard Duty + +- Answer: **VPC Flow Logs, DNS logs, CloudTrail events** + - Amazon GuardDuty is a threat detection service that continuously monitors for malicious activity and unauthorized behavior to protect your AWS accounts, workloads, and data stored in Amazon S3. + - With the cloud, the collection and aggregation of account and network activities is simplified, but it can be time-consuming for security teams to continuously analyze event log data for potential threats. With GuardDuty, you now have an intelligent and cost-effective option for continuous threat detection in AWS. + - The service uses machine learning, anomaly detection, and integrated threat intelligence to identify and prioritize potential threats. + - GuardDuty analyzes tens of billions of events across multiple AWS data sources, such as AWS CloudTrail events, Amazon VPC Flow Logs, and DNS logs. + - With a few clicks in the AWS Management Console, GuardDuty can be enabled with no software or hardware to deploy or maintain. + - By integrating with Amazon EventBridge Events, GuardDuty alerts are actionable, easy to aggregate across multiple accounts, and straightforward to push into existing event management and workflow systems. + +--- + +- A leading carmaker would like to build a new car-as-a-sensor service by leveraging fully serverless components that are provisioned and managed automatically by AWS. The development team at the carmaker does not want an option that requires the capacity to be manually provisioned, as it does not want to respond manually to changing volumes of sensor data. + Given these constraints, which of the following solutions is the BEST fit to develop this car-as-a-sensor service? + +- Keyword: SQS + +- Answer: Ingest the sensor data in an Amazon SQS standard queue, which is polled by a Lambda function in batches and the data is written into an auto-scaled DynamoDB table for downstream processing. + + - AWS manages all ongoing operations and underlying infrastructure needed to provide a highly available and scalable message queuing service. With SQS, there is no upfront cost, no need to acquire, install, and configure messaging software, and no time-consuming build-out and maintenance of supporting infrastructure. SQS queues are dynamically created and scale automatically so you can build and grow applications quickly and efficiently. + - As there is no need to manually provision the capacity, so this is the correct option. + + - **Incorrect options:** Ingest the sensor data in Kinesis Data Firehose, which directly writes the data into an auto-scaled DynamoDB table for downstream processing + - Amazon Kinesis Data Firehose is a fully managed service for delivering real-time streaming data to destinations such as Amazon Simple Storage Service (Amazon S3), Amazon Redshift, Amazon OpenSearch Service, Splunk, and any custom HTTP endpoint or HTTP endpoints owned by supported third-party service providers, including Datadog, Dynatrace, LogicMonitor, MongoDB, New Relic, and Sumo Logic. + - **Firehose cannot directly write into a DynamoDB table, so this option is incorrect.** + +--- + +- A gaming company is looking at improving the availability and performance of its global flagship application which utilizes UDP protocol and needs to support fast regional failover in case an AWS Region goes down. The company wants to continue using its own custom DNS service. + Which of the following AWS services represents the best solution for this use-case? + +- Keyword: Global Accelerator + +- Answer: **AWS Global Accelerator** + - AWS Global Accelerator utilizes the Amazon global network, allowing you to improve the performance of your applications by lowering first-byte latency (the round trip time for a packet to go from a client to your endpoint and back again) and jitter (the variation of latency), and increasing throughput (the amount of time it takes to transfer data) as compared to the public internet. + - Global Accelerator improves performance **for a wide range of applications over TCP or UDP by proxying packets at the edge to applications running in one or more AWS Regions.** Global Accelerator is a good fit for non-HTTP use cases, such as gaming (UDP), IoT (MQTT), or Voice over IP, as well as for HTTP use cases that specifically require static IP addresses or deterministic, fast regional failover. + +--- + +- A junior scientist working with the Deep Space Research Laboratory at NASA is trying to upload a high-resolution image of a nebula into Amazon S3. The image size is approximately 3GB. The junior scientist is using S3 Transfer Acceleration (S3TA) for faster image upload. It turns out that S3TA did not result in an accelerated transfer. + Given this scenario, which of the following is correct regarding the charges for this image transfer? + +- Keyword: S3 + +- Answer: The junior scientist does not need to pay any transfer charges for the image upload + - **There are no S3 data transfer charges when data is transferred in from the internet. Also with S3TA, you pay only for transfers that are accelerated.** Therefore the junior scientist does not need to pay any transfer charges for the image upload because S3TA did not result in an accelerated transfer. + +--- + +- The engineering team at a Spanish professional football club has built a notification system for its website using Amazon SNS notifications which are then handled by a Lambda function for end-user delivery. During the off-season, the notification systems need to handle about 100 requests per second. During the peak football season, the rate touches about 5000 requests per second and it is noticed that a significant number of the notifications are not being delivered to the end-users on the website. + As a solutions architect, which of the following would you suggest as the BEST possible solution to this issue? + +- Keyword: SNS + +- Answer: Amazon SNS message deliveries to AWS Lambda have crossed the account concurrency quota for Lambda, so the team needs to contact AWS support to raise the account limit. + - AWS Lambda currently supports 1000 concurrent executions per AWS account per region. If your Amazon SNS message deliveries to AWS Lambda contribute to crossing these concurrency quotas, your Amazon SNS message deliveries will be throttled. You need to contact AWS support to raise the account limit. Therefore this option is correct. + + +--- + +- A technology blogger wants to write a review on the comparative pricing for various storage types available on AWS Cloud. The blogger has created a test file of size 1GB with some random data. Next he copies this test file into AWS S3 Standard storage class, provisions an EBS volume (General Purpose SSD (gp2)) with 100GB of provisioned storage and copies the test file into the EBS volume, and lastly copies the test file into an EFS Standard Storage filesystem. At the end of the month, he analyses the bill for costs incurred on the respective storage types for the test file. + What is the correct order of the storage charges incurred for the test file on these three storage types? + +- Answer: **Cost of test file storage on S3 Standard < Cost of test file storage on EFS < Cost of test file storage on EBS** + + - With Amazon EFS, you pay only for the resources that you use. The EFS Standard Storage pricing is $0.30 per GB per month. Therefore the cost for storing the test file on EFS is $0.30 for the month. + + - For EBS General Purpose SSD (gp2) volumes, the charges are $0.10 per GB-month of provisioned storage. Therefore, for a provisioned storage of 100GB for this use-case, the monthly cost on EBS is $0.10*100 = $10. This cost is irrespective of how much storage is actually consumed by the test file. + + - For S3 Standard storage, the pricing is $0.023 per GB per month. Therefore, the monthly storage cost on S3 for the test file is $0.023. + + - Therefore this is the correct option. + +--- + +- A video analytics organization has been acquired by a leading media company. The analytics organization has 10 independent applications with an on-premises data footprint of about 70TB for each application. The CTO of the media company has set a timeline of two weeks to carry out the data migration from on-premises data center to AWS Cloud and establish connectivity. + Which of the following are the MOST cost-effective options for completing the data transfer and establishing connectivity? (Select two) + +- Keyword: Snowball + - Keyword: VPN + +- Answer-1: **Order 10 Snowball Edge Storage Optimized devices to complete the one-time data transfer** + - Snowball Edge Storage Optimized is the optimal choice if you need to securely and quickly transfer dozens of terabytes to petabytes of data to AWS. It provides up to 80 TB of usable HDD storage, 40 vCPUs, 1 TB of SATA SSD storage, and up to 40 Gb network connectivity to address large scale data transfer and pre-processing use cases. + - As each Snowball Edge Storage Optimized device can handle 80TB of data, you can order 10 such devices to take care of the data transfer for all applications. + - Exam Alert: + - The original Snowball devices were transitioned out of service and Snowball Edge Storage Optimized are now the primary devices used for data transfer. You may see the Snowball device on the exam, just remember that the original Snowball device had 80TB of storage space. + +- Answer-2: **Setup Site-to-Site VPN to establish on-going connectivity between the on-premises data center and AWS Cloud** + - **AWS Site-to-Site VPN enables you to securely connect your on-premises network or branch office site to your Amazon Virtual Private Cloud (Amazon VPC)**. You can securely extend your data center or branch office network to the cloud with an AWS Site-to-Site VPN connection. A VPC VPN Connection utilizes IPSec to establish encrypted network connectivity between your intranet and Amazon VPC over the Internet. VPN Connections can be configured in minutes and are a good solution if you have an immediate need, have low to modest bandwidth requirements, and can tolerate the inherent variability in Internet-based connectivity. + - Therefore this option is the right fit for the given use-case as the connectivity can be easily established within the given timeframe. + + +--- + +- A geological research agency maintains the seismological data for the last 100 years. The data has a velocity of 1GB per minute. You would like to store the data with only the most relevant attributes to build a predictive model for earthquakes. + What AWS services would you use to build the most cost-effective solution with the LEAST amount of infrastructure maintenance? + +- Keyword: Kinesis + +- Answer: Ingest the data in **Kinesis Data Firehose** and use an intermediary Lambda function to filter and transform the incoming stream before the output is **dumped on S3** + +--- + +**DAX is a DynamoDB-compatible caching service that enables you to benefit from fast in-memory performance for demanding applications.** + +- Keyword: DAX + +--- + +A company is transfering a cluster of NoSQL databases to Amazon EC2. The database automatically duplicates data so as to retain at leasy three copies of it. I/O throughput of the servers is most vital. What sort of instance a solutions architect should suggest for the migration? + +- A. Bustable general purpost instance with an EBS volume +- B. Memory optimized instance with an EBS optimization enabled +- C. Compute optimized instance with an EBS optimization enabled +- D. Instance store with storage optimized instances + +- Keyword: EBS + +- answer: D + +- A and C is relate with CPU intensive workloads. Instead we need I/O throughput intensive as per the question. And memory optimization(B) is no connection with I/O throuput. + +- However Storage optimized instances are porvide `high, sequential read and write access to very large data sets on local storage`. So, answer is D. + +image + +--- + +You need to design a solution for migrating a persistent database from on-premise to AWS. The database needs 64000 IOPS, which needs to be hosted on database instance on a single EBS volume. Which solution will meet the goal? + +- Keyword: EBS + +- Answer: Provision **Nitro**-based EC2 instances with Amazon **EBS provisioned IOPS SSD (io1)** volume attaches. Configure the volume to have 6400 IOPS + +Nitro is the underlying platform for the latest generation of EC2 instances that enables AWS to innovate faster, further reduce cost for our customers, and deliver added benefits like increased security and new instance types. + +It is possible to reach 64000 IOPS when use Nitro system + +--- + +- Keyword: Aurora + +Aurora replication differs from RDS replicas in the sense that **it is possible for Aurora's replicas to be both a standby as part of a multi-AZ configuration as well as a target for read traffic**. In RDS, the multi-AZ standby cannot be configured to be a read endpoint and only read replicas can serve that function. + +--- + +A company wants to create a multi-instance application which requires low latency between the instances. What recommendation should you make? + +- Answer: Implement auto scaling group with cluster placement group. + +--- + +A e-commerce company hosts its internet-facing containerized web application on an Amazon **EKS cluster**. The EKS cluster is situated within a VPC's private subnet. The EKS cluster is accessed by developers using a bastion server on a public network. **As per new compliance requirement, security policy prohibits use of bastion hosts and public internet access to the EKS cluster.** Which of the following is most cost-effective solution? + +- Keyword: VPN + +- Answer: **Establish a VPN connection.** + +--- + +- To improve the performance and security of the application, the engineering team at a company has created a CloudFront distribution with an Application Load Balancer as the custom origin. The team has also set up a Web Application Firewall(WAF) with CloudFront distribution. The security team at the company has noticed a surge in malicious attacks from a specific IP address to steal sensitive data stored on the EC2 instances. + As a solutions architect, which of the following actions would you recommend to stop the attacks? + +- Keyword: WAF + +- Answer: **Create IP match condition in the WAF to block the malicious IP address** + - AWS WAF is a web application firewall that helps protect your web applications or APIs against common web exploits that may affect availability, compromise security, or consume excessive resources. AWS WAF gives you control over how traffic reaches your applications by enabling you to create secuirty rules that block common attack patterns, such as SQL injection or cross-dite scripting, and rules that filter out specific traffic patterns you define. + - If you want to aloow or block web requests based on the IP addresses that the requests originate from, create one or more IP match conditions. An IP match condition lists up to 10,000 UP addresses or UP address ranges that your requests originate from. So, this option is correct. + - **NACLs are not associated with instances.** + +--- + +- You have multiple AWS accounts within a single AWS Region managed by AWS Organizations and you would like to ensure all EC2 instances in all these accounts can communicate privately, Which of the following solutions provides the capability at the CHEAPEST cost? + +- Keyword: RAM + + - Answer: **Create a VPC in an account and share one or more of its subnets with the other accounts usning Resource Access Manager.** + - AWS Resource Access Manager is a service that enables you to easily and securely share AWS resources with any AWS account or within your AWS Organization. + - You can share AWS Transit Gatewayss, Subnets, AWS License Manager configurations. and Amazon Route 53 Resolver rules resources with RAM. + - RAM eliminates the need to create suplicate resources in multiple accounts, reducing the operational overhead of managing those resources in every sigle account you own You can create resources centrally in a multi-account environment, and use RAM to share those resources across account's in three simple steps: create a Resource Share, specify resources, and specify accounts. RAM is available to you at no additional charge. + - The correct solution i s to share the subnet(s) within a VPC using RAM. This will aloow all EC2 instances to be deployed in the same VPC (although from different accounts) and easily communicate with on another. + - **Incorect option : Create a Private Link between all the EC2 instances** + - AWS PrivateLink simplifies the **security of data shared with cloud-based applications** by eliminating the exposure of data to the public Internet. + - AWS PrivateLink provides private connectivity between VPCs, AWS services, and on-premises applications, securely on the Amazon network. Private Link is a distractor in this question. + - Private Link is leveraged to create a private connection between an application that is fronted by an NLB in an account, and an Elastic Network Interface (ENI) in another account, without the need of VPC peering and allowing the connections between the two to remain within the AWS network. + +--- + +- The engineering team at a logistics company has noticed that the Auto Scaling group (ASG) is not terminating an unhealthy Amazon EC2 instance. + As a Solutions Architect, which of the following options would you suggest to troubleshoot the issue? (Select three) + +- Keyword: Auto Scaling + +Answer: +- **The health check grace period for the instance has not expired** + - Amazon EC2 Auto Scaling doesn't terminate an instance that came into service based on EC2 status checks and ELB health checks until the health check grace period expires. + - https://docs.aws.amazon.com/autoscaling/ec2/userguide/healthcheck.html#health-check-grace-period + +- **The instance maybe in Impaired status** + - Amazon EC2 Auto Scaling does not immediately terminate instances with an Impaired status. Instead, Amazon EC2 Auto Scaling waits a few minutes for the instance to recover. Amazon EC2 Auto Scaling might also delay or not terminate instances that fail to report data for status checks. This usually happens when there is insufficient data for the status check metrics in Amazon CloudWatch. + +- **The instance has failed the ELB health check status** + - By default, Amazon EC2 Auto Scaling doesn't use the results of ELB health checks to determine an instance's health status when the group's health check configuration is set to EC2. As a result, Amazon EC2 Auto Scaling doesn't terminate instances that fail ELB health checks. If an instance's status is OutofService on the ELB console, but the instance's status is Healthy on the Amazon EC2 Auto Scaling console, confirm that the health check type is set to ELB. + +--- + +- Your company has a monthly big data workload, running for about 2 hours, which can be efficiently distributed across multiple servers of various sizes, with a variable number of CPUs. The solution for the workload should be able to withstand server failures. + Which is the MOST cost-optimal solution for this workload? + +- Keyword: Spot Fleet + +- Answer: **Run the workload on a Spot Fleet** + - The Spot Fleet selects the Spot Instance pools that meet your needs and launches Spot Instances to meet the target capacity for the fleet. By default, Spot Fleets are set to maintain target capacity by launching replacement instances after Spot Instances in the fleet are terminated. + + - A Spot Instance is an unused EC2 instance that is available for less than the On-Demand price. Spot Instances provide great cost efficiency, **but we need to select an instance type in advance.** In this case, we want to use the most cost-optimal option and leave the selection of the cheapest spot instance to a Spot Fleet request, which can be optimized with the lowestPrice strategy. So this is the correct option. + +--- + +- Amazon EC2 Auto Scaling needs to terminate an instance from Availability Zone (AZ) us-east-1a as it has the most number of instances amongst the AZs being used currently. There are 4 instances in the AZ us-east-1a like so: + - Instance A has the oldest launch template + - Instance B has the oldest launch configuration + - Instance C has the newest launch configuration + - Instance D is closest to the next billing hour. +- Which of the following instances would be terminated per the default termination policy? + +- Keyword: Auto Scaling + +- Answer: Instance B + - Per the default termination policy, the first priority is given to any allocation strategy for On-Demand vs Spot instances. As no such information has been provided for the given use-case, so this criterion can be ignored. + - The next priority is to consider any instance with the oldest launch template unless there is an instance that uses a launch configuration. So this rules out Instance A. + - Next, you need to consider any instance which has the oldest launch configuration. This implies Instance B will be selected for termination and Instance C will also be ruled out as it has the newest launch configuration. + - Instance D, which is closest to the next billing hour, is not selected as this criterion is last in the order of priority. + +--- + +- A retail company wants to rollout and test a **blue-green deployment** for its global application in the next 48 hours. Most of the customers use mobile phones which are **prone to DNS caching**. The company has only two days left for the annual Thanksgiving sale to commence. + As a Solutions Architect, which of the following options would you recommend to test the deployment on as many users as possible in the given time frame? + +- Keyword: Global Accelerator + +- Answer: **Use AWS Global Accelerator to distribute a portion of traffic to a particular deployment** + + - AWS Global Accelerator is a network layer service that directs traffic to optimal endpoints over the AWS global network, this improves the availability and performance of you r internet applications. + + - It provides two static anycast IP addresses that act as a fixed entry porint to your application endpoints in a single or multiple regions, such as your Amazon EC2 instances, in a single or in multiple regions. + + - Global Accelerator uses **endpoint weights to determine the proportion of traffic** that is directed to endpoints in an endpoint group, and traffic dials to control the percentage of traffic that is directed to an endpoint group (an AWS regions where your application is deployed) + + - While relying on the DNS service is a great option for blue/green deployments, it may not fit use-cases that require a fast and controlled transition of the traffic. Some client devices and internet resolvers cache DNS answers for long periods; this DNS feature improves the efficiency of the DNS service as it resuces the DNS traffic across the Internet, and serves as a resiliency technique by preventing authoritative name-server overloads. + + - The downside of this in blue/green deployments is that you don't know how long it will take before all of your users receive updated UP addresses when you update a record, change your routing preference or when thate is an application failure. + + - **With Global Accelerator, you can shift traffic gradually or all at once between the blue and the green environment** and vice-versa without being subject to DNS caching on client devices and internet resolvers, traffic dials an dendpoint weights changes are effective within seconds. + +--- + +- You have a team of developers in your company, and you would like to ensure they can quickly experiment with AWS Managed Policies by attaching them to their accounts, but you would like to prevent them from doing an escalation of privileges, by granting themselves the AdministratorAccess managed policy. How should you proceed? + +- Keyword: IAM + +- Answer: **For each developer, define an IAM permission boundary that will restrict the managed policies they can attach to themselves** + - AWS supports permissions boundaries for IAM entities (users or roles). A permissions boundary is an advanced feature for using a managed policy to set the maximum permissions that an identity-based policy can grant to an IAM entity. An entity's permissions boundary allows it to perform only the actions that are allowed by both its identity-based policies and its permissions boundaries. Here we have to use an IAM permission boundary. They can only be applied to roles or users, not IAM groups. + - https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html + - Attach an IAM policy to your developers, that prevents them from attaching the AdministratorAccess policy - This option is incorrect as the developers can remove this policy from themselves and escalate their privileges. + +--- + +- Keyword: S3 + +**By default, an S3 object is owned by the AWS account that uploaded it. So the S3 bucket owner will not implicitly have access to the objects written by Redshift cluster** + +--- + +- Keyword: User data +- Keyword: EC2 + +- An engineering team wants to examine the feasibility of the `user data` feature of Amazon EC2 for an upcoming project. + - User Data is generally used to perform common automated configuration tasks and even run scripts after the instance starts. When you launch an instance in Amazon EC2, you can pass two types of user data - shell scripts and cloud-init directives. You can also pass this data into the launch wizard as plain text or as a file. + - **By default, scripts entered as user data are executed with root user privileges** - Scripts entered as user data are executed as the root user, hence do not need the sudo command in the script. Any files you create will be owned by root; if you need non-root users to have file access, you should modify the permissions accordingly in the script. + - **By default, user data runs only during the boot cycle when you first launch an instance** - By default, user data scripts and cloud-init directives run only during the boot cycle when you first launch an instance. You can update your configuration to ensure that your user data scripts and cloud-init directives run every time you restart your instance. + - https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/user-data.html + +--- + +- An e-commerce application uses an **Amazon Aurora Multi-AZ deployment** for its database. While analyzing the performance metrics, the engineering team has found that the database **reads are causing high I/O and adding latency to the write requests against the database.** + As an AWS Certified Solutions Architect Associate, what would you recommend to separate the read requests from the write requests? + +- Keyword: Aurora + +- Answer: **Set up a read replica and modify the application to use the appropriate endpoint** + + - An Amazon Aurora DB cluster consists of one or more DB instances and a cluster volume that manages the data for those DB instances. An Aurora cluster volume is a virtual database storage volume that spans multiple Availability Zones, with each Availability Zone having a copy of the DB cluster data. Two types of DB instances make up an Aurora DB cluster: + + - **Primary DB instance** – Supports read and write operations, and performs all of the data modifications to the cluster volume. Each Aurora DB cluster has one primary DB instance. + + - **Aurora Replica** – Connects to the same storage volume as the primary DB instance and supports only read operations. Each Aurora DB cluster can have up to 15 Aurora Replicas in addition to the primary DB instance. Aurora automatically fails over to an Aurora Replica in case the primary DB instance becomes unavailable. You can specify the failover priority for Aurora Replicas. Aurora Replicas can also offload read workloads from the primary DB instance. + + - You use the reader endpoint for read-only connections for your Aurora cluster. This endpoint uses a load-balancing mechanism to help your cluster handle a query-intensive workload. The reader endpoint is the endpoint that you supply to applications that do reporting or other read-only operations on the cluster. The reader endpoint load-balances connections to available Aurora Replicas in an Aurora DB cluster. + + - Provision another Amazon Aurora database and link it to the primary database as a read replica - **You cannot provision another Aurora database and then link it as a read-replica for the primary database**. This option is ruled out. + - Activate read-through caching on the Amazon Aurora database - **Aurora does not have built-in support for read-through caching**, so this option just serves as a distractor. To implement caching, you will need to integrate something like ElastiCache and that would need code changes for the application. + +--- + +- A media company has created an AWS Direct Connect connection for migrating its flagship application to the AWS Cloud. The on-premises application writes hundreds of video files into a mounted NFS file system daily. Post-migration, the company will host the application on an Amazon EC2 instance with a mounted EFS file system. Before the migration cutover, the company must build a process that will replicate the newly created on-premises video files to the EFS file system. + Which of the following represents the MOST operationally efficient way to meet this requirement? + +- Answer: Configure an AWS DataSync agent on the on-premises server that has access to the NFS file system. Transfer data over the Direct Connect connection to an AWS PrivateLink interface VPC endpoint for Amazon EFS by using a private VIF. Set up a DataSync scheduled task to send the video files to the EFS file system every 24 hours + +- **You cannot use the S3 VPC endpoint to transfer data over the Direct Connect connection from the on-premises systems to S3.** + +--- + +- A company has historically operated only in the `us-east-1` region and stores encrypted data in S3 using SSE-KMS. +- As part of enhancing its security posture as well as improving the backup and recovery architecture, the company wants to store the encrypted data in S3 that is replicated into the `us-west-1` AWS region. The security policies mandate that the data must be encrypted and decrypted using the same key in both AWS regions. + Which of the following represents the best solution to address these requirements? + +- Keyword: KMS + +- Answer: Create a new S3 bucket in the `us-east-1` region with replication enabled from this new bucket into another bucket in `us-west-1` region. Enable SSE-KMS encryption on the new bucket in `us-east-1` region by using an **AWS KMS multi-region key.** Copy the existing data from the current S3 bucket in `us-east-1` region into this new S3 bucket in `us-east-1` region + +- https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html + +- **You cannot share an AWS KMS key to another region.** + +--- + +- A financial services company wants a **single log processing model** for all the log files (consisting of system logs, application logs, database logs, etc) that can be processed in a serverless fashion and then durably stored for downstream analytics. The company wants to use an AWS managed service that automatically scales to match the throughput of the log data and requires no ongoing administration. + As a solutions architect, which of the following AWS services would you recommend solving this problem? + +- Answer: Kinesis Data Firehose + +--- + +- A financial services company has deployed its flagship application on EC2 instances. Since the application handles sensitive customer data, the security team at the company wants to ensure that any third-party SSL/TLS certificates configured on EC2 instances via the AWS Certificate Manager (ACM) are renewed before their expiry date. The company has hired you as an AWS Certified Solutions Architect Associate to build a solution that notifies the security team 30 days before the certificate expiration. The solution should require the least amount of scripting and maintenance effort. + +- Answer: Leverage **AWS Config managed rule** to check if any third-party SSL/TLS certificates imported into ACM are marked for expiration within 30 days. Configure the rule to trigger an SNS notification to the security team if any certificate expires within 30 days + + - **AWS Certificate Manager** is a service that lets you easily provision, manage, and deploy public and private Secure Sockets Layer/Transport Layer Security (SSL/TLS) certificates for use with AWS services and your internal connected resources. SSL/TLS certificates are used to secure network communications and establish the identity of websites over the Internet as well as resources on private networks. + + - **AWS Config provides a detailed view of the configuration of AWS resources in your AWS account.** This includes how the resources are related to one another and how they were configured in the past so that you can see how the configurations and relationships change over time. + + - https://docs.aws.amazon.com/config/latest/developerguide/how-does-config-work.html + + - AWS Config provides **AWS-managed rules**, which are predefined, customizable rules that AWS Config uses to evaluate whether your AWS resources comply with common best practices. You can leverage an AWS Config managed rule to check if any ACM certificates in your account are marked for expiration within the specified number of days. Certificates provided by ACM are automatically renewed. ACM does not automatically renew the certificates that you import. The rule is NON_COMPLIANT if your certificates are about to expire. + + - You can configure AWS Config to stream configuration changes and notifications to an Amazon SNS topic. For example, when a resource is updated, you can get a notification sent to your email, so that you can view the changes. You can also be notified when AWS Config evaluates your custom or managed rules against your resources. + + - It is certainly possible to use the days to expiry CloudWatch metric to build a CloudWatch alarm to monitor the imported ACM certificates. The alarm will, in turn, trigger a notification to the security team. But this option needs more configuration effort than directly using the AWS Config managed rule that is available off-the-shelf. + +--- + +- You would like to migrate an AWS account from an AWS Organization A to an AWS Organization B. What are the steps do to it? + +- Answer: + - Remove the member account from the old organization. + - Send an invite to the member account from the new Organization. + - Accept the invite to the new organization from the member account + +--- + +- The engineering team at an e-commerce company is working on cost optimizations for EC2 instances. The team wants to manage the workload using a mix of on-demand and spot instances across multiple instance types. They would like to create an Auto Scaling group with a mix of these instances. + Which of the following options would allow the engineering team to provision the instances for this use-case? + +- Answer: **You can only use a launch template to provision capacity across multiple instance types using both On-Demand Instances and Spot Instances to achieve the desired scale, performance, and cost.** + - A launch template is similar to a launch configuration, in that it **specifies instance configuration information such as the ID of the Amazon Machine Image (AMI), the instance type, a key pair, security groups, and the other parameters that you use to launch EC2 instances.** Also, defining a launch template instead of a launch configuration allows you to have multiple versions of a template. + - With launch templates, you can provision capacity across multiple instance types using both On-Demand Instances and Spot Instances to achieve the desired scale, performance, and cost. Hence this is the correct option. + - A launch configuration is an instance configuration template that an Auto Scaling group uses to launch EC2 instances. When you create a launch configuration, you specify information for the instances such as the ID of the AMI, the instance type, a key pair, one or more security groups, and a block device mapping. + - You cannot use a launch configuration to provision capacity across multiple instance types using both On-Demand Instances and Spot Instances. Therefore that options are incorrect. + +--- + +- What is true about RDS Read Replicas encryption? + +- Keyword: RDS + +- Answer: If the master database is encrypted, the read replicas are encrypted + - Amazon RDS Read Replicas provide enhanced performance and durability for RDS database (DB) instances. They make it easy to elastically scale out beyond the capacity constraints of a single DB instance for read-heavy database workloads. For the MySQL, MariaDB, PostgreSQL, Oracle, and SQL Server database engines, Amazon RDS creates a second DB instance using a snapshot of the source DB instance. It then uses the engines' native asynchronous replication to update the read replica whenever there is a change to the source DB instance. read replicas can be within an Availability Zone, Cross-AZ, or Cross-Region. + - **On a database instance running with Amazon RDS encryption, data stored at rest in the underlying storage is encrypted, as are its automated backups, read replicas, and snapshots**. Therefore, this option is correct. + - ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/d7c2173b-5fcc-479a-bdd1-0ae026e9dda8) + - https://aws.amazon.com/rds/features/read-replicas/ + +--- + +- A social media application is hosted on an EC2 server fleet running behind an Application Load Balancer. The application traffic is fronted by a CloudFront distribution. The engineering team wants to decouple the user authentication process for the application, so that the application servers can just focus on the business logic. + As a Solutions Architect, which of the following solutions would you recommend to the development team so that it requires minimal development effort? + +- Keyword: Cognito + +- Answer: Use Cognito Authentication via Cognito User Pools for your Application Load Balancer + - Application Load Balancer can be used to securely authenticate users for accessing your applications. This enables you to offload the work of authenticating users to your load balancer so that your applications can focus on their business logic. You can use Cognito User Pools to authenticate users through well-known social IdPs, such as Amazon, Facebook, or Google, through the user pools supported by Amazon Cognito or through corporate identities, using SAML, LDAP, or Microsoft AD, through the user pools supported by Amazon Cognito. You configure user authentication by creating an authenticate action for one or more listener rules. + - **There is no such thing as using Cognito Authentication via Cognito Identity Pools for managing user authentication for the application. ** + +--- + +- You would like to store a database password in a secure place, and enable automatic rotation of that password every 90 days. What do you recommend? + +- Keyword: Secrets Manager + +- Answer: "Secrets Manager" + - AWS Secrets Manager helps you protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle. Users and applications retrieve secrets with a call to Secrets Manager APIs, eliminating the need to hardcode sensitive information in plain text. Secrets Manager offers secret rotation with built-in integration for Amazon RDS, Amazon Redshift, and Amazon DocumentDB. The correct answer here is Secrets Manager + +--- + +- A big data consulting firm needs to set up a data lake on Amazon S3 for a Health-Care client. The data lake is split in raw and refined zones. For compliance reasons, the source data needs to be kept for a minimum of 5 years. The source data arrives in the raw zone and is then processed via an AWS Glue based ETL job into the refined zone. The business analysts run ad-hoc queries only on the data in the refined zone using AWS Athena. The team is concerned about the cost of data storage in both the raw and refined zones as the data is increasing at a rate of 1TB daily in each zone. As a solutions architect, which of the following would you recommend as the MOST cost-optimal solution? (Select two) + +Answer: +- Setup a lifecycle policy to transition the raw zone data into Glacier Deep Archive after 1 day of object creation + - You can manage your objects so that they are stored cost-effectively throughout their lifecycle by configuring their Amazon S3 Lifecycle. An S3 Lifecycle configuration is a set of rules that define actions that Amazon S3 applies to a group of objects. For example, you might choose to transition objects to the S3 Standard-IA storage class 30 days after you created them, or archive objects to the S3 Glacier storage class one year after creating them. + - For the given use-case, the raw zone consists of the source data, so it cannot be deleted due to compliance reasons. Therefore, you should use a lifecycle policy to transition the raw zone data into Glacier Deep Archive after 1 day of object creation. + - https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html + +- Use Glue ETL job to write the transformed data in the refined zone using a compressed file format + - AWS Glue is a fully managed extract, transform, and load (ETL) service that makes it easy for customers to prepare and load their data for analytics. You cannot transition the refined zone data into Glacier Deep Archive because it is used by the business analysts for ad-hoc querying. Therefore, the best optimization is to have the refined zone data stored in a compressed format via the Glue job. The compressed data would reduce the storage cost incurred on the data in the refined zone. + +--- + +- A big-data consulting firm is working on a client engagement where the ETL workloads are currently handled via a Hadoop cluster deployed in the on-premises data center. The client wants to migrate their ETL workloads to AWS Cloud. The AWS Cloud solution needs to be highly available with about 50 EC2 instances per Availability Zone. + As a solutions architect, which of the following EC2 placement groups would you recommend handling the distributed ETL workload? + +- Keyword: tenancy + +- Answer: Partition placement group + - You can use placement groups to influence the placement of a group of interdependent instances to meet the needs of your workload. Depending on the type of workload, you can create a placement group using one of the following placement strategies: + - Partition – spreads your instances across logical partitions such that groups of instances in one partition do not share the underlying hardware with groups of instances in different partitions. This strategy is typically **used by large distributed and replicated workloads, such as Hadoop, Cassandra, and Kafka. Therefore, this is the correct option for the given use-case.** + - Spread – strictly places a small group of instances across distinct underlying hardware to reduce correlated failures. This is not suited for distributed and replicated workloads such as Hadoop. + - Cluster – packs instances close together inside an Availability Zone. This strategy enables workloads to achieve the low-latency network performance necessary for tightly-coupled node-to-node communication that is typical of HPC applications. This is not suited for distributed and replicated workloads such as Hadoop. + +--- + +- An IT company is looking to move its on-premises infrastructure to AWS Cloud. The company has a portfolio of applications with a few of them using server bound licenses that are valid for the next year. To utilize the licenses, the CTO wants to use dedicated hosts for a one year term and then migrate the given instances to default tenancy thereafter. + As a solutions architect, which of the following options would you identify as CORRECT for changing the tenancy of an instance after you have launched it? (Select two) + +- Keyword: tenancy + +Answer: +- You can change the tenancy of an instance from dedicated to host +- You can change the tenancy of an instance from host to dedicated +**You can only change the tenancy of an instance from dedicated to host, or from host to dedicated after you've launched it.** + +--- + +- The development team at a retail company wants to optimize the cost of EC2 instances. The team wants to move certain nightly batch jobs to spot instances. The team has hired you as a solutions architect to provide the initial guidance. + +- Keyword: spot instance + +Answer: +- If a spot request is persistent, then it is opened again after your Spot Instance is interrupted +- Spot blocks are designed not to be interrupted +- When you cancel an active spot request, it does not terminate the associated instance + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/1a852d0d-2647-4609-a0df-1906a804b813) + +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-requests.html + +- Spot Instances with a defined duration (also known as Spot blocks) are designed not to be interrupted and will run continuously for the duration you select. You can use a duration of 1, 2, 3, 4, 5, or 6 hours. In rare situations, Spot blocks may be interrupted due to Amazon EC2 capacity needs. Therefore, the option - "Spot blocks are designed not to be interrupted" - is correct. + +- If your Spot Instance request is active and has an associated running Spot Instance, or your Spot Instance request is disabled and has an associated stopped Spot Instance, canceling the request does not terminate the instance; you must terminate the running Spot Instance manually. Moreover, to cancel a persistent Spot request and terminate its Spot Instances, you must cancel the Spot request first and then terminate the Spot Instances. Therefore, the option - "When you cancel an active spot request, it does not terminate the associated instance" - is correct. + +--- + +- A gaming company uses Application Load Balancers (ALBs) in front of Amazon EC2 instances for different services and microservices. The architecture has now become complex with too many ALBs in multiple AWS Regions. Security updates, firewall configurations, and traffic routing logic have become complex with too many IP addresses and configurations. + The company is looking at an easy and effective way to bring down the number of IP addresses allowed by the firewall and easily manage the entire network infrastructure. Which of these options represents an appropriate solution for this requirement? + +- Keyword: Global accelerator + +- Answer: Launch AWS Global Accelerator and create endpoints for all the Regions. Register the ALBs of each Region to the corresponding endpoints + - AWS Global Accelerator is a networking service that sends your user’s traffic through Amazon Web Service’s global network infrastructure, improving your internet user performance by up to 60%. When the internet is congested, Global Accelerator’s automatic routing optimizations will help keep your packet loss, jitter, and latency consistently low. + - With Global Accelerator, you are provided two global static customer-facing IPs to simplify traffic management. On the back end, add or remove your AWS application origins, such as Network Load Balancers, Application Load Balancers, Elastic IPs, and EC2 Instances, without making user-facing changes. To mitigate endpoint failure, Global Accelerator automatically re-routes your traffic to your nearest healthy available endpoint. + +--- + +CloudFormation templates cannot be used to deploy the same template across AWS accounts and regions. + +--- + +"Use AWS Config to review resource configurations to meet compliance guidelines and maintain a history of resource configuration changes" + +- AWS Config is a service that enables you to assess, audit, and evaluate the configurations of your AWS resources. With Config, you can review changes in configurations and relationships between AWS resources, dive into detailed resource configuration histories, and determine your overall compliance against the configurations specified in your internal guidelines. You can use Config to answer questions such as - “What did my AWS resource look like at xyz point in time?”. + +--- + +- An e-commerce company runs its web application on EC2 instances in an Auto Scaling group and it's configured to handle consumer orders in an SQS queue for downstream processing. The DevOps team has observed that the performance of the application goes down in case of a sudden spike in orders received. + As a solutions architect, which of the following solutions would you recommend to address this use-case? + +- Keyword: Auto Scaling + +- Answer: **Use a target tracking scaling policy based on a custom Amazon SQS queue metric** + - If you use a target tracking scaling policy based on a custom Amazon SQS queue metric, dynamic scaling can adjust to the demand curve of your application more effectively. You may use an existing CloudWatch Amazon SQS metric like ApproximateNumberOfMessagesVisible for target tracking but you could still face an issue so that the number of messages in the queue might not change proportionally to the size of the Auto Scaling group that processes messages from the queue. + - The main issue with simple scaling is that after a scaling activity is started, **the policy must wait for the scaling activity or health check replacement to complete and the cooldown period to expire before responding to additional alarms.** This implies that the application would not be able to react quickly to sudden spikes in orders. + +--- + +**You cannot use EventBridge events to directly trigger the recovery of the EC2 instance.** + +--- + +- As part of the on-premises data center migration to AWS Cloud, a company is looking at using multiple AWS Snow Family devices to move their on-premises data. + +- Which Snow Family service offers the feature of storage clustering? + +- Keyword: Snow Family + +- Answer: Among the AWS Snow Family services, **AWS Snowball Edge Storage Optimized device offers the feature of storage clustering.** + - The AWS Snowball Edge Storage Optimized device is designed for data migration, edge computing, and storage purposes. It provides a large amount of on-premises storage capacity and supports clustering, which allows you to combine multiple Snowball Edge devices into a cluster for increased storage capacity and data processing capabilities. + - By creating a storage cluster with multiple Snowball Edge Storage Optimized devices, you can aggregate their storage capacities and manage them as a single logical storage unit. This clustering feature enables you to work with larger datasets and perform distributed computing tasks using the combined resources of the clustered devices. + - Note that AWS Snowcone and AWS Snowmobile do not offer storage clustering capabilities. AWS Snowcone is a smaller and more portable device, while AWS Snowmobile is a massive data transfer solution for extremely large datasets. + +--- + +- A video conferencing application is hosted on a fleet of EC2 instances which are part of an Auto Scaling group (ASG). The ASG uses a Launch Configuration (LC1) with "dedicated" instance placement tenancy but the VPC (V1) used by the Launch Configuration LC1 has the instance tenancy set to default. Later the DevOps team creates a new Launch Configuration (LC2) with "default" instance placement tenancy but the VPC (V2) used by the Launch Configuration LC2 has the instance tenancy set to dedicated. + Which of the following is correct regarding the instances launched via Launch Configuration LC1 and Launch Configuration LC2? + +- Keyword: Auto Scaling + +- Answer: The instances launched by both Launch Configuration LC1 and Launch Configuration LC2 will have dedicated instance tenancy + - When you create a launch configuration, the default value for the instance placement tenancy is **null** and the instance tenancy is **controlled by the tenancy attribute of the VPC.** + - If you set the Launch Configuration Tenancy to default and the VPC Tenancy is set to dedicated, then the instances have dedicated tenancy. If you set the Launch Configuration Tenancy to dedicated and the VPC Tenancy is set to default, then again the instances have dedicated tenancy. + +--- + +- The engineering team at a social media company wants to use Amazon CloudWatch alarms to automatically recover EC2 instances if they become impaired. The team has hired you as a solutions architect to provide subject matter expertise. + As a solutions architect, which of the following statements would you identify as CORRECT regarding this automatic recovery process? (Select two) + +- Keyword: Cloud watch + +- Answer: **A recovered instance is identical to the original instance, including the instance ID, private IP addresses, Elastic IP addresses, and all instance metadata** + +- **If your instance has a public IPv4 address, it retains the public IPv4 address after recovery** + +Terminated EC2 instances can be recovered if they are configured at the launch of instance - This is incorrect as **terminated instances cannot be recovered.** + +--- + +- A startup has created a new web application for users to complete a risk assessment survey for COVID-19 symptoms via a self-administered questionnaire. The startup has purchased the domain covid19survey.com using Route 53. The web development team would like to create a Route 53 record so that all traffic for covid19survey.com is routed to `www.covid19survey.com`. + As a solutions architect, which of the following is the MOST cost-effective solution that you would recommend to the web development team? + +- Keyword: Route 53 + +- Answer: **Create an alias record for covid19survey.com that routes traffic to** `www.covid19survey.com` + - Alias records provide a Route 53–specific extension to DNS functionality. Alias records let you route traffic to selected AWS resources, such as CloudFront distributions and Amazon S3 buckets. + - You can create an alias record at the top node of a DNS namespace, also known as the zone apex, however, **you cannot create a CNAME record for the top node of the DNS namespace.** So, if you register the DNS name covid19survey.com, the zone apex is covid19survey.com. You can't create a CNAME record for covid19survey.com, but you can create an alias record for covid19survey.com that routes traffic to `www.covid19survey.com`. + - Exam Alert: + - You should also note that **Route 53 doesn't charge for alias queries to AWS resources but Route 53 does charge for CNAME queries**. Additionally, an alias record can only redirect queries to selected AWS resources such as S3 buckets, CloudFront distributions, and another record in the same Route 53 hosted zone; however a CNAME record can redirect DNS queries to any DNS record. So, you can create a CNAME record that redirects queries from app.covid19survey.com to app.covid19survey.net. + +--- + +- **Internet gateways cannot be provisioned in private subnets of a VPC.** + +--- + +- **Use ElastiCache to improve latency and throughput for read-heavy application workloads** +- **Use ElastiCache to improve the performance of compute-intensive workloads** + +--- + +- A big data analytics company is working on a real-time vehicle tracking solution. The data processing workflow involves both I/O intensive and throughput intensive database workloads. The development team needs to store this real-time data in a NoSQL database hosted on an EC2 instance and needs to support up to 25,000 IOPS per volume. + As a solutions architect, which of the following EBS volume types would you recommend for this use-case? + +- Keyword: EBS + +- Answer: + - Provisioned IOPS SSD (io1) + - Provisioned IOPS SSD (io1) is backed by solid-state drives (SSDs) and is a high-performance EBS storage option designed for critical, I/O intensive database and application workloads, as well as throughput-intensive database workloads. io1 is designed to deliver a consistent baseline performance of up to **50 IOPS/GB to a maximum of 64,000 IOPS** and **provide up to 1,000 MB/s of throughput per volume.** Therefore, the io1 volume type would be able to meet the requirement of 25,000 IOPS per volume for the given use-case. + +- Incorrect options: + - General Purpose SSD (gp2) + - gp2 is backed by solid-state drives (SSDs) and is suitable for a broad range of transactional workloads, including dev/test environments, low-latency interactive applications, and boot volumes. It supports **max IOPS/Volume of 16,000.** + - Cold HDD (sc1) + - sc1 is backed by hard disk drives (HDDs). It is ideal for less frequently accessed workloads with large, cold datasets. It supports **max IOPS/Volume of 250.** + - Throughput Optimized HDD (st1) + - st1 is backed by hard disk drives (HDDs) and is ideal for frequently accessed, throughput-intensive workloads with large datasets and large I/O sizes, such as MapReduce, Kafka, log processing, data warehouse, and ETL workloads. It supports **max IOPS/Volume of 500.** + +- https://aws.amazon.com/ebs/volume-types/ + +--- + +- A retail company uses AWS Cloud to manage its IT infrastructure. The company has set up "AWS Organizations" to manage several departments running their AWS accounts and using resources such as EC2 instances and RDS databases. The company wants to provide shared and centrally-managed VPCs to all departments using applications that need a high degree of interconnectivity. + As a solutions architect, which of the following options would you choose to facilitate this use-case? + +- Answer: Use VPC sharing to share one or more subnets with other AWS accounts belonging to the same parent organization from AWS Organizations + - **VPC sharing (part of Resource Access Manager)** allows multiple AWS accounts to create their application resources such as EC2 instances, RDS databases, Redshift clusters, and Lambda functions, into shared and centrally-managed Amazon Virtual Private Clouds (VPCs). + - To set this up, **the account that owns the VPC (owner) shares one or more subnets with other accounts (participants) that belong to the same organization from AWS Organizations.** After a subnet is shared, the participants can view, create, modify, and delete their application resources in the subnets shared with them. Participants cannot view, modify, or delete resources that belong to other participants or the VPC owner. + - You can share Amazon VPCs to leverage the implicit routing within a VPC for applications that require a high degree of interconnectivity and are within the same trust boundaries. This reduces the number of VPCs that you create and manage while using separate accounts for billing and access control. + +- **The owner account cannot share the VPC itself.** + +--- + +- An AWS Organization is using Service Control Policies (SCP) for central control over the maximum available permissions for all accounts in their organization. This allows the organization to ensure that all accounts stay within the organization’s access control guidelines. + Which of the given scenarios are correct regarding the permissions described below? (Select three) + +- Keyword: SCP + +Answer: +- **If a user or role has an IAM permission policy that grants access to an action that is either not allowed or explicitly denied by the applicable SCPs, the user or role can't perform that action** +- **SCPs affect all users and roles in attached accounts, including the root user** +- **SCPs do not affect service-linked role** + +https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scp.html + +--- + +- The DevOps team at an IT company has recently migrated to AWS and they are configuring security groups for their two-tier application with public web servers and private database servers. The team wants to understand the allowed configuration options for an inbound rule for a security group. + As a solutions architect, which of the following would you identify as an **INVALID** option for setting up such a configuration? + +- Keyword: Security Group + +- Answer: You can use an Internet Gateway ID as the custom source for the inbound rule + - IGW ID is can not be a set as security groups inbound rules. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/cb7a5608-fd31-45da-b8a7-e71dfbf4f744) +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html + +--- + +- A healthcare company has deployed its web application on Amazon ECS container instances running behind an Application Load Balancer (ALB). The website slows down when the traffic spikes and the website availability is also reduced. The development team has configured CloudWatch alarms to receive notifications whenever there is an availability constraint so the team can scale out resources. The company wants an automated solution to respond to such events. + Which of the following addresses the given use case? + +- Answer: **Configure AWS Auto Scaling to scale out the ECS cluster when the ECS service's CPU utilization rises above a threshold** + + - You use the Amazon ECS first-run wizard to create a cluster and a service that runs behind an Elastic Load Balancing load balancer. Then you can configure a **target tracking scaling policy that scales your service automatically based on the current application load as measured by the service's CPU utilization** (from the ECS, ClusterName, and ServiceName category in CloudWatch). + + - When the average CPU utilization of your service rises above 75% (meaning that more than 75% of the CPU that is reserved for the service is being used), a scale-out alarm triggers Service Auto Scaling to add another task to your service to help out with the increased load. + + - Conversely, when the average CPU utilization of your service drops below the target utilization for a sustained period, a scale-in alarm triggers a decrease in the service's desired count to free up those cluster resources for other tasks and services. + + + + + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\230\244\353\213\265\353\205\270\355\212\270\342\200\2054~6.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\230\244\353\213\265\353\205\270\355\212\270\342\200\2054~6.md" new file mode 100644 index 00000000..3fd5f042 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\230\244\353\213\265\353\205\270\355\212\270\342\200\2054~6.md" @@ -0,0 +1,628 @@ +--- +title: 'SAA 오답노트 4~6' +lastUpdated: '2023-11-27' +--- +- A CRM web application was written as a monolith in PHP and is facing scaling issues because of performance bottlenecks. The CTO wants to re-engineer towards microservices architecture and expose their application from the same load balancer, linked to different target groups with different URLs: checkout.mycorp.com, www.mycorp.com, yourcorp.com/profile and yourcorp.com/search. The CTO would like to expose all these URLs as HTTPS endpoints for security purposes. + As a solutions architect, which of the following would you recommend as a solution that requires MINIMAL configuration effort? + +- Keyword: SNI + +- Answer: **Use SSL certificates with SNI** + + - You can host multiple TLS secured applications, each with its own TLS certificate, behind a single load balancer. **To use SNI, all you need to do is bind multiple certificates to the same secure listener on your load balancer.** ALB will automatically choose the optimal TLS certificate for each client. + + - ALB’s smart certificate selection goes beyond SNI. In addition to containing a list of valid domain names, certificates also describe the type of key exchange and cryptography that the server supports, as well as the signature algorithm (SHA2, SHA1, MD5) used to sign the certificate. + + - With SNI support AWS makes it easy to use more than one certificate with the same ALB. The most common reason you might want to use multiple certificates is to handle different domains with the same load balancer. + + - It’s always been possible to use wildcard and subject-alternate-name (SAN) certificates with ALB, but these come with limitations. Wildcard certificates only work for related subdomains that match a simple pattern and while SAN certificates can support many different domains, the same certificate authority has to authenticate each one. That means you have to reauthenticate and reprovision your certificate every time you add a new domain. + + - https://aws.amazon.com/blogs/aws/new-application-load-balancer-sni/ + - https://docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/network/create-tls-listener.html + +- Change the ELB SSL Security Policy - ELB SSL Security Policy will not provide multiple secure endpoints for different URLs such as `checkout.mycorp.com` or `www.mycorp.com`, therefore it is incorrect for the given use-case. + +--- + +- A company wants to adopt a hybrid cloud infrastructure where it uses some AWS services such as S3 alongside its on-premises data center. The company wants a dedicated private connection between the on-premise data center and AWS. **In case of failures though, the company needs to guarantee uptime and is willing to use the public internet for an encrypted connection.** + What do you recommend? + +Answer: +- **Use Direct Connect as a primary connection** + + - AWS Direct Connect lets you establish a **dedicated network connection between your network and one of the AWS Direct Connect locations.** Using industry-standard 802.1q VLANs, this dedicated connection can be partitioned into multiple virtual interfaces. + + - AWS Direct Connect does not involve the Internet; instead, it uses dedicated, private network connections between your intranet and Amazon VPC. + + - (Direct Connect is a highly secure, physical connection. It is also a costly solution and hence does not make much sense to set up the connection and keep it only as a backup.) + + - AWS Direct Connect does not involve the Internet; instead, it uses dedicated, private network connections between your intranet and Amazon VPC. **Direct Connect involves significant monetary investment and takes at least a month to set up.** + +- **Use Site to Site VPN as a backup connection** + + - AWS Site-to-Site VPN enables you to securely connect your on-premises network or branch office site to your Amazon Virtual Private Cloud (Amazon VPC). + + - You can securely extend your data center or branch office network to the cloud with an AWS Site-to-Site VPN connection. A VPC VPN Connection utilizes IPSec to establish encrypted network connectivity between your intranet and Amazon VPC over the Internet. VPN Connections can be configured in minutes and are a good solution if you have an immediate need, have low to modest bandwidth requirements, and can tolerate the inherent variability in Internet-based connectivity. + + - Direct Connect as a primary connection guarantees great performance and security (as the connection is private). Using Direct Connect as a backup solution would work but probably carries a risk it would fail as well. **As we don't mind going over the public internet (which is reliable, but less secure as connections are going over the public route), we should use a Site to Site VPN which offers an encrypted connection to handle failover scenarios.** + +--- + +- A financial services firm has traditionally operated with an on-premise data center and would like to create a disaster recovery strategy leveraging the AWS Cloud. + As a Solutions Architect, you would like to ensure that a scaled-down version of a fully functional environment is always running in the AWS cloud, and in case of a disaster, the recovery time is kept to a minimum. Which disaster recovery strategy is that? + +- Answer: Warm Standby + - The term warm standby is used to describe a DR scenario in which **a scaled-down version of a fully functional environment is always running in the cloud.** A warm standby solution extends the pilot light elements and preparation. It further decreases the recovery time because some services are always running. By identifying your business-critical systems, you can fully duplicate these systems on AWS and have them always on. + +Incorrect options: + +- **Backup and Restore** + - In most traditional environments, data is backed up to tape and sent off-site regularly. If you use this method, it can take a long time to restore your system in the event of a disruption or disaster. Amazon S3 is an ideal destination for backup data that might be needed quickly to perform a restore. Transferring data to and from Amazon S3 is typically done through the network, and is therefore accessible from any location. Many commercial and open-source backup solutions integrate with Amazon S3. + +- **Pilot Light** + - The term pilot light is often used to describe a DR scenario in **which a minimal version of an environment is always running in the cloud.** The idea of the pilot light is an analogy that comes from the gas heater. In a gas heater, a small flame that’s always on can quickly ignite the entire furnace to heat up a house. This scenario is similar to a backup-and-restore scenario. For example, with AWS you can maintain a pilot light by configuring and running the most critical core elements of your system in AWS. When the time comes for recovery, you can rapidly provision a full-scale production environment around the critical core. + +- **Multi Site** + - A multi-site solution runs in AWS as well as on your existing on-site infrastructure, in an active-active configuration. The data replication method that you employ will be determined by the recovery point that you choose. + +--- + +- A CRM company has a SaaS (Software as a Service) application that feeds updates to other in-house and third-party applications. The SaaS application and the in-house applications are being migrated to use AWS services for this inter-application communication. + As a Solutions Architect, which of the following would you suggest to asynchronously decouple the architecture? + +- Answer: Use Amazon EventBridge to decouple the system architecture + + - Both Amazon EventBridge and Amazon SNS can be used to develop event-driven applications, but for this use case, EventBridge is the right fit. + + - **Amazon EventBridge is recommended when you want to build an application that reacts to events from SaaS applications and/or AWS services.** Amazon EventBridge is the only event-based service that integrates directly with third-party SaaS partners. + + - Amazon EventBridge also automatically ingests events from over 90 AWS services without requiring developers to create any resources in their account. Further, Amazon EventBridge uses a defined JSON-based structure for events and allows you to create rules that are applied across the entire event body to select events to forward to a target. + + - Amazon EventBridge currently supports over 15 AWS services as targets, including AWS Lambda, Amazon SQS, Amazon SNS, and Amazon Kinesis Streams and Firehose, among others. At launch, Amazon EventBridge is has limited throughput (see Service Limits) which can be increased upon request, and typical latency of around half a second. + +--- + +- You have developed a new REST API leveraging the API Gateway, AWS Lambda and Aurora database services. Most of the workload on the website is read-heavy. The data rarely changes and it is acceptable to serve users outdated data for about 24 hours. Recently, the website has been experiencing high load and the costs incurred on the Aurora database have been very high. + How can you easily **reduce the costs while improving performance, with minimal changes?** + +- Answer: **Enable API Gateway Caching** + - Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. APIs act as the "front door" for applications to access data, business logic, or functionality from your backend services. Using API Gateway, you can create RESTful APIs and WebSocket APIs that enable real-time two-way communication applications. API Gateway supports containerized and serverless workloads, as well as web applications. + - You can enable API caching in Amazon API Gateway to cache your endpoint's responses. With caching, you can reduce the number of calls made to your endpoint and also improve the latency of requests to your API. When you enable caching for a stage, API Gateway caches responses from your endpoint for a specified time-to-live (TTL) period, in seconds. API Gateway then responds to the request by looking up the endpoint response from the cache instead of requesting your endpoint. The default TTL value for API caching is 300 seconds. The maximum TTL value is 3600 seconds. TTL=0 means caching is disabled. Using API Gateway Caching feature is the answer for the use case, as we can accept stale data for about 24 hours. + +Incorrect options: +- Add Aurora Read Replicas + - Adding Aurora Read Replicas would greatly increase the cost, therefore this option is ruled out. + +--- + +- A Big Data analytics company writes data and log files in Amazon S3 buckets. The company now wants to stream the existing data files as well as any ongoing file updates from Amazon S3 to Amazon Kinesis Data Streams. + As a Solutions Architect, which of the following would you suggest as the fastest possible way of building a solution for this requirement? + +- Answer: **Leverage AWS Database Migration Service (AWS DMS) as a bridge between Amazon S3 and Amazon Kinesis Data Streams** + + - You can achieve this by using AWS Database Migration Service (AWS DMS). AWS DMS enables you to seamlessly migrate data from supported sources to relational databases, data warehouses, streaming platforms, and other data stores in AWS cloud. + + - The given requirement needs the functionality to be implemented in the least possible time. You can use AWS DMS for such data-processing requirements. AWS DMS lets you expand the existing application to stream data from Amazon S3 into Amazon Kinesis Data Streams for real-time analytics without writing and maintaining new code. + + - AWS DMS supports specifying Amazon S3 as the source and streaming services like Kinesis and Amazon Managed Streaming of Kafka (Amazon MSK) as the target. AWS DMS allows migration of full and change data capture (CDC) files to these services. AWS DMS performs this task out of box without any complex configuration or code development. You can also configure an AWS DMS replication instance to scale up or down depending on the workload. + + - AWS DMS supports Amazon S3 as the source and Kinesis as the target, so data stored in an S3 bucket is streamed to Kinesis. Several consumers, such as AWS Lambda, Amazon Kinesis Data Firehose, Amazon Kinesis Data Analytics, and the Kinesis Consumer Library (KCL), can consume the data concurrently to perform real-time analytics on the dataset. Each AWS service in this architecture can scale independently as needed. + +Incorrect options: +- Configure EventBridge events for the bucket actions on Amazon S3. An AWS Lambda function can then be triggered from the EventBridge event that will send the necessary data to Amazon Kinesis Data Streams + - You will need to enable a Cloudtrail trail to use object-level actions as a trigger for EventBridge events. Also, using Lambda functions would require significant custom development to write the data into Kinesis Data Streams, so this option is not the right fit. + +--- + +- As a Solutions Architect, you are tasked to design a distributed application that will run on various EC2 instances. This application needs to have the highest performance local disk to cache data. Also, data is copied through an EC2 to EC2 replication mechanism. It is acceptable if the instance loses its data **when stopped or terminated**. + Which storage solution do you recommend? + +- Answer: Instance Store + + - An instance store provides temporary block-level storage for your instance. This storage is located on disks that are physically attached to the host computer. Instance store is ideal for the temporary storage of information that changes frequently, such as buffers, caches, scratch data, and other temporary content, or for data that is replicated across a fleet of instances, such as a load-balanced pool of web servers. + + - Instance store volumes are included as part of the instance's usage cost. Some instance types use NVMe or SATA-based solid-state drives (SSD) to deliver high random I/O performance. This is a good option when you need storage with very low latency, but you don't need the data to persist when the instance terminates. + +--- + +- The engineering team at a social media company has recently migrated to AWS Cloud from its on-premises data center. The team is evaluating CloudFront to be used as a CDN for its flagship application. The team has hired you as an AWS Certified Solutions Architect Associate to advise on CloudFront capabilities on routing, security, and high availability. + Which of the following would you identify as correct regarding CloudFront? + +Answer: + +- **CloudFront can route to multiple origins based on the content type** + + - You can configure a single CloudFront web distribution to serve different types of requests from multiple origins. For example, if you are building a website that serves static content from an Amazon Simple Storage Service (Amazon S3) bucket and dynamic content from a load balancer, you can serve both types of content from a CloudFront web distribution. + +- **Use an origin group with primary and secondary origins to configure CloudFront for high availability and failover** + + - You can set up CloudFront with origin failover for scenarios that require high availability. To get started, you create an origin group with two origins: a primary and a secondary. If the primary origin is unavailable or returns specific HTTP response status codes that indicate a failure, CloudFront automatically switches to the secondary origin. + + - To set up origin failover, you must have a distribution with at least two origins. Next, you create an origin group for your distribution that includes two origins, setting one as the primary. Finally, you create or update a cache behavior to use the origin group. + + - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html + +- **Use field level encryption in CloudFront to protect sensitive data for specific content** + + - Field-level encryption allows you to enable your users to securely upload sensitive information to your web servers. The sensitive information provided by your users is encrypted at the edge, close to the user, and remains encrypted throughout your entire application stack. This encryption ensures that only applications that need the data—and have the credentials to decrypt it—are able to do so. + + - To use field-level encryption, when you configure your CloudFront distribution, specify the set of fields in POST requests that you want to be encrypted, and the public key to use to encrypt them. You can encrypt up to 10 data fields in a request. (You can’t encrypt all of the data in a request with field-level encryption; you must specify individual fields to encrypt.) + +--- + +- The engineering team at an e-commerce company has been tasked with migrating to a serverless architecture. The team wants to focus on the key points of consideration when using Lambda as a backbone for this architecture. + As a Solutions Architect, which of the following options would you identify as correct for the given requirement? + +Answer: +- **By default, Lambda functions always operate from an AWS-owned VPC and hence have access to any public internet address or public AWS APIs.** Once a Lambda function is VPC-enabled, it will need a route through a NAT gateway in a public subnet to access public resources + - Lambda functions always operate from an AWS-owned VPC. By default, your function has the full ability to make network requests to any public internet address + - this includes access to any of the public AWS APIs. For example, your function can interact with AWS DynamoDB APIs to PutItem or Query for records. You should only enable your functions for VPC access when you need to interact with a private resource located in a private subnet. An RDS instance is a good example. + - Once your function is VPC-enabled, all network traffic from your function is subject to the routing rules of your VPC/Subnet. If your function needs to interact with a public resource, you will need a route through a NAT gateway in a public subnet. + - https://aws.amazon.com/blogs/architecture/best-practices-for-developing-on-aws-lambda/ + +- Since Lambda functions can scale extremely quickly, its a good idea to **deploy a CloudWatch Alarm that notifies your team** when function metrics such as **ConcurrentExecutions or Invocations exceeds the expected threshold** + - Since Lambda functions can scale extremely quickly, this means you should have controls in place to notify you when you have a spike in concurrency. A good idea is to deploy a CloudWatch Alarm that notifies your team when function metrics such as ConcurrentExecutions or Invocations exceeds your threshold. You should create an AWS Budget so you can monitor costs on a daily basis. + +- **If you intend to reuse code in more than one Lambda function, you should consider creating a Lambda Layer for the reusable code** + - You can configure your Lambda function to pull in additional code and content in the form of layers. A layer is a ZIP archive that contains libraries, a custom runtime, or other dependencies. With layers, you can use libraries in your function without needing to include them in your deployment package. Layers let you keep your deployment package small, which makes development easier. A function can use up to 5 layers at a time. + - You can create layers, or use layers published by AWS and other AWS customers. Layers support resource-based policies for granting layer usage permissions to specific AWS accounts, AWS Organizations, or all accounts. The total unzipped size of the function and all layers can't exceed the unzipped deployment package size limit of 250 MB. + - https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html + +--- + +- A social media company wants the capability to dynamically alter the size of a geographic area from which traffic is routed to a specific server resource. + Which feature of Route 53 can help achieve this functionality? + +- Answer: Geoproximity routing + - Geoproximity routing lets Amazon Route 53 route traffic to your resources based on the geographic location of your users and your resources. You can also optionally choose to route more traffic or less to a given resource by specifying a value, known as a bias. A bias expands or shrinks the size of the geographic region from which traffic is routed to a resource. + - To optionally change the size of the geographic region from which Route 53 routes traffic to a resource, specify the applicable value for the bias: 1. To expand the size of the geographic region from which Route 53 routes traffic to a resource, specify a positive integer from 1 to 99 for the bias. Route 53 shrinks the size of adjacent regions. + - + +--- + +- A retail company is using AWS Site-to-Site VPN connections for secure connectivity to its AWS cloud resources from its on-premises data center. Due to a surge in traffic across the VPN connections to the AWS cloud, users are experiencing slower VPN connectivity. + Which of the following options will **maximize the VPN throughput**? + +- Answer: **Create a transit gateway with equal cost multipath routing and add additional VPN tunnels** + + - VPN connection is a secure connection between your on-premises equipment and your VPCs. Each VPN connection has two VPN tunnels which you can use for high availability. A VPN tunnel is an encrypted link where data can pass from the customer network to or from AWS. The following diagram shows the high-level connectivity with virtual private gateways. + + - With AWS Transit Gateway, you can simplify the connectivity between multiple VPCs and also connect to any VPC attached to AWS Transit Gateway with a single VPN connection. **AWS Transit Gateway also enables you to scale the IPsec VPN throughput with equal cost multi-path (ECMP) routing support over multiple VPN tunnels.** A single VPN tunnel still has a maximum throughput of 1.25 Gbps. If you establish **multiple VPN tunnels to an ECMP-enabled transit gateway**, it can scale beyond the default maximum limit of 1.25 Gbps. You also must enable the dynamic routing option on your transit gateway to be able to take advantage of ECMP for scalability. + +--- + +- A company uses Application Load Balancers (ALBs) in multiple AWS Regions. The ALBs receive inconsistent traffic that varies throughout the year. The engineering team at the company needs to allow the IP addresses of the ALBs in the on-premises firewall to enable connectivity. + Which of the following represents the MOST scalable solution with minimal configuration changes? + +- Answer: **Set up AWS Global Accelerator. Register the ALBs in different Regions to the Global Accelerator. Configure the on-premises firewall's rule to allow static IP addresses associated with the Global Accelerator** + + - AWS Global Accelerator is a networking service that helps you improve the availability and performance of the applications that you offer to your global users. AWS Global Accelerator is easy to set up, configure, and manage. It provides static IP addresses that provide a fixed entry point to your applications and eliminate the complexity of managing specific IP addresses for different AWS Regions and Availability Zones. + + - Associate the static IP addresses provided by AWS Global Accelerator to regional AWS resources or endpoints, such as Network Load Balancers, Application Load Balancers, EC2 Instances, and Elastic IP addresses. The IP addresses are anycast from AWS edge locations so they provide onboarding to the AWS global network close to your users. + + - Simplified and resilient traffic routing for multi-Region applications using Global Accelerator: + +--- + +- The development team at a social media company wants to handle some complicated queries such as "What are the number of likes on the videos that have been posted by friends of a user A?". + As a solutions architect, which of the following AWS database services would you suggest as the BEST fit to handle such use cases? + +- Answer: **Amazon Neptune Amazon Neptune** + - It is a fast, reliable, fully managed graph database service that makes it easy to build and run applications that work with highly connected datasets. The core of Amazon Neptune is a purpose-built, high-performance graph database engine optimized for storing billions of relationships and querying the graph with milliseconds latency. Neptune powers graph use cases such as recommendation engines, fraud detection, knowledge graphs, drug discovery, and network security. + + - Amazon Neptune is highly available, with read replicas, point-in-time recovery, continuous backup to Amazon S3, and replication across Availability Zones. Neptune is secure with support for HTTPS encrypted client connections and encryption at rest. Neptune is fully managed, so you no longer need to worry about database management tasks such as hardware provisioning, software patching, setup, configuration, or backups. + + - Amazon Neptune can quickly and easily process large sets of user-profiles and interactions to build social networking applications. Neptune enables highly interactive graph queries with high throughput to bring social features into your applications. For example, if you are building a social feed into your application, you can use Neptune to provide results that prioritize showing your users the latest updates from their family, from friends whose updates they ‘Like,’ and from friends who live close to them. + +--- + +- A systems administrator is creating IAM policies and attaching them to IAM identities. After creating the necessary identity-based policies, the administrator is now creating resource-based policies. + Which is the only resource-based policy that the IAM service supports? + +- Answer: **Trust policy** + - Trust policies define **which principal entities (accounts, users, roles, and federated users) can assume the role**. An IAM role is both an identity and a resource that supports resource-based policies. For this reason, you must attach both a trust policy and an identity-based policy to an IAM role. The IAM service supports only one type of resource-based policy called a role trust policy, which is attached to an IAM role. + +Incorrect options: +- Access control list (ACL) + - Access control lists (ACLs) are service policies that allow you to control which principals in another account can access a resource. **ACLs cannot be used to control access for a principal within the same account**. Amazon S3, AWS WAF, and Amazon VPC are examples of services that support ACLs. + +--- + +- You are working for a SaaS (Software as a Service) company as a solutions architect and help design solutions for the company's customers. One of the customers is a bank and has a requirement to whitelist up to two public IPs when the bank is accessing external services across the internet. + Which architectural choice do you recommend to maintain high availability, support scaling-up to 10 instances and comply with the bank's requirements? + +- Answer: Use a Network Load Balancer with an Auto Scaling Group (ASG) + - Network Load Balancer is best suited for use-cases involving low latency and high throughput workloads that involve scaling to millions of requests per second. Network Load Balancer operates at the connection level (Layer 4), routing connections to targets - Amazon EC2 instances, microservices, and containers – within Amazon Virtual Private Cloud (Amazon VPC) based on IP protocol data. A Network Load Balancer functions at the fourth layer of the Open Systems Interconnection (OSI) model. It can handle millions of requests per second. + - Network Load Balancers expose a fixed IP to the public web, therefore allowing your application to be predictably reached using these IPs, while allowing you to scale your application behind the Network Load Balancer using an ASG. + +Incorrect options: + - Classic Load Balancers and Application Load Balancers use the private IP addresses associated with their Elastic network interfaces as the source IP address for requests forwarded to your web servers. + + - These IP addresses can be used for various purposes, such as allowing the load balancer traffic on the web servers and for request processing. It's a best practice to use security group referencing on the web servers for whitelisting load balancer traffic from Classic Load Balancers or Application Load Balancers. + + - However, because Network Load Balancers don't support security groups, based on the target group configurations, the IP addresses of the clients or the private IP addresses associated with the Network Load Balancers must be allowed on the web server's security group. + +--- + +- Use Exponential Backoff + - While this may help in the short term, as soon as the request rate increases, you will see the ProvisionedThroughputExceededException exception again. + +- Increase the number of shards + - Increasing shards could be a short term fix but will substantially increase the cost, so this option is ruled out. + +- Decrease the Stream retention duration + - This operation may result in data loss and won't help with the exceptions, so this option is incorrect. + +--- + +- The EBS volume was configured as the root volume of the Amazon EC2 instance. On termination of the instance, the default behavior is to also terminate the attached root volume + +--- + +- A company hires experienced specialists to analyze the customer service calls attended by its call center representatives. Now, the company wants to move to AWS Cloud and is looking at an automated solution to analyze customer service calls for sentiment analysis via ad-hoc SQL queries. + As a Solutions Architect, which of the following solutions would you recommend? + +- Answer: Use Amazon Transcribe to convert audio files to text and Amazon Athena to understand the underlying customer sentiments + - Amazon Transcribe is an automatic speech recognition (ASR) service that makes it easy to convert audio to text. One key feature of the service is called speaker identification, which you can use to label each individual speaker when transcribing multi-speaker audio files. You can specify Amazon Transcribe to identify 2–10 speakers in the audio clip. + - Amazon Athena is an interactive query service that makes it easy to analyze data in Amazon S3 using standard SQL. Athena is serverless, so there is no infrastructure to manage, and you pay only for the queries that you run. To leverage Athena, you can simply point to your data in Amazon S3, define the schema, and start querying using standard SQL. Most results are delivered within seconds. + - Analyzing multi-speaker audio files using Amazon Transcribe and Amazon Athena: + - ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/270f3abd-a126-419d-b81e-66b4cf277dea) + +--- + +- A retail company wants to establish encrypted network connectivity between its on-premises data center and AWS Cloud. The company wants to get the solution up and running in the fastest possible time and it should also support encryption in transit. + As a solutions architect, which of the following solutions would you suggest to the company? + +- Answer: Use Site-to-Site VPN to establish encrypted network connectivity between the on-premises data center and AWS Cloud + - You can securely extend your data center or branch office network to the cloud with an AWS Site-to-Site VPN connection. A VPC VPN Connection utilizes IPSec to establish encrypted network connectivity between your on-premises network and Amazon VPC over the Internet. IPsec is a protocol suite for securing IP communications by authenticating and encrypting each IP packet in a data stream. + + - To encrypt the data in transit that traverses AWS Direct Connect, you must use the transit encryption options for that service. As **AWS Direct Connect does not support encrypted network connectivity between an on-premises data center and AWS Cloud**, therefore this option is incorrect. + +--- + +**it is not possible to modify a launch configuration once it is created. Hence, this option is incorrect.** + +--- + +- An application hosted on Amazon EC2 contains sensitive personal information about all its customers and needs to be protected from all types of cyber-attacks. The company is considering using the AWS Web Application Firewall (WAF) to handle this requirement. + Can you identify the correct solution leveraging the capabilities of WAF? + +- Answer: Create a CloudFront distribution for the application on Amazon EC2 instances. Deploy AWS WAF on Amazon CloudFront to provide the necessary safety measures + + - When you use AWS WAF with CloudFront, you can protect your applications running on any HTTP webserver, whether it's a webserver that's running in Amazon Elastic Compute Cloud (Amazon EC2) or a web server that you manage privately. You can also configure CloudFront to require HTTPS between CloudFront and your own webserver, as well as between viewers and CloudFront. + + - **AWS WAF is tightly integrated with Amazon CloudFront and the Application Load Balancer (ALB)**, services that AWS customers commonly use to deliver content for their websites and applications. + + - When you use AWS WAF on Amazon CloudFront, your rules run in all AWS Edge Locations, located around the world close to your end-users. This means security doesn’t come at the expense of performance. Blocked requests are stopped before they reach your web servers. When you use AWS WAF on Application Load Balancer, your rules run in the region and can be used to protect internet-facing as well as internal load balancers. + +--- + +- S3 One Zone-IA is a good choice for storing secondary backup copies of on-premises data or easily re-creatable data. The given scenario clearly states that the business-critical data is not easy to reproduce, so this option is incorrect. + +--- + +- A financial services firm uses a high-frequency trading system and wants to write the log files into Amazon S3. The system will also read these log files in parallel on a near real-time basis. The engineering team wants to address any data discrepancies that might arise when the trading system overwrites an existing log file and then tries to read that specific log file. + Which of the following options BEST describes the capabilities of Amazon S3 relevant to this scenario? + +- Answer: **A process replacas an existing object and immediately tries to read it. Amazon S3 always returns the latest version of the object** + + - Amazon S3 delivers strong read-after-write consistency automatically, without changes to performance or availability, without sacrificing regional isolation for applications, and at no additional cost. + + - After a successful write of a new object or an overwrite of an existing object, any subsequent read request immediately receives the latest version of the object. S3 also provides strong consistency for list operations, so after a write, you can immediately perform a listing of the objects in a bucket with any changes reflected. + + - Strong read-after-write consistency helps when you need to immediately read an object after a write. For example, strong read-after-write consistency when you often read and list immediately after writing objects. + + - To summarize, all S3 GET, PUT, and LIST operations, as well as operations that change object tags, ACLs, or metadata, are strongly consistent. What you write is what you will read, and the results of a LIST will be an accurate reflection of what’s in the bucket. + +--- + +- An application with global users across AWS Regions had suffered an issue when the Elastic Load Balancer (ELB) in a Region malfunctioned thereby taking down the traffic with it. The manual intervention cost the company significant time and resulted in major revenue loss. + What should a solutions architect recommend to reduce internet latency and add automatic failover across AWS Regions? + +- Answer: **Set up AWS Global Accelerator and add endpoints to cater to users in different geographic locations** + + - As your application architecture grows, so does the complexity, with longer user-facing IP lists and more nuanced traffic routing logic. AWS Global Accelerator solves this by providing you with two static IPs that are anycast from our globally distributed edge locations, giving you a single entry point to your application, regardless of how many AWS Regions it’s deployed in. This allows you to add or remove origins, Availability Zones or Regions without reducing your application availability. Your traffic routing is managed manually, or in console with endpoint traffic dials and weights. If your application endpoint has a failure or availability issue, AWS Global Accelerator will automatically redirect your new connections to a healthy endpoint within seconds. + + - By using AWS Global Accelerator, you can: + + 1. Associate the static IP addresses provided by AWS Global Accelerator to regional AWS resources or endpoints, such as Network Load Balancers, Application Load Balancers, EC2 Instances, and Elastic IP addresses. The IP addresses are anycast from AWS edge locations so they provide onboarding to the AWS global network close to your users. + + 2. Easily move endpoints between Availability Zones or AWS Regions without needing to update your DNS configuration or change client-facing applications. + + 3. Dial traffic up or down for a specific AWS Region by configuring a traffic dial percentage for your endpoint groups. This is especially useful for testing performance and releasing updates. + + 4. Control the proportion of traffic directed to each endpoint within an endpoint group by assigning weights across the endpoints. + +- Incorrect: Set up an Amazon Route 53 geoproximity routing policy to route traffic + - Geoproximity routing lets Amazon Route 53 route traffic to your resources based on the geographic location of your users and your resources. + - Unlike Global Accelerator, managing and routing to different instances, ELBs and other AWS resources will become an operational overhead as the resource count reaches into the hundreds. With inbuilt features like Static anycast IP addresses, fault tolerance using network zones, Global performance-based routing, TCP Termination at the Edge - Global Accelerator is the right choice for multi-region, low latency use cases. + +--- + +**Database cloning is only available for Aurora and not for RDS.** + +--- + +**Data transfer pricing over Direct Connect is lower than data transfer pricing over the internet.** + +--- + +- A company wants to ensure high availability for its RDS database. The development team wants to opt for Multi-AZ deployment and they would like to understand what happens when the primary instance of the Multi-AZ configuration goes down. + As a Solutions Architect, which of the following will you identify as the outcome of the scenario? + +- Answer: **The CNAME record will be updated to point to the standby DB** + - Amazon RDS provides high availability and failover support for DB instances using Multi-AZ deployments. Amazon RDS uses several different technologies to provide failover support. Multi-AZ deployments for MariaDB, MySQL, Oracle, and PostgreSQL DB instances use Amazon's failover technology. SQL Server DB instances use SQL Server Database Mirroring (DBM) or Always On Availability Groups (AGs). + + - In a Multi-AZ deployment, Amazon RDS automatically provisions and maintains a synchronous standby replica in a different Availability Zone. The primary DB instance is synchronously replicated across Availability Zones to a standby replica to provide data redundancy, eliminate I/O freezes, and minimize latency spikes during system backups. Running a DB instance with high availability can enhance availability during planned system maintenance, and help protect your databases against DB instance failure and Availability Zone disruption. + + - Failover is automatically handled by Amazon RDS so that you can resume database operations as quickly as possible without administrative intervention. When failing over, Amazon RDS simply flips the canonical name record (CNAME) for your DB instance to point at the standby, which is in turn promoted to become the new primary. Multi-AZ means the URL is the same, the failover is automated, and the CNAME will automatically be updated to point to the standby database. + +--- + +- An engineering team wants to orchestrate multiple Amazon ECS task types running on EC2 instances that are part of the ECS cluster. The output and state data for all tasks need to be stored. The amount of data output by each task is approximately 20 MB and there could be hundreds of tasks running at a time. As old outputs are archived, the storage size is not expected to exceed 1 TB. + As a solutions architect, which of the following would you recommend as an optimized solution for high-frequency reading and writing? + +Amazon EFS file systems are distributed across an unconstrained number of storage servers. This distributed data storage design enables file systems to grow elastically to petabyte scale. It also enables massively parallel access from compute instances, including Amazon EC2, Amazon ECS, and AWS Lambda, to your data. + +- **Use Amazon EFS with Provisioned Throughput mode** + + - Provisioned Throughput mode is available for applications with high throughput to storage (MiB/s per TiB) ratios, or with requirements greater than those allowed by the Bursting Throughput mode. For example, say you're using Amazon EFS for development tools, web serving, or content management applications where the amount of data in your file system is low relative to throughput demands. Your file system can now get the high levels of throughput your applications require without having to pad your file system. + + - If your file system is in the Provisioned Throughput mode, you can increase the Provisioned Throughput of your file system as often as you want. You can decrease your file system throughput in Provisioned Throughput mode as long as it's been more than 24 hours since the last decrease. Additionally, you can change between Provisioned Throughput mode and the default Bursting Throughput mode as long as it’s been more than 24 hours since the last throughput mode change. + +- **Use Amazon EFS with Bursting Throughput mode** + + - With Bursting Throughput mode, a file system's throughput scales as the amount of data stored in the standard storage class grows. File-based workloads are typically spiky, driving high levels of throughput for short periods of time, and low levels of throughput the rest of the time. To accommodate this, Amazon EFS is designed to burst to high throughput levels for periods of time. By default, AWS recommends that you run your application in the Bursting Throughput mode. But, if you're planning to migrate large amounts of data into your file system, consider switching to Provisioned Throughput mode. + + - The use-case mentions that the solution should be optimized for high-frequency reading and writing even when the old outputs are archived, therefore Provisioned Throughput mode is a better fit as it guarantees high levels of throughput your applications require without having to pad your file system. + +--- + +- You are deploying a critical monolith application that must be deployed on a single web server, as it hasn't been created to work in distributed mode. Still, you want to make sure your setup can automatically recover from the failure of an AZ. + Which of the following options should be combined to form the MOST cost-efficient solution? (Select three) + +- **Create an auto-scaling group that spans across 2 AZ, which min=1, max=1, desired=1** + + - Amazon EC2 Auto Scaling helps you ensure that you have the correct number of Amazon EC2 instances available to handle the load for your application. You create collections of EC2 instances, called Auto Scaling groups. You can specify the minimum number of instances in each Auto Scaling group, and Amazon EC2 Auto Scaling ensures that your group never goes below this size. + - So we have an ASG with desired=1, across two AZ, so that if an instance goes down, it is automatically recreated in another AZ. So this option is correct. + +- Create an Elastic IP and use the EC2 user-data script to attach it + + - Application Load Balancer (ALB) operates at the request level (layer 7), routing traffic to targets – EC2 instances, containers, IP addresses, and Lambda functions based on the content of the request. Ideal for advanced load balancing of HTTP and HTTPS traffic, Application Load Balancer provides advanced request routing targeted at delivery of modern application architectures, including microservices and container-based applications. + + - An Elastic IP address is a static IPv4 address designed for dynamic cloud computing. + - An Elastic IP address is associated with your AWS account. With an Elastic IP address, you can mask the failure of an instance or software by rapidly remapping the address to another instance in your account. + + - Now, between the ALB and the Elastic IP. If we use an ALB, things will still work, but we will have to pay for the provisioned ALB which sends traffic to only one EC2 instance. Instead, to minimize costs, we must use an Elastic IP. + +- Assign an EC2 Instance Role to perform the necessary API calls + + - For that Elastic IP to be attached to our EC2 instance, we must use an EC2 user data script, and our EC2 instance must have the correct IAM permissions to perform the API call, so we need an EC2 instance role. + +--- + +**Redis does not support multi-threading** + +--- + +DB 엔진 유지 관리 +- 데이터베이스 엔진 수준으로 업그레이드하려면 가동 중지가 필요합니다. RDS DB 인스턴스가 다중 AZ 배포를 사용하더라도 기본 및 대기 DB 인스턴스는 동시에 업그레이드됩니다. 이로 인해 업그레이드가 완료될 때까지 가동 중지가 발생하고 가동 중지 기간은 DB 인스턴스의 크기에 따라 달라집니다. 자세한 내용은 DB 인스턴스 엔진 버전 업그레이드의 DB 엔진 섹션을 참조하세요. + +- 참고: SQL Server DB 인스턴스를 다중 AZ 배포로 업그레이드하는 경우 기본 및 대기 인스턴스가 모두 업그레이드됩니다. Amazon RDS는 롤링 업그레이드를 수행하므로 장애 조치 기간에만 중단됩니다. 자세한 내용은 다중 AZ 및 인 메모리 최적화 고려 사항을 참조하세요. + +--- + +- The engineering team at a startup is evaluating the most optimal block storage volume type for the EC2 instances hosting its flagship application. The storage volume should support very low latency but it does not need to persist the data when the instance terminates. As a solutions architect, you have proposed using Instance Store volumes to meet these requirements. + Which of the following would you identify as the key characteristics of the Instance Store volumes? (Select two) + +- **You can't detach an instance store volume from one instance and attach it to a different instance** + - You can specify instance store volumes for an instance only when you launch it. You can't detach an instance store volume from one instance and attach it to a different instance. The data in an instance store persists only during the lifetime of its associated instance. If an instance reboots (intentionally or unintentionally), data in the instance store persists. + +- **If you create an AMI from an instance, the data on its instance store volumes isn't preserved** + - If you create an AMI from an instance, the data on its instance store volumes isn't preserved and isn't present on the instance store volumes of the instances that you launch from the AMI. + +Incorrect options: + +- Instance store is reset when you stop or terminate an instance. Instance store data is preserved during hibernation + - **When you stop, hibernate, or terminate an instance, every block of storage in the instance store is reset.** Therefore, this option is incorrect. + +- You can specify instance store volumes for an instance when you launch or restart it + - **You can specify instance store volumes for an instance only when you launch it.** + +- An instance store is a network storage type + - **An instance store provides temporary block-level storage for your instance.** This storage is located on disks that are physically attached to the host computer. + + +--- + +**you should create a read-replica with the same compute capacity and the same storage capacity as the primary.** + +--- + +- A company runs an ecommerce application on Amazon EC2 instances behind an Application Load Balancer. The instances run in an Amazon EC2 Auto Scaling group across multiple Availability Zones. The Auto Scaling group scales based on CPU utilization metrics. The ecommerce application stores the transaction data in a MySQL 8.0 database that is hosted on a large EC2 instance. + The database's performance degrades quickly as application load increases. The application handles more read requests than write transactions. The company wants a solution that will automatically scale the database to meet the demand of unpredictable read workloads while maintaining high availability. Which solution will meet these requirements? + +- I would recommend option C: Use AWS Network Firewall to create the required rules for traffic inspection and traffic filtering for the production VPC. + AWS Network Firewall is a managed firewall service that provides filtering for both inbound and outbound network traffic. It allows you to create rules for traffic inspection and filtering, which can help protect your production VPC. + Option A: Amazon GuardDuty is a threat detection service, not a traffic inspection or filtering service. + Option B: Traffic Mirroring is a feature that allows you to replicate and send a copy of network traffic from a VPC to another VPC or on-premises location. It is not a service that performs traffic inspection or filtering. + Option D: AWS Firewall Manager is a security management service that helps you to centrally configure and manage firewalls across your accounts. It is not a service that performs traffic inspection or filtering. + +--- + + Incorrect: Amazon QuickSight only support users(standard version) and groups (enterprise version). QuickSight don't support IAM. We use users and groups to view the QuickSight dashboard + +--- + +- A development team runs monthly resource-intensive tests on its general purpose Amazon RDS for MySQL DB instance with Performance Insights enabled. The testing lasts for 48 hours once a month and is the only process that uses the database. The team wants to reduce the cost of running the tests without reducing the compute and memory attributes of the DB instance. + Which solution meets these requirements MOST cost-effectively? + +- A. Stop the DB instance when tests are completed. Restart the DB instance when required. + - By stopping the DB although you are not paying for DB hours you are still paying for Provisioned IOPs, the storage for Stopped DB is more than Snapshot of underlying EBS vol. and Automated Back ups. +- C. Create a snapshot when tests are completed. Terminate the DB instance and restore the snapshot when required. + - Create a manual Snapshot of DB and shift to S3- Standard and Restore form Manual Snapshot when required. + +--- + +- The data stored on the Snowball Edge device can be copied into the S3 bucket and later transitioned into AWS Glacier via a lifecycle policy. You can't directly copy data from Snowball Edge devices into AWS Glacier. + +--- + +- **Only Standard SQS queue is allowed as an Amazon S3 event notification destination, whereas FIFO SQS queue is not allowed.** + +- The Amazon S3 notification feature enables you to receive notifications when certain events happen in your bucket. To enable notifications, you must first add a notification configuration that identifies the events you want Amazon S3 to publish and the destinations where you want Amazon S3 to send the notifications. + - Amazon S3 supports the following destinations where it can publish events: + - Amazon Simple Notification Service (Amazon SNS) topic + - Amazon Simple Queue Service (Amazon SQS) queue + - AWS Lambda + +- Currently, the Standard SQS queue is only allowed as an Amazon S3 event notification destination, whereas the FIFO SQS queue is not allowed. + +https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html + +--- + +- Use a Web Application Firewall and setup a rate-based rule + +- AWS WAF is a web application firewall that helps protect your web applications or APIs against common web exploits that may affect availability, compromise security, or consume excessive resources. AWS WAF gives you control over how traffic reaches your applications by enabling you to create security rules that block common attack patterns, such as SQL injection or cross-site scripting, and rules that filter out specific traffic patterns you define. + The correct answer is to use WAF (which has integration on top of your ALB) and define a rate-based rule. + + +- AWS Shield Advanced will **give you DDoS protection overall, and you cannot set up rate-based rules in Shield.** + +--- + +**Any database engine level upgrade for an RDS DB instance with Multi-AZ deployment triggers both the primary and standby DB instances to be upgraded at the same time. This causes downtime until the upgrade is complete +** + +--- + +**Multi-Attach is supported exclusively on Provisioned IOPS SSD volumes.** + +--- + +image + +> https://www.examtopics.com/exams/amazon/aws-certified-solutions-architect-associate-saa-c03/ + +--- + +- A company is moving its on-premises Oracle database to Amazon Aurora PostgreSQL. The database has several applications that write to the same tables. +- The applications need to be migrated one by one with a month in between each migration. +- Management has expressed concerns that the database has a high number of reads and writes. The data must be kept in sync across both databases throughout the migration. +- What should a solutions architect recommend? + + - A. Use AWS DataSync for the initial migration. Use AWS Database Migration Service (AWS DMS) to create a change data capture (CDC) replication task and a table mapping to select all tables. + - B. Use AWS DataSync for the initial migration. Use AWS Database Migration Service (AWS DMS) to create a full load plus change data capture (CDC) replication task and a table mapping to select all tables. + - C. Use the AWS Schema Conversion Tool with AWS Database Migration Service (AWS DMS) using a memory optimized replication instance. Create a full load plus change data capture (CDC) replication task and a table mapping to select all tables. Most Voted + - D. Use the AWS Schema Conversion Tool with AWS Database Migration Service (AWS DMS) using a compute optimized replication instance. Create a full load plus change data capture (CDC) replication task and a table mapping to select the largest tables. + + - Answer: C. + - The AWS SCT is used to convert the schema and code of the Oracle database to be compatible with Aurora PostgreSQL. AWS DMS is utilized to migrate the data from the Oracle database to Aurora PostgreSQL. Using a memory-optimized replication instance is recommended to handle the high number of reads and writes during the migration process. + - By creating a full load plus CDC replication task, the initial data migration is performed, and ongoing changes in the Oracle database are continuously captured and applied to the Aurora PostgreSQL database. Selecting all tables for table mapping ensures that all the applications writing to the same tables are migrated. + - Option A & B are incorrect because using AWS DataSync alone is not sufficient for database migration and data synchronization. + - Option D is incorrect because using a compute optimized replication instance is not the most suitable choice for handling the high number of reads and writes. + +--- + +- A company wants to experiment with individual AWS accounts for its engineer team. The company wants to be notified as soon as the Amazon EC2 instance usage for a given month exceeds a specific threshold for each account. + What should a solutions architect do to meet this requirement MOST cost-effectively? + + - A. Use Cost Explorer to create a daily report of costs by service. Filter the report by EC2 instances. Configure Cost Explorer to send an Amazon Simple Email Service (Amazon SES) notification when a threshold is exceeded. + - B. Use Cost Explorer to create a monthly report of costs by service. Filter the report by EC2 instances. Configure Cost Explorer to send an Amazon Simple Email Service (Amazon SES) notification when a threshold is exceeded. + - C. Use AWS Budgets to create a cost budget for each account. Set the period to monthly. Set the scope to EC2 instances. Set an alert threshold for the budget. Configure an Amazon Simple Notification Service (Amazon SNS) topic to receive a notification when a threshold is exceeded. Most Voted + - D. Use AWS Cost and Usage Reports to create a report with hourly granularity. Integrate the report data with Amazon Athena. Use Amazon EventBridge to schedule an Athena query. Configure an Amazon Simple Notification Service (Amazon SNS) topic to receive a notification when a threshold is exceeded. + + - Answer: C, AWS Budgets allows you to set a budget for costs and usage for your accounts and you can set alerts when the budget threshold is exceeded in real-time which meets the requirement. + - Why not B: B would be the most cost-effective if the requirements didn't ask for real-time notification. You would not incur additional costs for the daily or monthly reports and the notifications. **But doesn't provide real-time alerts.** + +--- + +- A company is implementing a shared storage solution for a media application that is hosted in the AWS Cloud. The company needs the ability to use SMB clients to access data. The solution must be fully managed. +- Which AWS solution meets these requirements? + +- D. Create an Amazon FSx for Windows File Server file system. Attach the file system to the origin server. Connect the application server to the file system + - SMB + fully managed = fsx for windows imo + +--- + +- A company recently announced the deployment of its retail website to a global audience. The website runs on multiple Amazon EC2 instances behind an Elastic Load Balancer. The instances run in an Auto Scaling group across multiple Availability Zones. + The company wants to **provide its customers with different versions of content based on the devices that the customers use to access the website.** Which combination of actions should a solutions architect take to meet these requirements? (Choose two.) + +- A. Configure Amazon CloudFront to cache multiple versions of the content. Most Voted +- C. Configure a Lambda@Edge function to send specific objects to users based on the User-Agent header. + +--- + +- A company plans to use Amazon ElastiCache for its multi-tier web application. A solutions architect creates a Cache VPC for the ElastiCache cluster and an App VPC for the application’s Amazon EC2 instances. Both VPCs are in the us-east-1 Region. + The solutions architect must implement a solution to provide the application’s EC2 instances with access to the ElastiCache cluster. + Which solution will meet these requirements MOST cost-effectively? + +- Create a peering connection between the VPCs. Add a route table entry for the peering connection in both VPCs. Configure an inbound rule for the ElastiCache cluster’s security group to allow inbound connection from the application’s security group. + +--- + +- A company is using a centralized AWS account to store log data in various Amazon S3 buckets. A solutions architect needs to ensure that the data is encrypted at rest before the data is uploaded to the S3 buckets. The data also must be encrypted in transit. + Which solution meets these requirements? + +- Use client-side encryption to encrypt the data that is being uploaded to the S3 buckets. + - here keyword is "before" "the data is encrypted at rest before the data is uploaded to the S3 buckets." + +--- + +- A company runs an application on Amazon EC2 instances. The company needs to implement a disaster recovery (DR) solution for the application. The DR solution needs to have a recovery time objective (RTO) of less than 4 hours. The DR solution also needs to use the fewest possible AWS resources during normal operations. + +- B. Create Amazon Machine Images (AMIs) to back up the EC2 instances. Copy the AMIs to a secondary AWS Region. Automate infrastructure deployment in the secondary Region by using AWS CloudFormation. + - Creating AMIs for backup and using AWS CloudFormation for infrastructure deployment in the secondary Region is a more streamlined and automated approach. CloudFormation allows you to define and provision resources in a declarative manner, making it easier to maintain and update your infrastructure. This solution is more operationally efficient compared to Option A. + +--- + +- A company has an application that is backed by an Amazon DynamoDB table. The company’s compliance requirements specify that database backups must be taken every month, must be available for 6 months, and must be retained for 7 years. + Which solution will meet these requirements? + +- A. Create an AWS Backup plan to back up the DynamoDB table on the first day of each month. Specify a lifecycle policy that transitions the backup to cold storage after 6 months. Set the retention period for each backup to 7 years. Most Voted +- B. Create a DynamoDB on-demand backup of the DynamoDB table on the first day of each month. Transition the backup to Amazon S3 Glacier Flexible Retrieval after 6 months. Create an S3 Lifecycle policy to delete backups that are older than 7 years. + +--- + +- A research company runs experiments that are powered by a simulation application and a visualization application. The simulation application runs on Linux and outputs intermediate data to an NFS share every 5 minutes. The visualization application is a Windows desktop application that displays the simulation output and requires an SMB file system. + The company maintains two synchronized file systems. This strategy is causing data duplication and inefficient resource usage. The company needs to migrate the applications to AWS without making code changes to either application. + Which solution will meet these requirements? + + +- Answer: D. Migrate the simulation application to Linux Amazon EC2 instances. Migrate the visualization application to Windows EC2 instances. Configure Amazon FSx for NetApp ONTAP for storage. + +--- + +**EBS volumes can only attach to a single EC2 instance.** They cannot be mounted by multiple servers concurrently and do not provide a shared file system. + +--- + +- A university research laboratory needs to migrate 30 TB of data from an on-premises Windows file server to Amazon FSx for Windows File Server. The laboratory has a 1 Gbps network link that many other departments in the university share. + The laboratory wants to implement a data migration service that will maximize the performance of the data transfer. However, the laboratory needs to be able to control the amount of bandwidth that the service uses to minimize the impact on other departments. The data migration must take place within the next 5 days. + Which AWS solution will meet these requirements? + +--- + +- A company is designing a shared storage solution for a gaming application that is hosted in the AWS Cloud. The company needs the ability to use SMB clients to access data. The solution must be fully managed. + Which AWS solution meets these requirements? + +- Answer: C, Create an Amazon FSx for Windows File Server file system. Attach the file system to the origin server. Connect the application server to the file system. + - Amazon FSx for Windows File Server provides a fully managed native Windows file system that can be accessed using the industry-standard SMB protocol. This allows Windows clients like the gaming application to directly access file data. + - FSx for Windows File Server handles time-consuming file system administration tasks like provisioning, setup, maintenance, file share management, backups, security, and software patching - reducing operational overhead. + - FSx for Windows File Server supports high file system throughput, IOPS, and consistent low latencies required for performance-sensitive workloads. This makes it suitable for a gaming application. + - The file system can be directly attached to EC2 instances, providing a performant shared storage solution for the gaming servers. + +--- + +AWS Storage Gateway Volume Gateway provides two configurations for connecting to iSCSI storage, namely, stored volumes and cached volumes, The storaed volume configuration stores the entire data set on-premises and asynchronoudly backs up the date to AWS> The cached volume configuration stores recently accessed data on-premises, and the remaining data is stored in S3. +- https://docs.amazonaws.cn/en_us/storagegateway/latest/vgw/StorageGatewayConcepts.html#storage-gateway-cached-concepts + +--- + +- A solutions architect needs to optimize storage costs. The solutions architect must identify any Amazon S3 buckets that are no longer being accessed or are rarely accessed. + Which solution will accomplish this goal with the LEAST operational overhead? + +- Answer: A. Analyze bucket access patterns by using the S3 Storage Lens dashboard for advanced activity metrics. + - S3 Storage Lens is a cloud-storage analytics feature that provides you with 29+ usage and activity metrics, including object count, size, age, and access patterns. This data can help you understand how your data is being used and identify areas where you can optimize your storage costs. + - https://aws.amazon.com/ko/blogs/aws/s3-storage-lens/ diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\232\224\354\225\275.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\232\224\354\225\275.md" new file mode 100644 index 00000000..efa243a7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/SAA\342\200\205\354\232\224\354\225\275.md" @@ -0,0 +1,312 @@ +--- +title: 'SAA 요약' +lastUpdated: '2023-09-27' +--- +## Analytics +- QuickSight: Visualization + +## Compute +- [EC2](./Computing/EC2.md): Elastic Compute Cloud + - 종류 + - **On-Demand instances**: 서버 대여, 시간 당 지불 + - **Reserved instance**: 1~3년간 대여 + - **Spot instances**: 사용자 제시 가격(입찰가격)을 정해놓고 저렴할 때 이용 + - placement + - default + - partition: 대규모 분산 및 복제 워크로드 + - cluster: HPC +- [API Gateway](./Netwoking/API%E2%80%85Gateway.md): REST 및 Websocker API를 생성, 유지, 관리 + +## Database +- [EFS](./Database/EFS.md): File System +- [DynamoDB](./Database/DynamoDB.md): NoSQL +- [RDS](./Database/RDS.md): RDBMS +- [Aurora](./Database/Aurora.md): 안정성과 고가용성을 겸비한 RDBMS (Serverless 가능) +- [Redshift](./Database/Redshift.md): fully managed, petabyte-scale data warehouse service in the cloud. + +## Management and governance +- [CloudWatch](./Management%E2%80%85and%E2%80%85governance/CloudWatch.md): 애플리케이션 지표, 로그 모니터링 + - [EventBridge](https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html): 변경 사항 실시간 전달 +- [CloudTrail](./Management%E2%80%85and%E2%80%85governance/CloudTrail.md): 권한이나 Policy에 대한 기록 트래킹 +- [CloudFormation](./Management%E2%80%85and%E2%80%85governance/CloudFormation.md): 클라우드 리소스 전체 모델링 관리 + - Origin Shield: Caching Layer + +## Networking +- [WAF]: SQL injection, macious ip 등 막아주는 7계층 방화벽 +- Shield: DDos 방지 +- [VPC](./Netwoking/VPC.md): 가상 네트워크, 다른 네트워크와 논리적 분리 +- [Security Groups](./Netwoking/Security%E2%80%85Groups.md): instance 수준의 control access, stateful, allow만 가능 +- [NACLs](./Netwoking/NACLs.md): subnet 수준의 control access, stateless, allow와 disallow 가능 + +## Security +- [Cognito](./Security/Cognite.md): User pools, Identity pools 제공 +- [IAM](./Security/IAM.md): 접근 유저 및 Policy 관리 +- [KMS](./Security/KMS.md): Key 관리 +- AWS Shield Advanced: DDoS 보호 + +## Storage +- [DayaSync](./Storage/DayaSync.md) : on premises와 AWS Storage services 사이 데이터를 sync 해줌 +- [EBS](./Storage/EBS.md): EC2 Image +- [EFS](./Storage/EFS.md): Serverless NFS file system +- [FSx](./Storage/FSx.md): fully managed File Systen + - FSx for NetApp ONTAP: 범용 + - FSx for Lustre: ML 특화 + - FSx for OpenZFS: ZFS, Linux 기반 범용 + - Windows File Server: Window 기반 +- [Storage Gateway](./Storage/Storage%E2%80%85Gateway.md): on-prem 이전용 + - (FSx) File Gateway, Volume Gateway, Tape Gateway +- [S3](./Storage/S3.md) : 파일 스토리지 + - Standard: 일반적인 스토리지, 99.99% availability와 11 9s durability를 지원 + - Infrequently Accessed(IA): 덜 조회되는 데이터 + - One Zone Infrequently Accessed: IA에서 availability를 포기했기 때문에 더 저렴 + - Intelligent Tiering: 특정 기간동안 접근되지 않으면 더 싼 Storage로 옮겨줌 + - Glacier: Data achiving을 위한 저장소, 조회하려면 몇 시간씩 걸릴 수 있음 +- [Amazon Macie](https://docs.aws.amazon.com/ko_kr/macie/latest/user/what-is-macie.html) : S3에서 민감한 데이터 감지, 보안 및 액세스 제어를 위한 데이터 평가 및 모니터링 + +## AI + +- [Comprehend](https://docs.aws.amazon.com/ko_kr/comprehend/latest/dg/what-is.html): 자연어 처리 +- [Textract](https://aws.amazon.com/ko/textract/): 문서에서 텍스트 추출 +- [Rekognition](https://aws.amazon.com/ko/rekognition/): 이미지 인식 및 비디오 분석 +- [SageMaker](https://aws.amazon.com/ko/sagemaker/): 완전 관리형 기계 학습 서비스 + + +## data + +- kinesis: 비디오와 데이터 스트림을 실시간으로 손 쉽게 수집 및 처리, 분석 + - Kinesis Data Streams: 데이터 스트림을 분석하는 사용자 정의 애플리케이션 개발에 사용 + - Kinesis Data Firehose: 데이터 스트림을 AWS 데이터 저장소에 적재 (S3나 redshift, elasticsearch 등) + - Kinesis Data Analytics: SQL을 사용해 데이터 스트림 분석 + - Kinesis Data Stream 또는 Firehose에 쉽게 연결하고 SQL 검색을 할 수 있다. + - 수행 결과를 다시 Data Stream 또는 Fireshose로 보냄 + - 스트리밍 소스에 연결 → SQL 코드를 쉽게 작성 → 지속적으로 SQL 결과를 전달함 + - 데이터를 처리하기 위한 2가지 컨셉을 사용하고 있다. + - 스트림(인-메모리 테이블) → 가상의 테이블 or view라고 봐야 함 + - 펌프(연속 쿼리) → 실제 데이터를 앞서 만든 view에 넣어주는 역할 + - Kinesis Video Streams: 분석을 위한 비디오 스트림 캡쳐 및 처리, 저장 + +--- + +**Shield** - Amazon Elastic Compute Cloud (EC2), Elastic Load Balancing (ELB), Amazon CloudFront, AWS Global Accelerator, and Route 53. + +**WAF** - Amazon CloudFront, the Application Load Balancer (ALB), Amazon API Gateway, and AWS AppSync + +--- + +- **AWS 스토리지간 데이터 복사** + - DataSync: 파일시스템간 데이터 복사가 필요할때 사용. + - Database Migration Service: 데이터 베이스에 특화된 서비스. 무중단 및 지속적인 동기화가 가능하다. 데이터베이스를 대상으로 복제가 필요할 경우 사용. + - Backup: 백업주기, 보관주기, 모니터링 등 백업 정책을 관리하는 목적이 강하다. 데이터 백업이 목적일 경우 사용. 파일시스템, DB 모두 가능 +- **온프레미스에서 AWS로 데이터 복사** + - DataSync: 파일시스템간 데이터 복사가 필요할때 사용 + - Database Migration Service: 데이터베이스를 대상으로 복제가 필요한 경우 사용 +- **클라우드 스토리지 최대한 활용하기** + - File Gateway: 온프레미스의 Application이 클라우드의 스토리지(File, Tape, Volume)를 활용할수 있음 + - Transfer Family: 클라우드의 스토리지를 파일서버로 사용 (SFTP, FTP) + +--- + + +- [Security Group](./Security%E2%80%85Groups.md) : instance level 보안 규칙 + - allow만 가능 / stateful +- [NACLs](./NACLs.md) : subnet level 보안 규칙 + - allow, disallow 가능 / stateles + +--- + +- API Calllog를 확인하기 위해서는 Cloudtrail을 씀 +- S3-IA 와 S3 의 처리량, 지연시간은 동일함 +- CloudWatch 의 default 수집주기는 5 분이지만, 최소 1 분까지 가능함 +- S3는 RedirectWebsite를 지원함 +- S3는 Multipartuploads를 통해 S3TransferAcceleration이 가능함 +- Glacier 에서 오브젝트를 복원할 때는 S3 API 혹은 AWS Console 을 이용해야함 +- EC2 의 SLA는 99.95% +- S3-IA 의 오브젝트 최소 사이즈는 128KB +- S3 멀티파트 업로드 파트의 크기는 5MB ~ 5GB (오브젝트 최대 크기는 5TB) +- S3에 특정 오브젝트가 반드시 CRR이 되어야 한다면 그 오브젝트의 subset에만 CRR를 허용해 줄 수 있음 (소스 설정 가능) +- S3는 HTTPS를 이용하여 SSL, HTTP Endpoint에 접근할 수 있음 +- Versioning이 활성화된 Bucket은 소유자만이 지울 수 있음 + +- CloudWatch는 데이터를 최대 2주까지 보관 +- CRR은 S3 오브젝트의 Metadata 와 ACL을 복제함 +- Multi AZ가 활성화된 상태에서는 Primary RDS 가 아닌 Standby가 Backup 실시 +- infrastructure를 다른 리전에 복사 및 배포하고 싶을 경우, Cloud formation을 사용해야함 +- StorageGateway with CachedVolume은 자주 사용되는 데이터만 Cache하고 나머지 데이터를 S3 에 저장함 +- Reserved instance를 사용하다가 나중에 다시 사용해야할 경우, 스냅샷을 떠놓고 종료해야 함 +- S3 RRS 는 99.99%의 가용성과 내구성을 보장하며, 재생성이 쉬운 데이터를 보관함 +- 각 서비스들의 설정 변경을 감독, 관리하고 싶은 경우 AWS Config를 사용하면 됨 +- Read Replica, Elastic Cache 까지 썼음에도 병목현상이 발생한다면 DB 파티셔닝 후 다수의 DB instance 로 분산하는 것이 좋음 +- ReadReplica는 동기식 복제를 지원하지 않음 (Asynchronous) +- RDS 가 Standby Replica 로 Failover 되는 요건 3 가지 : Compute Unit fail, 네트워크 연결 끊김, AZ 가용성 상실 +- EBS SSD 볼륨은 1GiB ~ 16TiB +- Read Replica 의 Multi-AZ 복사는 불가능 +- RDS가 삭제될 때, automatic backup은 자동으로 삭제되며, final snapshot이 생성되어 남음 (설정을 활성화했을 경우) +- EC2 메타 데이터 얻는 법 : curl http://169.254.169.254/latest/meta-data/public-hostname +- Read Replica 는 MySQL, PostgreSQL, MariaDB, Aurora, Oracle 서비스만 가능 +- Multi-AZ RDS Standby에서는 동기식 복제를 지원함 + +--- + +- AWS Migration Service 를 이용할 경우, 동시에 Migration 가능한 VM 의 갯수는 50개 +- CloudHSM은 SSL offload를 목적으로 사용하므로 Network Latency를 최소화하기 위해 EC2 주변에 두는 것이 좋음 +- KMS 대신 CloudHSM 을 써야하는 경우 : **VPC 고성능 암호화가 필요한 경우, 키가 사용자의 독점적 제어 하에 다른 하드웨어 내에 저장되어있음, 애플리케이션과 통합되어있음** +- S3 업로드시 edge location 에 직접 쓰는 것이 가능 (Transfer Acceleration) +- SQS는 최소 한번 메시지를 전달하지만 순서를 보장하지 못 하고, 중복전송을 할 가능성이 있음 +- IAM을 이용해 EC2의 Root Account 에 접근하는 것을 막을 수 없음(Root account는 모든 서비스에 접근 가능) + +- VPC Peering은 인접 VPC에 대한 Routing Table 필요 +- VPC Peering은 두 VPC간 두 개의 Peering을 생성할 수 없으며, 다른 Region의 VPC이 가능하고, CIDR block이 충돌하는 경우 사용 불가능 + +- SQS의 짧은 폴링 구성은 Receive Message Wait Time을 0초로 만드는 것임 + - 짧은 폴링을 쓸 경우, 처리되지 못하는 메시지가 발생할 수 있음 + - SQS Standard는 FIFO를 보장하지 않음 + - SQS queue에서 메시지를 실행하기만 하고 지우지 않으면 그 메시지가 queue로 돌아가 다시 실행됨 + - SQS 메시지의 표시되는 시간이 끝나면 다른 인스턴스에 의해 활용가능해짐 + - SQS의 메시지 보관 최대 일수는 14일 -> 앱의 문제로 메시지가 13 일간 쌓여있다하더라도 앱이 다시 시작만 한다면 바로 처리 가능 + +- S3 Object의 최소 사이즈는 0bytes임 (즉 빈 파일을 올릴 수 있음) +- AWS의 WellArchitected Framework의 구성요소는 **보안, 신뢰성, 성능, 비용최적화**임 (가용성은 없음) +- EFS를 활성화하기 위해 EC2 와 EFS 의 Security group에 포트를 열고, 리눅스에 chmod 명령어를 실행하여 권한을 줌 +- Autoscaling 생성 후 '예악된 작업'에서 예악된 시간에 정책 적용 가능 +- Error: No supported authentication methods available + - 이 에러가 뜰 경우, 로그인시 ID와 private key를 확인해야 함 + +- RDS와 Dynamo DB + - Schema가flexible한 경우 RDS를 사용 + - Scale up/down 은 RDS가 아닌 Dynamo DB 가능 + - RDS는 다음과 같은 이유로 확장성(Scaleup/down)이 떨어짐 + - 데이터를 정상화하고 디스크에 쓰려면 여러 개의 쿼리가 필요한 여러 테이블에 저장한다. + - 일반적으로 ACID 준수 트랜잭션 시스템의 성능 비용을 발생시킨다. + - 고가의 조인을 이용하여 조회 결과의 필요한 뷰를 재조립한다. + - RDBMS 의 경우, 세부적인 구현이나 성능을 걱정하지 않고 유연성을 목적으로 설계. 일반적으로 쿼리 최적화가 스키마 설계에 영향을 미치지 않지만, 정규화가 아주 중요 + + +- 온프레미스에서 사용하던 고유의 IP 를 가져오기 위해서는 **ROA(Route Origin Authorization)**을 사용하여 Amazon ASN 이 해당 주소를 광고하도록 허용하게 함 +- AWS 내부가 아닌 외부에서 AWS 에 access 할 수 있도록 하기 위해 SAML(SSO)을 연동하면 됨 +- RDS 내 보다 면밀한 모니터링을 위해서는 Enhanced Monitoring을 하는 것이 좋음 + +- Redshift + - 쿼리 큐를 정의하는 방식은 WLM(Workload management)가 있음 + - Redshift에서 클러스터와 VPC 외부의 COPY, UNLOAD 트래픽을 모니터링하기 위해서는 Enhanced VPC routing 을 사용해야함 + +- API Gateway 에는 트래픽 쏠림으로 인한 병목현상을 막아주는 Throttling Limit 기능이 존재 +- Memory utilization, disk swap utilization, disk space utilization, page file utilization, log collection 은 custom monitoring 항목 +- EC2에 에이전트를 설치하고 해당 항목을 감시해야 함 +- ELB를 쓰지 않으려면, EC2에 공인 IP를 할당하고 스크립트로 헬스체크를 하고 Failover하는 것이 좋음 + +- Cloudfront의 Signed URL: RTMP를 사용할 경우 Signed Cookie를 지원하지 않으므로 사용 + - 개별 파일에 대한 액세스를 제공하려는 경우 + - 클라이언트가 Cookie를 지원하지 않을 경우 +- Cloudfront의 Signed Cookie + - HLS 형식의 비디오 파일 전체 또는 웹 사이트의 구독자 영역에 있는 파일 전체 등 제한된 파일 여러 개에 대한 액세스 권한을 제공하려는 경우 + - 현재의 URL을 변경하고 싶지 않은 경우 +- EBS 스냅샷이 진행되는 동안 EBS의 읽기 및 쓰기는 영향을 받지 않음 + +- 온프레미스에서 이미 메시지 큐 서비스를 사용하고 있다면 MQ로 넘어가는 것이 유리함 +- 오로라에는 Endpoint가 있어 트래픽을 분산할 수 있음 + +- Lambda의 배포방법 (기존 Lambda 함수에서 새로운 Lambda 함수로) + - Canary : 트래픽이 2번에 걸쳐 이동하여 2번째 이동에서 이동할 트래픽 비율, 간격을 정할 수 있음 + - Linear : 트래픽을 동일한 비율로 이동시키며 증분간 간격이 동일하고, 비율과 간격시간을 정할 수 있음 + - All-at-once : 한 번에 이동 + +- AWS IoT Core + - AWS에 연결된 디바이스들이AWSService와 쉽게 상호작용하도록 돕는 서비스 +- EC2 에 호스팅된 Database(Raid array)를 백업시 다운타임을 최소화하는 방법 + - 모든 RAID array로 쓰기 작동을 멈춤 + - 모든 cache를 disk에 flush함 + - EC2가 RAID에 쓰기작업을 하지 않는지 확인 + - RAID에 대한 모든 디스크 관련 활동을 중지하는 단계를 수행한 후 스냅샷 생성 + +- 기본적으로 data at rest를 암호화하는 솔루션은 Storage Gateway 와 Glacier +- PFS가 지원되는 솔루션은 Cloudfront 와 ELB + +- EBS의 특징 + - EBS를 생성할 경우, 다른 AZ가 아닌 해당 AZ에만 자동으로 복제됨 + - EBS는 해당 AZ 어느 EC2든 연결할 수 있음 +- 서비스 사용중인 상태에서 volume type(gp2, io1, standard), size, IOPS를 바꿀 수 있음 +- API Gateway는 받거나 처리한 양만큼 요금을 지불하면 됨 + +- SNI(Server Name Indication) + - 여러 도메인을 하나의 IP주소로 연결하는 TLS의 확장 표준 중 하나(인증서에서 사용하는 방식) + - SNI를 사용하게 되면 하나의 웹서버에서 여러 도메인의 웹사이트를 서비스하는 경우에도 인증서를 사용한 HTTPS 활성화가 가능 + +- S3 + - S3에서 사용 가능한 Event Notification Service는 SQS, SNS, Lambda + - Standard에서 IA, Onezone IA로 가려면 30일을 기다려야 함 + - S3에서 모든 액세스 요청에 대한 자세한 정보를 확인하고 싶다면 Server Access Log를 사용 가능 + - Cloudwatch는 ec2 메모리 사용 관련 지표가 없으므로 인스턴스 내 스크립트를 통해 지표를 생성하고 Cloudwatch로 보내야 함 + - 확장 모니터링의 경우, 인스턴스 내 에이전트에서 정보를 받기 때문에 메모리 관련 정보를 얻음 + +- AWS SSO가 STS를 이용하여 권한을 발급함 + - STS: AWS Security Token Service(AWS STS)를 사용하면 AWS 리소스에 대한 액세스를 제어할 수 있는 임시 보안 자격 증명을 생성하여 신뢰받는 사용자에게 제공할 수 있다. + +- EBS volume의 백업을 자동화하기 위해서는 DLM(Data Lifecycle Manager)를 쓰는 +것이 좋음 +- Autoscaling cool down 정책 +- scaling action 이 발동되기 전에는 launch나 termination 을 하지 않음 - 기본값은 300 초임 +- cooldown은 scaleout 후 발동되는 것 + +- EC2의 경우 Region당 20개가 한계이며 별도의 요청이 있으면 그 이상의 생성이 가능 + +- 확장성과 탄력성을 위해서는 ELB 와 Route 53(Weighted Routing Policy)를 사용하는 것이 바람직 + +- non-default subnet은 public ipv4, DNS hostname 을 받지 않음 + +- Lambda monitoring metric + - Invocations : 5분 기간 동안 함수가 호출된 횟수 + - Duration : 평균, 최소, 최대 실행 시간 + - 오류 수 및 성공률(%) : 오류 수 및 오류 없이 완료된 실행의 비율 + - Throttles : 동시 사용자 한도로 인해 실행에 실패한 횟수 + - IteratorAge : 스트림 이벤트 소스에서 Lambda 가 배치의 마지막 항목을 받아 함수를 호출했을 때 해당 항목의 수명 + - DeadLetterErrors : Lambda가 배달 못한 편지 대기열에 쓰려고 시도했으나 실패한 이벤트 수 + +- VPC Peering의 기능 중 다음 2가지는 불가능함 + - Transit Gateway : A VPC가 Peering된 B VPC를 통해 C VPC로 갈 수 없음 + - EdgetoEdgeRouting : Peering을 통해 다른 서비스로의 이동이 불가능함 - 이미 생성된 Autoscaling 의 시작구성은 변경할 수 없음 + +- VPC 내 IP 대역은 `/16` ~ `/28` 사이이며, 새로운 서브넷을 생성하면 main route table에 연계됨(`172.16.0.0/16`) + +- Redshift Spectrum : S3 의 exabyte 급 데이터 처리를 가능하게 함 + +- SQS의 중복문제를 궁극적으로 해결하고 싶을 경우, SWF를 쓰는 것이 좋음 + +- SSL/TLS 인증서를 안전하게 import 할 수 있게 도와주는 서비스는 ACM, IAM cert store + +- Management Console을 통해 Glacier로 직접 업로드하는 것은 불가능 +- Spot Instance는 사용 후 첫 1시간 이내에 AWS에 의해 종료되면 요금을 부과하지 않고, 그 이후에 AWS에 의해 종료되면 초단위로 부과됨 +- Trusted Advisor 는 비용 최적화, 성능, 보안, 내결함성, 서비스 한도 등을 체크하여 사용자에게 알려줌 + +- Aurora Failover + - ReadReplica가 있는 경우 : CNAME이 정상 복제본을 가리키도록 변경되며, 해당 복제본이 승격됨 + - Read Replica가 없는 경우 : 동일한 AZ에 새 인스턴스를 하나 생성시도, 생성이 어려운 경우 다른AZ에 생성 시도 +- CloudHSM은 키 또는 자격 증명에 대한 액세스 권한을 가지지 않으므로 자격 증명을 분실할 경우 키 복구 불가 + +- API Gateway는 오로지 HTTP Sendpoints만을 게시함 + +- EBS의 스냅샷의 경우, 하나의 스냅샷을 유지하면서 변경된 부분만 증분함 (하나의 스냅샷만이 유지됨) +- 예약 인스턴스의 경우, 비용을 아끼려면 마켓플레이스에 팔거나 인스턴스를 종료시켜야 함 + +- Elastic beanstalk의 application file은 S3에 쌓이고 로그는 선택적으로 S3 혹은 Cloudwatch Log에 쌓임 + +- ENI에는 고정된 MAC주소가 지정됨 + +- 온프레미스 AD로 디렉터리 요청을 하기 위해서는 AD Connector가 필요하며 IAM Role을 생성해 권한을 정의함 +- PrivateLink 를 사용하면 VPC 를 지원하는 AWS 서비스, 다른 계정에서 호스팅하는 서비스에 연결 가능 +- 낮은 대기시간 및 높은 네트워크 처리량을 보장하는 EC2 디자인은 향상된 네트워킹과 Placement Group +- IGW는 대역폭에 대한 제한이 없음 +- 프록시 프로토콜은 L4 단계에서 실행하는 것으로 웹계층에서는 소용 없음 +- AWS Directory service 는 AD connector 를 사용하여 온프레미스 AD 사용자와 +그룹에 할당할 수 있으며 IAM 정책에 따라 사용자 액세스 제어 + +- Auto scaling 은 손상된 인스턴스가 확인될 경우 이를 종료한 후!!! 새로운 인스턴스로 교체함 + +- ALB 는 Cognito 와 통합되어 OIDC ID 공급자 인증을 지원함 +- DynamoDB 의 경우, 읽기/쓰기 용량을 결정해야 하며 Lambda, Kinesis 는 용량을 결정하지 않음 + +- 부서당 AWS 계정을 만든 상태에서 단일 Direct Connect 회로를 주문하면 가상 인터페이스를 구성하고 부서 계정번호로 태그를 걸면 가능 +- VPC Peering의 경우 기본적으로 NACL에서 거부 + +Shield is DDoS protection and also located "at the edge". GuardDuty is intelligent threat detection. That means without much configuration, it reads your CloudTrail, Config and VPC FlowLogs and notifies if something unexpected happened. That is usually for infrastructure. + +Amazon Inspector is more for applications. It's an automated security assessment service that helps improve the security and compliance of applications. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/AWS\342\200\205Managed\342\200\205Microsoft\342\200\205AD.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/AWS\342\200\205Managed\342\200\205Microsoft\342\200\205AD.md" new file mode 100644 index 00000000..0da50453 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/AWS\342\200\205Managed\342\200\205Microsoft\342\200\205AD.md" @@ -0,0 +1,114 @@ +--- +title: 'AWS Managed Microsoft AD' +lastUpdated: '2024-03-02' +--- + +AWS offers [AWS Directory Service for Microsoft Active Directory](https://aws.amazon.com/directoryservice/), also known as AWS Managed Microsoft AD, to provide a highly available and resilient Active Directory service. + +## Starting with Kerberos + +Kerberos is a subject that, on the surface, is simple enough, but can quickly become much more complex. If you wish to look further into the topic, see [the Microsoft Kerberos documentation](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc772815(v=ws.10)?redirectedfrom=MSDN). In this post, I’m just going to give you an overview of how Kerberos authentication works across trusts. + +image + +- If you only remember one thing about Kerberos and trust, **it should be referrals**. Let’s look at the workflow in Figure, which shows a user from Domain A who is logged into a computer in Domain A and wants to access an Amazon FSx file share in Domain B. For simplicity’s sake, I’ll say there is a two-way trust between Domains A and B. + +- The steps of the Kerberos authentication process over trusts are as follows: + 1. Kerberos authentication service request (KRB_AS_REQ): The client contacts the authentication service (AS) of the KDC (which is running on a domain controller) for Domain A, which the client is a member of, for a short-lived ticket called a Ticket-Granting Ticket (TGT). + The default lifetime of the TGT is 10 hours. For Windows clients this happens at logon, but Linux clients might need to run a kinit command. + + 2. Kerberos authentication service response (KRB_AS_REP): The AS constructs the TGT and creates a session key that the client can use to encrypt communication with the ticket-granting service (TGS). + At the time that the client receives the TGT, the client has not been granted access to any resources, even to resources on the local computer. + + 3. Kerberos ticket-granting service request (KRB_TGS_REQ): The user’s Kerberos client sends a KRB_TGS_REQ message to a local KDC in Domain A, specifying fsx@domainb as the target. + The Kerberos client compares the location with its own workstation’s domain. Because these values are different, the client sets a flag in the KDC Options field of the `KRB_TGS_REQ` message for `NAME_CANONICALIZE`, which indicates to the KDC that the server might be in another realm (domain). + + 4. Kerberos ticket-granting service response (KRB_TGS_REP): The user’s local KDC (for Domain A) receives the KRB_TGS_REQ and sends back a TGT referral ticket for Domain B. + The TGT is issued for the next intervening domain along the shortest path to Domain B. The TGT also has a referral flag set, so that the KDC will be informed that the KRB_TGS_REQ is coming from another realm. This flag also tells the KDC to fill in the Transited Realms field. The referral ticket is encrypted with the interdomain key that is decrypted by Domain B’s TGS. + > Note: When a trust is established between domains or forests, an interdomain key based on the trust password becomes available for authenticating KDC functions and is used to encrypt and decrypt Kerberos tickets. + + 5. Kerberos ticket-granting service request (KRB_TGS_REQ): The user’s Kerberos client sends a KRB_TGS_REQ along with the TGT it received from the Domain A KDC to a KDC in Domain B. + + 6. Kerberos ticket-granting service response (KRB_TGS_REP): The TGS in Domain B examines the TGT and the authenticator. If these are acceptable, the TGS creates a service ticket. The client’s identity is taken from the TGT and copied to the service ticket. Then the ticket is sent to the client. + For more details on the authenticator, see [How the Kerberos Version 5 Authentication Protocol Works](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc772815(v=ws.10)#the-authenticator). + + 7. Application server service request (KRB_TGS_REQ): After the client has the service ticket, the client sends the ticket and a new authenticator to the target server, requesting access. The server will decrypt the ticket, validate the authenticator, and (for Windows services), create an access token for the user based on the SIDs in the ticket. + + 8. Application server service response (KRB_TGS_REP): Optionally, the client might request that the target server verify its own identity. This is called mutual authentication. If mutual authentication is requested, the target server takes the client computer’s timestamp from the authenticator, encrypts it with the session key the TGS provided for client-target server messages, and sends it to the client. + +## The basics of trust transitivity, direction, and types + +Let’s start off by defining a trust. Active Directory trusts are **a relationship between domains**, which makes it possible for users in one domain to be authenticated by a domain controller in the other domain. Authenticated users, if given proper permissions, can access resources in the other domain. + +Active Directory Domain Services supports four types of trusts: External (Domain), Forest, Realm, and Shortcut. Out of those four types of trusts, AWS Managed Microsoft AD supports the External (Domain) and Forest trust types. This post will focus on External (Domain) and Forest trust types for this post. + +## Transitivity: What is it? + +Before We dive into the types of trusts, it’s important to understand the concept of transitivity in trusts. + +A trust that is transitive allows authentication to flow through other domains (Child and Trees) in the trusted forests or domains. In contrast, a non-transitive trust is a point-to-point trust that allows authentication to flow exclusively between the trusted domains. + +image + +The example in above figure shows a Forest trust between `Example.com` and `Example.local`. The `Example.local` forest has a child domain named Child. With a transitive trust, users from the `Example.local` and Child.`Example.local` domain can be authenticated to resources in the Example.com domain. + +If figure has an External trust, only users from `Example.local` can be authenticated to resources in the `Example.com` domain. Users from Child.`Example.local` cannot traverse the trust to access resources in the `Example.com` domain. + +## Trust direction + +- **Two-way trusts are bidirectional trusts that allow authentication referrals from either side of the trust to give users access resources in either domain or forest.** + If you look in the Active Directory Domains and Trusts area of the Microsoft Management Console (MMC), which provides consoles to manage the hardware, software, and network components of Microsoft Windows operating system, you can see both an incoming and an outgoing trust for the trusted domain. + +- **One-way trusts are a single-direction trust that allows authentication referrals from one side of the trust only**. A one-way trust is either outgoing or incoming, but not both (that would be a two-way trust). + An outgoing trust allows users from the trusted domain (`Example.com`) to authenticate in this domain (`Example.local`). + An incoming trust allows users from this domain (`Example.local`) to authenticate in the trusted domain (`Example.com`). + +image +https://www.musinsa.com/app/goods/1752735?loc%253Dgoods_rank +Let’s use a diagram to further explain this concept. Figure 3 shows a one-way trust between `Example.com` and `Example.local`. This an outgoing trust from `Example.com` and an incoming trust on `Example.local`. Users from `Example.local` can authenticate and, if given proper permissions, access resources in Example.com. Users from Example.com cannot access or authenticate to resources in `Example.local`. + +> A two-way trust is required for AWS Enterprise Apps such as Amazon Chime, Amazon Connect, Amazon QuickSight, AWS IAM Identity Center (successor to AWS Single Sign-On), Amazon WorkDocs, Amazon WorkMail, Amazon WorkSpaces, and the AWS Management Console. AWS Managed Microsoft AD must be able to query the users and groups in your self-managed AD. +> Amazon EC2, Amazon RDS, and Amazon FSx will work with either a one-way or two-way trust. + +## Trust types +In this section of the post, I’ll examine the various types of Active Directory trusts and their capabilities. + +### External trusts + +This trust type is used to **share resources between two domains**. These can be individual domains within or external to a forest. Think of this as a point-to-point trust between two domains. See [Understanding When to Create an External Trust](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/cc732859(v=ws.10)) for more details on this trust type. + +- Transitivity: Non-transitive +- Direction: One-way or two-way +- Authentication types: NTLM Only* (Kerberos is possible with caveats; see the Microsoft Windows Server documentation for details) +- AWS Managed Microsoft AD support: Yes + +### Forest trusts + +This trust type is used to share resources between two forests. This is the preferred trust model, because it works fully with Kerberos without any caveats. See Understanding When to Create a Forest Trust for more details. + +- Transitivity: Transitive +- Direction: One-way or two-way +- Authentication types: Kerberos and NTLM +- AWS Managed Microsoft AD support: Yes + +### Realm trusts + +This trust type is used to form a trust relationship between a non-Windows Kerberos realm and an Active Directory domain. See Understanding When to Create a Realm Trust for more details. + +- Transitivity: Non-transitive or transitive +- Direction: One-way or two-way +- Authentication types: Kerberos Only +- AWS Managed Microsoft AD support: No + +### Shortcut trusts + +This trust type is used to shorten the authentication path between domains within complex forests. See Understanding When to Create a Shortcut Trust for more details. + +- Transitivity: Transitive +- Direction: One-way or two-way +- Authentication types: Kerberos and NTLM +- AWS Managed Microsoft AD support: No + +--- +reference +- https://aws.amazon.com/es/blogs/security/everything-you-wanted-to-know-about-trusts-with-aws-managed-microsoft-ad/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Cognito.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Cognito.md" new file mode 100644 index 00000000..6c83aacc --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Cognito.md" @@ -0,0 +1,55 @@ +--- +title: 'Cognito' +lastUpdated: '2024-03-02' +--- + +Amazon Cognito is an identity platform for web and mobile apps. It’s a user directory, an authentication server, and an authorization service for OAuth 2.0 access tokens and AWS credentials. + +With Amazon Cognito, you can authenticate and authorize users from the built-in user directory, from your enterprise directory, and from consumer identity providers like Google and Facebook. + +## User pools + + + +- Create a user pool when you want to authenticate and authorize users to your app or API. + +- User pools are a user directory with both self-service and administrator-driven user creation, management, and authentication. Your user pool can be an independent directory and OIDC identity provider (IdP), and an intermediate service provider (SP) to third-party providers of workforce and customer identities. + +- Your organization's SAML 2.0 and OIDC IdPs bring workforce identities into Cognito and your app. The public OAuth 2.0 identity stores Amazon, Google, Apple and Facebook bring customer identities. + +- User pools don’t require integration with an identity pool. From a user pool, you can issue authenticated JSON web tokens (JWTs) directly to an app, a web server, or an API. + +### Provision of user pools + +- Membership registration and login service +- Built-in custom web UI for user login +- Support for social login via Facebook, Google, Amazon, Apple, and login via SAML and OpenID Connect (OIDC) in user pools +- Managing Users and User Profiles +- Provides security features such as multi-factor authentication (MFA), credential verification, account theft protection, and phone and email verification +- Customizing the authentication process of Cognito using AWS Lambda triggers + +## Identity pools + + + +- Set up an Amazon Cognito identity pool when you want to authorize authenticated or anonymous users to access your AWS resources. + +- An identity pool issues AWS credentials for your app to serve resources to users. You can authenticate users with a trusted identity provider, like a user pool or a SAML 2.0 service. It can also optionally issue credentials for guest users. Identity pools use both role-based and attribute-based access control to manage your users’ authorization to access your AWS resources. + +- Identity pools don’t require integration with a user pool. An identity pool can accept authenticated claims directly from both workforce and consumer identity providers. + +### An Amazon Cognito user pool and identity pool used together + +In the diagram that begins this topic, you use Amazon Cognito to authenticate your user and then grant them access to an AWS service. + +Your app user signs in through a user pool and receives OAuth 2.0 tokens. + +Your app exchanges a user pool token with an identity pool for temporary AWS credentials that you can use with AWS APIs and the AWS Command Line Interface (AWS CLI). + +Your app assigns the credentials session to your user, and delivers authorized access to AWS services like Amazon S3 and Amazon DynamoDB. + +--- + +**reference** +- https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html +- https://www.youtube.com/watch?v=SiCQtRmvQBY&t=1s \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Conformance\342\200\205Packs\342\200\205&\342\200\205Security\342\200\205Hub.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Conformance\342\200\205Packs\342\200\205&\342\200\205Security\342\200\205Hub.md" new file mode 100644 index 00000000..75048c0d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Conformance\342\200\205Packs\342\200\205&\342\200\205Security\342\200\205Hub.md" @@ -0,0 +1,19 @@ +--- +title: 'Conformance Packs & Security Hub' +lastUpdated: '2023-09-23' +--- +## Conformance Packs + +A conformance pack is a collection of AWS Config rules and remediation actions that can be easily deployed as a single entity in an account and a Region or across an organization in AWS Organizations. + +Conformance packs are created by authoring a YAML template that contains the list of AWS Config managed or custom rules and remediation actions. + +You can also use AWS Systems Manager documents (SSM documents) to store your conformance pack templates on AWS and directly deploy conformance packs using SSM document names. + +You can deploy the template by using the AWS Config console or the AWS CLI. To quickly get started and to evaluate your AWS environment, use one of the sample conformance pack templates. You can also create a conformance pack YAML file from scratch based on Custom Conformance Pack. + +## Security Hub + +AWS Security Hub provides you with a comprehensive view of your security state in AWS and helps you check your environment against security industry standards and best practices. + +Security Hub collects security data from across AWS accounts, services, and supported third-party partner products and helps you analyze your security trends and identify the highest priority security issues. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/IAM.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/IAM.md" new file mode 100644 index 00000000..9490909a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/IAM.md" @@ -0,0 +1,68 @@ +--- +title: 'IAM' +lastUpdated: '2024-03-02' +--- + +**IAM** enables you to securely control access to AWS services and resources for your AWS users, groups, and roles. Using IAM, you can create and manage fine-grained access controls with permissions, specify who can access which services and resources, and under which conditions. IAM allows you to do the following: + +## terms + +**Users** - any individual end user such as an employee, system architect, CTO, etc. + +**Groups** - any collection of similar people with shared permissions such as system administrators, HR employees, finance teams, etc. Each user within their specified group will inherit the permissions set for the group. + +**Roles** - any software service that needs to be granted permissions to do its job, e.g- AWS Lambda needing write permissions to S3 or a fleet of EC2 instances needing read permissions from a RDS MySQL database. + +**Policies** - the documented rule sets that are applied to grant or limit access. In order for users, groups, or roles to properly set permissions, they use policies. Policies are written in JSON and you can either use custom policies for your specific needs or use the default policies set by AWS. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/10ceb062-91ea-4921-a845-1932b4b272c2) + +IAM Policies are separated from the other entities above because they are not an IAM Identity. Instead, they are attached to IAM Identities so that the IAM Identity in question can perform its necessary function. + +## Key Details + +- IAM is a global AWS services that is not limited by regions. Any user, group, role or policy is accessible globally. + +- When creating you AWS account, you may have an existing identity provider internal to your company that offers Single Sign On(SSO). If this is the case, it is useful efficient, and entirely possible to reuse you existing identities on AWS. To do this, you let an IAM role be assumed by one of the Active Directories. This is because the IAM ID Fedeation feature allows an external service to have the ability to assume an IAM role. + +- IAM Roles can be assigned to a service, such as an EC2 instance, prior to its first use/creation or after its been in used/created. You can change permissions as many times as you need. This can all be done by using both the AWS console and the AWS command line tools. +- +- With IAM Policies, you can easily add tags that help define whish resources are accessible by whom. These tags are then used to control access via a particular IAM policy. For example, production an development EC2 instances might be tagged as such. This would ensure that people who should only be able to access development instances cannot access production instances. + +- AWS has classified service processes into five levels of access: `List`, `Read`, `Write`, `Permissions management`, `Tagging ` + +## Priority Levels + +- Explicit Deny: Denies access to a particular resource and this ruling cannot be overruled. + +- Explicit Allow: Allows access to a particular resource so long as there is not an associated Explicit Deny. + +- Default Deny (or Implicit Deny): IAM identities start off with no resource access. Access instead must be granted. + +## IAM Security Tools: + +### IAM Access Advisor(user level) +- Acess advisor shows service permissions granted to a user and when those services were last accessed. +- You can use this information to revise your policies. + +### IAM Credentials Report(account level) +- a report that list all your account users and the status of their various credentials. + +## best practice + +- **Lock the root user's access key** + Root access key has a most of the information related to the account, including credit cards, and payment information. So you should never create a root access key for security. + Instead, you can make a seperate admin IAM to access to necessary. If you apply an IAM policy to an individual or group, you must apply only the authentication required to perform the process. + +- **Enable MFA to authenticated user** + To enhance security, we apply multi-factor authentication (MFA) for IAM users who are granted access to critical resources or API operations. + +- **Monitoring AWS account's activity** + Use AWS's logging capabilities to increase security by checking what users have done on their accounts and the resources they have used. + The log file shows the operation time and date, the source IP of the operation, and the operation that failed due to insufficient privileges. Through these records, it is possible to check whether there is an abnormal approach. + + +--- +reference +- https://medium.com/@tkdgy0801/aws-solutions-architect-associate-certificate-study-%EA%B3%B5%EC%8B%9D-%EB%AC%B8%EC%84%9C-%EC%A0%95%EB%A6%AC-part-3-b14f3e4005b +- https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/KMS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/KMS.md" new file mode 100644 index 00000000..30bb84f8 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/KMS.md" @@ -0,0 +1,55 @@ +--- +title: 'KMS' +lastUpdated: '2024-03-02' +--- + +KMS는 Key Management Service의 약자로, 데이터를 암호화 할떄 사용되는 암호화 Key를 안전하게 관리하는데 목적을 둔 AWS의 서비스이다. + +KMS는 크게 세가지 방식으로 key 관리 서비스를 제공한다. +- AWS managed key + - AWS 서비스들이 내부적으로 발급받는 Key로, 내부적으로 자동으로 일어나게 되며 사용자가 직접적으로 제어가 불가능하다. + - 자신의 AWS 계정에 들어가면 만들어진 Key의 목록을 확인하는건 가능하다. [(참고)](https://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html) + - 모든 AWS managed keys는 1년마다 rotate된다. 이 주기는 사용자가 변경할 수 없다. +- Customer managed key(CMK) + - 사용자가 직접 key를 생성하고 관리하는 것이다. CMK에 대해서는 IAM을 통해 권한을 부여받아 제어 할 수 있다. +- Custom key stores + - AWS에서 제공하는 또 다른 key 관리형 서비스인 CloudHSM을 활용한 key 관리 형태를 의미한다. KMS 와 CloudHSM의 차이가 무엇인지는 AWS의 공식 FAQ 문서를 보면 알 수 있다. + - "AWS Key Management Service(KMS)는 암호화 키를 사용하고 관리할 수 있는 멀티 테넌트 관리형 서비스입니다. 두 서비스 모두 암호화 키를 위한 높은 수준의 보안을 제공합니다. AWS CloudHSM은 Amazon Virtual Private Cloud(VPC)에서 바로 FIPS 140-2 레벨 3 전용 HSM을 제공하며 이 HSM은 사용자가 독점적으로 제어하게 됩니다." + - 즉, KMS는 Shared 형태의 managed 서비스이며, CloudHSM은 dedicated managed 서비스로 사용자 VPC의 EC2에 HSM(Hardware Security Module)을 올려서 서비스되는 형태라고 보면 된다. 결국 CloudHSM이 조금 더 강력한 형태의 보안 안정성을 제공한다고 이해하면 될 것 같다. + +## Data keys + +Data keys는 많은 양의 데이터 및 기타 데이터 암호화 키를 포함한 여러 data를 encrypt 하는데 사용할 수 있는 대칭키이다. 다운로드할 수 없는 [KMS 대칭키](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#kms_keys)와 달리 직접 다운로드받을 수 있다. + +AWS KMS는 data key를 생성하고, 암호화하고, 복호화 한다. 하지만 KMS는 key를 저장하거나, 관리하거나, 트래킹하거나, 다른 작업을 수행하지 않는다. data key는 본인이 직접 보관해야하는데, 그 key를 더 안전하게 보관하고 싶다면 [AWS Encryption SDK](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/)를 사용할 수 있다고 한다. + +### Create data key + +데이터 키를 만들려면 GenerateDataKey 작업을 호출해야한다. + +KMS가 data key를 만들면, 바로 사용할 수 있는 plaintext 형태의 key와 해당 key를 안전하게 보관할 수 있도록 encrypt된 복사본을 반환한다. data를 복호화 하고싶다면 encrypt된 ket로 KMS에 요청을 보내면 된다. + +image + +### Encrypt data with a data key + +AWS KMS는 data key를 사용하여 데이터를 암호화할 수 없다. 하지만 OpenSSL 또는 [AWS Encryption SDK](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/) 같은 암호화 라이브러리를 사용하먄 AWS KMS 외부에서 data key를 사용할 수 있다. + +plain text data key를 사용하여 데이터를 암호화한 다음 해당 키는 가능한 빨리 메모리에서 제거하길 권장된다. 데이터 암호화를 해제하는 데 사용할 수 있도록 암호화된 데이터와 함께 암호화된 data key를 안전하게 저장할 수 있다. + +image + +### Decrypt data with a data key + +데이터를 해독하기 위해서 암호화된 data key를 [Decrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html) 작업에 전달해준다. KMS는 당신의 KMS key를 해독해서 plain text data key를 반환해준다. 해당 key를 사용해서 해독한 다음, 암호화하는 경우와 마찬가지로 해당 키는 가능한 빨리 메모리에서 제거하길 권장된다. + +image + +KMS의 전체 동작 방식을 간단히 도식화하면 다음과 같다. + +image + +--- +참고 +- https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html +- https://aws.amazon.com/ko/cloudhsm/faqs \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/KMS\342\200\205Datakey.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/KMS\342\200\205Datakey.md" new file mode 100644 index 00000000..92baeddb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/KMS\342\200\205Datakey.md" @@ -0,0 +1,102 @@ +--- +title: 'KMS Datakey' +lastUpdated: '2024-03-02' +--- + +## 1. Intro + +IAM and KMS are easily one of the most 2 important services in terms of AWS security. KMS is used extensively for cryptographic operations not only within the AWS universe itself, but also in various hybrid architectures. It is a solid service which offers a wide variety of cryptographic services and possibilities. In this article, we will compare from a security point of view, the usage of CMK's `kms:encrypt` versus data-keys. + +There is a lot to like about the CMK’s `kms:encrypt/decrypt` functionality from a security perspective. It encrypts and decrypts data purely through the AWS KMS APIs. Access to the cryptographic APIs is managed through IAM `roles/policies`, the KMS CMK’s key policy, and grants. Therefore, the actual cryptographic keys never leave the KMS service, while having full granularity on cryptographic access control. For instance, I can allow person/System A to only encrypt data from a certain IP range or VPC-endpoint, while allowing person/System B to only decrypt it after going through 2FA. + +It does not stop there: no traces of cryptographic material (whether symmetric or asymmetric) are left permanently behind in logs, debug output, terminal output, or even memory. It's nice and clean. + +A good real-world use-case would be encrypting/decrypting secrets stored in Parameter Store, all of which can be consumed by other AWS-services or backend systems, via assume-roles and STS tokens behind the scenes. + +But this is where things will quickly hit a wall: The CMK's `kms:encrypt` functionality only works for data up to a **maximum of 4KB** (I know, it is not a lot by any means). Hence, it is super useful for envelope encryption or encrypting small amounts of data (e.g, Credit Card numbers). However, for anything beyond 4KB of data, we will have to use what is referred to as Data-Keys (`kms:Generate-Data-Key`). + +## 2. Data-Keys + +Data-Keys work completely differently. Let’s say we have a backend system which needs to encrypt and decrypt large amounts of data. It will first call the `kms:generate-data-key` API, which will return a CMK-generated plaintext `"encryption/decryption"` data-key. That, in essence, is very similar to generating your own **AES-256 symmetric key via openssl** and using it to encrypt/decrypt data. The only major difference is that the data-keys are generated by the KMS service, which can always re-issue the plaintext data-key (i.e, when decryption is needed via kms:decrypt). + +Here is how the API call is made as well as the output given: + +```js +$ aws kms generate-data-key --key-id zzzzzzzz-yyyy-zzzz-yyyy-zzzzzzzzzzzz --key-spec AES_256 + +Anonymized output: +{ + "CiphertextBlob": "MADIAY54G%Tyo4r0pl.... …. …. ", + "Plaintext": "ey54rb50obR55Yujj34#24ffsq2&#$Ty0plkmr8Ze+C=", + "KeyId": "arn:aws:kms:us-west-2:000000000000:key/n78ctr2d-16f4-E4TY-7hy5-BL0DFR" +} +``` + +Now that we have received the data-key, let's explore what AWS recommends when dealing with them: +- "You must use and manage data keys outside of AWS KMS." +- "You can write your own code or use a client-side encryption library, such as the AWS Encryption SDK" +- "Use the plaintext data key (in the Plaintext field of the response) to encrypt your data outside of AWS KMS. Then erase the plaintext data key from memory." +- "Use the Decrypt operation to decrypt the encrypted data key. The operation returns a plaintext copy of the data key." + +Say we decided to go with writing our own code route to manage data-keys. Realistically, here is a list of the steps needed to do handle them securely: + +### >> For encrypting data: + +1) Clearing the terminal from output and/or files pertaining to the following command: + +```bash +$ aws kms generate-data-key +# which returns a Base64-encoded data-key +``` + +2) Once we receive the output from step one, we need to Base64 decode the data-key itself. The output of that command would give us the decoded cleartext data-key (which is actually used for encryption/decryption). We therefore need to protect the output of that command, especially if the output was directly present in the terminal, such as: + +```bash +$ echo 'plaintext_data_key_Base64_encoded' | base64 --decode +``` + +3) Similarly to step 2, if a file was used to save the decoded plaintext data-key, we would need to remove it (after the data-key was used to encrypt data), so that would be the decoded_datakey.file below: + +```bash +$ echo 'plaintext_data_key_Base64_encoded' | base64 --decode >> decoded_datakey.file +``` + +4) Removing all traces of the data-key from the OS's memory, as well as any temporary cashes, logs outputs, etc + +### >> And then for decrypting the data: + +5) Clearing the terminal output from the terminal screen for the following command: + +```bash +$ aws kms decrypt command + the encoded_encrypted_data_key that is returned +``` + +6) Doing the same process as step 2 and 3 above, following `kms:decrypt`, in terms of terminal output and/or if a file was used to save the output of the decoded plaintext data-key + +7) Repeat Step 4 + +As we can see, this can quickly become messy if we have to encrypt and decrypt data repeatedly in a production environment which handles sensitive data. The above steps can probably be automated, but luckily we have a much better option. + +## 3. AWS Encryption SDK + +The AWS encryption SDK handles a lot of these tasks cleanly and automatically for us, including key wrapping of the data-key itself and memory wiping: + +“To protect your data keys, the AWS Encryption SDK encrypts them under one or more key-encryption keys known as wrapping keys or master keys. After the AWS Encryption SDK uses your plaintext data keys to encrypt your data, it removes them from memory as soon as possible. Then it stores the encrypted data keys with the encrypted data in the encrypted message that the encrypt operations return.” + +For more info regarding the AWS Encryption SDK: +https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html + +## 4. Conclusion + +Data-Keys are necessary if we decide to use the AWS KMS service for the encryption/decryption of large amounts of data, for example within a hybrid cloud-environment. Another use-case would be the encryption and decryption of data between two different entities (or companies): one entity encrypts the data via data-keys, and the other entity decrypts it via calling `kms:decrypt` + the ciphertext-blob of the CMK which generated those data-keys. Both entities will use their own IAM roles and permissions independently of each others. + +The most important take away of this article would be not to use custom code to handle CMK data-keys. Consider it the equivalent of how writing your own "custom AES" encryption algorithm would simply be a bad idea. If using KMS + the AWS Encryption SDK is not an option because of technological or other limitations, I would reconsider the usage of CMK data-keys via KMS for other alternative solutions, where we can ensure the security of the cryptographic keys during their entire lifecycle. + +By the way, there is also a KMS API for data-keys with asymmetric encryption, namely kms:GenerateDataKeyPairs, which returns "a plaintext public key, a plaintext private key, and a copy of the private key that is encrypted under the symmetric CMK you specify". Most, if not all, of the same discussion and conclusions we discussed apply to it as well. More info on that: https://docs.aws.amazon.com/kms/latest/APIReference/API_GenerateDataKeyPair.html + +--- +reference +- [https://www.linkedin.com/pulse/aws-kms-security-review-data-keys-versus-kmsencrypt-ziyad-almbasher/](https://www.linkedin.com/pulse/aws-kms-security-review-data-keys-versus-kmsencrypt-ziyad-almbasher/) +- [https://aws.amazon.com/ko/blogs/database/column-level-encryption-on-amazon-rds-for-sql-server/](https://aws.amazon.com/ko/blogs/database/column-level-encryption-on-amazon-rds-for-sql-server/) +- [https://stackoverflow.com/questions/58200584/how-to-encrypt-data-in-aws-rds-with-aws-kms-on-the-column-level](https://stackoverflow.com/questions/58200584/how-to-encrypt-data-in-aws-rds-with-aws-kms-on-the-column-level) +- [https://docs.aws.amazon.com/pdfs/kms/latest/cryptographic-details/kms-crypto-details.pdf](https://docs.aws.amazon.com/pdfs/kms/latest/cryptographic-details/kms-crypto-details.pdf) diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/MalformedPolicyDocument.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/MalformedPolicyDocument.md" new file mode 100644 index 00000000..784de385 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/MalformedPolicyDocument.md" @@ -0,0 +1,94 @@ +--- +title: 'MalformedPolicyDocument' +lastUpdated: '2024-03-02' +--- + +```bash +aws_iam_policy.codebuild: Modifying... [id=arn:aws:iam:::policy/bot-dev-CodeBuild-policy] +Error: Error updating IAM policy arn:aws:iam:::policy/bot-dev-CodeBuild-policy: MalformedPolicyDocument: Policy document should not specify a principal. + status code: 400, request id: ... +``` + +Here is the relevant fragment of the policy document (ref: Terraform doc) I was using: + +```json +data "aws_iam_policy_document" "codebuild" { + statement { + sid = "EC2NICperms" + effect = "Allow" + actions = [ + "ec2:CreateNetworkInterfacePermission" + ] + resources = ["arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:network-interface/*"] + condition { + test = "StringEquals" + variable = "ec2:Subnet" + values = [ + var.pubnet1, + var.pubnet2 + ] + } + principals { + type = "Service" + identifiers = ["codebuild.amazonaws.com"] + } +} +``` + +This brings us to a fundamental (mis)unserstanding of [two different types of IAM policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html): + +- **Identity Based**: attached to an IAM user, group or role +- **Resource based**: attached to AWS resources like S3 buckets, SQS queues etc. + +> The Principal element specifies the user, account, service, or other entity that is allowed or denied access to a resource. + +In simple terms, if access control defines Who has access to What, then the Principal is the Who, and the Resource is the What. In our case, the principal desired is exactly what is specified in the policy document, i.e. the AWS CodeBuild service. + +An IAM role consists of a set of rules to allow or deny access to specified resources, i.e. an IAM policy and who is allowed to invoke the permissions listed in that IAM policy, i.e., a Trust relationship. + +A role is an IAM identity, therefore we cannot use “Principal” in its policy. So how do we specify that only AWS CodeBuild service has access to the action and resource specified in the above policy? As the second quoted sentence above says, through a “Trust Policy” (seen in the console under “Trust relationships”). This is the Terraform version: + +```json +data “aws_iam_policy_document” “sts_codebuild” { + statement { + sid = “STSassumeRole” + effect = “Allow” + actions = [“sts:AssumeRole”] + principals { + type = “Service” + identifiers = [“codebuild.amazonaws.com”] + } + } +} +``` + +STS is Amazon’s Simple Token Service, which generates temporary credentials that allow the specified principals to “Assume Role”, which means take on the permissions mentioned in the IAM policies attached to this role. It is somewhat like Linux’s sudo mechanism, where you have users who are able to temporarily elevate privileges, and what privileges they elevate to are also defined in the sudoers file. + +We can generate a role starting with the trust policy: + +```json +resource “aws_iam_role” “codebuild” { + name = “custom-codebuild-role” + assume_role_policy = data.aws_iam_policy_document.sts_codebuild.json +} +``` + +And then attach the IAM policies to it: + +```json +resource “aws_iam_policy” “codebuild” { + name = “custom-CodeBuild-policy” + policy = data.aws_iam_policy_document.codebuild.json +} +resource “aws_iam_role_policy_attachment” “codebuild” { + role = aws_iam_role.codebuild.name + policy_arn = aws_iam_policy.codebuild.arn +} +``` + +This fixes the “MalformedPolicyDocument” error and allows Terraform apply to run successfully, generating an IAM policy and trust relationship and an IAM role associated with them. This role can then be invoked by the principal specified (CodeBuild) in this case, to do whatever it needs to do, as long as you have included the appropriate permissions in the IAM policy document. + +--- +reference +- https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_identity-vs-resource.html +- https://stackoverflow.com/questions/70002403/malformedpolicydocument-policy-document-should-not-specify-a-principal \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Microsoft\342\200\205Active\342\200\205Directory.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Microsoft\342\200\205Active\342\200\205Directory.md" new file mode 100644 index 00000000..5c15b6d0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/Microsoft\342\200\205Active\342\200\205Directory.md" @@ -0,0 +1,43 @@ +--- +title: 'Microsoft Active Directory' +lastUpdated: '2024-03-02' +--- + +- In 2017, AWS introduced AWS Directory Service for Microsoft Active Directory, also known as AWS Microsoft AS, which is managed Microsotf Active Directory (AD) that is performance optimized for small and midsize businesses. + +- AWS Microsoft AD offers you a highly available and cost-effective primary directory in the AWS Cloud that you can use to manage users, groups, and computers. It enables you to join Amazon EC2 instances to your domain easily and supports many AWS and third-party applications and services. + +- It also can support most of the common usecases of small and midsize businesses. When you use AWS Mictosoft AD as your primary directory, you can manage access and provide SSO to cloud applications such as Microsoft Office 365. + +- If you have an existing Microsoft AD directory, you can also use AWS Microft AD as a resource forest that contains primarily computers and groups, allowing you to migrate your AD-aware applications to the AWS Cloud while using existing on-promises AD credentials. + +### What do I get? + +- When you create an AWS Microsotf AD directory, AWS deploys two Microsoft AD domain controllers powerd by Microsoft Windows Server 2013 R2 in your VPC. + +- As a managed service, AWS Microsoft AD configures directory replication, automates daily snapshots, and handles all patching and software updates. In addition, AWS Microsoft AD monitors and automatically recovers domain controllers in the event of a failure. + +- AWS Microsoft AD has been optimized as a primary directory for small and midsize businesses with the capacity to support approximately 5,000 employees. + +- With 1 GB of directory object storage, AWS Microsoft AD has the capacity to store 30,000 or more total directory objects (users, groups, and computers). AWS Microsoft AD also gives you the option to [add domain controllers](https://aws.amazon.com/blogs/security/how-to-increase-the-redundancy-and-performance-of-your-aws-directory-service-for-microsoft-ad-directory-by-adding-domain-controllers/) to meet the specific performance demands of your applications. You also can use AWS Microsoft AD as a resource forest with a [trust relationship](http://docs.aws.amazon.com/directoryservice/latest/admin-guide/tutorial_setup_trust.html) to your on-premises directory. + +### How can I use it? + +- With AWS Microsoft AD, you can share a single directory for multiple use cases. + +- For example, you can share a directory to authenticate and authorize access for .NET applications, Amazon RDS for SQL Server with Windows Authentication enabled, and Amazon Chime for messaging and video conferencing. + +- The following diagram shows some of the use cases for your AWS Microsoft AD directory, including the ability to grant your users access to external cloud applications and allow your on-premises AD users to manage and have access to resources in the AWS Cloud. + +image + +- You can enable multiple AWS applications and services such as the AWS Management Console, Amazon WorkSpaces, and Amazon RDS for SQL Server to use your AWS Microsoft AD (Standard Edition) directory. + +- When you enable an AWS application or service in your directory, your users can access the application or service with their AD credentials. + +--- +reference +- https://docs.aws.amazon.com/directoryservice/latest/admin-guide/directory_microsoft_ad.html +- https://learn.microsoft.com/ko-kr/training/modules/understand-azure-active-directory/3-compare-azure-active-directory-domain-services +- https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/get-started/virtual-dc/active-directory-domain-services-overview + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/WAF\342\200\205&\342\200\205Firewall\342\200\205Manager\342\200\205&\342\200\205Shield\342\200\205Advanced.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/WAF\342\200\205&\342\200\205Firewall\342\200\205Manager\342\200\205&\342\200\205Shield\342\200\205Advanced.md" new file mode 100644 index 00000000..80df9eb7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Security/WAF\342\200\205&\342\200\205Firewall\342\200\205Manager\342\200\205&\342\200\205Shield\342\200\205Advanced.md" @@ -0,0 +1,78 @@ +--- +title: 'WAF & Firewall Manager & Shield Advanced' +lastUpdated: '2024-03-02' +--- + +You can use AWS WAF, AWS Shield, and AWS Firewall Manager together to create a comprehensive security solution. + +- **AWS WAF**: A web application firewall that you can use to monitor web requests that your end users send to your applications and to control access to your content. + +- **AWS Shield**: It provides protection against distributed denial of service (DDoS) attacks for AWS resources, at the network and transport layers (layer 3 and 4) and the application layer (layer 7). + +- **AWS Firewall Manager**: It provides management of protections like AWS WAF and Shield Advanced across accounts and resources, even as new resources are added. + +## AWS WAF + +[AWS WAF](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html) is a web application firewall that lets you monitor the HTTP and HTTPS requests that are forwarded to your protected web application resources. You can protect the following resource types: + +- Amazon CloudFront distribution +- Amazon API Gateway REST API +- Application Load Balancer +- AWS AppSync GraphQL API +- Amazon Cognito user pool +- AWS App Runner service +- AWS Verified Access instance + +AWS WAF lets you control access to your content. Based on conditions that you specify, such as the IP addresses that requests originate from or the values of query strings, your protected resource responds to requests either with the requested content, with an HTTP 403 status code (Forbidden), or with a custom response. + +At the simplest level, AWS WAF lets you choose one of the following behaviors: + +- Allow all requests except the ones that you specify +- Block all requests except the ones that you specify +- Count requests that match your criteria +- Run CAPTCHA or challenge checks against requests that match your criteria + +Using AWS WAF has several benefits: + +- Additional protection against web attacks using criteria that you specify. You can define criteria using characteristics of web requests such as the following: +- `IP addresses` that requests originate from. +- `Country` that requests originate from. +- Values in request headers. +- Strings that appear in requests, either specific strings or strings that match regular expression (regex) patterns. +- `Length of requests`. +- Presence of SQL code that is likely to be malicious (known as `SQL injection`). +- Presence of a script that is likely to be malicious (known as `cross-site scripting`). +- Rules that can allow, block, or count web requests that meet the specified criteria. Alternatively, rules can block or count web requests that not only meet the specified criteria, but also exceed a specified number of requests in any 5-minute period. +- Rules that you can reuse for multiple web applications. +- Managed rule groups from AWS and AWS Marketplace sellers. +- Real-time metrics and sampled web requests. +- Automated administration using the AWS WAF API. + +If you want granular control over the protections that you add to your resources, AWS WAF alone might be the right choice. + +## AWS Shield + +You can use AWS WAF web access control lists (web ACLs) to help minimize the effects of a Distributed Denial of Service (DDoS) attack. + +But, for **additional protection against DDoS attacks**, AWS also provides AWS Shield Standard and [AWS Shield Advanced](https://docs.aws.amazon.com/waf/latest/developerguide/ddos-advanced-summary-capabilities.html). AWS Shield Standard is automatically included at no extra cost beyond what you already pay for AWS WAF and your other AWS services. + +AWS Shield Advanced provides expanded DDoS attack protection for your Amazon EC2 instances, Elastic Load Balancing load balancers, CloudFront distributions, Route 53 hosted zones, and AWS Global Accelerator standard accelerators + +AWS Shield Advanced incurs additional charges. Shield Advanced options and features include automatic application layer DDoS mitigation, advanced event visibility, and dedicated support from the Shield Response Team (SRT). + +If you own high visibility websites or are otherwise prone to frequent DDoS attacks, consider purchasing the additional protections that Shield Advanced provides. More information is [here](https://docs.aws.amazon.com/waf/latest/developerguide/ddos-advanced-summary-deciding.html) + +> AWS Shield Advanced will give you DDoS protection overall, and you cannot set up rate-based rules in Shield. So if you want to define a rate-based rule, use AWS WAF. + +## AWS Firewall Manager + +AWS Firewall Manager **simplifies your administration and maintenance tasks across multiple accounts and resources** for a variety of protections, including `AWS WAF`, `AWS Shield Advanced`, `Amazon VPC security groups`, `AWS Network Firewall`, and `Amazon Route 53 Resolver DNS Firewall`. + +With Firewall Manager, you set up your protections just once and the service automatically applies them across your accounts and resources, even as you add new accounts and resources. + +--- +reference +- https://docs.aws.amazon.com/waf/latest/developerguide/what-is-aws-waf.html + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/AppSync.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/AppSync.md" new file mode 100644 index 00000000..c49bfb93 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/AppSync.md" @@ -0,0 +1,489 @@ +--- +title: 'AppSync' +lastUpdated: '2024-03-02' +--- + +image + +AppSync는 AWS에서 제공하는 Managed GraphQL 서비스이다. 즉, 서버리스의 형태로 GraphQL 백엔드를 개발할 수 있는 서비스이다. + +AppSyn 를 사용하지 않고도 AWS Lambda 등을 활용하여 GraphQL 백엔드를 구축하는 것이 가능했으나, 이 경우에는 Lambda 메모리 사이즈, 콜드스타트, DataSource와의 통신, 인증된 유저 토큰 처리 등등 고민해야하고 직접 개발해야하는 것들이 더 많았다. 그러나 AppSync 를 활용하면 GraphQL 스키마를 작성하고 스키마의 각각의 필드에 대한 resolvers 를 작성하는 것만으로도 GraphQL 엔드포인트를 생성할 수 있다. + +### Resolver + +AppSync에서는 resolver를 VTL이라는 자바 기반 템플릿 언어으로 작성한다. + +AppSync에서는 request와 response할 시에 호출될 resolver를 각각 정의해줘야 한다. 따라서 각각의 필드에 대하여 request mapping template과 response mapping template이 한쌍을 이뤄 하나의 resolver를 이루게 된다. AppSync 에서 사용할 수 있는 resolver 타입은 2종류가 있다. + +image + +- **Unit Resolver** + - 간단한 형태로 구성되어있고, 한 개의 데이터소스(DynamoDB, RDS 등)와 연결시켜서 request 와 response 를 처리해주는 resolver이다. +- **Pipeline Resolver** + - 백엔드 API 를 개발하다보면 Unit resolvers로 해결되지 않는 복잡한 로직들이 많다. 예를 들면, Friendship 테이블에서 두 사람이 친구로 등록된 경우에만 해당 로직을 처리한다던지, 포인트를 사용하여 결제하려는 경우 Point 테이블에서 유저의 포인트가 충분한 경우에만 결제 로직을 처리한다던지 등 여러가지 상황들이 있다. 이럴 때 Pipeline resolvers를 활용할 수 있다. + + image + + Pipeline resolver 타입은 하나하나의 `request mapping template` + `response mapping template` 쌍을 Function으로 등록하여 사용한다. 이 Function은 다른 resolver 에서도 사용할 수 있어서, 공통적인 로직을 만들어두고 다양한 resolver에서 사용하는 패턴 등의 활용이 가능합니다. + +### Scalar Types + +알고 계시듯이 GraphQL은 쿼리언어 자체적으로 type check을 처리해준다. 그러므로 request 나 response 에서 주고받는 데이터 각각이 Int 타입인지 String 타입인지를 개발자가 고려할 필요가 없다. + +GraphQL에서 정의하고 있는 일반적인 Scalar 타입은 아래와 같다. + +- ID +- String +- Int +- Float +- Double + +이에 추가적으로 AppSync에서 제공하는 Scalar 타입을 활용하면 더욱 편리하게 API를 개발할 수 있다. + +- AWSDate +- AWSTime +- AWSDateTime +- AWSTimestamp +- AWSEmail +- AWSJSON +- AWSURL +- AWSPhone +- AWSIPAddress + +이러한 Scalar type 을 활용하여 커스텀 타입을 지정할 수 있다. + +```go +type User { + id: ID! + name: String! + phone: AWSPhone! + email: AWSEmail! + myPageUrl: AWSURL! + createdAt: AWSDateTime! +} +``` + + +## 예시 + +appsync를 생성해보자. + +우선 프로젝트 디렉토리를 구성한다. + +```bash +mkdir appsync-tutorial +cd appsync-tutorial + +touch serverless.yml +mkdir schema +mkdir resolvers +``` + +그리고 먼저 serverless-appsync-plugin를 설치해준다. + +```bash +yarn add serverless-appsync-plugin +``` + +### Schema + +이제 스키마를 작성해보자. `serverless-appsync-plugin`에서 Schema stitching 이라는 기능을 제공하고 있기때문에 모듈별로 분리해서 스키마를 작성하는 것이 가능하다. + +```bash +cd schema +touch user.graphql +touch post.graphql +touch like.graphql +``` + +3가지 스키마 파일을 생성했다면 각각 아래와 같이 스키마를 작성해준다. + +```go +// user.graphql +type User { + userId: ID! + name: String! + email: AWSEmail! + posts: [Post!]! + createdAt: AWSDateTime! +} + +input CreateInputUser { + name: String! + email: AWSEmail! +} + +type Query { + listUser: [User!]! + getUser(userId: ID!): User +} + +type Mutation { + createUser(input: CreateInputUser!): User +} + +// post.graphql +type Post { + postId: ID! + user: User! + title: String! + content: String! + likes: [Like!]! + createdAt: AWSDateTime! +} + +input CreatePostInput { + userId: ID! + title: String! + content: String! +} + +type Query { + listPost: [Post!]! + listPostByUser(userId: ID!): [Post!]! + getPost(postId: ID!): Post +} + +type Mutation { + createPost(input: CreatePostInput!): Post +} + +type Subscription { + /* + * Subscription을 사용하면 AppSync에서 Mutation이 실행될 때 관련 데이터를 클라이언트에게 + * 실시간으로 전달해준다. 여기에서는 createPost가 실행되면 onNewPostCreated라는 + * subscription을 등록한 클라이언트에게 값을 실시간으로 전달해주게 된다. + */ + onNewPostCreated: Post @aws_subscribe(mutations: ["createPost"]) +} + + +// like.graphql +type Like { + likeId: ID! + userId: ID! + postId: ID! + createdAt: AWSDateTime! +} + +type Query { + listLike(postId: ID!): [Like!]! +} + +type Mutation { + likePost(userId: ID!, postId: ID!): Like + cancelLikePost(likeId: ID!): Like +} + +type Subscription { + /* + * 특정한 포스트 ID에 대한 subscription 을 받아온다. + */ + onPostLiked(postId: ID!): Like @aws_subscribe(mutations: ["likePost"]) + onPostLikeCanceled(postId: ID!): Like @aws_subscribe(mutations: ["cancelLikePost"]) +} +``` + +### Resolvers + +resolvers 폴더로 이동하여 resolver 파일을 생성해보자. + +파일명은 serverless-appsync-plugin에서 default로 인식하는 `{type}.{field}.request.vtl`, `{type}.{field}.respose.vtl`로 지정하였고, 내용은 [이곳](https://github.com/twkiiim/appsync-tutorial)에 있는 내용을 기반으로 작성했다. + +```bash +cd ../resolvers + +# User +touch User.posts.response.vtl +touch User.posts.request.vtl +touch Query.getUser.request.vtl +touch Query.getUser.response.vtl +touch Query.listUser.request.vtl +touch Query.listUser.response.vtl +touch Mutation.createUser.request.vtl +touch Mutation.createUser.response.vtl + +# Post +touch Post.user.request.vtl +touch Post.user.response.vtl +touch Post.likes.request.vtl +touch Post.likes.response.vtl +touch Query.getPost.request.vtl +touch Query.getPost.response.vtl +touch Query.listPost.request.vtl +touch Query.listPost.response.vtl +touch Query.listPostByUser.request.vtl +touch Query.listPostByUser.response.vtl +touch Mutation.createPost.request.vtl +touch Mutation.createPost.response.vtl + +# Like +touch Query.listLike.request.vtl +touch Query.listLike.response.vtl +touch Mutation.likePost.request.vtl +touch Mutation.likePost.response.vtl +touch Mutation.cancelLikePost.request.vtl +touch Mutation.cancelLikePost.response.vtl +``` + +### serverless.yml + +위에서 본 모든 리소스들이 `serverless.yml`에 마지막으로 정리된다. + +```yaml +service: classmethod-appsync-tutorial + +frameworkVersion: ">=1.48.0 <2.0.0" + +provider: + name: aws + runtime: nodejs10.x + stage: dev + region: ap-northeast-2 + +plugins: + - serverless-appsync-plugin + +custom: + appSync: + name: AppSyncTutorialByClassmethod + authenticationType: AMAZON_COGNITO_USER_POOLS + userPoolConfig: + awsRegion: ap-northeast-2 + defaultAction: ALLOW + userPoolId: { Ref: AppSyncTutorialUserPool } + region: ap-northeast-2 + mappingTemplatesLocation: resolvers + mappingTemplates: + + # User + - + type: User + field: posts + dataSource: Post + - + type: Query + field: listUser + dataSource: User + - + type: Query + field: getUser + dataSource: User + - + type: Mutation + field: createUser + dataSource: User + + # Post + - + type: Post + field: user + dataSource: User + - + type: Post + field: likes + dataSource: Like + - + type: Query + field: listPost + dataSource: Post + - + type: Query + field: listPostByUser + dataSource: Post + - + type: Query + field: getPost + dataSource: Post + - + type: Mutation + field: createPost + dataSource: Post + + # Like + - + type: Query + field: listLike + dataSource: Like + - + type: Mutation + field: likePost + dataSource: Like + - + type: Mutation + field: cancelLikePost + dataSource: Like + + + schema: + - schema/user.graphql + - schema/post.graphql + - schema/like.graphql + + #serviceRole: # if not provided, a default role is generated + dataSources: + - type: AMAZON_DYNAMODB + name: User + description: User Table + config: + tableName: User + iamRoleStatements: + - Effect: Allow + Action: + - dynamodb:* + Resource: + - arn:aws:dynamodb:${self:provider.region}:*:table/User + - arn:aws:dynamodb:${self:provider.region}:*:table/User/* + + - type: AMAZON_DYNAMODB + name: Post + description: Post Table + config: + tableName: Post + iamRoleStatements: + - Effect: Allow + Action: + - dynamodb:* + Resource: + - arn:aws:dynamodb:${self:provider.region}:*:table/Post + - arn:aws:dynamodb:${self:provider.region}:*:table/Post/* + + - type: AMAZON_DYNAMODB + name: Like + description: Like Table + config: + tableName: Like + iamRoleStatements: + - Effect: Allow + Action: + - dynamodb:* + Resource: + - arn:aws:dynamodb:${self:provider.region}:*:table/Like + - arn:aws:dynamodb:${self:provider.region}:*:table/Like/* + + +resources: + Resources: + AppSyncTutorialUserPool: + Type: AWS::Cognito::UserPool + DeletionPolicy: Retain + Properties: + UserPoolName: AppSyncTutorialUserPool + AutoVerifiedAttributes: + - email + Policies: + PasswordPolicy: + MinimumLength: 8 + UsernameAttributes: + - email + + AppSyncTutorialUserPoolWebClient: + Type: AWS::Cognito::UserPoolClient + Properties: + ClientName: Web + GenerateSecret: false + RefreshTokenValidity: 30 + UserPoolId: { Ref: AppSyncTutorialUserPool } + + + UserTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: User + KeySchema: + - + AttributeName: userId + KeyType: HASH + AttributeDefinitions: + - + AttributeName: userId + AttributeType: S + BillingMode: PAY_PER_REQUEST + + PostTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: Post + KeySchema: + - + AttributeName: postId + KeyType: HASH + AttributeDefinitions: + - + AttributeName: postId + AttributeType: S + - + AttributeName: userId + AttributeType: S + BillingMode: PAY_PER_REQUEST + + # GSI - userId + GlobalSecondaryIndexes: + - + IndexName: userId-index + KeySchema: + - AttributeName: userId + KeyType: HASH + - AttributeName: postId + KeyType: RANGE + Projection: + ProjectionType: ALL + + LikeTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: Like + KeySchema: + - AttributeName: likeId + KeyType: HASH + AttributeDefinitions: + - AttributeName: likeId + AttributeType: S + - AttributeName: userId + AttributeType: S + - AttributeName: postId + AttributeType: S + BillingMode: PAY_PER_REQUEST + + GlobalSecondaryIndexes: + + # GSI - userId + - IndexName: userId-index + KeySchema: + - + AttributeName: userId + KeyType: HASH + - + AttributeName: likeId + KeyType: RANGE + Projection: + ProjectionType: ALL + + # GSI - postId + - IndexName: postId-index + KeySchema: + - + AttributeName: postId + KeyType: HASH + - + AttributeName: likeId + KeyType: RANGE + Projection: + ProjectionType: ALL +``` + +### 배포하기 + +```bash +serverless deploy -v +``` + +프로젝트 디렉토리에서 이 명령어를 치면 CloudFormation을 통해 배포가 시작된다. + +--- +참고 +- https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-programming-guide.html +- https://docs.aws.amazon.com/appsync/latest/devguide/real-time-data.html +- https://github.com/twkiiim/appsync-tutorial +- https://medium.com/@wesselsbernd/bff-back-end-for-front-end-architecture-as-of-may-2019-5d09b913a8ed +- https://www.youtube.com/watch?v=rjiiNpJzOYk \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/DayaSync.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/DayaSync.md" new file mode 100644 index 00000000..3ff1d9d9 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/DayaSync.md" @@ -0,0 +1,45 @@ +--- +title: 'DayaSync' +lastUpdated: '2024-03-02' +--- + +AWS DataSync is a secure, online service that automates and accelerates moving data between on premises and AWS Storage services. + +DataSync can copy data between belows: + +- Network File System (NFS) +- Server Message Block (SMB) +- Hadoop Distributed File Systems (HDFS) +- self-managed object storage +- AWS Snowcone +- Amazon Simple Storage Service (Amazon S3) buckets +- Amazon Elastic File System (Amazon EFS) file systems +- Amazon FSx for Windows File Server file systems +- Amazon FSx for Lustre file systems +- Amazon FSx for OpenZFS file systems +- Amazon FSx for NetApp ONTAP file systems + +## Use cases + +- **Migrate your data: **Quickly move file and object data to AWS. Your data is secure with in-flight encryption and end-to-end data validation. + +- **Protect your data: **Securely replicate your data into cost-efficient AWS storage services, including any Amazon S3 storage class. + +- **Archive your cold data: ** Reduce on-premises storage costs by moving data directly to Amazon S3 Glacier archive storage classes. + +- **Manage your hybrid data workflows: **Seamlessly move data between on-premises systems and AWS to accelerate your critical hybrid workflows. + +## diffence with other migration services + +|Other service|Difference| +|-|-| +|Snowball Edge|AWS DataSync is ideal for **online data** transfers. You can use DataSync to migrate active data to AWS, transfer data to the cloud for analysis and processing, archive data to free up on-premises storage capacity, or replicate data to AWS for business continuity.
AWS Snowball Edge is ideal for **offline data** transfers, for customers who are bandwidth constrained, or transferring data from remote, disconnected, or austere environments. +|AWS Storage Gateway|Use AWS DataSync to migrate existing data to Amazon S3, and subsequently use the File Gateway configuration of AWS Storage Gateway to retain access to the migrated data and for ongoing updates from your on-premises file-based applications.
You can use a combination of DataSync and File Gateway to minimize your on-premises infrastructure while seamlessly connecting on-premises applications to your cloud storage. AWS DataSync enables you to automate and accelerate online data transfers to AWS Storage services. After the initial data transfer phase using AWS DataSync, File Gateway provides your on-premises applications with low latency access to the migrated data. When using DataSync with NFS shares, POSIX metadata from your source on-premises storage is preserved, and permissions from the source storage apply when accessing your files using File Gateway. +|S3 Transfer Acceleration|If your applications are already integrated with the Amazon S3 API, and you want **higher throughput for transferring large files to S3, you can use S3 Transfer Acceleration.**
If you want to **transfer data from existing storage systems** (e.g., Network Attached Storage), or from instruments that cannot be changed (e.g., DNA sequencers, video cameras), or if you want multiple destinations, you use AWS DataSync. DataSync also automates and simplifies the data transfer by providing additional functionality, such as built-in retry and network resiliency mechanisms, data integrity verification, and flexible configuration to suit your specific needs, including bandwidth throttling, etc. +|Transfer Family|If you currently use SFTP to exchange data with third parties, AWS Transfer Family provides a fully managed **SFTP, FTPS, and FTP transfer directly into and out of Amazon S3**, while reducing your operational burden.
If you want an **accelerated and automated data transfer between NFS servers, SMB file shares, Hadoop clusters, self-managed or cloud object storage, AWS Snowcone, Amazon S3, Amazon EFS, and Amazon FSx,** you can use AWS DataSync. DataSync is ideal for customers who need online migrations for active data sets, timely transfers for continuously generated data, or replication for business continuity.| + +--- + +reference +- https://aws.amazon.com/datasync/faqs/ +- https://aws.amazon.com/datasync \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS.md" new file mode 100644 index 00000000..9cd28db6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS.md" @@ -0,0 +1,125 @@ +--- +title: 'EBS' +lastUpdated: '2023-09-26' +--- +## Elastic Block Store (EBS) + +An Amazon EBS volume is a durable, block-level storage device that you can attach to a single EC2 instance. You can think of EBS as a cloud-based virtual hard dist. You can use EBS volumes as primary storage for data that requires frequent updates, such as the system drive for an instance or storage for a database application. You can also use them for throughput-intensive applications that perform continuous disk scans. + +image + +--- + +- EBS columes persist independently from the runnign life of an EV2 instance. + +- Each EBS volume is automatically replicated within its Availability Zone to protect from both component faulure and disaster recovery (similar to Standard S3). + +- There are five differnt types of EBS Storage: + - General Purpose (SSD) + - Provisioned IOPS (SSD, built for speed) + - Throughput Optimized Hard Disk Drive (magnetic, built for lager data loads) + - cold Hard Disk Drive (magnetic, built for less frequently accessed workloads) + - Magnetic + +- EBS Volumes offer 99.999% SLA. + +- Wherever you Ec2 instance is, your volume for it is going to be in the same availability zone. + +- Amazon EBS provides the ability to create snapshots (backups) of any EBS volume and write a copy of the data in the volume to S3, where it is stored redundantly in multiple Availability Zones. + +- An EBS snapshot reflects the contents of the volume during a concrete instant in time. + +- An image (AMI) is the same this, but includes an operating system and a boot loader so it can be used to boot an instance. + +- AMIs can also be thought of as pre-baked, launchable servers. AMIs are always used when launching an instance. + +- When you provision an EC2 instance, an AMI is actually the first thing you are asked to specify. You can choose a pre-made aAMI or choose your own made from an EBS snapshot. + +- You can also use the following criteria to help pick you AMI: + - Operating System + - Architecture (32-bit or 64-bit) + - Region + - Launch permissions + - Root Device Storage (more in the relevant section below) + +- You can copy AMIs into entirely new reginos. + +- When copying AMIs to new regions, Amazon won't copy launch permissions, user-defined tags, or Amazon S3 bucket permissions from the source AMI to the new AMI. You must ensure those details are properly set for the instances in the new region. + +- You can change EBS volumes on the fly, including the size and storage type. + +## SSD vs. HDD + +- SSD-backed volumes are built for transactional workloads involving frequent read/write operations, where the dominant performance attribute is IOPS. Rule of thumb: Will your workload be IOPS heavy? Plan for SSD. + +- HHD-backed volumes are built for large streaming workloads where throughput (measured in MiB/s) is a better performance measure than IOPS. Rule of thumb: Will your workload be throughput heavy? Plan for HDD. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/628b819e-754f-4ee5-ac37-6bb7f3494f0a) + +## EBS Snapshots + +- EBS Snapshots are point in time copies of volumes. You can think of Snapshots as photographs of the disk's current state and the state of everything within it. + +- A snapshot is constriained to the region where it was created. + +- Snapshots only capture the state of change from when the last snapshot was taken. This is what is recorded in each new snapshot, not the entire state of the server. + +- Because of this, it may take some time for your first snapchot to be created. This is because the very first snapshot's change of state is the entire new volume. Only afterwards will the delta be captured because there will then be something previous to compare against. + +- EBS snapshots occur asynchronoudly which means thhat a volume can be used as normal while a snapshot is taking place. + +- When creating a snapshot for a future root device, it is considered best practices to stop the running instance where the original device is before taking the snapshot. + +- The easiest way to move an EC@ instance and a volume to another availability zone to take a snapshot. + +- When creating an image from a snapshot, if you want to deploy a different volume type for new image (e.g. General Purpose SSD -> Throughput Optimized HDD) + +- A short summary for creating copies of EC2 instances: Old instance -> Snapshot -> Image(AMI) -> New instance + +- You cannot delete a snapshot of an EBS Volume that is used as the root device of a registered AMI, If the original snapshot was deleted, then the AMI would not be able to use it as the basis to create new instances. For this reason, AWS protects you from accidentally deleting the EBS Snapshot, since it could be critical to your systems. To delete an EBS Snapshot attached to a registered AMI, first remove the AMI, then the snapshot can be deleted. + +## EBS Root Device Storage + +- All AMI root voulumes (where teh EC2's OS is installed) are of two types: EBS-backed or Instance Store-backed. + +- When you delete an EC2 instance that was using an Instance Store-backed root volume, your root volume will also be deleted. Any additional or secondary velumes will persist however. + +- If you use an EBS-backed root volume, the root volume will not be terminated with its EC2 instance when the instance is brought offline. EBS-backed volumes are not temporary storage devices like Instance Store-backed volumes. + +- EBS-backed Volumes are launched from an AWS EBS snapshot, as the name implies. + +- Instance Store-backed Volumes are launched from an AWS S3 stored template. They are ephemeral, so be carefuel when shutting down an instance. + +- Secondary instance stores for an instance store backed root device must be installed during the original provisioning of the server. You cannot add more after the fack. However, you can add EBS volumes to the came instance after the server's creation. + +- With these drawbacks of Insstance Store volumes, why pick one? Because they have a very high IOPS reate. So while an Instance Store can't provide data persistence, it can provide mush higher IOPS compared to network attached storage like EBS. + +- Further, Instance stores are ideal for temporary storage of information that changes frequently such as buffers, caches, scratch data, and other temporary content, or for data that is replicated across a fleet of instances, such as a load-balanced pool of web servers. + +- When to use one over the other? + - Use EBS for DB data, critical logs, and application configs. + - Use instance storage for in-process data, non-critical logs, and transient application state. + - Use S3 for data shared between systems like input data sets and processed results, or for static data needed by each new system when launched. + +## EBS ncryption + +- EBS encryption offers a straight-forward encryption solution for EBS resources that doesn't require you to build, maintain, and sercure your own key management infrastructure. + +- it uses AWS key Management Service ([KMS](../Security/KMS.md)) customer master keys(CMK) when creating encrypted volumes and snapshots. +- > Amazon EBS sends a GenerateDataKeyWithoutPlaintext request to AWS KMS, specifying the CMK that you chose for volume encryption. + +- You can encrypt both the root device and secondary volumes of an EC2 instance. When you create an encrypted EBS volume and attach it to a supported instance type, the following types of data are encrypted: + - Data at rest inside the volume + - All data moving between the volume and the instance + - All snapshots created from the volume + - All volumes created from those snapshots + +- EBS encrypts your volume with a data key using the AES-256 algorithm. +- Snapshots of encrypted snapthoys are also encrypted. You can only share unencrypted snapshots. +- The old way of encrypting a root device was to create a snapshot of a procisioned EC2 instance. While making a copy of that snapshot, you then enabled encryption during the copy's creation. Finally, once the copy was encryptes, you then created an AMI from the encrypted copy and used to have an EC2 instance with encryption on the root device. Because of how complex this is, you can now simply encrypt root devices as part of the EC2 provisioning options. + +--- +reference +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html +- https://stackoverflow.com/questions/62805025/aws-cmk-vs-data-key-clarification-question +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html?icmpid=docs_ec2_console \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS\342\200\205gp2\342\200\205vs\342\200\205gp3.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS\342\200\205gp2\342\200\205vs\342\200\205gp3.md" new file mode 100644 index 00000000..1403b69a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS\342\200\205gp2\342\200\205vs\342\200\205gp3.md" @@ -0,0 +1,27 @@ +--- +title: 'EBS gp2 vs gp3' +lastUpdated: '2024-03-02' +--- + +- The main difference between gp2 and gp3, however, is gp3’s decoupling of IOPS, throughput, and volume size. This flexibility – the ability to configure each piece independently – is where the savings come in. + +- On the opposite end of the spectrum, gp2 is quite inflexible. Sizing a gp2 volume involves considering both the storage and throughput requirements simultaneously, as volume performance has a baseline of 3 IOPS / GB, at a minimum of 100 IOPS. In other words, gp2 volume performance scales in proportion to volume size, until the 16,000 limit. As a result, gp2 volumes greater than 1TB are often oversized relative to the amount of data to be stored in order to increase the throughput. + +- That’s why you’re probably paying too much for gp2. The extra TBs of storage capacity – and the money spent to enable it – are essentially wasted, as they were only necessary to increase the IOPS limit. Fortunately, there’s a better way: paying for only what you need with gp3. + +|Volume Type|gp3|gp2| +|-|-|-| +|Short Description|Lowest cost SSD volume that balances price performance for a wide variety of transactional workloads|General Purpose SSD volume that balances price performance for a wide variety of transactional workloads| +|Durability|99.8% - 99.9% durability|99.8% - 99.9% durability| +|Use Cases|Virtual desktops, medium sized single instance databases such as Microsoft SQL Server and Oracle, latency sensitive interactive applications, boot volumes, and dev/test environments|Virtual desktops, medium sized single instance databases such as Microsoft SQL Server and Oracle, latency sensitive interactive applications, boot volumes, and dev/test environments| +|Volume Size|1 GB - 16 TB|1 GB - 16 TB| +|Max IOPS/Volume|16,000|16,000| +|Max Throughput/Volume|1,000 MB/s|250 MB/s| +|Max IOPS/Instance|260,000|260,000| +|Max Throughput/Instance|10,000 MB/s|7,500 MB/s| +|Price|$0.08/GB-month
3,000 IOPS free and
$0.005/provisioned IOPS-month over 3,000;
125 MB/s free and
$0.04/provisioned MB/s-month over 125|$0.10/GB-month| + +--- +reference +- https://aws.amazon.com/ebs/general-purpose/?nc1=h_ls +- https://cloudfix.aurea.com/blog/migrate-gp2-to-gp3-better-performance-lower-costs/#:~:text=The%20main%20difference%20between%20gp2,spectrum%2C%20gp2%20is%20quite%20inflexible. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS\342\200\205vs\342\200\205Instance\342\200\205Store.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS\342\200\205vs\342\200\205Instance\342\200\205Store.md" new file mode 100644 index 00000000..3dea7e95 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EBS/EBS\342\200\205vs\342\200\205Instance\342\200\205Store.md" @@ -0,0 +1,41 @@ +--- +title: 'EBS vs Instance Store' +lastUpdated: '2023-09-26' +--- +## Characteristics + +- **Instance Store:** + - **Ephemeral storage:** Data stored in Instance Store volumes is tied to the lifecycle of the EC2 instance. If the instance is stopped, terminated, or experiences a failure, the data is lost. + - **High I/O performance:** Instance Store provides high I/O performance and low latency since the storage is directly attached to the physical host of the EC2 instance. + - **Instance-dependent:** Instance Store volumes are specific to the instance and cannot be detached or reattached to other instances. + +- **EBS:** + - **Persistent storage:** Data stored in EBS volumes persists independently of the EC2 instance. It remains even if the instance is stopped or terminated. + - **Various volume types:** EBS offers different volume types with varying performance characteristics and cost considerations. + - **Flexible attachment**: EBS volumes can be detached from one instance and attached to another, providing flexibility and easy data migration. + +## Use case + +- **Instance Store:** + - Temporary data or scratch space: Instance Store is ideal for temporary storage needs, such as caching, temporary files, or processing large datasets that can be recreated if lost. + - High-performance workloads: Applications requiring high IOPS, low latency, and high-performance storage, such as database workloads, real-time analytics, or caching systems, can benefit from Instance Store. + +- **EBS:** + - Data persistence: EBS is suitable for applications that require data persistence, durability, and the ability to survive instance failures. + - General-purpose workloads: EBS volumes, such as General Purpose SSD (gp2/gp3), offer a good balance of price and performance for a wide range of applications, including web servers, development environments, and small-to-medium databases. + - High-performance and predictable workloads: Provisioned IOPS SSD (io1/io2) volumes are ideal for applications requiring consistent and predictable I/O performance, such as large databases, data warehousing, and transactional workloads. + - Cost-effective storage: Throughput Optimized HDD (st1) and Cold HDD (sc1) volumes are suitable for applications that prioritize cost savings over high IOPS, such as big data processing, log processing, and infrequently accessed data storage. + +## Pricing + +When selecting between Instance Store and EBS, consider the requirements of your application. If you require persistent storage or need data to survive instance failures, EBS is the recommended option. However, if your application can tolerate the temporary nature of storage and doesn't require data persistence, Instance Store can be a cost-effective choice. + +## I/O performance + +When choosing between Instance Store and EBS for I/O performance, consider the specific needs of your application: + +- If you require high-performance storage with low latency and high IOPS, and can tolerate the temporary nature of the storage, Instance Store is often the preferred choice. + +- If you need persistent storage with different levels of I/O performance and the ability to survive instance failures, EBS volumes, particularly Provisioned IOPS SSD or General Purpose SSD volumes, would be more suitable. + +Remember to consider the specific requirements of your workload, expected I/O patterns, and any budgetary constraints when selecting the appropriate storage option \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EFS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EFS.md" new file mode 100644 index 00000000..b696b6d7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/EFS.md" @@ -0,0 +1,60 @@ +--- +title: 'EFS' +lastUpdated: '2024-03-02' +--- + +EFS provides a simple and fully managed elastic NFS file system for use within AWS. EFS automatically and instantly scales you file system storage capacity up or down as you add or remove files without disrupting your application. + +--- + +- In EFS, storage capacity is elastic (grows and shrinks automatically) and its size changes based on adding or removing files. + +- While EBS mounts on EBS volume to one instance, you can attach one EFS volume across multiple EC2 instances. + +- The EC2 instances communicate to the remote file system using the NFSv4 protocol. This makes it required to open up the NFS port for our security group (EC2 firewall rules) to allow inbound traffic on that port. + +- Within an EFS volume, the mount target state will let you know what instances are available for mounting + +- With EFS, you only pay for the storage that you use so you pay as you go. No pre-provisioning required. + +- EFS can scale up to the petabytes and can support thousands of concurrent NFS connections. + +- Data is stored across multiple AZs in a region and EFS ensures read after write consistency. + +- It is best for file storage that is accessed by a fleet of servers rather than just one server + +--- + +EFS 성능 모드 +- Provisioned Throughput mode: + - 고정된 처리량을 가지는 EFS 파일 시스템 제공. + - 이 모드에서는 파일 시스템이 필요로하는 I/O 처리량을 사전에 선언하면 파일 시스템의 처리량이 일정하게 유지된다. (예약한 처리량을 통해 일관된 성능을 제공) + - 파일 시스템 크기에 관계없이 특정 처리량을 예약할 수 있다. + - 파일 시스템에 대한 트래픽이 일시적으로 증가하는 경우에도 성능을 일관되게 유지할 수 있다. + - 사용례 + - 높은 처리량이 필요한 애플리케이션: 대량의 동시 사용자 요청을 처리해야 하는 웹 서버 애플리케이션 등과 같이 높은 처리량이 필요한 경우, Provisioned Throughput mode를 사용하여 일관된 성능을 유지할 수 있다. + - 대용량 데이터 분석: 대용량의 데이터를 읽고 분석해야 하는 데이터 분석 애플리케이션에서도 Provisioned Throughput mode를 사용하여 원활한 데이터 처리를 보장할 수 있다. + +- Bursting Throughput mode: + - first-burst 처리량을 가지는 EFS 파일 시스템을 제공. + - 파일 시스템의 처리량은 평상시에는 크레딧을 소모하지 않고 first-burst 상태로 유지됩니다. + - 파일 시스템에 대한 트래픽이 증가하면 크레딧을 사용하여 일시적으로 더 높은 처리량을 제공할 수 있다. + - 파일 시스템의 크레딧이 소진되면 처리량이 평상시 수준으로 돌아간다. + - 사용례 + - 저렴한 비용으로 가변적인 트래픽 처리: 일정 기간 동안 예측할 수 없는 트래픽 패턴이 있는 애플리케이션에서는 Bursting Throughput mode를 사용하여 가변적인 트래픽에 대한 비용 효율적인 파일 스토리지를 구축할 수 있습니다. + - 개발 및 테스트 환경: 개발 및 테스트 환경에서는 일시적인 트래픽 증가가 있을 수 있으므로 Bursting Throughput mode를 사용하여 필요한 처리량을 제공할 수 있습니다. + +- Max I/O mode: + - 매우 높은 처리량이 필요한 애플리케이션에 사용된다. 최대 100MB/s의 처리량이 제공된다. + +- Cold mode: + - 드문 액세스 또는 액세스 비율이 낮은 파일 시스템에 사용한다. 파일 시스템의 데이터는 자주 액세스되지 않으며, 액세스할 때 전체 데이터를 로드해야 한다. + +- Infrequent Access mode: + - 일반적으로 액세스 비율이 낮은 데이터를 위한 비용 효율적인 스토리지이다. 파일 시스템의 데이터는 자주 액세스되지 않으며, 액세스할 때 전체 데이터를 로드해야 한다. + + +--- +reference +- https://aws.amazon.com/efs/ +- https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/AmazonEFS.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/FSx.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/FSx.md" new file mode 100644 index 00000000..29ed03c8 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/FSx.md" @@ -0,0 +1,48 @@ +--- +title: 'FSx' +lastUpdated: '2024-03-02' +--- + +Amazon FSx for windows File Server provides a fully managed native Microsoft File System. + +- With FSx for Windows, you can easily move your Windows-based applications that require file storage in AWS. + +- It is built on Windows Server and exists solely for Microsoft-based applications so if you need SMB-based file storage then choose FSx. + +- FSx for Windows also permits connectivity between on-premise servers and AWS so those same on-premise servers can make use of Amazon FSx too. + +- You can use Microsoft Active Directory to authenticate into the file system. + +- Amazon FSx for Windows provides multiple levels of security and compliance to help ensure your data is protected. Amazon FSx automatically encrypts your data at-rest and in-transit. + +- You can access Amazon FSx for Windows from a variety of compute resources, not just EC2. + +- You can deploy your Amazon FSx for Windows in a single AZ or in a Multi-AZ configuration. + +- You can use SSD or HDD for the storage device depending on your requirements. + +- FSx for Windows support daily automated backups and admins in taking backups when needed as well. + +- FSx for Windows removes duplicated content and compresses common content. By default, all data is encrypted at rest. + + +# Amazon FSx for Lustre + +Amazon FSx for Lustre makes it easy and cost effective to launch and run the open source Lustre file system for high-performance computing applications. With FSx for Lustre, you can launch and run a file system that can process massive data sets at up to hundreds of gigabytes per second of throughput, millions of IOPS, and sub-millisecond latencies. + +- FSx for Lustre is compatible with the most popular Linux-based AMIs, including Amazon Linux, Amazon Linux 2, Red Hat Enterprise Linux (RHEL), CentOS, SUSE Linux and Ubuntu. + +- Since the Lustre file system is designed for high-performance computing workloads that typically run on compute clusters, choose EFS for normal Linux file system if your requirements don't match this use case. + +- FSx Lustre has the ability to store and retrieve data directly on S3 on its own. + +## Amazon FSx for NetApp ONTAP + +- Amazon FSx for NetApp ONTAP is a storage service that allows you to launch and run fully managed NetApp ONTAP file systems in the AWS Cloud. It provides the familiar features, performance, capabilities, and APIs of NetApp file systems with the agility, scalability, and simplicity of a fully managed AWS service. + +- Amazon FSx for NetApp ONTAP offers high-performance file storage that’s broadly accessible from Linux, Windows, and macOS compute instances via the industry-standard NFS, SMB, and iSCSI protocols. It enables you to use ONTAP’s widely adopted data management capabilities, like snapshots, clones, and replication, with the click of a button. In addition, it provides low-cost storage capacity that’s fully elastic and virtually unlimited in size, and supports compression and deduplication to help you further reduce storage costs. + +--- +reference +- https://aws.amazon.com/fsx/ +- https://aws.amazon.com/ko/fsx/windows/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/S3.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/S3.md" new file mode 100644 index 00000000..cc6abb07 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/S3.md" @@ -0,0 +1,178 @@ +--- +title: 'S3' +lastUpdated: '2024-03-02' +--- + +S3 provides developers and IT teams with secure, durable, and highly-scalable object storage. Object storage, as opposed to block storage, is a general term that refers to data composed of three things: + +1. the data that you want to store +2. an expandable amount of metadata +3. a unique identifier so that the data can be retrieved + +This makes it a perfect candidate to host files or directories and a poor candidate to host databases or operating systems. The following table highlights key differences between object and block storage: + +||object storage|blockstorage| +|-|-|-| +|Performance|Performs best for big content and high stream throughput|Strong performance with database and transactional data| +|Geography|Data can be stored across multiple resions|The greater the distance vetween storage and application, the higher the latency| +|Scalability|Can scale infinitely to petabytes and beyond|Addressing requirements limit scalability| +|Analytics|Customizable metadata allows data to be easily orginized and retrieved|No metadata| + +Data uploaded into S3 is spread across multiple files and facilities. The files uploaded into S3 have an upper-bound of 5TB per file and the number of files that can be uploaded is virtually limiless. S3 buckets, which contain all files, are names in a universal namespace so uniqueness is required. All successful uploads will return an HTTP 200 response. + +--- + +- Objects (regular files or directories) are stored in S3 with a key, value, version ID, and metadata. They can also contain torrents and sub resources for access controll lists which are basically permissions for the object inself. + +- The data consistency model for S3 ensures immediate read access for new objects after the initial PUT requests. These new objects are introduced into AWS for the first time and thus do not need to be updated anywhere so thet are availavle immediately. + The data consistency for S3 also ensures immediate read access for PUTS and DELETES of already existing objects, since [december 2020](https://aws.amazon.com/fr/about-aws/whats-new/2020/12/amazon-s3-now-delivers-strong-read-after-write-consistency-automatically-for-all-applications/) + > Strong read-after-write consistency helps when you need to immediately read an object after a write; for example, when you often read and list immediately after writing objects. High-performance computing workloads also benefit in that when an object is overwritten and then read many times simultaneously, strong read-after-write consistency provides assurance that the latest write is read across all reads. These applications automatically and immediately benefit from strong read-after-write consistency. The strong consistency of S3 also reduces costs by removing the need for extra infrastructure to provide strong consistency. + +- S3 comes with the following main features: + 1. tiered storage and pricing variability + 2. lifecycle management to expire older content + 3. versinoing for version control + 4. encryption for privacy + 5. MFA deletes to prevent accidental or malicious removal of content + 6. acess control lists & bucket policies to secure the data + +- S3 charges by: + 1. storage size + 2. number of requests + 3. storage management pricing(known ad tiers) + 4. data transfer pricing (objects leaving/entering AWS via the internet) + 5. transfer acceleration (an optional speed increase for moving objects via Cloudfront) + 6. cross region replication (more HA than offered by default) + +- Bucket policies secure data at the bucket level while access control lists secure data at the more granular object level. + +- By default, all newly created buckets are private + +- S3 can be configured to create access logs which can be shipped into another bucket in the current account or even a separate account all together. This makes it easy to monitor who accesses what inside S3. + +- There are 3 Different ways to chare S3 buckets across AWS accounts: + 1. For programmatic access only, use IAM & Bucket Policies to chare entire buckets + 2. For progremmatic access only, use ACLs & Bucket Policies to chare objects + 3. For access via the console & the terminal, use cross-account IAM roles + +- S3 is a great candidate for static website hosting. for static website hosting. When you enable static website hosting for S3 you need both an `index.html` file and an `html` file. Static website hosting creates a website endpoint that can be accessed via the internet. + +- When you upload new files and have versioning enabled, they will not inherit the properties of the previous version. + +## S3 Storage Classes: + +**S3 Standard** - 99.99% availability and 11 9s durability. Data in this class is stored redundantly across multiple devices in multiple devices in multiple facilities and is designed to withstand the failure of 2 concurrent data centers. + +**S3 Infrequently Accessed(IA)** - For data that is needed less often, but when it is needed the data should be available quickly. The storage fee is cheaper, but you are charged for retrieval. + +**S3 One Zone Infrequently Accessed (an improvement of the legacy RRS / Reduced Redundancy Storage)** - For when you want the lower costs of IA, but do not require high availability. This is even cheaper because of the lack of HA. + +**S3 Intelligent Tiering** - Cloud storage that automatically reduces your storage costs on a granular object level by automatically moving data to the most cost-effective access tier based on access frequency, without performance impact, retrieval fees, or operational overhead. + +S3 Intelligent-Tiering monitors access patterns and automatically moves objects that have not been accessed to lower-cost access tiers. + +**S3 Glacier** - low-cost storage class for data archiving. This class is for pure storage purposes where retrieval isn't needed often at all. Retrieval times range from minutes to hours. There are differing retrieval metohd depending on how acceptable the default retrieval times are for you: + +``` +Expedited: 1 - 5 minutes, but this option is the most expensive. +Standard: 3 - 5 hours to restore. +Bulk: 5 - 12 hours. This option has the lowest cost and is good for a large set of data. +``` + +**S3 Deep Glacier** - The lowest const S3 storage where retrieval can take 12 hours. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/37124a95-e633-4b59-8749-73edd5bfe181) + +## Encryption + +S3 data can be encrypted both in transit and at rest. + +**Encryption In Transit:** When the traffic passing between one endpoint to another is indecipherable. Anyone eavesdropping between server A and server B won't be able to make sense of the information passing by. Encryption in transit for S3 is always achieved by SSL/TLS + +**Encryption At Rest:** When the immobile data sitting inside S3 is encrypted. If someone breaks into a server, they still won't be done either on the server-side or the client-side. The server-side is when S3 encrypts your data as it is being written to disk and decrypts it when you access it. The client-side is when you personally encrypt the object on you own and then upload it into S3 afterward. + +You can encrypt on the AWS supported server-side in the following ways: + +- **S3 Managed Keys / SSE - S3 (server side encryption S3)** - when Amazon manages the encryption and decryption keys for you automatically. In this scenario, you concede a little control to Amazon in exchange for ease of use. +- **AWS Key Management Service / SSE - KMS** - when Amazon and you both manage the encryption and decryption keys together. +- **Server Side Encryption w/ customer provided keys / SSE** - C - when I give Amazon my own keys that I manage. In this scenario, you concede ease of use in exchange for more control. + +## Versioning + +- When versioning is enabled, S3 stores all versions of an object including all writes and even deletes. +- It is a great feature for implicitly backing up content and for easy rollbacks in case of human error. +- It can be thought of as analogous to Git. +- Once versioning is enabled on a bucket, it cannot be disabled - only suspended. +- Versioning integrates w/ lifecycle rules so you can set rules to expire or migrate data based on their version. +- Versioning also has MFA delete capability to provide an additional layer of security. + +## Lifecycle Management + +- Automates the moving of objects between the different storage tiers. +- Can be used in conjunction with versioning. +- Lifecycle rules can be applied to both current and previous versions of an object. + +## Cross Region Replication + +- Cross resion replicatino only work if versioning is enabled. +- When cross region replication is enabled, no pre-existing data is transferred. Only new uploads into the original bucket are replicated. All subsequent updates are replicated. +- When you replicate the contents of one bucket to another, you can actually change the ownership of the content if you want. You can also change the storage tier of the new bucket with the replicated content. +- When files are deleted in the original bucket (via a delete marker as versioning prevents true deletions), those deletes are not replicated. +- [Cross Region Replication Overview](https://aws.amazon.com/solutions/cross-region-replication-monitor/) +- [What is and isn’t replicated such as encrypted objects, deletes, items in glacier, etc.](https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-what-is-isnot-replicated.html#replication-what-is-not-replicated) + +## Transfer Acceleration + +- Transfer acceleration makes use of the CloudFront network by sending or receiving data at CDN points of presence (called edge locations) rather than slower uploads or downloads at the origin. +- This is accomplished by uploading to a distinc URL for the edge location instead of the bucket itself. This is then transferred over the AWS network backbone at a mush faster speed. +- [You can test transfer acceleration speed directly in comparison to regular uploads.](https://s3-accelerate-speedtest.s3-accelerate.amazonaws.com/en/accelerate-speed-comparsion.html) + +## ElasticSearch + +- If you are using S3 to store log files, ElasticSearch provides full search capabilities for logs and can be used to search through data stored in an S3 bucket. +- You can integrate you ElesticSearch domain with S3 and Lambda. In this setup, any new logs received by S3 will trigger an event notification to Lambda, whish in turn will then run your application code on the new log data. After your code finishes processing, the data will be streamed into you ElasticSearch domain and be available for observation. + +## Maximizing S3 Read/Write Performance + +- If the request rate for reading and writing objects to S3 is extremely high, you can use sequential date-based naming for you prefixes to improve performance. Earlier versions of the AWS Docs also suggested to use hash keys or random strings to prefix the object's name. In such cases, the partitions used to store the objects will be better distributed and therefore will allow better read.write performance on you objects. + +- If your S3 data is receiving a high number of GET request from users, you should consider uding Amazon CloudFront for performance optimizatino. + By intergrating CloudFront with S3, you can distribute content via CloudFront's cache to your users for lower latency and a higher data transfer rate. This also has the added bonus of sending fewer direct requests to S3 which will reduce costs. For example, suppose that you have a few objects that are very popular. CloudFront fetches those objects from S3 and caches them. CloudFront can then serve future requests for the objects from its cache, reducing the total number of GET requests it sends to Amazon S3. + +- [More information on how to ensure high performance in S3](https://docs.aws.amazon.com/AmazonS3/latest/dev/request-rate-perf-considerations.html) + +## Server Access Logging + +- Server access logging provides detailed records for the requests that are made to a bucket. Server access logs are useful for many applications. For example, access log information van be useful in security and access audits. It can also help you learn about your customer base and better understand your Amazon S3 bill. +- By default, logging is disabled. When logging is enabled, logs are saved to a bucket in the same AWS Region as the source bucket. +- Each access log record provides details about a single access request, such as the requester, bucket name, request time, request action, response status, and an error code, if relevant. +- It works in the following way: + - S3 periodically collects access log records of the bucket you want to monitor + - S3 then consolidates those records into log files + - S3 finally uploads the log files to your secondary monitoring bucket as log objects + +## Multipart Upload +- Multipart upload allows you to upload a single object as a set of parts. Each part is a contignous porting of the object;s data. You can upload these object parts independently and in any order. +- Multipart uploads are recommended for files over 100MB and is the only way to upload files over 5GB. It achieves functionality by uploading your data in parallel to boost efficiency. +- If transmission of any part fails, you can retransmit that part without affecting other parts. After all parts of your object are uploaded, Amazon S3 assembles these part and creates the object. +- Possible reasons for why you would want to use Multipart upload: + - Multipart upload delivers the ability to begin an upload before you know the final object size. + - Multipart upload delivers improved throughput. + - Multipart upload delivers the ability to pause and resume object uploads. + - Multipart upload delivers quick recovery from network issues. +- You can use an AWS SDK to upload an object in parts. Alternatively, you can perform the same action via the AWS CLI. +- You can also parallelize downloads from S3 using byte-range fetches. If there's a failure during the download, the failure is localized just to the specific byte range and not the whole object. + +## Athena + +- Athena is an interactive query service which allows you to interact and query data from S3 using standard SQL commands. This is beneficial for programmatic querying for the average developer. It is serverless, requires no provisioning, and you pay per query and per TB scanned. You basically turn S3 into a SQL supported database by using Athena. + +- Example use cases: + - Query logs that are dumped into S3 buckets as an alternative or supplement to the ELK stack + - Setting queries to run business reports based off of the data regularly entering S3 + - Running queries on click-stream data to have further insight of customer behavior + +--- +reference +- https://aws.amazon.com/s3/ +- https://aws.amazon.com/s3/faqs/?nc=sn&loc=7 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/S3\342\200\205Glacier\342\200\205Vault\342\200\205Lock.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/S3\342\200\205Glacier\342\200\205Vault\342\200\205Lock.md" new file mode 100644 index 00000000..835aec30 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/S3\342\200\205Glacier\342\200\205Vault\342\200\205Lock.md" @@ -0,0 +1,108 @@ +--- +title: 'S3 Glacier Vault Lock' +lastUpdated: '2024-03-02' +--- + +- S3 Glacier Vault Lock helps you to easily deploy and enforce compliance controls for individual S3 Glacier vaults with a Valut Lock policy. + +- You can specify controls such as "write once read many" (WORM) in a Vault Lock policy and lock the policy from future edits. + +> After a Vault Lock policy is locked, the policy can no longer be changed or deleted. + +- S3 Glacier enforces the controls set in the Vault Lock policy to help achieve your compliance objectives. For example, you can use Vault Lock policies to enforce data retention. You can deploy a variety of compliance controls in a [Vault Lock policy](https://docs.aws.amazon.com/amazonglacier/latest/dev/vault-lock-policy.html) by using the AWS IAM policy language. + +- A Vault Lock policy is different from a vault access policy. Both policies govern access controls to your vault. However, a Vault Lock policy can be locked to prevent future changes, which provides strong enforcement for your compliance controls. + +- You can use the Vault Lock policy to deploy regulatory and compliance controls, whice typically require tight controls on data access. In contrast, you use a vault access policy to implement access controls that are not compliance related, temporary, and subject to frequent modification. + +- You can use Vault lock and vault access policies together. For example, you can implement time-based data-retention rules in the Vault Lock policy (deny deletes), and grant read access to designated third parties or your business partners (allow reads) in your vault access policy. + +- Locking a vault takes two steps: + 1. Intiate the lock by attaching a Vault Lock poly to your vault, which sets the lock to an in-progress state and returns a lock ID. While the policy is in the in-progress state, you have 24 hours to validate you Vault Lock policy before the lock ID expires. To prevent your vault from exiting the in-progress state, you must complete the Vault Lock process within these 24 hours. Otherwise, your Vault Lock policy will be deleted. + 2. Use the lock ID to complete the lock process. If the Vault Lock policy doesn't work as expected, you can stop the Vault Lock process and restart from the beginning. + +## Policy Example + +### Deny Deletion Permissions for Archives Less Than 365 Days Old + +Suppose that you have a regulatory requirement to retain archives for up to one year before you can delete them. You can enforce that requirement by implementing the following Vault Lock policy. The policy denies the `glacier:DeleteArchive` action on the examplevault vault if the archive being deleted is less than one year old. The policy uses the S3 Glacier-specific condition key `ArchiveAgeInDays` to enforce the one-year retention requirement. + +```json +{ + "Version":"2012-10-17", + "Statement":[ + { + "Sid": "deny-based-on-archive-age", + "Principal": "*", + "Effect": "Deny", + "Action": "glacier:DeleteArchive", + "Resource": [ + "arn:aws:glacier:us-west-2:123456789012:vaults/examplevault" + ], + "Condition": { + "NumericLessThan" : { + "glacier:ArchiveAgeInDays" : "365" + } + } + } + ] +} +``` + +### Deny Deletion Permissions Based on a Tag + +Suppose that you have a time-based retention rule that an archive can be deleted if it is less than a year old. At the same time, suppose that you need to place a legal hold on your archives to prevent deletion or modification for an indefinite duration during a legal investigation. In this case, the legal hold takes precedence over the time-based retention rule specified in the Vault Lock policy. + +To put these two rules in place, the following example policy has two statements: + +- The first statement denies deletion permissions for everyone, locking the vault. This lock is performed by using the `LegalHold` tag. +- The second statement grants deletion permissions when the archive is less than 365 days old. But even when archives are less than 365 days old, no one can delete them when the condition in the first statement is met. + +```json +{ + "Version":"2012-10-17", + "Statement":[ + { + "Sid": "lock-vault", + "Principal": "*", + "Effect": "Deny", + "Action": [ + "glacier:DeleteArchive" + ], + "Resource": [ + "arn:aws:glacier:us-west-2:123456789012:vaults/examplevault" + ], + "Condition": { + "StringLike": { + "glacier:ResourceTag/LegalHold": [ + "true", + "" + ] + } + } + }, + { + "Sid": "you-can-delete-archive-less-than-1-year-old", + "Principal": { + "AWS": "arn:aws:iam::123456789012:root" + }, + "Effect": "Allow", + "Action": [ + "glacier:DeleteArchive" + ], + "Resource": [ + "arn:aws:glacier:us-west-2:123456789012:vaults/examplevault" + ], + "Condition": { + "NumericLessThan": { + "glacier:ArchiveAgeInDays": "365" + } + } + } + ] +} +``` + +--- +reference +- https://docs.aws.amazon.com/amazonglacier/latest/dev/vault-lock-policy.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/Snow\342\200\205Famliy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/Snow\342\200\205Famliy.md" new file mode 100644 index 00000000..18b94ace --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/Snow\342\200\205Famliy.md" @@ -0,0 +1,50 @@ +--- +title: 'Snow Famliy' +lastUpdated: '2024-03-02' +--- + +Snowcone is the smallest AWS Snow family data transfer device. It can delivery 8TB Storage. Send data offline via device delivery or to AWS via AWS DataSync over the Internet + +# Snowball + +Snowball is a giant physical disk that is used for migrating high quantities of data into AWS. It is a peta-byte scale data transport solution. Using a large disk like Snowball helps to circumvent common large scale data transfer problems such as high network costs, long transfer times, and security concerns. Snowballs are extremely secure by defign and once the data transfer is complete, the snow balls are wiped clean of your data. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/30ca9d39-7718-4343-8764-679a0e86145f) + +--- + +- Snowball is a strong choice for a data transfer job if you need a secure an dquick data transfer renging in the terabytes to many petabytes into AWS. + +- Snowball can also be the right choice if you don't want to make expensice upgrades to your existing network infrastructure, if tou frequently experience large basklogs of data, if you're located in a physically isolated environment, or if you're in an area where high-speed internet connections are not available or cost-prohibitive. + +- As a rule of thumb, if it takes more than one week to upload your data to AWS using the spare capacity of you existing internet connection, the you should condider using Snowball. + +- For example, if you have a 100Mb connection that you can solely dedicate to transferring your data and you need to transfer 100TB of data in total, it will take more than 100 days for the transfer to complete over that connection. You can make the same transfer in about a week by using multiple Snowballs. + +- Here is a reference for when Snowball should be considered based on the number of days it would take to make the same transfer over san internet connection: +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/1bdce7f4-49b4-4f94-844d-ea1256c3b5ad) + +## Snowball Edge + +- Snowball Edge is a specific type of Snowball that comes with both compute and storage capabilities via AWS Lambda and specvific EC2 instance types. This means you can run code within your snowball while your data is en route to an Amazon data center. + This enables support of local workloads in remote or offline locations and as a result, Snowball Edge does not need to be limited to a data transfer service. An interesting use case is with airliners. Planes sometimes fly with snowball edges onboeard so they can store large amounts of flight data and compute necessary functions for the plane's own systems. Snobal Edges can also be clustered locally for even better performance. + +Snowball Edge diveces have the following options for device donfigurations: + +- **Snowball Edge Storage Optimized(for data transfer):** This Snowball Edge device option has a 100TB(80TB usable) storage capacity + +- **Snowball Edge Storage Optimized (with EC2 compute functionality):** This Snowball Edge device optoion has up to 80TB of usable storage space, 40 vCPUs, and 80 GiB of memory for compute functionallity. It also comes with 1TB of additional SSD storage space for block volumes attached to EC2 AMIs. + +- **Snowball Edge Compute Optimized:** This Snowball Edge device option has the most compute functionality. with 52 vCPUs, 208 GiB of memory, and 42TB(39.5 TB usable) plus 7.68 TB of dedicated MVMe SSD for compute instances for block storage volumes for EC2 compute instances, and 42 TB fo HDD capacity for either object storage or block storage volumes. + +- **Snowball Edge Compute Optimized with GPU:** This Snowball Edge device option is identical to the Compute Optimized option, except for an installed GPU, equivalent to the on available in the P3 Amazon EC2 instance type. It has 42TB(39.5 TB of HDD storage that can be used for a combination of Amazon S3 compatible object storage and Amazon EBS compatible block storage volumes) plus 7.68 TB of dedicated NVMe SSD for compute instances. + +## Snowmobile + +- Snowmobile is an exabyte-scale data transfer solution. It is a data transport solution for 100 petabytes of data and is contained within a 45-foot shipping container hauled by a semi-truck. This massive transfer makes sense if you want to move your entire data center with years of data into the cloud. + +--- +reference +- https://aws.amazon.com/blogs/storage/data-migration-best-practices-with-snowball-edge/ +- https://aws.amazon.com/blogs/big-data/best-practices-using-aws-sct-and-aws-snowball-to-migrate-from-teradata-to-amazon-redshift/ +- https://aws.amazon.com/blogs/architecture/migrating-to-an-amazon-redshift-cloud-data-warehouse-from-microsoft-aps/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/Storage\342\200\205Gateway.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/Storage\342\200\205Gateway.md" new file mode 100644 index 00000000..bc4039e4 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Storage/Storage\342\200\205Gateway.md" @@ -0,0 +1,38 @@ +--- +title: 'Storage Gateway' +lastUpdated: '2024-03-02' +--- + +Storage Gateway is a service that connects on-premise environments with cloud-based storage in order to seamlessly and securely intergrate an on-prem application with a cloud storage backend. Storage Gateway comes in three flavors: File Gateway, Volume Gateway and Tape Gateway. + +## Storage Gateway Key Details: + +- The Storage Gateway service can either be a **physical device or a VM image downloaded onto a host in an on-prem data center**. It acts as a bridge to send or receive data from AWS. + +- Storage Gateway can sit on top of VMWare's EXCi Hypervisor for Linux machine and Microsoft's Hyper-V hypervisor for Windows machines. + +- The three types of Storage Gateways are below: + - **File Gateway** - Operates via NFS or SMB and is used to store files in S3 over a network filesystem mount point in the supplied virtual machine. Simply put, you can think of a File Gateway as a file system mount on S3. + - **Volume Gateway** - Operates via iSCSI and is used to store copies of hard disk drives or virtual hard disk drives in S3. These can be achieved via Stored Volumes or Cached Volumes. Simply put, you can think of Volume Gateway as a way of storing virtual hard disk drives in the cloud. + - **Tape Gateway** - Operates as a Virtual Tape Library + +- Relevant file information passing through Storage Gateway like file ownership, permissions, timestamps, etc. are stored as metadata for the objects that they belong to. Once these file details are stored in S3, they can be managed natively. This mean all S3 features like versiong, lifecycle management, bucket policies, cross region replication, etc. can be applied as a part of Storage Gateway. + +- Applications interfacing with AWS over the Volume Gateway is done over the iSCSI block protocol. Data written to these volumes can be asynchronously backed up into AWS Elastic Blok Store (EBS) as point-in-time snapshots of the volumes' content. These kind of snapshots act as increamental backups that capture only changed state similar to a pull request in Git. Further, all snapshots are compressed to reduce storage costs. + +- Tape Gateway offers a durable, cost-effective way of archiving and replicating data into S3 while getting rid of tapes (old-school data storage). The Virtual Yape Library (VTL), leveages existing tape-based backup infrastructure to store data on virtual tape cartridges that you create on the Tape Gateway. It's a great way to modernize and move backups into the cloud. + +## Stored Volumes VS Cached Volumes + +- Volume Gateway's **Stored Volumes** let you store data locally on-prem and backs the data up to AWS as a secondary data source. Stored Volumes allow low-latency access to entire datasets, while providing high availability over a hybrid cloud solution. Further, you can mount Stored Volumes on application infrastructure as iSCSI drives so when data is written to these volumes, the data is both written onto the on-prem hardware and asynchronously backed up as snapshots in AWS EBS or S3. + - In the following diagram of a Stored Volume architecture, data is served to the user from the Storage Area Network, Network Attached, or Direct Attached Stoage within you data center. S3 exists just as a secure ans reliable backup. + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/c3037bd8-5280-4346-867d-83def895e911) + +- Volume Gateway's Cached Volumes differ as they do not store the entire dataset locally like Stored Volumes. Instead, AWS is used as the primary data source and the local hardware is used as a caching layer. Only the most frequenfly used components are retained onto the on-prem-infrastructure while the remaining data is served from AWS. The minimizes the need to scale on-prem infrastructure while still maintaining low-latency access to the most referenced data. + - In ther following diagram of a Cached Volume architecture, the most frequently accessed data is served to the user from the Storage Area Network, Network Attaches, or Direct Attached Storage within your data center. S3 serves the rest of the data from AWS. + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/35ba8705-bb7e-4a0f-93bc-a8f41c0ba2e5) + +--- +reference +- https://docs.amazonaws.cn/storagegateway/index.html +- https://docs.amazonaws.cn/en_us/storagegateway/latest/vgw/StorageGatewayConcepts.html#storage-gateway-stored-volume-concepts \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Well\342\200\205Architected.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Well\342\200\205Architected.md" new file mode 100644 index 00000000..12f0e5ab --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/AWS/Well\342\200\205Architected.md" @@ -0,0 +1,110 @@ +--- +title: 'Well Architected' +lastUpdated: '2024-03-02' +--- + +AWS provide [Well-Architecture Framework](https://aws.amazon.com/ko/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&wa-lens-whitepapers.sort-order=desc&wa-guidance-whitepapers.sort-by=item.additionalFields.sortDate&wa-guidance-whitepapers.sort-order=desc) to build a secure, high-performance, resilient and efficient infrastructure for a wide variety of applications and workloads. AWS Well-Architected provides a consistent approach to helping customers and partners evaluate architectures and implement scalable designs. + +The framework is based on six pillars: +- Operational Excellence +- Security +- Reliability +- Performance Efficiency +- Cost Optimization +- Sustainability + +# Operational Excellence + +Operational excellence is defined as a commitment to build software correctly while consistently delivering a great customer experience. It contains best practices for organizing team, designing worload, operating it at scale, and evolving it over time. + +The goal of operational excellence is to get new features and bug fixes into customer's hands quickly and reliably. Along the way, operational excellence drives towards continuous integration and continous delivery([CI/CD](https://github.com/rlaisqls/TIL/blob/main/%EB%8D%B0%EB%B8%8C%EC%98%B5%EC%8A%A4%E2%80%85DevOps/CI%EF%BC%8FCD%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8.md)) by helping developers achieve high quality results consistently. + +The following are the design principles for operational excellence in the cloud: + +- **Perform operations as code:** In the cloud, you can apply the same engineering discipline that you use for application code to your entire environment. You can define your entire workload (applications, infrastructure, etc.) as code and update it with code. You can script your operations procedures and automate their process by launching them in response to events. By performing operations as code, you limit human error and create consistent responses to events. + +- **Make frequent, small, reversible changes:** Design workloads to allow components to be updated regularly to increase the flow of beneficial changes into your workload. Make changes in small increments that can be reversed if they fail to aid in the identification and resolution of issues introduced to your environment (without affecting customers when possible). + +- **Refine operations procedures frequently:** As you use operations procedures, look for opportunities to improve them. As you evolve your workload, evolve your procedures appropriately. Set up regular game days to review and validate that all procedures are effective and that teams are familiar with them. + +- **Anticipate failure:** Perform “pre-mortem” exercises to identify potential sources of failure so that they can be removed or mitigated. Test your failure scenarios and validate your understanding of their impact. Test your response procedures to ensure they are effective and that teams are familiar with their process. Set up regular game days to test workload and team responses to simulated events. + +- **Learn from all operational failures:** Drive improvement through lessons learned from all operational events and failures. Share what is learned across teams and through the entire organization. + +# Security + +The Security pillar encompasses the ability to protect data, systems, and assets to take advantage of cloud technologies to improve your security. + +Before you architect any workload, you need to put in place practices that influence security. You will want to control who can do what. In addition, you want to be able to identify security incidents, protect your systems and services, and maintain the confidentiality and integrity of data through data protection. You should have a well-defined and practiced process for responding to security incidents. These tools and techniques are important because they support objectives such as preventing financial loss or complying with regulatory obligations. + +The AWS Shared Responsibility Model helps organizations that adopt the cloud to achieve their security and compliance goals. Because AWS physically secures the infrastructure that supports our cloud services, as an AWS customer can focus on using services to accomplish your goals. The AWS Cloud also provides greater access to security data and an automated approach to responding to security events. + +The following are the design principles for security in the cloud: + +- **Implement a strong identity foundation:** Implement the principle of least privilege and enforce separation of duties with appropriate authorization for each interaction with your AWS resources. Centralize identity management, and aim to eliminate reliance on long-term static credentials. + +- **Maintain traceability:** Monitor, alert, and audit actions and changes to your environment in real time. Integrate log and metric collection with systems to automatically investigate and take action. + +- **Apply security at all layers:** Apply a defense in depth approach with multiple security controls. Apply to all layers (for example, edge of network, VPC, load balancing, every instance and compute service, operating system, application, and code). + +- **Automate security best practices:** Automated software-based security mechanisms improve your ability to securely scale more rapidly and cost-effectively. Create secure architectures, including the implementation of controls that are defined and managed as code in version-controlled templates. + +- **Protect data in transit and at rest:** Classify your data into sensitivity levels and use mechanisms, such as encryption, tokenization, and access control where appropriate. + +- **Keep people away from data:** Use mechanisms and tools to reduce or eliminate the need for direct access or manual processing of data. This reduces the risk of mishandling or modification and human error when handling sensitive data. + +- **Prepare for security events:** Prepare for an incident by having incident management and investigation policy and processes that align to your organizational requirements. Run incident response simulations and use tools with automation to increase your speed for detection, investigation, and recovery. + +# Reliability + +The Reliability pillar encompasses the ability of a workload to perform its intended function correctly and consistently when it’s expected to. This includes the ability to operate and test the workload through its total lifecycle. This paper provides in-depth, best practice guidance for implementing reliable workloads on AWS. + +To achieve reliability, you must start with the foundations — an environment where Service Quotas and network topology accommodate the workload. The workload architecture of the distributed system must be designed to prevent and mitigate failures. The workload must handle changes in demand or requirements, and it must be designed to detect failure and automatically heal itself. + +There are five design principles for reliability in the cloud: + +- **Automatically recover from failure:** By monitoring a workload for key performance indicators (KPIs), you can start automation when a threshold is breached. These KPIs should be a measure of business value, not of the technical aspects of the operation of the service. This provides for automatic notification and tracking of failures, and for automated recovery processes that work around or repair the failure. With more sophisticated automation, it’s possible to anticipate and remediate failures before they occur. + +- **Test recovery procedures:** In an on-premises environment, testing is often conducted to prove that the workload works in a particular scenario. Testing is not typically used to validate recovery strategies. In the cloud, you can test how your workload fails, and you can validate your recovery procedures. You can use automation to simulate different failures or to recreate scenarios that led to failures before. This approach exposes failure pathways that you can test and fix before a real failure scenario occurs, thus reducing risk. + +- **Scale horizontally to increase aggregate workload availability:** Replace one large resource with multiple small resources to reduce the impact of a single failure on the overall workload. Distribute requests across multiple, smaller resources to verify that they don’t share a common point of failure. + +- **Stop guessing capacity:** A common cause of failure in on-premises workloads is resource saturation, when the demands placed on a workload exceed the capacity of that workload (this is often the objective of denial of service attacks). In the cloud, you can monitor demand and workload utilization, and automate the addition or removal of resources to maintain the more efficient level to satisfy demand without over- or under-provisioning. There are still limits, but some quotas can be controlled and others can be managed (see Manage Service Quotas and Constraints). + +- **Manage change in automation:** Changes to your infrastructure should be made using automation. The changes that must be managed include changes to the automation, which then can be tracked and reviewed. + +# Performance efficiency + +The Performance Efficiency pillar includes the ability to use computing resources efficiently to meet system requirements, and to maintain that efficiency as demand changes and technologies evolve. + +Take a data-driven approach to building a high-performance architecture. Gather data on all aspects of the architecture, from the high-level design to the selection and configuration of resource types. + +Reviewing your choices on a regular basis validates that you are taking advantage of the continually evolving AWS Cloud. Monitoring verifies that you are aware of any deviance from expected performance. Make trade-offs in your architecture to improve performance, such as using compression or caching, or relaxing consistency requirements. + +- **Democratize advanced technologies:** Make advanced technology implementation smoother for your team by delegating complex tasks to your cloud vendor. Rather than asking your IT team to learn about hosting and running a new technology, consider consuming the technology as a service. For example, NoSQL databases, media transcoding, and machine learning are all technologies that require specialized expertise. In the cloud, these technologies become services that your team can consume, permitting your team to focus on product development rather than resource provisioning and management. + +- **Go global in minutes:** Deploying your workload in multiple AWS Regions around the world permits you to provide lower latency and a better experience for your customers at minimal cost. + +- **Use serverless architectures:** Serverless architectures remove the need for you to run and maintain physical servers for traditional compute activities. For example, serverless storage services can act as static websites (removing the need for web servers) and event services can host code. This removes the operational burden of managing physical servers, and can lower transactional costs because managed services operate at cloud scale. + +- **Experiment more often:** With virtual and automatable resources, you can quickly carry out comparative testing using different types of instances, storage, or configurations. + +- **Consider mechanical sympathy:** Understand how cloud services are consumed and always use the technology approach that aligns with your workload goals. For example, consider data access patterns when you select database or storage approaches. + +# Cost Optimization + +The Cost Optimization pillar includes the ability to run systems to deliver business value at the lowest price point. + +As with the other pillars within the Well-Architected Framework, there are tradeoffs to consider, for example, whether to optimize for speed-to=market or for cost. In some cases, it's more efficient to optimize for speed, going to market quickly, shipping new features, or meeting a deadlin, rather than investing in upfront cost optimization. Design decisions are sometimes directed by haste rather than spend time benchmarking for the most cost-optimal deplyments. However, this is a reasonable choice when you must "lift and shift" resources from your on-premises environment to ther cloud and then optimize afterwards. Investing the right amount of effort in a cost optimization strategy up front permits you to realize the economix benefits of the cloud more readily by achieving a conststent adherence to best practices and avoiding unnecessary over prosioning. + +There are five design principles for cost optimization in the cloud: + +- **Implement Cloud Financial Management:** To achieve financial success and accelerate business value realization in the cloud, invest in Cloud Financial Management and Cost Optimization. Your organization should dedicate time and resources to build capability in this new domain of technology and usage management. Similar to your Security or Operational Excellence capability, you need to build capability through knowledge building, programs, resources, and processes to become a cost-efficient organization. + +- **Adopt a consumption model:** Pay only for the computing resources that you require and increase or decrease usage depending on business requirements, not by using elaborate forecasting. For example, development and test environments are typically only used for eight hours a day during the work week. You can stop these resources when they are not in use for a potential cost savings of 75% (40 hours versus 168 hours). + +- **Measure overall efficiency:** Measure the business output of the workload and the costs associated with delivering it. Use this measure to know the gains you make from increasing output and reducing costs. + +- **Stop spending money on undifferentiated heavy lifting:** AWS does the heavy lifting of data center operations like racking, stacking, and powering servers. It also removes the operational burden of managing operating systems and applications with managed services. This permits you to focus on your customers and business projects rather than on IT infrastructure. + +- **Analyze and attribute expenditure:** The cloud makes it simple to accurately identify the usage and cost of systems, which then permits transparent attribution of IT costs to individual workload owners. This helps measure return on investment (ROI) and gives workload owners an opportunity to optimize their resources and reduce costs. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/CI\357\274\217CD\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/CI\357\274\217CD\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270.md" new file mode 100644 index 00000000..80780b17 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/CI\357\274\217CD\355\214\214\354\235\264\355\224\204\353\235\274\354\235\270.md" @@ -0,0 +1,32 @@ +--- +title: 'CI/CD파이프라인' +lastUpdated: '2024-03-02' +--- + +## CI(Continuous Integration) +- CI는 지속적 통합이라는 뜻으로 여러 명이 하나의 코드에 대해서 수정을 진행해도 지속적으로 통합하면서 충돌 없이 작업, 관리할 수 있음을 의미한다. +- 또한, 공유 버전 관리 레포지토리에 통합되어 있는 코드에 대해, 자동화된 테스트를 진행하여 통합된 코드에서 생기는 문제를 신속하게 파악하고 해결할 수 있도록 하는 것이 CI의 특징이다. + +## CD(Continuous Delivery/Deployment) +

CD는 지속적인 제공또는 지속적인 배포를 모두 의미한다. 두 가지 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만, 자세한 개념은 다르다.

+ +### 지속적인 제공(Continuous Delivery) +- 지속적인 제공은 개발자들이 애플리케이션에 적용한 변경사항이 버그 테스트를 거쳐 리포지토리에 자동으로 업로드되는 것을 뜻한다. 운영팀은 이 리포지토리에서 애플리케이션을 실시간 환경으로 배포할 수 있기 때문에 개발팀과 비즈니스팀 간의 가시성과 커뮤니케이션 부족 문제를 해결할 수 있다. 지속적인 제공은 최소한의 노력으로 새로운 코드를 배포하는 것을 목표로 한다. + +### 지속적인 배포(Continuous Deployment) +- 지속적인 배포는 개발자의 변경사항을 리포지토리에서 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스하는 것을 의미한다 수동으로 배포하는 프로세스를 수행하지 않아도 되기 때문에 배포 과정을 보다 효율적으로 할 수 있다. + +## CI/CD 과정 + +![image](https://github.com/rlaisqls/TIL/assets/81006587/fe3bdd7a-9dff-42ae-a980-4383300b8fd4) + +참고:
+redhat CI/CD(지속적 통합/지속적 제공): 개념,방법,장점,구현 과정 + +
+
+ +--- + +더 알아보기:
+CI/CD 툴들 https://ichi.pro/ko/hyeonjae-sayong-ganeunghan-choegoui-ci-cd-dogu-27gaji-194611649728144 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Cloud\342\200\205Agnostic\342\200\205Design.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Cloud\342\200\205Agnostic\342\200\205Design.md" new file mode 100644 index 00000000..884611ab --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Cloud\342\200\205Agnostic\342\200\205Design.md" @@ -0,0 +1,61 @@ +--- +title: 'Cloud Agnostic Design' +lastUpdated: '2024-03-02' +--- + +Agnostic is a term that refers to a kind of belief that does not determine the authenticity of religion, but it is often used in an extended sense in many fields. When it's commonly used, it means that you're not tied to a particular idea, concept, or theory. + +It's same to Cloud Agnostic. + +Storage, compute, and networking are at the heart of any cloud infrastructure. In addition, managed, unmanaged, and serverless services are available across multiple clouds. This enables you to design your workloads to run in the cloud of your choice while also providing the flexibility to switch to other clouds. If you aim for cloud-agnostic design. + +It is sometimes necessary to be specific to cloud providers and locations. The utility used to detect sensitive data should not move data from the cloud, and scans should be performed locally. As a result, we should be able to deploy our solution and utility across multiple clouds, and cloud-agnostic design is the way to go. + +Cloud Agnostic designs: + +- Allows you to avoid vendor lock-in. +- Provides a broader range of location availability. +- Makes it easy to design hybrid cloud solutions. +- The ability to easily migrate to another cloud provider. + +## Strategies + +Let’s discuss strategies and most commonly used tools to achieve the cloud-agnostic design. + +### 1. Containerization + +- Containerization is supported by all major clouds. When deploying solutions with different stacks on different clouds, managed Kubernetes clusters come in handy. It assists you in orchestrating your microservices containers and is managed, so it offers excellent capabilities such as automated upgrades and HA features. + +- Alternatively, you can also use RedHat OpenShift, DC/OS, and Docker Swarm for container orchestration. + +### 2. Cloud Foundry + +- CNCF ecosystem is vendor-neutral, made up of technologies that are open, accessible, resilient, manageable, and observable. + +- Cloud Foundry provides flexible infrastructure which means you can deploy it to run your apps on your own computing infrastructure, or deploy on an IaaS like vSphere, AWS, Azure, GCP, or OpenStack. + +image + +### 3. Monitoring/instrumentation/observability + +- Use open-source monitoring tools which can be easily integrated on all major clouds. Prometheus is used for monitoring and alerting, along with Grafana for dashboarding. It’s a popular and active CNCF supported open source project. below is the sample design on how Prometheus works with open-source tools such as Grafana. + image + +### 4. Message Broker + +- You have different options here as mentioned earlier you can have your own deployment of Kafka/RabbitMQ or you can use managed services available across all the clouds. +“KubeMQ is a Single Messaging Platform that runs on every environment, Edge, Multi-cloud, and On-prem. KubeMQ is deployable on all K8s platforms, eliminating the overhead of managing multiple messaging systems, creating true freedom, and control to run on any cloud without vendor lock-in.” + +### 5. Serverless + +- If your application needs serverless functions, all major cloud supports serverless functions (Azure Functions, AWS lambda function, and Google cloud functions) with all major programming language support. +In the example given below, Each AWS Lambda function has a corresponding Azure Functions. + image + +### 6. Infrastructure as Code + +Cloud service providers provide deployment templates that can be used to deploy resources on a specific cloud. Though it may be advantageous to deploy resources on a single cloud, more generic IaC configurations are more beneficial. For example, use Terraform templates or Ansible playbook to set up deployments on cloud providers. + +--- +reference +- https://medium.com/path-to-software-architect/cloud-agnostic-design-925c08e1d610 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/CNI.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/CNI.md" new file mode 100644 index 00000000..29a3c3d3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/CNI.md" @@ -0,0 +1,28 @@ +--- +title: 'CNI' +lastUpdated: '2024-03-02' +--- + +![image](https://user-images.githubusercontent.com/81006587/216500706-744fd3ac-ca09-443b-b026-9c27f276c2b0.png) + + +container를 돌리는 모든 소프트웨어들은(ex. docker, rkt, mesos, k8s) 각 컨테이너간의 네트워크를 구현한다. 그것은 모두 [네트워크 네임스페이스](../linux/network namespaces.md)를 통해 구현되고, 서로 비슷한 절차를 거쳐 브릿지를 생성한다. 약간의 차이는 있을 수 있지만 전반적인 흐름은 아주 유사하다. + +그렇다면 그 작업을 표준화한 인터페이스를 만든다면 어떨까? 이를 위해 정의된 것이 바로 CNI(Container Network Interface)이다. + +CNI는 컨테이너 네트워크 작업을 수행하는 코드가 대략적으로 어떤 동작을 해야하고, 어떻게 호출되어야햐는지를 정의한다. 컨테이너를 돌리는 소프트웨어는 CNI 스펙에 맞추어 함꼐 동작할 수 있도록 구현됐기 때문에 해당 Interface를 구현하는 구현체중 원하는 것을 선택하여 사용하기만 하면 된다. + +CNI를 구현하는 플러그인으론 BRIDGE, VLAN, IPVLAN, MACVLAN, DHCP, Calico, Canal, romana, Weave, Flannel, NSX 등등이 있다. + +(도커는 CNM이라는 별도 네트워킹을 구현하기 떄문에, CNI에 호환되지 않는다.) + +## CNI Plugin이 하는 일 + +- 컨테이너를 ADD, DELETE, CHECK할 수 있어야한다. +- container id, network ns 등등의 파라미터를 지원해야한다. +- Pod에 IP 주소를 할당하고 관리해야한다. +- 조회 결과 반환시, 특정한 형식에 맞추어야한다. (JSON) + +## IPAM + +CNI 설정 파일은 CNI 플러그인의 유형, 사용할 서브넷과 라우트를 명시하는 IPAM(IP Address Management)이라는 섹션을 가지고있다. 하드코딩 없이 적절한 플러그인을 호출할 수 있도록 하는 역할을 한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/Calico.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/Calico.md" new file mode 100644 index 00000000..a1dd1a24 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/Calico.md" @@ -0,0 +1,54 @@ +--- +title: 'Calico' +lastUpdated: '2024-03-02' +--- + +Calico is a networking and security solution that enables Kubernetes workloads and non-kubernetes/legacy worloads to communicate seamlessly and securely. + +In k8s, the default for networking traffic to/from pods is default-allow. If you do not lock down network connectivity using network policy, then all pods can communicate freely with other pods. + +Calico consists of networking to secure network communication, and advanced network policy to secure cloud-native microservices/applications at scale. + +### Component + +#### **Calico CNI for networking** + +Calico CNI is a control plane that programs several dataplanes. It is an L3/L4 networking solution that secure containers. kubernetes clusters, virtual machines, and native host-based workloads. + +Main features are: +- Built-in data encryption +- Advanced IPAM management +- Overlay and non-overlay networking options +- Choice of dataplanes: iptables, eBPF, Windows HNS, or VPP + +#### **Calico network policy suite** for network policy + +Calico network policy suite is an interface tothe Calico CNI that contains rules for the dataplane to execute. + +Clico network policy: +- Is designed with a zero-trust security model (deny-all, allow only where needed) +- Integrates with the kubernetes API server (so you can still use kubernates network policy) +- Supports legacy systems (bare metal, non-cluster hosts) using that same network policy model. + +Main features are: +- **Namespace** and **global** policy to allow/deny traffic within a cluster, between pods and the outside world, and for non-cluster hosts. +- **Network sets** (an arbitrary set of IP subnetworks, CIDRs, or domains) to limit IP ranges for egress and ingress traffic to workloads. +- **Application layer (L7) policy** to enforce traffic using attributes like HTTP methods, paths, and cryptographically-secure identities. + +### Feature summary +The following table summarizes the main Calico features. + +|Feature|Description| +|-|-| +|Dataplanes|eBPF, standard Linux iptables, Windows HNS, VPP.| +|Networking|• Scalable pod networking using BGP or overlay networking
• Advanced IP address management that is customizable| +|Security|• Network policy enforcement for workload and host endpoints
• Data-in-transit encryption using WireGuard| +|Monitor Calico components|Uses Prometheus to monitor Calico component metrics.| +|User interfaces|CLIs: kubectl and calicoctl| +|APIs|• Calico API for Calico resources
• Installation API for operator installation and configuration| +|Support and maintenance|Community-driven. Calico powers 2M+ nodes daily across 166 countries.| + +--- +reference +- https://www.calicolabs.com/ +- https://www.tigera.io/project-calico/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/Install\342\200\205Calico.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/Install\342\200\205Calico.md" new file mode 100644 index 00000000..5261f32e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/CNI/Install\342\200\205Calico.md" @@ -0,0 +1,59 @@ +--- +title: 'Install Calico' +lastUpdated: '2024-03-02' +--- + +1. Install the Calico operator and cudtom resource definitions + +```bash +kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml +``` + +> Due to the large size of the CRD bundle, `kubectl apply` might exceed request limits. Instead, use `kubectl create` or `kubectl replace`. + +2. Install Calico by creating the necessary custom resource. For more information on configuration options available in this manifest, see [the installation reference.](https://docs.tigera.io/calico/latest/reference/installation/api) + +```bash +kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml +``` + +> Before creating this manifest, read its contents and make sure its settings are correct for your environment. For example, you may need to change the default IP pool CIDR to match your pod network CIDR. + +3. Confirm that all of the pods are running with the following command. + +```bash +watch kubectl get pods -n calico-system +``` + +Wait until each pod has the `STATUS` of `Running`. + +> The Tigera operator installs resources in the calico-system namespace. Other install methods may use the `kube-system` namespace instead. + +4. Remove the taints on the control plane so that you can schedule pods on it. + +```bash +kubectl taint nodes --all node-role.kubernetes.io/control-plane- +kubectl taint nodes --all node-role.kubernetes.io/master- +``` + +It should return the following. + +```bash +node/ untainted +``` + +Confirm that you now have a node in your cluster with the following command. + +```bash +kubectl get nodes -o wide +``` + +It should return something like the following. + +```bash +NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME + Ready master 52m v1.12.2 10.128.0.28 Ubuntu 18.04.1 LTS 4.15.0-1023-gcp docker://18.6.1 +``` + +You now have a single-host Kubernetes cluster with Calico! + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/ContainerRuntime.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/ContainerRuntime.md" new file mode 100644 index 00000000..116fea7a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/ContainerRuntime.md" @@ -0,0 +1,43 @@ +--- +title: 'ContainerRuntime' +lastUpdated: '2024-03-02' +--- + +> 컨테이너를 쉽게 다운로드받거나 공유하고 구동할 수 있게 도와주는 툴 + +![image](https://user-images.githubusercontent.com/81006587/201902133-348e4256-8d6a-494c-9c0d-5057e591275b.png) + +컨테이너를 실행하기 위해서는 다음과 같은 세 단계를 거쳐야한다. 즉, 컨테이너 런타임은 이러한 단계들을 실행해주는 기능을 가진다. + +런타임은 실제 컨테이너를 실행하는 단계만 수행하는 **저수준 컨테이너 런타임**(OCI 런타임)과 컨테이너 이미지의 전송 및 관리, 이미지 압축 풀기 등을 실행하는 **고수준 컨테이너 런타임**으로 나뉜다. + +컨테이너를 실행하려면 저수준 및 고수준 컨테이너 런타임이 각각의 역할을 해야한다. 즉, 컨테이너를 실행하려면 두 런타임이 모두 있어야한다. + +## 저수준 컨테이너 런타임(Low-Level Container Runtimes) + +컨테이너는 Linux namespace와 cgroup을 사용하여 구현한다. + +**namespace :** 각 컨테이너에 대해 파일 시스템이나 네트워킹과 같은 시스템 리소스를 가상화함 +**cgroup :** 각 컨테이너가 사용할 수 있는 CPU 및 메모리와 같은 리소스 양을 제한하는 역할을 함. + +저수준 컨테이너 런타임은 이러한 namespace와 cgroup을 설정한 다음 해당 namespace 및 cgroup 내에서 명령을 실행하는 기능을 가지고 있다. + +저수준 컨테이너 런타임은 컨테이너를 실제 실행하는 역할을 하지만 이미지로부터 컨테이너를 실행하려면 이미지와 관련된 API와 같은 기능이 필요하다. 이러한 기능은 고수준 컨테이너 런타임에서 제공된다. + +이러한 저수준 컨테이너 런타입이 표준적으로 지켜야 하는 스펙을 **OCI** 라고 부른다. + +## 고수준 컨테이너 런타임(High-Level Container Runtimes) + +일반적으로 고수준 컨테이너 런타임은 원격 애플리케이션이 컨테이너를 논리적으로 실행하고 모니터링 하는데 사용할 수 있는 데몬 및 API를 제공한다. 또한 컨테이너를 실행하기 위해 **저수준 런타임 위에 배치**된다. + +### **CRI(Container Runtime Interface)** + +CRI는 쿠버네티스에서 만든 컨테이너 런타임 인터페이스이다. + +명확하게 정의된 추상화 계층을 제공함으로써 개발자가 컨테이너 런타임 구축에 집중할 수 있게 한다. + + + + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Container\342\200\205Orchestration.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Container\342\200\205Orchestration.md" new file mode 100644 index 00000000..70696b3e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Container\342\200\205Orchestration.md" @@ -0,0 +1,28 @@ +--- +title: 'Container Orchestration' +lastUpdated: '2024-03-02' +--- + +레드햇(Red Hat)의 정의에 따르면, IT 업계에서 오케스트레이션(Orchestration)이란 용어는 "컴퓨터 자원과 어플리케이션, 서비스에 대한 자동화된 설정, 관리 및 제어 체계"를 의미한다. + +비슷한 느낌으로 컨테이너 오케스트레이션은, `"컨테이너화 된 애플리케이션에 대한 자동화된 설정, 관리 및 제어 체계"`로 받아들일 수 있다. + + + + +## 컨테이너 오케스트레이션이 필요한 이유 + +단일 호스트로 구성된 환경은 확장성(Scalability)과 가용성(Availabilty), 그리고 장애 허용성(Fault Tolerance) 측면에서 많은 한계점을 가지기 때문에, 애플리케이션을 여러개의 호스트로 나누어 구축해야하는 경우가 많다. + +마이크로서비스 아키텍처(MSA; Microservice Architecture)에서는 프로젝트에 포함된 세부 기능들이 작은 서비스 단위로 분리되어 구축된다. 이 각각의 서비스를 구현할 때 컨테이너 기술이 흔하게 이용된다. 한정된 적은 양의 컨테이너는 개별 관리자가 손수 관리할 수도 있겠지만, 대규모의 상용 프로젝트 환경에서 수많은 컨테이너를 이런 식으로 제어하는 것은 불가능하다. + +동시에 수백, 수천 개의 컨테이너를 배포하고 관리해야 하는 상황이라면 아래의 4가지 이슈에 대한 해답을 반드시 찾아야 한다. + +1. **배포 관리** : 어떤 컨테이너를 어느 호스트에 배치하여 구동시킬 것인가? 각 호스트가 가진 한정된 리소스에 맞춰 어떻게 최적의 스케줄링을 구현할 것인가? 어떻게 하면 이러한 배포 상태를 최소한의 노력으로 유지 관리할 수 있을 것인가? +2. **제어 및 모니터링** : 구동 중인 각 컨테이너들의 상태를 어떻게 추적하고 관리할 것인가? +3. **스케일링** : 수시로 변화하는 운영 상황과 사용량 규모에 어떻게 대응할 것인가? +4. **네트워킹** : 이렇게 운영되는 인스턴스 및 컨테이너들을 어떻게 상호 연결할 것인가? + +컨테이너 오케스트레이션은 이러한 이슈들을 한눈에 확인하고, 편하게 관리할 수 있도록 하는 체계를 제공한다. + +컨테이너 오케스트레이션을 위한 툴로는 Docker Swarm, Kubernetes, Nomad, Mesos, Rancher 등등이 있다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/DockerSwarm.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/DockerSwarm.md" new file mode 100644 index 00000000..10d181bd --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/DockerSwarm.md" @@ -0,0 +1,84 @@ +--- +title: 'DockerSwarm' +lastUpdated: '2024-03-02' +--- + +Docker Swarm은 도커에서 자체적으로 제작한 컨테이너 오케스트레이션 도구이다. + +Docker Swarm을 사용한다면 여러 컨테이너의 배포를 관리하고, 네트워크 및 컨테이너 상태를 제어 및 모니터링 할 수 있다. + +## 쿠버네티스와의 차이? + +컨테이너 오케스트레이션을 위한 툴로는 여러가지가 있는데 그중 가장 널리 쓰이는 것은 쿠버네티스로, 사실상 표준 기술로 자리잡았다 볼 수 있을 정도로 널리 쓰이고 있다. + +하지만 도커 스웜(Docker Swarm)은 Mirantis가 Docker의 엔터프라이즈 플랫폼 사업을 인수한 이래로 유지보수 단계에 접어들어 더 이상의 발전과 기능 추가를 기대할 수 없게 되었다. + +그렇다면 도커 스웜보다는 쿠버네티스를 배우는게 좋지 않을까? 굳이 도커 스웜을 사용해야하는 이유가 뭘까? + +도커 스웜은 아래와 같은 장점을 가지고 있다. + +- 쿠버네티스 만큼은 아니더라도, 여러 대의 호스트로 구성된 중소 규모의 클러스터에서 컨테이너 기반 애플리케이션 구동을 제어하기에 충분한 기능을 갖추고 있다. + +- 도커 엔진(Docker Engine)이 설치된 환경이라면 별도의 구축 비용 없이 스웜 모드(Swarm Mode)를 활성화하는 것만으로 시작할 수 있다. + +- 도커 컴포즈(Docker Compose)를 사용해 본 사람이라면 도커 스웜(Docker Swarm)의 스택(Stack)을 이용한 애플리케이션 운영에 곧바로 적응할 수 있다. + +- 도커 데스크탑(Docker Desktop)으로도 클러스터 관리와 배포가 모두 가능한 단일 노드 클러스터를 바로 만들 수 있다. 따라서 최소한의 자원으로 컨테이너 오케스트레이션 환경을 만들어 시험해볼 수 있다. + +이처럼 진입 장벽이 낮고, 간단한 구조로 빠르게 시험 가능한 특성은 학습자의 입장에서 매우 큰 이점이다. 같은 컨테이너 오케스트레이션 도구로서 도커 스웜에 대해 익힌 내용은 추후 쿠버네티스 등 엔터프라이즈 레벨의 도구를 다루는 과정에도 도움이 될 수 있다. + +## 주요 용어 + +#### 노드(Node) +- 클러스터를 구성하는 개별 도커 서버를 의미한다. + +#### 매니저 노드(Manager Node) +- 클러스터 관리와 컨테이너 오케스트레이션을 담당한다. 쿠버네티스의 마스터 노드(Master Node)와 같은 역할이라고 할 수 있다. + +#### 워커 노드(Worker Node) +- 컨테이너 기반 서비스(Service)들이 실제 구동되는 노드를 의미한다. 쿠버네티스와 다른 점이 있다면, Docker Swarm에서는 매니저 노드(Manager Node)도 기본적으로 워커 노드(Worker Node)의 역할을 같이 수행할 수 있다는 것이다. 물론 스케줄링을 임의로 막는 것도 가능하다. + +#### 스택(Stack) +- 하나 이상의 서비스(Service)로 구성된 다중 컨테이너 애플리케이션 묶음을 의미한다. 도커 컴포즈(Docker Compose)와 유사한 양식의 YAML 파일로 스택 배포를 진행한다. + +#### 서비스(Service) +- 노드에서 수행하고자 하는 작업들을 정의해놓은 것으로, 클러스터 안에서 구동시킬 컨테이너 묶음을 정의한 객체라고 할 수 있다. 도커 스웜에서의 기본적인 배포 단위로 취급된다. 하나의 서비스는 하나의 이미지를 기반으로 구동되며, 이들 각각이 전체 애플리케이션의 구동에 필요한 개별적인 마이크로서비스(microservice)로 기능한다. + +#### 태스크(Task) +- 클러스터를 통해 서비스를 구동시킬 때, 도커 스웜은 해당 서비스의 요구 사항에 맞춰 실제 마이크로서비스가 동작할 도커 컨테이너를 구성하여 노드에 분배한다. 이것을 태스크(Task)라고 한다. 하나의 서비스는 지정된 복제본(replica) 수에 따라 여러 개의 태스크를 가질 수 있으며, 각각의 태스크에는 하나씩의 컨테이너가 포함된다. + +### 스케줄링(Scheduling) +- 도커 스웜에서 스케줄링은 서비스 명세에 따라 태스크(컨테이너)를 노드에 분배하는 작업을 의미한다. 2022년 8월 기준으로 도커 스웜에서는 오직 균등 분배(spread) 방식만 지원하고 있다. 물론 노드별 설정 변경 또는 라벨링(labeling)을 통해 스케줄링 가능한 노드의 범위를 제한할 수도 있다. + +## Docker Swarm (Swarmpit) 사용하는법 + +도커를 설치한 후 아래 명령어를 linux 터미널에 입력한다. + +```js +docker swarm init +``` + +Docker swarm을 관리하기 쉽게 GUI형태로 확인할 수 있는 도구인 Swarmpit을 다운받는다. + +```js +sudo docker run -it --rm \ + --name swarmpit-installer \ + --volume /var/run/docker.sock:/var/run/docker.sock \ +swarmpit/install:1.9 +``` + +다운 과정에서 name, port 등의 정보를 원하는대로 설정해준다. +대괄호 안에 특정 값이 써있는 경우에는, 엔터를 누르면 해당 값으로 세팅된다. 기본값으로 생각하면 된다. + +```js +Application setup +Enter stack name [swarmpit] : +Enter application port [888] : +Enter database volume driver [local] : +Enter admin username [admin] : +Enter admin password (min 8 characters long): qwertyuiop... +``` + +ec2에서 swarmpit을 사용하는 경우 설정한 포트를 인바운드 규칙에 추가해줘야한다. + +`서버 주소:swarmpit 포트`를 통해 접속 후, 로그인하면 Swarmpit으로 docker 컨테이너들을 GUI로 배포 및 관리할 수 있다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Docker\342\200\205Image\342\200\205Layer.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Docker\342\200\205Image\342\200\205Layer.md" new file mode 100644 index 00000000..b12b985a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Docker\342\200\205Image\342\200\205Layer.md" @@ -0,0 +1,123 @@ +--- +title: 'Docker Image Layer' +lastUpdated: '2024-03-02' +--- + +Lets take a contrived example Dockerfile: + +``` +FROM busybox + +RUN mkdir /data +# imagine this is downloading source code +RUN dd if=/dev/zero bs=1024 count=1024 of=/data/one +RUN chmod -R 0777 /data +# imagine this is compiling the app +RUN dd if=/dev/zero bs=1024 count=1024 of=/data/two +RUN chmod -R 0777 /data +# and now this cleans up that downloaded source code +RUN rm /data/one + +CMD ls -alh /data +``` + +Each of those `dd` commands outputs a 1M file to the disk. Lets build the image with an extra flag to save the temporary containers: + +``` +docker image build --rm=false . +``` + +In the output, you'll see each of the running commands happen in a temporary container that we now keep instead of automatically deleting: + +``` +... +Step 2/7 : RUN mkdir /data + ---> Running in 04c5fa1360b0 + ---> 9b4368667b8c +Step 3/7 : RUN dd if=/dev/zero bs=1024 count=1024 of=/data/one + ---> Running in f1b72db3bfaa +1024+0 records in +1024+0 records out +1048576 bytes (1.0MB) copied, 0.006002 seconds, 166.6MB/s + ---> ea2506fc6e11 +``` + +If you run a `docker diff` on each of those container id's, you'll see what files were created in those containers: + +``` +$ docker diff 04c5fa1360b0 # mkdir /data +A /data +$ docker diff f1b72db3bfaa # dd if=/dev/zero bs=1024 count=1024 of=/data/one +C /data +A /data/one +$ docker diff 81c607555a7d # chmod -R 0777 /data +C /data +C /data/one +$ docker diff 1bd249e1a47b # dd if=/dev/zero bs=1024 count=1024 of=/data/two +C /data +A /data/two +$ docker diff 038bd2bc5aea # chmod -R 0777 /data +C /data/one +C /data/two +$ docker diff 504c6e9b6637 # rm /data/one +C /data +D /data/one +``` + +Each line prefixed with an `A` is adding the file, the `C` indicates a change to an existing file, and the `D` indicates a delete. + +### Here's the TL;DR part + +Each of these container filesystem diffs above goes into one "layer" that gets assembled when you run the image as a container. The entire file is in each layer when there's an add or change, so each of those `chmod` commands, despite just changing a permission bit, results in the entire file being copied into the next layer. The deleted /data/one file is still in the previous layers, 3 times in fact, and will be copied over the network and stored on disk when you pull the image. + +### Examining existing images + +You can see the commands that goes into creating the layers of an existing image with the `docker history` command. You can also run a `docker image inspect` on an image and see the list of layers under the RootFS section. + +Here's the history for the above image: + +``` +IMAGE CREATED CREATED BY SIZE COMMENT +a81cfb93008c 4 seconds ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "ls -… 0B +f36265598aef 5 seconds ago /bin/sh -c rm /data/one 0B +c79aff033b1c 7 seconds ago /bin/sh -c chmod -R 0777 /data 2.1MB +b821dfe9ea38 10 seconds ago /bin/sh -c dd if=/dev/zero bs=1024 count=102… 1.05MB +a5602b8e8c69 13 seconds ago /bin/sh -c chmod -R 0777 /data 1.05MB +08ec3c707b11 15 seconds ago /bin/sh -c dd if=/dev/zero bs=1024 count=102… 1.05MB +ed27832cb6c7 18 seconds ago /bin/sh -c mkdir /data 0B +22c2dd5ee85d 2 weeks ago /bin/sh -c #(nop) CMD ["sh"] 0B + 2 weeks ago /bin/sh -c #(nop) ADD file:2a4c44bdcb743a52f… 1.16MB +``` + +The newest layers are listed on top. Of note, there are two layers at the bottom that are fairly old. They come from the busybox image itself. When you build one image, you inherit all the layers of the image you specify in the `FROM` line. There are also layers being added for changes to the image meta-data, like the `CMD` line. They barely take up any space and are more for record keeping of what settings apply to the image you are running. + +### Why layers? + +The layers have a couple advantages. First, they are immutable. Once created, that layer identified by a sha256 hash will never change. That immutability allows images to safely build and fork off of each other. If two dockerfiles have the same initial set of lines, and are built on the same server, they will share the same set of initial layers, saving disk space. That also means if you rebuild an image, with just the last few lines of the Dockerfile experiencing changes, only those layers need to be rebuilt and the rest can be reused from the layer cache. This can make a rebuild of docker images very fast. + +Inside a container, you see the image filesystem, but that filesystem is not copied. On top of those image layers, the container mounts it's own read-write filesystem layer. Every read of a file goes down through the layers until it hits a layer that has marked the file for deletion, has a copy of the file in that layer, or the read runs out of layers to search through. Every write makes a modification in the container specific read-write layer. + +### Reducing layer bloat + +One downside of the layers is building images that duplicate files or ship files that are deleted in a later layer. The solution is often to merge multiple commands into a single `RUN` command. Particularly when you are modifying existing files or deleting files, you want those steps to run in the same command where they were first created. A rewrite of the above Dockerfile would look like: + +``` +FROM busybox + +RUN mkdir /data \ + && dd if=/dev/zero bs=1024 count=1024 of=/data/one \ + && chmod -R 0777 /data \ + && dd if=/dev/zero bs=1024 count=1024 of=/data/two \ + && chmod -R 0777 /data \ + && rm /data/one + +CMD ls -alh /data +``` + +And if you compare the resulting images: + +- busybox: ~1MB +- first image: ~6MB +- second image: ~2MB + +Just by merging together some lines in the contrived example, we got the same resulting content in our image, and shrunk our image from 5MB to just the 1MB file that you see in the final image. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Overlay\342\200\205Network.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Overlay\342\200\205Network.md" new file mode 100644 index 00000000..fa5c4af5 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Overlay\342\200\205Network.md" @@ -0,0 +1,246 @@ +--- +title: 'Overlay Network' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/2aa88149-c805-4bcc-abb6-5326db36f458) + +* 컨테이너를 설치하게 되면 default로 bridge 네트워크가 연결된다. +* private internal network +* 각 컨테이너는 veth (virtual) 경로로 bridge 네트워크와 연결된다 +* bridge를 통해 Single-host networking 효과를 낼 수 있다 +* 외부에서 접근시에는 port-mapping이 필요하다 + +```bash +$ docker container + +Usage: docker network COMMAND + +Manage Docker networks + +Options: + --help Print usage + +Commands: + connect Connect a container to a network + create Create a network + disconnect Disconnect a container from a network + inspect Display detailed information on one or more networks + ls List networks + rm Remove one or more networks + +Run 'docker network COMMAND --help' for more information on a command. +``` + +docker container 명령어는 컨테이너 네트워크를 규명하고 관리하는 데에 사용되는 가장 기본적인 명령어이다. + +명령어 그대로 입력하면 지원하는 sub-command를 확인할 수 있는데, `create`/`inspect` 와 같이 다양한 명령어들을 확인할 수 있다. + +```bash +$ docker network ls # list networks +NETWORK ID NAME DRIVER SCOPE +1befe23acd58 bridge bridge local +726ead8f4e6b host host local +ef4896538cc7 none null local +``` + +​네트워크는 각각 유니크한 이름과 ID를 지니게 되고, 한 개의 드라이브를 소유하고 있다. + +위에서 'bridge'란 이름의 네트워크를 찾아볼 수 있는데, 이는 도커를 가장 처음 설치했을 때 자동으로 설치되는 default 네트워크이다. + +브릿지 네트워크는 브릿지 드라이버를 사용하는데, 위 예시는 name과 driver 명이 동일해서 헷갈릴 수 있지만, 두 개는 다른 개념이다. + +위 예시에서 브릿지 네트워크는 local로 범위가 정해져있는데, 이 의미는 해당 네트워크가 도커 호스트 내에서만 존재한다는 것이다. + +어떤 컨테이너를 생성하더라도, 특별히 네트워크를 지정해주지 않았다면 브릿지 네트워크에 기본적으로 연결되어 있다. 아래에 예제로 백그라운드 환경에서 `sleep infinity` 명령어를 수행하는 `ubuntu:latest` 이미지로 생성된 컨테이너를 실행해보자. + +`docker run` 명령어에 별도로 네트워크를 지정해주지 않았기 때문에 bridge 네트워크로 연결되어있을 것이다. + +```bash +$ docker run -dt ubuntu sleep infinity +6dd93d6cdc806df6c7812b6202f6096e43d9a013e56e5e638ee4bfb4ae8779ce + +$ brctl show +bridge name bridge id STP enabled interfaces +docker0 8000.0242f17f89a6 no veth3a080f +``` + +linux bridge 네트워크를 리스팅하는 명령어 `brctl`을 사용하여 수행하면 위와 같이 결과를 확인할 수 있다. + +인터페이스에 값이 생긴 것으로 보아 방금 생성한 컨테이너에 bridge 네트워크가 연결되었음을 확인할 수 있다. + +```bash +$ docker network inspect bridge + + "Containers": { + "6dd93d6cdc806df6c7812b6202f6096e43d9a013e56e5e638ee4bfb4ae8779ce": { + "Name": "reverent_dubinsky", + "EndpointID": "dda76da5577960b30492fdf1526c7dd7924725e5d654bed57b44e1a6e85e956c", + "MacAddress": "02:42:ac:11:00:02", + "IPv4Address": "172.17.0.2/16", + "IPv6Address": "" + } + }, + +``` + +`docker network inspect`를 해보게 되면, 컨테이너 ID가 보임으로써 컨테이너에 네트워크가 올바르게 attach된 것을 확인할 수 있다. + +도커 호스트의 쉘 프롬프트를 실행하여 `ping <도커 컨테이너 IP주소>`를 하게 되면 정상적으로 응답하는 것을 확인할 수 있다. 거꾸로 도커 컨테이너에 접속하여 ping을 설치한 뒤 어느 웹사이트에의 ping 명령어를 수행해도 마찬가지로 정상적인 응답을 확인할 수 있다. + +```bash +$ docker ps // 컨테이너 ID를 얻기 위해 수행 +CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES +6dd93d6cdc80 ubuntu "sleep infinity" 5 mins Up reverent_dubinsky + +// Exec into the container +$ docker exec -it 6dd93d6cdc80 /bin/bash // bash쉘을 실행하기 위해 -it 옵션 +// -i : interactive (stdin활성화) +// -t : tty 모드 설정 여부 + +# Update APT package lists and install the iputils-ping package +root@6dd93d6cdc80:/# apt-get update + + +apt-get install iputils-ping // ping설치 +Reading package lists... Done + + +# Ping www.dockercon.com from within the container +root@6dd93d6cdc80:/# ping www.dockercon.com +PING www.dockercon.com (104.239.220.248) 56(84) bytes of data. +64 bytes from 104.239.220.248: icmp_seq=1 ttl=39 time=93.9 ms +64 bytes from 104.239.220.248: icmp_seq=2 ttl=39 time=93.8 ms +64 bytes from 104.239.220.248: icmp_seq=3 ttl=39 time=93.8 ms +^C +--- www.dockercon.com ping statistics --- +3 packets transmitted, 3 received, 0% packet loss, time 2002ms +rtt min/avg/max/mdev = 93.878/93.895/93.928/0.251 ms +``` + +이제는 NAT를 구성해보자. 도커 호스트의 8080포트와 컨테이너 내부의 80 포트를 연결하도록 publishing하는 nginx 이미지 서버를 띄워보자. + +```bash +$ docker run --name web1 -d -p 8080:80 nginx +``` + +이후 docker ps를 통해 포트 매핑이 정상적으로 이루어졌음을 확인할 수 있다. + +--- + +## Overlay Network + +![image](https://github.com/rlaisqls/TIL/assets/81006587/4670516f-27d1-424e-bca9-ddd81589a177) + +docker swarm을 설치해서 멀티 노드를 구성하여 실습해볼 것이다. linux-base 도커 호스트 2개를 사용하고, 각 노드를 node1, node2로 구분하였다. + +Manager노드와 worker node를 구성하는 것인데, manager노드에서 worker node에게 ping을 날렸을 때 응답을 받을 수 있어야한다. + +```bash +node1$ docker swarm init +Swarm initialized: current node (cw6jpk7pqfg0jkilff5hr8z42) is now a manager. +To add a worker to this swarm, run the following command: + +docker swarm join \ +--token SWMTKN-1-3n2iuzpj8jynx0zd8axr0ouoagvy0o75uk5aqjrn0297j4uaz7-63eslya31oza2ob78b88zg5xe \ +172.31.34.123:2377 + +To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions. +``` + +node1에서 docker swarm을 init하게 되면 `swarm join`이 가능한 토큰이 발행되는데, 이를 노드 2에서 사용할 것이다. + +```bash +node2$ docker swarm join \ +> --token SWMTKN-1-3n2iuzpj8jynx0zd8axr0ouoagvy0o75uk5aqjrn0297j4uaz7-63eslya31oza2ob78b88zg5xe \ +> 172.31.34.123:2377 + +This node joined a swarm as a worker. +``` + +노드 1로 돌아가서 아래와 같이 수행하면 정상적으로 swarm 에 두 노드가 돌아가고 있는 것을 확인할 수 있다. + +```bash +node1$ docker node ls +ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS +4nb02fhvhy8sb0ygcvwya9skr ip-172-31-43-74 Ready Active +cw6jpk7pqfg0jkilff5hr8z42 * ip-172-31-34-123 Ready Active Leader +``` + +이제 `overlay` 네트워크를 만들어볼 차례인데, node1에서 overnet이라는 이름의 `overlay` 드라이브의 도커 네트워크를 생성한다. + +```bash +node1$ docker network create -d overlay overnet +0cihm9yiolp0s9kcczchqorhb +``` + +docker network를 리스팅 해보면 아래와 같이 swarm 스코프에 생성된 네트워크 두 개가 발견이 된다. + +(ingress와 docker_gwbridge 네트워크는 `overlay` 네트워크가 생성되면서 자동으로 생성되었다) + +```bash +node1$ docker network ls +NETWORK ID NAME DRIVER SCOPE +1befe23acd58 bridge bridge local +726ead8f4e6b host host local +8eqnahrmp9lv ingress overlay swarm +0ea6066635df docker_gwbridge bridge local +ef4896538cc7 none null local +0cihm9yiolp0 overnet overlay swarm +``` + +node2에서도 동일한 명령어를 수행하면 `overlay` 네트워크를 찾아볼 수 없는데, 그 이유는 도커가 `overlay` 네트워크가 연결된 호스트 내에 서비스가 동작하여 수행중일 때에만 해당 네트워크를 연결해주기 때문이다. + +노드1에서 위에 생성한 `overlay` 네트워크로 서비스를 하나 생성해보자. 서비스는 간단하게 sleep하는 우분투 서버이다. + +```bash +node1$ docker service create --name myservice \ +--network overnet \ +--replicas 2 \ +ubuntu sleep infinity + +e9xu03wsxhub3bij2tqyjey5t +``` + +위에서 replica를 2개를 생성하였는데, 아래와 같이 docker service ps {서비스이름} 을 수행하게 되면, 각 서비스가 다른 노드에서 돌고 있는 것을 확인할 수 있다. 이제 node2 역시도 `overlay` 네트워크에서 서비스를 수행하고 있는 상황이라, node2 콘솔에서 위의 docker network 리스팅을 해보면 안보였던 overnet 네트워크가 확인이 될 것이다. + +```bash +node1$ docker service ps myservice +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR +5t4wh...fsvz myservice.1 ubuntu node1 Running Running 2 mins +8d9b4...te27 myservice.2 ubuntu node2 Running Running 2 mins +``` + +이제 네트워크가 정상적으로 연결되어 서로 확인이 가능한지 보자. + +node1에 접속하여 `docker ps`를 수행하게 되면, 위에 생성해둔 Myservice 서비스 중 node1에서 돌고 있는 서비스가 확인이 된다. + +```bash +node1$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES +053abaac4f93 ubuntu:latest "sleep infinity" 19 mins ago Up 19 mins myservice.2.8d9b4i6vnm4hf6gdhxt40te27 +``` + +위에서 얻은 container ID로 docker를 execute하여, ping을 설치하고 node2 IP로 ping을 보내보면 응답을 받는 것을 확인할 수 있다. + +```bash +node1$ docker exec -it 053abaac4f93 /bin/bash +root@053abaac4f93:/# apt-get update && apt-get install iputils-ping + +root@053abaac4f93:/# ping 10.0.0.4 // node2 IP 주소 +PING 10.0.0.4 (10.0.0.4) 56(84) bytes of data. +64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=0.726 ms +64 bytes from 10.0.0.4: icmp_seq=2 ttl=64 time=0.647 ms +^C +--- 10.0.0.4 ping statistics --- +2 packets transmitted, 2 received, 0% packet loss, time 999ms +rtt min/avg/max/mdev = 0.647/0.686/0.726/0.047 ms +``` +이를 통해 node1과 node2가 정상적으로 네트워크를 공유하고 있음을 확인할 수 있다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/ed6fb619-cfd3-4ed3-973a-e46a056396be) + +--- +참고 +- https://docs.docker.com/network/network-tutorial-overlay/ +- https://docs.docker.com/network/drivers/overlay/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Private\342\200\205registry\342\200\205\352\265\254\354\266\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Private\342\200\205registry\342\200\205\352\265\254\354\266\225.md" new file mode 100644 index 00000000..1991496d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Private\342\200\205registry\342\200\205\352\265\254\354\266\225.md" @@ -0,0 +1,87 @@ +--- +title: 'Private registry 구축' +lastUpdated: '2023-06-07' +--- + +# 🐳 Private registry 구축 + +내부 Private Cloud 환경에 적용가능한 Docker Private Registry를 구현해보자. 구현하는 이유와 목적은 다음과 같다. + +- Docker Hub등의 Public Registry의 경우 하나의 이미지만 private 등록이 가능하고 organization의 경우 비용을 지불해야 하지만, Private Registry는 제한이 없다. + +- 개인 공간에서 보다 많은 권한을 부여하여 사용할 수 있다. + +Docker Private registry는 내부망에 Registry를 쉽게 구축해서 프로젝트 단위의 이미지를 관리하기 위한 좋은 방법이다. + +### 1. Docker registry Images 가져오기 + +```bash +# docker pull registry:2 +Trying to pull repository docker.io/library/registry ... +latest: Pulling from docker.io/library/registry +c87736221ed0: Pull complete +1cc8e0bb44df: Pull complete +54d33bcb37f5: Pull complete +e8afc091c171: Pull complete +b4541f6d3db6: Pull complete +Digest: sha256:8004747f1e8cd820a148fb7499d71a76d45ff66bac6a29129bfdbfdc0154d146 +Status: Downloaded newer image for docker.io/registry:latest +``` + +`docker images`로 이미지를 확인해보자. + +### 2. Docket Registry 실행 + +```bash +# docker run -d -p 5000:5000 --restart=always --name registry registry:2 +``` + +docker run 명령어로 컨테이너를 실행한다. + +- --name은 docker image 이름 + +- -d daemon으로 (백그라운드) 실행 + +- -p 5000:5000 registry 실행 (local 5000번 포트 -> 이미지 5000번 포트로 바인딩) + +Docker registry가 잘 실행되었는지 확인해보자. + +``` +# docker ps -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +3d407c3736dd registry "/entrypoint.sh /e..." About a minute ago Up About a minute 0.0.0.0:5000->5000/tcp repo-registry +``` + +만약 레지스트리에 비밀번호를 설정하고 싶다면 이렇게 하면 된다. + +``` +# 사용자 정보 담을 파일 경로 생성 +cd ~ +mkdir .registry_auth + +# 사용자 정보담은 파일 생성 +docker run --entrypoint htpasswd httpd -Bbn {user} {password} > /home/admin/.registry_auth/htpasswd + +# registry 컨테이너 띄우기 +docker run -d \ +--name registry \ +--restart=always \ +-p 5000:5000 \ +-v /home/admin/registry_data:/var/lib/registry \ +-v /home/admin/registry_auth:/auth \ +-e "REGISTRY_AUTH=htpasswd" \ +-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ +-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ +registry +``` + +### 3. 이미지 push + +다음과 같은 형식으로 이미지를 build하고 push할 수 있다. + +Dockerfile이 있다고 가정했을때, 이렇게 해주면 된다. + +```bash +docker build -t {주소(IP:Port)}/{레포지토리 이름}:{버전} . +docker push {주소(IP:Port)}/{레포지토리 이름}:{버전} +``` diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Prune.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Prune.md" new file mode 100644 index 00000000..06cd9734 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/Prune.md" @@ -0,0 +1,63 @@ +--- +title: 'Prune' +lastUpdated: '2024-03-02' +--- + +Docker를 오랜 시간 사용하게 되면 여러가지 오브젝트들이 시스템에 쌓이게 된다. 컨테이너나 이미지는 많으면 수십 수백개까지도 늘어난다. Docker 컨테이너, 이미지, 볼륨은 사용중이 아니더라도 디스크를 차지하고 있다. + +오브젝트들을 일일히 삭제하거나 통째로 날려버릴 수도 있지만, 사용하지 않는 오브젝트들을 파악해 빠르게 시스템 자원을 확보하는 방법도 있다. prune 서브 커맨드가 바로 이런 역할을 한다. + +Prune 커맨드를 사용하면 사용하지 않는 컨테이너, 볼륨, 이미지를 일괄적으로 삭제할 수 있다. + +## container + +``` +docker container prune +``` + +`--filter` 옵션으로 특정 오브젝트만 삭제할 수도 있다. + +``` +# 중지된 지 1시간 이상 지난 컨테이너만 삭제 +docker container prune --filter until=1h + +# env 키가 있는 컨테이너 +docker container prune --filter label=env + +# env 키가 없는 컨테이너 +docker container prune --filter label!=env + +# env 키의 값이 development인 컨테이너 +docker container prune --filter label=env=development + +# env 키의 값이 production이 아닌 컨테이너 +docker container prune --filter label!=env=production +``` + +## image + +docker image prune 명령어가 삭제하고자 하는 대상은 dangling된 이미지들이다. 일반적으로 이미지는 이미지를 구분하기 위한 이름을 가지고 있는데, dangling된 이미지는 해당 이미지를 지칭하는 이름이 없는 상태를 의미한다. 예를 들어 같은 이름으로 도커 이미지를 여러번 빌드하다보면 새로 만들어진 이미지가 기존 이미지의 이름을 뺏어버려서, 기존 이미지는 dangling된 상태가 된다. + +dangling된 이미지 뿐만아니라 컨테이너에서 사용하고 있지 않은 이미지도 삭제하고 싶다면 `-a` 태그를 사용하면 된다. + +``` +# dangling된 이미지 삭제 +docker image prune + +# dangling된 이미지 삭제 +docker image prune -a +``` + +## 볼륨, network +``` +docker volume prune +docker network prune +``` + +## 전체삭제 + +아래 명령어를 치면, 사용하지 않는 도커 오브젝트가 모두 삭제된다. + +``` +docker system prune +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/README.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/README.md" new file mode 100644 index 00000000..b1b861bd --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/README.md" @@ -0,0 +1,13 @@ +--- +title: 'README' +lastUpdated: '2024-03-02' +--- +도커는 LXC(리눅스 컨테이너스)라는 커널 컨테이너 기술을 이용하여 만든 컨테이너 기술 중 하나로, 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 컨테이너를 모듈식 가상머신처럼 유연하게 사용하여 애플리케이션을 안정적으로 배포 및 구축할 수 있도록 한다. 또, 이미지 기반 배포 모델을 제공하고 여러 환경 전반에서 애플리케이션 또는 서비스를 모든 종속 항목과 손쉽게 공유할 수 있다. + +운영체제를 가상화하지 않는 컨테이너 기술이기 때문에 가상머신에 비해 가볍고, 한 대의 서버에 여러 애플리케이션을 실행하기 좋다. 단일한 물리적 컴퓨터 위에서 여러 애플리케이션을 돌릴 수 있기 때문에 물리적 하드웨어의 컴퓨팅 용량을 효율적으로 사용할 수 있다. 가상머신(VM)들과 달리, 기존 리눅스 자원(디스크, 네트워크 등)을 그대로 활용할 수 있어서 여러 서비스들을 한 서버에서 관리하기 더욱 수월하다. + + + +애플리케이션을 컨테이너에 올려 관리할때의 장점은 가상화와 컨테이너 문서에서 볼 수 있다. + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/dockersock\342\200\205\352\266\214\355\225\234\354\227\220\353\237\254.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/dockersock\342\200\205\352\266\214\355\225\234\354\227\220\353\237\254.md" new file mode 100644 index 00000000..fc21c969 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/dockersock\342\200\205\352\266\214\355\225\234\354\227\220\353\237\254.md" @@ -0,0 +1,28 @@ +--- +title: 'dockersock 권한에러' +lastUpdated: '2023-06-07' +--- +## docker 설치 후 /var/run/docker.sock의 permission denied 발생하는 경우 + +docker 설치 후 usermod로 사용자를 docker 그룹에 추가해도 permission denied가 발생했다. + +``` +# docker ps -a +Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/json?all=1: dial unix /var/run/docker.sock: connect: permission denied +``` + +### 해결 +/var/run/docker.sock 파일의 권한을 666으로 변경하여 그룹 내 다른 사용자도 접근 가능하게 변경한다! +``` +sudo chmod 666 /var/run/docker.sock +``` +또는 chown 으로 group ownership 변경 +``` +sudo chown root:docker /var/run/docker.sock +``` + + +- [Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock](https://stackoverflow.com/a/58433757/7110084) +- [Docker socket file ownership is set to root:docker](https://docs.datadoghq.com/security_monitoring/default_rules/cis-docker-1.2.0-3.15/#default-value) +- [Docker socket file permissions are set to 660 or more restrictively](https://docs.datadoghq.com/security_monitoring/default_rules/cis-docker-1.2.0-3.16/#default-value) +- [docker in docker의 Permission denied 문제](https://blog.dasomoli.org/docker-docker-in-docker%ec%9d%98-permission-denied-%eb%ac%b8%ec%a0%9c/) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/exec\342\200\205user\342\200\205process\342\200\205caused\342\200\205exec\342\200\205format\342\200\205error.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/exec\342\200\205user\342\200\205process\342\200\205caused\342\200\205exec\342\200\205format\342\200\205error.md" new file mode 100644 index 00000000..9099cd30 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/exec\342\200\205user\342\200\205process\342\200\205caused\342\200\205exec\342\200\205format\342\200\205error.md" @@ -0,0 +1,54 @@ +--- +title: 'exec user process caused exec format error' +lastUpdated: '2024-03-02' +--- + +docker image build시 이런 에러가 날 때가 있다. + +```bash +exec user process caused “exec format error” +``` + +m1으로 빌드한 이미지를 서버가 arm 운영체제인 상황에서 돌리려고 할 떄 나는 에러이다. + +이 경우 이미지 빌드시 플랫폼을 지정해줌으로써 해결할 수 있다. + +```bash +docker buildx build --platform=linux/amd64 ... +``` + +## [Buildx](https://github.com/docker/buildx) + +Docker는 multi-architecture 빌드 등, 다양한 빌드 옵션을 지원하는 CLI 플러그인인 Buildx를 제공한다. Docker Desktop을 사용하는 Windows나 MacOS 사용자 혹은 DEB, RPM 패키지로 도커를 설치한 사용자들은 자동으로 Buildx 플러그인이 같이 설치된다. + +docker buildx 명령어를 터미널에 입력했을 때, 다음과 같은 화면이 출력된다면 buildx를 사용할 수 있다. + +```bash +$ docker buildx + +Usage: docker buildx [OPTIONS] COMMAND + +Extended build capabilities with BuildKit + +Options: + --builder string Override the configured builder instance + +Management Commands: + imagetools Commands to work on images in registry + +Commands: + bake Build from a file + build Start a build + create Create a new builder instance + du Disk usage + inspect Inspect current builder instance + ls List builder instances + prune Remove build cache + rm Remove a builder instance + stop Stop builder instance + use Set the current builder instance + version Show buildx version information + +Run 'docker buildx COMMAND --help' for more information on a command +``` + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\352\260\200\354\203\201\355\231\224\354\231\200\342\200\205\354\273\250\355\205\214\354\235\264\353\204\210.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\352\260\200\354\203\201\355\231\224\354\231\200\342\200\205\354\273\250\355\205\214\354\235\264\353\204\210.md" new file mode 100644 index 00000000..1fe6318a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\352\260\200\354\203\201\355\231\224\354\231\200\342\200\205\354\273\250\355\205\214\354\235\264\353\204\210.md" @@ -0,0 +1,57 @@ +--- +title: '가상화와 컨테이너' +lastUpdated: '2024-03-02' +--- +# 가상화 + + 가상화는 단일 컴퓨터의 프로세서, 메모리, 스토리지 등과 같은 하드웨어 요소를 가상 머신(VM, Virtual Machine)이라고 하는 다수의 가상 컴퓨터로 분할할 수 있도록 해주는 추상화 계층을 구축하는 기술을 말한다. 실제로는 컴퓨터의 일부에서만 실행됨에도 불구하고, 각각의 VM은 **자체적으로 운영체제(Guest OS)를 실행**하며 마치 여러개의 컴퓨터인 것 처럼 독립적으로 작동된다. + + ## ✔️가상화의 장점 + ### 1. 리소스 효율성 + 가상화 이전에는 애플리케이션마다 별도의 서버를 구성해야했다. 그렇기 때문에 한 애플리케이션이 유휴상태에 있을때는 서버가 활용되지 못한 채 방치된다. 하지만 서버 가상화를 사용하면 단일한 물리적 컴퓨터 위에서 여러 애플리케이션을 돌릴 수 있기 때문에 물리적 하드웨어의 컴퓨팅 용량을 효율적으로 사용할 수 있다. + ### 2. 관리 편의성 + 기존에는 각각의 애플리케이션마다 서버를 맞춤형으로 구축하여, 새로 배포하거나 환경설정을 바꿀 때 시간이 오래 걸렸지만 해당 설정과 정책들이 소프트웨어로 구성되어있는 가상화 환경에선 서버에 설치되어있는 애플리케이션을 VM단위로 나누어 보다 손쉽게 사용하고 관리할 수 있다. + + ## ❌ 가상화의 단점 + ### 1. OS의 메모리 차지 + 가상화 환경에서의 VM에는 Guest OS가 각각 설치되어야 하기 때문에 운영체제를 위해 대량의 메모리를 소모해야한다. + ### 2. 시스템 운영에 대한 통합적인 지식 필요(DevOps) + 일반적인 시스템에서는 소프트웨어 개발과 시스템 운영이 분리가 되어있지만, 하지만 가상머신체제에서는 이미 만들어진 시스템 위에 새로운 시스템을 구축하기 때문에 시스템 운영에 대한 통합적인 지식과 소프트웨어 개발 지식을 모두 갖고있어야 가상화 시스템을 관리할 수 있다. + +# 컨테이너 +컨테이너는 애플리케이션을 구동하는 환경을 격리한 공간을 의미한다. 컨테이너는 하이퍼바이저와 Guest OS를 필요로 했던 기존의 가상화에서 더 발전하여, **프로세스**를 격리하여 모듈화된 프로그램 패키지로서 작동한다. +컨테이너도 VM처럼 프로세싱을 위한 별도의 공간(private space)과 권한, IP를 갖추고 있다는 점에서 비슷하지만 컨테이너는 호스트 시스템의 커널을 다른 컨테이너들과 공유한다는 점에서 차이가 있다. + + ## ✔️ 컨테이너의 장점 + ### 1. 시스템 성능 부하가 적음 + 컨테이너의는 생성, 실행되었을때 마치 운영체제 위에서 하나의 어플리케이션이 동작하는 것과 동일한 수준의 컴퓨팅 자원만을 필요로 한다. 시스템은 기존 응용프로그램을 실행시키는 것과 비슷한 만큼의 여유자원만 있으면 된다. + ### 2. 이동성이 높음 + 컨테이너는 이동성이 높기 때문에 개발자가 자신의 PC에서 만든 컨테이너를 그대로 퍼블릭 클라우드에 가져가 실행할 수 있다. + + ## ❌ 컨테이너의 단점 + ### 1. 자원의 격리가 어려움 + 가상머신은 가상 하드웨어를 직접 제어할 수 있기 때문에 자원을 비교적 철저히 격리할 수 있었지만, 컨테이너는 자원이 크게 격리되어있지 않아 보안적으로 더 취약하다. + ### 2. 호스트 운영체제의 제약을 받음 + 컨테이너는 호스트 운영체제의 커널을 공유하기 때문에 호스트 운영체제 환경이 유지된다. 예를 들어, 리눅스에 컨테이너를 띄우면 리눅스 기반의 컨테이너만 가동할 수 있다. + + +# 📈 기술 발전과정 + + +### 전통적인 배포 +하나의 물리서버에 애플리케이션을 배포하는 방식이다. 한 물리 서버에서 여러 애플리케이션의 리소스 한계를 정의할 방법이 없었기 때문에 리소스 할당 문제가 생길 수 있고, 리소스가 효율적으로 활용되지 않는다. + +### 가상화 배포 +Host OS위에 여러 가상 머신(VM)을 실행시키는 방법이다. VM간에 애플리케이션이 격리되어 일정 수준의 보안이 보장된다. 전통적인 배포방식보다 리소스를 효율적으로 사용할 수 있고, 애플리케이션을 추가하거나 업데이트하기 쉽다. 하지만 가상환경에 Guest OS를 통째로 설치하기 때문에 자원이 불필요하게 소모될 수 있다. + +### 컨테이너 배포 +컨테이너 실행을 담당하는 컨테이너 런타임이라는 소프트웨어 위에 Guest OS 없이 애플리케이션을 올리는 방식이다. 컨테이너에서도 OS가 실행되기는 하지만 각 컨테이너가 OS를 공유하기 때문에 가상화 방식보다 가볍다. 또한 Guest OS가 없기 때문에 OS 실행 없이 별도의 환경에서 애플케이션 실행이 가능하다. + + + + --- + +참고:
+컨테이너 가상화 - IT위키 https://itwiki.kr/w/%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88_%EA%B0%80%EC%83%81%ED%99%94
+가상화의 장단점(토론) https://www.sharedit.co.kr/qnaboards/21498
+ diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254.md" new file mode 100644 index 00000000..b0ee39fc --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254.md" @@ -0,0 +1,31 @@ +--- +title: '도커 네트워크' +lastUpdated: '2024-03-02' +--- + +Docker 컨테이너(container)는 격리된 환경에서 돌아가기 때문에 기본적으로 다른 컨테이너와의 통신이 불가능하다. 하지만 여러 개의 컨테이너를 하나의 Docker 네트워크(network)에 연결시키면 서로 통신이 가능해잔다. 다시말해, Docker 네트워크는 컨테이너 간 네트워킹이 가능하도록 도와주는 역할을 한다. + +## 네트워크 종류 + +Docker는 목적에 따라 다양한 종류의 네트워크 드라이버를 지원한다. 그 종류에 대해 알아보자. + +- none + noen 네트워크에서 도커 컨테이너는 서로 독립되어서, 정보를 전혀 주고받을 수 없다. 호스트 외부로 접근하는 경우에도 네트워크가 연결되지 않는다. + +- host + host 네트워크에서는 host와 컨테이너의 네트워크의 구분 없이 서로 면결된다. 만약 컨테이너가 80 포트로 listening하고 있다면 호스트의 80 포트로 들어오는 요청을 같이 받을 수 있다. 추가적인 포트 매핑이 필요 없다. + 다른 컨테이너의 80 포트를 열어놓으면, 충돌되어 생성되지 않는다. + +- bridge + + bridge 네트워크는 컨테이너 사이에 임의의 네트워크가 구성되어있는 형태이다. 각 컨테이너끼리는 기본적으로 `127.0.0.1` (localhost)를 통해 각각 네트워킹할 수 있다. + + 아까 예시로 보았던 `docker network ls` 결과의 bridge가 바로 이 bridge를 의미하는 것이다. + + 호스트에서는 이 네트워크를 `docker0`라는 이름으로 생성하고, `172.17.0.1` IP를 할당한다. docker0는 호스트 내부에서 컨테이너간의 통신을 조정하는 스위치와 같은 역할을 한다. 도커는 컨테이너가 생성될 때 마다 해당 컨테이너에 인터페이스를 생성하고 bridge에 연결하는 과정을 자동으로 수행해준다. 자세한 구현에 대한 것은 [network namespace](../linux/network namespaces.md)에 대한 내용을 참고하면 좋다. + + 외부의 트래픽을 컨테이너의 특정 포트로 연결해주는 포트포워딩 기능도 제공한다. (내부적으로는 iptable을 사용한다.) + + ``` + docker run -p 8080:80 nginx + ``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\353\252\205\353\240\271\354\226\264.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\353\252\205\353\240\271\354\226\264.md" new file mode 100644 index 00000000..2320741e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\353\252\205\353\240\271\354\226\264.md" @@ -0,0 +1,110 @@ +--- +title: '도커 네트워크 명령어' +lastUpdated: '2024-03-02' +--- + +Docker 컨테이너(container)는 격리된 환경에서 돌아가기 때문에 기본적으로 다른 컨테이너와의 통신이 불가능하다. 하지만 여러 개의 컨테이너를 하나의 Docker 네트워크(network)에 연결시키면 서로 통신이 가능해진다. 컨테이너 간 네트워킹이 가능하도록 도와주는 Docker 네트워크에 대해 알아보도록 하자. + +## 네트워크 조회 + +`docker network ls` 명령어를 사용하면 현재 생성되어 있는 Docker 네트워크 목록을 조회할 수 있다. + +```bash +$ docker network ls +NETWORK ID NAME DRIVER SCOPE +143496b94e57 bridge bridge local +311d6534f79f host host local +aa89f58200a6 none null local +``` + +bridge, host, none은 Docker 데몬(daemon)이 실행되면서 디폴트로 생성되는 네트워크이다. 대부분의 경우에는 이러한 디폴트 네트워크를 이용하는 것 보다는 사용자가 직접 네트워크를 생성해서 사용하는 것이 권장된다. + +network의 driver는 해당 네트워크의 작동 방식에 대한 것으로, [여기](../Docker/도커 네트워크.md)에서 더 자세한 내용을 볼 수 있다. + +## 네트워크 생성 + +먼저 docker network create 커맨드를 사용해서 새로운 Docker 네트워크를 생성해보자. + +```bash +$ docker network create our-net +x6wfa4e9a5ec85abcb484662sdc30a3sdc76df21svsdqw76d52fac39faqw8412zz68 +$ docker network ls +NETWORK ID NAME DRIVER SCOPE +143496b94e57 bridge bridge local +311d6534f79f host host local +aa89f58200a6 none null local +e6dfe4a9a5ec our-net bridge local +``` + +추가된 네트워크는 docker network ls 커맨드로 확인할 수 있다. -d 옵션을 사용하지 않았기 때문에 기본값인 bridge 네트워크로 생성된 것을 볼 수 있다. + +## 상세 정보 확인 + +`docker network inspect`로 네트워크의 상세 정보를 확인할 수 있다. + +```json +$ docker network inspect our-net +[ + { + "Name": "our-net", + "Id": "e6dfe4a9a5ec85abcb484662c30a3a0fc76df217dde76d52fac39fae8412ca68", + "Created": "2020-04-26T19:23:04.563643516Z", + "Scope": "local", + "Driver": "bridge", + "EnableIPv6": false, + "IPAM": { + "Driver": "default", + "Options": {}, + "Config": [ + { + "Subnet": "172.19.0.0/16", + "Gateway": "172.19.0.1" + } + ] + }, + "Internal": false, + "Attachable": false, + "Ingress": false, + "ConfigFrom": { + "Network": "" + }, + "ConfigOnly": false, + "Containers": {}, + "Options": {}, + "Labels": {} + } +] +``` + +아직 컨테이너에 연결하지 않았기 떄문에 `Containers` 항목이 비어있다. + +## 네트워크에 컨테이너 연결 + +```bash +docker network connect [OPTIONS] NETWORK CONTAINER +# docker network connect our-net one +``` + +our-net 네트워크의 상세 정보를 확인해보면 one이라는 컨테이너가 연결되어있는 것을 볼 수 있다. + +```json +$ docker network inspect bridge + ... + "Containers": { + "660bafdce2996378cde070dfd894731bb90745e46d2ab10d6504c0cc9f4bdea9": { + "Name": "one", + "EndpointID": "40b4bbd8385debf86eef2fc2136315e1a82fa1ef72877bfae25477d6e8e46726", + "MacAddress": "02:42:ac:11:00:04", + "IPv4Address": "172.17.0.4/16", + "IPv6Address": "" + }, + }, + ... +``` + +생성하는 동시에 연결하고싶다면 이렇게 할 수 있다. + +``` +$ docker run -itd --name two --network our-net busybox +0e7fe8a59f9d3f8bd545d3e557ffd34100a09b8ebe92ae5a375f37a5d072873d +``` diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\252\205\353\240\271\354\226\264.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\252\205\353\240\271\354\226\264.md" new file mode 100644 index 00000000..b2d0ee2f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\353\252\205\353\240\271\354\226\264.md" @@ -0,0 +1,70 @@ +--- +title: '도커 명령어' +lastUpdated: '2023-06-07' +--- + +유용한 도커 명령어를 모아놓은 파일입니다 + +#### 도커 빌드(dos) +```js +docker build --build-arg DEPENDENCY=build/dependency -t ${유저명}/${레포명} --platform linux/amd64 . +docker push ${유저명}/${레포명} +``` + +#### 도커 설치, 실행(linux) +```js +sudo yum install docker +sudo systemctl start docker +``` + +#### 도커 허브에서 이미지 pull 및 실행(linux) +```js +sudo docker pull ${유저명}/${레포명} +sudo nohup docker run -p 8080:8080 ${유저명}/${레포명} & +``` + +#### 환경변수 파일로 등록 +```js +sudo nohup docker run --env-file ${파일경로} -p 8080:8080 ${유저명}/${레포명} & +``` + +#### 포트 추가 +```js +sudo nohup docker run -p 8080:8080 -p 8081:8081 ${유저명}/${레포명} & +``` + +--- + +#### 컨테이너, 이미지 전체 삭제 + +dos +```js +FOR /f "tokens=*" %i IN ('docker ps -a -q') DO docker rm %i + +FOR /f "tokens=*" %i IN ('docker images -q -f "dangling=true"') DO docker rmi %i +``` + +linux +```js +sudo docker rm -f $(sudo docker ps -aq) + +sudo docker rmi $(sudo docker images -q) +``` + +--- + +#### 도커 삭제 + +linux에서 컨테이너, 이미지 지워준 후 Docker 관련 애플리케이션 검색 +```js +sudo yum list installed | grep docker +``` + +검색 결과로 나온 패키지 삭제 +```js +$ sudo yum remove containerd.io.x86_64 +$ sudo yum remove docker-ce.x86_64 +$ sudo yum remove docker-ce-cli.x86_64 +``` + +컨테이너와 관련된 애플리케이션을 모두 삭제하면, 도커가 완전히 삭제된다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\354\212\244\355\206\240\353\246\254\354\247\200.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\354\212\244\355\206\240\353\246\254\354\247\200.md" new file mode 100644 index 00000000..ea7b75b0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\354\212\244\355\206\240\353\246\254\354\247\200.md" @@ -0,0 +1,47 @@ +--- +title: '도커 스토리지' +lastUpdated: '2024-03-02' +--- + +여러 작업 중 컨테이너 내부에서 생성한 정보, 파일은 컨테이너가 종료된 후에 모두 사라진다. 이러한 데이터를 저장하려면 별도의 저장공간이 필요한데, 컨테이너가 동작 중인 상태에서는 접근이 제한되기 때문에 직접 옮기기는 쉽지 않다. + +도커는 **스토리지**에 파일을 저장하여 컨테이너가 종료되더라도 파일을 유지할 수 있도록 한다. 스토리지는 원본 이미지를 수정 하는 대신 **변경된 정보를 따로 저장**하고, **원본 데이터와 변경된 정보를 조합해서 복원**하는 식으로 데이터를 읽는다. 이렇게 하여 원본 이미지를 수정 할 필요 없이 각 컨테이너마다 다른 정보를 저장 할 수 있다. + +## 스토리지 종류 + +도커 스토리지의 종류는 3가지가 있다. Volume, bind mount, tmpfs mount이다. 아래 그림은 각 방식을 나타내는 그림이다. + +![image](https://user-images.githubusercontent.com/81006587/201569330-d2e34b09-53cb-47e0-a3be-be05f7dbb4c9.png) + +|종류|설명| +|-|-| +|Volume|도커에 의해 관리되는 호스트 파일 시스템에 저장하는 방식이다. 호스트 내의 도커 프로세스가 아닌 다른 프로세스들은 Volume 관련 파일 시스템을 수정할 수 없다.| +|Bind mounts|호스트 파일 시스템의 특정 경로를 컨테이너로 마운트하는 방식이다. Volume만큼 해당 파일이 엄격하게 관리되진 않아서, 도커 내의 모든 프로세스가 접근 수정할 수 있다.| +|tmpfs mounts|호스트 시스템의 메모리에 저장하는 방식이다.| + +### 1. Volume + +볼륨은 Docker 컨테이너에서 생성하고 사용하는 데이터를 유지하기 위한 기본적인 방법이다. Docker에서 완전히 관리하고 있는 공간에 저장된다. Volume의 특징은 다음과 같다. + +- 보안이 철저하고, 백업이나 마이그레이션이 쉽다. +- Docker CLI나 Docker API 명령어를 사용해 관리할 수 있다. +- 다른 OS간에 정보를 옮길 수 있다. (ex: `Linux -> Window`) +- 성능이 좋다. +- 컨테이너가 정지 또는 제거되더라도 그에 대한 volume은 삭제되지 않는다. +- 여러 컨테이너들이 동일 volume에 대하여 마운트 할 수 있다. + +### 2. bind mounts + +일반적인 경우에는 Volume을 사용하는 것을 추천합니다. 하지만 특정 경우에 한 해 bind mount를 사용하는 경우가 있습니다. 몇몇 유스케이스는 아래와 같습니다. + +- Host Machine에서 Container로 설정 파일을 공유해야하는 경우 + Docker는 Host Machine의 /etc/resolv.conf를 각 Container에 bind mount하여 DNS Resolution을 제공하고 있다. +- Docker Host 개발 환경과 Container 간 소스코드 또는 빌드된 아티팩트를 공유하는 경우 + Maven의 target/ 디렉토리를 Container에 Mount하면 Docker Host에서 Maven Project를 빌드할 때마다, Container에서 재작성된 JAR/WAR에 접근할 수 있다. + 만약 이런 방식으로 개발을 진행한다면, Production Dockerfile은 bind mount에 의존하지 않고도, Production-Ready 아티팩트를 Image에 직접 복사할 수 있다. +- Docker Host의 파일 또는 디렉토리 구조가 Container가 요구하는 bind mount와 일치하는 경우 +- 호스트 시스템에서 컨테이너로 구성 파일을 공유하는 경우 + +### 3. tmpfs mounts + +tmpfs 마운트는 데이터가 호스트 시스템이나 컨테이너 내에서 유지되지 않도록 하려는 경우에 사용된다.보안적으로 오래 유지하면 안되는 데이터거나, 애플리케이션이 많은 양의 비영구 상태 데이터를 써야 할때 성능상 이점을 위해 사용할 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" new file mode 100644 index 00000000..98686518 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/Docker/\353\217\204\354\273\244\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" @@ -0,0 +1,74 @@ +--- +title: '도커 아키텍처' +lastUpdated: '2024-03-02' +--- + +도커는 client-server architecture를 사용한다. + +도커 Client와 Daemon은 UNIX Socket 또는 Network Interface를 기반으로하는 REST API를 사용하여 커뮤니케이션한다. Client에서는 Docker Daemon에 명령어를 보내서, 컨테이너를 빌드, 실행 및 배포하도록 한다. + +도커 Client와 Daemon은 같은 시스템 안에서 실행될 수도 있고, remote Docker Daemon에 Client를 연결하여 사용할 수도 있다. + +아래 사진을 보며 도커 아키텍처 구조에 대해 더 상세히 알아보자. + +![image](https://user-images.githubusercontent.com/81006587/201558603-b3b6f3ab-015f-4957-b32b-d4b82dd7f7bb.png) + +# Docker Daemon +도커 데몬(dockerd)는 Docker API 요청을 받고, image/container/network/volume과 같은 도커 Object를 관리한다 도커 서비스를 관리하는 다른 데몬과 커뮤니케이션할 수 있다. + +# Docker Client +도커 클라이언트(Docker)는 도커 유저가 도커와 Interact할 수 있는 주요 방법 중의 하나이다. +'docker run'이라는 커맨드를 입력하게 되면, 클라이언트는 이 커맨드를 dockerd으로 보내고, dockerd에서 이 커맨드를 실행한다. +클라이언트는 Docker API를 사용하며, 하나 이상의 데몬과 커뮤니케이션이 가능하다. + +# Docker Registries +도커 레지스트리는 **도커 이미지(image)를 저장할 수 있는 공간**이다. +Docker Hub는 누구나 이용가능한 public registry이며, 기본적으로 도커는 Docker Hub에서 이미지를 찾도록 설정되어 있다. 물론, 개인 Registry를 사용할 수도 있다. + +# Docker Objects +Docker를 사용하면 이미지, 컨테이너, 네트워크, 스토리지 및 기타 개체를 만들고 사용하게 된다. 이러한 것들을 도커 Objects라고 한다. + +각 Object의 역할은 다음과 같다. + +### 이미지 + +이미지는 도커 컨테이너의 정보를 담은 템플릿이다. 특정 구문을 사용한 Dockerfile 을 만들면 도커가 그에 대한 이미지를 만들어주고, 그 이미지의 정보를 읽으면 그에 따른 컨테이너를 생성할 수 있다. + +### 컨테이너 + +컨테이너는 이미지를 실행한 애플리케이션 인스턴스이며, 다른 컨테이너 및 해당 호스트 시스템과 격리되어있는 하나의 공간이다. + +Docker API 또는 CLI를 사용하면 컨테이너를 생성, 시작, 중지, 이동 또는 삭제할 수 있다. 그리고 컨테이너를 하나 이상의 네트워크에 연결하거나, 스토리지를 연결하거나, 현재 상태를 기반으로 새 이미지를 생성할 수도 있다. + +### 네트워크 + +Docker 네트워킹은 격리된 모든 컨테이너간의 통신 통로이다. docker 네트워크에는 주로 5가지의 종류가 있다.
+ +**Bridge:** +- docker0 네트워크와 같다. +- 같은 브릿지에 있는 컨테이너끼리는 통신이 가능하게 해주고, 다른 브릿지는 통신할 수 없도록 한다 +- 컨테이너를 생성하면 Bridge가 디폴트로 설정된다. + +**Host:** +- 호스트의 네트워크 환경을 그대로 사용하는 방식이다. +- 주로 컨테이너가 한개일 때 유용하다. +- 호스트의 네트워크를 그대로 사용하기 때문에 포트포워딩이 필요 없다. + +**none:** +- 네트워크를 사용하지 않는다. +- `--net=none` + +**container:** +- 다른 컨테이너의 네트워크 환경을 공유한다. + +**Overlay:** +- 분산된 네트워크(호스트가 여러개인 상황)에서 도커를 사용해야 할 떄 사용한다. +- 각 머신에서 swarm mode가 활성화되어야 한다 (도커가 도커엔진 다수를 관리할 수 있도록 하는 방법) + +### 스토리지 + +여러 작업 중 컨테이너 내부에서 생성한 정보, 파일은 컨테이너가 종료된 후에 모두 사라진다. 이러한 데이터를 저장하려면 별도의 저장공간이 필요한데, 컨테이너가 동작 중인 상태에서는 접근이 제한되기 때문에 직접 옮기기는 쉽지 않다. + +도커는 **스토리지**에 파일을 저장하여 컨테이너가 종료되더라도 파일을 유지할 수 있도록 한다. 스토리지는 원본 이미지를 수정 하는 대신 **변경된 정보를 따로 저장**하고, **원본 데이터와 변경된 정보를 조합해서 복원**하는 식으로 데이터를 읽는다. 이렇게 하여 원본 이미지를 수정 할 필요 없이 각 컨테이너마다 다른 정보를 저장 할 수 있다. + +도커 스토리지의 종류는 여기에서 더 알아보자. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/cAdvisor.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/cAdvisor.md" new file mode 100644 index 00000000..38e0b2de --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Container/cAdvisor.md" @@ -0,0 +1,23 @@ +--- +title: 'cAdvisor' +lastUpdated: '2024-03-02' +--- + +Kubernetes(k8s)에서 cAdvisor는 "Container Advisor"의 줄임말로, 컨테이너 메트릭을 수집하고 모니터링하기 위한 도구이다. cAdvisor는 Google에서 개발한 오픈 소스 프로젝트로, 컨테이너화된 환경에서 작동하는 애플리케이션 및 서비스의 성능을 실시간으로 추적하고 이해하는 데 도움을 준다. + +## 주 기능 + +- **컨테이너 메트릭 수집**: cAdvisor는 호스트 시스템에서 실행 중인 각 컨테이너에 대한 메트릭을 수집한다. 이 메트릭은 CPU 사용량, 메모리 사용량, 디스크 I/O, 네트워크 사용량 및 다른 성능 관련 정보를 포함한다. +- **시각화**: cAdvisor는 수집된 메트릭을 시각적으로 표시하여 사용자가 컨테이너의 성능과 리소스 사용 상태를 쉽게 이해할 수 있도록 도와준다. +- **리소스 모니터링**: cAdvisor는 각 컨테이너의 리소스 사용량을 모니터링하고 경고를 설정하여 리소스 부족 현상을 감지할 수 있다. +- **컨테이너 및 호스트 정보 제공**: cAdvisor는 실행 중인 컨테이너와 호스트 시스템에 대한 정보를 제공하여 컨테이너 환경을 더 잘 이해하고 관리할 수 있게 해준다. + +Kubernetes 클러스터에서 cAdvisor는 kubelet과 함께 실행되며, kubelet은 각 노드에서 cAdvisor를 통해 컨테이너 메트릭을 수집하고 이러한 정보를 Kubernetes 마스터 노드에 보고한다. 마스터 노드에서는 이 정보를 사용하여 컨테이너의 상태 및 성능을 모니터링하고 관리한다. + +k8s와 함께 쓰는 것이 아니더라도 Docker container를 모니터링하기 위해 따로 설치하여 사용할 수 있는 툴이다. + + + +--- +참고 +- https://www.kubecost.com/kubernetes-devops-tools/cadvisor/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/DR/DR\342\200\205strategies.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/DR/DR\342\200\205strategies.md" new file mode 100644 index 00000000..2adfd1b0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/DR/DR\342\200\205strategies.md" @@ -0,0 +1,68 @@ +--- +title: 'DR strategies' +lastUpdated: '2024-03-02' +--- + +DR is a crucial part of your Business Continuity Plan. How can we architect for disaster recovery (DR), which is the process of preparing for and recovering from a disaster? + +Because a disaster event can potentially take down your workload, your objective for DR should be bringing your workload back up or avoiding downtime altogether. We use the following objectives: + +- Recovery time objective (RTO): The maximum acceptable delay between the interruption of service and restoration of service. This determines an acceptable length of time for service downtime. +- Recovery point objective (RPO): The maximum acceptable amount of time since the last data recovery point. This determines what is considered an acceptable loss of data. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/662360ab-2732-42a6-9043-13683496de51) + +For RTO and RPO, lower numbers represent less downtime and data loss. However, lower RTO and RPO cost more in terms of spend on resources and operational complexity. Therefore, you must choose RTO and RPO objectives that provide appropriate value for your workload. + +## DR strategies + +## Backup and restore + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/eddbe7fb-1f4b-42e5-952b-c4975693ee08) + +- Lower priority usecases +- Provision all AWS resources after event +- Restore backups after event +- cost: $ + +- Backups are created in the same Region as their source and are also copied to another Region. This gives you the most effective protection from disasters of any scope of impact. + +- The backup and recovery strategy is considered the least efficient for RTO. However, you can use AWS resources like Amazon EventBridge to build serverless automation, which will reduce RTO by improving detection and recovery + +## Pilot Light + +image + +- Data live +- Services idle +- Provision some AWS resources and sacle after event +- cost: $$ + +- With the pilot light strategy, the data is live, but the services are idle. + - Live data means the data stores and databases are up-to-date (or nearly up-to-date) with the active Region and ready to service read operations. +- But as with all DR strategies, backups are also necessary. In the case of disaster events that wipe out or corrupt your data, these backups let you “rewind” to a last known good state. + +## Warm Standby + +image + +- Always running, but smaller +- Business critical +- Scale AWS resources after event +- cost: $$$ + +- Like the pilot light strategy, the warm standby strategy maintains live data in addition to periodic backups. The difference between the two is infrastructure and the code that runs on it. +- A warm standby maintains a minimum deployment that can handle requests, but at a reduced capacity—it cannot handle production-level traffic. + +## Multi-site active/active + +image + +- With multi-site active/active, two or more Regions are actively accepting requests. +- Failover consists of re-routing requests away from a Region that cannot serve them. +- Here, data is replicated across Regions and is actively used to serve read requests in those Regions. For write requests, you can use several patterns that include writing to the local Region or re-routing writes to specific Regions. + +--- +reference +- https://aws.amazon.com/ko/blogs/architecture/disaster-recovery-dr-architecture-on-aws-part-i-strategies-for-recovery-in-the-cloud/ +- https://aws.amazon.com/ko/blogs/architecture/disaster-recovery-dr-architecture-on-aws-part-iii-pilot-light-and-warm-standby/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/DR/Fail\342\200\205over\354\231\200\342\200\205\354\204\234\353\262\204\342\200\205\354\235\264\354\244\221\355\231\224.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/DR/Fail\342\200\205over\354\231\200\342\200\205\354\204\234\353\262\204\342\200\205\354\235\264\354\244\221\355\231\224.md" new file mode 100644 index 00000000..88e9e1b0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/DR/Fail\342\200\205over\354\231\200\342\200\205\354\204\234\353\262\204\342\200\205\354\235\264\354\244\221\355\231\224.md" @@ -0,0 +1,51 @@ +--- +title: 'Fail over와 서버 이중화' +lastUpdated: '2023-07-24' +--- +## Fail over + +- fail over는 장애 대비 기능을 의미한다. +- 컴퓨터 서버, 시스템, 네트워크 등에서 이상이 생겼을 때 미리 준비했던 다른 시스템으로 자동으로 전환해서 무정지 시스템을 운영하여 fail over할 수 있다. +- failback : 페일오버에 따라 전환된 운용환경을 장애 발생 전 상태로 되돌리는 처리 +- High Availability(서비스를 정상적으로 사용 가능한 정도를 의미)을 요구할 때 구성한다. +- 웹 서버, 네트워크 장비, DB 등 다양한 영역에 대비한다. 하드웨어적인 장애 뿐만 아니라 소프트웨어를 포함한 서비스가 운영되지 않은 모든 장애를 포함한다. + +## 서버 이중화 + +### Active-Active + +- 주로 부하 분산 등의 목적, 서비스 단위를 나누어서 분산 +- 다중화 장비 두 대가 모두 활성화 되어 동작하는 구성 +- 많은 비용이 필요하지만 높은 수준의 안정성을 확보할 수 있다. 사용자가 많고 안정성을 우선시 한다면 사용하는 방식이다. +- 부하 분산 등을 목적으로 주로 활용하며, 서비스 단위를 나누어서 분산시키기도 한다. + +단점 +- Active-Standby에 비해 처리률이 높지만, 설정 및 구성이 복잡해진다. +- 한대에 장애가 생겼을 경우, 두 대의 처리량을 한 대가 처리해야하기 때문에 리소스 관리에 대한 계획이 있어야 한다. + +### Active-Standby + +- 한대는 평상시 운영하는 서버이고, 그와 비슷한 스펙의 서버를 대기 상태(Standby)로 준비한다. +- 대기중인 서버는 OS가 설치된 형태로 언제라도 부팅하면 바로 사용할 수 있는 상태, 장애(클러스터 하트비트 등으로 시스템의 상태를 주기적으로 체크하고 특이사랑 발생시 수동으로 전환하거나, 크리티컬한 장애 발생 시 자동으로 서비스를 전환)시 서비스를 이전하여 운영한다. +- 즉각적인 failover를 위해 주로 구성 +- 장애 발생시 Standby는 Active로 변경된다. Failover 되는 시간은 서비스가 불가능하다. +- Active-Standby는 Standby 방식에 따라 아래와 같이 세 종류로 나뉜다. + - Hot Standby - Standby 가동 후 즉시 이용 가능 + - Warm Standby - Standby 가동 후 이용 가능하게 하기 위해서 준비가 필요 + - Cold Standby - Standby를 정지시켜 두는 구성, 필요시 직접 켜서 구성 +- Hot - Warm - Cold 순으로 Failover 소요시간이 짧다 + +- 서버 이중화에서 주의점은 같은 데이터가 여러 개 존재한다는 것이다. 어떤 데이터가 올바른 것인지 또는 어떤 데이터가 최신 데이터인지 제대로 관리해야 한다. +- 또한 여러 개의 데이터가 정확하게 일치하고 있는 상태를 유지해야 한다. +- 이를 위한 대책으로는 Shared Disk와 스토리지 비공유 방식 Shared Nothing 방식이 있다. + +### Shared Disk +- 공유 스토리지를 하나 두고 이용하는 방식이다. +- Shared Disk 방식은 스토리지를 공유하기 때문에 정합성에 대해 특별히 문제는 없지만, Shared Disk 자체를 다중화해야하기 때문에 비용 측면에서 부담이 생긴다. (대체로 Shared Disk 기능이 있는 스토리지 기기는 비싸다.) + +### Shared Nothing +- 스토리지 간 통신을 하여 데이터 정합성을 확보하는 방식이다. +- 이 통신을 리플리케이션이라고 하고, 리플리케이션의 데이터 송신 측을 Master, 데이터 수신 측을 Slave라고 한다. +- Multi-Master라고 하여 Master와 Slave 역할을 모두 갖는 방식도 있지만 문제가 많이 발생하여 잘 사용되지 않는다. +- 리플리케이션에도 동기, 비동기식 리플리케이션이 있으며, 동기식 리플리케이션은 오버헤드가 커 동기 처리에 따라 성능 저하의 정도가 크지만 데이터 정합성을 확보하기 좋다. +- 비동기식 레플리케이션은 데이터 손실이 발생할 수 있지만 성능 저하의 정도가 작은 방식이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Ansible.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Ansible.md" new file mode 100644 index 00000000..b632dd36 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Ansible.md" @@ -0,0 +1,209 @@ +--- +title: 'Ansible' +lastUpdated: '2023-07-03' +--- +## Ansible + +## 구성 요소 + +- 제어 노드 - control node + - Ansible을 실행하는 node + - Ansible 제공 프로그램을 이용하여 매니지드 노드 관리 + - Ansible이 설치된 computer가 제어 노드가 된다 + - 제어 노드와 매니지드 노드 사이는 SSH를 통해 통신한다. + +- 매니지드 노드 - managed node + - Ansible로 관리하는 서버를 매니지드 노드 또는 호스트(host), 타겟(target) 이라 한다. + - 매니지드 노드에는 Ansible이 설치되지 않는다. + +- 인벤토리 - Inventory + - 매니지드 노드 목록 + - 인벤토리 파일은 호스트 파일 이라고도 한다. + - 인벤토리 파일은 각 매니지드 노드에 대한 IP Address, 호스트 정보, 변수 와 같은 정보를 저장 + +- 모듈 - module + - Ansible이 실행하는 코드 단위 + - 미리 만들어진 동작 관련 코드 집합 + - 각 모듈은 데이터베이스 처리, 사용자 관리, 네트워크 장치 관리 등 다양한 용도로 사용 + - 단위 모듈을 호출하거나 playbook에서 여러 모듈을 호출 할 수 있다. + +- 태스크 - Task + - Ansible 작업 단위 + - Ad-hoc 명령을 사용하여 단일 작업을 한 번 실행할 수 있다. + +- 플레이북 - Playbook + - 순서가 지정된 태스크 목록 + - 지정된 작업을 해당 순서로 반복적으로 실행할 수 있다. + - 플레이 북에는 변수와 작업이 포함 될 수 있다. + - YAML로 작성 + +## Ansible 동작 과정 + +- Ansible은 인벤토리 파일 내용을 참조하여, 관리에 대한 매니지드 노드 파악 +- Ansible을 통한 매니지드 노드 관리 방법 + - 모듈을 통한 매니지드 노드 관리 + - Ad-hoc 명령을 통한 매니지드 노드 관리 + - 태스크 단위로 매니지드 노드 관리 + - 플레이 북을 이용한 매니지드 노드 관리 + +### Ansible 환경 구축 + +- 실습 환경 구성 +![image](https://github.com/rlaisqls/TIL/assets/81006587/8dc35032-a77b-4a0d-a364-fda18a7e42d0) + +### Ansible 제어노드 구축 + - Ansible 제어 노드와 Managed node 연결 + - 제어 노드는 Managed Node와 연결 시 SSH를 통해서 연결한다. + - 따라서 제어 노드의 SSH key를 Managed node에 전송해야 한다. + - 하지만 AWS EC2 instance 환경에서는 keypair를 이용하여 연결하므로, Control node에는 Managed node의 keypair를 모두 가지고 있어야 한다. + + - Control Node 구성 + - Ansible 환경은 Control Node에 구성하고, Managed node에는 특별한 환경 구성이 없다. + - Ansible 설치 + - Ansible은 Linux 환경에 설치 가능 + - `sudo amazon-linux-extras install ansible2` + + - Ansible 설치 확인 + - `ansible --version` - ansible 버전 확인 + +### Ansible 환경 설정 파일 + - `/etc/ansible/ansible.cfg` - Ansible 환경 설정 파일 + - Ansible이 동작할 때 마다 참조하는 파일 + - Ansible 호스트 키 검사 속성을 비활성화 설정 + - 처음 설치시에는 `ansible.cfg` 파일에 주석 처리 되어 있다. + - host_key_checking = False + - Control node에서 Managed node에 접속 시, 별도의 key 확인 과정 없이 명령 수행을 위하여 비활성화 속성을 정의함. + - `/etc/ansible/hosts` - 인벤토리 파일 + +### Ansible 환경 설정 적용 순서 +1. ANSIBLE_CONFIG 환경 변수에 지정된 파일 +2. 현재 디렉토리에 있는 `ansible.cfg` 파일 +3. 사용자 홈 디렉토리에 있는 `ansible.cfg` 파일 + - 지역설정(현재 사용자 - 리눅스 일반 사용자) +4. `/etc/ansible/ansible.cfg` 파일(글로벌 전역 파일) + - 전역 설정(모든 사용자 - 리눅스 관리자/일반 사용자) + +### Managed Node와 연결 확인 + - Control Node에서 Managed node와 연결을 하려면 Managed Node에 대한 정보를 알고 있어야 한다. + - `/etc/ansible/hosts` 파일은 인벤토리 라고 하며, 이 파일에 Managed Node에 대한 정보를 기술한다. + - `/etc/ansible/hosts` 파일은 전역으로 사용하는 인벤토리이며, 현재 사용자에 대한 인벤토리를 구성하려면 현재 사용자의 작업 디렉토리에 별도의 인벤토리를 작성하여 사용할 수 있다. + +hosts 파일 변경 정보 + +``` +[managed] +host1 ansible_host=10.0.1.5 +ansible_connection=ssh +ansible_port=22 +ansible_user=ec2-user +ansible_ssh_private_key_file=/home/ec2-user/work-ansible/Goorm-aicore0940-20220906.pem +``` + +### Ad-hoc 명령 +- Ansible은 일반적으로 playbook을 사용하도록 설계되어있다. 하지만 한 번만 수행하거나, 간단한 명령을 통한 상태 확인 등은 별도의 playbook을 사용하지 않고 간단한 명령 구문으로 수행할 수 있는데, 이 방식을 Ad-hoc 명령이라고 한다. + +- 형식 + - ansible <호스트명 패턴( Managed node )> [옵션] + - `all` : 호스트명 패턴으로 사용하면 모든 managed node 대상으로 Ad-hoc 명령 실행 + - `-m` : 모듈명 + - `-a <인수목록>` : 모듈 인수 + - `-i <인벤토리 파일명>` : 인벤토리 (별도의 인벤토리를 지정하지 않으면 /etc/ansible/hosts 파일을 사용한다.) + - `--become` : 관리자 권한으로 실행 + - `-k` : ansible 실행 시 암호 확인 + - `-K` : ansible 실행 시 root 권한으로 실행 +- 예시 + - `ansible all -m ping -i ./hosts` -> 호스트 패턴 all 은 인벤토리의 모든 호스트에 대하여 Ad-hoc 명령 적용 시 사용하는 호스트 패턴 + - `ansible managed -m ping -i ./hosts` -> managed 호스트 패턴에 대하여 ping 모듈 적용, 인벤토리는 현재 디렉토리의 hosts 사용 + - ping 모듈 - Ansible Control Node와 Managed node 사이의 통신 연결 상태 확인 + +## Inventory 이해 + +- Control Node에서 Managed node에 연결하기 위한 정보를 가지고 있는 파일 +- 기본 위치 + - `/etc/ansible/hosts` +- 기본 위치의 파일은 default로 적용되는 인벤토리이고, 관리자 권한으로만 수정 가능 +- 사용자가 원하는 디렉토리에 복사한 후 편집하여 사용 + - `sudo cp /etc/ansible/hosts .` + - `sudo chown <사용자ID>:<사용자그룹> <인벤토리 파일>` + +- Ansible 설치 후 기본 인벤토리의 내용은 사용법에 대한 주석으로 구성되어 있다. + +```bash +# This is the default ansible 'hosts' file. +# +# It should live in /etc/ansible/hosts +# +# - Comments begin with the '#' character +# - Blank lines are ignored +# - Groups of hosts are delimited by [header] elements +# - You can enter hostnames or ip addresses +# - A hostname/ip can be a member of multiple groups + +# Ex 1: Ungrouped hosts, specify before any group headers. + +## green.example.com +## blue.example.com +## 192.168.100.1 +## 192.168.100.10 + +# Ex 2: A collection of hosts belonging to the 'webservers' group +## [webservers] +## alpha.example.org +## beta.example.org +## 192.168.1.100 +## 192.168.1.110 + +# If you have multiple hosts following a pattern you can specify +# them like this: + +## www[001:006].example.com + +# Ex 3: A collection of database servers in the 'dbservers' group + +## [dbservers] +## +## db01.intranet.mydomain.net +## db02.intranet.mydomain.net +## 10.25.1.56 +## 10.25.1.57 + +# Here's another example of host ranges, this time there are no +# leading 0s: + +## db-[99:101]-node.example.com +``` +- 인벤토리 내용 + - 그룹을 지정하지 않는 방식 + - [속성] + - 10.0.1.97 ansible_connection=ssh ansible_port=22 ansible_user=ec2-user ansible_ssh_private_key_file=/home/ec2-user/work-ansible/gurum-aicore0942-20220906.pem + - 그룹을 지정하는 방식 + - [그룹명] + - [속성] + +```bash +[managed] +host1 ansible_host=10.0.1.5 ansible_connection=ssh ansible_port=22 ansible_user=ec2-user ansible_ssh_private_key_file=/home/ec2-user/work-ansible/Goorm-aicore0940-20220906.pem +``` + +- 공통 정보를 변수에 저장하여 공유하는 방식 + - [그룹명:vars] + +```bash + [속성] +[managed:vars] +ansible_connection=ssh -> 연결 방법 +ansible_port=22 -> 연결 port number +ansible_user=ec2-user -> host( managed node ) user id +ansible_ssh_private_key_file=/home/ec2-user/work-ansible/gurum-aicore0942-20220906.pem -> 개인키 파일 위치 +ansible_python_interpreter=/usr/bin/python3 -> host( managed node ) python 경고 메시지를 출력하지 않도록 파이썬 인터프리터 위치 지정 +``` + +- 여러 그룹을 하나의 그룹으로 묶어서 변수를 공유하는 방법 + - ```bash + [그룹명:children] + <그룹명1> + <그룹명2> + ... + [그룹명:vars] + [속성] + ``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Cobbler.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Cobbler.md" new file mode 100644 index 00000000..8948477f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Cobbler.md" @@ -0,0 +1,17 @@ +--- +title: 'Cobbler' +lastUpdated: '2024-03-02' +--- + +Cobbler is a Linux installation server that allows for rapid setup of network installation environments. It glues together and automates many associated Linux tasks so you do not have to hop between many various commands and applications when deploying new systems, and, in some cases, changing existing ones. Cobbler can help with provisioning, managing DNS and DHCP, package updates, power management, configuration management orchestration. + +Just as configuration management systems rely on templates to simplify updates, so to does Cobbler. Templates are used extensively for management of services like DNS and DHCP, and the response files given to the various distributions (kickstart, preseed, etc.) are all templated to maximize code reuse. + +In addition to templates, Cobbler relies on a system of snippets - small chunks of code (which are really templates themselves) that can be embedded in other templates. This allows admins to write things once, use it wherever they need it via a simple include, all while managing the content in just one place. + +Automation is the key to speed, consistency and repeatability. These properties are critical to managing an infrastructure, whether it is comprised of a few servers or a few thousand servers. Cobbler helps by automating the process of provisioning servers from bare metal, or when deploying virtual machines onto various hypervisors. + +--- +reference +- https://en.wikipedia.org/wiki/Cobbler_(software) +- https://cobbler.readthedocs.io/en/latest/quickstart-guide.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Configuration\342\200\205Drift.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Configuration\342\200\205Drift.md" new file mode 100644 index 00000000..16ee1ae6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Configuration\342\200\205Drift.md" @@ -0,0 +1,28 @@ +--- +title: 'Configuration Drift' +lastUpdated: '2024-03-02' +--- + +Configuration Drift is the phenomenon where servers in an infrastructure become more and more different from one another as time goes on, due to manual ad-hoc changes and updates, and general entropy. + +A nice automated server provisioning process helps ensure machines are consistent when they are created, but during a given machine’s lifetime it will drift from the baseline, and from the other machines. + +There are two main methods to combat configuration drift. One is to use automated configuration tools such as Puppet or Chef, and run them frequently and repeatedly to keep machines in line. The other is to rebuild machine instances frequently, so that they don’t have much time to drift from the baseline. + +## Why Configuration Drift Matters + +Configuration drift can lead to serious consequences, including: + +- **Security vulnerabilities**—misconfigurations or unauthorized changes to configuration can lead to issues like escalation of privilege, use of vulnerable open source components, vulnerable container images, images pulled from untrusted repositories, or containers running as root. +- **Inefficient resource utilization**—configuration drift can result in over-provisioned workloads or older workloads that keep running when they are no longer needed, which can significantly impact cloud costs. +- **Reduced resilience and reliability**—configuration problems in production can cause crashes, bugs, and performance issues, which can be difficult to debug and resolve. + +## IaC + +An IaC approach helps with drift, but additional drift management is critical. Ansible helps you combat drift with Ansible Playbooks (automation workflows) that can be set up to detect drift. When drift is detected, it sends a notification to the appropriate person who can make the required modification and return the system to its baseline. + +--- +reference +- https://opensourceforu.com/2015/03/ten-tools-for-configuration-management/ +- http://kief.com/configuration-drift.html +- https://www.aquasec.com/cloud-native-academy/vulnerability-management/configuration-drift/#:~:text=Configuration%20drift%20is%20when%20the%20configuration%20of%20an%20environment%20%E2%80%9Cdrifts,without%20being%20recorded%20or%20tracked. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Phoenix\342\200\205Server.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Phoenix\342\200\205Server.md" new file mode 100644 index 00000000..b77a8475 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Phoenix\342\200\205Server.md" @@ -0,0 +1,32 @@ +--- +title: 'Phoenix Server' +lastUpdated: '2024-03-02' +--- + +예전에 일반적으로 서버를 운영할때는 주로 서버를 설치한 후 OS를 설치한 후에, 필요한 소프트웨어와 애플리케이션을 설치하여 운영하였다. 여기에 문제가 생긴 경우 패치를 하거나 정기적인 보안 패치 튜닝들을 해당 서버에 지속적으로 적용하고, 애플리케이션은 CI/CD 등의 툴을 이용하여 배포하는 구조를 가지고 있었다. + +이렇게 **한번 설치한 서버에 계속해서 설정을 변경하고 패치를 적용하는 등의 업데이트를 지속적으로 적용하여 운영하는 서버를 Snowflakes Server**라고 하는데, 이렇게 설정된 서버는 다시 똑같이 설정하기가 매우 어렵다. 모든 설정과정이 문서화가 잘되어 있으면 모르겠지만 대부분 문서화가 꼼꼼한 경우가 굉장히 드물고, 담당자가 바뀌거나 관리 조직이 바뀌는 경우에는 그 이력이 제대로 유지되는 경우가 없다. 그래서 장비를 업그레이드 하거나 OS를 새로 인스톨해서 같은 환경을 꾸미고자 할때 예전 환경과 동일한 환경을 구성하기가 어렵고, 그래서 누락된 설정이나 패치등에 의해서 장애가 발생하는 경우가 많다. + +Snowflakes Server는 이렇게 한번 설정을 하고 다시 설정이 불가능한 형태를 의미하기도 한다. + +이런 Snowflakes Server는 재구성 문제 뿐만 아니라 구성 편차를 유발하기도 하는데, 여러대의 웹서버를 운영하고 있는 조직에서, 문제가 있어서 특정 서버를 패치한 경우, 다른 동일한 웹서버를 모두 패치 하지 않는 이상 구성이 달라진다. 이는 또 운영상의 문제를 일으킬 수 있다. + +## Phoenix Server + +그래서 나온 서버 패턴이 Phoenix Server 패턴인데, 피닉스(불사조)는 불멸로도 알려져있지만 정확히는 불속에서 다시 태어나는 re-born (재탄생)의 개념을 가지고 있다. 이 재탄생의 개념을 서버 설정 방식에 적용한 패턴이 피닉스 서버 패턴이다. + +새로운 소프트웨어를 인스톨하거나 설정을 변경할때 기존 서버에 변경 작업을 더 하는 것이 아니라, 처음 OS 설치에서 부터, 소프트웨어 인스톨, 설정 변경까지를 다시 반복하는 것이다. + +예를 들어 우분투 16, 톰캣 7.0 버전으로 운영되고 있는 서버가 있고, 이 서버에 로그 수집 모듈은 `fluentd`를 설치하는 케이스가 있다고 가정하자. + +Snowflakes Server 서버 패턴의 경우에는 이미 운영되고 있는 서버에 새롭게 fluentd를 일일이 설치하거나 자동화가 되어 있는 경우에는 ansible 이나 chef등의 configuration management 도구를 이용하여 fluentd를 설치하게 된다. + +피닉스 서버 패턴의 경우에는 새 VM을 다시 만들고, 우분투 16 OS를 설치하고, 톰캣 7.0을 설치하고 fluentd를 설치한 다음. 이 VM으로 기존 VM을 교체한다. + +물론 매번 이렇게 새롭게 모든 스택을 설치하지는 않는다. 어느정도 공통 스택은 가상머신의 베이스 이미지 (VM Base Image)로 만들어놓고, 이 이미지를 이용하여 VM을 생성한 후에, 차이가 나는 부분만 설정을 하는 구조를 설정하는 구조를 사용하게 되고 이 과정은 스크립트 코드를 이용해서 자동화하기 때문에, 그렇게 많은 시간이 소요되지 않는다. + +피닉스 서버 패턴에서는 매번 전체를 스크립트를 이용해서 설치하기 때문에, 다음과 같은 장점을 가지게 된다. + +- 스크립트에 모든 설정 정보가 유지되게 된다. + +- 이 스크립트 코드를, git와 같은 소스 코드 관리 시스템을 이용해서 관리하게 되면, 어떤 부분을 누가 어떻게 수정을 했는지 추적이 가능하게 된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform.md" new file mode 100644 index 00000000..2930377e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform.md" @@ -0,0 +1,59 @@ +--- +title: 'Terraform' +lastUpdated: '2024-03-02' +--- + +Terraform is an open source “Infrastructure as Code” tool, created by HashiCorp. +A declarative coding tool, Terraform enables developers to use a high-level configuration language called HCL (HashiCorp Configuration Language) to describe the desired “end-state” cloud or on-premises infrastructure for running an application. It then generates a plan for reaching that end-state and executes the plan to provision the infrastructure. + +Because Terraform uses a simple syntax, can provision infrastructure across multiple cloud and on-premises data centers, and can safely and efficiently re-provision infrastructure in response to configuration changes, it is currently one of the most popular infrastructure automation tools available. If your organization plans to deploy a hybrid cloud or multicloud environment, you’ll likely want or need to get to know Terraform. + +## Why Infrastructure as Code (IaC)? + +To better understand the advantages of Terraform, it helps to first understand the benefits of Infrastructure as Code (IaC). IaC allows developers to codify infrastructure in a way that makes provisioning automated, faster, and repeatable. It’s a key component of Agile and DevOps practices such as version control, continuous integration, and continuous deployment. + +Infrastructure as code can help with the following: + +- **Improve speed**: Automation is faster than manually navigating an interface when you need to deploy and/or connect resources. + +- **Improve reliability**: If your infrastructure is large, it becomes easy to misconfigure a resource or provision services in the wrong order. With IaC, the resources are always provisioned and configured exactly as declared. + +- **Prevent configuration drift**: Configuration drift occurs when the configuration that provisioned your environment no longer matches the actual environment. (See ‘Immutable infrastructure’ below.) + +- **Support experimentation, testing, and optimization**: Because Infrastructure as Code makes provisioning new infrastructure so much faster and easier, you can make and test experimental changes without investing lots of time and resources; and if you like the results, you can quickly scale up the new infrastructure for production. + +## Why Terraform? +There are a few key reasons developers choose to use Terraform over other Infrastructure as Code tools: + +- **Open source**: Terraform is backed by large communities of contributors who build plugins to the platform. Regardless of which cloud provider you use, it’s easy to find plugins, extensions, and professional support. This also means Terraform evolves quickly, with new benefits and improvements added consistently. + +- **Platform agnostic**: Meaning you can use it with any cloud services provider. Most other IaC tools are designed to work with single cloud provider. + +- **Immutable infrastructure**: Most Infrastructure as Code tools create mutable infrastructure, meaning the infrastructure can change to accommodate changes such as a middleware upgrade or new storage server. The danger with mutable infrastructure is configuration drift—as the changes pile up, the actual provisioning of different servers or other infrastructure elements ‘drifts’ further from the original configuration, making bugs or performance issues difficult to diagnose and correct. + Terraform provisions immutable infrastructure, which means that with each change to the environment, the current configuration is replaced with a new one that accounts for the change, and the infrastructure is reprovisioned. Even better, previous configurations can be retained as versions to enable rollbacks if necessary or desired. + +## Terraform modules + +Terraform modules are small, reusable Terraform configurations for multiple infrastructure resources that are used together. Terraform modules are useful because they allow complex resources to be automated with re-usable, configurable constructs. Writing even a very simple Terraform file results in a module. A module can call other modules—called child modules—which can make assembling configuration faster and more concise. Modules can also be called multiple times, either within the same configuration or in separate configurations. + +## Terraform providers + +Terraform providers are plugins that implement resource types. Providers contain all the code needed to authenticate and connect to a service—typically from a public cloud provider—on behalf of the user. You can find providers for the cloud platforms and services you use, add them to your configuration, and then use their resources to provision infrastructure. Providers are available for nearly every major cloud provider, SaaS offering, and more, developed and/or supported by the Terraform community or individual organizations. Refer to the [Terraform documentation](https://developer.hashicorp.com/terraform/language/providers) for a detailed list. + +## Terraform vs. Kubernetes + +Sometimes, there confusion between Terraform and Kubernetes and what they actually do. The truth is that they are not alternatives and actually work effectively together. + +Kubernetes is an open source container orchestration system that lets developers schedule deployments onto nodes in a compute cluster and actively manages containerized workloads to ensure that their state matches the users’ intentions. + +Terraform, on the other hand, is an Infrastructure as Code tool with a much broader reach, letting developers automate complete infrastructure that spans multiple public clouds and private clouds. + +Terraform can automate and manage Infrastructure-as-a-Service (IaaS), Platform-as-a-Service (PaaS), or even Software-as-a-Service (SaaS) level capabilities and build all these resources across all those providers in parallel. You can use Terraform to automate the provisioning of Kubernetes—particularly managed Kubernetes clusters on cloud platforms— and to automate the deployment of applications into a cluster. + +## Terraform vs. Ansible + +Terraform and Ansible are both Infrastructure as Code tools, but there are a couple significant differences between the two: + +**While Terraform is purely a declarative tool (see above), Ansible combines both declarative and procedural configuration.** In procedural configuration, you specify the steps, or the precise manner, in which you want to provision infrastructure to the desired state. Procedural configuration is more work but it provides more control. + +Terraform is open source; Ansible is developed and sold by Red Hat. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205import\354\231\200\342\200\205Terraforming.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205import\354\231\200\342\200\205Terraforming.md" new file mode 100644 index 00000000..0c69367f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205import\354\231\200\342\200\205Terraforming.md" @@ -0,0 +1,88 @@ +--- +title: 'Terraform import와 Terraforming' +lastUpdated: '2024-03-02' +--- + +이미 사용하고 있는 인프라를 Terraform으로 가져오려면 import 명령어를 사용해야한다. + +> The current implementation of Terraform import can only import resources into the state. It does not generate configuration. A future version of Terraform will also generate configuration. + +import 명령어를 사용하면 상태 파일에 기존 리소스의 상태가 적용된다. import는 아래와 같이 사용할 수 있다. + +```bash +$ terraform import {유형}.{이름} {식별자} +$ terraform import aws_instance.web i-12345678 +``` + +`aws_instance`는 리소스의 유형(여기서는 EC2 인스턴스)을 나타내는 것이고 `web`은 직접 지정한 이름이다. `i-12345678`는 AWS에서 만든 인스턴스 ID다. 다시 풀어서 설명하면 `i-12345678` 인스턴스를 `aws_instance.web`으로 가져오겠다는 뜻이다. 뒷부분에 있는 식별자는 리소스 유형마다 조금씩 다를 수 있는데, 그런 경우는 [공식 문서](https://www.terraform.io/docs/providers/aws/r/instance.html#import)를 참조하면 된다. + + +한 번에 딱 한 개만 가져올 수 있으므로 인스턴스가 3개 있으면 3번 가져와야 한다. + +```bash +$ terraform import aws_instance.server1 i-017eb8d5ec586a067 + +aws_instance.server1: Importing from ID "i-017eb8d5ec586a067"... +aws_instance.server1: Import complete! + Imported aws_instance (ID: i-017eb8d5ec586a067) +aws_instance.server1: Refreshing state... (ID: i-017eb8d5ec586a067) + +Import success! The resources imported are shown above. These are +now in your Terraform state. Import does not currently generate +configuration, so you must do this next. If you do not create configuration +for the above resources, then the next `terraform plan` will mark +them for destruction. +``` + +EC2 인스턴스를 성공적으로 가져왔다. 현재 폴더를 보면 다음과 같은 `terraform.tfstate` 파일이 생긴 걸 볼 수 있다. 기존에 `terraform.tfstate` 파일이 없었지만, 자동으로 만들어 준다. 이미 파일이 존재했거나 backend가 설정되어 있었다면 그곳에 반영되었을 것이다. + +```json +{ + "version": 3, + "terraform_version": "0.9.6", + "serial": 0, + "lineage": "20c7dcdf-4258-4834-ab4c-54bc1b6fa67e", + "modules": [ + { + "path": [ + "root" + ], + "outputs": {}, + "resources": { + "aws_instance.server1": { + ... + } + }, + "depends_on": [] + } + ] +} +``` + +## Terraforming + +원래 이 설정은 처음 작성할 때와 마찬가지로 손으로 직접 작성해야 한다. 하지만 이를 도와주는 terraforming이라는 Ruby 프로젝트가 있다. `brew install terraforming`으로 설치하면 terraforming이라는 명령어를 쓸 수 있다. EC2 인스턴스를 가져오는 명령어는 terraforming ec2이다. + +```bash +# 설치 +brew install terraforming + +# 실행 +terraform $리소스 +``` + +terraform import와는 달리 terraforming은 한 서비스의 자원을 한꺼번에 가져오므로 여기서 필요한 자원을 가져다 써야 한다. 위에서 보듯이 AWS에 설정된 내용을 그대로 가져온 것이므로 이름이나 참조 관계 등은 알아서 지정해주지 않는다. 처음부터 작성하는 대신 teraforming 한 설정을 바탕으로 작성해서 노력을 줄여줄 수는 있다. + +약간씩 다른 부분이 있다면 Terrafoam 문법에 맞추어 조금씩 수정하면 된다. + +> https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/ + +Cloudflare를 사용하는 경우에는 `cf-terraforming`라는 별도의 도구를 사용할 수 있다. 마찬가지로 brew로 깔 수 있다. + +```bash +# 설치 +brew install --cask cloudflare/cloudflare/cf-terraforming + +# 실행 +cf-terraforming generate --email $CLOUDFLARE_EMAIL --token $CLOUDFLARE_API_TOKEN --resource-type $리소스 > importing-example.tf +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205taint.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205taint.md" new file mode 100644 index 00000000..7eefc1b5 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205taint.md" @@ -0,0 +1,92 @@ +--- +title: 'Terraform taint' +lastUpdated: '2024-03-02' +--- + +terraform taint는 특정 리소스를 "tainted" 상태로 표시하여, 다음 terraform apply 때 해당 리소스를 강제로 다시 만들게 한다. 특정 리소스를 교체해서 테스트하거나 디버깅해보고 싶을때 taint를 이용할 수 있다. + +### 예제 + +igw에 장애가 있다고 가정해보자. 우선 `tf state list`를 통해 state 목록을 출력한다. + +```bash +$ tf state list +module.route_table__private.aws_resourcegroups_group.this[0] +module.route_table__private.aws_route_table.this +module.route_table__private.aws_route_table_association.subnets[0] +module.route_table__private.aws_route_table_association.subnets[1] +module.route_table__public.aws_resourcegroups_group.this[0] +module.route_table__public.aws_route.ipv4["0.0.0.0/0"] +module.route_table__public.aws_route_table.this +module.route_table__public.aws_route_table_association.subnets[0] +module.route_table__public.aws_route_table_association.subnets[1] +module.subnet_group__private.aws_resourcegroups_group.this[0] +module.subnet_group__private.aws_subnet.this["default-private-001/az1"] +module.subnet_group__private.aws_subnet.this["default-private-002/az2"] +module.subnet_group__public.aws_resourcegroups_group.this[0] +module.subnet_group__public.aws_subnet.this["default-public-001/az1"] +module.subnet_group__public.aws_subnet.this["default-public-002/az2"] +module.vpc.data.aws_region.current +module.vpc.aws_internet_gateway.this[0] +module.vpc.aws_resourcegroups_group.this[0] +module.vpc.aws_vpc.this +``` + +`taint` 명령어로 다시 생성할 요소를 지정한다. + +```bash +tf taint 'module.vpc.aws_internet_gateway.this[0]' +>>> +Resource instance module.vpc.aws_internet_gateway.this[0] has been marked as tainted. +``` + +`tf apply`를 입력하면 igw에 연결되어있던 라우팅 테이블 규칙이 다시 생성된다. + +```bash +$ tf apply +>>> +# 중략 +Terraform will perform the following actions: + + # module.route_table__public.aws_route.ipv4["0.0.0.0/0"] will be updated in-place + ~ resource "aws_route" "ipv4" { + ~ gateway_id = "igw-09be7ce12b07438cf" -> (known after apply) + id = "r-rtb-0314c188a357c38491080289494" + # (4 unchanged attributes hidden) + } + + # module.vpc.aws_internet_gateway.this[0] is tainted, so must be replaced +-/+ resource "aws_internet_gateway" "this" { + ~ arn = "arn:aws:ec2:ap-northeast-2:671393671211:internet-gateway/igw-09be7ce12b07438cf" -> (known after apply) + ~ id = "igw-09be7ce12b07438cf" -> (known after apply) + ~ owner_id = "671393671211" -> (known after apply) + tags = { + "Name" = "default" + "Owner" = "posquit0" + "Project" = "Network" + "module.terraform.io/full-name" = "terraform-aws-network/vpc" + "module.terraform.io/instance" = "default" + "module.terraform.io/name" = "vpc" + "module.terraform.io/package" = "terraform-aws-network" + "module.terraform.io/version" = "0.24.0" + } + # (2 unchanged attributes hidden) + } + +Plan: 1 to add, 1 to change, 1 to destroy. +``` + +taint 상태를 제거하고 싶다면, `terraform untaint` 명령어를 사용하면 된다. + +```bash +tf untaint 'module.vpc.aws_internet_gateway.this[0]' +``` + +리소스를 갱신하고 싶을 때, taint 외에도 아래와 같은 방법을 사용할 수 있다: + +```bash +terraform plan -replace=resource +terraform apply -replace=resource +``` + +이러한 방법은 하나의 리소스를 대체할 때 유용하며, 여러 리소스를 갱신하고 싶을 때는 **taint**를 사용하는 것이 좋다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205with\342\200\205AWS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205with\342\200\205AWS.md" new file mode 100644 index 00000000..780a2f4e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205with\342\200\205AWS.md" @@ -0,0 +1,764 @@ +--- +title: 'Terraform with AWS' +lastUpdated: '2024-03-02' +--- + +AWS Provider로 간단한 인프라를 구성해보자. + +우선 AWS IAM에 가서 Terraform이 사용할 계정을 만들고, 사용할 서비스 VPC, EC2에 대한 권한을 부여한다. + +생성된 계정에 할당된 access key와 secret key로 다음과 같은 aws.tf 파일을 생성한다. + +```hcl +provider "aws" { + access_key = "ACCESS-KEY" + secret_key = "SECRET-KEY" + region = "ap-northeast-2" +} +``` + +이제 AWS 리소스를 정의할 때 이 파일을 이용하게 된다. + +Terraform을 사용하면 API Gateway, App Autoscaleing, CloudFomation, CloudFront, CloudWatch, CodeCommit, CodeDeploy, DynamoDB, EC2, ECS, ElasticCache, Elastic Beanstalk, ElasticSearch, IAM, Kinesis, Lambda, OpsWorks, RDS, RedShift, Route53, S3, SES, SimpleDB, SNS, SQS, VPC 등 AWS의 거의 모든 인프라를 관리할 수 있다. + +### VPC 구성 + +VPC부터 Terraform으로 구성해서 사용해보자. vpc.tf라는 파일을 만들어서 다음 내용을 입력한다. + +```hcl +resource "aws_vpc" "example" { + cidr_block = "172.10.0.0/20" + tags { + Name = "example" + } +} + +resource "aws_subnet" "example-a" { + vpc_id = "${aws_vpc.example.id}" + cidr_block = "172.10.0.0/24" + availability_zone = "ap-northeast-2a" +} + +resource "aws_subnet" "example-c" { + vpc_id = "${aws_vpc.example.id}" + cidr_block = "172.10.1.0/24" + availability_zone = "ap-northeast-2c" +} +``` + +VPC 하나(`aws_vpc`)와 VPC에서 사용할 서브넷을 2개 만들었다(`aws_subnet`). AWS 프로바이더의 리소스이므로 리소스 타입의 이름의 접두사에 aws_가 붙은 것을 볼 수 있다. 이 리소스 타입은 Terraform에서 미리 정의된 이름이므로 문서를 봐야 한다. 그 뒤에 온 example, example-a 같은 이름은 알아보기 쉽게 이름을 직접 지정한 것이다. + +example이라는 VPC를 정의하고 서브넷을 정의할 때는 앞에서 정의한 리소스의 ID를 `${aws_vpc.example.id}`처럼 참조해서 VPC 내에 서브넷을 정의했다. + +VPC에서 사용할 시큐리티 그룹을 정의해보자. 다음 파일은 security-group.tf의 내용이다. + +```hcl +resource "aws_security_group" "example-allow-all" { + name = "example-allow_all" + description = "Allow all inbound traffic" + vpc_id = "${aws_vpc.example.id}" + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} +``` + +여기서도 마찬가지로 인바운드/아웃바운드를 모두 허용하는 시큐리티 그룹을 VPC 내에서 생성하는 설정이다. + +```bash +├── aws.tf +├── security-group.tf +└── vpc.tf +``` + +이제 현재 디렉터리의 위처럼 3개의 파일이 존재한다. Terraform이 로딩은 알파벳순으로, 의존성 관계는 알아서 맺어주므로 리소스 정의 순서는 전혀 상관없다. + +## Terraform으로 적용하기 + +이제 실제로 Terraform을 사용해보자. + +우선 `terraform plan` 명령어로 설정이 이상 없는지도 확인하고 실제로 적용하면 인프라가 어떻게 달라지는지 확인할 수 있다. 현재 폴더에서 `terraform plan` 명령어를 실행한다. + +```bash +$ terraform plan +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but +will not be persisted to local or remote state storage. + + +The Terraform execution plan has been generated and is shown below. +Resources are shown in alphabetical order for quick scanning. Green resources +will be created (or destroyed and then created if an existing resource +exists), yellow resources are being changed in-place, and red resources +will be destroyed. Cyan entries are data sources to be read. + +Note: You didn't specify an "-out" parameter to save this plan, so when +"apply" is called, Terraform can't guarantee this is what will execute. + ++ aws_security_group.example-allow-all + description: "Allow all inbound traffic" + egress.#: "1" + egress.482069346.cidr_blocks.#: "1" + egress.482069346.cidr_blocks.0: "0.0.0.0/0" + egress.482069346.from_port: "0" + egress.482069346.prefix_list_ids.#: "0" + egress.482069346.protocol: "-1" + egress.482069346.security_groups.#: "0" + egress.482069346.self: "false" + egress.482069346.to_port: "0" + ingress.#: "1" + ingress.482069346.cidr_blocks.#: "1" + ingress.482069346.cidr_blocks.0: "0.0.0.0/0" + ingress.482069346.from_port: "0" + ingress.482069346.protocol: "-1" + ingress.482069346.security_groups.#: "0" + ingress.482069346.self: "false" + ingress.482069346.to_port: "0" + name: "example-allow_all" + owner_id: "" + vpc_id: "${aws_vpc.example.id}" + ++ aws_subnet.example-a + availability_zone: "ap-northeast-1a" + cidr_block: "172.10.0.0/24" + map_public_ip_on_launch: "false" + vpc_id: "${aws_vpc.example.id}" + ++ aws_subnet.example-c + availability_zone: "ap-northeast-1c" + cidr_block: "172.10.1.0/24" + map_public_ip_on_launch: "false" + vpc_id: "${aws_vpc.example.id}" + ++ aws_vpc.example + cidr_block: "172.10.0.0/20" + default_network_acl_id: "" + default_route_table_id: "" + default_security_group_id: "" + dhcp_options_id: "" + enable_classiclink: "" + enable_dns_hostnames: "" + enable_dns_support: "true" + instance_tenancy: "" + main_route_table_id: "" + tags.%: "1" + tags.Name: "example" + + +Plan: 4 to add, 0 to change, 0 to destroy. +``` + +새로 추가되는 설정은 +로 표시되는데, 다음 4개가 추가된 것을 볼 수 있다. + +```bash ++ aws_security_group.example-allow-all ++ aws_subnet.example-a ++ aws_subnet.example-c ++ aws_vpc.example +``` + +이 plan 기능은 미리 구성을 테스트해볼 수 있다는 점에서 매력적이고, 실수로 인프라를 변경하지 않도록 확인해 볼 수 있는 장치이기도 하다. 그래서 HCL 파일을 작성하면서 plan으로 확인해 보고 다시 변경해보고 하면서 사용할 수 있다. + +plan에 이상이 없으므로 이제 적용해보자. 적용은 `terraform apply` 명령어를 사용한다. + + +```bash +$ terraform apply +aws_vpc.example: Creating... + cidr_block: "" => "172.10.0.0/20" + default_network_acl_id: "" => "" + default_route_table_id: "" => "" + default_security_group_id: "" => "" + dhcp_options_id: "" => "" + enable_classiclink: "" => "" + enable_dns_hostnames: "" => "" + enable_dns_support: "" => "true" + instance_tenancy: "" => "" + main_route_table_id: "" => "" + tags.%: "" => "1" + tags.Name: "" => "example" +aws_vpc.example: Creation complete +aws_subnet.example-a: Creating... + availability_zone: "" => "ap-northeast-1a" + cidr_block: "" => "172.10.0.0/24" + map_public_ip_on_launch: "" => "false" + vpc_id: "" => "vpc-14ff2570" +aws_subnet.example-c: Creating... + availability_zone: "" => "ap-northeast-1c" + cidr_block: "" => "172.10.1.0/24" + map_public_ip_on_launch: "" => "false" + vpc_id: "" => "vpc-14ff2570" +aws_security_group.example-allow-all: Creating... + description: "" => "Allow all inbound traffic" + egress.#: "" => "1" + egress.482069346.cidr_blocks.#: "" => "1" + egress.482069346.cidr_blocks.0: "" => "0.0.0.0/0" + egress.482069346.from_port: "" => "0" + egress.482069346.prefix_list_ids.#: "" => "0" + egress.482069346.protocol: "" => "-1" + egress.482069346.security_groups.#: "" => "0" + egress.482069346.self: "" => "false" + egress.482069346.to_port: "" => "0" + ingress.#: "" => "1" + ingress.482069346.cidr_blocks.#: "" => "1" + ingress.482069346.cidr_blocks.0: "" => "0.0.0.0/0" + ingress.482069346.from_port: "" => "0" + ingress.482069346.protocol: "" => "-1" + ingress.482069346.security_groups.#: "" => "0" + ingress.482069346.self: "" => "false" + ingress.482069346.to_port: "" => "0" + name: "" => "example-allow_all" + owner_id: "" => "" + vpc_id: "" => "vpc-14ff2570" +aws_subnet.example-a: Creation complete +aws_subnet.example-c: Creation complete +aws_security_group.example-allow-all: Creation complete + +Apply complete! Resources: 4 added, 0 changed, 0 destroyed. + +The state of your infrastructure has been saved to the path +below. This state is required to modify and destroy your +infrastructure, so keep it safe. To inspect the complete state +use the `terraform show` command. + +State path: terraform.tfstate +``` + +적용이 완료되었다. 제대로 적용되었는지 AWS에 들어가서 확인해 보자. + +설정한 VPC, Subnet, Security Group이 모두 정상적으로 만들어 진 것을 볼 수 있다. + +```hcl +resource "aws_vpc" "example" { + cidr_block = "172.10.0.0/20" + tags { + Name = "example" + } +} +``` + +앞에서 정의한 VPC 구성을 보면 여기서 리소스 이름의 example은 실제 AWS 구성되는 이름과는 아무런 상관이 없고 Terraform에서 참조하기 위해서 이름을 할당한 것일 뿐이다. 그래서 VPC의 이름으로 표시되는 것은 tags에서 `Name = "example"`로 설정한 이름이 지정된다. + +추가로 apply한 마지막 로그를 보면 `State path: terraform.tfstate`라고 나온 걸 볼 수 있는데 이는 적용한 인프라의 상태를 관리하는 파일로 다음과 같이 hcl으로 되어 있다. 적용된 인프라를 이 파일에서 관리하고 있으므로 Terraform으로 인프라를 관리한다면 `terraform.tfstate` 파일도 Git 등으로 관리하고 보관해야 한다. 이후 적용하면 `terraform.tfstate.backup `파일이 하나 생기면서 마지막 버전을 하나 더 보관한다. + +```hcl +{ + "version": 3, + "terraform_version": "0.8.2", + "serial": 0, + "lineage": "d7b033b3-03a4-4020-b389-fe8f7e95dec0", + "modules": [ + { + "path": [ + "root" + ], + "outputs": {}, + "resources": { + "aws_security_group.example-allow-all": { + "type": "aws_security_group", + "depends_on": [ + "aws_vpc.example" + ], + "primary": { + "id": "sg-d2fa7db5", + "attributes": { + "description": "Allow all inbound traffic", + "egress.#": "1", + "egress.482069346.cidr_blocks.#": "1", + "egress.482069346.cidr_blocks.0": "0.0.0.0/0", + "egress.482069346.from_port": "0", + "egress.482069346.prefix_list_ids.#": "0", + "egress.482069346.protocol": "-1", + "egress.482069346.security_groups.#": "0", + "egress.482069346.self": "false", + "egress.482069346.to_port": "0", + "id": "sg-d2fa7db5", + "ingress.#": "1", + "ingress.482069346.cidr_blocks.#": "1", + "ingress.482069346.cidr_blocks.0": "0.0.0.0/0", + "ingress.482069346.from_port": "0", + "ingress.482069346.protocol": "-1", + "ingress.482069346.security_groups.#": "0", + "ingress.482069346.self": "false", + "ingress.482069346.to_port": "0", + "name": "example-allow_all", + "owner_id": "410655858509", + "tags.%": "0", + "vpc_id": "vpc-14ff2570" + }, + "meta": {}, + "tainted": false + }, + "deposed": [], + "provider": "" + }, + "aws_subnet.example-a": { + "type": "aws_subnet", + "depends_on": [ + "aws_vpc.example" + ], + "primary": { + "id": "subnet-4fbcdb39", + "attributes": { + "availability_zone": "ap-northeast-1a", + "cidr_block": "172.10.0.0/24", + "id": "subnet-4fbcdb39", + "map_public_ip_on_launch": "false", + "tags.%": "0", + "vpc_id": "vpc-14ff2570" + }, + "meta": {}, + "tainted": false + }, + "deposed": [], + "provider": "" + }, + "aws_subnet.example-c": { + "type": "aws_subnet", + "depends_on": [ + "aws_vpc.example" + ], + "primary": { + "id": "subnet-40b81718", + "attributes": { + "availability_zone": "ap-northeast-1c", + "cidr_block": "172.10.1.0/24", + "id": "subnet-40b81718", + "map_public_ip_on_launch": "false", + "tags.%": "0", + "vpc_id": "vpc-14ff2570" + }, + "meta": {}, + "tainted": false + }, + "deposed": [], + "provider": "" + }, + "aws_vpc.example": { + "type": "aws_vpc", + "depends_on": [], + "primary": { + "id": "vpc-14ff2570", + "attributes": { + "cidr_block": "172.10.0.0/20", + "default_network_acl_id": "acl-2b28b94f", + "default_route_table_id": "rtb-ff04639b", + "default_security_group_id": "sg-d3fa7db4", + "dhcp_options_id": "dopt-a30b4bc6", + "enable_classiclink": "false", + "enable_dns_hostnames": "false", + "enable_dns_support": "true", + "id": "vpc-14ff2570", + "instance_tenancy": "default", + "main_route_table_id": "rtb-ff04639b", + "tags.%": "1", + "tags.Name": "example" + }, + "meta": {}, + "tainted": false + }, + "deposed": [], + "provider": "" + } + }, + "depends_on": [] + } + ] +} +``` + +`terraform show` 명령어로 적용된 내용을 확인해 볼 수 있다. + +## EC2 인스턴스 구성 + +이제 EC2 인스턴스를 구성해 보자. `aws-ec2.tf`라는 파일로 다음의 내용을 넣는다. + +```hcl +variable "key_pair" { + default = "outsider-aws" +} + +data "aws_ami" "ubuntu" { + most_recent = true + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] + } + filter { + name = "virtualization-type" + values = ["hvm"] + } + owners = ["099720109477"] # Canonical +} + +resource "aws_instance" "example-server" { + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.example-a.id}" + vpc_security_group_ids = ["${aws_security_group.example-allow-all.id}"] + key_name = "${var.key_pair}" + count = 3 + tags { + Name = "examples" + } +} +``` + +여기서 처음으로 `data` 키워드로 데이터소스를 정의했다. 데이터소스는 프로바이더에서 값을 가져오는 기능을 하는데 `aws_ami`로 타입을 지정하고 Canonical에서 등록한 Ubuntu 16.04의 AMI ID를 조회해 온 것이다. 이런 식으로 최신 Amazon Linux의 AMI를 조회해서 사용한다거나 할 수 있어서 이런 값을 하드 코딩할 필요가 없다. + +그 아래 `aws_instance`는 EC2 인스턴스를 지정한 것이다. `t2.micro`로 띄우고 앞에서 조회한 AMI의 ID를 사용하도록 했다. 그리고 위에서 정의한 VPC와 subnet을 사용하고 키는 선언해놓은 `var.key_pair` 변수를 참조하도록 했다. count=3이므로 서버는 3대를 띄우겠다는 의미이다. + +이제 plan을 먼저 실행해보자. + +```bash +$ terraform plan +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but +will not be persisted to local or remote state storage. + +aws_vpc.example: Refreshing state... (ID: vpc-14ff2570) +data.aws_ami.ubuntu: Refreshing state... +aws_subnet.example-a: Refreshing state... (ID: subnet-4fbcdb39) +aws_subnet.example-c: Refreshing state... (ID: subnet-40b81718) +aws_security_group.example-allow-all: Refreshing state... (ID: sg-d2fa7db5) + +The Terraform execution plan has been generated and is shown below. +Resources are shown in alphabetical order for quick scanning. Green resources +will be created (or destroyed and then created if an existing resource +exists), yellow resources are being changed in-place, and red resources +will be destroyed. Cyan entries are data sources to be read. + +Note: You didn't specify an "-out" parameter to save this plan, so when +"apply" is called, Terraform can't guarantee this is what will execute. + ++ aws_instance.example-server.0 + ami: "ami-18afc47f" + associate_public_ip_address: "" + availability_zone: "" + ebs_block_device.#: "" + ephemeral_block_device.#: "" + instance_state: "" + instance_type: "t2.micro" + key_name: "outsider-aws" + network_interface_id: "" + placement_group: "" + private_dns: "" + private_ip: "" + public_dns: "" + public_ip: "" + root_block_device.#: "" + security_groups.#: "" + source_dest_check: "true" + subnet_id: "subnet-4fbcdb39" + tags.%: "1" + tags.Name: "examples" + tenancy: "" + vpc_security_group_ids.#: "1" + vpc_security_group_ids.2117745025: "sg-d2fa7db5" + ++ aws_instance.example-server.1 + ami: "ami-18afc47f" + associate_public_ip_address: "" + availability_zone: "" + ebs_block_device.#: "" + ephemeral_block_device.#: "" + instance_state: "" + instance_type: "t2.micro" + key_name: "outsider-aws" + network_interface_id: "" + placement_group: "" + private_dns: "" + private_ip: "" + public_dns: "" + public_ip: "" + root_block_device.#: "" + security_groups.#: "" + source_dest_check: "true" + subnet_id: "subnet-4fbcdb39" + tags.%: "1" + tags.Name: "examples" + tenancy: "" + vpc_security_group_ids.#: "1" + vpc_security_group_ids.2117745025: "sg-d2fa7db5" + ++ aws_instance.example-server.2 + ami: "ami-18afc47f" + associate_public_ip_address: "" + availability_zone: "" + ebs_block_device.#: "" + ephemeral_block_device.#: "" + instance_state: "" + instance_type: "t2.micro" + key_name: "outsider-aws" + network_interface_id: "" + placement_group: "" + private_dns: "" + private_ip: "" + public_dns: "" + public_ip: "" + root_block_device.#: "" + security_groups.#: "" + source_dest_check: "true" + subnet_id: "subnet-4fbcdb39" + tags.%: "1" + tags.Name: "examples" + tenancy: "" + vpc_security_group_ids.#: "1" + vpc_security_group_ids.2117745025: "sg-d2fa7db5" + + +Plan: 3 to add, 0 to change, 0 to destroy. +``` + +EC2 인스턴스 3대가 잘 표시되었으므로 이제 실제로 적용해보자. + +```bash +terraform apply +aws_vpc.example: Refreshing state... (ID: vpc-14ff2570) +data.aws_ami.ubuntu: Refreshing state... +aws_subnet.example-c: Refreshing state... (ID: subnet-40b81718) +aws_security_group.example-allow-all: Refreshing state... (ID: sg-d2fa7db5) +aws_subnet.example-a: Refreshing state... (ID: subnet-4fbcdb39) +aws_instance.example-server.1: Creating... + ami: "" => "ami-18afc47f" + associate_public_ip_address: "" => "" + availability_zone: "" => "" + ebs_block_device.#: "" => "" + ephemeral_block_device.#: "" => "" + instance_state: "" => "" + instance_type: "" => "t2.micro" + key_name: "" => "outsider-aws" + network_interface_id: "" => "" + placement_group: "" => "" + private_dns: "" => "" + private_ip: "" => "" + public_dns: "" => "" + public_ip: "" => "" + root_block_device.#: "" => "" + security_groups.#: "" => "" + source_dest_check: "" => "true" + subnet_id: "" => "subnet-4fbcdb39" + tags.%: "" => "1" + tags.Name: "" => "examples" + tenancy: "" => "" + vpc_security_group_ids.#: "" => "1" + vpc_security_group_ids.2117745025: "" => "sg-d2fa7db5" +aws_instance.example-server.0: Creating... + ami: "" => "ami-18afc47f" + associate_public_ip_address: "" => "" + availability_zone: "" => "" + ebs_block_device.#: "" => "" + ephemeral_block_device.#: "" => "" + instance_state: "" => "" + instance_type: "" => "t2.micro" + key_name: "" => "outsider-aws" + network_interface_id: "" => "" + placement_group: "" => "" + private_dns: "" => "" + private_ip: "" => "" + public_dns: "" => "" + public_ip: "" => "" + root_block_device.#: "" => "" + security_groups.#: "" => "" + source_dest_check: "" => "true" + subnet_id: "" => "subnet-4fbcdb39" + tags.%: "" => "1" + tags.Name: "" => "examples" + tenancy: "" => "" + vpc_security_group_ids.#: "" => "1" + vpc_security_group_ids.2117745025: "" => "sg-d2fa7db5" +aws_instance.example-server.2: Creating... + ami: "" => "ami-18afc47f" + associate_public_ip_address: "" => "" + availability_zone: "" => "" + ebs_block_device.#: "" => "" + ephemeral_block_device.#: "" => "" + instance_state: "" => "" + instance_type: "" => "t2.micro" + key_name: "" => "outsider-aws" + network_interface_id: "" => "" + placement_group: "" => "" + private_dns: "" => "" + private_ip: "" => "" + public_dns: "" => "" + public_ip: "" => "" + root_block_device.#: "" => "" + security_groups.#: "" => "" + source_dest_check: "" => "true" + subnet_id: "" => "subnet-4fbcdb39" + tags.%: "" => "1" + tags.Name: "" => "examples" + tenancy: "" => "" + vpc_security_group_ids.#: "" => "1" + vpc_security_group_ids.2117745025: "" => "sg-d2fa7db5" +aws_instance.example-server.1: Still creating... (10s elapsed) +aws_instance.example-server.2: Still creating... (10s elapsed) +aws_instance.example-server.0: Still creating... (10s elapsed) +aws_instance.example-server.1: Still creating... (20s elapsed) +aws_instance.example-server.2: Still creating... (20s elapsed) +aws_instance.example-server.0: Still creating... (20s elapsed) +aws_instance.example-server.1: Creation complete +aws_instance.example-server.2: Creation complete +aws_instance.example-server.0: Creation complete + +Apply complete! Resources: 3 added, 0 changed, 0 destroyed. + +The state of your infrastructure has been saved to the path +below. This state is required to modify and destroy your +infrastructure, so keep it safe. To inspect the complete state +use the `terraform show` command. + +State path: terraform.tfstate +``` + +AWS 웹 콘솔에 가면 3대가 잘 뜬 걸 볼 수 있다. + +이런 식으로 필요한 리소스를 관리하고 설정을 변경하면서 관리하면 인프라를 모두 코드로 관리할 수 있다. + +## 리소스 그래프 +`terraform graph` 명령어를 사용하면 설정한 리소스의 의존성 그래프를 그릴 수 있다. + +```hcl +$ terraform graph +digraph { + compound = "true" + newrank = "true" + subgraph "root" { + "[root] aws_instance.example-server" [label = "aws_instance.example-server", shape = "box"] + "[root] aws_security_group.example-allow-all" [label = "aws_security_group.example-allow-all", shape = "box"] + "[root] aws_subnet.example-a" [label = "aws_subnet.example-a", shape = "box"] + "[root] aws_subnet.example-c" [label = "aws_subnet.example-c", shape = "box"] + "[root] aws_vpc.example" [label = "aws_vpc.example", shape = "box"] + "[root] data.aws_ami.ubuntu" [label = "data.aws_ami.ubuntu", shape = "box"] + "[root] provider.aws" [label = "provider.aws", shape = "diamond"] + "[root] aws_instance.example-server" -> "[root] aws_security_group.example-allow-all" + "[root] aws_instance.example-server" -> "[root] aws_subnet.example-a" + "[root] aws_instance.example-server" -> "[root] data.aws_ami.ubuntu" + "[root] aws_instance.example-server" -> "[root] var.key_pair" + "[root] aws_security_group.example-allow-all" -> "[root] aws_vpc.example" + "[root] aws_subnet.example-a" -> "[root] aws_vpc.example" + "[root] aws_subnet.example-c" -> "[root] aws_vpc.example" + "[root] aws_vpc.example" -> "[root] provider.aws" + "[root] data.aws_ami.ubuntu" -> "[root] provider.aws" + "[root] root" -> "[root] aws_instance.example-server" + "[root] root" -> "[root] aws_subnet.example-c" + } +} +``` + +이 형식은 GraphViz 형식이므로 그래프를 그려주는 서비스에 접근하면 의존성 그래프를 그려준다. + +image + +## 인프라 삭제 + +실제로 운영할 때는 전체 인프라를 삭제할 일은 없겠지만, 삭제 기능도 제공한다. 실제 사용할 때는 아마 일부 구성을 지우고 적용하는 접근을 할 것이다. + +`terraform plan -destroy`처럼 plan에 -destroy 옵션을 제공하면 전체 삭제에 대한 플랜을 볼 수 있다. + +```bash +$ terraform plan -destroy +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but +will not be persisted to local or remote state storage. + +aws_vpc.example: Refreshing state... (ID: vpc-14ff2570) +data.aws_ami.ubuntu: Refreshing state... +aws_subnet.example-a: Refreshing state... (ID: subnet-4fbcdb39) +aws_subnet.example-c: Refreshing state... (ID: subnet-40b81718) +aws_security_group.example-allow-all: Refreshing state... (ID: sg-d2fa7db5) +aws_instance.example-server.1: Refreshing state... (ID: i-0d3d99af50750c06b) +aws_instance.example-server.0: Refreshing state... (ID: i-08e00391c847c219f) +aws_instance.example-server.2: Refreshing state... (ID: i-03a26df56274ab044) + +The Terraform execution plan has been generated and is shown below. +Resources are shown in alphabetical order for quick scanning. Green resources +will be created (or destroyed and then created if an existing resource +exists), yellow resources are being changed in-place, and red resources +will be destroyed. Cyan entries are data sources to be read. + +Note: You didn't specify an "-out" parameter to save this plan, so when +"apply" is called, Terraform can't guarantee this is what will execute. + +- aws_instance.example-server.0 + +- aws_instance.example-server.1 + +- aws_instance.example-server.2 + +- aws_security_group.example-allow-all + +- aws_subnet.example-a + +- aws_subnet.example-c + +- aws_vpc.example + +- data.aws_ami.ubuntu + + +Plan: 0 to add, 0 to change, 7 to destroy. +``` + +모두 지워지는 것을 확인했으므로 이제 terraform destroy를 실행하면 여기서 설정한 모든 구성이 제거된다. + +```bash +$ terraform destroy +Do you really want to destroy? + Terraform will delete all your managed infrastructure. + There is no undo. Only 'yes' will be accepted to confirm. + + Enter a value: ㅛyes + +aws_vpc.example: Refreshing state... (ID: vpc-14ff2570) +data.aws_ami.ubuntu: Refreshing state... +aws_subnet.example-a: Refreshing state... (ID: subnet-4fbcdb39) +aws_subnet.example-c: Refreshing state... (ID: subnet-40b81718) +aws_security_group.example-allow-all: Refreshing state... (ID: sg-d2fa7db5) +aws_instance.example-server.0: Refreshing state... (ID: i-08e00391c847c219f) +aws_instance.example-server.2: Refreshing state... (ID: i-03a26df56274ab044) +aws_instance.example-server.1: Refreshing state... (ID: i-0d3d99af50750c06b) +aws_subnet.example-c: Destroying... +aws_instance.example-server.2: Destroying... +aws_instance.example-server.0: Destroying... +aws_instance.example-server.1: Destroying... +aws_subnet.example-c: Destruction complete +aws_instance.example-server.0: Still destroying... (10s elapsed) +aws_instance.example-server.2: Still destroying... (10s elapsed) +aws_instance.example-server.1: Still destroying... (10s elapsed) +aws_instance.example-server.2: Still destroying... (20s elapsed) +aws_instance.example-server.0: Still destroying... (20s elapsed) +aws_instance.example-server.1: Still destroying... (20s elapsed) +aws_instance.example-server.2: Still destroying... (30s elapsed) +aws_instance.example-server.0: Still destroying... (30s elapsed) +aws_instance.example-server.1: Still destroying... (30s elapsed) +aws_instance.example-server.2: Still destroying... (40s elapsed) +aws_instance.example-server.0: Still destroying... (40s elapsed) +aws_instance.example-server.1: Still destroying... (40s elapsed) +aws_instance.example-server.1: Still destroying... (50s elapsed) +aws_instance.example-server.2: Still destroying... (50s elapsed) +aws_instance.example-server.0: Still destroying... (50s elapsed) +aws_instance.example-server.2: Destruction complete +aws_instance.example-server.1: Destruction complete +aws_instance.example-server.0: Still destroying... (1m0s elapsed) +aws_instance.example-server.0: Destruction complete +aws_security_group.example-allow-all: Destroying... +aws_subnet.example-a: Destroying... +aws_subnet.example-a: Destruction complete +aws_security_group.example-allow-all: Destruction complete +aws_vpc.example: Destroying... +aws_vpc.example: Destruction complete + +Destroy complete! Resources: 7 destroyed. +``` diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205\355\202\244\354\233\214\353\223\234.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205\355\202\244\354\233\214\353\223\234.md" new file mode 100644 index 00000000..6d404890 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terraform\342\200\205\355\202\244\354\233\214\353\223\234.md" @@ -0,0 +1,251 @@ +--- +title: 'Terraform 키워드' +lastUpdated: '2024-03-02' +--- + +resource는 테라폼에서 가장 중요한 요소이다. resource 블록은 하나 이상의 인프라스트럭처의 오브젝트를 기술한다. 아래는 providers로 AWS를 사용하고 있을 때 인스턴스를 사용하는 예제이다. + +```hcl +resource "aws_instance" "web" { + ami = "ami-a1b2c3d4" + instance_type = "t2.micro" +} +``` + +위에 첫 번째 라인을 살펴보면 resource 옆에 첫 번째 자리는 리소스 타입으로 “aws_instance”를 써주고 두 번째 자리에는 “web”을 적어줬다. 리소스 타입은 사전에 정의된 AWS 리소스 이름이다. 여기서 “aws_instance”는 인스턴스를 나타내고 있다. aws라는 프로바이더 정보를 prefix로 사용하고 있는 것을 알 수 있다. HCL에서 key-value 조합으로 코드를 쓸 때 등호(=)는 보통 라인을 맞춰서 사용한다. + +resource 옆 두 번째 자리에 `web`은 실제 생성되는 리소스의 이름과 별개로 테라폼 내에서 사용될 이름을 써준 것이다. 또한 providers에 따라 사용 가능한 resource 타입이 다르다. + +각 resource는 오직 한 개의 리소스 타입만을 갖는다. + +```hcl +resource "aws_vpc" "my_vpc" { + cidr_block = "172.16.0.0/16" + + tags = { + Name = "tf-example" + } +} + +resource "aws_subnet" "my_subnet" { + vpc_id = aws_vpc.my_vpc.id + cidr_block = "172.16.10.0/24" + availability_zone = "us-west-2a" + + tags = { + Name = "tf-example" + } +} + +resource "aws_network_interface" "foo" { + subnet_id = aws_subnet.my_subnet.id + private_ips = ["172.16.10.100"] + + tags = { + Name = "primary_network_interface" + } +} + +resource "aws_instance" "foo" { + ami = "ami-005e54dee72cc1d00" # us-west-2 + instance_type = "t2.micro" + + network_interface { + network_interface_id = aws_network_interface.foo.id + device_index = 0 + } + + credit_specification { + cpu_credits = "unlimited" + } +} +``` + +resource 블록 바디에는 ami, instance_type이 지정되었는데 AWS 리소스에서 사용 가능한 매개변수를 적어주면 된다. AWS 콘솔에서 설정하는 변수는 대부분 사용 가능하다. + +```hcl +resource "aws_db_instance" "example" { + # ... + + timeouts { + create = "60m" + delete = "2h" + } +``` + + +# environment variables + +AWS_ACCESS_KEY 처럼 중요한 정보는 환경변수로 관리한다. TF_VAR_를 Prefix로 사용하면 terraform에서 읽어서 등록해준다. + +```hcl +export TF_VAR_aws_access_key="blabla" + ``` + +development, staging, production 환경에 상관없는 공통으로 적용되는 변수는 terraform.tfvars과 같은 파일로 관리한다. 이 파일은 terraform apply 명령에서 자동으로 읽힌다. 파일 형식은 다음과 같이 지정한다. + +# terraform.tfvars + +```hcl +region = "us-west-2" +cidrs = [ "10.0.0.0/16", "10.1.0.0/16" ] +``` + +환경에 따라 구분되어야 하는 변수는 production.tfvars, development.tfvars처럼 별도의 파일로 관리해준다. + +terraform apply는 아래와 같이 -var-file 옵션과 함께 사용한다. 위에서 등록한 환경변수가 쓰인다. 자동으로 읽히는 terraform.tfvars는 -var-file로 지정할 필요 없다. + +```hcl +terraform apply \ + -var-file="secret.tfvars" \ + -var-file="production.tfvars" +``` +변수 사용 예시는 다음과 같다. var.region를 적어주면 plan, apply 시에 us-west-2로 대치돼서 사용된다. +```hcl +provider "aws" { + profile = "default" + region = var.region +} +``` + +# normal variables + +한편, 환경변수 외에 변수는 variables.tf 파일에 지정해주면 된다. terraform은 확장자가 tf인 모든 파일을 읽어 들인다. + +아래는 위에서 지정한 환경변수를 tf 파일에 담아내는 예제이다. (앞에 글에서도 이야기했지만 terraform 명령어가 실행되는 디렉터리에 있는 파일만 읽어 들인다. 다른 경로의 tf 파일을 읽기 위해서는 module을 사용해야 한다) + +```hcl +# variables.tf +variable "region" { + default = "us-west-2" +} +``` + + +map은 아래와 같이 사용하면 된다. + +```hcl +# variables.tf +variable "amis" { + type = "map" + default = { + "us-east-1" = "ami-b374d5a5" + "us-west-2" = "ami-fc0b939c" + } +} + +``` +위에서 설정한 변수와 환경변수를 조합해서 resource를 생성하는 예제를 통해 변수가 어떻게 사용이 되는지 살펴보자. variables.tf에 있는 변수를 var.amis를 통해 가져왔고, 환경변수 var.regionvar.region을 사용해주는 예제이다. + +```hcl +resource "aws_instance" "example" { + ami = var.amis[var.region] + instance_type = "t2.micro" +} + ``` + +# output + +output은 테라폼이 apply 될 때 지정된 리소스를 출력해 준다. 혹은 terraform output으로 확인할 수 있다. + +아래는 테라폼의 공식 예제로 AWS에 인스턴스를 생성하고 Elastic IP를 연결하는 코드이다. + +```hcl +# example.tf +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 2.70" + } + } +} + +provider "aws" { + profile = "default" + region = "us-west-2" +} + +resource "aws_instance" "example" { + ami = "ami-08d70e59c07c61a3a" + instance_type = "t2.micro" +} + +resource "aws_eip" "ip" { + vpc = true + instance = aws_instance.example.id +} + ``` + +위 예제에서 생성되는 eip는 다음과 같이 output.tf 파일을 생성해서 확인할 수 있다. 파일 이름은 중요하지 않다. 앞에 과정에서 이야기했듯이 테라폼은 확장자가 tf인 모든 파일을 읽다. public_ippublic_ip는 위에서 생성한 aws_eip의 사전에 정의된 속성이다. + +output "ip" { +```hcl + value = aws_eip.ip.public_ip +} + +``` + +terraform apply가 되면 아래와 같은 출력을 확인할 수 있다. + +```hcl +$ terraform apply +... + +Apply complete! Resources: 0 added, 0 changed, 0 destroyed. + +Outputs: + + ip = 50.17.232.209 +또한 다음과 같이 terraform output을 통해 관심사만 뽑아볼 수 있다. + +$ terraform output ip +50.17.232.209 + ``` + +# data source + +data source는 data 블록을 통해 사용할 수 있다. 프로바이더에서 제공하는 리소스 정보를 가져와서 테라폼에서 사용할 수 있는 형태로 매핑시킬 수 있다. 즉, 이미 클라우드 콘솔에 존재하는 리소스를 가져오는 것이다. 아래 예시를 살펴보자. + +```hcl +data "aws_ami" "example" { + most_recent = true + + owners = ["self"] + tags = { + Name = "app-server" + Tested = "true" + } +} + +``` +“aws_ami”의 prefix 정보를 통해 프로바이더가 AWS라는 것을 알 수 있다. data 블록에 지정된 tags 정보를 기반으로 이름이 “app-server”, Tested 필드가 “true”인 AMI 정보를 가져오게 된다. + +다음과 같이 필터를 통해 정보를 가져오는 방법도 있다. + +```hcl +# Find the latest available AMI that is tagged with Component = web +data "aws_ami" "web" { + filter { + name = "state" + values = ["available"] + } + + filter { + name = "tag:Component" + values = ["web"] + } + + most_recent = true +} + ``` + +이렇게 가져온 정보는 인스턴스를 생성하는 리소스에서 다음과 같이 data.aws_ami.web.id처럼 사용된다. + +```hcl +resource "aws_instance" "web" { + ami = data.aws_ami.web.id + instance_type = "t1.micro" +} + +``` diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terratest.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terratest.md" new file mode 100644 index 00000000..4b820e1f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/Terratest.md" @@ -0,0 +1,206 @@ +--- +title: 'Terratest' +lastUpdated: '2024-03-02' +--- + +Terratest has a pretty simple premise. It is ans open-source testing framework created by Terragrunt the includes helper functions for testing terraform code by providing a simple, reusable, and easy-to-use interface for creating and running automated tests on cloud resources. + +It's able to apply terraform code, provision the resources, run the test cases, and then destroy everything. Terratest is most beneficially used for testing terraform modules to make sure that the modules work as expected before being consumed to create real infrastructues. + +There’s a common pitfall of testing for basic or obvious tests that don’t really provide any use. For instance, testing a terraform module that creates an s3 bucket to make sure it creates the s3 bucket is almost as bad as testing whether 1 == 1. We can trust terraform to create what we tell it to. And if there is a syntax error in the terraform code, it will be obvious when the apply fails and tells us what mistake we are making. + +Tests should be insightful and designed to catch instances when the apply will still complete successfully, but the final infrastructure is not what we are expecting. That’s the sweet spot where terratest can truly shine. + +# Interconnectivity and Functionality of Resources + +- Let's say we have a terraform module that creates an ECS clustser and runs tasks on that cluster. If you've ever created an ECS cluster using EC2 as the launch type, you'll know we need to create a decent amount of infrastructure to run ECS on top of. +- Not only do we need to create the ECS cluster, ECR repository, ECS service, and ECS task definition, but we also need to create the EC2s that ECS will use to place tasks on. And that includes an ASG, launch template, capacity provider, etc. +- And we'll also need to create a load balancer to send traffic to our tasks running on ECS. For that we'll need an ALB, target group, listeners, stc. Not to mention all the additional resources you'll need to properly configure your networking and security, such as IAM roles and security groups. + +
+ +- Terraform is fully capable of creating each of these resources and we can trust that terraform will create whay we've declaratively defined. But what about how these resources interact with each other? A terraform apply will tell us that all the resources have been created, but what terraform can't tell us is whether our EC2 instances have joined the ECS cluster, or if the tasks running on ECS are in a running state, or if our app is even accessible through the load balancer. These are behaviors that happen outside of the resource creation. + +- The interconnectivity between resources and their functionality is just as important as the creation of the resources. If not properly tested, we could end up with a broken environment that passes a terraform apply flawlessly. + +- This is exactly the type of terraform module that could benefit from using terratest to validate its functionality. Let's look at some examples of how we would do that: + +## Instance Count + +Let's say we want to make sure that the EC2 instances are joining the cluster properly. There are a few issues that could cause our instances to not be able to join the cluster, ssuch as an issue with the ECS agent or IAM permissions. We want to make sure that any changes we make to the modulw don't affect the ability of our instances to join the cluster. + +For this, we will create a test case in our terratest to make sure that our cluster's instance count is as expected. + +```go +func TestECSClusterInstanceCount(t *testing.T) { + + // Generate a random AWS region to use for the test + awsRegion := "us-west-2" + + // Generate a random name to use for the ECS cluster and Terraform resources + ecsClusterName := fmt.Sprintf("ecs-cluster-%s", random.UniqueId()) + + // Define the Terraform module that creates the ECS cluster + terraformOptions := &terraform.Options{ + TerraformDir: "./ecs-cluster", + Vars: map[string]interface{}{ + "ecs_cluster_name": ecsClusterName, + "aws_region": awsRegion, + "instance_type": "t2.micro", + "instance_count": 3, + }, + } + + // Create the ECS cluster using Terraform + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) + + // Create an AWS session to use for the test + sess := session.Must(session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + })) + + // Create an ECS client using the AWS SDK + ecsClient := ecs.New(sess, aws.NewConfig().WithRegion(awsRegion)) + + // Get the ECS cluster by name + describeClustersInput := &ecs.DescribeClustersInput{ + Clusters: []*string{aws.String(ecsClusterName)}, + } + describeClustersOutput, err := ecsClient.DescribeClusters(describeClustersInput) + if err != nil { + t.Fatalf("Failed to describe ECS cluster: %s", err) + } + assert.Len(t, describeClustersOutput.Clusters, 1) + + // Get the instance count of the ECS cluster + instanceCount := aws.Int64Value(describeClustersOutput.Clusters[0].RegisteredContainerInstancesCount) + + // Assert that the instance count matches the expected value + expectedInstanceCount := int64(3) + assert.Equal(t, expectedInstanceCount, instanceCount) +} +``` + +- This test functions as an important check on the terraform module to make sure that the EC2s and autoscaling group are properly associated with the cluster. It uses the AWS SDK to get the instance count of the cluster and assert that it matches the expected value. + +- Terraform alone would tell us that the instances have been created, but only terratest can tell us that the instances have joined the cluster. + +## Running Task Count + +- ECS is designed to run tasks on the cluster, but there are a multitude of issues that could cause our tasks to fail before reaching a steady state. + +- It could be related to permissions, network connectivity, or even an issue with the application the task is running. Terraform can tell us that the ECS service is created ans starting tasks, but terraform has no idea if those tasks are constantly failing or stopping. Enter terratest: + +```go +func TestECSServiceTaskCount(t *testing.T) { + // Generate a random AWS region to use for the test + awsRegion := "us-west-2" + + // Generate a random name to use for the ECS service, task definition, and Terraform resources + ecsServiceName := fmt.Sprintf("ecs-service-%s", random.UniqueId()) + taskDefinitionName := fmt.Sprintf("task-def-%s", random.UniqueId()) + + // Define the Terraform module that creates the ECS service and task definition + terraformOptions := &terraform.Options{ + TerraformDir: "./ecs-service", + Vars: map[string]interface{}{ + "ecs_service_name": ecsServiceName, + "task_definition_name": taskDefinitionName, + "aws_region": awsRegion, + "container_image": "nginx:latest", + "container_port": 80, + "desired_task_count": 3, + "launch_type": "FARGATE", + "fargate_platform_arn": "arn:aws:ecs:us-west-2:123456789012:platform/awsvpc", + "subnet_ids": []string{"subnet-12345678", "subnet-23456789"}, + "security_group_ids": []string{"sg-12345678"}, + "assign_public_ip": true, + "task_execution_role_arn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole", + }, + } + + // Create the ECS service and task definition using Terraform + defer terraform.Destroy(t, terraformOptions) + terraform.InitAndApply(t, terraformOptions) + + // Create an AWS session to use for the test + sess := session.Must(session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + })) + + // Create an ECS client using the AWS SDK + ecsClient := ecs.New(sess, aws.NewConfig().WithRegion(awsRegion)) + + // Get the ECS service by name + describeServicesInput := &ecs.DescribeServicesInput{ + Cluster: aws.String("default"), + Services: []*string{aws.String(ecsServiceName)}, + } + describeServicesOutput, err := ecsClient.DescribeServices(describeServicesInput) + if err != nil { + t.Fatalf("Failed to describe ECS service: %s", err) + } + assert.Len(t, describeServicesOutput.Services, 1) + + // Get the task count of the ECS service + taskCount := aws.Int64Value(describeServicesOutput.Services[0].RunningCount) + + // Assert that the task count matches the expected value + expectedTaskCount := int64(3) + assert.Equal(t, expectedTaskCount, taskCount) +} +``` + +- This terratest function uses the AWS SDK to get the running task count of the service and assert that is matches the expected value. If there is an issue with how the task is configured, terratest will inform the user. + +## Network Health Checks + +- Another important area that requires testing is the networking. It's great that terraform can verify that it's built all our resources, but that won't tell us that our resources are properly talking to each other. +- In our ECS example, we are creating a load balancer to send traffic to our tasks running in the cluster. A good way to verify that our endpoint can reach the application is to make sure we have health checks that aren't failing. That is something we can test with terratest. + +```go +func TestTargetGroupHealthCheck(t *testing.T) { + + // Generate a random name to use for the Target Group + targetGroupName := fmt.Sprintf("test-tg-%s", random.UniqueId()) + + // Set up an AWS session and create a new Target Group + awsRegion := "us-west-2" // Replace with your desired AWS region + awsSession := session.Must(session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + })) + elbv2Client := elbv2.New(awsSession) + targetGroupArn, err := aws.CreateALBTargetGroup(t, awsRegion, targetGroupName) + if err != nil { + t.Fatalf("Failed to create Target Group: %v", err) + } + + // Wait for the Target Group to become active + err = aws.WaitForALBTargetGroupAvailability(t, elbv2Client, targetGroupArn) + if err != nil { + t.Fatalf("Failed waiting for Target Group to become active: %v", err) + } + + // Check the Target Group health checks + targetGroupHealthCheckResult, err := elbv2Client.DescribeTargetHealth(&elbv2.DescribeTargetHealthInput{ + TargetGroupArn: targetGroupArn, + }) + if err != nil { + t.Fatalf("Failed to describe Target Group health: %v", err) + } + + // Check that all targets in the Target Group are healthy + for _, targetHealth := range targetGroupHealthCheckResult.TargetHealthDescriptions { + assert.Equal(t, "healthy", *targetHealth.TargetHealth.State, "Target %s is not healthy", *targetHealth.Target.Id) + } +} +``` + +- This terratest function will check the health of our target group to make sure that the target is healthy. This will help us verify that traffic can properly flow to our ECS cluster. + +--- +reference +- https://gist.github.com/DanielWsk/f2f9aef6aca0a9a3df597b1103cb8d02 +- https://spacelift.io/blog/what-is-terratest +- https://opendevops.academy/terratest-test-a-kubernetes-deployment-and-service-890ec4d1bfd0 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/aws\342\200\205\354\204\234\353\262\204\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\352\265\254\354\266\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/aws\342\200\205\354\204\234\353\262\204\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\352\265\254\354\266\225.md" new file mode 100644 index 00000000..e906d40d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Terraform/aws\342\200\205\354\204\234\353\262\204\342\200\205\353\204\244\355\212\270\354\233\214\355\201\254\342\200\205\352\265\254\354\266\225.md" @@ -0,0 +1,168 @@ +--- +title: 'aws 서버 네트워크 구축' +lastUpdated: '2024-03-02' +--- + +### 1. 네트워크 구성 + +```hcl +resource "aws_vpc" "scenario_1_vpc" { + cidr_block = "172.16.0.0/26" + tags = { + Name = "scenario-1-vpc" + } +} +``` + +- VPC는 AWS에서 다루는 네트워크의 기본단위이다. +- 사용자들이 앞으로 만들 서버와 인터넷을 통해 통신하기 위해선 네트워크 구축이 필요한데 가장 먼저 VPC를 만들어주어야 한다. +- cidr_block을 통해 어떤 ip 대역을 사용할지 결정한다. 보통 `10.0.0.0/8`, `172.16.0.0/16`, `192.168.0.0/24`의 private ip 대역을 사용하지만 공인 ip 대역 역시 사용이 가능하다. +- 다만 공인 ip 대역 사용시 인터넷을 통한 통신이 불가능하므로 private ip대역을 사용하는것이 좋다. + +VPC를 구축한 다음엔 다음 코드로 서브넷팅을 할 차례다. + +```hcl +resource "aws_subnet" "scenario-1-public-subnet" { + vpc_id = aws_vpc.scenario_1_vpc.id + cidr_block = "172.16.0.0/28" + availability_zone = "ap-northeast-2a" + tags = { + Name = "scenario-1-public-subnet" + } +} +resource "aws_subnet" "scenario-1-private-subnet" { + vpc_id = aws_vpc.scenario_1_vpc.id + cidr_block = "172.16.0.16/28" + availability_zone = "ap-northeast-2a" + tags = { + Name = "scenario-1-private-subnet" + } +} +resource "aws_subnet" "scenario-1-private-subnet-2" { + vpc_id = aws_vpc.scenario_1_vpc.id + cidr_block = "172.16.0.32/28" + availability_zone = "ap-northeast-2b" + tags = { + Name = "scenario-1-private-subnet-2" + } +} +``` + +- VPC가 허용하는 대역 안에서 웹서버를 배치할 public subnet 1개, 데이터베이스를 배치할 private 2개를 생성했다. +- 서브넷을 사용할때는 가용영역을 설정할 수 있는데, 여러 가용영역에 서브넷을 만들어서 장애발생시에도 고가용성을 유지하도록 구현할수 있다. +- private 서브넷은 internet gateway와 연결되어있지 않기 때문에 인터넷을 통한 외부 공격을 방지할수 있는 장점이 있다. + +public 서브넷을 만들기 위해선 internet gateway를 정의해야한다. + +```hcl +resource "aws_internet_gateway" "scenario_1_igw" { + vpc_id = aws_vpc.scenario_1_vpc.id + tags = { + Name = "main" + } +} +``` + +- internet gateway는 VPC와 인터넷 사이를 연결해준다. +- internet gateway에 연결된 서브넷(public 서브넷)은 인터넷을 통한 통신이 가능하며, 공인 ip 주소를 VPC내의 사설 ip 주소로 변환해주는 NAT로써의 역할을 수행한다 + +internet gateway를 생성 했다면 router가 참조할수 있는 routing table들을 생성해주는 작업이 필요하다. + +```hcl +resource "aws_route_table" "scenario_1_public_route_table" { + vpc_id = aws_vpc.scenario_1_vpc.id + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.scenario_1_igw.id + } + tags = { + Name = "main" + } +} + +resource "aws_route_table" "scenario_1_private_route_table" { + vpc_id = aws_vpc.scenario_1_vpc.id + tags = { + Name = "main" + } +} + +resource "aws_route_table_association" "scenario_1_public_rt_association" { + subnet_id = aws_subnet.scenario-1-public-subnet.id + route_table_id = aws_route_table.scenario_1_public_route_table.id +} + +resource "aws_route_table_association" "scenario_1_private_rt_1_association" { + subnet_id = aws_subnet.scenario-1-private-subnet.id + route_table_id = aws_route_table.scenario_1_private_route_table.id +} + +resource "aws_route_table_association" "scenario_1_private_rt_2_association" { + subnet_id = aws_subnet.scenario-1-private-subnet-2.id + route_table_id = aws_route_table.scenario_1_private_route_table.id +} +``` + +- routing table은 서브넷의 라우터가 통신할때 참조하는 테이블이다. +- 여기서 public subnet과 private의 차이점이 한번더 드러나는데, public subnet이 참조하는 routing table은 목적지가 공인 ip 대역인 요청을 internet gateway로 보내는 규칙이 필요하다. +- routing table들을 생성하고 필요한 규칙을 추가하면 네트워크 구성은 마무리 된다. + +### security group 구성 + +- security group은 instance의 방화벽의 역활을 담당하는 중요한 오브젝트이다. +- 이 시나리오에서는 웹서버와 데이터베이스와 웹서버를 위해 각각 1개의 security group이 필요하다. +- 웹서버는 고객들이 접근할수 있도록 `0.0.0.0/0` 에 대해 443 포트를, ssh 연결을 위해 `x.x.x.x/32`(관리자가 접속하는 ip)의 22번 포트 연결을 허용해야 한다. +- AWS가 제공하는 security group은 특정 IP 대역뿐만 아니라 특정 security group에 속해 있는 instance와의 통신만 허용하는것이 가능하다. +- 이 점에 착안하며 웹서버를 위한 security group을 생성한후 데이터베이스는 웹서버가 속한 security group의 소스들만 통신을 허용하도록 구성해야 한다. + +```hcl +resource "aws_security_group" "scenario_1_ec2" { + description = "Allow" + vpc_id = aws_vpc.scenario_1_vpc.id + ingress { + description = "communication" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + ingress { + description = "ssh" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["/32"] + } + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_security_group" "scenario_1_rds" { + description = "Allow" + vpc_id = aws_vpc.scenario_1_vpc.id + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_groups = [aws_security_group.scenario_1_ec2.id] + } + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + tags = { + Name = "with logic" + } +} +``` + +--- +참고 +- ttps://www.44bits.io/ko/post/terraform_introduction_infrastrucute_as_code +- https://registry.terraform.io/providers/hashicorp/aws/latest/docs diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Understanding\342\200\205Ansible,\342\200\205Terraform,\342\200\205Puppet,\342\200\205Chef,\342\200\205and\342\200\205Salt.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Understanding\342\200\205Ansible,\342\200\205Terraform,\342\200\205Puppet,\342\200\205Chef,\342\200\205and\342\200\205Salt.md" new file mode 100644 index 00000000..dc2ed026 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/IaC/Understanding\342\200\205Ansible,\342\200\205Terraform,\342\200\205Puppet,\342\200\205Chef,\342\200\205and\342\200\205Salt.md" @@ -0,0 +1,79 @@ +--- +title: 'Understanding Ansible, Terraform, Puppet, Chef, and Salt' +lastUpdated: '2024-03-02' +--- + +Choosing the automation solution that works best for your organization is no easy task. There’s not a single "right" approach—you can automate your enterprise in multiple ways. Indeed, many IT organizations today use more than one automation tool, and a major consideration is how well they work together to achieve business goals. + +Other factors to keep in mind when evaluating automation tools include architecture (is it agent-based or agentless?), programming (is it declarative or procedural?), and language (is it data-serialized or domain-specific?). And of course your operating system. It’s also important to understand the level of community support for each product and what each is primarily engineered to do, such as provisioning, configuration management, and compliance. + +# Each tool approaches IT automation differently + +## Ansible + +Known for its simplicity and ease of use, Ansible Automation Platform is an open source, command-line IT automation software application that uses YAML based `playbooks` to configure systems, deploy software, and orchestrate advanced workflows to support application deployment, system updates, networking configuration and operation, and more. It does not require the installation of an agent on managed nodes, which simplifies the deployment process. And it has strong support for cloud-based infrastructure. + +### declarative and procedural programming + +Ansible can be both **declarative and procedural**—many modules work declaratively, while other modules prefer a procedural programming approach. Additionally, some constructs in the Ansible language, such as conditionals and loops, allow the users to define a procedural logic. This mix offers you the flexibility to focus on what you need to do, rather than strictly adhere to one paradigm. + +### mutable infrastructure + +Configuration mutability means that the configuration (of an infrastructure or an application) can be changed. For example, newer versions of applications can be provisioned by updating or modifying the existing resource instead of eliminating or replacing it. + +Ansible is designed assuming **configuration mutability**. The advantage of this approach is that the automation workflows are simple to understand and easy to troubleshoot. However, in certain scenarios, it can be challenging to deprovision resources without knowing the correct order of operations. + +### Configuration drift + +Ansible helps you combat drift with Ansible Playbooks (automation workflows) that can be set up to detect drift. When drift is detected, it sends a notification to the appropriate person who can make the required modification and return the system to its baseline. + +Because Ansible uses a procedural programming approach, developers can more easily understand when and where their automation configuration is changing, making it faster to isolate a specific portion of the configuration and remediate the drift. + +Depending on the complexity of the IT infrastructure, performing configuration changes on automation solutions that use a declarative programming approach (such as Terraform) can be much more challenging. As a result, sometimes IT organizations prefer to use Ansible to perform simple configuration changes rather than holistically reconfigure an entire IT system with a solution like Terraform. + +## Terrafom + +Terraform is a cloud infrastructure provisioning and deprovisioning tool with an [infrastructure as code (IaC)](https://www.redhat.com/en/topics/automation/what-is-infrastructure-as-code-iac) approach. It's a specific tool with a specific purpose-provisioning. Like Ansible, it has an active open source community and well=supported downstream commercial products. And it has strengths that-when combined with Andible Automation Platform-work well to create efficiencies for many businesses. + +### declarative programming + +Terraform uses an approach called **declarative programming**, which tries to preserve the configuration of an IT infrastructure by defining a desired state. Otherwise, that the sequence of commands that Terraform has to perform to achieve the required configuration changes are not visible or known to the end user. + +### immutable infrastructure + +And Terraform uses an **immutable infrastructure** approach which the configuration (of an infrastructure or an application) can’t be changed. For example, provisioning the newer version of an app requires the previous version to be eliminated and replaced—rather than modified and updated. Resources are destroyed and recreated automatically. It can help users get started quickly as they can easily spin up resources, test something, then tear it down. However, depending on the size of the infrastructure, it can become complex and hard to manage. + +## Puppet + +Puppet is an automation application designed to manage large and complex infrastructuers. By using a model-driven approach with imperative task execution and declareative language to define configurations, it can enforce consistency across a laarge number of systems. It allso has strong reporting and monitoring capabilities, which can help IT teams identify and diagnose issues quickly. + +Puppet is usually run as an agent-based solution, requiring a piece of software on every device it manages, though it also includes agentless capabilities. + +### declarative programming + +Puppet follows the concept of **declarative programming**, meaning the user defines the desired state of the machines being managed. Puppet uses a Domain-Specific Language (DSL) for defining these configurations. Puppet then automates the steps it takes to get the systems to their defined states. Puppet handles automation using a primary server (where you store the defined states) and a Puppet agent (which runs on the systems you specify). + +### Agent-based architecture + +Agent-based architecture describes an infrastructure and automation model that requires specific software components called agents to run on the inventory being managed. The agent and all of its dependencies need to be installed on every target node, requiring additional security checks and rules. This can become a challenge when it’s time to automate objects on which the agent is unavailable or not allowed to run. It also requires agents to be maintained as part of the maintenance support life cycle for organizations. + +## Chef + +Chef is an IT automation platform written in Ruby DSL that transforms infrastructure into code. Similar to Ansible Playbooks, Chef uses reusable definitions known as `cookbooks` and `recipes` to automate how infrastructure is configured. + +### Agent-based architecture + +Chef operates with a master-client architecture. The server part runs on the master machine, while the client portion runs as an agent on every client machine. Chef also has an extra component named “workstation” that stores all of the configurations that are tested then pushed to the central server. + +## Salt + +Salt is a modular automation application written in Python. Desinged for high-speed data collection and execution, it's a configuration management tool with a lightweight ZeroMQ messaging library and concurrency framework that established persistent TCP connections between the server and agents. + +### Agent(client)-based architecture + +chef also has a Server-Client architecture.In contrast to Ansible, Chef uses an agent-based architecture. Here, the Chef server runs on the main machine and the Chef client runs as an agent on each client machine. In addition, there is an extra component called the workstation, which contains all the configurations that are tested and then pulled from the main Chef server to the client machines without any commands. Since managing these pull configurations requires programmer expertise, Chef is more complicated to use than other automation tools—even for seasoned DevOps professionals. + +--- +reference +- https://www.redhat.com/en/topics/automation/understanding-ansible-vs-terraform-puppet-chef-and-salt +- http://kief.com/configuration-drift.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/AWS\342\200\205Load\342\200\205Balancer\342\200\205Controller.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/AWS\342\200\205Load\342\200\205Balancer\342\200\205Controller.md" new file mode 100644 index 00000000..35447c83 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/AWS\342\200\205Load\342\200\205Balancer\342\200\205Controller.md" @@ -0,0 +1,60 @@ +--- +title: 'AWS Load Balancer Controller' +lastUpdated: '2024-03-02' +--- + +AWS Load Balancer Controller is a controller to help manage Elastic Load Balancers for a Kubernetes cluster. + +- It satisfies Kubernetes Ingress resources by provisioning Application Load Balancers. +- It satisfies Kubernetes Service resources by provisioning Network Load Balancers. + +This project was formerly known as "AWS ALB Ingress Controller", we rebranded it to be "AWS Load Balancer Controller". + +## Design + +The following diagram details the AWS components this controller creates. It also demonstrates the route ingress traffic takes from the ALB to the Kubernetes cluster. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/021fb689-acb2-42ac-aa67-b083f9f19fe6) + +> The controller manages the configurations of the resources it creates, and we do not recommend out-of-band modifications to these resources because the controller may revert the manual changes during reconciliation. We recommend to use configuration options provided as best practice, such as ingress and service annotations, controller command line flags and IngressClassParams. + +## Ingress Creation + +This section describes each step (circle) above. This example demonstrates satisfying 1 ingress resource. + +1. The controller watches for ingress events from the API server. When it finds ingress resources that satisfy its requirements, it begins the creation of AWS resources. + +2. An ALB (ELBv2) is created in AWS for the new ingress resource. This ALB can be internet-facing or internal. You can also specify the subnets it's created in using annotations. + +3. Target Groups are created in AWS for each unique Kubernetes service described in the ingress resource. + +4. Listeners are created for every port detailed in your ingress resource annotations. When no port is specified, sensible defaults (80 or 443) are used. Certificates may also be attached via annotations. + +5. Rules are created for each path specified in your ingress resource. This ensures traffic to a specific path is routed to the correct Kubernetes Service. + +Along with the above, the controller also... + +- deletes AWS components when ingress resources are removed from k8s. +- modifies AWS components when ingress resources change in k8s. +- assembles a list of existing ingress-related AWS components on start-up, allowing you to recover if the controller were to be restarted. + +## Ingress Traffic + +AWS Load Balancer controller supports two traffic modes: + +- Instance mode +- IP mode + +By default, Instance mode is used, users can explicitly select the mode via alb.ingress.kubernetes.io/target-type annotation. + +### Instance mode +Ingress traffic starts at the ALB and reaches the Kubernetes nodes through each service's NodePort. This means that services referenced from ingress resources must be exposed by type:NodePort in order to be reached by the ALB. + +### IP mode +Ingress traffic starts at the ALB and reaches the Kubernetes pods directly. CNIs must support directly accessible POD ip via secondary IP addresses on ENI. + + +--- +reference +- https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/guide/service/nlb_ip_mode/ +- https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.5/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Add\342\200\205IAM\342\200\205to\342\200\205RBAC.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Add\342\200\205IAM\342\200\205to\342\200\205RBAC.md" new file mode 100644 index 00000000..b49fd838 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Add\342\200\205IAM\342\200\205to\342\200\205RBAC.md" @@ -0,0 +1,76 @@ +--- +title: 'Add IAM to RBAC' +lastUpdated: '2023-08-26' +--- +## Add new IAM user or role to the Kubernetes RBAC, using kubectl or eksctl + +Before you choose the kubectl or eksctl tool to edit the aws-auth ConfigMap, make sure that you complete step 1. Then, follow steps 2-4 to edit with kubectl. To edit with eksctl, proceed to step 5. + +1. After you identify the cluster creator or admin, configure AWS CLI to use the cluster creator IAM. See Configuration basics for more information. + +To verify that AWS CLI is correctly configured with the IAM entity, run the following command: + +```bash +$ aws sts get-caller-identity +``` +The output returns the ARN of the IAM user or role. For example: +```json +{ + "UserId": "XXXXXXXXXXXXXXXXXXXXX", + "Account": "XXXXXXXXXXXX", + "Arn": "arn:aws:iam::XXXXXXXXXXXX:user/testuser" +} +``` +> Note: If you receive errors when running the CLI commands, make sure that you're using the most recent version of AWS CLI. + +**2. To modify the aws-auth ConfigMap with kubectl, you must have access to the cluster.** + +Run the following kubectl command: + +```bash +$ kubectl edit configmap aws-auth -n kube-system +``` + +The console shows the current configMap. + +If you can't connect to the cluster, then try updating your kubeconfig file. Run the file with an IAM identity that has access to the cluster. The identity that created the cluster always has cluster access. + +```bash +aws eks update-kubeconfig --region region_code --name my_cluster +``` +> Note: Replace region_code with your EKS cluster AWS Region code and my_cluster with your EKS cluster name. + +The kubectl commands must connect to the EKS server endpoint. If the API server endpoint is public, then you must have internet access to connect to the endpoint. If the API server endpoint is private, then you must connect to the EKS server endpoint from within the VPC where the EKS cluster is running. + +3. To edit the aws-auth ConfigMap in the text editor as the cluster creator or admin, run the following command: +```bash +$ kubectl edit configmap aws-auth -n kube-system +``` + +**4. Add an IAM user or role:** + +```bash +mapUsers: | + - userarn: arn:aws:iam::XXXXXXXXXXXX:user/testuser + username: testuser + groups: + - system:bootstrappers + - system:nodes +``` +-or- + +Add the IAM role to mapRoles. For example: + +```bash +mapRoles: | + - rolearn: arn:aws:iam::XXXXXXXXXXXX:role/testrole + username: testrole + groups: + - system:bootstrappers + - system:nodes +``` + +### Consider the following information: + +`system:masters` allows a superuser access to perform any action on any resource. This isn't a best practice for production environments. +It’s a best practice to minimize granted permissions. Consider creating a role with access to only a specific namespace. See Using RBAC Authorization on the Kubernetes website for information. Also, see Required permissions, and review the View Kubernetes resources in a specific namespace section for an example on the Amazon EKS console’s restricted access. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Amazon\342\200\205VPC\342\200\205CNI.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Amazon\342\200\205VPC\342\200\205CNI.md" new file mode 100644 index 00000000..db651ca3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Amazon\342\200\205VPC\342\200\205CNI.md" @@ -0,0 +1,170 @@ +--- +title: 'Amazon VPC CNI' +lastUpdated: '2024-03-02' +--- + +- The AWS-provided VPC CNI is the default networking add-on that runs on Kubernetes worker nodes for EKS clusters. VPC CNI add-on is installed by default when you provision EKS clusters. + +- The VPC CNI provides configuration options for pre-allocation of ENIs and IP addresses for fast Pod startup times.\ + +### VPC CNI components + +Amazon VPC CNI has two components: + +- **CNI Binary**: which will setup Pod network to enable Pod-to-Pod communication. The CNI binary runs on a node root file system and is invoked by the kubelet when a new Pod gets added to, or an existing Pod removed from the node. +- **ipamd**: a long-running node-local IP Address Management (IPAM) daemon and is responsible for: + - managing ENIs on a node, and + - maintaining a warm-pool of available IP addresses or prefix + +When an instance is created, EC2 creates and attaches a primary ENI associated with a primary subnet. The primary subnet may be public or private. The Pods that run in hostNetwork mode use the primary IP address assigned to the node primary ENI and share the same network namespace as the host. + +**The CNI plugin manages ENI on the node.** When a node is provisioned, the CNI plugin automatically allocates a pool of slots (IPs or Prefix's) from the node's subnet to the primary ENI. This pool is known as the _warm pool_, and its size id determined by the node's instance type. + +--- + +Depending on CNI settings, a slot may be an IP address or a prefix. When a slot on an ENI has been assigned, the CNI may attach additional ENIs with warm pool of slots to the nodes. + +These additional ENIs are called **Secondary ENIs**. Each ENI can only support a certain number of slots, based oninstance type. The CNI attaches more ENIs to instances based on the number of slots needed, which usually corresponds to the number of Pods, This process continues until the node can no longer support additinal ENI. + +The CNI also preallocates "warm" ENIs and slots for faster Pord startup. Note each instance type has a maximum number of ENIs that may be attached. This one constraint on Pod densiry (number of Pods per node), in addition to compute resources. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/a98b50c6-e189-4b4c-8136-ac0a20cb58fe) + +The maximum number of network interfaces, and the maximum number of slots of slots that you can use varies by the type of EC2 instance. Since each Pod consumes an IP address on a slot, the number of Pord you can run on a particular EC2 Instance depends on how many ENIs can be attached to it and how many slots each ENI supports. + +AWS suggests setting the maximum Pods per EKS user guide to avoid exhaustion of the instance’s CPU and memory resources. Pods using `hostNetwork` are excluded from this calculation. You may consider using a script called [max-pod-calculator.sh](https://github.com/awslabs/amazon-eks-ami/blob/master/files/max-pods-calculator.sh) to calculate EKS’s recommended maximum Pods for a given instance type. + +## Secondary IP mode + +Secondary IP mode is the default mode for VPC CNI. This guide provides a generic overview of VPC CNI behavior when Secondary IP mode is enabled. The functionality of ipamd (allocation of IP addresses) may vary depending on the configuration settings for VPC CNI, such as [Prefix Mode](https://aws.github.io/aws-eks-best-practices/networking/prefix-mode/index_linux/), [Security Groups Per Pod](https://aws.github.io/aws-eks-best-practices/networking/sgpp/), and [Custom Networking.](https://aws.github.io/aws-eks-best-practices/networking/custom-networking/) + +The Amazon VPC CNI is deployed as a Kubernetes Daemonset named aws-node on worker nodes. When a worker node is provisioned, it has a default ENI, called the primary ENI, attached to it. + +The CNI allocates a warm pool of ENIs and secondary IP addresses from the subnet attached to the node's primary ENI. By default, ipamd attempts to allocate an additional ENI to the node. The IPAMD allocates additional ENI when a single Pod is scheduled and assigned a secondary IP address from the primay ENI. This "warm" ENI enables faster Pod networking. As the pool of secondary IP addresses runs out, the CNI adds another ENI to assign more. + +The number of ENIs and IP addreses in a pool are configured through environment variables called `WARM_ENI_TARGET`, `WARM_IP_TARGET`, `MINIMUM_IP_TARGET`. The `aws-node` Daemonset will periodically check that a sufficient number of ENIs are attached. A sufficient number of ENIs are attached when all pf the `WARM_ENI_TARGET`, or `WARM_IP_TARGET` and `MINIMUM_IP_TARGET` conditions are met. If there are insufficient ENIs attached, the CNI will make an API call to EC2 to attach more until `MAX_ENI` limit is reached. + +- `WARM_ENI_TARGET` - Integer, Values > 0 indicate requirement Enabled + - The number of Warm ENIs to be maintained. An ENI is "warm" when it is attached as a secondary ENI to a node, but it is not in use by any Pod. More specifically, no IP addresses of the ENI have been associated with a Pod. + + - Example: Consider an instance with 2 ENIs, each ENI supporting 5 IP addresses. `WARM_ENI_TARGET` is set to 1. If exactly 5 addresses are associated with the instance, the CNI maintains 2 ENIs attached to the instance. The first ENI is in use, and all 5 possible IP addresses of this ENI are used. + The second ENI is “warm” with all 5 IP addresses in pool. If another Pod is launched on the instance, a 6th IP address will be needed. The CNI will assign this 6th Pod an IP address from the second ENI and from 5 IPs from the pool. The second ENI is now in use, and no longer in a “warm” status. The CNI will allocate a 3rd ENI to maintain at least 1 warm ENI. + + > The warm ENIs still consume IP addresses from the CIDR of your VPC. IP addresses are “unused” or “warm” until they are associated with a workload, such as a Pod. + +- `WARM_IP_TARGET`, Integer, Values > 0 indicate requirement Enabled + - The number of Warm IP addresses to be maintaines. A warm IP is available on an actively attached ENI, but has not been assigned to a Pod. In other words, the number of Warm IPs available is the number of IPs that may be assigned to a Pod without requiring an additional ENI. + - Example: Consider an instance with 1 ENI, each ENI supporting 20 IP addresses `WARM_IP_TARGET` is set to 5. `WARM_ENI_TARGET` is set to 0. Only 1 ENI will be attached until a 16th IP address is needed. Then, the CNI will attach a second ENI, consuming 20 possible addresses from the subnet CIDR. + +- `MINIMUM_IP_TARGET`, Integer, Values > 0 indicate requirement Enabled + - The minimum number of IP addresses to be allocated at any time. This is commonly used to front-load the assignment of multiple ENIs at instance launch. + - Example: Consider a newly launched instance. It has 1 ENI and each ENI supports 10 IP addresses. `MINIMUM_IP_TARGET` is set to 100. The ENI immediately attaches 9 more ENIs for a total of 100 addresses. This happens regardless of any `WARM_IP_TARGET` or WARM_ENI_TARGET values. + +image + +When Kubelet recieves an add Pod request, the CNI binary queries ipamd for an available IP address, which ipamd then provides to the Pod. The CNI binary wires up the host and Pod network. + +Pods deployed on a node are, by default, assigned to the same security groups as the primary ENI. Alternatively, Pord ay be configured with defferent security groups. + +image + +As the pool of IP addresses is depleted, the plugin automatically attaches another elastic network interface to the instance and allocates another set of secondary IP addresses to that interface. This process continues until the node can non longer support addtional elastic network interfaces. + +image + +When a Pod is deleted, VPC CNI places the Pod's IP address in a 30-second cool down cache. The IPs in a coll down cache are not assigned to new Pords. When the colling-off period is over, VPC CNI moves Pod IP back to the warm pool. The cooling-off periond prevents Pod IP addresses from being recycled prematurely and allows kube-proxy on all cluster nodes to finish updating the iptables rules. When the number of IPs or ENIs exceeds the number of warm pool settings, the ipamd plugin return IPs and ENIs to the VPC. + +As described above in Secondary IP mode, each Pod receives one secondary private IP address form one of the ENIs attached to an instance. Since each Pod uses an IP address, the number of Pods you can run on a particular EC2 Instance depends on how many ENIs can be attached to it and how many IP addresses it supports. The VPC CNI checks the limits file to find out how many ENIs and IP addresses are allowed for each type of instance. + +You can use the following formula to determin maximum number of Pods you can deploy on a node. + +```bash +(Number of network interfaces for the instance type × (the number of IP addresses per network interface - 1)) + 2 +``` + +The +2 indicates Pods that require host networking, such as kube-proxy and VPC CNI. Amazon EKS requires kube-proxy and VPC CNI to be operating on each node, and these requirements are factored into the max-pods value. If you want to run additional host networking pods, consider updating the max-pods value. + +The +2 indicates Kubernetes Pods that use host networking, such as kube-proxy and VPC CNI. Amazon EKS requires kube-proxy and VPC CNI to be running on every node and are calculated towards max-pods. Consider updating max-pods if you plan to run more host networking Pods. You can specify `--kubelet-extra-args "—max-pods=110"` as user data in the launch template. + +As an example, on a cluster with 3 c5.large nodes (3 ENIs and max 10 IPs per ENI), when the cluster starts up and has 2 CoreDNS pods, the CNI will consume 49 IP addresses and keeps them in warm pool. The warm pool enables faster Pod launches when the application is deployed. + +- Node 1 (with CoreDNS pod): 2 ENIs, 20 IPs assigned + +- Node 2 (with CoreDNS pod): 2 ENIs, 20 IPs assigned + +- Node 3 (no Pod): 1 ENI. 10 IPs assigned. + +Keep in mind that infrastructure pods, often running as daemon sets, each contribute to the max-pod count. These can include: + +- CoreDNS +- Amazon Elastic LoadBalancer +- Operational pods for metrics-server + +We suggest that you plan your infrastructure by combining these Pod's capacities. For a list of the maximum number of Pods supported by each instance type, see [eni-max-Pods.txt](https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt) on GitHub. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/d74d2bc1-b54b-4e5c-a30c-a30c36bce9fd) + +### Use Secondary IP Mode when +Secondary IP mode is an ideal configuration option for ephemeral EKS clusters. Greenfield customers who are either new to EKS or in the process of migrating can take advantage of VPC CNI in secondary mode. + +### Avoid Secondary IP Mode when +If you are experiencing Pod density issues, we suggest enabling prefix mode. If you are facing IPv4 depletion issues, we advise migrating to IPv6 clusters. If IPv6 is not on the horizon, you may choose to use custom networking. + +## Deploy VPC CNI Managed Add-On + +When you provision a cluster, Amazon EKS installs VPC CNI automatically. Amazon EKS nevertheless supports managed add-ons that enable the cluster to interact with underlying AWS resources such as computing, storage, and networking. AWS highly recommends that you deploy clusters with managed add-ons including VPC CNI. + +Amazon EKS managed add-on offer VPC CNI installation and management for Amazon EKS clusters. Amazon EKS add-ons include the latest security patches, bug fixes, and are validated by AWS to work with Amazon EKS. The VPC CNI add-on enables you to continuously ensure the security and stability of your Amazon EKS clusters and decrease the amount of effort required to install, configure, and update add-ons. Additionally, a managed add-on can be added, updated, or deleted via the Amazon EKS API, AWS Management Console, AWS CLI, and eksctl. + +You can find the managed fields of VPC CNI using `--show-managed-fields` flag with the kubectl get command. + +``` +kubectl get daemonset aws-node --show-managed-fields -n kube-system -o yaml +``` + +Managed add-ons prevents configuration drift by automatically overwriting configurations every 15 minutes. This means that any changes to managed add-ons, made via the Kubernetes API after add-on creation, will overwrite by the automated drift-prevention process and also set to defaults during add-on update process. + +The fields managed by EKS are listed under managedFields with manager as EKS. Fields managed by EKS include service account, image, image url, liveness probe, readiness probe, labels, volumes, and volume mounts. + +> The most frequently used fields such as `WARM_ENI_TARGET`, `WARM_IP_TARGET`, and `MINIMUM_IP_TARGET` are not managed and will not be reconciled. The changes to these fields will be preserved upon updating of the add-on.
AWS suggests testing the add-on behavior in your non-production clusters for a specific configuration before updating production clusters. Additionally, follow the steps in the EKS user guide for add-on configurations. + +## Understand Security Context + +AWS strongly suggests you to understand the security contexts configured for managing VPC CNI efficiently. Amazon VPC CNI has two components CNI binary and ipamd (aws-node) Daemonset. The CNI runs as a binary on a node and has access to node root file syetem, also has privileged access as it deals with iptables at the node level. The CNI binary is invoked by the kubelet when Podfs gets added or removed. + +The aws-node Daemonset is a long-running process responsible for IP address management at the node level. The aws-node runs in `hostNetwork` mode and allows access to the loopback device, and network activity of other pods on the same node. The aws-node init-container runs in privileged mode and mounts the CRI socket allowing the Daemonset to monitor IP usage by the Pods running on the node. Amazon EKS is working to remove the privileged requirement of aws-node init container. Additionally, the aws-node needs to update NAT entries and to load the iptables modules and hence runs with `NET_ADMIN` privileges. + +Amazon EKS recommends deploying the security policies as defined by the aws-node manifest for IP management for the Pods and networking settings. + +## Monitor IP Address Inventory + +You can monitor the IP addresses inventory of subnets using [CNI Metrics Helper](https://docs.aws.amazon.com/eks/latest/userguide/cni-metrics-helper.html). + +- maximum number of ENIs the cluster can support +- number of ENIs already allocated +- number of IP addresses currently assigned to Pods +- total and maximum number of IP address available +You can also set CloudWatch alarms to get notified if a subnet is running out of IP addresses. Please visit EKS user guide for install instructions of CNI metrics helper. Make sure `DISABLE_METRICS` variable for VPC CNI is set to false. + +## Configure IP and ENI Target values in address constrained environments + +> Improving your VPC design is the recommended response to IP address exhaustion. Consider solutions like IPv6 and Secondary CIDRs. Adjusting these values to minimize the number of Warm IPs should be a temporary solution after other options are excluded. Misconfiguring these values may interfere with cluster operation. + +In the default configuration, VPC CNI keeps an entire ENI (and associated IPs) in the warm pool. This may consume a large number of IPs, especially on larger instance types. + +If your cluster subnet has a limited number of free IP addresses, scrutinize these VPC CNI configuration environment variables: + +- `WARM_IP_TARGET` +- `MINIMUM_IP_TARGET` +- `WARM_ENI_TARGET` + +Configure the value of `MINIMUM_IP_TARGET` to closely match the number of Pods you expect to run on your nodes. Doing so will ensure that as Pods get created, and the CNI can assign IP addresses from the warm pool without calling the EC2 API. + +Avoid setting the value of `WARM_IP_TARGET` too low as it will cause additional calls to the EC2 API, and that might cause throttling of the requests. For large clusters use along with `MINIMUM_IP_TARGET` to avoid throttling of the requests. + +To configure these options, download `aws-k8s-cni.yaml` and set the environment variables. At the time of writing, the latest release is located here. Check the version of the configuration value matches the installed VPC CNI version. + +--- +reference +- https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/prefix-and-ip-target.md +- https://team-xquare.notion.site/node-pod-b6ba5bf5b75145869e0797f3ee601458?pvs=4 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EBS\342\200\205CSI\342\200\205driver.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EBS\342\200\205CSI\342\200\205driver.md" new file mode 100644 index 00000000..b586c9c9 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EBS\342\200\205CSI\342\200\205driver.md" @@ -0,0 +1,50 @@ +--- +title: 'EBS CSI driver' +lastUpdated: '2024-03-02' +--- + +If you want to persist your own data to EBS in EKS environment, you can use EBS CSI driver addon provided by AWS. + +Beforehand we need to enable the IAM OIDC provider and create the IAM role for the EBS CSI driver. The easiest way to do both is to use eksctl (other ways like using plain aws cli or the AWS GUI are described in the docs). + +### 1. Enable IAM OIDC provider + +A prerequisite for the EBS CSI driver to work is to have an existing AWS Identity and Access Management (IAM) OpenID Connect (OIDC) provider for your cluster. This IAM OIDC provider can be enabled with the following command: + +```bash +eksctl utils associate-iam-oidc-provider --region=eu-central-1 --cluster=YourClusterNameHere --approve +``` + +### 2. Create Amazon EBS CSI driver IAM role + +Now having eksctl in place, create the IAM role: + +```bash +eksctl create iamserviceaccount \ + --name ebs-csi-controller-sa \ + --namespace kube-system \ + --cluster YourClusterNameHere \ + --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \ + --approve \ + --role-only \ + --role-name AmazonEKS_EBS_CSI_DriverRole +``` + +As you can see AWS maintains a managed policy for us we can simply use (AWS maintains a managed policy, available at ARN `arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy`). Only if you use encrypted EBS drives you need to additionally add configuration to the policy. + +> deploys an AWS CloudFormation stack that creates an IAM role, attaches the IAM policy to it, and annotates the existing ebs-csi-controller-sa service account with the Amazon Resource Name (ARN) of the IAM role. + +### 4. Add the Amazon EBS CSI add-on + +Now we can finally add the EBS CSI add-on. Therefore we also need the AWS Account id which we can obtain by running `aws sts get-caller-identity --query Account --output text` (see Quick way to get AWS Account number from the AWS CLI tools?). Now the eksctl create addon command looks like this: + +```bash +eksctl create addon --name aws-ebs-csi-driver --cluster YourClusterNameHere --service-account-role-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/AmazonEKS_EBS_CSI_DriverRole --force +``` + +Now your PersistentVolumeClaim should get the status Bound while a EBS volume got created for you - and the Tekton Pipeline should run again. + +--- +reference +- https://kubernetes.io/blog/2019/01/15/container-storage-interface-ga/ +- https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205ALB.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205ALB.md" new file mode 100644 index 00000000..5dd82b5a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205ALB.md" @@ -0,0 +1,116 @@ +--- +title: 'EKS ALB' +lastUpdated: '2024-03-02' +--- + +When you create a Kubernetes `ingress`, an AWS Application Load Balancer (ALB) is provisioned that load balances application traffic. ALBs can be used with Pods that are deployed to nodes or to AWS Fargate. You can deploy an ALB to public or private subnets. + +Before you can load balance application traffic to an application, you must meet the following requirements. + +#### Prerequisites + +- Have an existing cluster. If you don't have an existing cluster +- Have the AWS Load Balancer Controller deployed on your cluster. +- At least two subnets in different Availability Zones. The AWS Load Balancer Controller chooses one subnet from each Availability Zone. When multiple tagged subnets are found in an Availability Zone, the controller chooses the subnet whose subnet ID comes first lexicographically. Each subnet must have at least eight available IP addresses. + If you're using multiple security groups attached to worker node, exactly one security group must be tagged as follows. Replace my-cluster with your cluster name. + **Key** – `kubernetes.io/cluster/my-cluster` + **Value** – `shared or owned` + +- Your public and private subnets must meet the following requirements. This is unless you explicitly specify subnet IDs as an annotation on a service or ingress object. Assume that you provision load balancers by explicitly specifying subnet IDs as an annotation on a service or ingress object. +- In this situation, Kubernetes and the AWS load balancer controller use those subnets directly to create the load balancer and the following tags aren't required. + - **Private subnets** – Must be tagged in the following format. This is so that Kubernetes and the AWS load balancer controller know that the subnets can be used for internal load balancers. If you use eksctl or an Amazon EKS AWS CloudFormation template to create your VPC after March 26, 2020, the subnets are tagged appropriately when created. For more information about the Amazon EKS AWS CloudFormation VPC templates, see Creating a VPC for your Amazon EKS cluster. + - **Key** – `kubernetes.io/role/internal-elb` + - **Value** – `1` + - **Public subnets** – Must be tagged in the following format. This is so that Kubernetes knows to use only the subnets that were specified for external load balancers. This way, Kubernetes doesn't choose a public subnet in each Availability Zone (lexicographically based on their subnet ID). If you use eksctl or an Amazon EKS AWS CloudFormation template to create your VPC after March 26, 2020, the subnets are tagged appropriately when created. For more information about the Amazon EKS AWS CloudFormation VPC templates, see Creating a VPC for your Amazon EKS cluster. + - **Key** – `kubernetes.io/role/elb` + - **Value** – `1` + +If the subnet role tags aren't explicitly added, the Kubernetes service controller examines the route table of your cluster VPC subnets. This is to determine if the subnet is private or public. We recommend that you don't rely on this behavior. Rather, explicitly add the private or public role tags. The AWS Load Balancer Controller doesn't examine route tables. It also requires the private and public tags to be present for successful auto discovery. + +### Considerations + +- The AWS Load Balancer Controller creates ALBs and the necessary supporting AWS resources whenever a Kubernetes ingress resource is created on the cluster with the `kubernetes.io/ingress.class: alb` annotation. The ingress resource configures the ALB to route HTTP or HTTPS traffic to different Pods within the cluster. To ensure that your ingress objects use the AWS Load Balancer Controller, add the following annotation to your Kubernetes ingress specification. [More information](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/ingress/spec/) + +```yml +annotations: + kubernetes.io/ingress.class: alb + # If you want to use IPv6 + # alb.ingress.kubernetes.io/ip-address-type: dualstack +``` + +- The AWS Load Balancer Controller supports the following traffic modes: + - **Instance** – Registers nodes within your cluster as targets for the ALB. Traffic reaching the ALB is routed to NodePort for your service and then proxied to your Pods. This is the default traffic mode. You can also explicitly specify it with the `alb.ingress.kubernetes.io/target-type: instance` annotation. + - **IP** – Registers Pods as targets for the ALB. Traffic reaching the ALB is directly routed to Pods for your service. You must specify the `alb.ingress.kubernetes.io/target-type`: ip annotation to use this traffic mode. The IP target type is required when target Pods are running on Fargate. + +- To tag ALBs created by the controller, add the following annotation to the controller: `alb.ingress.kubernetes.io/tags`. For a list of all available annotations supported by the AWS Load Balancer Controller, see [Ingress annotations](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/guide/ingress/annotations/) on GitHub. + +- Upgrading or downgrading the ALB controller version can introduce breaking changes for features that rely on it. For more information about the breaking changes that are introduced in each release, see[ the ALB controller release notes](https://github.com/kubernetes-sigs/aws-load-balancer-controller/releases) on GitHub. + +- **To share an application load balancer across multiple service resources using IngressGroups** + To join an ingress to a group, add the following annotation to a Kubernetes ingress resource specification. + ```yml + alb.ingress.kubernetes.io/group.name: my-group + ``` + +## (Optional) Deploy a sample application + +#### Prerequisites + +- At least one public or private subnet in your cluster VPC. +- Have the AWS Load Balancer Controller deployed on your cluster. For more information, see Installing the AWS Load Balancer Controller add-on. We recommend version 2.4.7 or later. + +#### To deploy a sample application + +You can run the sample application on a cluster that has Amazon EC2 nodes, Fargate Pods, or both. + +1. If you're not deploying to Fargate, skip this step. If you're deploying to Fargate, create a Fargate profile. You can create the profile by running the following command or in the AWS Management Console using the same values for name and namespace that are in the command. Replace the `example values` with your own. + +```bash +eksctl create fargateprofile \ + --cluster my-cluster \ + --region region-code \ + --name alb-sample-app \ + --namespace game-2048 +``` + +2. Deploy the game [2048](https://play2048.co/) as a sample application to verify that the AWS Load Balancer Controller crates an AWS ALB as a result of the ingress object. Complete the steps for the type of subnet you're deploying to. + + - **Public** + ```bash + kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/examples/2048/2048_full.yaml + ``` + - **Private** + 1. Download the manifest + ```bash + curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/examples/2048/2048_full.yaml + ``` + 2. Edit the file and find the line that says `alb.ingress.kubernetes.io/scheme: internet-facing.` + 3. Change `internet-facing` to `internal` and save the file + 4. Apply the manifest to your cluster. + ```bash + kubectl apply -f 2048_full.yaml + ``` + +3. After a few minutes, verify that the ingress resource was created with the following command. + + ```bash + $ kubectl get ingress/ingress-2048 -n game-2048 + NAME CLASS HOSTS ADDRESS PORTS AGE + ingress-2048 * k8s-game2048-ingress2-xxxxxxxxxx-yyyyyyyyyy.region-code.elb.amazonaws.com 80 2m32s + ``` + +4. If you deployed to a public subnet, open a browser and navigate to the ADDRESS URL from the previous command output to see the sample application. If you don't see anything, refresh your browser and try again. If you deployed to a private subnet, then you'll need to view the page from a device within your VPC, such as a bastion host. + +5. When you finish experimenting with your sample application, delete it by running one of the the following commands. + +- If you applied the manifest, rather than applying a copy that you downloaded, use the following command. + + ```bash + kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/examples/2048/2048_full.yaml + ``` + +- If you downloaded and edited the manifest, use the following command. + + ```bash + kubectl delete -f 2048_full.yaml + ``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Control\342\200\205Plane.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Control\342\200\205Plane.md" new file mode 100644 index 00000000..92e428d1 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Control\342\200\205Plane.md" @@ -0,0 +1,49 @@ +--- +title: 'EKS Control Plane' +lastUpdated: '2024-03-02' +--- + +EKS is a managed Kubernetes service that mekes it easy for you to run Kubernetes on AWS without needing to install, operate, and main your own Kubernetes control plane or worker nodes. It runs ipstream Kubernetes and is certified Kubernetes conformant. + +EKS automatically manages the availability and scalability of the Kubernetes control plane nodes, and it automatically replaces unhealthy control plane nodes. + +## EKS Architecture + +EKS architecture is designed to eliminate any single points of failure that may compromise the availabilty and durability of the Kubernetes control plane. + +The Kubernetes control plane managed by EKS **runs inside an EKS managed VPC**. The EKS control plane conprises the Kubernetes API server nodes, etcd cluster. Kubernetes API server nodes that run components like the API server, scheduler, ans `kube-controller-manager` run in an auto-scailing group. EKS runs a minimum of two API server nodes in distinct Availability Zones (AZs) within in AWS region. Likewise, for durability, the etcd server nodes also run in an auto-scailing group that spans three AZs. + +EKS runs a NAT Gateway in each AZ, and API servers and etcd servers run in a private subnet. This architecture ensures that an event in a single AZ doesn't affect the EKS cluster's availability. + +When you create a new cluster, Amazon EKS creates a highly-avaiilable endpoint for the managed Kubernetes API server that you use to communicate with your cluster (using tools like `kubectl`). The managed endpoint uses NLB to load balance Kubernetes API servers. EKS also provisions two [ENI](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html)s in different AZs to facilitate communication to your worker nodes. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/2fd7669f-69a6-49bb-88d5-a2fa9eabd544) + +You can configure whether your Kubernetes cluster's API server is reachable from the publiv internet (using public endpoint) or through your VPC (using the EKS-managed ENIs) or both. + +Whether users and worker nodes connect to the API server using the public endpoint or the EKS-managed ENI, there are redundant paths for connection. + +## EKS Control Plane Communication + +EKS has two ways to control access to the cluster endpoint. Endpoint access control lets you choose whether the endpoint can be reached from the public internet or only through your VPC. You can turn on the public endpoint (which is the default), the private endpoint, or both at once. + +The configuration of the cluster API endpoint determines the path that nodes take to communicate to the control plane. Note that these endpoint settings can be changed at any time through the EKS console or API. + +### Public Endpoint + +This is the default behavior for new Amazon EKS clusters. When only the public endpoint for the cluster is enabled, Kubernetes API requests that originate from within your cluster's VPC (such as worker node to control plane communication) leave the VPC, but not Amazon's network. In order for nodes to connect to the control plan, they must have a public IP address and a route to an internet gateway or a route to a NAT gateway or a route to a NAT gateway where they can use the public IP address of the NAI gateway. + +### Public and private Endpoint + +When both the public and private endpoints are enabled, Kubernetes API requests from within the VPC communicate to the control plane via the X-ENIs with in your VPC. Your cluster API server is accessible from the internet. + +### Private Endpoint + +There is no public access to your API server from the internet when only private endpoint is enabled. All traffic to your cluster API server must come from within your cluster's VPC or a connected network. The nodes communicate to API server via X-ENIs within your VPC. Note that cluster management tools must have access to the private endpoint. Learn more about [how to connect to a private Amazon EKS cluster endpoint from outside the Amazon VPC.](https://aws.amazon.com/premiumsupport/knowledge-center/eks-private-cluster-endpoint-vpc/) + +Note that the **cluster's API server endpoint is resolved by public DNS servers to a private IP address from the VPC**. In the past, the endpoint could only be resolved from within the VPC. + +--- +reference +- https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html +- https://aws.github.io/aws-eks-best-practices/reliability/docs/controlplane/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Network\342\200\205BestPractice.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Network\342\200\205BestPractice.md" new file mode 100644 index 00000000..b5599949 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Network\342\200\205BestPractice.md" @@ -0,0 +1,83 @@ +--- +title: 'EKS Network BestPractice' +lastUpdated: '2023-07-24' +--- +It is critical to understand Kubernetes networking to operate you cluster and applications efficiently. Pod networkin, also called the cluster networking, is the center of Kubernetes networkin. Kubernetes supports [Container Network Interface](https://github.com/containernetworking/cni)(CNI) plugins for cluster networking. + +Amazon EKS officially supports VPC CNI plugin to implement Kubernetes Pod networking. The VPC CNI provides native integration with AWS VPC and works in underlay mode. In underlay mode, Pods and hosts are located at the same network layer and share the network namespace. The IP address of the Pod is consistent from the cluster and VPC perspective. + +Let's more know about Amazon VPC CNI in the context of Kubernetes cluster networking. The VPC CNI is the default networking plugin supported by EKS, so it is important to fully understand that. Check the[ EKS Alternate CNI](https://docs.aws.amazon.com/eks/latest/userguide/alternate-cni-plugins.html) documentation for a list of partners and resources for managing alternate CNIs effectively. + +### [Kubernetes Networking Model](../Kubernetes Networking Model.md) + +Kubernetes sets the following requirements on cluster networking: +- Pords scheduled on the same node must be able to communicate with other Pods without using NAT. +- All system daemons (background processes, for example, [kublet](https://kubernetes.io/docs/concepts/overview/components/)) running on a particular node can communicate with the Pods runnign on the same node. +- Pods that use the [host network](https://docs.docker.com/network/host/) must be able to contact all other pods on all othe nodes without using NAT. + +See the [Kubernetes network model](https://kubernetes.io/docs/concepts/services-networking/#the-kubernetes-network-model) for details on what k9s expect from compatible networking implementations. The following figure illustrates the relationship between Pod network namespaces and the host network namespace. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/e83e30d5-c00c-4c90-88a6-873244274a70) + +### Container Networking Interface (CNI) + +Kubernetes supports CNI specifications and plugins to implement Kubernetes network model. A CNI consists of a specification and libraries for writing plugins to configure network interfaces in containers, along with a number of supported plugins. CNI concerns itself only with network connectivity of containers and removing allocated resources when the container is deleted. + +- The CNI plugin is enabled by passing kubelet the `--network-plugin=cni` command-line option. +- Kubelet reads a file from `--cni-conf-dir` (default /etc/cni/net.d) and uses the CNI configuration from that file to set up each Pod’s network. +- The CNI configuration file must match the CNI specification (minimum v0.4.0) and any required CNI plugins referenced by the configuration must be present in the `--cni-bin-dir` directory (default /opt/cni/bin). + +If there are multiple CNI configuration files in the directory, the kubelet uses the configuration file that comes first by name in lexicographic order. + +## Amazon Virtual Private Cloud(VPC) CNI + +The AWS-provided VPC CNI is the default networking add-on for EKS clusters. VPC CNI add-on is installed by default when you provision EKS clusters. + +VPC CNI runs on Kubernetes worker nodes. + +The VPC CNI provides configuration options for pre-allocation of ENIs and IP addresses for fast Pod startup times. Refer to [Amazon VPC CNI](Amazon VPC CNI.md) for recommended plugin management best practices. + + +### secondary IP mode + +Amazon VPC CNI alloacates **a warm pool of ENIs and secondary IP addresses from the subnet attached to the node's primary ENI**. This mode of VPC CNI is called the ["Secondary IP mode"](https://aws.github.io/aws-eks-best-practices/networking/vpc-cni/). The number of IP addresses and hence the number of Pods (Pod density) is defined by the number of ENIs and the IP address per ENI (limits) as defined by the instance type. The secondary mode is the default and workd well for small clusters with smaller instance types. Please consider using [prefix mode](https://aws.github.io/aws-eks-best-practices/networking/prefix-mode/index_linux/) if you are experiencing pod density challenges. You can also increase the available IP addresses on node for Pods by assigning prefixes to ENIs. + +### security groups for Pods + +Amazon VPC CNI natively integrates with AWS VPC and allows usets to apply existing AWS VPC networking and security best practices for building Kubernetes clusters. This includes the ability to use VPC flow logs, VPC routing policis, and security groups for network traffic isolation. By default the Amazon VPC CNI applies security group associated with the primary ENI on the node to the Pods. Consider enabling security groups for Pods when you would like to assign different network rules for Pod. + +### custom networking + +By default, VPC CNI assigns IP addresses to Pods from the subnet assigned to the primary ENI of a node. It is common **to experience a shortage of IPv4 addresses when running large clusters** with thousands of workloads. AWS VPC allows you to extend available IPs by [assigning a secondary CIDRs](https://docs.aws.amazon.com/vpc/latest/userguide/configure-your-vpc.html#add-cidr-block-restrictions) to work around exhaustion of IPv4 CIDR blocks. AWS VPC CIN is called [custom networking](https://aws.github.io/aws-eks-best-practices/networking/custom-networking/). You might consider using custom networking to use `100.64.0.0/10` and `198.19.0.0/16` CIDRs (CG-NAT) with EKS. This effectively allows you to create an environment where Pods no longer consume any RFC1918 UP addresses from you VPC. + +Custom networking is one option to address the IPv4 address achaustion problem, but it requires operational overhead. AWS recommend IPv6 clusters over custom networking to resolve this problem. Specifically, aws recommend migrating to [IPv6 clusters](https://aws.github.io/aws-eks-best-practices/networking/ipv6/) if you have completely exhausted all available IPv4 address space for your VPC. Evaluate you organization's plans to support IPv5, and consider if investing in IPv6 may have more long-term value. + +## VPC and Subnet + +Amazon EKS recommends you specify subnets in at least two availability zones when you craete a cluster. Amazon VPC CNI allocates IP addresses to Pods from the node subnets. We strongly recommend checking the subnets for available IP addresses. + +### EKS Cluster Architecture + +An EKS cluster consists of two VPCs: + +- An AWS-managed VPC that hosts the Kubernetes control plane. This VPC does not appear in the customer account. +- A customer-managed VPC that hosts the Kubernetes nodes. This is where containers run, as well as other customer-managed AWS infrastructure such as load balancers used by the cluster. This VPC appears in the customer account. You need to create customer-managed VPC prior creating a cluster. The eksctl creates a VPC if you do not privide one. + +The nodes in the customer VPC need the ability to connect to the managed API server endpoint in the AWS VPC. This allows the nodes to register with the Kubernetes control plane and recieve requests to run application Pods. + +The nodes connect to the EKS control plane through (a) an EKS public endpoint or (b) a Cross-Account elastic network interfaces (X-ENI) managed by EKS. When a cluster is created, you need to specify at least two VPC subnets. EKS places a X-ENI in each subnet specified during cluster create (also called cluster subnets). The Kubernetes API server uses these Cross-Account ENIs to cmmunicate with nodes deployed on the customer-manages cluster VPC subnets. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/e85fe0ee-03c5-4223-bea8-e07115883a03) + +### VPC configurations + +**Amazon VPC supports IPv4 and IPv6 addressing.** Amazon EKS supports IPv4 by default. A VPC must have an IPv4 CIDR block associated with it. You can optionally associate multiple IPv4 CIDR blocks and multiple IPv6 CIDR blocks to your VPC. When you create a VPC, you must specify an IPv4 CIDR block for the VPC from the private IPv4 address ranges as specified in RFC 1918. The allowed block size is between a `/16` prefix (65,536 IP addresses) and `/28` prefix (16 IP addresses). + +Amazon EKS **recommends you use at least two subnets that are in different Availability Zones during cluster creation.** The subnets you pass in during cluster creation are known as cluster subnets. When you create a cluster, Amazon EKS creates up to 4 cross account (x-account or x-ENIs) ENIs in the subnets that you specify. The x-ENIs are always deployed and are used for cluster administration traffic such as log delivery, exec, and proxy. + +Kubernetes worker nodes can run in the sluster subnets, but it is not recommended. You can create new subnets dedicated to run nodes and any Kubernetes resources. Nodes can run in either a public or a private subnet. Whether a subnet is public or private refers to whether traffic within the subnet is routed through an igw. + +--- + +reference +- https://github.com/aws/aws-eks-best-practices \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Spot\342\200\205Instances\342\200\205with\342\200\205Karpenter.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Spot\342\200\205Instances\342\200\205with\342\200\205Karpenter.md" new file mode 100644 index 00000000..46ebd215 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205Spot\342\200\205Instances\342\200\205with\342\200\205Karpenter.md" @@ -0,0 +1,187 @@ +--- +title: 'EKS Spot Instances with Karpenter' +lastUpdated: '2024-03-02' +--- + +Karpenter is a **dynamic, high performance cluster auto scaling solution** for the Kubernetes platform. Customers choose an auto scaling solution for a number of reasons, including improving the high availability and reliability of their workloads at the same reduced costs. With the introduction of [Amazon EC2 Spot Instances](https://aws.amazon.com/ec2/spot/), customers can reduce costs up to 90% compared to On-Demand prices. Combining a high performing cluster auto scaler like Karpenter with EC2 Spot Instances, EKS clusters can acquire compute capacity within minutes while keeping costs low. + +## Getting started + +To get started with Karpenter in AWS, you need a Kubernetes cluster. We will be using an EKS cluster throughout this blog post. To provision an Amazon Elastic Kubernetes Service (Amazon EKS) cluster and install Karpenter, please follow the getting started docs from the Karpenter [documentation](https://karpenter.sh/docs/getting-started/). + +Karpenter’s single responsibility is to **provision compute for your Kubernetes clusters**, which is configured by a custom resource called Provisioner. + +Currently, when a pod is newly created, kube-scheduler is responsible for finding the best feasible node so that kubelet can run it. If none of the scheduling criteria are met, the pod stays in a pending state and remains unscheduled. Karpenter relies on the kube-scheduler and waits for unscheduled events and then provisions new node(s) to accommodate the pod(s). The following code snippet shows an example of Spot Provisioner configuration specifying instance types, Availability Zones, and capacity type. + +```yaml +apiVersion: karpenter.sh/v1alpha5 +kind: Provisioner +metadata: + name: spot-fleet +spec: + requirements: + - key: "node.kubernetes.io/instance-type" + operator: In + values: ["m5.large", "m5.2xlarge"] + - key: "topology.kubernetes.io/zone" + operator: In + values: ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"] + - key: "karpenter.sh/capacity-type" # Defaults to on-demand + operator: In + values: ["spot"] # ["spot", "on-demand"] + provider: + subnetSelector: + karpenter.sh/discovery: ${CLUSTER_NAME} + ttlSecondsUntilExpired: 2592000 +``` + +The constraints `ttlSecondsUntilExpired` defines the node expiry so a newer node will be provisioned, and `ttlSecondsAfterEmpty` defines when to delete a node since the last workload stops running. (Note: DaemonSets are not taken into account.) + +## Node selection + +Karpenter default settings should satisfy for most types of workloads when acquiring capacity. It uses an approximation between lowest-price and capacity-optimized allocation strategy when selecting a node for provisioning. In most cases, we don’t have to specify the instance type in the Provisioner unless you have specific constraints for your workloads. + +When you choose an instance type, it does not have to be similarly sized, as Karpenter automatically selects the best instance type for unscheduled pod(s). There are a couple of ways to provide Karpenter a better chance to acquire capacity when you specify your own instance types. + +One handy tool to get a list of instance types based on vCPU and memory is [amazon-ec2 instance-selector](https://github.com/aws/amazon-ec2-instance-selector) CLI. It takes input parameters such as memory, vCPU, architecture, and Region to provide the list of EC2 instances that satisfy the constraints. + +```bash +$ ec2-instance-selector --memory 4 --vcpus 2 --cpu-architecture x86_64 -r ap-southeast-1 +c5.large +c5a.large +c5ad.large +c5d.large +c6i.large +t2.medium +t3.medium +t3a.medium +``` + +Secondly, provision the instances across multiple Availability Zones to further increase the number of Spot Instance pools. + +It is recommended to use a wide range of instance types to acquire the compute capacity for your cluster and, as a general rule, to not mix burstable and non-burstable instances in the same Provisioner. For example, t3 instances do not support pod security groups, so mixing t3 and m5 instance types will cause unpredictable issues when pod security group is enabled. + +```yaml +spec: + requirements: + - key: node.kubernetes.io/instance-type + operator: In + values: ["c5.large","c5a.large", "c5ad.large", "c5d.large", "c6i.large", "t2.medium", "t3.medium", "t3a.medium"] +``` + +## Capacity type + +When creating a provisioner, we can use either Spot, On-Demand Instances, or both. When you specify both, and if the pod does not explicitly specify whether it needs to use Spot or On-Demand, then Karpenter opts to use Spot when provisioning a node. + +If the Spot capacity is not available, then Karpenter falls back to On-Demand Instances to schedule the pods. However, if a Spot quota limit has been reached at the account level, you might get a `MaxSpotInstanceCountExceeded` exception. In this case, Karpenter won’t perform a fallback. The users have to implement adequate monitoring for quotas and exceptions to create necessary alerts and reach AWS support for the necessary quota increase. + +```yaml + - key: "karpenter.sh/capacity-type" + operator: In + values: ["spot", "on-demand"] +``` + +## Resiliency + +Karpenter does not handle Spot Instance interruption natively, although this feature is in the roadmap. Therefore, a separate solution needs to be implemented. **AWS Node Termination Handler (NTH)** is a dedicated project that helps to make sure the **NTH control plane** acts appropriately during EC2 Spot Instance interruptions, EC2 scheduled maintenance windows, or scaling events. + +Node Termination Handler operates in two modes, using Instance Metadata Services (IMDS) or using a Queue Processor. + +1. **The IMDS service** runs a pod on each node to **monitor the events and act accordingly**. +2. Whereas the **queue processor** uses Amazon Simple Queue Service (Amazon SQS) to receive Auto Scaling Group (ASG) lifecycle events, EC2 status change events, Spot interruption termination notice events, and Spot rebalance recommendation events. These events can be configured to be published to Amazon EventBridge. + +In Karpenter’s case, Auto Scaling Group lifecycle events should not be considered because the instances provisioned using Karpenter are not part of an ASG. Running NTH using queue processor mode is more recommended because it provides more functionality. + +Please refer to the table below. + +|Feature|IMDS Processor Queue|Processor| +|-|-|-| +|Spot Instance Termination Notifications (ITN)|O|O| +|Scheduled Events|O|O| +|Instance Rebalance Recommendation|O|O| +|AZ Rebalance Recommendation|X|O| +|ASG Termination Lifecycle Hooks|X|O| +|Instance State Change Events|X|O| + +One important event that is **worth mentioning when using Spot Instances is the rebalance recommendations signal**. It either arrives sooner or along with the Spot termination notice. When rebalance reconnendations signals arrive ahead of a Spot Instance termination notice, it doesn't mean Spot is interrepting the node. + +It's just a recommendation to give an opportunity to **proactively manage the capacity needs**. This can occur if you are overly constraining the instance pools configured, which is why it is recommended to use the Karpenter as default for all instance types (with some exceptions). + +As mentioned before, instances provisioned by Karpenter **do not belong to any auto scaling group**. Therefore, in the installation instruction, skip steps for Set up a Termination Lifecycle Hook on an Auto Scaling group and Tag the Auto Scaling groups. In the step Create Amazon Eventbridge Rules, skip the step to create Auto Scaling event rules. When deploying the Helm chart for NTH Queue Processor, refer to the following values: + +```yaml +## Queue processor values.yaml + +enableSqsTerminationDraining: true +queueURL: "" +awsRegion: "" +serviceAccount: + create: false + name: nth # <-- adjust to your service account +checkASGTagBeforeDraining: false # <-- set to false as instances do not belong to any ASG +enableSpotInterruptionDraining: true +``` + +Depending on your choice of mode to implement Node Termination Handler, use the appropriate values.yaml file and install NTH using helm. + +```yaml +helm upgrade --install aws-node-termination-handler \ + --namespace kube-system \ + -f values.yaml \ + eks/aws-node-termination-handler +``` + +## Monitoring + +Spot interruptions can occur at any time. Monitoring Kubernetes cluster metrics and logs can help to create notifications when Karpenter fails to acquire capacity. We have to set up adequate monitoring at the Kubernetes cluster level for all the Kubernetes objects and monitor the Karpenter provisioner. We will use Prometheus and Grafana to collect the metrics for the Kubernetes cluster and Karpenter. CloudWatch Logs will be used to collect the logs. + +To get started with Prometheus and Grafana on Amazon EKS, please follow the Prometheus and Grafana installation instruction from the [Karpenter getting started guide](https://karpenter.sh/v0.6.0/getting-started/#deploy-a-temporary-prometheus-and-grafana-stack-optional). The Grafana dashboards are preinstalled with dashboards containing controller metrics, node metrics, and pod metrics. + +Using the panel Pod Distribution by Phase included in the prebuilt Grafana dashboard named General / Pod Statistic, you can check for pods that have Pending status for more than a predefined period (for example, three minutes). This will help us to understand if there are any workloads that can’t be scheduled. + +image + +Karpenter controller logs can be sent to CloudWatch Logs using either Fluent Bit or FluentD. (Here’s information on [how to get started with CloudWatch Logs for Amazon EKS](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-EKS-logs.html).) To view the Karpenter controller logs, go to the log group `/aws/containerinsights/cluster-name/application` and search for Karpenter. + +In the log stream, search for Provisioning failed log messages in the Karpenter controller logs for any provisioning failures. The example below shows provisioning failure due to reaching the account limit for Spot Instances. + +```log +2021-12-03T23:45:29.257Z ERROR controller.provisioning Provisioning failed, launching capacity, launching instances, with fleet error(s), UnfulfillableCapacity: Unable to fulfill capacity due to your request configuration. Please adjust your request and try again.; MaxSpotInstanceCountExceeded: Max spot instance count exceeded; launching instances, with fleet error(s), MaxSpotInstanceCountExceeded: Max spot instance count exceeded {"commit": "6984094", "provisioner": "default"} +``` + +### Clean up + +To avoid incurring any additional charges, clean up the resources depending on the Getting Started guide that you used or how you have provisioned Karpenter. + +1. Uninstall Karpenter controller (depending on how you installed Karpenter; the following example shows using Helm). + ```bash + helm uninstall karpenter --namespace karpenter + ``` +2. Delete the service account; the following command assumes that you have used eksctl. + ```bash + eksctl delete iamserviceaccount + --cluster ${CLUSTER_NAME} + --name karpenter + --namespace karpenter + ``` + +3. Delete the stack using + ```bash + aws cloudformation delete-stack --stack-name Karpenter-${CLUSTER_NAME} or + terraform destroy -var cluster_name=$CLUSTER_NAME + ``` + +4. Delete the cluster using + ```bash + eksctl delete cluster --name ${CLUSTER_NAME} + ``` + +## Conclusion + +In this post, we did a quick overview of Karpenter and how we can use EC2 Spot Instances with Karpenter to scale the compute needs in an Amazon EKS cluster. We encourage you to check out the Further Reading section below to discover more about Karpenter. + +--- +reference +- https://aws.amazon.com/ko/blogs/containers/using-amazon-ec2-spot-instances-with-karpenter/ +- https://karpenter.sh/v0.6.1/faq/#what-if-there-is-no-spot-capacity-will-karpenter-fallback-to-on-demand +- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-best-practices.html#be-instance-type-flexible \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205kubecofig.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205kubecofig.md" new file mode 100644 index 00000000..025fd1d2 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205kubecofig.md" @@ -0,0 +1,160 @@ +--- +title: 'EKS kubecofig' +lastUpdated: '2024-03-02' +--- + +EKS 기본 환경 Setting 방법을 알아보자. + +쿠버네티스는 kubectl에 대한 인증정보를 kubeconfig라는 yaml파일에 저장한다. EKS 클러스터는 어떻게 접근을 하는지, 어떻게 인증을 받아오는지 알아보자. + +### Kubeconfig + +EKS 클러스터에 접근 가능한 kubeconfig파일을 생성하는 것은 간단하다. aws command 한 줄이면 인증 정보를 kubeconfig 파일에 저장할 수 있다. + +```bash +aws eks --region update-kubeconfig --name +``` + +kubeconfig 파일 내용을 살펴보자. + +```yml +apiVersion: v1 +clusters: +- cluster: + server: + certificate-authority-data: + name: kubernetes +contexts: +- context: + cluster: kubernetes + user: aws + # namesapce: "" + name: aws +current-context: aws +kind: Config +preferences: {} +users: +- name: aws + user: + exec: + apiVersion: client.authentication.k8s.io/v1alpha1 + command: aws + args: + - "eks" + - "get-token" + - "--cluster-name" + - "" + # - "--role-arn" + # - "" + # env: + # - name: AWS_PROFILE + # value: "" + +``` + +kubeconfig 파일은 크게 `clusters` / `contexts` / `users`로 구분할 수 있다. clusters는 클러스터의 인증 정보, users는 사용자 eks로는 IAM에 대한 인증 정보를 토큰 값으로 저장하는 방식이다. 그리고 context는 clusters와 users의 조합하는 구간으로 namespace 또한 지정 가능하다. + +잘 생성되었는지 테스트 해보자. + +```bash +$ kubectl get svc + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +svc/kubernetes ClusterIP 10.100.0.1 443 +``` + +### 수동으로 kubeconfig 파일 생성 + +kubeconfig 파일을 수동으로 생성하려면 우선 몇 개의 환경변수를 설정해준다. (`******` 부분의 값을 알맞게 변경) + +```bash +export region_code=****** +export cluster_name=****** +export account_id=****** +``` + +클러스터의 엔드포인트를 검색하고 값을 변수에 저장한다. + +```bash +export cluster_endpoint=$(aws eks describe-cluster \ + --region $region_code \ + --name $cluster_name \ + --query "cluster.endpoint" \ + --output text) +``` + +클러스터와 통신하는 데 필요한 Base64 인코딩 인증서 데이터를 검색하고 값을 변수에 저장한다. + +```bash +certificate_data=$(aws eks describe-cluster \ + --region $region_code \ + --name $cluster_name \ + --query "cluster.certificateAuthority.data" \ + --output text) +``` + +기본 `~/.kube` 디렉터리가 아직 없으면 생성해줘야한다. + +```bash +mkdir -p ~/.kube +``` + +아래 쉘 스크립트에 값을 적어주고 실행한다. + +```bash +#!/bin/bash +read -r -d '' KUBECONFIG < ~/.kube/config +``` + +KUBECONFIG 환경 변수에 파일 경로를 추가한다. (kubectl은 클러스터 구성을 찾아야 하는 위치를 알 수 있도록 함) + +```bash +export KUBECONFIG=$KUBECONFIG:~/.kube/config +``` + +잘 생성되었는지 테스트 해보자. + +```bash +$ kubectl get svc + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +svc/kubernetes ClusterIP 10.100.0.1 443 +``` + +--- +참고 +- https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/create-kubeconfig.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205\354\235\270\354\246\235\352\263\274\354\240\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205\354\235\270\354\246\235\352\263\274\354\240\225.md" new file mode 100644 index 00000000..3774db23 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/EKS\342\200\205\354\235\270\354\246\235\352\263\274\354\240\225.md" @@ -0,0 +1,86 @@ +--- +title: 'EKS 인증과정' +lastUpdated: '2024-03-02' +--- + + + +kubectl에서 EKS Cluster의 K8s API Server에 접근하거나, Worker Node에서 동작하는 kubelet에서 EKS Cluster의 K8s API Server 접근시에는 **AWS IAM Authenticator**가 이용된다. + +image +image + +왼쪽은 kubelet의 kubeconfig이고, 오른쪽은 kubectl의 kubeconfig이다. 두 kubeconfig 모두 user 부분을 확인해보면 `aws eks get-token` 명령어를 수행하는 것을 확인할 수 있다. `aws eks get-token` 명령어는 해당 명령어를 수행하는 Identity가 누구인지 알려주는 AWS STS의 GetCallerIdentity API의 Presigned URL을 생성하고, 생성한 URL을 Encoding하여 Token을 생성한다. 여기서 Identity는 AWS IAM의 User/Role을 의미한다. + +Presigned URL은 의미 그대로 미리 할당된 URL을 의미한다. AWS STS의 GetCallerIdentity API를 호출하기 위해서는 AccessKey/SecretAccessKey와 같은 Secret이 필요하지만, Presigned URL을 이용하여 GetCallerIdentity API를 호출하면 Secret없이 호출이 가능하다. Token을 통해서 전달되는 GetCallerIdentity API의 Presigned URL은 AWS IAM Authenticator에게 전달되어 `aws eks get-token` 명령어를 수행한 Identity이 누구인지 파악하는데 이용된다. + +```bash +# "aws eks get-token" 명령어 출력 결과 예시 +$ aws eks get-token --cluster-name test-eks-cluster +{ + "kind": "ExecCredential", + "apiVersion": "client.authentication.k8s.io/v1alpha1", + "spec": {}, + "status": { + "expirationTimestamp": "2022-04-26T17:46:42Z", + "token": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFSNVFPRVpQVTRRWFg1SDRGJTJGMjAyMjA0MjYlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjIwNDI2VDE3MzI0MlomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPTIxOGQ4MDQ5NTBlZGMxMWRlZmQ0OWMwYTFkNWZkYWNjMzI0Y2M4MzBmZDZmMDZkNTlhN2Q5NzUwMGZhM2U3Mzg" + } +} + +$ base64url decode aHR0cHM6Ly9zdHMuYXAtbm9ydGhlYXN0LTIuYW1hem9uYXdzLmNvbS8_QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNSZYLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFSNVFPRVpQVTRRWFg1SDRGJTJGMjAyMjA0MjYlMkZhcC1ub3J0aGVhc3QtMiUyRnN0cyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjIwNDI2VDE3MzI0MlomWC1BbXotRXhwaXJlcz02MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QlM0J4LWs4cy1hd3MtaWQmWC1BbXotU2lnbmF0dXJlPTIxOGQ4MDQ5NTBlZGMxMWRlZmQ0OWMwYTFkNWZkYWNjMzI0Y2M4MzBmZDZmMDZkNTlhN2Q5NzUwMGZhM2U3Mzg +https://sts.ap-northeast-2.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAR5QOEZPU4QXX5H4F%2F20220426%2Fap-northeast-2%2Fsts%2Faws4_request&X-Amz-Date=20220426T173242Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=218d804950edc11defd49c0a1d5fdacc324cc830fd6f06d59a7d97500fa3e738 +``` + +token의 "k8s-aws-v1" 뒷부분의 문자열을 base64url로 decoding을 수행하면 GetCallerIdentity API의 Presigned URL을 확인할 수 있다. + +GetCallerIdentity API의 Presigned URL에 `x-k8s-aws-id: Cluster 이름` Header와 함께 Get 요청을 수행하면 "aws eks get-token" 명령어를 수행한 Identity이 누구인지 알 수 있다. GetCallerIdentity API의 Presigned URL을 대상으로 Get 요청을 수행하면 "test" User가 "aws eks get-token" 명령어를 수행했다는 사실을 알 수 있다. + +AWS IAM Authenticator는 EKS Cluster의 K8s API Server에 인증 Webhook Server로 등록되어 있다. 따라서 kubelet/kubectl이 "aws eks get-token" 명령어를 통해서 생성한 Token은 AWS IAM Authenticator에게 전달된다. + +```bash +$ curl -H "x-k8s-aws-id: test-eks-cluster" "https://sts.ap-northeast-2.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAR5QOEZPU4QXX5H4F%2F20220426%2Fap-northeast-2%2Fsts%2Faws4_request&X-Amz-Date=20220426T173242Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Signature=218d804950edc11defd49c0a1d5fdacc324cc830fd6f06d59a7d97500fa3e738" + + + arn:aws:iam::142021912854:user/test + DCDAJXZHJQB4JQK2FDWQ + 142021912854 + + + 9bdb9ca4-65c5-4659-8ca0-0e0625d14c5d + + +``` + +## "aws-auth" ConfigMap + +AWS IAM Authenticator는 "aws eks get-token" 명령어를 수행한 Identity를 파악한 다음, 파악한 Identity가 EKS Cluster의 어떤 User/Group과 Mapping 되는지 확인한다. 이후 AWS IAM Authenticator는 Mapping 되는 EKS Cluster의 User/Group을 EKS Cluster의 K8s API Server에게 전달한다. + +"aws eks get-token" 명령어을 수행한 Identity와 EKS Cluster의 User/Group과의 Mapping 정보는 kube-system Namespace에 존재하는 aws-auth ConfigMap에 저장되어 있다. mapUser 항목은 "aws eks get-token" 명령어을 수행한 AWS IAM User와 EKS Cluster의 User/Group을 Mapping을 하는데 이용되며, mapRoles 항목은 "aws eks get-token" 명령어를 수행한 AWS IAM Role과 EKS Cluster의 User/Group을 Mapping 하는데 이용한다. + +에서 test AWS IAM User는 EKS Cluster의 admin User 또는 system:master Group에 Mapping되는걸 확인할 수 있다. EKS Cluster에서 Node Group 생성시 각 Node Group에서 이용하는 AWS IAM Role이 생성되는데, Node Group의 AWS IAM Role도 [파일 3]의 mapRoles 항목에서 확인할 수 있다. + +```yaml +apiVersion: v1 +data: + mapRoles: | + - groups: + - system:bootstrappers + - system:nodes + rolearn: arn:aws:iam::132099418825:role/eksctl-test-eks-cluster-nodegrou-NodeInstanceRole-1CR0AFVMLFHSE + username: system:node: + - groups: + - system:bootstrappers + - system:nodes + rolearn: arn:aws:iam::132099418825:role/eksctl-test-eks-cluster-nodegrou-NodeInstanceRole-1FLORRGQWIWD8 + username: system:node: + mapUsers: | + - userarn: arn:aws:iam::142627221238:user/test + username: admin + groups: + - system:masters +kind: ConfigMap +metadata: + name: aws-auth + namespace: kube-system +... +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/IP\342\200\205addresse\342\200\205prefix.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/IP\342\200\205addresse\342\200\205prefix.md" new file mode 100644 index 00000000..3369895f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/IP\342\200\205addresse\342\200\205prefix.md" @@ -0,0 +1,71 @@ +--- +title: 'IP addresse prefix' +lastUpdated: '2024-03-02' +--- + +Each Amazon EC2 instance supports a maximum number of elastic network interfaces and a maximum number of IP addresses that can be assigned to each network interface. Each node requires one IP address for each network interface. All other available IP addresses can be assigned to Pods. Each Pod requires its own IP address. As a result, you might have nodes that have available compute and memory resources, but can't accommodate additional Pods because the node has run out of IP addresses to assign to Pods. + +We can significantly increase the number of IP addresses that nodes can assign to Pods by assigning **IP prefixes**, rather than assigning individual secondary IP addresses to your nodes. Each prefix includes several IP addresses. + +If you don't configure your cluster for IP prefix assignment, your cluster must make more Amazon EC2 application programming interface (API) calls to configure network interfaces and IP addresses necessary for Pod connectivity. + +As clusters grow to larger sizes, the frequency of these API calls can lead to longer Pod and instance launch times. This results in scaling delays to meet the demand of large and spiky workloads, and adds cost and management overhead because you need to provision additional clusters and VPCs to meet scaling requirements. For more information, see [Kubernetes Scalability threshold](https://github.com/kubernetes/community/blob/master/sig-scalability/configs-and-limits/thresholds.md)s on GitHub. + +## Considerations + +- Each Amazon EC2 instance type supports a maximum number of Pods. If your managed node group consists of multiple instance types, the smallest number of maximum Pods for an instance in the cluster is applied to all nodes in the cluster. + +- By default, the maximum number of Pods that you can run on a node is 110, but you can change that number. If you change the number and have an existing managed node group, the next AMI or launch template update of your node group results in new nodes coming up with the changed value. + +- When transitioning from assigning IP addresses to assigning IP prefixes, we recommend that you create new node groups to increase the number of available IP addresses, rather than doing a rolling replacement of existing nodes. Running Pods on a node that has both IP addresses and prefixes assigned can lead to inconsistency in the advertised IP address capacity, impacting the future workloads on the node. For the recommended way of performing the transition, see Replace all nodes during migration from [Secondary IP mode to Prefix Delegation mode or vice versa](https://github.com/aws/aws-eks-best-practices/blob/master/content/networking/prefix-mode/index_windows.md#replace-all-nodes-during-migration-from-secondary-ip-mode-to-prefix-delegation-mode-or-vice-versa) in the Amazon EKS best practices guide. + +- For clusters with Linux nodes. + - Once you configure the add-on to assign prefixes to network interfaces, you can't downgrade your Amazon VPC CNI plugin for Kubernetes add-on to a version lower than `1.9.0` (or `1.10.1`) without removing all nodes in all node groups in your cluster. + - If you're also using security groups for Pods, with `POD_SECURITY_GROUP_ENFORCING_MODE=standard` and AWS_VPC_K8S_CNI_EXTERNALSNAT=false, when your Pods communicate with endpoints outside of your VPC, the node's security groups are used, rather than any security groups you've assigned to your Pods. + - If you're also using security groups for Pods, with `POD_SECURITY_GROUP_ENFORCING_MODE=strict`, when your Pods communicate with endpoints outside of your VPC, the Pod's security groups are used. + +## Prerequisites + +- An existing cluster. To deploy one, see Creating an Amazon EKS cluster. + +- The subnets that your Amazon EKS nodes are in must have sufficient contiguous `/28` (for IPv4 clusters) or /80 (for IPv6 clusters) Classless Inter-Domain Routing (CIDR) blocks. You can only have Linux nodes in an IPv6 cluster. Using IP prefixes can fail if IP addresses are scattered throughout the subnet CIDR. We recommend that following: + +- Using a subnet CIDR reservation so that even if any IP addresses within the reserved range are still in use, upon their release, the IP addresses aren't reassigned. This ensures that prefixes are available for allocation without segmentation. + +- Use new subnets that are specifically used for running the workloads that IP prefixes are assigned to. Both Windows and Linux workloads can run in the same subnet when assigning IP prefixes. + +- To assign IP prefixes to your nodes, your nodes must be AWS Nitro-based. Instances that aren't Nitro-based continue to allocate individual secondary IP addresses, but have a significantly lower number of IP addresses to assign to Pods than Nitro-based instances do. + +- For clusters with Linux nodes – If your cluster is configured for the IPv4 family, you must have version 1.9.0 or later of the Amazon VPC CNI plugin for Kubernetes add-on installed. You can check your current version with the following command. + +```bash +kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2 +``` + +- If your cluster is configured for the IPv6 family, you must have version 1.10.1 of the add-on installed. If your plugin version is earlier than the required versions, you must update it. For more information, see the updating sections of Working with the Amazon VPC CNI plugin for Kubernetes Amazon EKS add-on. + +## To increase the amount of available IP addresses for your Amazon EC2 nodes + +Configure your cluster to assign IP address prefixes to nodes. Complete the procedure on the tab that matches your node's operating system. + +### for Linux + +1. Enable the parameter to assign prefixes to network interfaces for the Amazon VPC CNI DaemonSet. When you deploy a `1.21` or later cluster, version `1.10.1` or later of the Amazon VPC CNI plugin for Kubernetes add-on is deployed with it. If you created the cluster with the IPv6 family, this setting was set to true by default. If you created the cluster with the IPv4 family, this setting was set to false by default. + ```bash + kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true + ``` + - Even if your subnet has available IP addresses, if the subnet does not have any contiguous `/28` blocks available, you will see the following error in the Amazon VPC CNI plugin for Kubernetes logs. + - `InsufficientCidrBlocks: The specified subnet does not have enough free cidr blocks to satisfy the request` + - This can happen due to fragmentation of existing secondary IP addresses spread out across a subnet. To resolve this error, either create a new subnet and launch Pods there, or use an Amazon EC2 subnet CIDR reservation to reserve space within a subnet for use with prefix assignment. For more information, see [Subnet CIDR reservations](https://docs.aws.amazon.com/vpc/latest/userguide/subnet-cidr-reservation.html) in the Amazon VPC User Guide. + +- If you plan to deploy a managed node group without a launch template, or with a launch template that you haven't specified an AMI ID in, and you're using a version of the Amazon VPC CNI plugin for Kubernetes at or later than the versions listed in the prerequisites, then skip to the next step. Managed node groups automatically calculates the maximum number of Pods for you. + If you're deploying a self-managed node group or a managed node group with a launch template that you have specified an AMI ID in, then you must determine the Amazon EKS recommend number of maximum Pods for your nodes. Follow the instructions in Amazon EKS recommended maximum Pods for each Amazon EC2 instance type, adding `--cni-prefix-delegation-enabled` to step 3. Note the output for use in a later step. + +- Create one of theh following types of node groups with at least one Amazon EC2 +- + + +--- +reference +- https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html +- [Amazon VPC CNI](./Amazon VPC CNI.md) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/NLB\342\200\205IP\342\200\205mode.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/NLB\342\200\205IP\342\200\205mode.md" new file mode 100644 index 00000000..35dedf2b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/NLB\342\200\205IP\342\200\205mode.md" @@ -0,0 +1,36 @@ +--- +title: 'NLB IP mode' +lastUpdated: '2024-03-02' +--- + +AWS Load Balancer Controller supports Network Load Balancer (NLB) with IP tergets for pods runing on Amazon EC2 instances and AWS Fargate through Kubernetes service of type `LoadBalancer` with proper annotation. In this mode, the AWS NLB targets traffic directly to the Kubernetes pods behind the service, aliminationg the need for an extra network hop through the worker nodes in the Kubernetes cluster. + +### Configuration + +The NLB IP mode is determined based on the annotations added to the service object. For NLB in IP mode, apply the following annotation to the service: + +```yaml +metadata: + name: my-service + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "nlb-ip" + +``` + +> Do not modify the service annotation `service.beta.kubernetes.io/aws-load-balancer-type` on an existing service object. If you need to modify the underlying AWS LoadBalancer type, for example from classic to NLB, delete the kubernetes service first and create again with the correct annotation. Failure to do so will result in leaked AWS load balancer resources. + +> The default load balancer is internet-facing. To create an internal load balancer, apply the following annotation to your service: `service.beta.kubernetes.io/aws-load-balancer-internal: "true"`` + +### Protocols + +Support is avalilable for both TCP and UDP protocols. In case of TCP, NLB in IP mode does not pass the client source IP address to the pods. You can configure [NLB proxy protocol v2](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol) via annotation if you need the client source IP address. + +to enable proxy protocol v2, apply the following annotation to your service: + +```yaml +service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" +``` + +### Security group + +NLB does not currently support a managed security group. For ingress access, the controller will resolve the security group for the ENI corresponding to the endpoint pod. If the ENI has a single security group, it gets used. In case of multiple security groups, the controller expects to find only one security group tagged with the Kubernetes cluster id. Controller will update the ingress rules on the security groups as per the service spec. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Node\342\200\205not\342\200\205Ready.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Node\342\200\205not\342\200\205Ready.md" new file mode 100644 index 00000000..255cf945 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/EKS/Node\342\200\205not\342\200\205Ready.md" @@ -0,0 +1,92 @@ +--- +title: 'Node not Ready' +lastUpdated: '2024-03-02' +--- + +A Kubernetes node is a physical or virtual machine participating in a Kubernetes cluster, which can e used to run pods. When a node **shuts down** or **crashed**, it enters the NotReasy state, meaning it cannot be used to run pods. All stateful pods running on the node then becom unavailable. + +Common reasons for a Kubernetes `node not ready` error include lack of resources on the node, a problem with the kubelet (the agent enabling the Kubernetes control plane to access and control the node), or an error related to kube-proxy(the networking agent on the node). + +To identify a Kubernetes node not ready error: run the kubectl get nodes command. Nodes that are not ready will appear like this: + +```bash +NAME STATUS ROLES AGE VERSION +master.example.com Ready master 5h v1.17 +node1.example.com NotReady compute 5h v1.17 +node2.example.com Ready compute 5h v1.17 +``` + +We'll provide best practices for diagnosing simple cases of the `node not ready` error, but more complex cases will require advanced diagnosis and troubleshooting, which is beyond the scope of this article. + +## Node states + +At any given time, a Kubernetes node can be in one of the following states: + +- **Ready**: able to run pods. +- **NotReady**: not operating due to a problem, and cannot run pods. +- **SchedulingDisabled**: the node is healthy but has been marked by the cluster as not schedulable. +- **Unknown**: If the node controller cannot communicate with the node, it waits a default of 40 seconds, and then sets the node status to unknown. + +If a note is in the `NodeReady` state, it indicates that the kubelet is installed on the node, but Kubernetes has detected a problem on the node that prevents it from running pods. + +## Troubleshooing Node Not Ready Error + +Here are some common reasons that a Kubernetes node may enter the `NotRead` state: + +### Lack of System Resources + +A node must have enough dist space, memory, and processing power to run Kubernetes workloads. + +If non-Kubernetes processes on the node are taking up too many resources, or if there are too many processes running on the node, it can be marked by the control plane as `NotReady` + +Run `kubectl describe node` and look in the Conditions section to see if resources are missing on the node: +- **MemoryPressure**: node is running out of memory. +- **DiskPressure**: node is running out of disk space. +- **PIDPressure**: node is running too many processes. + +Here are a few ways to resolve a system resource issue on the node: + +- Identify which non-Kubernetes processes are running on the node. If there are any, shut them down or reduce them to a minimum to conserve resources. +- Run a malware scan—there may be hidden malicious processes taking up system resources. +- Upgrade the node. +- Check for hardware issues or misconfigurations and resolve them. + +### Kubelet Issue + +The Kubelet must run on each node to enable it to perticipate in the cluster. If the kubelet crashes or stops on a node, it cannot communicate with the API server and the node goes into a not ready state. + +Run `kubectl describe node [name]` and look in the Conditions section. If all the conditions are unknown, this indicates the kubelet is down. + +To resolve a kubelet issue, SSH into the node and run the command systemctl status kubelet + +Look at the value of the Active field: + +- `active (running)` means the kubelet is actually operational, look for the problem elsewhere. +- `active (exited)` means the kubelet was exited, probably in error. Restart it.> +- `inactive (dead)` means the kubelet crashed. To identify why, run the command `journalctl -u kubelet` and examine the kubelet logs. + +### Kube-proxy Issue + +kube-proxy runs on every node and is responsible for regulating network traffic between the node and other entities inside and outside the cluster. If kube-proxy stops running for any reason, the node goes into a not ready state. + +Run `kubectl get pods - kube-system` to show pods belonging to the Kubernetes system. + +Try looking in the following places to identify what is the issue with kube-proxy: + +- Run the command `kubectl describe pod` using the name of the kube-proxy pod that failed, and check the Events section in the output. +- Run the command `kubectl logs [pod-name] -n kube-system` to see a full log of the failing kube-proxy pod. +- Run the command `kubectl describe daemonset kube-proxy -n kube-system` to see the status of the kube-proxy daemonset, which is responsible for ensuring there is a kube-proxy running on every Kubernetes node. + +Please note that these procedures can help you gather more information about the problem, but additional steps may be needed to resolve the problem. If one of the quick fixes above did not work, you’ll need to undertake a more complex, non-linear diagnosis procedure to identify which parts of the Kubernetes environment contribute to the node not ready problem and resolve it. + +## Connectivity Issue + +Even if a node is configured perfectly, but it has no network connectivity, Kubernetes treats the node as not ready. This could be due to a disconnected network cable, no Internet access, or misconfigured networking on the machine. + +Run `kubectl describe node [name]` and look in the Conditions section. if the `NetworkUnavailable` flag is `true`, this means the node has a connectivity issue. + +--- +reference +- https://www.airplane.dev/blog/debugging-kubernetes-nodes-in-not-ready-state +- https://stackoverflow.com/questions/59493326/aws-eks-worker-nodes-going-notready +- https://komodor.com/learn/how-to-fix-kubernetes-node-not-ready-error/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/K8s\342\200\205Architecture.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/K8s\342\200\205Architecture.md" new file mode 100644 index 00000000..ca2864a7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/K8s\342\200\205Architecture.md" @@ -0,0 +1,78 @@ +--- +title: 'K8s Architecture' +lastUpdated: '2024-03-02' +--- + +쿠버네티스는 중앙(Master)에 API 서버와 상태 저장소를 두고 각 서버(Node)의 에이전트(kubelet)와 통신하는구조로 동작한다. 하지만 이 개념을 여러 모듈로 쪼개어 구현하고 다양한 오픈소스를 사용하기 떄문에 설치가 까다롭고 구성이 복잡해보일 수 있다. + +## 마스터-노드 구조 + +쿠버네티스는 전체 클러스터를 관리하는 **마스터**와 컨테이너가 배포되는 **노드**로 구성되어있다. 모든 명령은 마스터의 API 서버를 호출하고 노드는 마스터와 통신하면서 필요한 작업을 수행한다. 특정 노드의 컨테이너에 명령하거나 로그를 조회할 때도 노드에 직접 명령하는게 아니라 마스터에 명령을 내리고 마스터가 노드에 접속하여 대신 결과를 응답한다. + +### Master + +마스터 서버는 다양한 모듈이 확장성을 고려하여 기능별로 쪼개져 있는 것이 특징이다. 관리자만 접속할 수 있도록 보안 설정을 해야하고 마스터 서버가 죽으면 클러스터를 관리할 수 없기 때문에 보통 3대를 구성하여 안정성을 높인다. AWS EKS 같은 경우 마스터를 AWS에서 자체 관리하여 안정성을 높였고(마스터에 접속 불가) 개발 환경이나 소규모 환경에선 마스터와 노드를 분리하지 않고 같은 서버에 구성하기도 한다. + +### Node + +노드 서버는 마스터 서버와 통신하면서 필요한 Pod를 생성하고 네트워크와 볼륨을 설저한다. 실제 컨테이너들이 생성되는 곳으로, 수백대로 확장 가능하다. 각각의 서버에 라벨을 붙여 사용목적을 정의할 수 있다. + +### Kubectl + +API 서버는 json 또는 protobuf 형식을 이용한 http 통신을 지원한다. 이 방식을 그대로 쓰면 불편하므로 보통 `kubectl`이라는 명령행 도구를 사용한다. 공식적으론 큐브컨트롤(cube control)이라고 읽는다. + +## Master 구성 요소 + + + +### API 서버 (Kube-apiserver) + +API 서버는 모든 요청을 처리하는 **마스터의 핵심 모듈**이다. Kubectl의 요청뿐 아니라 내부 모듈의 요청도 처리하며 권한을 체크하여 요청을 거부할 수 있다. 실제로 하는 일은 원하는 상태를 `Key-value` 저장소에 저장하고 저장된 상태를 조회하는 매우 단순한 작업이다. Pod을 노드에 할당하고 상태를 체크하는 일은 다른 모듈로 분리되어있다. 노드에서 실행중인 컨테이너의 로그를 보여주고 명령을 보내는 등 `디버거 역할`도 수행한다. + +API 서버는 요청을 받으면 etcd 저장소와 통신할 뿐, **실제로 상태를 바꾸는 건 스케줄러와 컨트롤러**이다. 현재 상태를 모니터링하다가 원하는 상태와 다르면 각자 맡은 작업을 수행하고 상태를 갱신한다. + +### 스케줄러 + +스케줄러는 할당되지 않은 Pod을 여러 가지 조건(필요한 자원, 라벨)에 따라 적절한 노드 서버에 할당해주는 모듈이다. + +### 컨트롤러 + +- **큐브 컨트롤러** (kube-controller-manager) + + 큐브 컨트롤러는 다양한 역할을 하는 아주 바쁜 모듈이다. 쿠버네티스에 있는 거의 모든 오브젝트의 상태를 관리한다. 오브젝트별로 철저하게 분업화되어 Deployment는 ReplicaSet을 생성하고 ReplicaSet은 Pod을 생성하고 Pod은 스케줄러가 관리하는 식이다. + +- **클라우드 컨트롤러** (cloud-controller-manager) + + 클라우드 컨트롤러는 AWS, GCE, Azure 등 클라우드에 특화된 모듈이다. 노드를 추가/삭제하고 로드 밸런서를 연결하거나 볼륨을 붙일 수 있다. 각 클라우드 업체에서 인터페이스에 맞춰 구현하면 되기 때문에 확장성이 좋고 많은 곳에서 자체 모듈을 만들어 제공하고 있다. + +### 분산 데이터 저장소 (etcd) + +**RAFT 알고리즘을 이용한 Key-value** 저장소이다. 여러개로 분산하여 복제할 수 있기 때문에 안정성이 높고 속도도 빠른 편이다. 단순히 값을 저장하고 읽는 기능뿐 아니라 `Watch` 기능이 있어 어떤 상태가 변경되면 바로 체크하여 로직을 실행할 수 있다. + +클러스터의 모든 설정, 상태 데이터는 여기 저장되고 나머지 모듈은 statless하게 동작하기 때문에 **etcd**만 잘 백업해두면 언제든지 클러스터를 복구할 수 있다. etcd는 **오직 API 서버와만** 통신하고, 다른 모듈은 API 서버를 거쳐 etcd 데이터에 접근한다. k3s 같은 초경량 쿠버네티스 배포판에서는 ectd 대신 sqlite를 사용하기도 한다. + +## Node 구성 요소 + + + +### 큐블릿 (Kublet) + +노드에 할당된 Pod의 생명주기를 관리한다. Pod를 생성하고 Pod 안의 컨테이너에 이상이 없는지 확인하면서 주기적으로 마스터에 상태를 전달한다. API 서버의 요청을 받아 컨테이너의 로그를 전달하거나 특정 명령을 대신 수행하기도 한다. + +큐블릿 서비스는 kube API 서버를 통해 클러스터의 변화를 감지하고, CNI 플러그인을 호출하여 노드 네트워크를 구성한다. + +### 프록시 (Kube-proxy) + +큐블릿이 Pod를 관리한다면 프록시는 Pod로 연결되는 네트워크를 관리한다. TCP, UDP, SCTP 스트림을 포워딩하고 여러개의 Pod를 라운드로빈 형태로 묶어 서비스를 제공할 수 있다. + +초기에는 Kube-proxy 자체가 프록시 서버로 동작하면서 실제 요청을 프록시 서버가 받고 각 Pod에 전달해 주었는데 시간이 지나면서 iptables를 설정하는 방식으로 변경되었다. iptables에 등록된 규칙이 많아지면 느려지는 문제가 발생하여 최근에는 IPVS를 지원하기 시작했다. + +### Container runtime + +컨테이너 런타임은 컨테이너 실행을 담당하는 소프트웨어이다. + +Kubernetes는 containerd, CRI-O 및 기타 Kubernetes CRI(Container Runtime Interface) 구현과 같은 컨테이너 런타임을 지원한다. + +--- +참고 +- https://kubernetes.io/docs/concepts/overview/components/ diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Kubernetes.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Kubernetes.md" new file mode 100644 index 00000000..ab0bd30e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Kubernetes.md" @@ -0,0 +1,40 @@ +--- +title: 'Kubernetes' +lastUpdated: '2024-03-02' +--- + +쿠버네티스(Kubernetes)는 컨테이너화 된 애플리케이션의 대규모 배포, 스케일링 및 관리를 간편하게 만들어주는 오픈 소스 기반 컨테이너 오케스트레이션 도구이다. + +프로덕션 환경에서는 애플리케이션을 실행하는 여러 컨테이너를 관리하고 다운타임이 없는지 확인해야하는데, Kubernetes는 다중 컨테이너 처리를 자동으로 처리하고, 분산 시스템을 탄력적으로 실행할 수 있는 프레임워크를 제공한다. K8s를 사용하면 애플리케이션의 확장 및 장애 조치를 처리하고 배포 패턴 등을 쉽게 처리할 수 있다. + + + +## 쿠버네티스의 특징 5가지 + +### 1. 선언적 접근 방식 + +쿠버네티스에서는 동작을 지시하는 개념보다는 상태를 선언하는 개념을 주로 사용한다. + +쿠버네티스는 원하는 상태(Desired state)와 현재의 상태(Current state)가 서로 일치하는지를 지속적으로 체크하고 업데이트한다. 만약 컨테이너의 상태에 문제가 생겼을 경우, 쿠버네티스는 해당 요소가 원하는 상태로 복구될 수 있도록 필요한 조치를 자동으로 취한다. + +### 2. 기능 단위의 분산 + +쿠버네티스에서는 각각의 기능들이 개별적인 구성 요소로서 독립적으로 분산되어 있다. 실제로 노드(Node), 레플리카셋(ReplicaSet), 디플로이먼트(Deployment), 네임스페이스(Namespace) 등 클러스터를 구성하는 주요 요소들이 모두 컨트롤러(Controller)로서 구성되어 있으며, 이들은 Kube Controller Manager 안에 패키징 되어 있다 + +### 3. 클러스터 단위 중앙 제어 + +쿠버네티스에서는 전체 물리 리소스를 클러스터 단위로 추상화하여 관리한다. 클러스터 내부에는 클러스터의 구성 요소들에 대해 제어 권한을 가진 컨트롤 플레인(Control Plane) 역할의 마스터 노드(Master Node)를 두게 되며, 관리자는 이 마스터 노드를 이용하여 클러스터 전체를 제어한다. + +### 4. 동적 그룹화 + +쿠버네티스의 구성 요소들에는 쿼리 가능한 레이블(Label)과 메타데이터용 어노테이션(Annotation)에 임의로 키-값 쌍을 삽입할 수 있다. 관리자는 selector를 이용해서 레이블 기준으로 구성 요소들을 유연하게 관리할 수 있고, 어노테이션에 기재된 내용을 참고하여 해당 요소의 특징적인 내역을 추적할 수 있다. + +### 5. API 기반 상호작용 + +쿠버네티스의 구성 요소들은 오직 Kubernetes API server(kube-apiserver)를 통해서만 상호 접근 가능한 구조를 가진다. 마스터 노드에서 kubectl을 거쳐 실행되는 모든 명령은 이 API 서버를 거쳐 수행되며, 컨트롤 플레인(Control Plane)에 포함된 클러스터 제어 요소나 워커 노드(Worker Node)에 포함된 kubelet, 프록시 역시 API 서버를 항상 바라보게 되어 있다. + +--- +참고 +- https://kubernetes.io/docs/home/ +- https://kubernetes.io/docs/concepts/overview/ +- https://kubernetes.io/docs/concepts/overview/components/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Kubernetes\342\200\205Overview\342\200\205Diagrams.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Kubernetes\342\200\205Overview\342\200\205Diagrams.md" new file mode 100644 index 00000000..df64bf5d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Kubernetes\342\200\205Overview\342\200\205Diagrams.md" @@ -0,0 +1,16 @@ +--- +title: 'Kubernetes Overview Diagrams' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/bddf24aa-95f2-43bb-a909-a4ce14aecfca) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/26e22254-148d-4038-b593-99079b030ddc) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/3822269c-5936-4ed1-8844-3cfd6944839d) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/9020eb5f-59f5-4888-8b30-eb7f1c638338) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/a26b929a-c3c8-4e65-affb-4b707d9ca258) + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/8ddf0dd2-97f5-4dda-85bc-bee616cb22e7) diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/CoreDNS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/CoreDNS.md" new file mode 100644 index 00000000..75e8e6a6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/CoreDNS.md" @@ -0,0 +1,67 @@ +--- +title: 'CoreDNS' +lastUpdated: '2024-03-02' +--- + +CoreDNS는 일반적으로 Kubernetes Cluster 내부에서 이용되는 DNS Server이다. Kubernetes Cluster 내부에서 Domain을 통해서 Service나 Pod의 IP를 찾기 위한 용도로 많이 이용된다. + +CoreDNS는 일반적으로 Worker Node에서 Deployment로 배포되어 다수의 Pod으로 구동된다. 그리고 다수의 CoreDNS Pod들은 CoreDNS Service를 통해서 VIP (ClusterIP)로 묶이게 된다. Kubernetes Cluster의 Pod들은 CoreDNS Service의 VIP를 통해서 CoreDNS에 접근하게 된다. 다수의 CoreDNS Pod와 CoreDNS Service를 이용하는 이유는 HA(High Availability) 때문이다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/afbef816-547f-41ac-a1ce-467e36b21b4f) + +```bash +$ kubectl -n kube-system get deployment coredns +NAME READY UP-TO-DATE AVAILABLE AGE +coredns 2/2 2 2 13d + +$ kubectl -n kube-system get service kube-dns +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kube-dns ClusterIP 10.96.0.10 53/UDP,53/TCP,9153/TCP 13d +[Shell 1] CoreDNS Deployment, Pod + +$ kubectl run my-shell --rm -i --tty --image nicolaka/netshoot -- bash +(container)# cat /etc/resolv.conf +nameserver 10.96.0.10 +search default.svc.cluster.local svc.cluster.local cluster.local +options ndots:5 +``` + +임의의 Shell Pod을 만들고 Shell Pod안에서 /etc/resolv.conf 파일에 설정된 DNS Server를 확인하는 과정이다. CoreDNS Service의 VIP (ClusterIP)가 설정되어 있는걸 확인할 수 있다. + +### Corefile + +```bash +.:53 { + log + errors + health { + lameduck 5s + } + ready + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + ttl 30 + } + prometheus :9153 + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance +} +``` + +CoreDNS는 Kubernetes API 서버로부터 Service와 Pod를 Watch하여 Service와 Pod의 Event를 수신한다. + +Kubernetes API 서버로부터 Service 생성/삭제 Event를 받은 CoreDNS는 Service, Pod의 Domain을 생성/삭제한다. 이러한 CoreDNS의 Kubernetes 관련 동작은 CoreDNS의 Config 파일을 통해서 설정할 수 있다. 위의 코드는 CoreDNS의 Config 파일을 나타내고 있다. kubernetes 설정 부분이 있는걸 확인할 수 있다. + +CoreDNS의 설정파일에서 한가지 더 주목해야하는 설정은 forward 설정이다. forward 설정은 CoreDNS의 Upstream DNS Server를 지정하는 역할을 수행한다. forward 설정에 `/etc/resolv.conf` 파일이 지정되어 있는것을 알 수 있다. CoreDNS Pod의 dnsPolicy는 "Default"이다. "Default"는 Pod가 떠있는 Node의 `/etc/resolv.conf `파일의 내용을 상속받아 Pod의 `/etc/resolv.conf` 파일을 생성하는 설정이다. 따라서 CoreDNS Pod의 `/etc/resolve.conf`는 Node의 DNS Server 정보가 저장되어 있다. 즉 CoreDNS는 Node의 DNS Server를 Upstream으로 설정한다. + +--- +참고 +- https://jonnung.dev/kubernetes/2020/05/11/kubernetes-dns-about-coredns/ +- https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ +- https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/ +- https://coredns.io/plugins/kubernetes/ + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/DNS\342\200\205in\342\200\205k8s.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/DNS\342\200\205in\342\200\205k8s.md" new file mode 100644 index 00000000..12310210 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/DNS\342\200\205in\342\200\205k8s.md" @@ -0,0 +1,49 @@ +--- +title: 'DNS in k8s' +lastUpdated: '2024-03-02' +--- + +두개의 포드와 서비스가 있다고 해보자. 각 포드의 이름은 test와 web이고, 가상 IP를 하나씩 할당받았다. + +``` + + /-----\ /--------- /-----\ + | pod | 《 service | pod | + \-----/ \--------- \-----/ + 10.244.1.5 10.107.37.188 10.244.1.5 + test web-service web + +``` + +서비스가 생성되면, k8s DNS sevice(CoreDNS)는 **해당 서비스를 위한 레코드**를 저장한다. 즉, ip를 직접 명시하지 않고 사용할 수 있는 대체 이름을 지정해준다는 것이다. 우선 클러스터 내부에서 포드는 해당 서비스의 이름으로 접근하여 그 서비스의 위치로 요청을 보낼 수 있다. + +``` +curl http://web-service + > Welcome to NGINX! +``` + +하지만 만약에 두 포드가 다른 네임스페이스에 있다고 해보자. 그런 경우에도 해당 네임스페이스명을 같이 적어주기만 하면 요청을 보낼 수 있다. + +``` +curl http://web-service.{namespace name} + > Welcome to NGINX! +``` + +그리고 모든 서비스는 svc라는 서브 도메인 안에 묶여있다. 그렇기 때문에 아래와 같이 접근하는 것 또한 가능하다. + +``` +curl http://web-service.{namespace name}.svc + > Welcome to NGINX! +``` + +그리고 모든 서비스와 포드는 cluster.local이라는 root 도메인 안에 묶여있다. 이 url이 바로, 10.107.37.188 IP를 가진 web-service 서비스에 대해서 k8s 내부에서 쓸 수 있는 완전한 도메인명이다. + +``` +curl http://web-service.{namespace name}.svc.cluster.local + > Welcome to NGINX! +``` + +## CoreDNS + +위에서 서비스를 레코드에 등록하고, 쿼리할 수 있는 기능은 k8s에 내장되어있는 CoreDNS라는 프로그램이다. 기존에는 KubeDNS라는 별도의 프로그램이 사용되었었는데, 메모리나 CPU 점유율 문제로 1.12 버전부터 기본 설정이 변경됐다. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/External\342\200\205DNS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/External\342\200\205DNS.md" new file mode 100644 index 00000000..4e5a9770 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/External\342\200\205DNS.md" @@ -0,0 +1,197 @@ +--- +title: 'External DNS' +lastUpdated: '2024-03-02' +--- + +ExternalDNS는 kubernetes dns(kube-dns)와 상반되는 개념으로 내부 도메인서버가 아닌 Public한 도메인서버(AWS Route53, GCP DNS 등)를 사용하여 쿠버네티스의 리소스를 쿼리할 수 있게 해주는 오픈소스 솔루션이다. + +ExternalDNS를 사용하면 public도메인 서버가 무엇이든 상관없이 쿠버네티스 리소스를 통해서 DNS레코드를 동적으로 관리 할 수 있다. + +### ExternalDNS 설치 + +쿠버네티스 서비스 계정에 IAM 역할을 사용하려면 OIDC 공급자가 필요하다. IAM OIDC 자격 증명 공급자를 생성한다. + +```bash +eksctl utils associate-iam-oidc-provider --cluster {cluster name} --approve +``` + +External DNS용도의 구분된 namespace를 생성한다. + +```bash +kubectl create namespace external-dns +``` + +External DNS가 Route53을 제어할 수 있도록 정책을 생성한다. IAM Policy json 파일을 생성해준다. + +```bash +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "route53:ChangeResourceRecordSets" + ], + "Resource": [ + "arn:aws:route53:::hostedzone/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "route53:ListHostedZones", + "route53:ListResourceRecordSets" + ], + "Resource": [ + "*" + ] + } + ] +} +``` + +생성한 json파일로 IAM Policy를 생성한다. + +```bash +aws iam create-policy --policy-name AllowExternalDNSUpdates --policy-document file://{json file name}. +``` + +`eksctl`을 이용하여 iam service account를 생성한다. + +```bash +eksctl create iamserviceaccount \ + --name \ ### Exteranl DNS service account명 = external-dns + --namespace \ ### External DNS namespace명 = external-dns + --cluster \ ### AWS EKS 클러스터명 + --attach-policy-arn \ ### 앞서 생성한 Policy arn + --approve \ + --override-existing-serviceaccounts +``` + +> 클러스터 노드 IAM Role에 Route53 접근권한이 설정되지 않는 경우 배포가 되지 않는다. 적용이 안되었으면 IAM Role 콘솔에서 직접 적용해야한다. + +### External DNS 배포 + +공식 문서를 참고하여 yaml파일을 작성하고 배포한다. + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns + namespace: external-dns + # If you're using Amazon EKS with IAM Roles for Service Accounts, specify the following annotation. + # Otherwise, you may safely omit it. + annotations: + # Substitute your account ID and IAM service role name below. + eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: external-dns + namespace: external-dns +rules: +- apiGroups: [""] + resources: ["services","endpoints","pods"] + verbs: ["get","watch","list"] +- apiGroups: ["extensions","networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list","watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer + namespace: external-dns +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: external-dns +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns + namespace: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + serviceAccountName: external-dns + containers: + - name: external-dns + image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + args: + - --source=service + - --source=ingress + - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones + - --provider=aws + - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization + - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) + - --registry=txt + - --txt-owner-id=my-hostedzone-identifier + securityContext: + fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files +``` + +`eks.amazonaws.com/role-arn`과 `domain-filter`를 정확하게 입력해줘야 한다. + +policy는 Route53에서 레코드 업데이트를 제어하는 정책을 뜻한다. + +- upsert-only: 생성, 업데이트 +- sync: 생성, 업데이트, 삭제 +- create-only: 생성 + +## 서비스 배포 + +ingress에 `external-dns.alpha.kubernetes.io/hostname`와 `service.beta.kubernetes.io/aws-load-balancer-ssl-cert` annotation을 추가하여 설정해준다. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: test-service + annotations: + external-dns.alpha.kubernetes.io/hostname: your.domain.com # 자신의 domain + service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:ap-northeast-2:{accountId}:certificate/{} # ssl cert의 arn + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http + service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https +spec: + selector: + app: test + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 + - name: https + port: 443 + targetPort: 8080 + protocol: TCP + type: LoadBalancer +``` + +배포된 external-dns의 로그를 확인해서 레코드가 정상적으로 생성되는지 확인할 수 있다. + +```yaml +kubectl logs -f {external-dns pod name} -n external-dns +``` + +--- +참고 +- https://github.com/kubernetes-sigs/external-dns#the-latest-release-v08 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/ServiceDNS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/ServiceDNS.md" new file mode 100644 index 00000000..f94e9b61 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/DNS/ServiceDNS.md" @@ -0,0 +1,15 @@ +--- +title: 'ServiceDNS' +lastUpdated: '2024-03-02' +--- + +생성된 서비스의 IP는 어떻게 알 수 있을까? 서비스가 생성된 후 kubectl get svc를 이용하면 생성된 서비스와 IP를 받아올 수 있지만, 이는 서비스가 생성된 후이고, 계속해서 변경되는 임시 IP이다. + +### DNS를 이용하는 방법 +가장 쉬운 방법으로는 DNS 이름을 사용하는 방법이 있다. + +서비스는 생성되면 `[서비스 명].[네임스페이스명].svc.cluster.local` 이라는 DNS 명으로 쿠버네티스 내부 DNS에 등록이 된다. 쿠버네티스 클러스터 내부에서는 이 DNS 명으로 서비스에 접근이 가능한데, 이때 DNS에서 리턴해주는 IP는 외부 IP (External IP)가 아니라 Cluster IP (내부 IP)이다. + +간단한 테스트를 해보자. hello-node-svc 가 생성이 되었는데, 클러스터내의 pod 중 하나에서 ping으로 `hello-node-svc.default.svc.cluster.local` 을 테스트 하니 hello-node-svc의 클러스터 IP인 `10.23.241.62`가 리턴되는 것을 확인할 수 있다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/c1cdf6d3-4506-4bc2-b639-cf0e2dc77d04) diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Debugging\342\200\205DNS\342\200\205Resolution.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Debugging\342\200\205DNS\342\200\205Resolution.md" new file mode 100644 index 00000000..88613861 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Debugging\342\200\205DNS\342\200\205Resolution.md" @@ -0,0 +1,274 @@ +--- +title: 'Debugging DNS Resolution' +lastUpdated: '2024-03-02' +--- + +You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. It is recommended to run this tutorial on a cluster with at least two nodes that are not acting as control plane hosts. + +### Create a simple Pod to use as a test environment + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: dnsutils + namespace: default +spec: + containers: + - name: dnsutils + image: registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 + command: + - sleep + - "infinity" + imagePullPolicy: IfNotPresent + restartPolicy: Always +``` + +> Note: This example creates a pod in the default namespace. DNS name resolution for services depends on the namespace of the pod. For more information, review DNS for Services and Pods. + +Use that manifest to create a Pod: + +```bash +$ kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml +pod/dnsutils created +``` + +…and verify its status: + +```bash +kubectl get pods dnsutils +NAME READY STATUS RESTARTS AGE +dnsutils 1/1 Running 0 +``` + +Once that Pod is running, you can exec nslookup in that environment. If you see something like the following, DNS is working correctly. + +```bash +kubectl exec -it dnsutils -- nslookup kubernetes.default +``` + +### Check the local DNS configuration first + +Take a look inside the resolv.conf file. (See Customizing DNS Service and Known issues below for more information) + +```bash +kubectl exec -it dnsutils -- cat /etc/resolv.conf +``` + +Verify that the search path and name server are set up like the following (note that search path may vary for different cloud providers): + +```bash +search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal +nameserver 10.0.0.10 +options ndots:5 +``` + +Errors such as the following indicate a problem with the CoreDNS (or kube-dns) add-on or with associated Services: + +```bash +kubectl exec -i -t dnsutils -- nslookup kubernetes.default +Server: 10.0.0.10 +Address 1: 10.0.0.10 + +nslookup: can't resolve 'kubernetes.default' +``` +or +```bash +kubectl exec -i -t dnsutils -- nslookup kubernetes.default +Server: 10.0.0.10 +Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local + +nslookup: can't resolve 'kubernetes.default' +``` + +### Check if the DNS pod is running +Use the kubectl get pods command to verify that the DNS pod is running. + +```bash +kubectl get pods --namespace=kube-system -l k8s-app=kube-dns +NAME READY STATUS RESTARTS AGE +... +coredns-7b96bf9f76-5hsxb 1/1 Running 0 1h +coredns-7b96bf9f76-mvmmt 1/1 Running 0 1h +... +``` + +> Note: The value for label k8s-app is kube-dns for both CoreDNS and kube-dns deployments. + +If you see that no CoreDNS Pod is running or that the Pod has failed/completed, the DNS add-on may not be deployed by default in your current environment and you will have to deploy it manually. + +### Check for errors in the DNS pod + +Use the kubectl logs command to see logs for the DNS containers. + +For CoreDNS: + +```bash +kubectl logs --namespace=kube-system -l k8s-app=kube-dns +Here is an example of a healthy CoreDNS log: + +.:53 +2018/08/15 14:37:17 [INFO] CoreDNS-1.2.2 +2018/08/15 14:37:17 [INFO] linux/amd64, go1.10.3, 2e322f6 +CoreDNS-1.2.2 +linux/amd64, go1.10.3, 2e322f6 +2018/08/15 14:37:17 [INFO] plugin/reload: Running configuration MD5 = 24e6c59e83ce706f07bcc82c31b1ea1c +``` + +See if there are any suspicious or unexpected messages in the logs. + +### Is DNS service up? + +Verify that the DNS service is up by using the kubectl get service command. + +```bash +kubectl get svc --namespace=kube-system +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +... +kube-dns ClusterIP 10.0.0.10 53/UDP,53/TCP 1h +... +``` + +### Are DNS endpoints exposed? + +You can verify that DNS endpoints are exposed by using the kubectl get endpoints command. + +```bash +kubectl get endpoints kube-dns --namespace=kube-system +NAME ENDPOINTS AGE +kube-dns 10.180.3.17:53,10.180.3.17:53 1h +``` + +If you do not see the endpoints, see the endpoints section in the debugging Services documentation. + +For additional Kubernetes DNS examples, see the cluster-dns examples in the Kubernetes GitHub repository. + +### Are DNS queries being received/processed? + +You can verify if queries are being received by CoreDNS by adding the log plugin to the CoreDNS configuration (aka Corefile). The CoreDNS Corefile is held in a ConfigMap named coredns. To edit it, use the command: + +```bash +kubectl -n kube-system edit configmap coredns +``` + +Then add log in the Corefile section per the example below: + +```bash +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns + namespace: kube-system +data: + Corefile: | + .:53 { + log + errors + health + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + upstream + fallthrough in-addr.arpa ip6.arpa + } + prometheus :9153 + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance + } +``` + +After saving the changes, it may take up to minute or two for Kubernetes to propagate these changes to the CoreDNS pods. + +Next, make some queries and view the logs per the sections above in this document. If CoreDNS pods are receiving the queries, you should see them in the logs. + +Here is an example of a query in the log: + +```bash +.:53 +2018/08/15 14:37:15 [INFO] CoreDNS-1.2.0 +2018/08/15 14:37:15 [INFO] linux/amd64, go1.10.3, 2e322f6 +CoreDNS-1.2.0 +linux/amd64, go1.10.3, 2e322f6 +2018/09/07 15:29:04 [INFO] plugin/reload: Running configuration MD5 = 162475cdf272d8aa601e6fe67a6ad42f +2018/09/07 15:29:04 [INFO] Reloading complete +172.17.0.18:41675 - [07/Sep/2018:15:29:11 +0000] 59925 "A IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR qr,aa,rd,ra 106 0.000066649s +``` + +### Does CoreDNS have sufficient permissions? + +CoreDNS must be able to list service and endpoint related resources to properly resolve service names. + +Sample error message: + +```bash +2022-03-18T07:12:15.699431183Z [INFO] 10.96.144.227:52299 - 3686 "A IN serverproxy.contoso.net.cluster.local. udp 52 false 512" SERVFAIL qr,aa,rd 145 0.000091221s +``` + +First, get the current ClusterRole of system:coredns: + +```bash +$ kubectl describe clusterrole system:coredns -n kube-system +PolicyRule: + Resources Non-Resource URLs Resource Names Verbs + --------- ----------------- -------------- ----- + endpoints [] [] [list watch] + namespaces [] [] [list watch] + pods [] [] [list watch] + services [] [] [list watch] + endpointslices.discovery.k8s.io [] [] [list watch] +``` + +If any permissions are missing, edit the ClusterRole to add them: + +kubectl edit clusterrole system:coredns -n kube-system +Example insertion of EndpointSlices permissions: + +```bash +... +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch +... +``` + +### Are you in the right namespace for the service? + +DNS queries that don't specify a namespace are limited to the pod's namespace. + +If the namespace of the pod and service differ, the DNS query must include the namespace of the service. + +This query is limited to the pod's namespace: + +```bash +kubectl exec -i -t dnsutils -- nslookup +``` + +This query specifies the namespace: + +```bash +kubectl exec -i -t dnsutils -- nslookup . +``` + +To learn more about name resolution, see DNS for Services and Pods. + +## Known issues + +- Some Linux distributions (e.g. Ubuntu) use a local DNS resolver by default (systemd-resolved). Systemd-resolved moves and replaces `/etc/resolv.conf` with a stub file that can cause a fatal forwarding loop when resolving names in upstream servers. This can be fixed manually by using kubelet's `--resolv-conf` flag to point to the correct resolv.conf (With systemd-resolved, this is `/run/systemd/resolve/resolv.conf`). kubeadm automatically detects systemd-resolved, and adjusts the kubelet flags accordingly. + +- Kubernetes installs **do not configure** the node's `resolv.conf` files to use the cluster DNS by default, because that process is **inherently distribution-specific**. This should probably be implemented eventually. + +- **Linux's libc (a.k.a. glibc) has a limit for the DNS nameserver records to 3 by default and Kubernetes needs to consume 1 nameserver record.** This means that if a local installation already uses 3 nameservers, some of those entries will be lost. To work around this limit, the node can run dnsmasq, which will provide more nameserver entries. You can also use kubelet's --resolv-conf flag. + +- If you are using **Alpine version 3.3 or earlier** as your base image, DNS may not work properly due to a known issue with Alpine. Kubernetes issue 30215 details more information on this. + +--- +reference +- https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/ +- https://kubernetes.io/docs/tasks/administer-cluster/dns-horizontal-autoscaling/ + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/HTTPProxy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/HTTPProxy.md" new file mode 100644 index 00000000..cc33e437 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/HTTPProxy.md" @@ -0,0 +1,139 @@ +--- +title: 'HTTPProxy' +lastUpdated: '2024-03-02' +--- + +The Ingress object was added to Kubernetes in version `1.1` to describe properties of a cluster-wide reverse HTTP proxy. Since that time, the Ingress API has remained relatively unchanged, and the need to express implementation-specific capabilities has inspired an explosion of annotations. + +The goal of the HTTPProxy Custom Resource Definition (CRD) is to **expand upon the functionality of the Ingress API to allow for a richer user experience** as well addressing the limitations of the latter’s use in multi tenant environments. + +## Benefits + +- Safely supports multi-team Kubernetes clusters, with the ability to limit which Namespaces may configure virtual hosts and TLS credentials. +- Enables including of routing configuration for a path or domain from another HTTPProxy, possibly in another Namespace. +- Accepts multiple services within a single route and load balances traffic across them. +- Natively allows defining service weighting and load balancing strategy without annotations. +- Validation of HTTPProxy objects at creation time and status reporting for post-creation validity. + +## Ingress to HTTPProxy +A minimal Ingress object might look like: + +```yml +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: basic +spec: + rules: + - host: foo-basic.bar.com + http: + paths: + - backend: + service: + name: s1 + port: + number: 80 +``` + +This Ingress object, named `basic`, will route incoming HTTP traffic with a `Host:` header for `foo-basic.bar.com` to a Service named `s1` on port `80`. Implementing similar behavior using an HTTPProxy looks like this: + +```yml +# httpproxy.yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: basic +spec: + virtualhost: + fqdn: foo-basic.bar.com + routes: + - conditions: + - prefix: / + services: + - name: s1 + port: 80 +``` + +- Lines 1-5: As with all other Kubernetes objects, an HTTPProxy needs apiVersion, kind, and metadata fields. + +- Lines 7-8: The presence of the virtualhost field indicates that this is a root HTTPProxy that is the top level entry point for this domain. + +## Interacting with HTTPProxies + +As with all Kubernetes objects, you can use kubectl to create, list, describe, edit, and delete HTTPProxy CRDs. + +#### Creating an HTTPProxy: + +```yml +$ kubectl create -f basic.httpproxy.yaml +httpproxy "basic" created +``` + +#### Listing HTTPProxies: + +```yml +$ kubectl get httpproxy +NAME AGE +basic 24s +``` + +#### Describing HTTPProxy: + +```yml +$ kubectl describe httpproxy basic +Name: basic +Namespace: default +Labels: +API Version: projectcontour.io/v1 +Kind: HTTPProxy +Metadata: + Cluster Name: + Creation Timestamp: 2019-07-05T19:26:54Z + Resource Version: 19373717 + Self Link: /apis/projectcontour.io/v1/namespaces/default/httpproxy/basic + UID: 6036a9d7-8089-11e8-ab00-f80f4182762e +Spec: + Routes: + Conditions: + Prefix: / + Services: + Name: s1 + Port: 80 + Virtualhost: + Fqdn: foo-basic.bar.com +Events: +``` + +#### Deleting HTTPProxies: + +```yml +$ kubectl delete httpproxy basic +``` +httpproxy "basic" deleted + +## Status Reporting + +There are many misconfigurations that could cause an HTTPProxy or delegation to be invalid. To aid users in resolving these issues, Contour updates a status field in all HTTPProxy objects. In the current specification, invalid HTTPProxy are ignored by Contour and will not be used in the ingress routing configuration. + +If an HTTPProxy object is valid, it will have a status property that looks like this: + +```yml +status: + currentStatus: valid + description: valid HTTPProxy +``` + +If the HTTPProxy is invalid, the currentStatus field will be invalid and the description field will provide a description of the issue. + +As an example, if an HTTPProxy object has specified a negative value for weighting, the HTTPProxy status will be: + +```yml +status: + currentStatus: invalid + description: "route '/foo': service 'home': weight must be greater than or equal to zero" +``` + +--- +reference +- https://projectcontour.io/docs/v1.18.0/config/api/ diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/IPVS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/IPVS.md" new file mode 100644 index 00000000..f9cef31c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/IPVS.md" @@ -0,0 +1,64 @@ +--- +title: 'IPVS' +lastUpdated: '2024-03-02' +--- + +IP Virtual Server (IPVS) is a Linux connection (L4) load balancer. + + + +iptables can do simple L4 load balancing by randomly routing connections, with the randomness shaped by the weights on individual DNAT rules. **IPVS supports multiple load balancing modes (in contrast with the iptables one), which are outlined in below Table.** This allows IPVS to spread load more effectively than iptables, depending on IPVS configuration and traffic patterns. + +|Name|Shortcode|Description| +|-|-|-| +|Round-robin|rr|Sends subsequent connections to the “next” host in a cycle. This increases the time between subsequent connections sent to a given host, compared to random routing like iptables enables.| +|Least connection|lc|Sends connections to the host that currently has the least open connections.| +|Destination hashing|dh|Sends connections deterministically to a specific host, based on the connections’ destination addresses.| +|Source hashing|sh|Sends connections deterministically to a specific host, based on the connections’ source addresses.| +|Shortest expected delay|sed|Sends connections to the host with the lowest connections to weight ratio.| +|Never queue|nq|Sends connections to any host with no existing connections, otherwise uses “shortest expected delay” strategy.| + +IPVS supports packet forwarding modes: +- NAT rewrites source and destination addresses. +- DR encapsulates IP datagrams within IP datagrams. +- IP tunneling directly routes packets to the backend server by rewriting the MAC address of the data frame with the MAC address of the selected backend server. + +There are three aspects to look at when it comes to issues with iptables as a load balancer: +- **Number of nodes in the cluster** + - Even though Kubernetes already supports 5,000 nodes in release v1.6, kube-proxy with iptables is a bottleneck to scale the cluster to 5,000 nodes. One example is that with a NodePort service in a 5,000-node cluster, if we have 2,000 services and each service has 10 pods, this will cause at least 20,000 iptables records on each worker node, which can make the kernel pretty busy. +- **Time** + - The time spent to add one rule when there are 5,000 services (40,000 rules) is 11 minutes. For 20,000 services (160,000 rules), it’s 5 hours. +- **Latency** + - There is latency to access a service (routing latency); each packet must traverse the iptables list until a match is made. There is latency to add/remove rules, inserting and removing from an extensive list is an intensive operation at scale. + +IPVS also supports session affinity, which is exposed as an option in services (`Service.spec.sessionAffinity` and `Service.spec.sessionAffinityConfig`). Repeated connections, within the session affinity time window, will route to the same host. This can be useful for scenarios such as minimizing cache misses. It can also make routing in any mode effectively stateful (by indefinitely routing connections from the same address to the same host), but the routing stickiness is less absolute in Kubernetes, where individual pods come and go. + +To create a basic load balancer with two equally weighted destinations, run `ipvsadm -A -t
-s `. `-A`, `-E`, and `-D` are used to add, edit, and delete virtual services, respectively. The lowercase counterparts, `-a`, `-e`, and `-d`, are used to add, edit, and delete host backends, respectively: + +```bash +$ ipvsadm -A -t 1.1.1.1:80 -s lc +$ ipvsadm -a -t 1.1.1.1:80 -r 2.2.2.2 -m -w 100 +$ ipvsadm -a -t 1.1.1.1:80 -r 3.3.3.3 -m -w 100 +``` + +You can list the IPVS hosts with `-L`. Each virtual server (a unique IP address and port combination) is shown, with its backends: + +```bash +$ ipvsadm -L +IP Virtual Server version 1.2.1 (size=4096) +Prot LocalAddress:Port Scheduler Flags + -> RemoteAddress:Port Forward Weight ActiveConn InActConn +TCP 1.1.1.1.80:http lc + -> 2.2.2.2:http Masq 100 0 0 + -> 3.3.3.3:http Masq 100 0 0 +``` + +`-L` supports multiple options, such as `--stats`, to show additional connection statistics. + + +--- +reference +- https://medium.com/google-cloud/load-balancing-with-ipvs-1c0a48476c4d +- https://en.wikipedia.org/wiki/IP_Virtual_Server +- http://www.linuxvirtualserver.org/software/ipvs.html +- https://kubernetes.io/blog/2018/07/09/ipvs-based-in-cluster-load-balancing-deep-dive/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/NetworkPolicy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/NetworkPolicy.md" new file mode 100644 index 00000000..5eb8690f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/NetworkPolicy.md" @@ -0,0 +1,216 @@ +--- +title: 'NetworkPolicy' +lastUpdated: '2024-03-02' +--- + +Kubernetes's default befavior is to allow traffic between any two pods in the cluster network. This behavior is a deliberate design choice for ease of adoption and flexibility of configuration, but it is highly undesirable in practice. Allowing any system to make (or receive) arbitrary connections creates risk. + +An attacker can probe systems and can porentially exploit captured credentials to find weakened or missing authentication. Allowing arbitrary connections also makes it easier to exfiltrate data from a system through a conpromised workload. + +All in all, we strongly discourage running real clusters without `NetworkPolicy`. Since all pods can communicate with all other pods, we strongly recommend that application owners use `NetworkPolicy` objects along with other application-layer security measures, such as authentication tokens or mutual Transport Layer Security (mTLS), for any network communication. + +`NetworkPolicy` is a **resource type in Kubernetes that contains allow-based firewall rules**. Users can add NetworkPolicy objects to restrict connections to and from pods. + +## with CNI plugins + +The NetworkPolicy resource acts as a configuration for CNI plugins, which themselves are responsible for ensuring connectivity between pods. The Kubernetes API declares that NetworkPolicy support is optional for CNI drivers, which means that some CNI drivers do not support network policies, as shown in below Table. + +If a developer creates a NetworkPolicy when using a CNI driver that does not support NetworkPolicy objects, it does not affect the pod’s network security. Some CNI drivers, such as enterprise products or company-internal CNI drivers, may introduce their equivalent of a NetworkPolicy. Some CNI drivers may also have slightly different “interpretations” of the NetworkPolicy spec. + +|CNI plugin|NetworkPolicy supported| +|-|-| +|Calico|Yes, and supports additional plugin-specific policies| +|Cilium|Yes, and supports additional plugin-specific policies| +|Flannel|No| +|Kubenet|No| + +## NetworkPolicy example + +Below example is details a NetworkPolicy object, which contains a pod selector, ingress rules, and egress rules. The policy will apply to all pods in the same namespace as the NetworkPolicy that matches the selector label. This use of selector labels is consistent with other Kubernetes APIs: a spec identifies pods by their labels rather than their names or parent objects. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: demo + namespace: default +spec: + podSelector: + matchLabels: + app: demo + policyTypes: + - Ingress + - Egress + ingress: []NetworkPolicyIngressRule # Not expanded + egress: []NetworkPolicyEgressRule # Not expanded +``` + +Before getting deep into the API, let’s walk through a simple example of creating a `NetworkPolicy` to reduce the scope of access for some pods. Let’s assume we have two distinct components: `demo` and `demo-DB`. As we have no existing NetworkPolicy in below, all pods can communicate with all other pods (including hypothetically unrelated pods, not shown). + +image + +Our `demo-db` should (only) be able to receive connections from `demo` pods. To do that, we must add an ingress rule to the `NetworkPolicy`: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: demo-db + namespace: default +spec: + podSelector: + matchLabels: + app: demo-db + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchLabels: + app: demo +``` + +Now demo-db pods can receive connections only from demo pods. Moreover, demo-db pods cannot make connections. + +image + +> If users can unwittingly or maliciously change labels, they can change how `NetworkPolicy` objects apply to all pods. In our prior example, if an attacker was able to edit the `app: demo-DB` label on a pod in that same namespace, the `NetworkPolicy` that we created would no longer apply to that pod. Similarly, an attacker could gain access from another pod in that namespace if they could add the label `app: demo` to their compromised pod. + +## Rules + +`NetworkPolicy` objects contain distinct ingress and egress configuration sections, which contain a list of ingress rules and egress rules, respectively. `NetworkPolicy` rules act as exceptions, or an “allow list,” to the default block caused by selecting pods in a policy. **Rules cannot block access; they can only add access**. + +If multiple `NetworkPolicy` objects select a pod, all rules in each of those `NetworkPolicy` objects apply. It may make sense to use multiple `NetworkPolicy` objects for the same set of pods (for example, declaring application allowances in one policy and infrastructure allowances like telemetry exporting in another). + +However, keep in mind that they do not need to be separate `NetworkPolicy` objects, and with too many `NetworkPolicy` objects it can become hard to reason. + +> To support health checks and liveness checks from the Kubelet, the CNI plugin **must always allow traffic from a pod’s node.**
It is possible to abuse labels if an attacker has access to the node (even without admin privileges). Attackers can spoof a node’s IP and deliver packets with the node’s IP address as the source. + +--- + +Ingress rules and egress rules are discrete types in the `NetworkPolicy` API (`NetworkPolicyIngressRule` and `NetworkPolicyEgressRule`). However, they are functionally structured the same way. Each `NetworkPolicyIngressRule`/`NetworkPolicyEgressRule` contains a list of ports and a list of `NetworkPolicyPeers`. + +A `NetworkPolicyPeer` has four ways for rules to refer to networked entities: `ipBlock`, `namespaceSelector`, `podSelector`, and a combination. + +`ipBlock` is useful for allowing traffic to and from external systems. It can be used only on its own in a rule, without a `namespaceSelector` or `podSelector`. `ipBlock` contains a CIDR and an optional `except` CIDR. The `except` CIDR will exclude a sub-CIDR (it must be within the CIDR range). + +#### example + +Allow traffic from all IP addresses in the range `10.0.0.0` to `10.0.0.255`, excluding `10.0.0.10`: + +```yaml +from: + - ipBlock: + - cidr: "10.0.0.0/24" + - except: "10.0.0.10" +``` + +Allows traffic from all pods in any namespace labeled group:`x`: + +```yaml +from: + - namespaceSelector: + - matchLabels: + group: x +``` + +allow traffic from all pods in any namespace labeled service: x.. podSelector behaves like the spec.podSelector field that we discussed earlier. If there is no namespaceSelector, it selects pods in the same namespace as the NetworkPolicy. + + +```yml +from: + - podSelector: + - matchLabels: + service: y +``` + + +If we specify a namespaceSelector and a podSelector, the rule selects all pods with the specified pod label in all namespaces with the specified namespace label. It is common and highly recommended by security experts to keep the scope of a namespace small; typical namespace scopes are per an app or service group or team. There is a fourth option shown in below example with a namespace and pod selector. This selector behaves like an AND condition for the namespace and pod selector: pods must have the matching label and be in a namespace with the matching label. + +```yml +from: + - namespaceSelector: + - matchLabels: + group: monitoring + podSelector: + - matchLabels: + service: logscraper +``` + +Be aware this is a distinct type in the API, although the YAML syntax looks extremely similar. As `to` and `from` sections can have multiple selectors, a single character can make the difference between an `AND` and an `OR`, so be careful when writing policies. + +Our earlier security warning about API access also applies here. If a user can customize the labels on their namespace, they can make a `NetworkPolicy` in another namespace apply to their namespace in a way not intended. In our previous selector example, if a user can set the label `group: monitoring `on an arbitrary namespace, they can potentially send or receive traffic that they are not supposed to. If the `NetworkPolicy` in question has only a namespace selector, then that namespace label is sufficient to match the policy. If there is also a pod label in the `NetworkPolicy` selector, the user will need to set pod labels to match the policy selection. However, in a typical setup, the service owners will grant create/update permissions on pods in that service’s namespace (directly on the pod resource or indirectly via a resource like a deployment, which can define pods). + +A typical NetworkPolicy could look something like this: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: store-api + namespace: store +spec: + podSelector: + matchLabels: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + app: frontend + podSelector: + matchLabels: + app: frontend + ports: + - protocol: TCP + port: 8080 + egress: + - to: + - namespaceSelector: + matchLabels: + app: downstream-1 + podSelector: + matchLabels: + app: downstream-1 + - namespaceSelector: + matchLabels: + app: downstream-2 + podSelector: + matchLabels: + app: downstream-2 + ports: + - protocol: TCP + port: 8080 +``` + +In this example, all pods in our `store` namespace can receive connections only from pods labeled `app: frontend` in a namespace labeled `app: frontend`. Those pods can only create connections to pods in namespaces where the pod and namespace both have `app: downstream-1` or app: `downstream-2`. In each of these cases, only traffic to port 8080 is allowed. Finally, remember that this policy does not guarantee a matching policy for `downstream-1` or `downstream-2` (see the next example). Accepting these connections does not preclude other policies against pods in our namespace, adding additional exceptions: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: store-to-downstream-1 + namespace: downstream-1 +spec: + podSelector: + app: downstream-1 + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + app: store + ports: + - protocol: TCP + port: 8080 +``` + +Although they are a “stable” resource (i.e., part of the networking/v1 API), we believe `NetworkPolicy` objects are still an early version of network security in Kubernetes. The user experience of configuring `NetworkPolicy` objects is somewhat rough, and the default open behavior is highly undesirable. There is currently a working group to discuss the future of `NetworkPolicy` and what a v2 API would contain. + +CNIs and those who deploy them use labels and selectors to determine which pods are subject to network restrictions. As we have seen in many of the previous examples, they are an essential part of the Kubernetes API, and developers and administrators alike must have a thorough knowledge of how to use them. + +`NetworkPolicy` objects are an important tool in the cluster administrator’s toolbox. They are the only tool available for controlling internal cluster traffic, native to the Kubernetes API. We discuss service meshes, which will add further tools for admins to secure and control workloads, in “Service Meshes”. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Network\342\200\205Troubleshooting.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Network\342\200\205Troubleshooting.md" new file mode 100644 index 00000000..842fb5aa --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Network\342\200\205Troubleshooting.md" @@ -0,0 +1,469 @@ +--- +title: 'Network Troubleshooting' +lastUpdated: '2024-03-02' +--- + +Troubleshooting network0related issues with Linux is a complex topic and could easily fill its own book. we will introduce some key troubleshooting tools and the basics of their use. + +There is substantial overlap in the tools that we describe, so you may find learning about some tools (or tool features) redundant. Some are better suited to a given task than others (for example, multiple tools will catch TLS errors, but OpenSSL provides the richest debugging information). + +Exact tool use may come down to preference, familiarity, and availability. + + +|Case|Tools| +|-|-| +|Checking connectivity|`traceroute`, `ping`, `telnet`, `netcat`| +|Port scanning|`nmap`| +|Checking DNS records|`dig`, commands mentioned in “Checking Connectivity” +|Checking HTTP/1|`cURL`, `telnet`, `netcat`| +|Checking HTTPS|`OpenSSL`, `cURL`| +|Checking listening programs|`netstat`| + +Some networking tools that we describe likely won’t be preinstalled in your distro of choice, but all should be available through your distro’s package manager. We will sometimes use `# Truncated` in command output where we have omitted text to avoid examples becoming repetitive or overly long. + +## Security Warning + +Before we get into tooling details, we got to notice about some security warning. An attacker can utilize any tool listed here in order to explore and access additional systems. There are many strong opinions on this topic, but we consider it best practice to leave the fewest possible networking tools installed on a given machine. + +An attacker may still be able to download tools themselves (e.g., by downloading a binary from the internet) or use the standard package manager (if they have sufficient permission). In most cases, you are simply introducing some additional friction prior to exploring and exploiting. However, in some cases you can reduce an attacker’s capabilities by not preinstalling networking tools. + +Linux file permissions include something called the _setuid bit_ that is sometimes used by networking tools. If a file has the setuid bit set, executing said file causes the file to be executed as the user who owns the file, rather than the current user. You can observe this by looking for an `s` rather than an `x` in the permission readout of a file: + +```bash +$ ls -la /etc/passwd +-rwsr-xr-x 1 root root 68208 May 28 2020 /usr/bin/passwd +``` + +This allows programs to expose limited, privileged capabilities (for example, passwd uses this ability to allow a user to update their password, without allowing arbitrary writes to the password file). A number of networking tools (ping, nmap, etc.) may use the setuid bit on some systems to send raw packets, sniff packets, etc. + +If an attacker downloads their own copy of a tool and cannot gain root privileges, they will be able to do less with said tool than if it was installed by the system with the setuid bit set. + +## ping + +```bash +ping
+``` + +ping is a simple program that sends ICMP `ECHO_REQUEST` packets to networked devices. It is a common, simple way to test network connectivity from one host to another. + +ICMP is a layer 4 protocol, like TCP and UDP. Kubernetes services support TCP and UDP, **but not ICMP**. **This means that pings to a Kubernetes service will always fail**. Instead, you will need to use telnet or a higher-level tool such as cURL to check connectivity to a service. Individual pods may still be reachable by ping, depending on your network configuration. + +> Firewalls and routing software are aware of ICMP packets and can be configured to filter or route specific ICMP packets. It is common, but not guaranteed (or necessarily advisable), to have permissive rules for ICMP packets. Some network administrators, network software, or cloud providers will allow ICMP packets by default. + +--- + +By default, ping will send packets forever, and must be manually stopped (e.g., with Ctrl-C). -c will make ping perform a fixed number before shutting down. On shutdown, ping also prints a summary: + +```bash +~ ping -c 2 k8s.io +PING k8s.io (34.107.204.206): 56 data bytes +64 bytes from 34.107.204.206: icmp_seq=0 ttl=51 time=9.934 ms +64 bytes from 34.107.204.206: icmp_seq=1 ttl=51 time=7.762 ms + +--- k8s.io ping statistics --- +2 packets transmitted, 2 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 7.762/8.848/9.934/1.086 ms +``` + +Common Options +|Option|Description| +|-|-| +|`-c `|Sends the specified number of packets. Exits after the final packet is received or times out.| +|`-i `|Sets the wait interval between sending packets. Defaults to 1 second. Extremely low values are not recommended, as ping can flood the network.| +|`-o`|Exit after receiving 1 packet. Equivalent to -c 1.| +|`-S `|Uses the specified source address for the packet.| +|`-W `|Sets the wait interval to receive a packet. If ping receives the packet later than the wait time, it will still count toward the final summary.| + +## traceroute + +`traceroute` shows the network route taken from one host to another. This allows users to easily validate and debug the route taken (or where routing fails) from one machine to another. + +`traceroute` sends packets with specific IP time-to-live values. When a host receives a packet and decrements the TTL to 0, it sends a `TIME_EXCEEDED` packet and discards the original packet. The `TIME_EXCEEDED` response packet contains the source address of the machine where the packet timed out. By starting with a TTL of 1 and raising the TTL by 1 for each packet, `traceroute` is able to get a response from each host along the route to the destination address. + +traceroute displays hosts line by line, starting with the first external machine. Each line contains the hostname (if available), IP address, and response time: + +```bash +$traceroute k8s.io +traceroute to k8s.io (34.107.204.206), 64 hops max, 52 byte packets + 1 router (10.0.0.1) 8.061 ms 2.273 ms 1.576 ms + 2 192.168.1.254 (192.168.1.254) 2.037 ms 1.856 ms 1.835 ms + 3 adsl-71-145-208-1.dsl.austtx.sbcglobal.net (71.145.208.1) +4.675 ms 7.179 ms 9.930 ms + 4 * * * + 5 12.122.149.186 (12.122.149.186) 20.272 ms 8.142 ms 8.046 ms + 6 sffca22crs.ip.att.net (12.122.3.70) 14.715 ms 8.257 ms 12.038 ms + 7 12.122.163.61 (12.122.163.61) 5.057 ms 4.963 ms 5.004 ms + 8 12.255.10.236 (12.255.10.236) 5.560 ms + 12.255.10.238 (12.255.10.238) 6.396 ms + 12.255.10.236 (12.255.10.236) 5.729 ms + 9 * * * +10 206.204.107.34.bc.googleusercontent.com (34.107.204.206) +64.473 ms 10.008 ms 9.321 ms +If traceroute receives no response from a given hop before timing out, it prints a *. Some hosts may refuse to send a TIME_EXCEEDED packet, or a firewall along the way may prevent successful delivery. +``` + +Common options + + +|Option|Syntax|Description| +|-|-|-| +|First TTL|`-f `, `-M `|Set the starting IP TTL (default value: 1). Setting the TTL to n will cause traceroute to not report the first n-1 hosts en route to the destination.| +|Max TTL|`-m `|Set the maximum TTL, i.e., the maximum number of hosts that traceroute will attempt to route through.| +|Protocol|`-P `|Send packets of the specified protocol (TCP, UDP, ICMP, and sometimes other options). UDP is default.| +|Source address|`-s
`|Specify the source IP address of outgoing packets.| +|Wait|`-w `|Set the time to wait for a probe response.| + +## dig +`dig` is a DNS lookup tool. You can use it to make DNS queries from the command line and display the results. + +The general form of a `dig` command is `dig [options] `. By default, `dig` will display the CNAME, A, and AAAA records: + +```bash +$ dig kubernetes.io + +; <<>> DiG 9.10.6 <<>> kubernetes.io +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51818 +;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 1452 +;; QUESTION SECTION: +;kubernetes.io. IN A + +;; ANSWER SECTION: +kubernetes.io. 960 IN A 147.75.40.148 + +;; Query time: 12 msec +;; SERVER: 2600:1700:2800:7d4f:6238:e0ff:fe08:6a7b#53 +(2600:1700:2800:7d4f:6238:e0ff:fe08:6a7b) +;; WHEN: Mon Jul 06 00:10:35 PDT 2020 +;; MSG SIZE rcvd: 71 +``` + +To display a particular type of DNS record, run dig (or dig -t ). This is overwhelmingly the main use case for dig: + +```bash +$ dig kubernetes.io TXT + +; <<>> DiG 9.10.6 <<>> -t TXT kubernetes.io +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16443 +;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags:; udp: 512 +;; QUESTION SECTION: +;kubernetes.io. IN TXT + +;; ANSWER SECTION: +kubernetes.io. 3599 IN TXT +"v=spf1 include:_spf.google.com ~all" +kubernetes.io. 3599 IN TXT +"google-site-verification=oPORCoq9XU6CmaR7G_bV00CLmEz-wLGOL7SXpeEuTt8" + +;; Query time: 49 msec +;; SERVER: 2600:1700:2800:7d4f:6238:e0ff:fe08:6a7b#53 +(2600:1700:2800:7d4f:6238:e0ff:fe08:6a7b) +;; WHEN: Sat Aug 08 18:11:48 PDT 2020 +;; MSG SIZE rcvd: 171 +``` +Common options +|Option|Syntax|Description| +|IPv4|`-4`|Use IPv4 only.| +|IPv6|`-6`|Use IPv6 only.| +|Address|`-b
[#]`|Specify the address to make a DNS query to. Port can optionally be included, preceded by #.| +|Port|`-p `|Specify the port to query, in case DNS is exposed on a nonstandard port. The default is 53, the DNS standard.| +|Domain|`-q `|The domain name to query. The domain name is usually specified as a positional argument.| +|Record Type|`-t `|The DNS record type to query. The record type can alternatively be specified as a positional argument.| + +## telnet + +`telnet` is both a network protocol and a tool for using said protocol. `telnet` was once used for remote login, in a manner similar to SSH. SSH has become dominant due to having better security, but `telnet` is still extremely useful for debugging servers that use a text-based protocol. For example, with `telnet`, you can connect to an HTTP/1 server and manually make requests against it. + +The basic syntax of `telnet` is `telnet
`. This establishes a connection and provides an interactive command-line interface. Pressing Enter twice will send a command, which easily allows multiline commands to be written. Press Ctrl-J to exit the session: + +```bash +$ telnet kubernetes.io +Trying 147.75.40.148... +Connected to kubernetes.io. +Escape character is '^]'. +> HEAD / HTTP/1.1 +> Host: kubernetes.io +> +HTTP/1.1 301 Moved Permanently +Cache-Control: public, max-age=0, must-revalidate +Content-Length: 0 +Content-Type: text/plain +Date: Thu, 30 Jul 2020 01:23:53 GMT +Location: https://kubernetes.io/ +Age: 2 +Connection: keep-alive +Server: Netlify +X-NF-Request-ID: a48579f7-a045-4f13-af1a-eeaa69a81b2f-23395499 +``` + +To make full use of `telnet`, you will need to understand how the application protocol that you are using works. `telnet` is a classic tool to debug servers running HTTP, HTTPS, POP3, IMAP, and so on. + +## nmap +`nmap` is a port scanner, which allows you to explore and examine services on your network. + +The general syntax of `nmap` is `nmap [options] `, where target is a domain, IP address, or IP CIDR. nmap’s default options will give a fast and brief summary of open ports on a host: + +```bash +$ nmap 1.2.3.4 +Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-29 20:14 PDT +Nmap scan report for my-host (1.2.3.4) +Host is up (0.011s latency). +Not shown: 997 closed ports +PORT STATE SERVICE +22/tcp open ssh +3000/tcp open ppp +5432/tcp open postgresql + +Nmap done: 1 IP address (1 host up) scanned in 0.45 seconds +``` + +In the previous example, nmap detects three open ports and guesses which service is running on each port. + +> Because nmap can quickly show you which services are accessible from a remote machine, it can be a quick and easy way to spot services that should not be exposed. nmap is a favorite tool for attackers for this reason. + +`nmap` has a dizzying number of options, which change the scan behavior and level of detail provided. As with other commands, we will summarize some key options, but we highly recommend reading `nmap`’s help/man pages. + +**common options** + +|Option|Syntax|Description| +|-|-|-| +|Additional detection|`-A`|Enable OS detection, version detection, and more.| +|Decrease verbosity|`-d`|Decrease the command verbosity. Using multiple d’s (e.g., -dd) increases the effect.| +|Increase verbosity|`-v`|Increase the command verbosity. Using multiple v’s (e.g., -vv) increases the effect.| + +## netstat +netstat can display a wide range of information about a machine’s network stack and connections: + +```bash +$ netstat +Active internet connections (w/o servers) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 164 my-host:ssh laptop:50113 ESTABLISHED +tcp 0 0 my-host:50051 example-host:48760 ESTABLISHED +tcp6 0 0 2600:1700:2800:7d:54310 2600:1901:0:bae2::https TIME_WAIT +udp6 0 0 localhost:38125 localhost:38125 ESTABLISHED +Active UNIX domain sockets (w/o servers) +Proto RefCnt Flags Type State I-Node Path +unix 13 [ ] DGRAM 8451 /run/systemd/journal/dev-log +unix 2 [ ] DGRAM 8463 /run/systemd/journal/syslog +[Cut for brevity] +``` + +Invoking netstat with no additional arguments will display all connected sockets on the machine. In our example, we see three TCP sockets, one UDP socket, and a multitude of UNIX sockets. The output includes the address (IP address and port) on both sides of a connection. + +We can use the `-a` flag to show all connections or `-l` to show only listening connections: + +```bash +$ netstat -a +Active internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State +tcp 0 0 0.0.0.0:ssh 0.0.0.0:* LISTEN +tcp 0 0 0.0.0.0:postgresql 0.0.0.0:* LISTEN +tcp 0 172 my-host:ssh laptop:50113 ESTABLISHED +[Content cut] +``` + +A common use of netstat is to check which process is listening on a specific port. To do that, we run `sudo netstat -lp - l` for “listening” and p for “program.” sudo may be necessary for netstat to view all program information. The output for `-l` shows which address a service is listening on (e.g., `0.0.0.0` or `127.0.0.1`). + +We can use simple tools like grep to get a clear output from netstat when we are looking for a specific result: + +```bash +$ sudo netstat -lp | grep 3000 +tcp6 0 0 [::]:3000 [::]:* LISTEN 613/grafana-server +``` + +**common options** + +|Option|Syntax|Description| +|-|-|-| +|Show all sockets|`netstat -a`|Shows all sockets, not only open connections.| +|Show statistics|`netstat -s`|Shows networking statistics. By default, netstat shows stats from all protocols.| +|Show listening sockets|`netstat -l`|Shows sockets that are listening. This is an easy way to find running services.| +|TCP|`netstat -t`|The `-t` flag shows only TCP data. It can be used with other flags, e.g., `-lt` (show sockets listening with TCP).| +|UDP|`netstat -u`|The `-u` flag shows only UDP data. It can be used with other flags, e.g., `-lu` (show sockets listening with UDP).| + +## netcat + +netcat is a multipurpose tool for making connections, sending data, or listening on a socket. It can be helpful as a way to “manually” run a server or client to inspect what happens in greater detail. netcat is arguably similar to telnet in this regard, though netcat is capable of many more things. + +> nc is an alias for netcat on most systems. + +netcat can connect to a server when invoked as netcat
. netcat has an interactive stdin, which allows you to manually type data or pipe data to netcat. It’s very telnet-esque so far: + +```bash +$ echo -e "GET / HTTP/1.1\nHost: localhost\n" > cmd +$ nc localhost 80 < cmd +HTTP/1.1 302 Found +Cache-Control: no-cache +Content-Type: text/html; charset=utf-8 +[Content cut] +``` + +## Openssl + +The OpenSSL technology powers a substantial chunk of the world’s HTTPS connections. Most heavy lifting with OpenSSL is done with language bindings, but it also has a CLI for operational tasks and debugging. openssl can do things such as creating keys and certificates, signing certificates, and, most relevant to us, testing TLS/SSL connections. Many other tools, including ones outlined in this chapter, can test TLS/SSL connections. However, openssl stands out for its feature-richness and level of detail. + +Commands usually take the form openssl [sub-command] [arguments] [options]. openssl has a vast number of subcommands (for example, openssl rand allows you to generate pseudo random data). The list subcommand allows you to list capabilities, with some search options (e.g., openssl list `--commands` for commands). To learn more about individual sub commands, you can check openssl --help or its man page (man openssl- or just man ). + +`openssl s_client -connect` will connect to a server and display detailed information about the server’s certificate. Here is the default invocation: + + +```bash +openssl s_client -connect k8s.io:443 +CONNECTED(00000003) +depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3 +verify return:1 +depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 +verify return:1 +depth=0 CN = k8s.io +verify return:1 +--- +Certificate chain +0 s:CN = k8s.io +i:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 +1 s:C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 +i:O = Digital Signature Trust Co., CN = DST Root CA X3 +--- +Server certificate +-----BEGIN CERTIFICATE----- +[Content cut] +-----END CERTIFICATE----- +subject=CN = k8s.io + +issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 + +--- +No client certificate CA names sent +Peer signing digest: SHA256 +Peer signature type: RSA-PSS +Server Temp Key: X25519, 253 bits +--- +SSL handshake has read 3915 bytes and written 378 bytes +Verification: OK +--- +New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 +Server public key is 2048 bit +Secure Renegotiation IS NOT supported +Compression: NONE +Expansion: NONE +No ALPN negotiated +Early data was not sent +Verify return code: 0 (ok) +--- +``` + +If you are using a self-signed CA, you can use -CAfile to use that CA. This will allow you to establish and verify connections against a self-signed certificate. + + +```bash +openssl x509 -text -noout -in ca.crt + +``` + +## cURL +cURL is a data transfer tool that supports multiple protocols, notably HTTP and HTTPS. + +> wget is a similar tool to the command curl. Some distros or administrators may install it instead of curl. + +cURL commands are of the form curl [options] . cURL prints the URL’s contents and sometimes cURL-specific messages to stdout. The default behavior is to make an HTTP GET request: + +```bash +$ curl example.org + + + + Example Domain +# Truncated +``` + +By default, cURL does not follow redirects, such as HTTP 301s or protocol upgrades. The -L flag (or --location) will enable redirect following: + +```bash +$ curl kubernetes.io +Redirecting to https://kubernetes.io + +$ curl -L kubernetes.io + +# Truncated +``` + +Use the -X option to perform a specific HTTP verb; e.g., use curl -X +```bash +DELETE foo/bar to make a DELETE request. +``` +You can supply data (for a POST, PUT, etc.) in a few ways: + +```bash +URL encoded: -d "key1=value1&key2=value2" +JSON: -d '{"key1":"value1", "key2":"value2"}' +``` + +As a file in either format: `-d @data.txt` + +The -H option adds an explicit header, although basic headers such as Content-Type are added automatically: + +```bash +-H "Content-Type: application/x-www-form-urlencoded" +``` + +Here are some examples: + +```bash +$ curl -d "key1=value1" -X PUT localhost:8080 + +$ curl -H "X-App-Auth: xyz" -d "key1=value1&key2=value2" +-X POST https://localhost:8080/demo +TIP +cURL can be of some help when debugging TLS issues, but more specialized tools such as openssl may be more helpful. + +cURL can help diagnose TLS issues. Just like a reputable browser, cURL validates the certificate chain returned by HTTP sites and checks against the host’s CA certs: + +$ curl https://expired-tls-site +curl: (60) SSL certificate problem: certificate has expired +More details here: https://curl.haxx.se/docs/sslcerts.html + +curl failed to verify the legitimacy of the server and therefore could not +establish a secure connection to it. To learn more about this situation and +how to fix it, please visit the web page mentioned above. +``` + +Like many programs, cURL has a verbose flag, -v, which will print more information about the request and response. This is extremely valuable when debugging a layer 7 protocol such as HTTP: + +```bash +$ curl https://expired-tls-site -v +* Trying 1.2.3.4... +* TCP_NODELAY set +* Connected to expired-tls-site (1.2.3.4) port 443 (#0) +* ALPN, offering h2 +* ALPN, offering http/1.1 +* successfully set certificate verify locations: +* CAfile: /etc/ssl/cert.pem + CApath: none +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* TLSv1.2 (IN), TLS handshake, Server hello (2): +* TLSv1.2 (IN), TLS handshake, Certificate (11): +* TLSv1.2 (OUT), TLS alert, certificate expired (557): +* SSL certificate problem: certificate has expired +* Closing connection 0 +curl: (60) SSL certificate problem: certificate has expired + +More details here: https://curl.haxx.se/docs/sslcerts.html +# Truncated +``` + +cURL has many additional features that we have not covered, such as the ability to use timeouts, custom CA certs, custom DNS, and so on. + + + + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Node\342\200\205and\342\200\205Pod\342\200\205Network\342\200\205Layout.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Node\342\200\205and\342\200\205Pod\342\200\205Network\342\200\205Layout.md" new file mode 100644 index 00000000..b9dbce33 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/Node\342\200\205and\342\200\205Pod\342\200\205Network\342\200\205Layout.md" @@ -0,0 +1,79 @@ +--- +title: 'Node and Pod Network Layout' +lastUpdated: '2024-03-02' +--- + +- The cluster must have a group of IP addresses that is controls to assign an IP address to a pod, for example, `10.1.0.0./16`. Nodes and pods must have L3 conectivity in this IP address space. In L3, ths Internet layer, connectivity means packets with an IP address can route to a host with that IP address. + +- It is important to note that the ability to deliver packets is more fundamental than creating connections (an L4 concept). In L4, firewalls may choose to allow connections from host A to B but reject connections initiating from host B to A. + L4 connections from A to B, connections at L3, A to B and B to A, must be allowed. Without L3 connectivity, TCP handshackes would not be possible, as the SYN-ACK could not be delivered. + Generally, pods do not have MAC addresses. Therefore, L2 connectivity to pods is not possible. The CNI will determine this for pods. + +- There are no requirements in Kubernetes about L3 connectivity to the outside world. Although the majority of clusters have internet connectivity, some are more isolated for security reasons. + We will broadly discuss both ingress (traffic leaving a host or cluster) and egress (traffic entering a host or cluster). Our use of “ingress” here shouldn’t be confused with the Kubernetes ingress resource, which is a specific HTTP mechanism to route traffic to Kubernetes services. + There are broadly three approaches, with many variations, to structuring a cluster’s network: **isolated, flat, and island networks**. + +## Isolated Networks + + + +- In an isolated cluster network, nodes are routable on the broader network (i.e., hosts that are not part of the cluster can reach nodes in the cluster), but pods are not. + +- Because the cluster is not routable from the broader network, multiple clusters can even use the same IP address space. Note that the Kubernetes API server will need to be routable from the broader network, if external systems or users should be able to access the Kubernetes API. Many managed Kubernetes providers have a "secure cluster" option like this, where no direct traffic is possible between the cluster and the internet. + +- That isolation to the local cluster can be splendid for security if the cluster's worklaods permit/require such a setup, such as clusters for batch processing. However, it is not reasonable for all clusters. + The majority of clusters will need to reach and/or be reached by external systems, such as clusters that must support services that have dependencies on the broader internet. Load balancers and procies can be used to breach this barrier and allow internet traffic into or out of an isolated cluster. + +## Flat Networks + + + +- In a flat network, all pods have an IP address that is routable from the broader network. Barring firewall rules, any host on the network can route to any pod inside or outside the cluster. This configuration has numerous upsides around network simpliciry and performance. Pods can connect directly to arbitrary hosts in the network. + +- Note that no two node's pod CIDRs overlap between the two clusters in above Figure, and therefore no two pods will be assigned the same IP address. Because the broader network can route every pod IP address to that pod's node, any host on the network is reachable to and from any pod. + +- This openness allows any host with sufficient service discobery data to devide which pod will receibe those packets. A load balancer outside the cluster can load blance pods, such as a gRPC client in another cluster. + +- External pod traffic (and incoming pod traffic, when the connection's destination is a specific pod IP address) has low latency and low overhead. Any from of proxying or packet rewriting incurs a latency and processing cost, which is small but nontrivial (especially in an application architecture that involves many backend services, where each delay adds up). + +- Unfortunately, this model requires a large, contiguous IP address space for each cluster. Kubernetes requires a single CIDR for pod IP addresses (for each IP family). This model is achievable with a private subnet (such as 10.0.0.0/8 or 172.16.0.0/12); however, it is much harder ans more expensive to do with public IP addresses, especially IPv4 addresses. Administrators will need to use NAT to connect a cluster running in a private IP address space to the internet. + +- Aside from needing a large IP address space, administrators also need an easily programmable network. The CNI plugin must allocate pod IP addresses and ensure a route exists to a given pod’s node. + +- Flat networks, on a private subnet, are easy to achieve in a cloud provider environment. The vast majority of cloud provider networks will provide large private subnets and have an API (or even preexisting CNI plugins) for IP address allocation and route management. + +## Island Networks + + + +- Island cluster networks are, at a high level, a combination of isolated and flat networks. In an islan cluster setup, nodes have L3 connectivity with the broader network, but pods do not. Traffix to and from pods must pass through some from of proxy, through nodes. Most often, this is achieved by `iptables` source NAY on a pod's packets leaving the node. This setup, called _masquerading_, uses SNAT to rewrite packet sources from the pod's IP address to the node's IP address. In other words, packets appear to be "from" the node, rather than the pod. + +- Sharing an IP address while also using NAT hides the indivisual pod IP addresses. IP address-based fiewalling and recognition becones difficult across the cluster boundary. + +- Within a cluster, it is still apparent which IP address is which pod (and, therefore, which application). Pords in other clusters, or other hosts on the broader network, will no longer have that mapping. IP address-based firewalling and allow lists are not sufficient security on their own but are a valuable and somtimes required layer. + +# kube-controller-manager Configuration + +- Now let’s see how we configure any of these network layouts with the kube-controller-manager. Control plane refers to all the functions and processes that determine which path to use to send the packet or frame. Data plane refers to all the functions and processes that forward packets/frames from one interface to another based on control plane logic. + +- The `kube-controller-manager` runs most individual Kubernetes controllers in one binary and on process, where most Kubernetes login lives. At a high level, a controller in Kubernetes terms is software that watches resources and takes action to synchronize or enforce a specific state (either the desired state or reflecting the current state as a status.) Kubernetes has many controllers which generally "own" a specific objecy type or specific operation. + +- `kube-controller-manager` includes multiple controllers that manage the Kubernetes network stack. Notably, administrators set the cluster CIDR here. + +- `kube-controller-manager`, due to running a significant number of controllers, also has a substantial number of flags. below table highlights some notable network configuration flags. + +|Flag|Default|Description| +|-|-|-| +|`--allocate-node-cidrs`|true|Sets whether CIDRs for pods should be allocated and set on the cloud provider.| +`--CIDR-allocator-type string`|RangeAllocator|Type of CIDR allocator to use.| +|`--cluster-CIDR`||CIDR range from which to assign pod IP addresses. Requires `--allocate-node-cidrs` to be true. If kube-controller-manager has IPv6DualStack enabled, `--cluster-CIDR` accepts a comma-separated pair of IPv4 and IPv6 CIDRs.| +|`--configure-cloud-routes`|true|Sets whether CIDRs should be allocated by `allocate-node-cidrs` and configured on the cloud provider.| +|`--node-CIDR-mask-size`|24 for IPv4 clusters, 64 for IPv6 clusters|Mask size for the node CIDR in a cluster. Kubernetes will assign each node 2^(node-CIDR-mask-size) IP addresses.| +|`--node-CIDR-mask-size-ipv4`|24|Mask size for the node CIDR in a cluster. Use this flag in dual-stack clusters to allow both IPv4 and IPv6 settings.| +|`--node-CIDR-mask-size-ipv6`|64|Mask size for the node CIDR in a cluster. Use this flag in dual-stack clusters to allow both IPv4 and IPv6 settings.| +|`--service-cluster-ip-range`||CIDR range for services in the cluster to allocate service ClusterIPs. Requires `--allocate-node-cidrs` to be true. If kube-controller-manager has IPv6DualStack enabled, `--service-cluster-ip-range` accepts a comma-separated pair of IPv4 and IPv6 CIDRs.| + +--- +reference +- https://kubernetes.io/docs/concepts/cluster-administration/networking/ +- https://sookocheff.com/post/kubernetes/understanding-kubernetes-networking-model/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/eBPF.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/eBPF.md" new file mode 100644 index 00000000..e4f2bc39 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Network/eBPF.md" @@ -0,0 +1,74 @@ +--- +title: 'eBPF' +lastUpdated: '2024-03-02' +--- + +eBPF is a programming system that allows special sandboxed programs to run in the kernel **without passing back and forth between kernel and user space**, like we saw with Netfilter and iptables. + +Before eBPF, there was the Berkeley Packet Filter (BPF). BPF is a technology used in the kernel, among other things, to analyze network traffic. BPF supports filtering packets, which allows a userspace process to supply a filter that specifies which packets it wants to inspect. One of BPF’s use cases is `tcpdump`, it compiles it as a BPF program and passes it to BPF. The techniques in BPF have been extended to other processes and kernel operations. + +**tcpdump example** +```bash +$ sudo tcpdump -n -i any +tcpdump: data link type LINUX_SLL2 +tcpdump: verbose output suppressed, use -v[v]... for full protocol decode +listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes +21:45:33.093065 ens5 Out IP 172.31.43.75.2222 > 14.50.190.128.63209: Flags [P.], seq 2457618334:2457618410, ack 3398066884, win 463, options [nop,nop,TS val 1101586736 ecr 1365458938], length 76 +21:45:33.094530 ens5 Out IP 172.31.43.75.2222 > 14.50.190.128.63209: Flags [P.], seq 76:280, ack 1, win 463, options [nop,nop,TS val 1101586738 ecr 1365458938], length 204 +21:45:33.103287 ens5 In IP 14.50.190.128.63209 > 172.31.43.75.2222: Flags [.], ack 76, win 2046, options [nop,nop,TS val 1365458993 ecr 1101586736], length 0 +21:45:33.104358 ens5 In IP 14.50.190.128.63209 > 172.31.43.75.2222: Flags [.], ack 280, win 2044, options [nop,nop,TS val 1365458994 ecr 1101586738], length 0 +... +``` + +An eBPF program has **direct access to syscalls**. eBPF programs can directly watch and block syscalls, without the usual approach of adding kernel hooks to a userspace program. Because of its performance characteristics, it is well suited for writing networking software. + +--- + +In addition to socket filtering, other supported attach points in the kernel are as follows: + +- **Kprobes** + - Dynamic kernel tracing of internal kernel components. +- **Uprobes** + - User-space tracing. +- **Tracepoints** + - Kernel static tracing. These are programed into the kernel by developers and are more stable as compared to kprobes, which may change between kernel versions. +- **perf_events** + - Timed sampling of data and events. +- **XDP** + - Specialized eBPF programs that can go lower than kernel space to access driver space to act directly on packets. + +Let’s return to tcpdump as an example. Below figure shows a simplified rendition of tcpdump’s interactions with eBPF. + + + +Suppose we run `tcpdump -i any`. + +The string is compiled by `pcap_compile` into a BPF program. The kernel will then use this BPF program to filter all packets that go through all the network devices we specified, any with the `-I` in our case. + +It will make this data abailable to `tcpdump` via a map. Maps wre a data structure consisting of key-value pairs used by the BPD programs to exchange data. + +There are many reasons to use eBPF with Kubernetes: + +- **Performance (hashing table versus `iptables` list)** + - For every service added to Kubernetes, the list of `iptables` rules that have to be traversed grows exponentially. Because of the lack of incremental updates, the entire list of rules has to be replaced each time a new rule is added. This lead to a total duration of 5 hours to install the 160,000 uptables rules representin 20,000 Kubernetes services. + +- **Tracing** + - Using BPF, we can gather pod and container-level network statistics. The BPF socket filter is nothing new, but the BPF socket filter per cgroup is. Introduced in Linux 4.10, `cgroup-bpf` allows attaching eBPF programs to cgroups. Once sttached, the program is executed for all packets entering or exiting any process in the cgroup. + +- **Auditing `kubectl exec` with eBPF** + - With eBPF, you can attach a program that will record any commands executed in the `kubectl exec` session and pass those commands to a user-space program that logs those events. + +- **Security** + - Seccomp + - Secured computing that restricts what syscalls are alloed. Seccomp filters can be written in eBPF. + - Falco + - Open source container-native runtime security that uses eBPF + +The most common use of eBPF in Kubernetes is **Cilium, CNI and service implementation**. Cilium replaces `kube-proxy`, which writes `iptables` rules to map a service’s IP address to its corresponding pods. + +Through eBPF, Cilium can intercept and route all packets directly in the kernel, which is faster and allows for application-level (layer 7) load balancing. We will cover kube-proxy in Chapter 4. + +--- +reference +- http://ebpf.io/ +- https://cilium.io/blog/2020/11/10/ebpf-future-of-networking/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Dry\342\200\205run.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Dry\342\200\205run.md" new file mode 100644 index 00000000..ab1e50dc --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Dry\342\200\205run.md" @@ -0,0 +1,178 @@ +--- +title: 'Dry run' +lastUpdated: '2024-03-02' +--- + +A dry run (or practice run) is a software testing process used to make sure that a system works correctly and will not result in severe failure. + +Istio has a experimental annotation `istio.io/dry-run` to dry-run the policy without actually enforcing it. + +The dry-run annotation allows you to better understand the effect of an authorization policy before applying it to the production traffic. This helps to reduce the risk of breaking the production traffic caused by an incorrect authorization policy. + +### Before you begin + +Before you begin this task, do the following: + +- Deploy Zipkin for checking dry-run tracing results. Follow the Zipkin task to install Zipkin in the cluster. + +- Deploy Prometheus for checking dry-run metric results. Follow the Prometheus task to install the Prometheus in the cluster. + +- Deploy test workloads: + +This task uses two workloads, `httpbin` and `sleep`, both deployed in namespace foo. Both workloads run with an Envoy proxy sidecar. Create the foo namespace and deploy the workloads with the following command: + +```bash +$ kubectl create ns foo +$ kubectl label ns foo istio-injection=enabled +$ kubectl apply -f samples/httpbin/httpbin.yaml -n foo +$ kubectl apply -f samples/sleep/sleep.yaml -n foo +``` + +- Enable proxy debug level log for checking dry-run logging results: + +```bash +$ istioctl proxy-config log deploy/httpbin.foo --level "rbac:debug" | grep rbac +rbac: debug +``` + +- Verify that `sleep` can access `httpbin` with the following command: + +```bash +$ kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}\n" +200 +``` + +If you don’t see the expected output as you follow the task, retry after a few seconds. Caching and propagation overhead can cause some delay. + +## Create dry-run policy + +1. Create an authorization policy with dry-run annotation `"istio.io/dry-run": "true"` with the following command: + +```bash +$ kubectl apply -n foo -f - < + +## Components + +### Envoy + +Istio uses an extended version of the Envoy proxy. Envoy is a high-performance proxy developed in C++ to mediate all inbound and outbound traffic for all services in the service mesh. Envoy proxies are the only Istio components that interact with data plane traffic. + +Envoy proxies are deployed as sidecars to services, logically augmenting the services with Envoy’s many built-in features, for example: + +- Dynamic service discovery +- Load balancing +- TLS termination +- HTTP/2 and gRPC proxies +- Circuit breakers +- Health checks +- Staged rollouts with %-based traffic split +- Fault injection +- Rich metrics + +This sidecar deployment allows Istio to enforce policy decisions and extract rich telemetry which can be sent to monitoring systems to provide information about the behavior of the entire mesh. + +The sidecar proxy model also allows you to add Istio capabilities to an existing deployment without requiring you to rearchitect or rewrite code. + +Some of the Istio features and tasks enabled by Envoy proxies include: + +- Traffic control features: enforce fine-grained traffic control with rich routing rules for HTTP, gRPC, WebSocket, and TCP traffic. + +- Network resiliency features: setup retries, failovers, circuit breakers, and fault injection. + +- Security and authentication features: enforce security policies and enforce access control and rate limiting defined through the configuration API. + +- Pluggable extensions model based on WebAssembly that allows for custom policy enforcement and telemetry generation for mesh traffic. + +### Istiod + +Istiod provides service discovery, configuration and certificate management. + +Istiod converts high level routing rules that control traffic behavior into Envoy-specific configurations, and propagates them to the sidecars at runtime. Pilot abstracts platform-specific service discovery mechanisms and synthesizes them into a standard format that any sidecar conforming with the Envoy API can consume. + +Istio can support discovery for multiple environments such as Kubernetes or VMs. + +You can use Istio’s Traffic Management API to instruct Istiod to refine the Envoy configuration to exercise more granular control over the traffic in your service mesh. + +Istiod security enables strong service-to-service and end-user authentication with built-in identity and credential management. You can use Istio to upgrade unencrypted traffic in the service mesh. Using Istio, operators can enforce policies based on service identity rather than on relatively unstable layer 3 or layer 4 network identifiers. Additionally, you can use Istio’s authorization feature to control who can access your services. + +Istiod acts as a Certificate Authority (CA) and generates certificates to allow secure mTLS communication in the data plane. + +--- +reference +- https://istio.io/latest/docs/ops/deployment/architecture/ + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Istio\342\200\205Configuration\342\200\205Profiles.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Istio\342\200\205Configuration\342\200\205Profiles.md" new file mode 100644 index 00000000..e8d9eb51 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Istio\342\200\205Configuration\342\200\205Profiles.md" @@ -0,0 +1,20 @@ +--- +title: 'Istio Configuration Profiles' +lastUpdated: '2024-03-02' +--- + +- **default**: enables components according to the default settings of the IstioOperator API. This profile is recommended for production deployments and for primary clusters in a multicluster mesh. You can display the default settings by running the istioctl profile dump command. + +- **demo**: configuration designed to showcase Istio functionality with modest resource requirements. It is suitable to run the Bookinfo application and associated tasks. This is the configuration that is installed with the quick start instructions. + > This profile enables high levels of tracing and access logging so it is not suitable for performance tests. + +- **minimal**: same as the default profile, but only the control plane components are installed. This allows you to configure the control plane and data plane components (e.g., gateways) using separate profiles. + +- **remote**: used for configuring a remote cluster that is managed by an external control plane or by a control plane in a primary cluster of a multicluster mesh. + +- **empty**: deploys nothing. This can be useful as a base profile for custom configuration. + +- **preview**: the preview profile contains features that are experimental. This is intended to explore new features coming to Istio. Stability, security, and performance are not guaranteed - use at your own risk. + +image + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Istio\342\200\205RBAC.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Istio\342\200\205RBAC.md" new file mode 100644 index 00000000..1240905a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Istio\342\200\205RBAC.md" @@ -0,0 +1,251 @@ +--- +title: 'Istio RBAC' +lastUpdated: '2024-03-02' +--- + +Istio RBAC를 통해 네임스페이스, 서비스, HTTP 메소드 수준의 권한 제어를 실습 해보자. + +### 준비작업 + +1. k8s, helm 설치 +2. Istio 초기화 (namespace, CRDs) + +```bash +$ wget https://github.com/istio/istio/releases/download/1.8.2/istio-1.8.2-osx.tar.gz +$ tar -vxzf istio-1.8.2-osx.tar.gz +$ cd istio-1.8.2 +$ kubectl create namespace istio-system +$ helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f - +``` + +3. Istio ingresgateway는 노드 포트로 설치 + +```bash +$ helm template install/kubernetes/helm/istio --name istio --namespace istio-system \ +--set gateways.istio-ingressgateway.type=NodePort \ +| kubectl apply -f - +``` + +4. Istio pod 정상상태 확인 및 대기 + +```bash +$ kubectl get pod -n istio-system +``` + +5. bookinfo 설치 + +```bash +$ kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml) +$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml +$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml +``` + +6. `/productpage` 정상 동작여부 확인 + +```bash +$ INGRESS_URL=http://$(minikube ip -p istio-security):$(k get svc/istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')/productpage +$ curl -I $INGRESS_URL +``` + +7. productpage 와 reviews 의 서비스를 위한 ServiceAccount 생성 + +```bash +$ kubectl apply -f <(istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo-add-serviceaccount.yaml) +``` + +8. 브라우저에서 `/productpage` URL 접속해보기 + +```bash +echo $INGRESS_URL +``` + +### Istio authorization 활성화 + +- ClusterRbacConfig를 구성하여 네임스페이스 “default”에 대한 Istio authorization을 활성화한다. + +```yaml +$ kubectl apply -f - < + +### 코드 예시 + +Authorization policy는 selector, action, rule 목록 이렇게 총 3개 부분으로 구성되어있다. + +- `selector`: policy의 타겟 지정 +- `action`: `ALLOW`, `DENY`, `CUSTOM` 중 하나의 action +- `rules`: 적용 규칙 + - `from`: 요청하는 주체에 대한 규칙 + - `to`: 요청을 처리하는 주체에 대한 규칙 + - `when`: 적용할 경우에 대한 condition 정의 + +```yaml +apiVersion: security.istio.io/v1 +kind: AuthorizationPolicy +metadata: + name: httpbin + namespace: foo +spec: + selector: + matchLabels: + app: httpbin + version: v1 + action: ALLOW + rules: + - from: + - source: + principals: ["cluster.local/ns/default/sa/sleep"] + - source: + namespaces: ["dev"] + to: + - operation: + methods: ["GET"] + when: + - key: request.auth.claims[iss] + values: ["https://accounts.google.com"] +``` + + +--- +참고 +- https://rafabene.com/istio-tutorial/istio-tutorial/1.2.x/8rbac.html +- https://istio.io/latest/docs/concepts/security/#authorization \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Resource\342\200\205Annotations\342\200\205&\342\200\205Labels.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Resource\342\200\205Annotations\342\200\205&\342\200\205Labels.md" new file mode 100644 index 00000000..73111ac3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/Resource\342\200\205Annotations\342\200\205&\342\200\205Labels.md" @@ -0,0 +1,75 @@ +--- +title: 'Resource Annotations & Labels' +lastUpdated: '2024-03-02' +--- + +The various resource annotations that Istio supports to control its behavior. + +> https://istio.io/latest/docs/reference/config/annotations/ + +|Annotation|Name|Resource Types|Description| +|-|-|-|-| +|`galley.istio.io/analyze-suppres`s|[Any]|A comma separated list of configuration analysis message codes to suppress when Istio analyzers are run. For example, to suppress reporting of IST0103 |(PodMissingProxy) and IST0108 (UnknownAnnotation) on a resource, apply the annotation 'galley.istio.io/analyze-suppress=IST0108,IST0103'. If the value is '*', then all configuration analysis messages are suppressed.| +|`inject.istio.io/templates`|[Pod]|The name of the inject template(s) to use, as a comma separate list. See https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/#custom-templates-experimental for more information| +|`install.operator.istio.io/chart-owner`|[Any]|Represents the name of the chart used to create this resource.| +|`install.operator.istio.io/owner-generation`|[Any]|Represents the generation to which the resource was last reconciled.| +|`install.operator.istio.io/version`|[Any]|Represents the Istio version associated with the resource| +|`istio.io/dry-run`|[AuthorizationPolicy]|Specifies whether or not the given resource is in dry-run mode. See https://istio.io/latest/docs/tasks/security/authorization/authz-dry-run/ for more information.| +|`istio.io/rev`|[Pod]|Specifies a control plane revision to which a given proxy is connected. This annotation is added automatically, not set by a user. In contrary to the label istio.io/rev, it represents the actual revision, not the requested revision.| +|`kubernetes.io/ingress.class`|[Ingress]|Annotation on an Ingress resources denoting the class of controllers responsible for it.| +|`networking.istio.io/exportTo`|[Service]|Specifies the namespaces to which this service should be exported to. A value of '*' indicates it is reachable within the mesh '.' indicates it is reachable within its namespace.| +|`prometheus.istio.io/merge-metrics`|[Pod]|Specifies if application Prometheus metric will be merged with Envoy metrics for this workload.| +|`proxy.istio.io/config`|[Pod]|Overrides for the proxy configuration for this specific proxy. Available options can be found at https://istio.io/docs/reference/config/istio.mesh.v1alpha1/#ProxyConfig.| +|`readiness.status.sidecar.istio.io/applicationPorts`|[Pod]|Specifies the list of ports exposed by the application container. Used by the Envoy sidecar readiness probe to determine that Envoy is configured and ready to receive traffic.| +|`readiness.status.sidecar.istio.io/failureThreshold`|[Pod]|Specifies the failure threshold for the Envoy sidecar readiness probe.| +|`readiness.status.sidecar.istio.io/initialDelaySeconds`|[Pod]|Specifies the initial delay (in seconds) for the Envoy sidecar readiness probe.| +|`readiness.status.sidecar.istio.io/periodSeconds`|[Pod]|Specifies the period (in seconds) for the Envoy sidecar readiness probe.| +|`sidecar.istio.io/agentLogLevel`|[Pod]|Specifies the log output level for pilot-agent.| +|`sidecar.istio.io/bootstrapOverride`|[Pod]|Specifies an alternative Envoy bootstrap configuration file.| +|`sidecar.istio.io/componentLogLevel`|[Pod]|Specifies the component log level for Envoy.| +|`sidecar.istio.io/controlPlaneAuthPolicy`(Deprecated)|[Pod]|Specifies the auth policy used by the Istio control plane. If NONE, traffic will not be encrypted. If MUTUAL_TLS, traffic between Envoy sidecar will be wrapped into mutual TLS connections.| +|`sidecar.istio.io/discoveryAddress`(Deprecated)|[Pod]|Specifies the XDS discovery address to be used by the Envoy sidecar.| +|`sidecar.istio.io/enableCoreDump`|[Pod]|Specifies whether or not an Envoy sidecar should enable core dump.| +|`sidecar.istio.io/extraStatTags`|[Pod]|An additional list of tags to extract from the in-proxy Istio telemetry. each additional tag needs to be present in this list.| +|`sidecar.istio.io/inject`(Deprecated)|[Pod]|Specifies whether or not an Envoy sidecar should be automatically injected into the workload. Deprecated in favor of `sidecar.istio.io/inject` label.| +|`sidecar.istio.io/interceptionMode`|[Pod]|Specifies the mode used to redirect inbound connections to Envoy (REDIRECT or TPROXY).| +|`sidecar.istio.io/logLevel`|[Pod]|Specifies the log level for Envoy.| +|`sidecar.istio.io/proxyCPU`|[Pod]|Specifies the requested CPU setting for the Envoy sidecar.| +|`sidecar.istio.io/proxyCPULimit`|[Pod]|Specifies the CPU limit for the Envoy sidecar.| +|`sidecar.istio.io/proxyImage`|[Pod]|Specifies the Docker image to be used by the Envoy sidecar.| +|`sidecar.istio.io/proxyImageType`|[Pod]|Specifies the Docker image type to be used by the Envoy sidecar. Istio publishes debug and distroless image types for every release tag.| +|`sidecar.istio.io/proxyMemory`|[Pod]|Specifies the requested memory setting for the Envoy sidecar.| +|`sidecar.istio.io/proxyMemoryLimit`|[Pod]|Specifies the memory limit for the Envoy sidecar.| +|`sidecar.istio.io/rewriteAppHTTPProbers`|[Pod]|Rewrite HTTP readiness and liveness probes to be redirected to the Envoy sidecar.| +|`sidecar.istio.io/statsInclusionPrefixes`(Deprecated)|[Pod]|Specifies the comma separated list of prefixes of the stats to be emitted by Envoy.| +|`sidecar.istio.io/statsInclusionRegexps`(Deprecated)|[Pod]|Specifies the comma separated list of regexes the stats should match to be emitted by Envoy.| +|`sidecar.istio.io/statsInclusionSuffixes`(Deprecated)|[Pod]|Specifies the comma separated list of suffixes of the stats to be emitted by Envoy.| +|`sidecar.istio.io/status`|[Pod]|Generated by Envoy sidecar injection that indicates the status of the operation. Includes a version hash of the executed template, as well as names of injected resources.| +|`sidecar.istio.io/userVolume`|[Pod]|Specifies one or more user volumes (as a JSON array) to be added to the Envoy sidecar.| +|`sidecar.istio.io/userVolumeMount`|[Pod]|Specifies one or more user volume mounts (as a JSON array) to be added to the Envoy sidecar.| +|status.sidecar.istio.io/port|[Pod]|Specifies the HTTP status Port for the Envoy sidecar. If zero, the sidecar will not provide status.| +|`topology.istio.io/controlPlaneClusters`|[Namespace]|A comma-separated list of clusters (or * for any) running istiod that should attempt leader election for a remote cluster thats system namespace includes this annotation. Istiod will not attempt to lead unannotated remote clusters.| +|`traffic.istio.io/nodeSelector`|[Service]|This annotation is a set of node-labels (key1=value,key2=value). If the annotated Service is of type NodePort and is a multi-network gateway (see topology.istio.io/network), the addresses for selected nodes will be used for cross-network communication.| +|`traffic.sidecar.istio.io/excludeInboundPorts`|[Pod]|A comma separated list of inbound ports to be excluded from redirection to Envoy. Only applies when all inbound traffic (i.e. '*') is being redirected.| +|`traffic.sidecar.istio.io/excludeInterfaces`|[Pod]|A comma separated list of interfaces to be excluded from Istio traffic capture| +|`traffic.sidecar.istio.io/excludeOutboundIPRanges`|[Pod]|A comma separated list of IP ranges in CIDR form to be excluded from redirection. Only applies when all outbound traffic (i.e. '*') is being redirected.| +|`traffic.sidecar.istio.io/excludeOutboundPorts`|[Pod]|A comma separated list of outbound ports to be excluded from redirection to Envoy.| +|`traffic.sidecar.istio.io/includeInboundPorts`|[Pod]|A comma separated list of inbound ports for which traffic is to be redirected to Envoy. The wildcard character '*' can be used to configure redirection for all ports. An empty list will disable all inbound redirection.| +|`traffic.sidecar.istio.io/includeOutboundIPRanges`|[Pod]|A comma separated list of IP ranges in CIDR form to redirect to Envoy (optional). The wildcard character '*' can be used to redirect all outbound traffic. An empty list will disable all outbound redirection.| +|`traffic.sidecar.istio.io/includeOutboundPorts`|[Pod]|A comma separated list of outbound ports for which traffic is to be redirected to Envoy, regardless of the destination IP.| +|`traffic.sidecar.istio.io/kubevirtInterfaces`|[Pod]|A comma separated list of virtual interfaces whose inbound traffic (from VM) will be treated as outbound.| + +# Resource Labels + +The various resource labels that Istio supports to control its behavior. + +|Label Name|Resource Types|Description| +|-|-|-| +|`istio.io/rev`|[Namespace]|Istio control plane revision associated with the resource; e.g. `canary`| +|`networking.istio.io/gatewayPort`|[Service]|IstioGatewayPortLabel overrides the default 15443 value to use for a multi-network gateway's port| +|service.istio.io/canonical-name|[Pod]|The name of the canonical service a workload belongs to| +|`service.istio.io/canonical-revision`|[Pod]|The name of a revision within a canonical service that the workload belongs to| +|`sidecar.istio.io/inject`|[Pod]|Specifies whether or not an Envoy sidecar should be automatically injected into the workload. +|`topology.istio.io/cluster`|[Pod]|This label is applied to a workload internally that identifies the Kubernetes cluster containing the workload. The cluster ID is specified during Istio installation for each cluster via `values.global.multiCluster.clusterName`. It should be noted that this is only used internally within Istio and is not an actual label on workload pods. If a pod contains this label, it will be overridden by Istio internally with the cluster ID specified during Istio installation. This label provides a way to select workloads by cluster when using DestinationRules. For example, a service owner could create a DestinationRule containing a subset per cluster and then use these subsets to control traffic flow to each cluster independently| +|`topology.istio.io/network`|[Namespace Pod Service]|A label used to identify the network for one or more pods. This is used internally by Istio to group pods resident in the same L3 domain/network.
Istio assumes that pods in the same network are directly reachable from one another. When pods are in different networks, an Istio Gateway (e.g. east-west gateway) is typically used to establish connectivity (with AUTO_PASSTHROUGH mode). This label can be applied to the following resources to help automate Istio's multi-network configuration.
* **Istio System Namespace**: Applying this label to the system namespace establishes a default network for pods managed by the control plane. This is typically configured during control plane installation using an admin-specified value.
* **Pod**: Applying this label to a pod allows overriding the default network on a per-pod basis. This is typically applied to the pod via webhook injection, but can also be manually specified on the pod by the service owner. The Istio installation in each cluster configures webhook injection using an admin-specified value.
* **Gateway Service**: Applying this label to the Service for an Istio Gateway, indicates that Istio should use this service as the gateway for the network, when configuring cross-network traffic. Istio will configure pods residing outside of the network to access the Gateway service via `spec.externalIPs`, `status.loadBalancer.ingress[].ip`, or in the case of a NodePort service, the Node's address. The label is configured when installing the gateway (e.g. east-west gateway) and should match either the default network for the control plane (as specified by the Istio System Namespace label) or the network of the targeted pods.| +|`topology.istio.io/subzone`|[Node]|User-provided node label for identifying the locality subzone of a workload. This allows admins to specify a more granular level of locality than what is offered by default with Kubernetes regions and zones.| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/ServiceEntry.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/ServiceEntry.md" new file mode 100644 index 00000000..b61b2759 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/ServiceEntry.md" @@ -0,0 +1,34 @@ +--- +title: 'ServiceEntry' +lastUpdated: '2024-03-02' +--- + +Since not all services live in the sevice mesh, we need a way for **services inside the mesh to communicate with those outdise the mesh**. Those could be existing HTTP services or, more likely, infrastructure services like databases or caches. We can still implement sophisticated routing for services that reside outside Istio, but first we have to introduce the concept of a `ServiceEntry`. + +Istio builds up an **internal service registry** of all the services that are known by the mesh and that can be accessed within the mesh. You can think of this registry as the canoncal representation of a service-discovery registry that services within the mesh can use to find other services. + +Istio builds this internal registry by making assumptions about the platforkm on which the control plane is deployed. Istio uses the default Kubernetes API to build its catalog of [services](https://kubernetes.io/docs/concepts/services-networking/service). For our services within the mesh to communicate with those outside the mesh, we need to let Istio's service-discovery registry know about this external service. + +We can specify `ServiceEntry` resources that augment and insert external services into the Istio service registry. + +### example + +The Istio ServiceEntry resource encapsulates registry metadata that we can use to insert an entry into Istio’s service registry. Here’s an example: + +```bash +apiVersion: networking.istio.io/v1alpha3 +kind: ServiceEntry +metadata: + name: jsonplaceholder +spec: + hosts: + - jsonplaceholder.typicode.com + ports: + - number: 80 + name: http + protocol: HTTP + resolution: DNS + location: MESH_EXTERNAL +``` + +This `ServiceEntry` resource inserts an entry into Istio’s service registry, which makes explicit that clients within the mesh are allowed to call JSON Placeholder using host `jsonplaceholder.typicode.com`. The JSON Placeholder service exposes a sample REST API that we can use to simulate talking with services that live outside our cluster. Before we create this service entry, let’s install a service that talks to the `jsonplaceholder.typicode.com` REST API and observe that Istio indeed blocks any outbound traffic. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/gateway\342\200\205log\342\200\205debug\342\200\205\355\225\230\353\212\224\342\200\205\353\262\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/gateway\342\200\205log\342\200\205debug\342\200\205\355\225\230\353\212\224\342\200\205\353\262\225.md" new file mode 100644 index 00000000..f6cb6684 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/Service\342\200\205Mesh/istio/gateway\342\200\205log\342\200\205debug\342\200\205\355\225\230\353\212\224\342\200\205\353\262\225.md" @@ -0,0 +1,107 @@ +--- +title: 'gateway log debug 하는 법' +lastUpdated: '2024-03-02' +--- + +Debugging Istio Envoy filters can be challenging but there are several techniques and tools that can help you troubleshoot issues. Here's a step-by-step guide to debug Istio Envoy filters: + +### 1. Enable Debug Logs: +By default, Istio's Envoy sidecar logs only contain essential information. To get more detailed logs, you can enable debug logging by changing the log level. To do this, you'll need to modify the istio-sidecar-injector ConfigMap. Add the following entry to the config section: + +```yaml +data: + log_level: "debug" +``` + +Then, restart the sidecar injection webhook and update your deployments to take effect. + +### 2. Check Sidecar Logs: + +After enabling debug logs, check the logs of the Envoy sidecar for the relevant service. You can use kubectl logs to view the sidecar logs. For example: + +```bash +kubectl logs -c istio-proxy -n +``` + +This will give you detailed logs of the Envoy proxy, including the execution of your custom Envoy filters. + +Istio Proxy Dashboard: Istio provides a dashboard to monitor the Envoy proxy. You can access it by port-forwarding to the Istio proxy pod and then navigating to http://localhost:15000/ in your browser. The dashboard provides detailed information about the proxy configuration, listeners, filters, etc. + +```bash +kubectl port-forward -n 15000:15000 +``` + +Envoy Admin Interface: Envoy itself provides an admin interface that you can use to inspect the configuration and behavior of the proxy. By default, the admin interface is disabled, but you can enable it in the EnvoyFilter's config section: + +```yaml +configPatches: + - applyTo: NETWORK_FILTER + match: + context: SIDECAR_INBOUND + listener: + filterChain: + filter: + name: "envoy.http_connection_manager" + patch: + operation: MERGE + value: + config: + admin: + access_log_path: "/dev/null" # Disable access logs (optional) + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 +``` + +After applying the EnvoyFilter, you can port-forward to the Envoy admin interface: + +```bash +kubectl port-forward -n 8001:8001 +``` + +Then, you can access the admin interface by navigating to http://localhost:8001/ in your browser. + +### 3. Istio Proxy Logs in Debug Mode: + +If you want to get even more detailed logs from the Istio proxy, you can set the log level to debug by modifying the Istio proxy pod directly: + +```bash +kubectl edit pod -n +``` + +Find the line that starts with args and append `--log_output_level` default:debug to enable debug logging. Save and exit the editor. The Istio proxy will now produce more verbose logs. + +### 4. Check the EnvoyFilter Configuration: + +Double-check your EnvoyFilter configuration for any mistakes or typos. Incorrectly defined filters can cause issues, so make sure you review your configuration carefully. + +### 5. Use istioctl to Analyze the Configuration: + +Istio provides the istioctl command-line tool, which can be used to analyze and debug your Istio configuration. Use istioctl analyze to check for any issues in your configuration: + +```bash +istioctl analyze +``` + +This command will check for potential problems in your configuration, including EnvoyFilter related issues. + +Remember, debugging Envoy filters can be complex, so having a good understanding of the Istio documentation and Envoy's documentation will be beneficial. Additionally, you can use various debugging tools, such as Wireshark or tcpdump, to capture and analyze network traffic between services if necessary. + +--- + +```bash +kubectl port-forward deployment/istio-ingressgateway -n istio-system 15000 +curl -X POST "localhost:15000/logging?wasm=debug" +curl -X POST "localhost:15090/logging?filter=debug" +curl -X POST "localhost:15000/logging?main=debug" +curl -X POST "localhost:15000/logging?config=debug" +curl -X POST "localhost:15000/logging?client=debug" +kubectl logs -l app=istio-ingressgateway -n istio-system +``` + +curl -X POST "localhost:150090/logging?wasm=warning" +curl -X POST "localhost:15000/logging?filter=warning" +curl -X POST "localhost:15000/logging?main=warning" +curl -X POST "localhost:15000/logging?config=warning" +curl -X POST "localhost:15000/logging?client=warning" \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/ApplicationSets.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/ApplicationSets.md" new file mode 100644 index 00000000..309d75fb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/ApplicationSets.md" @@ -0,0 +1,369 @@ +--- +title: 'ApplicationSets' +lastUpdated: '2024-03-02' +--- + +Applications deployed and managed using the GitOps philosophy are often made of many files. There’s Kubernetes manifests for Deployments, Services, Secrets, ConfigMaps, and many more which all go into a Git repository to be revision controlled. Argo CD, the engine behind the OpenShift GitOps Operator, then uses that Git repository as the source for the application. So, how do we define all that to Argo CD? Using the Application CRD. + +An Argo CD Application is a representation of a collection of Kubernetes-native manifests (usually YAML), that makes up all the pieces of your application. An Application is a Custom Resource Definition (CRD), used to define an Application source type. The source type is a definition of which deployment tool is used (helm or git) and where those manifests are located. But there are some challenges with this. + +In this description, I will go over how to use ApplicationSets and go over different implementations of them. + +## App of Apps + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/d9116e72-1791-4739-b81f-598d50852eda) + +Many users opted to solve this issue by creating an Argo CD Application that deploys other Argo CD Applications. + +This method solved a lot of problems. It was a way for users to massively deploy applications in one shot — so instead of deploying hundreds of Argo CD Application objects, you just deploy one that deploys the rest for you. + +And, it was also a way of logically grouping real world applications that are made up of YAML manifests and Helm charts using Argo CD. This method also gave a convenient “watcher” Application, that made sure all Applications were deployed and healthy. + +This was a precursor to ApplicationSets. + +## ApplicationSets Overview + +Argo CD ApplicationSets are took the idea of “App of Apps” and expanded it to be more flexible and deal with a wide range of use cases. The ArgoCD ApplicationSets runs as its own controller and supplements the functionality of the Argo CD Application CRD. + +ApplicationSets provide the following functionality: + +- Use a single manifest to target multiple Kubernetes clusters. +- Use a single manifest to deploy multiple Applications from a single, or multiple, git repos. +- Improve support for monolithic repository patterns (also known as a “monorepo”). This is where you have many applications and/or environments defined in a single repository. +- Within multi-tenant clusters, it improves the ability of teams within a cluster to deploy applications using Argo CD (without the need for privilege escalation). + +ApplicationSets interact with Argo CD by creating, updating, managing, and deleting Argo CD Applications. The ApplicationSets job is to make sure that the Argo CD Application remains consistent with the declared ApplicationSet resource. ApplicationSets can be thought of as sort of an “Application factory”. It takes an ApplicationSet and outputs one or more Argo CD Applications. + + +# Generators + +The ApplicationSet [controller](https://argocd-applicationset.readthedocs.io/en/stable/#introduction) is made up of “generators”. These “generators” instruct the ApplicationSet how to generate Applications by the provided repo or repos, and it also instructs where to deploy the Application. There are 3 “generators” that I will be exploring are: + +- List Generator +- Cluster Generator +- Git Generator + +Each “generator” tackles different scenarios and use cases. Every “generator” gives you the same end result: Deployed Argo CD Applications that are loosely coupled together for easy management. What you use would depend on a lot of factors like the number of clusters managed, git repo layout, and environmental differences. + +## List Generator + +The `List Generator`, generates Argo CD Application manifests based on a fixed list. This is the most straightforward, as it just passes the key/value you specify in the elements section into the template section of the ApplicatonSet manifest. See the following example: + +```yml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: bgd + namespace: openshift-gitops +spec: + generators: + - list: + elements: + - cluster: cluster1 + url: https://api.cluster1.chx.osecloud.com:6443 + - cluster: cluster2 + url: https://api.cluster2.chx.osecloud.com:6443 + - cluster: cluster3 + url: https://api.cluster3.chx.osecloud.com:6443 + template: + metadata: + name: '{{cluster}}-bgd' + spec: + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + source: + repoURL: https://github.com/christianh814/gitops-examples + targetRevision: master + path: applicationsets/list-generator/overlays/{{cluster}} + destination: + server: '{{url}}' + namespace: bgd +``` + +Here each iteration of `{{cluster}}` and `{{url}}` will be replaced by the elements above. This will produce 3 Applications. + + + +These clusters must already be defined within Argo CD, in order to generate applications for these values. The ApplicationSet controller does not create clusters. + +You can see that my ApplicationSet deployed an Application to each cluster defined. You can use any combination of list elements in your config. It doesn’t have to be clusters or overlays. Since this is just a simple key/value pair generator, you can mix and match as you see fit. + +### Cluster Generator + +Argo CD stores information about the clusters it manages in a Secret. You can list your clusters by looking at the list of your secrets in the openshift-gitops namespace. + +```bash +$ oc get secrets -n openshift-gitops -l argocd.argoproj.io/secret-type=cluster +NAME TYPE DATA AGE +cluster-api.cluster1.chx.osecloud.com-74873278 Opaque 3 23m +cluster-api.cluster2.chx.osecloud.com-2320437559 Opaque 3 23m +cluster-api.cluster3.chx.osecloud.com-2066075908 Opaque 3 23m +``` + +When you use the argocd CLI to list these clusters, the controller reads the secret to glean the information it needs. + +```bash +$ argocd cluster list +SERVER NAME VERSION STATUS MESSAGE +https://api.cluster1.chx.osecloud.com:6443 cluster1 1.20 Successful +https://api.cluster2.chx.osecloud.com:6443 cluster2 1.20 Successful +https://api.cluster3.chx.osecloud.com:6443 cluster3 1.20 Successful +``` + +The same is true for the ApplicationSet controller. It uses those same Secrets to generate parameters that will be used in the template section of the manifest. Furthermore, you can use label selectors to target specific configurations to specific clusters. You can then just label the corresponding secret. Here’s an example: + +```bash +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: bgd + namespace: openshift-gitops +spec: + generators: + - clusters: + selector: + matchLabels: + bgd: dev + template: + metadata: + name: '{{name}}-bgd' + spec: + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + source: + repoURL: https://github.com/christianh814/gitops-examples + targetRevision: master + path: applicationsets/cluster-generator/overlays/dev/ + destination: + server: '{{server}}' + namespace: bgd +``` + +Here, under `.spec.generators.clusters` you can see that I set the selector as bgd=dev. Any cluster matching this label will have the Application deployed to it. The `{{name}}` and `{{server}}` resources get populated by the corresponding name and server fields in the secret. + +Initially, when I apply this, I won’t see anything. + + + + +If I take a look at the controller logs, you see that it shows that it generated 0 applications. + +```bash +$ oc logs -l app.kubernetes.io/name=argocd-applicationset-controller | grep generated +time="2021-04-01T01:25:31Z" level=info msg="generated 0 applications" generator="&{0xc000745b80 0xc000118000 0xc000afa000 openshift-gitops 0xc0009bd810 0xc000bb01e0}" +``` + +This is because we used the label `bgd=dev`` to indicate which cluster we want to deploy this Application. Let’s take a look at the secrets. + +```bash +$ oc get secrets -l bgd=dev -n openshift-gitops +``` + +No resources found in openshift-gitops namespace. + +Let’s label cluster1, and then verify it, to deploy this Application to that cluster. + +```bash +$ oc label secret cluster-api.cluster1.chx.osecloud.com-74873278 bgd=dev -n openshift-gitops +secret/cluster-api.cluster1.chx.osecloud.com-74873278 labeled + +$ oc get secrets -l bgd=dev -n openshift-gitops +NAME TYPE DATA AGE +cluster-api.cluster1.chx.osecloud.com-74873278 Opaque 3 131m +``` + +Taking a look at the UI, I should see the Application deployed. + + + +Now to deploy this Application to another cluster, you can just label the secret of the cluster you want to deploy to. + +```bash +$ oc label secret cluster-api.cluster3.chx.osecloud.com-2066075908 bgd=dev -n openshift-gitops +secret/cluster-api.cluster3.chx.osecloud.com-2066075908 labeled +$ oc get secrets -l bgd=dev -n openshift-gitops +NAME TYPE DATA AGE +cluster-api.cluster1.chx.osecloud.com-74873278 Opaque 3 135m +cluster-api.cluster3.chx.osecloud.com-2066075908 Opaque 3 135m +``` + +Now I have my Application deployed to both cluster1 and cluster3, and all I had to do was label the corresponding secret. + + + +If you want to target all your clusters, you just need to set .spec.generators.clusters to an empty object {}. Example snippet below. + +```bash +spec: + generators: + - clusters: {} +``` + +This will target all clusters that Argo CD has managed, including the cluster that Argo CD is running on, which is called the “in cluster”. Note that there are issues with this method and “in clusters”. Please see the following GitHub issue for more information. + +## Git Generator + +The Git Generator takes how your Git repository is organized to determine how your application gets deployed.The Git Generator has two sub-generators: Directory and File. + +### Directory Generator + +The Git Directory Generator generates the parameters used based on your directory structure in your git repository. The ApplicationSet controller will create Applications based on the manifests stored in a particular directory in your repository. Here is an example ApplicationSet manifest. + +```yml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: pricelist + namespace: openshift-gitops +spec: + generators: + - git: + repoURL: https://github.com/christianh814/gitops-examples + revision: master + directories: + - path: applicationsets/git-dir-generator/apps/* + template: + metadata: + name: '{{path.basename}}' + spec: + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + source: + repoURL: https://github.com/christianh814/gitops-examples + targetRevision: master + path: '{{path}}' + destination: + server: https://api.cluster1.chx.osecloud.com:6443 + namespace: pricelist +``` + +This ApplicationSet deploys an Application that is made up of Helm charts and YAML working together. To understand how this works, it’s good to take a look at the tree view of my directory structure. + +```bash +$ tree applicationsets/git-dir-generator/apps +applicationsets/git-dir-generator/apps +├── pricelist-config +│ ├── kustomization.yaml +│ ├── pricelist-config-ns.yaml +│ └── pricelist-config-rb.yaml +├── pricelist-db +│ ├── Chart.yaml +│ └── values.yaml +└── pricelist-frontend + ├── kustomization.yaml + ├── pricelist-deploy.yaml + ├── pricelist-job.yaml + ├── pricelist-route.yaml + └── pricelist-svc.yaml +3 directories, 10 files +``` + +The name of the application is generated based on the name of the directory, denoted as `{{path.basename}}`` in the config, which is pricelist-config, pricelist-db, and pricelist-frontend. + +The path to each application denoted as `{{path}}` will be based on what is defined under `.spec.generators.git.directories.path`` in the config. Once I apply this configuration, it will show 3 Applications in the UI. + +Now, when you add a directory with Helm charts or bare YAML manifests it will automatically be added when you push to the tracked git repo. + + + +### File Generator + +This generator is also based on what is stored in your git repository but instead of directory structure, it will read a configuration file. This file can be called whatever you want, but it must be in JSON format. Take a look at my directory structure. + +```bash +$ tree applicationsets/git-generator/applicationsets/git-generator/ +├── appset-bgd.yaml +├── base +│ ├── bgd-deployment.yaml +│ ├── bgd-namespace.yaml +│ ├── bgd-route.yaml +│ ├── bgd-svc.yaml +│ └── kustomization.yaml +├── cluster-config +│ ├── cluster1 +│ │ └── config.json +│ ├── cluster2 +│ │ └── config.json +│ └── cluster3 +│ └── config.json +└── overlays + ├── cluster1 + │ ├── bgd-deployment.yaml + │ └── kustomization.yaml + ├── cluster2 + │ ├── bgd-deployment.yaml + │ └── kustomization.yaml + └── cluster3 + ├── bgd-deployment.yaml + └── kustomization.yaml +``` + +**Take note that this structure includes a cluster-config directory**. In this directory there is a `config.json` file that contains information about how to deploy the application by providing the information needed to pass to the template in the ApplicationSets manifest. + +You can configure the `config.json` file as you like, as long as it’s valid JSON. Here is my example for cluster 1. + +```bash +{ + "cluster": { + "name": "cluster1", + "server": "https://api.cluster1.chx.osecloud.com:6443", + "overlay": "cluster1" + } +} +``` +Based on this configuration, I can build the ApplicationSet YAML. + +```yml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: bgd + namespace: openshift-gitops +spec: + generators: + - git: + repoURL: https://github.com/christianh814/gitops-examples + revision: master + files: + - path: "applicationsets/git-generator/cluster-config/**/config.json" + template: + metadata: + name: '{{cluster.name}}-bgd' + spec: + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + source: + repoURL: https://github.com/christianh814/gitops-examples + targetRevision: master + path: applicationsets/git-generator/overlays/{{cluster.overlay}} + destination: + server: '{{cluster.server}}' + namespace: bgd +``` + +This configuration takes the configuration files you’ve stored, which is denoted under `.spec.generators.git.files.path` section, and reads the configuration files to use as parameters for the template section. + + + +Using the configuration file instructs the controller how to deploy the application, as you can see from the screenshot. This is the most flexible of all, since it’s based on what you put in the JSON configuration. + +Currently, only JSON is the supported format. You can track the progress of supporting YAML based configurations following [this GitHub Issue](https://github.com/argoproj-labs/applicationset/issues/106) + +--- +reference +- https://argocd-applicationset.readthedocs.io/en/stable/ +- https://cloud.redhat.com/blog/getting-started-with-applicationsets +- https://blog.argoproj.io/getting-started-with-applicationsets-9c961611bcf0 +- https://www.padok.fr/en/blog/introduction-argocd-applicationset \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Apps\342\200\205of\342\200\205Apps.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Apps\342\200\205of\342\200\205Apps.md" new file mode 100644 index 00000000..5b2753ac --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Apps\342\200\205of\342\200\205Apps.md" @@ -0,0 +1,49 @@ +--- +title: 'Apps of Apps' +lastUpdated: '2024-03-02' +--- + +ArgoCD application을 모아서 관리하는 패턴을 `App of Apps` 패턴이라고 한다. app of app 패턴으로 구성된 application을 sync하면 여러 argoCD application을 생성하고, 생성된 application은 바라보는 git에 저장된 쿠버네티스 리소스를 배포한다. + +상위 앱이 하위 앱을 여러개 정의하는 구조라고 보면 된다. + +공식 레포에 있는 예제를 살펴보자. (https://github.com/argoproj/argocd-example-apps/tree/master/apps) + +```yml +├── Chart.yaml +├── templates +│ ├── guestbook.yaml +│ ├── helm-dependency.yaml +│ ├── helm-guestbook.yaml +│ └── kustomize-guestbook.yaml +└── values.yaml +``` + +`Chart.yaml`는 여러 application들로 구성되어 있다. + +```yml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + destination: + namespace: argocd + server: {{ .Values.spec.destination.server }} + project: default + source: + path: guestbook + repoURL: https://github.com/argoproj/argocd-example-apps + targetRevision: HEAD +``` + +이 앱을 배포하면, 이 차트에 대한 배포와 하위에 대한 앱들이 자동으로 생긴다. + +--- + +## cascade + +삭제를 위한 cascade 옵션들이 있다. default 옵션인 cascade를 선택하면 app of app application과 모든 application이 삭제된다. 하지만, non cascade를 선택하면 app of app application만 삭제되고 다른 application은 유지된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/ArgoCD\342\200\205\354\204\244\354\271\230.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/ArgoCD\342\200\205\354\204\244\354\271\230.md" new file mode 100644 index 00000000..7e436172 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/ArgoCD\342\200\205\354\204\244\354\271\230.md" @@ -0,0 +1,70 @@ +--- +title: 'ArgoCD 설치' +lastUpdated: '2024-03-02' +--- + +출처: https://dev.to/airoasis/argo-cd-seolci-mic-seoljeong-45bd + +## ArhoCD, ArgCD CLI 설치 + +Argo CD를 kubernetes cluster에 설치한다. + +```yml +$ kubectl create namespace argocd +$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml +``` + +Argo CD의 CLI를 설치한다. + +```yml +$ curl -sSL -o ~/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 +$ chmod +x ~/bin/argocd +``` + +## Argo CD 서비스 노출 + +Argo CD는 default로 서버를 외부로 노출시키지 않는다. 아래와 같이 서비스타입을 LoadBalancer로 변경하여 외부로 노출시킨다. + +```js +$ kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}' +``` + +## admin password 변경 + +Argo CD는 최초 admin account의 초기 password를 kubernetes의 secret으로 저장해 놓는다. 아래와 같이 최초 password를 확인할 수 있다. + +```js +$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo +``` + +Argo CD CLI를 이용하여 Argo CD에 로그인한다. 우선 생성된 Load Balancer의 주소를 얻는다. + +```js +kubectl get svc argocd-server -n argocd +`` + +그리고 Login 한다. username은 admin 이다 + +```js +argocd login +``` + +admin 유저의 password를 업데이트 한다. + +```js +$ argocd account update-password +``` + +## webhook + +Argo CD는 Git repository를 3분에 한번씩 pollin 하면서 실제 kubernetes cluster 와 다른점을 확인한다. 따라서 배포시에 운이 없다면 최대 3분을 기다려야 Argo CD가 변경된 image를 배포하게 된다. 이렇게 Polling 으로 인한 delay를 없애고 싶다면 Git repository 에 Argo CD로 webhook을 만들어 놓으면 된다. + +https://argo-cd.readthedocs.io/en/stable/operator-manual/webhook/ + +webhook 을 만들어 놓았다면 반드시 Argo CD의 load balancer에 Github의 webhook 관련 API를 inbound로 열어줘야 한다. + +GitHub 의 IP 주소 관련 내용과 실제 inbound 에 넣줘야 하는 IP를 확인할 수 있는 링크이다. + +https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-githubs-ip-addresses + +https://api.github.com/meta diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Config\342\200\205Management\342\200\205Plugins.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Config\342\200\205Management\342\200\205Plugins.md" new file mode 100644 index 00000000..97be02d7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Config\342\200\205Management\342\200\205Plugins.md" @@ -0,0 +1,268 @@ +--- +title: 'Config Management Plugins' +lastUpdated: '2024-03-02' +--- + +Argo CD allows us to integrate more config management tools using config management plugins. Most prominent inbuilt tools are helm and kustomize. + +We have to option, when it comes to create our own Config Management Plugin (CMP): + +> Add the plugin config to the main Argo CD ConfigMap. Our repo-server container will then run the plugin's commands. + +There are two ways to install a Config Management Plugin: + +- sidecar plugin + - This is a good option for a more complex plugin that would clutter the Argo CD ConfigMap. A copy of the repository is sent to the sidecar container as a tarball and processed individually per application. + +- ConfigMap plugin (this method is deprecated and will be removed in a future version) + - The repo-server container will run your plugin's commands. + +## Sidecar plugin +An operator can configure a plugin tool via a sidecar to repo-server. The following changes are required to configure a new plugin: + +#### Write the plugin configuration file +Plugins will be configured via a ConfigManagementPlugin manifest located inside the plugin container. + +```yml +apiVersion: argoproj.io/v1alpha1 +kind: ConfigManagementPlugin +metadata: + # The name of the plugin must be unique within a given Argo CD instance. + name: my-plugin +spec: + version: v1.0 + # The init command runs in the Application source directory at the beginning of each manifest generation. The init + # command can output anything. A non-zero status code will fail manifest generation. + init: + # Init always happens immediately before generate, but its output is not treated as manifests. + # This is a good place to, for example, download chart dependencies. + command: [sh] + args: [-c, 'echo "Initializing..."'] + # The generate command runs in the Application source directory each time manifests are generated. Standard output + # must be ONLY valid YAML manifests. A non-zero exit code will fail manifest generation. + # Error output will be sent to the UI, so avoid printing sensitive information (such as secrets). + generate: + command: [sh, -c] + args: + - | + echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$ARGOCD_ENV_FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}" + # The discovery config is applied to a repository. If every configured discovery tool matches, then the plugin may be + # used to generate manifests for Applications using the repository. If the discovery config is omitted then the plugin + # will not match any application but can still be invoked explicitly by specifying the plugin name in the app spec. + # Only one of fileName, find.glob, or find.command should be specified. If multiple are specified then only the + # first (in that order) is evaluated. + discover: + # fileName is a glob pattern (https://pkg.go.dev/path/filepath#Glob) that is applied to the Application's source + # directory. If there is a match, this plugin may be used for the Application. + fileName: "./subdir/s*.yaml" + find: + # This does the same thing as fileName, but it supports double-start (nested directory) glob patterns. + glob: "**/Chart.yaml" + # The find command runs in the repository's root directory. To match, it must exit with status code 0 _and_ + # produce non-empty output to standard out. + command: [sh, -c, find . -name env.yaml] + # The parameters config describes what parameters the UI should display for an Application. It is up to the user to + # actually set parameters in the Application manifest (in spec.source.plugin.parameters). The announcements _only_ + # inform the "Parameters" tab in the App Details page of the UI. + parameters: + # Static parameter announcements are sent to the UI for _all_ Applications handled by this plugin. + # Think of the `string`, `array`, and `map` values set here as "defaults". It is up to the plugin author to make + # sure that these default values actually reflect the plugin's behavior if the user doesn't explicitly set different + # values for those parameters. + static: + - name: string-param + title: Description of the string param + tooltip: Tooltip shown when the user hovers the + # If this field is set, the UI will indicate to the user that they must set the value. + required: false + # itemType tells the UI how to present the parameter's value (or, for arrays and maps, values). Default is + # "string". Examples of other types which may be supported in the future are "boolean" or "number". + # Even if the itemType is not "string", the parameter value from the Application spec will be sent to the plugin + # as a string. It's up to the plugin to do the appropriate conversion. + itemType: "" + # collectionType describes what type of value this parameter accepts (string, array, or map) and allows the UI + # to present a form to match that type. Default is "string". This field must be present for non-string types. + # It will not be inferred from the presence of an `array` or `map` field. + collectionType: "" + # This field communicates the parameter's default value to the UI. Setting this field is optional. + string: default-string-value + # All the fields above besides "string" apply to both the array and map type parameter announcements. + - name: array-param + # This field communicates the parameter's default value to the UI. Setting this field is optional. + array: [default, items] + collectionType: array + - name: map-param + # This field communicates the parameter's default value to the UI. Setting this field is optional. + map: + some: value + collectionType: map + # Dynamic parameter announcements are announcements specific to an Application handled by this plugin. For example, + # the values for a Helm chart's values.yaml file could be sent as parameter announcements. + dynamic: + # The command is run in an Application's source directory. Standard output must be JSON matching the schema of the + # static parameter announcements list. + command: [echo, '[{"name": "example-param", "string": "default-string-value"}]'] + + # If set to then the plugin receives repository files with original file mode. Dangerous since the repository + # might have executable files. Set to true only if you trust the CMP plugin authors. + preserveFileMode: false +``` + +> While the ConfigManagementPlugin looks like a Kubernetes object, it is not actually a custom resource. It only follows kubernetes-style spec conventions. + +The `generate` command must print a valid YAML stream to stdout. Both `init` and `generate` commands are executed inside the application source directory. + +The `discover.fileName` is used as glob pattern to determine whether an application repository is supported by the plugin or not. + +```yml + discover: + find: + command: [sh, -c, find . -name env.yaml] +``` + +If `discover.fileName` is not provided, the `discover.find.command` is executed in order to determine whether an application repository is supported by the plugin or not. + +The `find` command should return a non-error exit code and produce output to stdout when the application source type is supported. + +#### Place the plugin configuration file in the sidecar + +Argo CD expects the plugin configuration file to be located at `/home/argocd/cmp-server/config/plugin.yaml` in the sidecar. + +If you use a custom image for the sidecar, you can add the file directly to that image. + +```dockerfile +WORKDIR /home/argocd/cmp-server/config/ +COPY plugin.yaml ./ +``` + +If you use a stock image for the sidecar or would rather maintain the plugin configuration in a ConfigMap, just nest the plugin config file in a ConfigMap under the `plugin.yaml` key and mount the ConfigMap in the sidecar (see next section). + +```yml +apiVersion: v1 +kind: ConfigMap +metadata: + name: my-plugin-config +data: + plugin.yaml: | + apiVersion: argoproj.io/v1alpha1 + kind: ConfigManagementPlugin + metadata: + name: my-plugin + spec: + version: v1.0 + init: + command: [sh, -c, 'echo "Initializing..."'] + generate: + command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"Foo\": \"$ARGOCD_ENV_FOO\", \"KubeVersion\": \"$KUBE_VERSION\", \"KubeApiVersion\": \"$KUBE_API_VERSIONS\",\"Bar\": \"baz\"}}}"'] + discover: + fileName: "./subdir/s*.yaml" +``` + +#### Register the plugin sidecar + +To install a plugin, patch argocd-repo-server to run the plugin container as a sidecar, with argocd-cmp-server as its entrypoint. You can use either off-the-shelf or custom-built plugin image as sidecar image. For example: + +```yml +containers: +- name: my-plugin + command: [/var/run/argocd/argocd-cmp-server] # Entrypoint should be Argo CD lightweight CMP server i.e. argocd-cmp-server + image: busybox # This can be off-the-shelf or custom-built image + securityContext: + runAsNonRoot: true + runAsUser: 999 + volumeMounts: + - mountPath: /var/run/argocd + name: var-files + - mountPath: /home/argocd/cmp-server/plugins + name: plugins + # Remove this volumeMount if you've chosen to bake the config file into the sidecar image. + - mountPath: /home/argocd/cmp-server/config/plugin.yaml + subPath: plugin.yaml + name: my-plugin-config + # Starting with v2.4, do NOT mount the same tmp volume as the repo-server container. The filesystem separation helps + # mitigate path traversal attacks. + - mountPath: /tmp + name: cmp-tmp +volumes: +- configMap: + name: my-plugin-config + name: my-plugin-config +- emptyDir: {} + name: cmp-tmp +``` + +## ConfigMap plugin + +> ConfigMap plugins are deprecated and will no longer be supported in 2.7. + +The following changes are required to configure a new plugin: + +1. Make sure required binaries are available in argocd-repo-server pod. The binaries can be added via volume mounts or using a custom image (see custom_tools for examples of both). +2. Register a new plugin in argocd-cm ConfigMap: + +```yml +data: + configManagementPlugins: | + - name: pluginName + init: # Optional command to initialize application source directory + command: ["sample command"] + args: ["sample args"] + generate: # Command to generate manifests YAML + command: ["sample command"] + args: ["sample args"] + lockRepo: true # Defaults to false. See below. +``` + +The generate command must print a valid YAML or JSON stream to stdout. Both init and generate commands are executed inside the application source directory or in path when specified for the app. + +Create an Application which uses your new CMP. + +## Migrating from argocd-cm plugins + +Installing plugins by modifying the argocd-cm ConfigMap is deprecated as of v2.4. Support will be completely removed in a future release. + +To converting the ConfigMap entry into a config file, First, copy the plugin's configuration into its own YAML file. Take for example the following ConfigMap entry: + +```yml +data: + configManagementPlugins: | + - name: pluginName + init: # Optional command to initialize application source directory + command: ["sample command"] + args: ["sample args"] + generate: # Command to generate manifests YAML + command: ["sample command"] + args: ["sample args"] + lockRepo: true # Defaults to false. See below. +``` + +The `pluginName` item would be converted to a config file like this: + +```yml +apiVersion: argoproj.io/v1alpha1 +kind: ConfigManagementPlugin +metadata: + name: pluginName +spec: + init: # Optional command to initialize application source directory + command: ["sample command"] + args: ["sample args"] + generate: # Command to generate manifests YAML + command: ["sample command"] + args: ["sample args"] +``` + +> The lockRepo key is not relevant for sidecar plugins, because sidecar plugins do not share a single source repo directory when generating manifests. + +## Debugging a CMP + +If you are actively developing a sidecar-installed CMP, keep a few things in mind: + +1. If you are mounting plugin.yaml from a ConfigMap, you will have to restart the repo-server Pod so the plugin will pick up the changes. +2. If you have baked plugin.yaml into your image, you will have to build, push, and force a re-pull of that image on the repo-server Pod so the plugin will pick up the changes. If you are using `:latest`, the Pod will always pull the new image. If you're using a different, static tag, set `imagePullPolicy: Always` on the CMP's sidecar container. +3. CMP errors are cached by the repo-server in Redis. Restarting the repo-server Pod will not clear the cache. Always do a "Hard Refresh" when actively developing a CMP so you have the latest output. + +--- +referece +- https://blog.ediri.io/how-to-create-an-argo-cd-plugin +- https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Health\342\200\205Check.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Health\342\200\205Check.md" new file mode 100644 index 00000000..c77b6eb6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/ArgoCD/Health\342\200\205Check.md" @@ -0,0 +1,37 @@ +--- +title: 'Health Check' +lastUpdated: '2023-08-17' +--- +Here are some common indicators and states you might encounter while working with ArgoCD: + +1. **Application States:** + - **Healthy:** The application is in the desired state and all resources are successfully deployed and reconciled. + - **OutOfSync:** The application's current state does not match the desired state defined in the Git repository. This can happen if there are differences in resource definitions or configuration. + - **SyncFailed:** The application failed to synchronize, and there was an issue during the reconciliation process. This could be due to errors in resource creation, conflicts, or other issues. + +2. **Sync Status:** + - **Syncing:** The application is currently being synchronized with the desired state. + - **Synced:** The synchronization process has completed successfully, and the application is in the desired state. + - **ComparisonError:** An error occurred while comparing the desired state with the current state. This can happen if there are issues retrieving the application manifests or if the desired state is not properly defined. + +3. **Resource Status:** + - **Healthy:** The individual Kubernetes resources associated with the application are in a healthy state. + - **Degraded:** Some resources are experiencing issues or are not fully functional. This might be indicated by warnings or errors in the resource status. + - **Failed:** One or more resources failed to deploy or reconcile properly. + +4. **ArgoCD Server and Components:** + - **Running:** The ArgoCD server pod or component is running and operational. + - **Error:** An error occurred during the startup or operation of the ArgoCD server or component. + - **Unavailable:** The ArgoCD server or component is not accessible or not functioning correctly. + +5. Repository Sync Status: + - **Syncing:** ArgoCD is currently synchronizing with the Git repository to retrieve the latest application manifests. + - **Synced:** The synchronization process with the Git repository has completed successfully. + - **Error:** There was an error during the synchronization process, such as authentication failure or repository access issues. + +These statuses and indicators provide insights into the health and synchronization status of applications and components managed by ArgoCD. Monitoring and analyzing these states can help identify issues, troubleshoot problems, and ensure that applications are deployed and maintained correctly within the Kubernetes cluster. + +--- +reference +- https://argo-cd.readthedocs.io/en/stable/applications/#application-health-status +- https://argo-cd.readthedocs.io/en/stable/operator-manual/health/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Install\342\200\205Cilium.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Install\342\200\205Cilium.md" new file mode 100644 index 00000000..be43bab9 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Install\342\200\205Cilium.md" @@ -0,0 +1,191 @@ +--- +title: 'Install Cilium' +lastUpdated: '2024-03-02' +--- + +Let’s deploy Cilium for testing with our Golang web server in below example. We will need a Kubernetes cluster for deploying Cilium. One of the easiest ways we have found to deploy clusters for testing locally is KIND, which stands for Kubernetes in Docker. It will allow us to create a cluster with a YAML configuration file and then, using Helm, deploy Cilium to that cluster. + +#### KIND configuration for Cilium local deploy + +```yaml +kind: Cluster #---1 +apiVersion: kind.x-k8s.io/v1alpha4 #---2 +nodes: #---3 +- role: control-plane #---4 +- role: worker #---5 +- role: worker #---6 +- role: worker #---7 +networking: #---8 +disableDefaultCNI: true #---9 +``` + +1. Specifies that we are configuring a KIND cluster +2. The version of KIND’s config +3. The list of nodes in the cluster +4. One control plane node +5. Worker node 1 +6. Worker node 2 +7. Worker node 3 +8. KIND configuration options for networking +9. Disables the default networking option so that we can deploy Cilium + +With the KIND cluster configuration yaml, we can use KIND to create that cluster with following command. If this is the first time you're runnging it, it will take some time to download all the Docker images for the working and control plane Docker images: + +```bash +$ kind create cluster --config=kind-config.yaml +Creating cluster "kind" ... +✓ Ensuring node image (kindest/node:v1.18. +2) Preparing nodes +✓ Writing configuration Starting control-plane +Installing StorageClass Joining worker nodes Set kubectl context to "kind-kind" +You can now use your cluster with: + +kubectl cluster-info --context kind-kind + +Have a question, bug, or feature request? +Let us know! https://kind.sigs.k8s.io/#community ߙ⊭--- + +Always verify that the cluster is up and running with kubectl. +``` + +```bash +$ kubectl cluster-info --context kind-kind +Kubernetes master -> control plane is running at https://127.0.0.1:59511 +KubeDNS is running at +https://127.0.0.1:59511/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy +To further debug and diagnose cluster problems, use 'kubectl cluster-info dump.' +``` + +> The cluster nodes will remain in state NotReady until Cilium deploys the network. This is normal behavior for the cluster. + +Now that our cluster is running locally, we can begin installing Cilium using Helm, a Kubernetes deployment tool. + +According to its documentation, Helm is the preferred way to install Cilium. First, we need to add the Helm repo for Cilium. Optionally, you can download the Docker images for Cilium and finally instruct KIND to load the Cilium images into the cluster: + +```bash +$ helm repo add cilium https://helm.cilium.io/ +# Pre-pulling and loading container images is optional. +$ docker pull cilium/cilium:v1.9.1 +kind load docker-image cilium/cilium:v1.9.1 +``` + +Now that the prerequisites for Cilium are completed, we can install it in our cluster with Helm. There are many configuration options for Cilium, and Helm configures options with `--set NAME_VAR=VAR`: + +```bash +$ helm install cilium cilium/cilium --version 1.10.1 --namespace kube-system + +NAME: Cilium +LAST DEPLOYED: Fri Jan 1 15:39:59 2021 +NAMESPACE: kube-system +STATUS: deployed +REVISION: 1 +TEST SUITE: None +NOTES: +You have successfully installed Cilium with Hubble. + +Your release version is 1.10.1. + +For any further help, visit https://docs.cilium.io/en/v1.10/gettinghelp/ +``` + +Cilium installs serveral pieces in the clster: the agent, the client, the operator, and the `cilium-cni` plugin: + +- **Agent** + - The Cilium agent, runs on each node in the cluster. The agent accepts configuration through Kubernetes APIs that describe networking, service load balancing, network policies, and visibility and monitoring requirements. + +- **Client (CLI)** + - The Cilium CLI client (Cilium) is a command-line tool installed along with the Cilium agent. It interacts with the REST API on the same node. The CLI allows developers to inspect the state and status of the local agent. It also provides tooling to access the eBPF maps to validate their state directly. + +- **Operator** + - The operator is responsible for managing duties in the cluster, which should be handled per cluster and not per node. + +- **CNI Plugin** + - The CNI plugin (cilium-cni) interacts with the Cilium API of the node to trigger the configuration to provide networking, load balancing, and network policies pods. + +We can observe all these components being deployed in the cluster with the `kubectl -n kube-system get pods --watch` command: + +```bash +$ kubectl -n kube-system get pods --watch +NAME READY STATUS +cilium-65kvp 0/1 Init:0/2 +cilium-node-init-485lj 0/1 ContainerCreating +cilium-node-init-79g68 1/1 Running +cilium-node-init-gfdl8 1/1 Running +cilium-node-init-jz8qc 1/1 Running +cilium-operator-5b64c54cd-cgr2b 0/1 ContainerCreating +cilium-operator-5b64c54cd-tblbz 0/1 ContainerCreating +cilium-pg6v8 0/1 Init:0/2 +cilium-rsnqk 0/1 Init:0/2 +cilium-vfhrs 0/1 Init:0/2 +coredns-66bff467f8-dqzql 0/1 Pending +coredns-66bff467f8-r5nl6 0/1 Pending +etcd-kind-control-plane 1/1 Running +kube-apiserver-kind-control-plane 1/1 Running +kube-controller-manager-kind-control-plane 1/1 Running +kube-proxy-k5zc2 1/1 Running +kube-proxy-qzhvq 1/1 Running +kube-proxy-v54p4 1/1 Running +kube-proxy-xb9tr 1/1 Running +kube-scheduler-kind-control-plane 1/1 Running +cilium-operator-5b64c54cd-tblbz 1/1 Running +``` + +Now that we have deployed Cilium, we can run the Cilium connectivity check to ensure it is running correctly: + +```bash +$ kubectl create ns cilium-test +namespace/cilium-test created + +$ kubectl apply -n cilium-test \ +-f \ +https://raw.githubusercontent.com/strongjz/advanced_networking_code_examples/master/chapter-4/connectivity-check.yaml + +deployment.apps/echo-a created +deployment.apps/echo-b created +deployment.apps/echo-b-host created +deployment.apps/pod-to-a created +deployment.apps/pod-to-external-1111 created +deployment.apps/pod-to-a-denied-cnp created +deployment.apps/pod-to-a-allowed-cnp created +deployment.apps/pod-to-external-fqdn-allow-google-cnp created +deployment.apps/pod-to-b-multi-node-clusterip created +deployment.apps/pod-to-b-multi-node-headless created +deployment.apps/host-to-b-multi-node-clusterip created +deployment.apps/host-to-b-multi-node-headless created +deployment.apps/pod-to-b-multi-node-nodeport created +deployment.apps/pod-to-b-intra-node-nodeport created +service/echo-a created +service/echo-b created +service/echo-b-headless created +service/echo-b-host-headless created +ciliumnetworkpolicy.cilium.io/pod-to-a-denied-cnp created +ciliumnetworkpolicy.cilium.io/pod-to-a-allowed-cnp created +ciliumnetworkpolicy.cilium.io/pod-to-external-fqdn-allow-google-cnp created +``` + +The connectivity test will deploy a series of Kubernetes deployments that will use various connectivity paths. Connectivity paths come with and without service load balancing and in various network policy combinations. The pod name indicates the connectivity variant, and the readiness and liveness gate indicates the success or failure of the test: + +```bash +$ kubectl get pods -n cilium-test -w +NAME READY STATUS +echo-a-57cbbd9b8b-szn94 1/1 Running +echo-b-6db5fc8ff8-wkcr6 1/1 Running +echo-b-host-76d89978c-dsjm8 1/1 Running +host-to-b-multi-node-clusterip-fd6868749-7zkcr 1/1 Running +host-to-b-multi-node-headless-54fbc4659f-z4rtd 1/1 Running +pod-to-a-648fd74787-x27hc 1/1 Running +pod-to-a-allowed-cnp-7776c879f-6rq7z 1/1 Running +pod-to-a-denied-cnp-b5ff897c7-qp5kp 1/1 Running +pod-to-b-intra-node-nodeport-6546644d59-qkmck 1/1 Running +pod-to-b-multi-node-clusterip-7d54c74c5f-4j7pm 1/1 Running +pod-to-b-multi-node-headless-76db68d547-fhlz7 1/1 Running +pod-to-b-multi-node-nodeport-7496df84d7-5z872 1/1 Running +pod-to-external-1111-6d4f9d9645-kfl4x 1/1 Running +pod-to-external-fqdn-allow-google-cnp-5bc496897c-bnlqs 1/1 Running +``` + +--- + +reference +- https://learning.oreilly.com/library/view/networking-and-kubernetes/9781492081647/ch04.html#idm46219938952648 +- https://cilium.io \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Karpenter/Karpenter.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Karpenter/Karpenter.md" new file mode 100644 index 00000000..1cbf906c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Karpenter/Karpenter.md" @@ -0,0 +1,138 @@ +--- +title: 'Karpenter' +lastUpdated: '2024-03-02' +--- + +Karpenter is **an open-source, flexible, high-performance Kubernetes cluster autoscaler** built with AWS. It helps improve your application availability and cluster efficiency by rapidly launching right-sized compute resources in response to changing application load. Karpenter also provides just-in-time compute resources to meet your application’s needs and will soon automatically optimize a cluster’s compute resource footprint to reduce costs and improve performance. + +Before Karpenter, Kubernetes users needed to dynamically adjust the compute capacity of their clusters to support applications using Amazon EC2 Auto Scaling groups and the Kubernetes Cluster Autoscaler. Nearly half of Kubernetes customers on AWS report that configuring cluster auto scaling using the Kubernetes Cluster Autoscaler is challenging and restrictive. + +When Karpenter is installed in your cluster, Karpenter observes the aggregate resource requests of unscheduled pods and makes decisions to launch new nodes and terminate them to reduce scheduling latencies and infrastructure costs. Karpenter does this by **observing events within the Kubernetes cluster and then sending commands to the underlying cloud provider’s compute service**, such as Amazon EC2. + +## Getting Started with Karpenter on AWS + +To get started with Karpenter in any Kubernetes cluster, ensure there is some compute capacity available, and install it using the Helm charts provided in the public repository. Karpenter also requires permissions to provision compute resources on the provider of your choice. + +Once installed in your cluster, the default Karpenter provisioner will observe incoming Kubernetes pods, which cannot be scheduled due to insufficient compute resources in the cluster and automatically launch new resources to meet their scheduling and resource requirements. + +Let's see a quick start using Karpenter in an Amazon EKS cluster based on Getting Started with Karpenter on AWS. It requires the installation of AWS Command Line Interface (AWS CLI), kubectl, eksctl, and Helm (the package manager for Kubernetes). After setting up these tools, create a cluster with eksctl. This example configuration file specifies a basic cluster with one initial node. + +```yaml +cat < cluster.yaml +--- +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: eks-karpenter-demo + region: us-east-1 + version: "1.20" +managedNodeGroups: + - instanceType: m5.large + amiFamily: AmazonLinux2 + name: eks-kapenter-demo-ng + desiredCapacity: 1 + minSize: 1 + maxSize: 5 +EOF +$ eksctl create cluster -f cluster.yaml +``` + +Karpenter itself can run anywhere, including on self-managed node groups, managed node groups, or AWS Fargate. Karpenter will provision EC2 instances in your account. + +Next, you need to create necessary IAM resources using the AWS CloudFormation template and IAM Roles for Service Accounts (IRSA) for the Karpenter controller to get permissions like launching instances following the documentation. You also need to install the Helm chart to deploy Karpenter to your cluster. + +```bash +$ helm repo add karpenter https://charts.karpenter.sh +$ helm repo update +$ helm upgrade --install --skip-crds karpenter karpenter/karpenter --namespace karpenter \ + --create-namespace --set serviceAccount.create=false --version 0.5.0 \ + --set controller.clusterName=eks-karpenter-demo \ + --set controller.clusterEndpoint=$(aws eks describe-cluster --name eks-karpenter-demo --query "cluster.endpoint" --output json) \ + --wait # for the defaulting webhook to install before creating a Provisioner +``` + +Karpenter provisioners are a Kubernetes resource that enables you to configure the behavior of Karpenter in your cluster. + +When you create a default provisioner, without further customization besides what is needed for Karpenter to provision compute resources in your cluster, Karpenter automatically discovers node properties such as instance types, zones, architectures, operating systems, and purchase types of instances. You don’t need to define these spec:requirements if there is no explicit business requirement. + +```yaml +cat < + +A standard Kyverno installation consists of a number of different componets, some of which are optional. + +- **Deployments** + - Admission controller (required): The main component of Kyverno which handles webhook callbacks from the API server for verification, mutation, [Policy Exceptions](https://kyverno.io/docs/writing-policies/exceptions/), and the processing engine. + - Background controller (optional): The component responsible for processing of generate and mutate-existing rules. + - Reports controller (optional): The component responsible for handling of [Policy Reports](https://kyverno.io/docs/policy-reports/). + - Cleanup controller (optional): The component responsible for processing of [Cleanup Policies](https://kyverno.io/docs/writing-policies/cleanup/). +- **Services** + - Services needed to receive webhook requests. + - Services needed for monitoring of metrics. +- **ServiceAccounts** + - One ServiceAccount per controller to segregate and confine the permissions needed for each controller to operate on the resources for which it is responsible. +- **ConfigMaps** + - ConfigMap for holding the main Kyverno configuration. + - ConfigMap for holding the metrics configuration. +- **Secrets** + - Secrets for webhook registration and authentication with the API server. +- **Roles and Bindings** + - Roles and ClusterRoles, Bindings and ClusterRoleBindings authorizing the various ServiceAccounts to act on the resources in their scope. +- **Webhooks** + - ValidatingWebhookConfigurations for receiving both policy and resource validation requests. + - MutatingWebhookConfigurations for receiving both policy and resource mutating requests. +- **CustomResourceDefinitions** + - CRDs which define the custom resources corresponding to policies, reports, and their intermediary resources. + +## Policies and Rules + +A Kyverno policy is a collection of rules. Each rule consists of a [match](https://kyverno.io/docs/writing-policies/match-exclude/) declaration, an optional [exclude](https://kyverno.io/docs/writing-policies/match-exclude/) declaration, and one of a [validate](https://kyverno.io/docs/writing-policies/validate/), [mutate](https://kyverno.io/docs/writing-policies/mutate/), [generate](https://kyverno.io/docs/writing-policies/generate), or [verifyImages](https://kyverno.io/docs/writing-policies/verify-images) declaration. + + + +Policies can be defined as cluster-wide resources (using the kind `ClusterPolicy`) or namespaced resources (using the kind `Policy`). As excepted, namespaced policies will only apply to resources within the namespace in which they are defined while cluster-wide policies are applied to matching resources acress all namespaces. Otherwise, there is no difference between the two types. + +Additional policy types include Policy Exceptions and Cleanup Policies which are separate resources and described futher in the documentation. + +--- +reference +- https://github.com/kyverno/kyverno \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Kyverno/Usecases.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Kyverno/Usecases.md" new file mode 100644 index 00000000..e756fc13 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Kyverno/Usecases.md" @@ -0,0 +1,429 @@ +--- +title: 'Usecases' +lastUpdated: '2024-03-02' +--- + +Kyverno is a Kubernetes native policy engine that helps in enforcing custom policies on Kubernetes objects. It is a highly scalable and declarative tool that allows Kubernetes administrators to enforce security, compliance, and operational policies across their clusters. + +Kyverno policies are written in YAML format and can be defined as cluster-wide resources (using the kind ClusterPolicy) or namespaced resources (using the kind Policy.) These policies can validate incoming objects, mutate them as required, or even reject them if they violate the defined rules. + +Kyvernopolicies are highly configurable and can be applied to a wide range of usecased, including enforcing RBAC policies, preventing deployment of untrusted imgages, enforcign naming conventions, and mush more. In this tutorial, we wiil explore some usecases where you might need to create custom policies with Kyverno. + +## 1. Resource Limits + +One of the most common use cases for creating custom policies is enforcing resource limits. Resource limits ensure that Kubernetes pods do not consume too much CPU or memory, which can cause performance issues or even bring down the entire cluster. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: Enforce-resource-limits +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-resource-limits + match: + resources: + kinds: + - Pod + validate: + message: "Pods must have CPU limit of 1 core and memory limit of 1 GiB, and request at least 100 milli-CPUs and 256 MiB of memory" + pattern: + spec: + containers: + - name: "*" + resources: + limits: + cpu: 1 + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi +``` +
+
+ +## 2. Custom Labels + +Labels can help you organize your Kubernetes resources and apply policies based on specific labels, For example, you might want to enforce a policy that requires all pods to have a specific label. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-backend-label +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-backend-label + match: + resources: + kinds: + - Pod + validate: + message: "Pods must have a 'team' label with value 'backend'" + pattern: + metadata: + labels: + team: backend +``` +
+
+ +## 3. Enforcing Custom Annotations + +Annotations can help you attach metadata to your Kubernetes resources. For example, you might want to enforce a policy that requires all pods to have a specific annotation. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-backend-description-annotation +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-backend-description-annotation + match: + resources: + kinds: + - Pod + validate: + message: "Pods must have a 'description' annotation with value 'backend'" + pattern: + metadata: + annotations: + description: backend +``` +
+
+ +## 4. Pod Security Policies +Pod security policies help you control the security settings of your Kubernetes pods. They allow you to control aspects such as the use of privileged containers, the use of host network or host IPC, and the use of certain volume types. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: disallow-privileged-containers +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-privileged + match: + resources: + kinds: + - Pod + validate: + message: "Pods must not use privileged containers" + pattern: + spec: + containers: + - name: "*" + securityContext: + privileged: false +``` +
+
+ +Here is another example YAML file that defines a ClusterPolicy which requires all pods to use seccomp and apparmor security profiles. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-pod-security-policies +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-security-profile + match: + resources: + kinds: + - Pod + validate: + message: "Pods must use the 'seccomp' and 'apparmor' security profiles" + pattern: + spec: + securityContext: + seccompProfile: + type: "RuntimeDefault" + seLinuxOptions: + type: "spc_t" + supplementalGroups: + - 100 + sysctls: + - name: net.ipv4.ip_forward + value: "0" +``` +
+
+ +## 5. Custom Naming Conventions + +Naming conventions can help you maintain consistency and avoid confusion in your Kubernetes cluster. For example, you might want to enforce a naming convention that requires all pods to have a specific prefix or suffix. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: prod-naming-convention +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-prod-naming + match: + resources: + kinds: + - Pod + validate: + message: "Pods must have a 'prod-' prefix in their name" + pattern: + metadata: + name: "prod-*" +``` + +
+
+ +## 6. Enforcing Service Accounts + +Service accounts allow you to control access to Kubernetes resources. You might want to enforce a policy that requires all pods to use a specific service account. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-backend-service-account +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-backend-service-account + match: + resources: + kinds: + - Pod + validate: + message: "Pods must use the 'backend' service account" + pattern: + spec: + serviceAccountName: backend +``` +
+
+ +## 7. Enforcing Network Policies + +Network policies allow you to control traffic flow to and from your Kubernetes pods. You might want to enforce a policy that restricts traffic to only certain IP ranges or ports. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: allow-specific-ip-range +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-specific-ip-range + match: + resources: + kinds: + - NetworkPolicy + validate: + message: "Network policies must allow traffic from 192.168.0.0/16" + pattern: + spec: + podSelector: + matchLabels: + app: myapp + ingress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 +``` +
+
+ +## 8. Enforcing Node Affinity + +Node affinity allows you to control which nodes your Kubernetes pods are scheduled on. You might want to enforce a policy that requires all pods to be scheduled on nodes with a specific label. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-backend-node-affinity +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-backend-node-affinity + match: + resources: + kinds: + - Pod + validate: + message: "Pods must be scheduled on nodes with the 'backend' label" + pattern: + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: role + operator: In + values: + - backend +``` +
+
+ +## 9. Pod Restart Policies + +Restart policies determine how Kubernetes handles pod restarts. You might want to enforce a policy that requires all pods to have a specific restart policy. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-always-restart-policy +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-restart-policy + match: + resources: + kinds: + - Pod + validate: + message: "Pods must have a restart policy of Always" + pattern: + spec: + restartPolicy: Always +``` +
+
+ +## 10. Resource Quotas on Namespaces + +Resource quotas allow you to control the amount of resources that your Kubernetes namespaces can use. You might want to enforce a policy that requires all namespaces to have specific resource quotas. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-resource-quotas +spec: + validationFailureAction: Enforce + background: true + rules: + - name: namespace-resource-quotas + match: + resources: + kinds: + - ResourceQuota + validate: + message: "Namespaces must have a CPU limit of 2 and a memory limit of 1 GiB" + pattern: + spec: + hard: + limits.cpu: "2" + limits.memory: "1Gi" +``` + +
+
+ +## 11. Pod Placement Constraints + +Pod placement constraints allow you to control where your Kubernetes pods are scheduled. You might want to enforce a policy that requires all pods to be scheduled on nodes with specific taints or tolerations. + +
+code +
+ +```yaml +apiVersion: kyverno.io/v +kind: ClusterPolicy +metadata: + name: require-tolerations +spec: + validationFailureAction: Enforce + background: true + rules: + - name: pod-tolerations + match: + resources: + kinds: + - Pod + validate: + message: "Pods must tolerate the 'app=backend' taint" + pattern: + spec: + tolerations: + - key: "app" + operator: "Equal" + value: "backend" + effect: "NoSchedule" +``` + +
+
+ + +--- +reference +- https://www.linkedin.com/pulse/kyverno-common-use-cases-afraz-ahmed/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/MetalLB.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/MetalLB.md" new file mode 100644 index 00000000..eaca6d05 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/MetalLB.md" @@ -0,0 +1,70 @@ +--- +title: 'MetalLB' +lastUpdated: '2024-03-02' +--- + +K8s에서 LB를 사용하기 위해선 AWS, GCP, Azure 등 플랫폼에서 제공해주는 서비스를 사용해야하고, 온프레미스 클러스터에서는 추가 모듈 설치가 필요하다. + +MetalLB는 BareMetal Loadbalancer의 약자로, 베어메탈 환경에서 사용할 수 있는 로드밸런서를 제공하는 오픈소스 프로젝트이다. + +MetalLB의 동작에는 두가지 방식이 있다. + +## 1. L2 Mode + +L2 모드는 2계층 통신을 이용한다. + +1. 각 노드마다 speaker라는 데몬셋이 생성되고, 호스트 네트워크를 사용한다. +2. 리더 speaker pod를 선출한다. 리더는 ARP(GARP)로 해당 External IP에 대한 소유를 주장한다. + - arpping을 이용하여 어떤 Speaker pod가 external IP를 관리하는지 직접 찾을 수 있다. + - 아래는 arpping을 이용하여 nginx ingress controller 서비스를 담당하는 Speaker pod를 찾는 예제이다. `FA:16:3E:5A:39:4C`라는 MAC주소를 가진 node가 Speaker Pod를 가졌다는 뜻이다. + + ```c + $ arping -I ens3 192.168.1.240 + ARPING 192.168.1.240 from 192.168.1.35 ens3 + Unicast reply from 192.168.1.240 [FA:16:3E:5A:39:4C] 1.077ms + Unicast reply from 192.168.1.240 [FA:16:3E:5A:39:4C] 1.321ms + Unicast reply from 192.168.1.240 [FA:16:3E:5A:39:4C] 0.883ms + Unicast reply from 192.168.1.240 [FA:16:3E:5A:39:4C] 0.968ms + ^CSent 4 probes (1 broadcast(s)) + Received 4 response(s) + ``` + +3. 모든 트래픽이 리더 pod로만 오도록 한다. +4. DNAT으로 나머지 노드에 뿌려준다. + +image + +특정 노드에 과한 부하가 발생할 수 있고, 리더 Pod가 알수없는 이유로 죽는다면 일시적 장애가 발생하기 때문에 테스트 또는 소규모의 환경에서만 사용하는 것이 권장된다. + +## 2. BGP Mode + +> In BGP mode, all machines in the cluster establish BGP peering sessions with nearby routers that you control, and tell those routers how to forward traffic to the service IPs. Using BGP allows for true load balancing across multiple nodes, and fine-grained traffic control thanks to BGP’s policy mechanisms. + +image + +1. Speaker Pod에 BGP가 동작하여 서비스 정보(External IP)를 전파한다. + - BGP 커뮤니티, localpref 등 BGP 관련 설정을 할 수 있다. +2. 외부에서 라우터를 통해 [ECMP 라우팅](https://ko.wikipedia.org/wiki/ECMP)으로 분산 접속한다. + +L2 모드와 다르게 speaker pod가 장애나더라도 매우 짧은 시간안에 장애복구가 가능하다. 하지만, 단순히 MetalLB만 설정하는 것이 아니라 BGP 라우팅 설정과 라우팅 전파 관련 최적화 설정이 필요하다는 것이 단점이다. + +**FRR Mode** + +BGP 계층의 백엔드로 FRR을 사용하는, BGP 모드에서 사용할 수 있는 별도 모드이다. FRR 모드가 켜지면 아래와 같은 기능들을 사용할 수 있다. + +- BFD가 지원되는 BGP 세션 +- BGP와 BFD의 IPv6 지원 +- 멀티 프로토콜 BGP + +기본 구현과 비교하여 FRR 모드에는 다음과 같은 제한이 있다 + +- BGP Advertisement의 RouterID 필드는 재정의할 수 있지만 모든 광고에 대해 동일해야 한다 (다른 RouterID를 가진 다른 광고가 있을 수 없음). +- BGP Advertisement의 myAsn 필드는 재정의할 수 있지만 모든 광고에 대해 동일해야 한다(myAsn이 다른 광고가 있을 수 없음). +- eBGP 피어가 노드에서 여러 홉 떨어져 있는 경우 ebgp-multihop 플래그를 true로 설정해야 한다. + +--- +참고 +- https://mlops-for-all.github.io/en/docs/appendix/metallb/ +- https://www.linkedin.com/pulse/metallb-loadbalancer-bgp-k8s-rock-music-dipankar-shaw/ +- https://stackoverflow.com/questions/62380153/hw-do-you-get-metallb-to-arp-around-its-ip +- https://metallb.universe.tf/concepts/bgp/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Node\342\200\205Termination\342\200\205Handler.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Node\342\200\205Termination\342\200\205Handler.md" new file mode 100644 index 00000000..036fe18d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/Node\342\200\205Termination\342\200\205Handler.md" @@ -0,0 +1,25 @@ +--- +title: 'Node Termination Handler' +lastUpdated: '2024-03-02' +--- + +Node Termination Handler는 EC2 인스턴스가 [EC2 maintenance events](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-instances-status-check_sched.html), [EC2 Spot interruptions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-interruptions.html), [ASG Scale-In](https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroupLifecycle.html#as-lifecycle-scale-in), ASG AZ 재조정, and EC2 Instance Termination 등의 이유로 사용할 수 없는 상태가 되었을 때 대응하기 위한 tool이다. 그에 대한 핸들링을 제때 해줌으로써 Pod를 더 빠르게 회복시키고 availability를 높일 수 있다. + +## modes + +The aws-node-termination-handler(NTH)에는 Instance Metadata Service (IMDS) 모드와 Queue Processor 모드 두가지가 있다. + +### IMDS mode + +The aws-node-termination-handler [Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) Monitor는 각 host에 작은 pod를 하나씩 돌려 `/spot`이나 `/events` 같은 metadata API에 주기적으로 요청을 보내 node를 drain 혹은 cordon 시킨다. + +현재 xquare에서 사용하고 있는 mode이다. Queue 모드에 비해서 지원되지 않는 기능이 있다는 단점이 있지만, EC2 metadata를 이용해 돌아가기 때문에 별도의 AWS 리소스를 사용하지 않고 간단하게 구축할 수 있는 것이 장점이다. + +### Queue Processor + +Queue Processor 모드는 SQS queue에 연결하여 EventBridge에서 온 Event를 받아 처리한다. 아래와 같은 이벤트들을 처리할 수 있다. + +- ASG lifecycle 이벤트 +- EC2 status change 이벤트 +- Spot Interruption Termination 알림 이벤트 +- Spot Rebalance Recommendation \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/OPA\342\200\205Gatekeeper.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/OPA\342\200\205Gatekeeper.md" new file mode 100644 index 00000000..8416bc19 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/OPA\342\200\205Gatekeeper.md" @@ -0,0 +1,177 @@ +--- +title: 'OPA Gatekeeper' +lastUpdated: '2024-03-02' +--- + +The [Open Policy Agent Gatekeeper](https://github.com/open-policy-agent/gatekeeper) project can be laveraged to help enforce policies and strengthen governence in your Kubernetes environment. + +If your organization has been operating Kubernetes, you probably have been looking for ways to control what end-users can do on the cluster and ways to ensure that clusters are in compliance with company policies. These policies may be there to meet governance and legal requirements or to enforce best practices and organizational conventions. With Kubernetes, how do you ensure compliance without sacrificing development agility and operational independence? + +For example, you can enforce policies like: + +- All images must be from approved repositories +- All ingress hostnames must be globally unique +- All pods must have resource limits +- All namespaces must have a label that lists a point-of-contact + +Kubernetes allows decoupling policy decisions from the API server by means of admission controller webhooks to intercept admission requests before they are persisted as objects in Kubernetes. + +Gatekeeper was created to enable users to customize admission control via configuration, not code and to bring awareness of the cluster’s state, not just the single object under evaluation at admission time. Gatekeeper is a customizable admission webhook for Kubernetes that enforces policies executed by the Open Policy Agent (OPA), a policy engine for Cloud Native environments hosted by CNCF. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/e870ebcc-1ae3-4cb7-86e2-f0e27a81b8f4) + +## Features + +let’s take a closer look at the current state of Gatekeeper and how you can leverage all the latest features. Consider an organization that wants to ensure all objects in a cluster have departmental information provided as part of the object’s labels. How can you do this with Gatekeeper? + +### Validating Admission Control + +Once all the Gatekeeper components have been installed in your cluster, the API server will trigger the Gatekeeper admission webhook to process the admission request whenever a resource in the cluster is created, updated, or deleted. + +During the validation process, Gatekeeper acts as a bridge between the API server and OPA. The API server will enforce all policies executed by OPA. + +### Policies and Constraints + +With the integration of the OPA Constraint Framework, a Constraint is a declaration that its author wants a system to meet a given set of requirements. Each Constraint is written with Rego, a declarative query language used by OPA to enumerate instances of data that violate the expected state of the system. All Constraints are evaluated as a logical AND. If one Constraint is not satisfied, then the whole request is rejected. + +Before defining a Constraint, you need to create a Constraint Template that allows people to declare new Constraints. Each template describes both the Rego logic that enforces the Constraint and the schema for the Constraint, which includes the schema of the CRD and the parameters that can be passed into a Constraint, much like arguments to a function. + +For example, here is a Constraint template CRD that requires certain labels to be present on an arbitrary object. + +```yaml +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: k8srequiredlabels +spec: + crd: + spec: + names: + kind: K8sRequiredLabels + listKind: K8sRequiredLabelsList + plural: k8srequiredlabels + singular: k8srequiredlabels + validation: + # Schema for the `parameters` field + openAPIV3Schema: + properties: + labels: + type: array + items: string + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package k8srequiredlabels + + deny[{"msg": msg, "details": {"missing_labels": missing}}] { + provided := {label | input.review.object.metadata.labels[label]} + required := {label | label := input.parameters.labels[_]} + missing := required - provided + count(missing) > 0 + msg := sprintf("you must provide labels: %v", [missing]) + } +``` + +Once a Constraint template has been deployed in the cluster, an admin can now create individual Constraint CRDs as defined by the Constraint template. For example, here is a Constraint CRD that requires the label hr to be present on all namespaces. + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: ns-must-have-hr +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + labels: ["hr"] +``` + +Similarly, another Constraint CRD that requires the label finance to be present on all namespaces can easily be created from the same Constraint template. + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: ns-must-have-finance +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + labels: ["finance"] +``` + +As you can see, with the Constraint framework, we can reliably share Regos via the Constraint templates, define the scope of enforcement with the match field, and provide user-defined parameters to the Constraints to create customized behavior for each Constraint. + +### Audit + +The audit functionality enables periodic evaluations of replicated resources against the Constraints enforced in the cluster to detect pre-existing misconfigurations. Gatekeeper stores audit results as violations listed in the status field of the relevant Constraint. + +```yaml +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: K8sRequiredLabels +metadata: + name: ns-must-have-hr +spec: + match: + kinds: + - apiGroups: [""] + kinds: ["Namespace"] + parameters: + labels: ["hr"] +status: + auditTimestamp: "2019-08-06T01:46:13Z" + byPod: + - enforced: true + id: gatekeeper-controller-manager-0 + violations: + - enforcementAction: deny + kind: Namespace + message: 'you must provide labels: {"hr"}' + name: default + - enforcementAction: deny + kind: Namespace + message: 'you must provide labels: {"hr"}' + name: gatekeeper-system + - enforcementAction: deny + kind: Namespace + message: 'you must provide labels: {"hr"}' + name: kube-public + - enforcementAction: deny + kind: Namespace + message: 'you must provide labels: {"hr"}' + name: kube-system +``` + +### Data Replication + +Audit requires replication of Kubernetes resources into OPA before they can be evaluated against the enforced Constraints. Data replication is also required by Constraints that need access to objects in the cluster other than the object under evaluation. For example, a Constraint that enforces uniqueness of ingress hostname must have access to all other ingresses in the cluster. + +To configure Kubernetes data to be replicated, create a sync config resource with the resources to be replicated into OPA. For example, the below configuration replicates all namespace and pod resources to OPA. + +```yaml +apiVersion: config.gatekeeper.sh/v1alpha1 +kind: Config +metadata: + name: config + namespace: "gatekeeper-system" +spec: + sync: + syncOnly: + - group: "" + version: "v1" + kind: "Namespace" + - group: "" + version: "v1" + kind: "Pod" +``` + +--- +reference +- https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/ +- [Intro: Open Policy Agent Gatekeeper](https://youtu.be/Yup1FUc2Qn0) +- [Deep Dive: Open Policy Agent](https://youtu.be/n94_FNhuzy4) +- https://github.com/open-policy-agent/gatekeeper-library/tree/master/library/general \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Chart\342\200\205Development\342\200\205Tips.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Chart\342\200\205Development\342\200\205Tips.md" new file mode 100644 index 00000000..3a4f96a3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Chart\342\200\205Development\342\200\205Tips.md" @@ -0,0 +1,164 @@ +--- +title: 'Chart Development Tips' +lastUpdated: '2024-03-02' +--- + +## Template Functions + +Helm uses Go templates for templating your resource files. While Go ships several built-in functions, we have added many others. + +First, we added all of the functions in the Sprig library, except env and expandenv, for security reasons. + +We also added two special template functions: include and required. The include function allows you to bring in another template, and then pass the results to other template functions. + +For example, this template snippet includes a template called mytpl, then lowercases the result, then wraps that in double quotes. + +```yml +value: {{ include "mytpl" . | lower | quote }} +``` + +The required function allows you to declare a particular values entry as required for template rendering. If the value is empty, the template rendering will fail with a user submitted error message. + +The following example of the required function declares an entry for .Values.who is required, and will print an error message when that entry is missing: + +```yml +value: {{ required "A valid .Values.who entry required!" .Values.who }} +``` + +## include Function + +Go provides a way of including one template in another using a built-in `template` directive. However, the built-in function cannot be used in Go template pipelines. + +To make it possible to include a template, and then perform an operation on that template's output, Helm has a special `include` function: + +```yml +{{ include "toYaml" $value | indent 2 }} +``` + +The above includes a template called `toYaml`, passes it `$value`, and then passes the output of that template to the `indent` function. + +Because YAML ascribes significance to indentation levels and whitespace, this is one great way to include snippets of code, but handle indentation in a relevant context. + +## required function + +Go provides a way for setting template options to control behavior when a map is indexed with a key that's not present in the map. This is typically set with `template.Options("missingkey=option")`, where `option` can be `default`, `zero`, or `error`. While setting this option to error will stop execution with an error, this would apply to every missing key in the map. There may be situations where a chart developer wants to enforce this behavior for select values in the `values.yaml` file. + +The required function gives developers the ability to declare a value entry as required for template rendering. If the entry is empty in `values.yaml`, the template will not render and will return an error message supplied by the developer. + +For example: + +```yml +{{ required "A valid foo is required!" .Values.foo }} +``` + +The above will render the template when `.Values.foo` is defined, but will fail to render and exit when `.Values.foo` is undefined. + +## tpl Function + +The tpl function allows developers to evaluate strings as templates inside a template. This is useful to pass a template string as a value to a chart or render external configuration files. Syntax: {{ tpl TEMPLATE_STRING VALUES }} + +Examples: + +```yml +# values +template: "{{ .Values.name }}" +name: "Tom" + +# template +{{ tpl .Values.template . }} + +# output +Tom +``` + +Rendering an external configuration file: + +```yml +# external configuration file conf/app.conf +firstName={{ .Values.firstName }} +lastName={{ .Values.lastName }} + +# values +firstName: Peter +lastName: Parker + +# template +{{ tpl (.Files.Get "conf/app.conf") . }} + +# output +firstName=Peter +lastName=Parker +``` + +### Creating Image Pull Secrets + +Image pull secrets are essentially a combination of registry, username, and password. You may need them in an application you are deploying, but to create them requires running `base64` a couple of times. We can write a helper template to compose the Docker configuration file for use as the Secret's payload. Here is an example: + +First, assume that the credentials are defined in the `values.yaml` file like so: + +```yml +imageCredentials: + registry: quay.io + username: someone + password: sillyness + email: someone@host.com +``` + +We then define our helper template as follows: + +```yml +{{- define "imagePullSecret" }} +{{- with .Values.imageCredentials }} +{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }} +{{- end }} +{{- end }} +``` + +Finally, we use the helper template in a larger template to create the Secret manifest: + +```yml +apiVersion: v1 +kind: Secret +metadata: + name: myregistrykey +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ template "imagePullSecret" . }} +``` + +## Tell Helm Not To Uninstall a Resource +Sometimes there are resources that should not be uninstalled when Helm runs a helm uninstall. Chart developers can add an annotation to a resource to prevent it from being uninstalled. + +```yml +kind: Secret +metadata: + annotations: + "helm.sh/resource-policy": keep +[...] +``` + +(Quotation marks are required) + +The annotation `"helm.sh/resource-policy": keep` instructs Helm to skip deleting this resource when a helm operation (such as helm uninstall, helm upgrade or helm rollback) would result in its deletion. However, this resource becomes orphaned. Helm will no longer manage it in any way. This can lead to problems if using helm install `--replace` on a release that has already been uninstalled, but has kept resources. + +## Using `"Partials"` and Template Includes + +Sometimes you want to create some reusable parts in your chart, whether they're blocks or template partials. And often, it's cleaner to keep these in their own files. + +In the `templates/` directory, any file that begins with an underscore(`_`) is not expected to output a Kubernetes manifest file. So by convention, helper templates and partials are placed in a `_helpers.tpl` file. + +## YAML is a Superset of JSON + +According to the YAML specification, YAML is a superset of JSON. That means that any valid JSON structure ought to be valid in YAML. + +This has an advantage: Sometimes template developers may find it easier to express a datastructure with a JSON-like syntax rather than deal with YAML's whitespace sensitivity. + +As a best practice, templates should follow a YAML-like syntax unless the JSON syntax substantially reduces the risk of a formatting issue. + +## Install or Upgrade a Release with One Command + +Helm provides a way to perform an install-or-upgrade as a single command. Use helm `upgrade` with the `--install` command. This will cause Helm to see if the release is already installed. If not, it will run an install. If it is, then the existing release will be upgraded. + +```yml +$ helm upgrade --install --values +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Helm.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Helm.md" new file mode 100644 index 00000000..2bda519c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Helm.md" @@ -0,0 +1,33 @@ +--- +title: 'Helm' +lastUpdated: '2024-03-02' +--- + +Helm이란, kubernetes 패키지 관리를 도와주는 tool이다. Node.js의 npm, Python의 pip와 같은 역할이라고 볼 수 있다. + +## Chart + +Helm은 차트라고 불리는 Package formet을 사용한다. + +차트는 Kubernetes Resouce들의 내용을 나타내는 파일들의 집합이다. 예를 들어 `Wordpress`의 차트는 아래와 같은 구조를 가지고있다. + +```bash +wordpress/ + Chart.yaml # chart에 대한 정보 + LICENSE # (선택사항) chart의 license에 대한 정보 + README.md # (선택사항) 사람이 읽을 수 있는 README 파일 + requirements.yaml # (선택사항) Chart의 dependency 리스트 + values.yaml # Chart의 기본 설정값 + charts/ # 이 차트가 의존하는 차트가 들어 있는 디렉터리 + templates/ # 설정값과 결함하여 menifest를 만들 template + templates/NOTES.txt # (선택사항) 짧은 사용 참고 사항이 포함된 텍스트파일 +``` + +# 컴포넌트 + +Helm은 크게 client와 server(tiller). 이렇게 두가지 파트로 나뉘어져있다. + +|이름|설명| +|-|-| +|client|end user를 위한 command line client. 주로 local chart 개발이나 repository managing, server(tiler)에 chart 설치 요청등 주로 chart의 정보를 관리함| +|server(tiller)|in-cluster 서버. chart의 배포, 릴리즈를 관리함| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Values\342\200\205Files.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Values\342\200\205Files.md" new file mode 100644 index 00000000..981377f8 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/helm/Values\342\200\205Files.md" @@ -0,0 +1,125 @@ +--- +title: 'Values Files' +lastUpdated: '2024-03-02' +--- + +Values is One of the built-in objects. This object provides access to values passed into the chart. Its contents come from multiple sources: + +- The `values.yaml` file in the chart +- If this is a subchart, the `values.yaml` file of a parent chart +- A values file if passed into helm install or helm upgrade with the -f flag (`helm install -f myvals.yaml ./mychart`) +- Individual parameters passed with `--set` (such as `helm install --set foo=bar ./mychart`) + + +The list above is in order of specificity: `values.yaml` is the default, which can be overridden by a parent chart's `values.yaml`, which can in turn be overridden by a user-supplied values file, which can in turn be overridden by `--set` parameters. + +Values files are plain YAML files. Let's edit `mychart/values.yaml` and then edit our ConfigMap template. + +Removing the defaults in `values.yaml`, we'll set just one parameter: + +```yml +favoriteDrink: coffee +``` + +Now we can use this inside of a template: + +```yml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favoriteDrink }} +``` + +Notice on the last line we access favoriteDrink as an attribute of `Values: {{ .Values.favoriteDrink }}`. + +Let's see how this renders. + +```yml +$ helm install geared-marsupi ./mychart --dry-run --debug +install.go:158: [debug] Original chart version: "" +install.go:175: [debug] CHART PATH: /home/bagratte/src/playground/mychart + +NAME: geared-marsupi +LAST DEPLOYED: Wed Feb 19 23:21:13 2020 +NAMESPACE: default +STATUS: pending-install +REVISION: 1 +TEST SUITE: None +USER-SUPPLIED VALUES: +{} + +COMPUTED VALUES: +favoriteDrink: coffee + +HOOKS: +MANIFEST: +--- +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: geared-marsupi-configmap +data: + myvalue: "Hello World" + drink: coffee +``` + +Because favoriteDrink is set in the default values.yaml file to coffee, that's the value displayed in the template. We can easily override that by adding a `--set` flag in our call to helm install: + +```yml +$ helm install solid-vulture ./mychart --dry-run --debug --set favoriteDrink=slurm +install.go:158: [debug] Original chart version: "" +install.go:175: [debug] CHART PATH: /home/bagratte/src/playground/mychart + +NAME: solid-vulture +LAST DEPLOYED: Wed Feb 19 23:25:54 2020 +NAMESPACE: default +STATUS: pending-install +REVISION: 1 +TEST SUITE: None +USER-SUPPLIED VALUES: +favoriteDrink: slurm + +COMPUTED VALUES: +favoriteDrink: slurm + +HOOKS: +MANIFEST: +--- +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: solid-vulture-configmap +data: + myvalue: "Hello World" + drink: slurm +``` + +Since `--set` has a higher precedence than the default values.yaml file, our template generates drink: slurm. + +Values files can contain more structured content, too. For example, we could create a favorite section in our values.yaml file, and then add several keys there: + +```yml +favorite: + drink: coffee + food: pizza +``` +Now we would have to modify the template slightly: + +```yml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink }} + food: {{ .Values.favorite.food }} +``` + +While structuring data this way is possible, the recommendation is that you keep your values trees shallow, favoring flatness. When we look at assigning values to subcharts, we'll see how values are named using a tree structure. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/node\342\200\205shell.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/node\342\200\205shell.md" new file mode 100644 index 00000000..2ffd470d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/tools/node\342\200\205shell.md" @@ -0,0 +1,56 @@ +--- +title: 'node shell' +lastUpdated: '2024-03-02' +--- + +kubernetes node에 쉽게 접속할 수 있도록 하는 kubectl 플러그인이다. 워커노드, 인그레스노드에 접근이 가능하고 root 권한으로 실행이 가능하다. + +### krew 설치하기 + +node-shell는 krew를 통해 설치할 수 있다. + +```bash +# krew 설치하기 +$ brew install krew + +# krew에 kvaps 추가 +$ kubectl krew index add kvaps https://github.com/kvaps/krew-index +``` + +### node-shell 설치하기 + +```bash +# node-shell을 설치한다. +$ kubectl krew install kvaps/node-shell +``` + +### export 추가하기 + +```bash +# vi 편집기 사용하여 ~/.zshrc or ~/.bash_profile열기 +$ vi ~/.zshrc + +# ~/.zshrc or ~/.bash_profile 하단에 해당 내용 추가하기 +$ export PATH="${PATH}:${HOME}/.krew/bin" + +# 변경내용 적용하기 +$ source ~/.zshrc +$ source ~/.bash_profile +``` + +### node-shell 접속 확인하기 + +```bash +# node 확인 +$ k get node + +# node-shell 을 이용해 접속하기 +$ kubectl node-shell {접속할 node 이름} +``` + +--- +참고 +- https://github.com/kvaps/kubectl-node-shell + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/CNI\342\200\205Specification.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/CNI\342\200\205Specification.md" new file mode 100644 index 00000000..b11670f0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/CNI\342\200\205Specification.md" @@ -0,0 +1,117 @@ +--- +title: 'CNI Specification' +lastUpdated: '2024-03-02' +--- + +The CNI specification itself is quite simple. According to the specification, there are four operations that a CNI plugin must support: + +- `ADD`: Add a container to the network. +- `DEL`: Delete a container from the network. +- `CHECK`: Return an error if there is a problem with the container’s network. +- `VERSION`: Report version information about the plugin. + +> The full CNI spec is available on [GitHub](https://github.com/containernetworking/cni/blob/main/SPEC.md) + +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/b65cf102-9c21-4546-9ff4-4c8db1c431f4) + +In above figure, we can see how Kubernetes (or the runtime, as the CNI project refers to container orchestrators) invokes CNI plugin operations by executing binaries. Kubernetes supplies any configuration for the command in JSON to `stdin` and receives the command’s output in JSON through `stdout`. + +CNI plugins frequently have very simple binaries, which act as a wrapper for Kubernetes to call, while the binary makes an HTTP or RPC API call to a persistent backend. CNI maintainers have discussed changing this to an HTTP or RPC model, based on performance issues when frequently launching Windows processes. + +Kubernetes uses only one CNI plugin at a time, though the CNI specification allows for mutiplugin setups (i.e., assigning multiple IP addresses to a container). Multus is a CNI plugin that works around this limitation in Kubernetes by acting as a fan-out to multiple CNI plugins. + +## CNI Plugins + +The CNI plugin has two primary responsibilities: + +- allocate and assign unique IP addresses for pods +- ensure that routes exist within Kubernetes to each pod IP address. + +These responsibilities mean that the overarching network that the cluster resides in dictates CNI plugin behavior. + +For example, if there are too few IP addresses or it is not possible to attach sufficient IP addresses to a node, cluster admins will need to use a CNI plugin that supports an overlay network. The hardware stack, or cloud provider used, typically dictates which CNI options are suitable. + +To use the CNI, add `--network-plugin=cni` to the Kubelet’s startup arguments. By default, the Kubelet reads CNI configuration from the directory `/etc/cni/net.d/` and expects to find the CNI binary in `/opt/cni/bin/`. Admins can override the configuration location with `--cni-config-dir=`, and the CNI binary directory with `--cni-bin-dir=`. + +## CNI network medel + +There are two broad categories of CNI network models: + +- **flat networks** + - In a flat network, the CNI driver uses IP addresses from the cluster’s network, which typically requires many IP addresses to be available to the cluster. +- **overlay network** + - In an overlay network, the CNI driver creates a secondary network within Kubernetes, which uses the cluster’s network (called the underlay network) to send packets. + - Overlay networks create a virtual network within the cluster. In an overlay network, the CNI plugin encapsulates packets. + - Overlay networks add substantial complexity and do not allow hosts on the cluster network to connect directly to pods. + - However, overlay networks allow the cluster network to be much smaller, as only the nodes must be assigned IP addresses on that network. + +CNI plugins also typically need a way to communicate state between nodes. Plugins take very different approaches, such as storing data in the Kubernetes API, in a dedicated database. + +The CNI plugin is also responsible for calling IPAM plugins for IP addressing. + +### The IPAM Interface + +The CNI spec has a second interface, the IP Address Management (IPAM) interface, to reduce duplication of IP allocation code in CNI plugins. The IPAM plugin must determine and output the interface IP address, gateway, and routes, as shown in below Example. **The IPAM interface is similar to the CNI: a binary with JSON input to `stdin` and JSON output from `stdout`.** + +```json +{ + "cniVersion": "0.4.0", + "ips": [ + { + "version": "<4-or-6>", + "address": "", + "gateway": "" (optional) + }, + ... + ], + "routes": [ (optional) + { + "dst": "", + "gw": "" (optional) + }, + ... + ] + "dns": { (optional) + "nameservers": (optional) + "domain": (optional) + "search": (optional) + "options": (optional) + } +} +``` + +Now we will review several of the options available for cluster administrators to choose from when deploying a CNI. + +## Popular CNI Plugins + +- **Cilium** is open source software for transparently securing network connectivity between application containers. + - Cilium is an L7/HTTP-aware CNI and can enforce network policies on L3–L7 using an identity-based security model decoupled from the network addressing. + - The Linux technology [eBPF](../eBPF.md) is what powers Cilium. We will do a deep dive into NetworkPolicy objects; for now know that they are effectively pod-level firewalls. + +- **Flannel** focuses on the network and is a simple and easy way to configure a layer 3 network fabric designed for Kubernetes. + - If a cluster requires functionalities like network policies, an admin must deploy other CNIs, such as Calico. Flannel uses the Kubernetes cluster’s existing etcd to store its state information to avoid providing a dedicated data store. + +- According to **Calico**, it “combines flexible networking capabilities with run-anywhere security enforcement to provide a solution with native Linux kernel performance and true cloud-native scalability.” + - Calico does not use an overlay network. Instead, **Calico configures a layer 3 network that uses the BGP routing protocol** to route packets between hosts. + - Calico can also integrate with Istio, a service mesh, to interpret and enforce policy for workloads within the cluster at the service mesh and network infrastructure layers. + +Below gives a brief overview of the major CNI plugins to choose from. + +|Name|NetworkPolicy support|Data storage|Network setup| +|-|-|-|-| +|Cilium|Yes|etcd or consul|Ipvlan(beta), veth, L7 aware| +|Flannel|No|etcd|Layer 3 IPv4 overlay network| +|Calico|Yes|etcd or Kubernetes API|Layer 3 network using BGP| +|Weave Net|Yes|No external cluster store|Mesh overlay network| + +--- +reference +- https://www.cni.dev/docs/spec/ +- https://github.com/containernetworking/cni/blob/main/SPEC.md +- https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/ + + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Disruption\342\200\205Budget.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Disruption\342\200\205Budget.md" new file mode 100644 index 00000000..4d094020 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Disruption\342\200\205Budget.md" @@ -0,0 +1,140 @@ +--- +title: 'Disruption Budget' +lastUpdated: '2024-03-02' +--- + +Let's explore to limit the number of concurrent disruptions that your application experiences, allowing for higher availability while permitting the cluster administrator to manage the clusters nodes. + +The most common use case when you want to protect an application specified by one of the build-in Kubernetes controllers: + +- Deployment +- ReplicationController +- ReplicaSet +- StatefulSet + +In this case, make a note of controller's `.spec.selector`; the same selector goes into the PDBs `.spec.selector` + +You can also use PDBs with pods which are not controlled by on of the above controllers, or arbitrary groups of pods, but there are some restriction, described in [Arbitrary workloads and arbitrary selectors](https://kubernetes.io/docs/tasks/run-application/configure-pdb/#arbitrary-controllers-and-selectors). + +## Think about how your application reacts to disruptions + +Decide how many instances can be down at the same time for a short period due to a voluntary disruption. + +- Stateless frontends: + - Concern: don't reduce serving capacity by more than 10%. + - Solution: use PDB with minAvailable 90% for example. + +- Single-instance Stateful Application: + - Concern: do not terminate this application without talking to me. + - Possible Solution 1: Do not use a PDB and tolerate occasional downtime. + - Possible Solution 2: Set PDB with maxUnavailable=0. Have an understanding (outside of Kubernetes) that the cluster operator needs to consult you before termination. When the cluster operator contacts you, prepare for downtime, and then delete the PDB to indicate readiness for disruption. Recreate afterwards. + +- Multiple-instance Stateful application such as Consul, ZooKeeper, or etcd: + - Concern: Do not reduce number of instances below quorum, otherwise writes fail. + - Possible Solution 1: set maxUnavailable to 1 (works with varying scale of application). + - Possible Solution 2: set minAvailable to quorum-size (e.g. 3 when scale is 5). (Allows more disruptions at once). + +- Restartable Batch Job: + - Concern: Job needs to complete in case of voluntary disruption. + - Possible solution: Do not create a PDB. The Job controller will create a replacement pod. + + +### Rounding logic when specifying percentages + +Values for `minAvailable` or `maxUnavailable` can be expressed as integers or as a percentage. + +- When you specify an integer, it represents a number of Pods. For instance, if you set `minAvailable` to 10, then 10 Pods must always be available, even during a disruption. +- When you specify a percentage by setting the value to a string representation of a percentage (eg. `"50%"`), it represents a percentage of total Pods. For instance, if you set `minAvailable` to `"50%"`, then at least 50% of the Pods remain available during a disruption. + +When you specify the value as a percentage, it may not map to an exact number of Pods. For example, if you have 7 Pods and you set `minAvailable` to `"50%"`, it's not immediately obvious whether that means 3 Pods or 4 Pods must be available. Kubernetes rounds up to the nearest integer, so in this case, 4 Pods must be available. When you specify the value `maxUnavailable` as a percentage, Kubernetes rounds up the number of Pods that may be disrupted. Thereby a disruption can exceed your defined `maxUnavailable` percentage. You can examine the code that controls this behavior. + +## Specifying a PodDisruptionBudget + +A PodDisruptionBudget has three fields: + +- A label selector `.spec.selector` to specify the set of pods to which it applies. This field is required. +- `.spec.minAvailable` which is a description of the number of pods from that set that must still be available after the eviction, even in the absence of the evicted pod. minAvailable can be either an absolute number or a percentage. +- `.spec.maxUnavailable` (available in Kubernetes 1.7 and higher) which is a description of the number of pods from that set that can be unavailable after the eviction. It can be either an absolute number or a percentage. + +> Note: The behavior for an empty selector differs between the policy/v1beta1 and policy/v1 APIs for PodDisruptionBudgets. For policy/v1beta1 an empty selector matches zero pods, while for policy/v1 an empty selector matches every pod in the namespace. + +You can specify only one of `maxUnavailable`and `minAvailable` in a single `PodDisruptionBudget`. `maxUnavailable` can only be used to control the eviction of pods that have an associated controller managing then. In the examples below, "desired replicas" is the `scale` of the controller managing the pods being selected by the `PodDisruptionBudget`. + +- Example 1: With a `minAvailable` of 5, evictions are allowed as long as they leave behind 5 or more [healthy](https://kubernetes.io/docs/tasks/run-application/configure-pdb/#healthiness-of-a-pod) pods among those selected by the PodDisruptionBudget's `selector`. + +- Example 2: With a `minAvailable` of 30%, evictions are allowed as long as at least 30% of the number of desired replicas are healthy. + +- Example 3: With a `maxUnavailable` of 5, evictions are allowed as long as there are at most 5 unhealthy replicas among the total number of desired replicas. + +- Example 4: With a `maxUnavailable` of 30%, evictions are allowed as long as the number of unhealthy replicas does not exceed 30% of the total number of desired replica rounded up to the nearest integer. If the total number of desired replicas is just one, that single replica is still allowed for disruption, leading to an effective unavailability of 100%. + +In typical usage, a single budget would be used for a collection of pods managed by a controller—for example, the pods in a single ReplicaSet or StatefulSet. + +> Node: A disruption budget does not truly guarantee that the specified number/percentage of pos will always be up. For example, a node that hosts a pod from the collection may fail when the collection is at the minimim size specified in the budget, thus bringing the number of available pods from the collection below the specified size. The budget can only protect against voluntary evictions, not all causes of unavailability. + +If you set `maxUnavailable` to 0% or 0, or you set `minAvailable` to 100% or the number of replicas, you are requiring zero voluntary evictions. When you set zero voluntary evictions for a workload object such as ReplicaSet, then you cannot successfully drain a Node running one of those Pods. If you try to drain a Node where an unevictable Pod is running, the drain never completes. This is permitted as per the semantics of `PodDisruptionBudget`. + +You can find examples of pod disruption budgets defined below. They match pods with the label `app: zookeeper`. + +Example PDB Using minAvailable: + +```yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: zk-pdb +spec: + minAvailable: 2 + selector: + matchLabels: + app: zookeeper +``` + +Example PDB Using maxUnavailable: + +```yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: zk-pdb +spec: + maxUnavailable: 1 + selector: + matchLabels: + app: zookeeper +``` + +For example, if the above `zk-pdb` object selects the pods of a StatefulSet of size 3, both specifications have the exact same meaning. The use of `maxUnavailable` is recommended as it automatically responds to changes in the number of replicas of the corresponding controller. + +## Unhealthy Pod Eviction Policys + +PodDisruptionBudget guarding an application ensures that `.status.currentHealthy` number of pods does not fall below the number specified in `.statue.desiredHealthy` by disallowing eviction of healthy pods, By using `.spec.unhealthyPodEvictionPolicy`, you can also define the criteria when unhealthy pods should be considerd for eviction. The default behavior when no policy is specified corresponds to the `IfHealthyBudget` policy. + +### policies: + +- **IfHealthyBudget** + - Running pods(`.status.phase="Running"`), but not yet healthy can be evicted only if the guarded application is not disrupted (`.status.currentHealthy` is at lease equal to `.status.desiredHealthy`). + - This policy enssures that runnign pods of an already disrupted application have the best chance to become healthy. This has negative implications for draining nodes, which can be blocked by misbehaving applications that are guarded by a PDB. More specifically application wit pods in `CrashLoopBackOff` state (due to a bug or misconfiguration), or pods that are just failing to report the `Ready` condition. + +- **AlwaysAllow** + - Running pods (`.status.phase="Running`), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. + - This means prospective running pods of a disrupted application might not get a chance to become healthy. By using this policy, cluster managers can easily evict misbehaving applications that are quarded by a PDB. More specifically applications with pods in `CrashLoopBackOff` state (due to a bug or misconfiguration), or pods that are just failing to report the `Ready` condition. + +> Note: Pods in Pending, Succeeded or Failed phase are always considered for eviction. + +## Arbitrary workloads and arbitrary selectors + +You can skip this section if you only use PDBs with the built-in workload resources (Deployment, ReplicaSet, StatefulSet and ReplicationController) or with custom reosurces that implement a `scale` subresource, and where the PDB selector exactly matches the selector of the Pod's owning resource. + +You can use a PDB with pods controlled by another resource, by an "operator", or bare pods, but with tease restrictions: +- only `.spec.minAvailable` can be used, not `.spec.maxUnavailable`. +- only an integer value can be used with `.spec.minAvailable`, not a percentage. + +It is not possible to use other availability configurations, because Kubernetes cannot derive a total number of pods without a supported owning resource. + +You can use a selector which selects a subset or superset of the pods beloging to a workload resource. The eviction API will disallow eviction of any pod covered by multiple PDBs, so most users will want to avoid overlappign selectors. One reasonable use of overlapping PDBs is when pods are being transitioned from on PDB to onother. + +--- +reference +- https://kubernetes.io/docs/tasks/run-application/configure-pdb/ +- https://medium.com/@suhasveil/building-highly-available-applications-on-kubernetes-f7e5601b7cb8 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Endpoints.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Endpoints.md" new file mode 100644 index 00000000..5eeef9c7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Endpoints.md" @@ -0,0 +1,40 @@ +--- +title: 'Endpoints' +lastUpdated: '2024-03-02' +--- + +쿠버네티스의 Services는 뒷단에 있는 Pod의 label 정보를 바탕으로한 selector로 그 대상이 되는 Pod를 매칭한다. 만약 해당 Label을 단 새로운 Pod이 생겼다면, Service는 자동으로 그 Pod과 연결되어 트래픽을 보낸다. + +이러한 일이 가능한 것은 service가 트래픽을 보낼 **오브젝트를 추적**해주는 **`EndPoint`**가 있기 때문이다. 매칭된 Pod의 IP 주소는 그 service의 endpoint가 되어 연결된다. + +service는 endpoints를 통해 트래픽을 보내는 주소의 목록을 관리한다. endpoints는 labels와 selectors를 통해 자동으로 업데이트 될 수 있다. 그리고 경우에 따라 수동으로 endpoints를 설정할 수 있다. + +service selector가 Pod의 label에 매칭되면 endpoints가 자동으로 만들어진다. + +```js +kubectl apply -f [manifest file].yml +``` + +`myapp`이라는 이름의 Deployment와 Service를 생성하는 minifest file을 실행하고 endpoint를 확인해보면 + +```js +$ kubectl get endpoints + +NAME ENDPOINT AGE +myapp 10.244.1.143:80,10.244.2.204:80 23h +Kuberneted 10.10.50.50.:6443 11d + +``` + +두개의 endpoints를 가지고 있으며 모두 80 포트라는것을 확인 할 수 있다. 이 endpoint들은 매니페스트로 배포한 pod들의 ip 주소여야 한다. 이것을 확인하기위해 get pods 커맨드를 `-o wide` 옵션과 함께 확인해보자. + +```js +$ kubectl get endpoints -o wide + +NAME READY STATUS RESTARTS AGE IP NODE +myapp-deployment-6d99f57cb4-ngc4g 1/1 Running 0 1h 10.244.1.143:80 kube-node2 +myapp-deployment-6d99f57cb4-x5gsm 1/1 Running 0 1h 10.244.2.204:80 kube-node1 + +``` + +Pod의 IP 주소들이 엔드포인트의 주소들과 매칭된다는 것을 확인할 수 있다. 보이지 않는 곳에서 endpoints가 매칭된다는 사실을 확인했다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/HPA\354\231\200\342\200\205VPA.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/HPA\354\231\200\342\200\205VPA.md" new file mode 100644 index 00000000..c64958b7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/HPA\354\231\200\342\200\205VPA.md" @@ -0,0 +1,135 @@ +--- +title: 'HPA와 VPA' +lastUpdated: '2024-03-02' +--- + +## HPA(Horizontal Pod Autoscaler) + +image + +- **Horizontal Pod Autoscaler**는 metric server를 통해 파드의 리소스를 감시하여 리소스가 부족한 경우 Controller의 replicas를 증가시켜 파드의 수를 늘린다. +- 위의 그림 처럼 Pod가 수평적으로 증가하는 것을 Scale Out, 수평적으로 감소하는 것을 Scale In 이라고 한다. +- Pod를 증가시키기 때문에 기존의 트래픽이 분산되어 서비스를 더 안정적으로 유지할 수 있게 된다. +- Replica의 수와 상관 없이 돌아갈 수 있는 Stateless 서비스에 적합하다. +- 트래픽이 급증하여 spike가 생기는 경우에 대응할 수 있다. +- 사용하는 매트릭과, 목표하는 매트릭을 계산하여 desire replica 수를 계산한다. + + ```bash + desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )] + ``` + +- Pod가 시작하고 얼마 되지 않았을 때는 적절한 메트릭 값이 나오지 않을 수 있으므로, HPA에는 시작한 지 30초 이상 된 포드부터 매트릭이 적용된다. `--horizontal-pod-autoscaler-initial-readiness-delay` 옵션을 사용하여 이 값을 직접 설정할 수 있다. + +- **예시** + ```yaml + apiVersion: autoscaling/v1 + kind: HorizontalPodAutoscaler + metadata: + name: k8s-autoscaler + spec: + maxReplicas: 10 + minReplicas: 2 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: k8s-autoscaler + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 60 + behavior: + scaleDown: + policies: + - type: Pods + value: 4 + periodSeconds: 60 + - type: Percent + value: 10 + periodSeconds: 60 + ``` + +### Container resource + +- HorizontalPodAutoscaler API는 Pod 뿐만 아니라 각 컨테이너의 리소스도 스케일링의 조건으로 넣을 수 있도록 하는 설정을 제공한다. + +```yaml +... +type: ContainerResource +containerResource: + name: cpu + container: application + target: + type: Utilization + averageUtilization: 60 +... +``` + +### Scaling policies + +- `spec`의 `behavior` 부분에 스케일링을 위한 정책을 설정할 수 있다. +- 위에 정의된 policy부터 적용된다. +- `periodSeconds`는 특정 시간 안에 scale을 조정할 수 있는 최대, 최소값을 정의한다. + - 아래 예시에서는 60초동안 최대 4개의 replica가 scale down 될 수 있고, 60초 동안 현재의 최대 10% 만큼 scale down될 수 있다. + +```yaml +... +behavior: + scaleDown: + policies: + - type: Pods + value: 4 + periodSeconds: 60 + - type: Percent + value: 10 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 0 # 메트릭들이 계속 변동하여 오차가 발생하는 것을 조정하기 위해 사용하는 옵션 + policies: + - type: Percent + value: 100 + periodSeconds: 15 + - type: Pods + value: 4 + periodSeconds: 15 + selectPolicy: Max +... +``` + +## VPA(Vertical Pod Autoscaler) + +image + +- Vertical Pod Autoscaler는 파드의 리소스를 감시하여, 파드의 리소스가 부족한 경우 파드를 Restart하며 파드의 리소스 제한을 증가시킨다. +- 이처럼 파드의 리소스가 수직적으로 증가하는 것을 Scale Up, 감소하는 것을 Scale Down이라고 한다. +- 리소스 활용률을 최적화하고 비용을 절감할 수 있다. +- 컨테이너의 리소스 Request를 조정한다. + +- **예시** + ```yaml + apiVersion: autoscaling.k8s.io/v1 + kind: VerticalPodAutoscaler + metadata: + name: k8s-autoscaler-vpa + spec: + targetRef: + apiVersion: "apps/v1" + kind: Deployment + name: k8s-autoscaler + updatePolicy: + updateMode: "Auto" + ``` + +--- + +## 여담 + +- HPA와 VPA는 보통 Kubernetes component에 있는 metric 서버가 제공하는 값을 받아 스케일링 여부를 결정하는데, 원한다면 다른 custom metric을 적용할 수 있다. [(참고)](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-metrics-apis) + +--- +참고 +- https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ +- https://medium.com/nerd-for-tech/autoscaling-in-kubernetes-hpa-vpa-ab61a2177950 +- https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/K8s\354\235\230\342\200\205\353\217\204\354\273\244\353\237\260\355\203\200\354\236\204\342\200\205\354\202\254\354\232\251\354\244\221\353\213\250.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/K8s\354\235\230\342\200\205\353\217\204\354\273\244\353\237\260\355\203\200\354\236\204\342\200\205\354\202\254\354\232\251\354\244\221\353\213\250.md" new file mode 100644 index 00000000..b063fdc3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/K8s\354\235\230\342\200\205\353\217\204\354\273\244\353\237\260\355\203\200\354\236\204\342\200\205\354\202\254\354\232\251\354\244\221\353\213\250.md" @@ -0,0 +1,42 @@ +--- +title: 'K8s의 도커런타임 사용중단' +lastUpdated: '2024-03-02' +--- + +![image](https://user-images.githubusercontent.com/81006587/201903310-cec614e1-d458-40be-afc3-9df77529e4d5.png) + +Source : https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/ + +쿠버네티스는 버전 v1.20 이후 Docker를 컨테이너 런타임으로서 사용하지 않겠다고 알렸다.(`2020.12.02`) + +GKE 및 EKS 등의 관리 Kubernetes 서비스를 사용하는 경우 오퍼레이터에서 지원되는 버전의 런타임을 사용하는 것을 확인하고 쿠버네티스 릴리스에서 도커 지원이 만료되기 전에 변경 해야한다. + +자세한 내용은 아래와 같다. + +> Deprecation
Docker support in the kubelet is now deprecated and will be removed in a future release. The kubelet uses a module called “dockershim” which implements CRI support for Docker and it has seen maintenance issues in the Kubernetes community. We encourage you to evaluate moving to a container runtime that is a full-fledged implementation of CRI (v1alpha1 or v1 compliant) as they become available. (#94624, @dims) [SIG Node] + +쿠버네티스는 컨테이너 런타임과 통신 할 때 CRI라는 표준 인터페이스 API를 사용하지만 Docker는 이를 지원하지 않는다. + +이런 이유로 쿠버네티스는 “dockershim”라는 브리지용 서비스로 Docker API와 CRI의 변환을 해주었으나, 이것이 deprecation 되면서 앞으로 마이너 릴리스 된 후에 도커가 삭제될 예정이다. 라는 뜻의 글이다. + +## 쿠버네티스는 왜 도커 지원을 중단했을까? + +가장 큰 이유는 도커는 CRI(Container Runtime Interface) 와 호환성이 없다는 것이다. (CRI와 컨타이너 런타임에 대한 내용은 여기에서 더 알아볼 수 있다.) + +Docker는 Kubernetes에 통합되도록 설계되어 있지 않기 때문에 많은 문제가 있다. + +쿠버네티스에서 도커를 사용하기 위해 필요한 Dockershim 은 유지 보수 비용이 높다는 지적이 있었고, 도커는 쿠버네티스에서는 사용하지 않는 많은 기능들이 포함되어 있어 자원의 오버 헤드가 높다는 문제로 런타임으로써의 지원을 중단한 것이다. + +## 도커를 사용하던 사용자에게 어떤 영향을 미치는 걸까? + +개발용으로 Docker 를 사용하는 것은 쿠버네티스 클러스터의 런타임과는 아무 상관이 없다. + +또한 OCI 표준을 준수하는 이미지는 도구에 관계없이 쿠버네티스에서 동일하게 사용할 수 있다. containerd와 CRI-O는 기존 도커 이미지와 호환성이 뛰어나다. + +이것이 바로 컨테이너 표준이 만들어진 이유이다. + +--- + +정리하자면, K8s 내부에서 도커라는 컨테이너 런타임을 사용하지 않겠다는 것이다. + +개발과정에서 도커를 사용하고, 도커에서 이미지를 빌드하는 것은 괜찮다. (OCI 표준을 지키고 있기 때문) diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Kubeproxy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Kubeproxy.md" new file mode 100644 index 00000000..37dde904 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Kubeproxy.md" @@ -0,0 +1,446 @@ +--- +title: 'Kubeproxy' +lastUpdated: '2024-03-02' +--- + +Networking is a crucial part of Kubernetes. Behind the Kubernetes network, there is a component that work under the hood. It trandlates your Services into some usable networkign rules. This componenet is called Kube-Proxy. `kube-proxy` is another per-node daemon in Kubernetes, like Kubelet. + +`kube-proxy` provides **basic load balancing functionality within the cluster**. It implements services and relies on Endpoints/EndpointSlices. It may help to reference that section, but the following is the relevant and quick explanation: + +- Services define a load balancer for a set of pods. +- Endpoints (and endpoint slices) list a set of ready pod IPs. They are created automatically from a service, with the same pod selector as the service. + +Most types of services have an **IP address for the service, called the cluster IP address, which is not routable outside the cluster**. + +`kube-proxy` is responsible for routing requests to a service’s cluster IP address to healthy pods. `kube-proxy` is by far the most common implementation for Kubernetes services, but there are alternatives to `kube-proxy`, such as a replacement mode Cilium. + +kube-proxy has four modes, which change its runtime mode and exact feature set: +- `userspace` +- `iptables` +- `ipvs` +- `kernelspace` + +You can specify the mode using `--proxy-mode `. It’s worth noting that all modes rely on iptables to some extent. + +And You can check the mode that using below command + +``` +kubectl logs -f [YOUR_POD_NAME] -n kube-system +``` + +## userspace Mode + +image + + +
+Chains +
+ +--- + +**KUBE-PORTALS-CONTAINER** + +```bash +Chain KUBE-PORTALS-CONTAINER (1 references) + pkts bytes target prot opt in out source destination + 0 0 REDIRECT tcp -- * * 0.0.0.0/0 10.96.98.173 /* default/my-nginx-loadbalancer: */ tcp dpt:80 redir ports 38023 + 0 0 REDIRECT tcp -- * * 0.0.0.0/0 172.35.0.200 /* default/my-nginx-loadbalancer: */ tcp dpt:80 redir ports 38023 + 0 0 REDIRECT tcp -- * * 0.0.0.0/0 10.103.1.234 /* default/my-nginx-cluster: */ tcp dpt:80 redir ports 36451 + 0 0 REDIRECT tcp -- * * 0.0.0.0/0 10.97.229.148 /* default/my-nginx-nodeport: */ tcp dpt:80 redir ports 44257 +``` + +--- + +**KUBE-NODEPORT-CONTAINER** + +```bash +Chain KUBE-NODEPORT-CONTAINER (1 references) + pkts bytes target prot opt in out source destination + 0 0 REDIRECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: */ tcp dpt:30781 redir ports 38023 + 0 0 REDIRECT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-nodeport: */ tcp dpt:30915 redir ports 44257 +``` + +--- + +**KUBE-PORTALS-HOST** + +```bash +Chain KUBE-PORTALS-HOST (1 references) + pkts bytes target prot opt in out source destination + 0 0 DNAT tcp -- * * 0.0.0.0/0 10.96.98.173 /* default/my-nginx-loadbalancer: */ tcp dpt:80 to:172.35.0.100:38023 + 0 0 DNAT tcp -- * * 0.0.0.0/0 172.35.0.200 /* default/my-nginx-loadbalancer: */ tcp dpt:80 to:172.35.0.100:38023 + 0 0 DNAT tcp -- * * 0.0.0.0/0 10.103.1.234 /* default/my-nginx-cluster: */ tcp dpt:80 to:172.35.0.100:46635 + 0 0 DNAT tcp -- * * 0.0.0.0/0 10.97.229.148 /* default/my-nginx-nodeport: */ tcp dpt:80 to:172.35.0.100:32847 +``` + +--- + +**KUBE-NODEPORT-HOST** + +```bash +Chain KUBE-NODEPORT-HOST (1 references) + pkts bytes target prot opt in out source destination + 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: */ tcp dpt:30781 to:172.35.0.100:38023 + 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-nodeport: */ tcp dpt:30915 to:172.35.0.100:44257 +``` + +
+
+ +The first and oldest mode is `userspace mode`. In userspace mode, `kube-proxy` runs a web server and **routes all service IP addresses to the web server, using iptables.** The web server terminates connections and proxies the request to a pod in the service’s endpoints. + +**userspace mode's disadvantage** + +The kube-proxy belongs to the User Space area as it operates as a Process. And Netfilter, which is in charge of Host's networking, belongs to the Kernel area. + +Essentially, the operation of User Space (Process) is done through Kernel. The User Space program is much slower than the Kernel's own service because it has a system that requests services from Kernel when the Process needs CPU time for calculation, disk for I/O operations, and memory. + +The kube-proxy in UserSpace Mode requires a lot of access between UserSpace and Kernel because most networking tasks such as load balancing and packet rule setting are mainly controlled by the kube-proxy itself, which is a process. Because of these issues, the kube-proxy in UserSpace Mode has a problem of slowing networking speed. So userspace mode is no longer commonly used, and we suggest avoiding it unless you have a clear reason to use it. + +## iptables Mode + +image + +- Since the request packet transmitted from most Pod is delivered to the Host's Network Namespace through the Pod's veth, the request packet is delivered to the `KUBE-SERVICES Table` by the `PREROUTING Table`. + - The request packet sent by Pod or Host Process using Host's Network Namespace is delivered to the `KUBE-SERVICES Table` by the `OUTPUT Table`. + +- `KUBE-SERVICES Table` + + - If the Dest IP and Dest Port of the request packet match the IP and Port of the ClusterIP Service, the request packet is forwarded to the NAT table of the matching ClusterIP Service, `KUBE-SVC-XXX Table`. + + - If the Dest IP of the request packet is Node's own IP, the request packet is delivered to the `KUBE-NODEPORTS Table`. + +- `KUBE-NODEPORTS Table` + + - If the Dest Port of the request packet matches the Port of the NodePort Service, the request packet is forwarded to the NAT Table of the NodePort Service, `KUBE-SVC-XXX Table`. + +- `KUBE-SERVICES Table` + + - If the Dest IP and Dest Port of the request packet match the External IP and Port of the Load Balancer Service, the request packet is delivered to the `KUBE-FW-XXX Table`, the NAT Table of the matching Load Balancer Service, and then to the `KUBE-SVC-XXX Table`. + +
+Chains +
+ +--- + +**KUBE-SERVICES** +```bash +Chain KUBE-SERVICES (2 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-MARK-MASQ tcp -- * * !192.167.0.0/16 10.96.98.173 /* default/my-nginx-loadbalancer: cluster IP */ tcp dpt:80 + 0 0 KUBE-SVC-TNQCJ2KHUMKABQTD tcp -- * * 0.0.0.0/0 10.96.98.173 /* default/my-nginx-loadbalancer: cluster IP */ tcp dpt:80 + 0 0 KUBE-FW-TNQCJ2KHUMKABQTD tcp -- * * 0.0.0.0/0 172.35.0.200 /* default/my-nginx-loadbalancer: loadbalancer IP */ tcp dpt:80 + 0 0 KUBE-MARK-MASQ tcp -- * * !192.167.0.0/16 10.103.1.234 /* default/my-nginx-cluster: cluster IP */ tcp dpt:80 + 0 0 KUBE-SVC-52FY5WPFTOHXARFK tcp -- * * 0.0.0.0/0 10.103.1.234 /* default/my-nginx-cluster: cluster IP */ tcp dpt:80 + 0 0 KUBE-MARK-MASQ tcp -- * * !192.167.0.0/16 10.97.229.148 /* default/my-nginx-nodeport: cluster IP */ tcp dpt:80 + 0 0 KUBE-SVC-6JXEEPSEELXY3JZG tcp -- * * 0.0.0.0/0 10.97.229.148 /* default/my-nginx-nodeport: cluster IP */ tcp dpt:80 + 0 0 KUBE-NODEPORTS all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL +``` + +--- + +**KUBE-NODEPORTS** + +```bash +Chain KUBE-NODEPORTS (1 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: */ tcp dpt:30781 + 0 0 KUBE-SVC-TNQCJ2KHUMKABQTD tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: */ tcp dpt:30781 + 0 0 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-nodeport: */ tcp dpt:30915 + 0 0 KUBE-SVC-6JXEEPSEELXY3JZG tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-nodeport: */ tcp dpt:30915 +``` + +--- + +**KUBE-FW-XXX** + +```bash +Chain KUBE-FW-TNQCJ2KHUMKABQTD (1 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-MARK-MASQ all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: loadbalancer IP */ + 0 0 KUBE-SVC-TNQCJ2KHUMKABQTD all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: loadbalancer IP */ + 0 0 KUBE-MARK-DROP all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/my-nginx-loadbalancer: loadbalancer IP */ +``` + +--- + +**KUBE-SVC-XXX** + +```bash +Chain KUBE-SVC-TNQCJ2KHUMKABQTD (2 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-SEP-6HM47TA5RTJFOZFJ all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.33332999982 + 0 0 KUBE-SEP-AHRDCNDYGFSFVA64 all -- * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.50000000000 + 0 0 KUBE-SEP-BK523K4AX5Y34OZL all -- * * 0.0.0.0/0 0.0.0.0/0 +``` + +--- + +**KUBE-SEP-XXX** + +```bash +Chain KUBE-SEP-6HM47TA5RTJFOZFJ (1 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-MARK-MASQ all -- * * 192.167.2.231 0.0.0.0/0 + 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp to:192.167.2.231:80 +``` + +--- + +**KUBE-POSTROUTING** + +```bash +Chain KUBE-POSTROUTING (1 references) + pkts bytes target prot opt in out source destination + 0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match +0x4000/0x4000 +``` + +--- + +**KUBE-MARK-MASQ** + +```bash +Chain KUBE-MARK-MASQ (23 references) + pkts bytes target prot opt in out source destination + 0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000 +``` + +--- + +**KUBE-MARK-DROP** + +```bash +Chain KUBE-MARK-DROP (10 references) + pkts bytes target prot opt in out source destination + 0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x8000 +``` + +
+
+ + +### Source IP + +image + +The Src IP of the Service Request Packet is maintained or SNATed to the IP of the Host through Masquerade. **The `KUBE-MARK-MASQ` Table is a table that performs marking on the packet for Masquerade of the request packet.** The marked packet is Masquerade in the `KUBE-POSTROUTING` Table, and the Src IP is SNATed to the Host's IP. If you look at the iptables tables, you can see that the packet to be performed Masquerade is marked through the `KUBE-MARK-MASQ` Table. + +If the `externalTrafficPolicy` value is set to Local, the `KUBE-NODEPORTS` Table disappears from the `KUBE-MARK-MASQ` Table, and Masquerade is not performed. Therefore, the Src IP of the request packet is attracted as it is. In addition, the request packet is not Load Balanced on the Host, but is delivered only to the Target Pod driven on the Host where the request packet was delivered. If the request packet is delivered to a Host without a Target Pod, the request packet is dropped. + +The left side of the figure below shows a figure that does not perform Masquerade by setting the external Traffic Policy to Local. + +image + +`ExternalTrafficPolicy` Local is mainly used in `LoadBalancer` Service. This is because the Src IP of the request packet can be maintained, and the Load Balancer of the Cloud Provider performs Load Balancing, so the Load Balancing process of the Host is unnecessary. + +If the `externalTrafficPolicy` value is Local, the packet is dropped on the Host without the Target Pod, so during the Host Health Check process performed by the LoadBalancer of the Cloud Provider, the Host without the Target Pod is excluded from the Load Balancing target. Therefore, Cloud Provider's Load Balancer only load balances the request packet to the Host with the Target Pod. + +Masquerade is also necessary if the request packet is returned to itself by sending the request packet from Pod to the IP of the service to which it belongs. +- The left side of the figure above shows this case. The request packet is DNATed, and both the Src IP and Dest IP of the packet become Pod's IP. +- Therefore, if you send a response packet to a request packet returned from Pod, SNAT is not performed because the packet is processed in Pod without going through the NAT Table of the Host. + +- Masquerade allows you to **force the request packet returned to Pod to the Host so that SNAT can be performed**. In this way, the technique of deliberately bypassing and receiving a packet is called **hairpinning**. + - The right side of the above figure shows the case of applying hairpinning using Masqurade. + +- In the `KUBE-SEP-XXX` Table, if the Src IP of the request packet is the same as the IP to be DNAT, that is, if the packet that Pod sent to the Service is received by itself, the request packet is marked through the `KUBE-MARK-MASQ` Table and Masquerade in the `KUBE-POSTROUTING` Table. +- Since the Src IP of the packet that Pod received is set to the IP of the Host, Pod's response is delivered to the Host's NAT Table, and then SNAT, DNAT, and delivered to Pod. + +## ipvs Mode + +image + + +- Since the request packet transmitted from most Pod is delivered to the Host's Network Namespace through the Pod's veth, the request packet is delivered to the `KUBE-SERVICES Table` by the `PREROUTING Table`. + - The request packet sent by Pod or Host Process using Host's Network Namespace is delivered to the `KUBE-SERVICES Table` by the `OUTPUT Table`. (same with iptable mode) + +- `KUBE-SERVICES Table` + + - If the Dest IP and Dest Port of the request packet match the IP and Port of the ClusterIP Service, the request packet is delivered to the **`IPVS`.** + + - If the Dest IP of the request packet is Node's own IP, the request packet is delivered to the **`IPVS` via the `KUBE-NODE-PORT` Table.** + +- `KUBE-NODEPORTS Table` + + - If the Dest Port of the request packet matches the Port of the NodePort Service, the request packet is delivered to the **`IPVS` via the `KUBE-NODE-PORT` Table.** + - If the Default Rule of the `PREROUTING` and `OUTPUT` Table is Accept, the packet delivered to the service is delivered to the `IPVS` even without the `KUBE-SERVICES` Table, **so the service is not affected**. + + +- IPVS performs DNAT in the following situations with the port set by Load Balancing and Pod's IP and Service. + - If the Dest IP, Dest Port in the request packet matches the Cluster-IP and Port in the service, + - If the Dest IP of the request packet is Node's own IP and the Dest Port matches the NodePort of the NodePort Service, + - If the Dest IP, Dest Port of the request packet matches the External IP and Port of the LoadBalancer Service, + +- The request packet DNATed to Pod's IP is delivered to the Pod through a container network built through CNI Plugin. [IPVS List] shows that Load Balancing and DNAT are performed for all IPs associated with services. + +- Like iptables, IPVS also uses TCP Connection information of Contrack of Linux Kernel. Therefore, **the response packet of the service packet** sent by DNAT due to the IPVS is SNATed again by the IPVS and delivered to the Pod or Host Process that requested the service. + +- In IPVS Mode, like the iptables Mode, **hairpinning is applied to solve the SNAT problem in the service response packet.** In the `KUBE-POSTROUTING` Table, if the `KUBE-LOOP-BACK` IPset rule is met, Masquerade is performed. + - It can be seen that the `KUBE-LOOP-BACK` IPset contains the **number of all cases where the Src IP and Dest IP of the packet can be IP of the same Pod**. + +
+Chains +
+ +--- + +**KUBE-SERVICES** + +```bash +Chain KUBE-SERVICES (2 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-LOAD-BALANCER all -- * * 0.0.0.0/0 0.0.0.0/0 /* Kubernetes service lb portal */ match-set KUBE-LOAD-BALANCER dst,dst + 0 0 KUBE-MARK-MASQ all -- * * !192.167.0.0/16 0.0.0.0/0 /* Kubernetes service cluster ip + port for masquerade purpose */ match-set KUBE-CLUSTER-IP dst,dst + 8 483 KUBE-NODE-PORT all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL + 0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst + 0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 match-set KUBE-LOAD-BALANCER dst,dst +``` + +--- + +**KUBE-NODE-PORT** + +```bash +Chain KUBE-NODE-PORT (1 references) + pkts bytes target prot opt in out source destination + 6 360 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* Kubernetes nodeport TCP port for masquerade purpose */ match-set KUBE-NODE-PORT-TCP dst +``` + +--- + +**KUBE-LOAD-BALANCER** + +```bash +Chain KUBE-LOAD-BALANCER (1 references) + pkts bytes target prot opt in out source destination + 0 0 KUBE-MARK-MASQ all -- * * 0.0.0.0/0 0.0.0.0/0 +``` + +--- + +**KUBE-POSTROUTING** + +```bash +Chain KUBE-POSTROUTING (1 references) + pkts bytes target prot opt in out source destination + 0 0 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000 + 1 60 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* Kubernetes endpoints dst ip:port, source ip for solving hairpinpurpose */ match-set KUBE-LOOP-BACK dst,dst,src +``` + +--- + +**KUBE-MARK-MASQ** + +```bash +Chain KUBE-MARK-MASQ (3 references) + pkts bytes target prot opt in out source destination + 2 120 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000 +``` + +--- + +**IPVS List** + +```bash +Name: KUBE-CLUSTER-IP +Type: hash:ip,port +Revision: 5 +Header: family inet hashsize 1024 maxelem 65536 +Size in memory: 600 +References: 2 +Number of entries: 1 +Members: +10.96.98.173,tcp:80 +10.97.229.148,tcp:80 +10.103.1.234,tcp:80 + +Name: KUBE-LOOP-BACK +Type: hash:ip,port,ip +Revision: 5 +Header: family inet hashsize 1024 maxelem 65536 +Size in memory: 896 +References: 1 +Number of entries: 3 +Members: +192.167.2.231,tcp:80,192.167.2.231 +192.167.1.123,tcp:80,192.167.1.123 +192.167.2.206,tcp:80,192.167.2.206 + +Name: KUBE-NODE-PORT-TCP +Type: bitmap:port +Revision: 3 +Header: range 0-65535 +Size in memory: 8268 +References: 1 +Number of entries: 2 +Members: +30781 +30915 + +Name: KUBE-LOAD-BALANCER +Type: hash:ip,port +Revision: 5 +Header: family inet hashsize 1024 maxelem 65536 +Size in memory: 152 +References: 2 +Number of entries: 1 +Members: +172.35.0.200,tcp:80 +``` + +--- + +**IPset List** + +```bash +TCP 172.35.0.100:30781 rr + -> 192.167.1.123:80 Masq 1 0 0 + -> 192.167.2.206:80 Masq 1 0 0 + -> 192.167.2.231:80 Masq 1 0 0 +TCP 172.35.0.100:30915 rr + -> 192.167.1.123:80 Masq 1 0 0 + -> 192.167.2.206:80 Masq 1 0 0 + -> 192.167.2.231:80 Masq 1 0 0 +TCP 172.35.0.200:80 rr + -> 192.167.1.123:80 Masq 1 0 0 + -> 192.167.2.206:80 Masq 1 0 0 + -> 192.167.2.231:80 Masq 1 0 0 +TCP 10.96.98.173:80 rr + -> 192.167.1.123:80 Masq 1 0 0 + -> 192.167.2.206:80 Masq 1 0 0 + -> 192.167.2.231:80 Masq 1 0 0 +TCP 10.97.229.148:80 rr + -> 192.167.1.123:80 Masq 1 0 0 + -> 192.167.2.206:80 Masq 1 0 0 + -> 192.167.2.231:80 Masq 1 0 0 +TCP 10.103.1.234:80 rr + -> 192.167.1.123:80 Masq 1 0 0 + -> 192.167.2.206:80 Masq 1 0 0 + -> 192.167.2.231:80 Masq 1 0 0 +``` +
+
+ +`ipvs mode` uses **[IPVS](../../../네트워크 Network/L2 internet layer/IPVS.md) for connection load balancing**. ipvs mode supports six load balancing modes, specified with `--ipvs-scheduler`: + +- `rr`: Round-robin +- `lc`: Least connection +- `dh`: Destination hashing +- `sh`: Source hashing +- `sed`: Shortest expected delay +- `nq`: Never queue + +Round-robin (`rr`) is the default load balancing mode. It is the closest analog to iptables mode’s behavior (in that connections are made fairly evenly regardless of pod state), though iptables mode does not actually perform round-robin routing. + +### kernelspace Mode + +`kernelspace` is the newest, Windows-only mode. It provides an alternative to `userspace` mode for Kubernetes on Windows, as `iptables` and `ipvs` are specific to Linux. + +--- +reference +- https://www.slideshare.net/Docker/deep-dive-in-container-service-discovery?from_action=save +- https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/ +- https://ikcoo.tistory.com/130 +- https://ssup2.github.io/theory_analysis/Kubernetes_Service_Proxy/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/NodePort\354\231\200\342\200\205ServicePort\354\231\200\342\200\205targetPort.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/NodePort\354\231\200\342\200\205ServicePort\354\231\200\342\200\205targetPort.md" new file mode 100644 index 00000000..9156cfdb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/NodePort\354\231\200\342\200\205ServicePort\354\231\200\342\200\205targetPort.md" @@ -0,0 +1,26 @@ +--- +title: 'NodePort와 ServicePort와 targetPort' +lastUpdated: '2024-03-02' +--- + +K8s의 포트는 노드와 서비스, 컨테이너별로 나뉘어있다. 이 개념에 대해 확실히 알아보자. + +만약에 하나의 서비스에 2개의 노드가 있고, 그 노드에 각각 하나의 포드가 있다고 하면 아래 그림과 같은 모양이 된다. + + + +NodePort는 각 노드의 클러스터 레벨에서 노출되는 포트, (그림에서 `30001`) + +Port는 서비스의 포트, (`80`) + +targetPort는 포드에서 컨테이너로 가는 앱 컨테이너 포트를 말한다. + +아래와 같이 설정할 수 있다. + +```yml + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + nodePort: 30000 +``` diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/Assigning\342\200\205Pods\342\200\205to\342\200\205Nodes.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/Assigning\342\200\205Pods\342\200\205to\342\200\205Nodes.md" new file mode 100644 index 00000000..7cafa905 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/Assigning\342\200\205Pods\342\200\205to\342\200\205Nodes.md" @@ -0,0 +1,121 @@ +--- +title: 'Assigning Pods to Nodes' +lastUpdated: '2024-03-02' +--- + +You can constrain a Pod so that it is restricted to run on particular node(s), or to prefer to run on particular nodes. + +There are several ways to do this and the recommended approaches all use label selectors to facilitate the selection. Often, you do not need to set any such constraints; the scheduler will automatically do a reasonable placement (for example, spreading your Pods across nodes so as not place Pods on a node with insufficient free resources). However, there are some circumstances where you may want to control which node the Pod deploys + +You can use any of the following methods to choose where Kubernetes schedules specific Pods: + +- nodeSelector field matching against node labels +- Affinity and anti-affinity +- nodeName field +- Pod topology spread constraints + +--- + +## nodeSelector + +Like many other Kubernetes objects, nodes have labels. You can attach labels manually. + +Adding labels to nodes allows you to target Pods for scheduling on specific nodes or groups of nodes. You can use this functionality to ensure that specific Pods only run on nodes with certain isolation, security, or regulatory properties. + +nodeSelector is the simplest recommended form of node selection constraint. You can add the nodeSelector field to your Pod specification and specify the node labels you want the target node to have. Kubernetes only schedules the Pod onto nodes that have each of the labels you specify. + +--- + +## Affinity and anti-affinity + +nodeSelector is the simplest way to constrain Pods to nodes with specific labels. Affinity and anti-affinity expands the types of constraints you can define. Some of the benefits of affinity and anti-affinity include: + +- The affinity/anti-affinity language is more expressive. nodeSelector only selects nodes with all the specified labels. Affinity/anti-affinity gives you more control over the selection logic. + +- You can indicate that a rule is soft or preferred, so that the scheduler still schedules the Pod even if it can't find a matching node. + +- You can constrain a Pod using labels on other Pods running on the node (or other topological domain), instead of just node labels, which allows you to define rules for which Pods can be co-located on a node. + +The affinity feature consists of two types of affinity: + +- Node affinity functions like the nodeSelector field but is more expressive and allows you to specify soft rules. +Inter-pod affinity/anti-affinity allows you to constrain Pods against labels on other Pods. + +### Node affinity weight + +You can specify a weight between 1 and 100 for each instance of the `preferredDuringSchedulingIgnoredDuringExecution` affinity type. When the scheduler finds nodes that meet all the other scheduling requirements of the Pod, the scheduler iterates through every preferred rule that the node satisfies and adds the value of the weight for that expression to a sum. + +The final sum is added to the score of other priority functions for the node. Nodes with the highest total score are prioritized when the scheduler makes a scheduling decision for the Pod. + + +```yml +apiVersion: v1 +kind: Pod +metadata: + name: with-affinity-anti-affinity +spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + preference: + matchExpressions: + - key: label-1 + operator: In + values: + - key-1 + - weight: 50 + preference: + matchExpressions: + - key: label-2 + operator: In + values: + - key-2 + containers: + - name: with-node-affinity + image: registry.k8s.io/pause:2.0 +``` + + +If there are two possible nodes that match the `preferredDuringSchedulingIgnoredDuringExecution` rule, one with the `label-1:key-1` label and another with the label-2:key-2 label, the scheduler considers the weight of each node and adds the weight to the other scores for that node, and schedules the Pod onto the node with the highest final score. + +--- + +## nodeName + +nodeName is a more direct form of node selection than affinity or nodeSelector. nodeName is a field in the Pod spec. If the nodeName field is not empty, the scheduler ignores the Pod and the kubelet on the named node tries to place the Pod on that node. Using nodeName overrules using nodeSelector or affinity and anti-affinity rules. + +Some of the limitations of using nodeName to select nodes are: + +- If the named node does not exist, the Pod will not run, and in some cases may be automatically deleted. +- If the named node does not have the resources to accommodate the Pod, the Pod will fail and its reason will indicate why, for example OutOfmemory or OutOfcpu. +- Node names in cloud environments are not always predictable or stable. + +```yml +apiVersion: v1 +kind: Pod +metadata: + name: nginx +spec: + containers: + - name: nginx + image: nginx + nodeName: kube-01 +``` + +--- + +## Pod topology spread constraints + +You can use topology spread constraints to control how Pods are spread across your cluster among failure-domains such as regions, zones, nodes, or among any other topology domains that you define. You might do this to improve performance, expected availability, or overall utilization. + +--- +reference +- https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/Taints\342\200\205and\342\200\205Tolerations.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/Taints\342\200\205and\342\200\205Tolerations.md" new file mode 100644 index 00000000..593ec72a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/Taints\342\200\205and\342\200\205Tolerations.md" @@ -0,0 +1,136 @@ +--- +title: 'Taints and Tolerations' +lastUpdated: '2024-03-02' +--- + +Node affinity is a property of Pods that attracts them to a set of nodes (either as a preference or a hard requirement) **Taints are the opposite** -- they allow a node to repel a set of pods. + +**Tolerations are applied to pods.** Tolerations allow the scheduler to schedule pods with matching taints. Tolerations allow scheduling but don't guarantee scheduling: the scheduler also evaluates other parameters as part of its function. + +Taints and tolerations work together to ensure that pods are not scheduled onto inappropriate nodes. One or more tains are applied to a node; this marks that the node should not accept any pods that do not tolerate the taints. + +In a nutshell +- `taint` : Set per node. Pod is not scheduled for the node you set up +- `Toleration` : Taint can be ignored + +There are three options available for taint. + +- `NoSchedule` : Pod is not scheduled without tolerance, not applied to existing pods. +- `PreferredNoSchedule`: If there is no tolerance, you do not want to schedule a pod but it is not mandatory, but if there are insufficient resources in the cluster, the pod can also be scheduled on nodes with taint. +- `NoExecute`: If there is no tolerance, the pod will not be scheduled, and if there is no tolerance, the existing pod will be terminated. + +## Concepts + +You add a taint to a node using `kubectl taint` + +```bash +kubectl taint nodes node1 key1=value1:NoSchedule +``` + +places a taint on node `node1`. The taint has key `key1`, value `value1`, and taint effect `NoSchedule`. This means that no pod will be able to schedule onto `node1` unless it has a matching toleration. + +To remove the taint added by the xommand above, you can run: + +```bash +kubectl taint nodes node1 key1=value1:NoSchedule- +``` + +You specify a toleration for a pod in the PodSpec. Both of the following tolerations "match" the taint created by the `kubectl taint` line above, and thus a prod with either toleration would be able to schedule onto `node1`: + +```yaml +tolerations: +- key: "key1" + operator: "Equal" + value: "value1" + effect: "NoSchedule" +``` + +```yaml +tolerations: +- key: "key1" + operator: "Exists" + effect: "NoSchedule" +Here's an example of a pod that uses tolerations: +``` + +Here's an example of a pod that uses tolerations: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: nginx + labels: + env: test +spec: + containers: + - name: nginx + image: nginx + imagePullPolicy: IfNotPresent + tolerations: + - key: "example-key" + operator: "Exists" + effect: "NoSchedule" +``` + +The default value for `operator` is `Equal`. + +A toleration "matches" a taint if the keys are the same and the effects are the same, and: + +- the `operator` is `Exists` (in which case no `value` should be specified), or +- the `operator` is `Equal` and the `value`s are equal. + +> There are two special cases:
An empty `key` with operator `Exists` matches all keys, values and effects which means this will tolerate everything.
An empty `effect` matches all effects with key `key1`. + +The above example used `effect` of `NoSchedule`. Alternatively, you can use `effect` of `PreferNoSchedule`. This is a "preference" or "soft" version of `NoSchedule` -- the system will try to avoid placing a pod that does not tolerate the taint on the node, but it is not required. The third kind of `effect` is `NoExecute`, described later. + +You can put multiple taints on the same node and multiple tolerations on the same pod. + +The way Kubernetes processes multiple taints and tolerations is like a filter: **start with all of a node's taints, then ignore the ones for which the pod has a matching toleration**; the remaining un-ignored taints have the indicated effects on the pod. In particular, + +- if there is at least one un-ignored taint with effect NoSchedule then Kubernetes will not schedule the pod onto that node +- if there is no un-ignored taint with effect NoSchedule but there is at least one un-ignored taint with effect PreferNoSchedule then Kubernetes will try to not schedule the pod onto the node +- if there is at least one un-ignored taint with effect NoExecute then the pod will be evicted from the node (if it is already running on the node), and will not be scheduled onto the node (if it is not yet running on the node). + +For example, imagine you taint a node like this + +```bash +kubectl taint nodes node1 key1=value1:NoSchedule +kubectl taint nodes node1 key1=value1:NoExecute +kubectl taint nodes node1 key2=value2:NoSchedule +``` + +And a pod has two tolerations: + +```yaml +tolerations: +- key: "key1" + operator: "Equal" + value: "value1" + effect: "NoSchedule" +- key: "key1" + operator: "Equal" + value: "value1" + effect: "NoExecute" +``` + +In this case, the pod will not be able to schedule onto the node, because there is no toleration matching the third taint. But it will be able to continue running if it is already running on the node when the taint is added, because the third taint is the only one of the three that is not tolerated by the pod. + +Normally, if a taint with effect NoExecute is added to a node, then any pods that do not tolerate the taint will be evicted immediately, and pods that do tolerate the taint will never be evicted. However, a toleration with NoExecute effect can specify an optional tolerationSeconds field that dictates how long the pod will stay bound to the node after the taint is added. For example, + + +```yaml +tolerations: +- key: "key1" + operator: "Equal" + value: "value1" + effect: "NoExecute" + tolerationSeconds: 3600 +``` + +means that if this pod is running and a matching taint is added to the node, then the pod will stay bound to the node for `3600` seconds, and then be evicted. If the taint is removed before that time, the pod will not be evicted. + +--- +reference +- https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ +- https://kubernetes.io/docs/concepts/scheduling-eviction/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/cordon,\342\200\205drain.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/cordon,\342\200\205drain.md" new file mode 100644 index 00000000..ad8d3d3a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Node\342\200\205Scheduling/cordon,\342\200\205drain.md" @@ -0,0 +1,113 @@ +--- +title: 'cordon, drain' +lastUpdated: '2024-03-02' +--- + +쿠버네티스 클러스터를 사용하다 보면 특정 노드에 있는 포드들을 모두 다른 곳으로 옮기거나 아니면 특정 노드에는 포드들이 스케쥴링 되지 않도록 제한을 걸어야 할 때가 있다. 이러한 기능들을 제공하는 kubectl 명령어가 cordon, drain, taint 등이다. + +## cordon + +kubectl cordon은 지정된 노드에 더이상 포드들이 스케쥴링되서 실행되지 않도록 한다. kubectl get nodes로 노드 이름을 확인한 다음에 cordon을 해보자. cordon을 한 다음에 다시 노드를 확인해 보면 노드의 status에 SchedulingDisabled라는 STATUS가 추가된 걸 확인할 수 있다. + +```bash +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +docker-for-desktop Ready master 4d v1.10.3 + +$ kubectl cordon docker-for-desktop +node "docker-for-desktop" cordoned + +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +docker-for-desktop Ready,SchedulingDisabled master 4d v1.10.3 +``` + +현재 실행중인 Deployment의 포드 개수를 늘려 실제로 스케쥴링이 되지 않는지 확인해 보보자. `kubernetes-simple-app`이란 디플로이먼트의 포드가 1개 실행중인걸 확인하고 scale 명령으로 replicas를 2개로 늘렸다. 하지만 포드가 정상적으로 실행되지 않고 Pending상태로 남아 있다. 노드가 하나뿐이라 cordon을 걸어놓은 노드에 스케쥴링을 시도했는데, `SchedulingDisabled` 이기 때문에 실행이 실패하고 있는 것이다. + +```bash +$ kubectl get deploy,pod +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +deployment.extensions/kubernetes-simple-app 1 1 1 1 8h + +NAME READY STATUS RESTARTS AGE +pod/kubernetes-simple-app-57585656fc-d6n7z 1/1 Running 0 33m + +$ kubectl scale deploy kubernetes-simple-app --replicas=2 +deployment.extensions "kubernetes-simple-app" scaled +$ kubectl get deploy,pod +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +deployment.extensions/kubernetes-simple-app 2 2 2 1 8h + +NAME READY STATUS RESTARTS AGE +pod/kubernetes-simple-app-57585656fc-82nmk 0/1 Pending 0 59s +pod/kubernetes-simple-app-57585656fc-d6n7z 1/1 Running 0 35m +``` + +포드가 노드에 정상적으로 스케쥴링될 수 있게 하기 위해서는 uncordon을 해주면 된다. 다음명령으로 노드를 uncordon할 수 있다. + +```bash +kubectl uncordon docker-for-desktop +``` + +uncordon이 정상적으로 되면 노드 상태는 Ready만 남게 되고 Pending으로 남아 있던 포드가 잠시 후 정상적으로 스케쥴링되서 실행되고 있는걸 확인할 수 있다. + +## drain + +kubectl drain은 노드 관리를 위해서 지정된 노드에 있는 포드들을 다른곳으로 이동시키는 명령이다. 우선 새로운 포드가 노드에 스케쥴링 되어서 실행되지 않도록 설정한다. 그리고 나서 기존에 이 노드에서 실행중이던 포드들을 삭제한다. + +이 때 노드에 데몬셋으로 실행된 포드들이 있으면 drain이 실패한다. 데몬셋으로 실행된 포드들은 삭제해도 데몬셋이 즉시 다시 실행하기 때문이다. 그래서 데몬셋으로 실행한 포드를 무시하고 진행하려면 `--ignore-daemonsets=true` 옵션을 주고 drain을 하면 된다. + +컨트롤러를 통해서 실행되지 않은 포드만으로 실행된 포드들이 있어도 drain이 실패한다. 컨트롤러에 의해 관리되고 있는 포드들은 삭제되더라도 컨트롤러가 클러스터내의 다른 노드에 다시 동일한 역할을 하는 포드를 실행한다. 하지만 포드만으로 실행된 포드들은 한번 삭제되면 그것으로 끝이기 때문에 삭제시 위험이 있어 drain이 진행되지 않고 실패하는 것이다. 이런 경우 강제로 삭제를 진행하려면 `--force` 옵션을 주고 실행하면 된다. 또한, api server를 통해서 실행되지 않은 kubelet이 직접 실행한 static pod들도 삭제되지 않는다. + +drain을 하게 되면 pod가 graceful하게 종료된다. 포드들이 종료 명령을 받았을때 바로 중단 되는게 아니라 정상적으로 잘 종료되도록 설정되어 있다면 기존 작업에 대한 정리를 하고 종료가 된다. drain은 이 과정을 모두 기다려 주도록 되어 있다. + +먼저, drain을 실행해 보기 위해서 node의 이름을 확인한다. + +```bash +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +docker-for-desktop Ready master 4d v1.10.3 +``` + +도커를 이용해서 설치한 쿠버네티스라면 docker-for-desktop으로 노드이름을 확인할 수 있다. +우선 kubectl drain을 해보면 다음처럼 에러가 발생한다. 데몬셋으로 떠 있는 포드가 존재하기 때문이다. + +```bash +$ kubectl drain docker-for-desktop +node "docker-for-desktop" cordoned +error: unable to drain node "docker-for-desktop", aborting command... + +There are pending nodes to be drained: +docker-for-desktop +error: DaemonSet-managed pods (use --ignore-daemonsets to ignore): kube-proxy-4s52d +``` + +`--ignore-daemonsets=true` 옵션을 주고 다시 실행하면 정상적으로 실행되는걸 확인할 수 있다. + +```bash +$ kubectl drain docker-for-desktop --ignore-daemonsets=true +node "docker-for-desktop" already cordoned +WARNING: Ignoring DaemonSet-managed pods: kube-proxy-4s52d +pod "compose-api-6fbc44c575-k6fz8" evicted +pod "compose-7447646cf5-w4mzb" evicted +pod "kubernetes-simple-app-57585656fc-nvkxm" evicted +pod "kube-dns-86f4d74b45-dt5rb" evicted +node "docker-for-desktop" drained +``` + +노드의 status부분을 보면 cordon을 했을 때와 똑같이 ScehdulingDisabled가 뜬 것을 볼 수 있다. 그 다음 하단의 포드들 상태를 보면 다른 node에 다시 scheduling 되기 위해 Pending, 혹은 Terminating 상태가 되어 있는걸 알 수 있다. 스태틱 포드인 것들은 Running 상태로 남아 있다. + + + +클러스터의 노드가 여러개 있다면 이 노드에서 삭제된 포드들은 다른 노드들로 스케쥴링이 되었을 것이다. 하지만 지금은 노드가 1개 밖에 없기 때문에 다시 이 노드에서 포드를 실행시키려고 하지만 노드가 `SchedulingDisabled` 상태이기 때문에 스케쥴링 되지 못하고 `Pending` 상태로 남아 있게 된다. drain 되어서 스케쥴링이 되지 않고 있는 상태를 풀어주려면 uncordon 명령을 사용하면 된다. + +```bash +kubectl uncordon docker-for-desktop +``` + +uncordon을 하고 나서 다시 클러스터 상태를 확인해 보면 다음처럼 노드의 status는 `Ready`만 남아 있고 `SchedulingDisabled` 상태가 없어진걸 확인할 수 있다. 조금 기다리면 Pending 상태에 있던 포드들이 `ContainerCreating` 상태를 거쳐서 모두 `Running` 상태가 되는걸 확인할 수 있다. + +--- +reference +- https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/ +- https://www.howtogeek.com/devops/cordons-and-drains-how-to-prepare-a-kubernetes-node-for-maintenance/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Volume/CSIDriver.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Volume/CSIDriver.md" new file mode 100644 index 00000000..8dcca45c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Volume/CSIDriver.md" @@ -0,0 +1,167 @@ +--- +title: 'CSIDriver' +lastUpdated: '2024-03-02' +--- + +CSIDriver **captures information about a Container Storage Interface (CSI) volume driver deployed on the cluster**. Kubernetes attach detach controller uses this object to determine whether attach is required. Kubelet uses this object to determine whether pod information needs to be passed on mount. CSIDriver objects are non-namespaced. + +The CSIDriver Kubernetes API object serves two purposes: + +1. Simplify driver discovery + - If a CSI driver creates a CSIDriver object, Kubernetes users can easily discover the CSI Drivers installed on their cluster (simply by issuing kubectl get CSIDriver) + +2. Customizing Kubernetes behavior + - Kubernetes has a default set of behaviors when dealing with CSI Drivers (for example, it calls the Attach/Detach operations by default). This object allows CSI drivers to specify how Kubernetes should interact with it. + +## What fields does the CSIDriver object have? + +Here is an example of a v1 CSIDriver object: + +```yaml +apiVersion: storage.k8s.io/v1 +kind: CSIDriver +metadata: + name: mycsidriver.example.com +spec: + attachRequired: true + podInfoOnMount: true + fsGroupPolicy: File # added in Kubernetes 1.19, this field is GA as of Kubernetes 1.23 + volumeLifecycleModes: # added in Kubernetes 1.16, this field is beta + - Persistent + - Ephemeral + tokenRequests: # added in Kubernetes 1.20. See status at https://kubernetes-csi.github.io/docs/token-requests.html#status + - audience: "gcp" + - audience: "" # empty string means defaulting to the `--api-audiences` of kube-apiserver + expirationSeconds: 3600 + requiresRepublish: true # added in Kubernetes 1.20. See status at https://kubernetes-csi.github.io/docs/token-requests.html#status + seLinuxMount: true # Added in Kubernetest 1.25. +``` + +These are the important fields: + +- `name` + - This should correspond to the full name of the CSI driver. + +- `attachRequired` + - Indicates this CSI volume driver requires an attach operation (because it implements the CSI `ControllerPublishVolume` method), and that Kubernetes should call attach and wait for any attach operation to complete before proceeding to mounting. + - If a `CSIDriver` object does not exist for a given CSI Driver, the default is true -- meaning attach will be called. + - If a `CSIDriver` object exists for a given CSI Driver, but this field is not specified, it also defaults to true -- meaning attach will be called. + - For more information see Skip Attach. +- `podInfoOnMount` + - Indicates this CSI volume driver requires additional pod information (like pod name, pod UID, etc.) during mount operations. + - If value is not specified or false, pod information will not be passed on mount. + - If value is set to true, Kubelet will pass pod information as volume_context in CSI NodePublishVolume calls: + - `"csi.storage.k8s.io/pod.name": pod.Name` + - `"csi.storage.k8s.io/pod.namespace": pod.Namespace` + - `"csi.storage.k8s.io/pod.uid": string(pod.UID)` + - `"csi.storage.k8s.io/serviceAccount.name": pod.Spec.ServiceAccountName` + - For more information see [Pod Info on Mount](https://kubernetes-csi.github.io/docs/pod-info.html). + +- `fsGroupPolicy` + - This field was added in Kubernetes 1.19 and cannot be set when using an older Kubernetes release. + - This field is beta in Kubernetes 1.20 and GA in Kubernetes 1.23. + - Controls if this CSI volume driver supports volume ownership and permission changes when volumes are mounted. + - The following modes are supported, and if not specified the default is `ReadWriteOnceWithFSType`: + - `None`: Indicates that volumes will be mounted with no modifications, as the CSI volume driver does not support these operations. + - `File`: Indicates that the CSI volume driver supports volume ownership and permission change via fsGroup, and Kubernetes may use fsGroup to change permissions and ownership of the volume to match user requested fsGroup in the pod's SecurityPolicy regardless of fstype or access mode. + - `ReadWriteOnceWithFSType`: Indicates that volumes will be examined to determine if volume ownership and permissions should be modified to match the pod's security policy. **Changes will only occur if the fsType is defined** and the persistent volume's accessModes contains ReadWriteOnce. This is the default behavior if no other FSGroupPolicy is defined. + - For more information see [CSI Driver fsGroup Support](https://kubernetes-csi.github.io/docs/support-fsgroup.html). + +- `volumeLifecycleModes` (beta) + - This field was added in Kubernetes 1.16 and cannot be set when using an older Kubernetes release. + - It informs Kubernetes about the volume modes that are supported by the driver. This ensures that the driver is not used incorrectly by users. The default is Persistent, which is the normal PVC/PV mechanism. Ephemeral enables inline ephemeral volumes in addition (when both are listed) or instead of normal volumes (when it is the only entry in the list). + +- `tokenRequests` + - This field was added in Kubernetes 1.20 and cannot be set when using an older Kubernetes release. + - This field is enabled by default in Kubernetes 1.21 and cannot be disabled since 1.22. + - If this field is specified, Kubelet will plumb down the bound service account tokens of the pod as volume_context in the NodePublishVolume: + - `"csi.storage.k8s.io/serviceAccount.tokens": {"gcp":{"token":"","expirationTimestamp":""}}` + - If CSI driver doesn't find token recorded in the volume_context, it should return error in NodePublishVolume to inform Kubelet to retry. + - Audiences should be distinct, otherwise the validation will fail. If the audience is "", it means the issued token has the same audience as kube-apiserver. + +- `requiresRepublish` + - This field was added in Kubernetes 1.20 and cannot be set when using an older Kubernetes release. + - This field is enabled by default in Kubernetes 1.21 and cannot be disabled since 1.22. + - If this field is true, Kubelet will periodically call NodePublishVolume. This is useful in the following scenarios: + - If the volume mounted by CSI driver is short-lived. + - If CSI driver requires valid service account tokens (enabled by the field tokenRequests) repeatedly. + - CSI drivers should only atomically update the contents of the volume. Mount point change will not be seen by a running container. + +- `seLinuxMount` + - This field is alpha in Kubernetes 1.25. It must be explicitly enabled by setting feature gates `ReadWriteOncePod` and `SELinuxMountReadWriteOncePod`. + - The default value of this field is false. + - When set to `true`, corresponding CSI driver announces that all its volumes are independent volumes from Linux kernel point of view and each of them can be mounted with a different SELinux label mount option (`-o context=`). Examples: + - A CSI driver that creates block devices formatted with a filesystem, such as xfs or ext4, can set seLinuxMount: **true**, because each volume has its own block device. + - A CSI driver whose volumes are always separate exports on a NFS server can set seLinuxMount: **true**, because each volume has its own NFS export and thus Linux kernel treats them as independent volumes. + - A CSI driver that can provide two volumes as subdirectories of a common NFS export **must set seLinuxMount: false**, because these two volumes are treated as a single volume by Linux kernel and must share the same `-o context=` option. + - See corresponding KEP for details. + - Always test Pods with various SELinux contexts with various volume configurations before setting this field to `true`! + +## What creates the CSIDriver object? + +To install, a CSI driver's deployment manifest must contain a CSIDriver object as shown in the example above. + +> NOTE: The cluster-driver-registrar side-car which was used to create CSIDriver objects in Kubernetes 1.13 has been deprecated for Kubernetes 1.16. No cluster-driver-registrar has been released for Kubernetes 1.14 and later. + +CSIDriver instance should exist for whole lifetime of all pods that use volumes provided by corresponding CSI driver, so Skip Attach and Pod Info on Mount features work correctly. + +### Listing registered CSI drivers + +Using the CSIDriver object, it is now possible to query Kubernetes to get a list of registered drivers running in the cluster as shown below: + +```bash +$> kubectl get csidrivers.storage.k8s.io +NAME ATTACHREQUIRED PODINFOONMOUNT MODES AGE +mycsidriver.example.com true true Persistent,Ephemeral 2m46s +``` + +Or get a more detailed view of your registered driver with: + +```bash +$> kubectl describe csidrivers.storage.k8s.io +Name: mycsidriver.example.com +Namespace: +Labels: +Annotations: +API Version: storage.k8s.io/v1 +Kind: CSIDriver +Metadata: + Creation Timestamp: 2022-04-07T05:58:06Z + Managed Fields: + API Version: storage.k8s.io/v1 + Fields Type: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + .: + f:kubectl.kubernetes.io/last-applied-configuration: + f:spec: + f:attachRequired: + f:fsGroupPolicy: + f:podInfoOnMount: + f:requiresRepublish: + f:tokenRequests: + f:volumeLifecycleModes: + .: + v:"Ephemeral": + v:"Persistent": + Manager: kubectl-client-side-apply + Operation: Update + Time: 2022-04-07T05:58:06Z + Resource Version: 896 + UID: 6cc7d513-6d72-4203-87d3-730f83884f89 +Spec: + Attach Required: true + Fs Group Policy: File + Pod Info On Mount: true + Volume Lifecycle Modes: + Persistent + Ephemeral +Events: +``` + +--- +reference +- https://kubernetes-csi.github.io/docs/csi-driver-object.html +- https://docs.openshift.com/container-platform/4.8/rest_api/storage_apis/csidriver-storage-k8s-io-v1.html +- https://kubernetes.io/blog/2019/01/15/container-storage-interface-ga/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Volume/attachdetach\342\200\205controller.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Volume/attachdetach\342\200\205controller.md" new file mode 100644 index 00000000..d76560ea --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Volume/attachdetach\342\200\205controller.md" @@ -0,0 +1,137 @@ +--- +title: 'attachdetach controller' +lastUpdated: '2024-03-02' +--- + +## Objective + +- Make volume attachment and detachment independent of any single node's availability + - If a node or kubelet goes down, the volumes attached to that node should be detached so that they are free to be attached to other nodes. +- Secure Cloud Provider Credentials + - Because each kubelet is responsible for triggering attach/detach logic, every node currently needs (often broad) permissions. These permissions should be limited to the master. For Google Compute Engine (GCE), for example, this means nodes should no longer need the computer-rw auth scope. +- Improve stability of volume attach/detach code + +## Solution Overview + +A node should not be responsible for determining whether to attach or detach a volume to itself. Instead, the responsibility should be moved (out of Kubelet) to a separate off-node “Volume Attachment/Detachment Controller” that has a life cycle independent of any individual node. + +More specifically, a new controller, responsible for attaching and detaching all volumes across the cluster, will be added to the controllermanager, running on the master. This controller will watch the API server to determine when pods are scheduled or killed. When a new pod is scheduled it will trigger the appropriate attach logic for that volume type based on the node it is scheduled to. When a pod is terminated, it will similarly trigger the appropriate detach logic. Some volume types may not have a concept of attaching to a node (e.g. NFS volume), and for these volume types the attach and detach operations will be no-ops. + +## Detailed Design + +### Attach/Detach Controller + +The controller will maintain an in-memory cache containing a list of volumes that are managed by the controller (i.e. volumes that require attach/detach). Each of these volumes will, in addition to the volume name, specify a list of nodes the volume is attached to, and similarly, each of these nodes will, in addition to the node name, specify a list of pods that are scheduled to the node and referencing the volume. This cache defines the state of the world according to the controller. This cache must be thread-safe. The cache will contain a single top level boolean used to track if state needs to be persisted or not (the value is reset every time state is persisted, and it is set anytime an attach or detach succeeds). + +On initial startup, the controller will fetch a list of all persistent volume (PV) objects from the API server and pre-populate the cache with volumes and the nodes they are attached to. + +The controller will have a loop that does the following: + +- Fetch State + - Fetch all pod and mirror pod objects from the API server. +- Acquire Lock + - Acquire a lock on the in-memory cache. +- Reconcile State + - Search for newly terminated/deleted pods: + - Loop through all cached pods (i.e. volume->node->pods) and delete them from in-memory cache if: + - The cached pod is not present in the list of fetched pods or the node it is scheduled to is different (indicating the pod object was was deleted from the API server or rescheduled). + - The cached pod is present in the list of fetched pods and the PodPhase is Succeeded or Failed and the VolumeStatus (detailed in “Safe Mount/Unmount” section below) indicates the volume is safe to detach. + - Search for newly attached volumes: + - Iterate through the fetched pods, and for each pod with a PodPhase Pending, check the volumes it defines or references (dereference any PersistentVolumeClaim objects to fetch associated PersistentVolume objects). If the volume is already tracked in memory cache, and the node the pod is scheduled to is listed in the in-memory cache for the specified volume (indicating the volume was successfully attached to the node): + - Add the pod to the list of pods in the in-memory cache under the node (indicating a new pod is referencing a volume that is already attached to the node it is scheduled to). +- Act + - Trigger detaches: + - Loop through all cached nodes (i.e. volume->node) and trigger detach logic (detailed below) for any volumes that are attached to a node (exist in-memory cache) but have no pods listed under that node (indicating no running pods using the volume) and implement the Detacher interface. + - Detaches are triggered before attaches so that volumes referenced by pods that are rescheduled to a different node are detached first. + - Trigger attaches: + - Iterate through the fetched pods, and for each pod with a PodPhase Pending check the volumes it defines or references (dereference any PersistentVolumeClaim objects to fetch associated PersistentVolume objects). For each volume that implements the Attacher interface: + - If **the volume is not already in-memory cache** (indicates a new volume has been discovered), then trigger attach logic (detailed in section below) to attach the volume to the node the pod is scheduled to. + -If **the volume is already tracked in memory cache**, and the node the pod is scheduled to is not listed in the in-memory cache for the specified volume (indicating the volume is not attached to the node), trigger attach logic (detailed in section below) to attach the volume to the node the pod is scheduled to. +- Persist State + - Persist the in-memory cache to API server for PersistentVolume volume types: + - If the top level boolean in the in-memory cache used to track if state needs to be persisted or not is not set, skip this operation (to prevent unnecessary writes to the API server). + - For each volume in the in-memory cache that is a PersistentVolume and is attached to a node (has more than one item in list of nodes), write the list of nodes to associated PersistentVolume object via the API server. + - Reset the top level boolean in the in-memory cache to indicate that state does not need to be persisted. +- Release Lock + - Release the lock on the in-memory cache (spawned threads have this opportunity to update state indicating volume attachment or detachment). + +Attach and detach operations can take a long time to complete, so the primary controller loop should not block on these operations. Instead the attach and detach operations should spawn separate threads for these operations. To prevent multiple attach or detach operations on the same volume, the main thread will maintain a table mapping volumes to currently active operations. The number of threads that can be spawned will be capped (possibly using a thread pool) and once the cap is hit, subsequent requests will have to wait for a thread to become available. + +For backwards compatibility, the controller binary will accept a flag (or some mechanism) to disable the new controller altogether. + +### Attach Logic +To attach a volume: + +- Acquire operation lock for volume so that no other attach or detach operations can be started for volume. + - Abort if there is already a pending operation for the specified volume (main loop will retry, if needed). +- Check “policies” to determine if the persistent volume can be attached to the specified node, if not, do nothing. + - This includes logic that prevents a ReadWriteOnce persistent volume from being attached to more than one node. +- Spawn a new thread: + - Execute the volume-specific logic to attach the specified volume to the specified node. + - If there is an error indicating the volume is already attached to the specified node, assume attachment was successful (this will be the responsibility of the plugin code). + - For all other errors, log the error, and terminate the thread (the main controller will retry as needed). + - Once attachment completes successfully: + - Acquire a lock on the in-memory cache (block until lock is acquired). + - Add the node the volume was attached to, to in-memory cache (i.e. volume->node), to indicate the volume was successfully attached to the node. + - Set the top level boolean in the in-memory cache to indicate that state does need to be persisted. + - Release the lock on the in-memory cache. + - Make a call to the API server to update the VolumeStatus object under the PodStatus for the volume to indicate that it is now safeToMount. +- Release operation lock for volume. + +### Detach Logic + +To detach a volume: + +- Acquire operation lock for volume so that no other attach or detach operations can be started for volume. + - Abort if there is already a pending operation for the specified volume (main loop will retry, if needed). +- Spawn a new thread: + - Execute the volume-specific logic to detach the specified volume from the specified node. + - If there is an error indicating the volume is not attached to the specified node, assume detachment was successful (this will be the responsibility of the plugin code). + - For all other errors, log the error, and terminate the thread (the main controller will retry as needed). + - Once detachment completes successfully: + - Acquire a lock on the in-memory cache (block until lock is acquired). + - Remove the node that the specified volume was detached from, from the list of attached nodes under the volume in-memory cache, to indicate the volume was successfully detached from the node. + - Set the top level boolean in the in-memory cache to indicate that state does need to be persisted. + - Release the lock on the in-memory cache. +- Acquire operation lock for volume. + +### Rationale for Storing State + +The new controller will need to constantly be aware of which volumes are attached to which nodes, and which scheduled pods reference those volumes. The controller should also be robust enough that if it terminates unexpectedly, it should continue operating where it left off when restarted. Ideally, the controller should also be a closed-loop system--it should store little or no state and, instead, operate strictly with an ”observe and rectify” philosophy; meaning, it should trust no cached state (never assume the state of the world), and should instead always verify (with the source of truth) which volumes are actually attached to which nodes, then take the necessary actions to push them towards the desired state (attach or detach), and repeat (re-verify with the source of truth the state of the world and take actions to rectify). + +The problem with this purist approach is that it puts an increased load on volume providers by constantly asking for the state of the world (repeated operations to list all nodes or volumes to determine attachment mapping). It makes the controller susceptible to instability/delays in the provider APIs. And, most importantly, some state simply cannot (currently) be reliably inferred through observation. + +For example, the controller must be able to figure out when to to trigger volume detach operations. When a pod is terminated the pod object will be deleted (normally by kubelet, unless kubelet becomes unresponsive, in which case, the node controller will mark the node as unavailable resulting in the pod forcibly getting terminated and the pod object getting deleted). So the controller could use the deletion of the pod object (a state change) as the observation that indicates that either the volume was safely unmounted or the node became unavailable, and trigger the volume detach operation. + +However, having the controller trigger such critical behavior based on observation of a state change makes the controller inherently unreliable because the controller cannot retrieve the state of the world on-demand and must instead constantly monitor for state changes; if a state change is missed, the controller will not trigger the correct behavior: imagine that the controller unexpectedly terminates, and the pod object is deleted during this period, when the controller comes back up it will have missed the pod deletion event and will have no knowledge that a volume was once attached to a node and should now be detached. + +Therefore, in order to make the controller reliable, it must maintain some state (specifically which volumes are attached to which nodes). It can maintain a mapping of volumes and the nodes they are attached to in local memory, and operate based on this state. + +To prevent this cached state of the world (which volumes are attached to which nodes) from becoming stale or out-of-sync with the true state of the world, the controller could, optionally, periodically poll the volume providers to find out exactly which volumes are attached to which nodes and update its cached state accordingly. + +In order to make the controller more robust, the cached state should also be persisted outside the controller. This can be done by adding a field to the PersistentVolume object, exposed by the Kubernetes API server. If the controller is unexpectedly terminated, it can come back up and pick up from where it left off by reading this state from the API server (except for “Pod Defined” and “Static Pod” Volumes, see below). If a PersistentVolume object is deleted by the user before it is detached, the controller will still behave normally, as long as the controller is not restarted during this period, since the volume still exists in the controller's local memory. + +### What about Pod Defined Volume Objects and Static Pods? +In the existing design, most volume types can be defined either as PersistentVolumeSource types or as plain VolumeSource types. During pod creation, Kubelet looks at any Volumes (dereferencing PersistentVolumeClaims) defined in the pod object and attaches/mounts them as needed. + +The solution proposed above for persisting controller state (by extending the PersistentVolume object with additional fields that the controller can use to save and read attach/detach state), works only for volumes created as PersistentVolume objects, because the API Server stores and exposes PersistentVolume objects. However, plain Volume objects, are defined directly in the pod definition config, and are simply fields in the pod object (not first class API server objects). If any state is stored inside the pod object (like what node a volume is attached to), once the pod API object is deleted (which happens routinely when a pod is terminated), information about what node(s) the volume was attached to would be lost. Additionally, users can create Static Pods and bypass the API server altogether. + +In order to support such “Pod Defined” and “Static Pod” Volumes, the controller will manage all attach/detach but persist only some state in API server objects: + +- Controller handles “persistent volume” objects by parsing PersistentVolume API objects from the API server, and persists state in the PersistentVolume API object. +- Controller handles “pod defined volumes” by parsing pod objects from the API server, and stores state only in local controller memory (best effort, no persisted state). +- Controller handles “static pods” by parsing mirror pod objects from the API server, and stores state only in local controller memory (best effort, no persisted state). + +Because “static pods” can be created via a kubelet running without a master (i.e. standalone mode), kubelet will support a legacy mode of operation where it continues to be responsible for attach/detach logic; same behavior as today (see below for details). + +### Safe Mount/Unmount + +The controller should wait for a volume to be safely unmounted before it tries to detach it. Specifically, the controller should wait for the kubelet (on the node that a volume is attached to) to safely unmount the volume before the controller detaches it. This means the controller must monitor some state (set by kubelet) indicating a volume has been unmounted, and detach once the signal is given. The controller should also be robust enough that if the kubelet is unavailable, act unilaterally to detach the disk anyway (so that no disks are left attached if kubelet becomes inaccessible). + +Similarly kubelet should wait for a volume to be safely attached before it tries to mount it. Specifically, the kubelet should wait for the attach/detach controller to indicate that a volume is attached before the kubelet attempts to mount it. This means the kubelet must monitor some state (set by the controller) indicating if a volume has been attached, and mount only once the signal is given. + +Both these goals can be achieved by introducing a list of VolumeStatus objects under the PodStatus API object. The VolumeStatus object, similar to ContainerStatus, will contain state information for a volume in the pod. This includes a safeToMount field indicating the volume is attached and ready to mount (set by controller, read by kubelet), and a safeToDetach field indicating the volume is unmounted and ready to detach (set by kubelet, read by controller). + +--- +reference +- https://github.com/kubernetes/kubernetes/issues/20262 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Workloads.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Workloads.md" new file mode 100644 index 00000000..a134608f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/Workloads.md" @@ -0,0 +1,26 @@ +--- +title: 'Workloads' +lastUpdated: '2024-03-02' +--- + +A workload is an application running on Kubernetes. Whether your workload is a single component or several that work together, on Kubernetes you run it inside a set of pods. In Kubernetes, a Pod represents a set of running containers on your cluster. + +Kubernetes pods have a defined lifecycle. For example, once a pod is running in your cluster then a critical fault on the node where that pod is running means that all the pods on that node fail. Kubernetes treats that level of failure as final: you would need to create a new Pod to recover, even if the node later becomes healthy. + +However, to make life considerably easier, **you don't need to manage each Pod directly. Instead, you can use workload resources that manage a set of pods on your behalf. These resources configure controllers that make sure the right number of the right kind of pod are running, to match the state you specified.** + +Kubernetes provides several built-in workload resources: + +- **Deployment and ReplicaSet** (replacing the legacy resource ReplicationController). Deployment is a good fit for managing a stateless application workload on your cluster, where any Pod in the Deployment is interchangeable and can be replaced if needed. +- **StatefulSet** lets you run one or more related Pods that do track state somehow. For example, if your workload records data persistently, you can run a StatefulSet that matches each Pod with a PersistentVolume. Your code, running in the Pods for that StatefulSet, can replicate data to other Pods in the same StatefulSet to improve overall resilience. +- **DaemonSet** defines Pods that provide facilities that are local to nodes. Every time you add a node to your cluster that matches the specification in a DaemonSet, the control plane schedules a Pod for that DaemonSet onto the new node. Each pod in a DaemonSet performs a job similar to a system daemon on a classic Unix / POSIX server. A DaemonSet might be fundamental to the operation of your cluster, such as a plugin to run cluster networking, it might help you to manage the node, or it could provide optional behavior that enhances the container platform you are running. +- **Job and CronJob** provide different ways to define tasks that run to completion and then stop. You can use a Job to define a task that runs to completion, just once. You can use a CronJob to run the same Job multiple times according a schedule. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/25ab55c7-c31e-4ad3-9ffd-166ee2ba4123) + +In the wider Kubernetes ecosystem, you can find third-party workload resources that provide additional behaviors. Using a custom resource definition, you can add in a third-party workload resource if you want a specific behavior that's not part of Kubernetes' core. For example, if you wanted to run a group of Pods for your application but stop work unless all the Pods are available (perhaps for some high-throughput distributed task), then you can implement or install an extension that does provide that feature. + +--- +reference +- https://kubernetes.io/docs/concepts/workloads/ +- https://azuredays.com/2020/12/09/understanding-kubernetes-workload-objects/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/etcd.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/etcd.md" new file mode 100644 index 00000000..e5c049d0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/etcd.md" @@ -0,0 +1,197 @@ +--- +title: 'etcd' +lastUpdated: '2024-03-02' +--- + +etcd는 key:value 형태의 데이터를 저장하는 스토리지이다. Kubernetes는 기반 스토리지(backing storage)로 etcd를 사용하고 있고, 모든 데이터를 etcd에 보관한다. 클러스터에 어떤 노드가 몇 개나 있고 어떤 파드가 어떤 노드에서 동작하고 있는지 등의 정보가 etcd에 저장되는 것이다. + +## RSM(Replicated state machine) + +etcd는 분산 컴퓨팅 환경에서 서버가 몇 개 다운되더라도 정상적으로 동작하는 Replicated state machine(RSM)방식으로 구현되었다. + + + +RSM은 위 그림과 같이 command가 들어있는 log 단위로 데이터를 처리한다. 데이터를 write하는 것을 `log append`라고 부르고, 머신은 받은 log를 순서대로 처리하는 특징을 갖는다. + +또한, RSM은 똑같은 데이터를 여러 서버에 계속해서 복제하며 데이터를 유지한다. 그리고 데이터 복제 과정에 발생할 수 있는 여러 가지 문제를 해결하고, 데이터의 정합성이 지켜지도록 하기 위해 (Consensus를 확보하기 위해) Raft 알고리즘을 사용한다. + +> Consensus를 확보한다는 것은 RSM이 아래 4가지 속성을 만족한다는 것과 같은 의미이다. + +|속성|설명| +|-|-| +|Safety|항상 올바른 결과를 리턴해야 한다| +|Available|일부 서버가 다운되더라도 항상 응답해야 한다.| +|Independent from timing|네트워크 지연이 발생해도 로그의 일관성이 깨져서는 안된다.| +|Reactivity|모든 서버에 복제되지 않았더라도 조건을 만족하면 빠르게 요청에 응답해야 한다.| + +--- + +Raft를 구현한 etcd의 동작애 대해 알아보자. 우선 이해를 위해 필요한 주요 용어들은 아래와 같다. + +- Quorum + - Quorum(쿼럼)이란 우리말로는 정족수라는 뜻을 가지며, 의사결정에 필요한 최소한의 서버 수를 의미한다. 예를 들어, RSM을 구성하는 서버의 숫자가 3대인 경우 쿼럼 값은 2(3/2+1)가 된다. + - etcd는 하나의 write 요청을 받았을 때, 쿼럼 숫자만큼의 서버에 데이터 복제가 일어나면 작업이 완료된 것으로 간주하고 다음 작업을 받아들일 수 있는 상태가 된다. + +- State + - Etcd를 구성하는 서버는 State를 가지며 이는 Leader, Follower, Candidate 중 하나가 된다. 각 서버는 상태에 따라 다른 동작을 수행한다. + +- Heartbeat + - Etcd의 Leader 서버는 다른 모든 서버에게 heartbeat를 주기적으로 전송하여, Leader가 존재함을 알린다. + - 만약 Leader가 아닌 서버들이 일정 시간(election timeout) 동안 heartbeat를 받지 못하게 되면 Leader가 없어졌다고 간주하고 다음 행동을 시작한다. + +- term + - heartbeat가 몇초동안 오지 않았는지를 새는 숫자이다. + +## 리더 선출(Leader election) + + + +3개의 서버를 이용해서 etcd 클러스터를 최초 구성했을 때, 각 서버는 모두 `follower` 상태(state)이며, `term`이 0으로 설정된다. 현재 etcd 클러스터는 리더가 없는 상태이다. + +이때, + +1. term이 0이기 때문에 etcd 서버 중 한대에서 election timeout이 발생하게 된다. +2. timeout이 발생한 서버는 자신의 상태를 candidate로 변경하고 term 값을 1 증가시킨 다음에 클러스터에 존재하는 다른 서버에게 RequestVote RPC call을 보낸다. +3. RequestVote를 받은 각 서버는 자신이 가진 term 정보와 log를 비교해서 candidate보다 자신의 것이 크다면 거절하는데, 현재는 term이 모두 1이므로 RequestVote에 대해서 OK로 응답한다. + +Candidate는 자기 자신을 포함하여 다른 서버로부터 받은 OK 응답의 숫자가 quorum과 같으면 leader가 된다. Leader가 된 서버는 클러스터의 다른 서버에게 `heartbeat`를 보내는데, 이 Append RPC call에는 leader의 term과 보유한 log index 정보가 들어있다. + +Leader가 아닌 서버는 Append RPC call을 받았을 때 자신의 term보다 높은 term을 가진 서버의 call 인지 확인하고, 자신의 term을 받은 term 값으로 업데이트한다. 이로써 etcd 클러스터는 leader가 선출되었고, 외부로부터 유입되는 write와 read 요청을 받아들일 준비가 되었다. + +## 로그 복제(Log replication) + +클러스터가 구성되고 leader가 선출된 이후, 사용자로부터 write 요청을 받았다고 해보자. + +각 follower 서버는 자신이 가지고 있는 log의 lastIndex 값을 가지고 있고, leader는 follower가 새로운 log를 써야 할 nextIndex까지 알고 있다. + +사용자로부터 log append 요청을 받은 leader는 자신의 lastIndex 다음 위치에 로그를 기록하고 lastIndex 값을 증가시긴다. 그리고 다른 heartbeat interval이 오면 각 forllower 서버의 nextIndex에 해당하는 log를 RPC call로 보낸다. (AppendEntry RPC call) + + + +첫 번째 follower(이하 F1) 자신의 entry(메모리)에 leader로부터 받은 log를 기록했고 두 번째 follower(이하 F2)는 아직 기록하지 못했다고 가정하자. + +F1은 log를 잘 받았다는 응답을 leader에게 보내주게 되고, leader는 F1의 nextIndex를 업데이트한다. F2가 아직 응답을 주지 않았거나 주지 못하더라도, leader 자신을 포함해서 쿼럼 숫자만큼의 log replication이 일어났기 때문에 commit을 수행한다. + +Commit이란, log entry에 먼저 기록된 데이터를 서버마다 가지고 있는 db(파일시스템)에 write 하는 것을 의미한다. 이제부터 사용자가 etcd 클러스터에서 x를 read하면 1이 return 된다. Follower들은 leader가 특정 로그를 commit한 사실을 알게 되면, 각 서버의 entry에 보관 중이던 log를 commit 한다. + +## 리더가 다운된 경우(Leader down) + +사용자의 write 요청에 따라 쿼럼 숫자만큼 log의 복제와 commit이 완료된 이후, leader가 다운(down)된다면 etcd는 어떻게 동작할까? 로그 복제(Log replication) 상황 이후에 leader 서버의 etcd 프로세스가 알 수 없는 이유로 다운되었다고 가정해보자. + +Follower들은 leader로부터 election timeout동안 heartbeat를 받지 못한다. 그 사이에 F1이나 F2중 하나가 타임아웃되면 다른 서버들에게 RequestVote RPC call을 보내 본인이 leader 자격이 있는지를 확인한다. (term 값이 다른 서버보다 크고 최신 로그를 가지고 있어야함) + +F2는 위에서 잠시 다운되어서 최신 로그 데이터를 가지고 있지 않기 때문에, 리더가 되는 것에 실패하고, 이후 F1이 타임아웃되었을때 다시 검증 과정을 거친 뒤 새로운 leader가 된다. F1이 새로운 leader가 된 상태에서 write 요청을 받게 되더라도 쿼럼 숫자만큼의 etcd 서버가 running 중이므로 etcd 클러스터는 정상 동작한다. + +> 이때, 새로운 leader를 뽑지 못하고 계속해서 election을 하게 된다면 사용자의 요청을 처리하지 못해 etcd 클러스터의 가용성이 떨어질 수 있다. Raft 알고리즘은 이런 상황을 대비해 randomize election timeout과 preVote라는 방법을 사용할 수 있다. Etcd에서는 preVote가 구현되어 있다. (https://github.com/etcd-io/etcd/pull/6624/files). + +F1이 새로운 leader가 된 상태에서 구 leader가 복구되면, F1에게 heartbeat를 보내거나 F1으로부터 heartbeat를 받을 텐데, lastIndex 값이 더 작고 term도 낮으므로 자기 자신을 follower로 변경하고 term 숫자를 높은 쪽에 맞추게 된다. + +## 런타임 재구성(Runtime reconfiguration) + +Etcd의 기본인 리더 선출(leader election)과 로그 복제(log replication)에 대해 살펴보았다. 이번에는 etcd 클러스터가 동작 중인 가운데 etcd 서버(멤버)가 추가/삭제되는 런타임 재구성(Runtime reconfiguration) 상황도 살펴보자. + +### 멤버 추가 + +Etcd에는 snapshot이라는 개념이 있는데, etcd 서버가 현재까지 받아들인 모든 log를 entry에서만 관리하는 것이 아니라 파일시스템에 백업해 놓는 것을 말한다. 얼마나 자주 snapshot을 생성할 것이냐는 etcd 클러스터를 생성하면서 옵션으로 지정할 수 있고, 디폴트 값은 100,000이다. + +새로운 4번째 서버를 추가하는 요청이 수신되었다면 즉시 클러스터의 일부로 취급되고, Quorum이 다시 계산된다. leader는 추가된 멤버가 가능한 한 빨리 같은 log를 보유할 수 있도록 snapshot을 보내준다. 이 snapshot은 현재 파일시스템에 백업으로 보유하고 있는 snapshot 중 가장 최신의 것과 leader의 현재 log entry를 합쳐 생성한 새로운 snapshot이며, 새로운 서버는 snapshot을 이용해 db를 만듦으로써 leader가 보낼 Append RPC call을 문제없이 받아들일 수 있는 상태가 된다. + +하지만 이떄 스냅샷의 크기가 너무 크다면 리더의 네트워크에 부하가 생겨 heart beat를 제때 전달하지 못할 수 있다. 그럼 기존에 있던 Flower들이 election timeout이 되어 재선거를 진행하고, 그동안 클러스터는 요청을 처리할 수 없는 상태가 된다. + +이러한 문제를 해결하기 위해 etcd애는 leaner라는 별도의 상태가 또 존재한다. Learner는 etcd 클러스터의 멤버이지만 쿼럼 카운트에서는 제외하는 특별한 상태로 etcd 3.4.0부터 구현되었다. (https://etcd.io/docs/v3.3.12/learning/learner/) + +Learner는 etcd의 promote API를 사용하여 일반적인 follower로 변경할 수 있다. Learner가 아직 log를 모두 따라잡지 못한 경우에는 거절된다. + + + +### 멤버 삭제 + +Etcd 클러스터에서 특정 서버를 삭제하는 요청도 etcd 멤버 추가에서 다룬 것과 같이 log를 복제하는 방식으로 진행된다. + +> 멤버 삭제 요청을 받았지만 그 멤버를 삭제했을때 started 상태인 서버의 수가 quorum보다 작아질 것으로 예상되는 경우엔 leader는 클라이언트의 멤버 삭제 요청을 거절한다. 하지만 다른 경우에는 write 진행중 노드를 삭제해도 quorum을 충족할 수 있기 때문에 그냥 삭제해버린다. + +단, leader가 leader를 제거하라는 요청을 받았을 때는 특별하게 동작한다. 이 경우에는 leader가 새로운 config log를 commit 시키는 데 필요한 log replication 숫자를 셀 때 자기 자신을 제외한다. 자신을 제외한 쿼럼 숫자만큼의 log replication이 확인되면 leader 자신이 삭제된 설정 Cnew를 commit 하고 클라이언트에게 OK 응답을 보낸다. + +Commit 이후에는 leader의 자리를 내려놓게 되고 더는 heartbeat를 보내지 않는다. 이것을 Raft와 etcd에서는 `step down`이라고 한다. 클러스터를 구성하는 나머지 서버 중 누군가 election timeout이 발생하면 candidate가 되어 RequestVote RPC call 전송을 통해 새로운 leader를 선출하고 다음 term을 계속해나간다. + +Leader가 클라이언트의 멤버 삭제 요청을 처리할 때 자기 자신을 제외한 쿼럼 숫자만큼의 log replication을 수행했기 때문에, 새로운 leader의 선출 성공이 보장된다. + +> 만약 아직 step down을 시작하지 않은 상태에서 write 요청을 받는다면, entry에 존재하는 가장 최신의 config에 자신이 없다 하더라도 여전히 leader 역할을 수행한다. + +## etcd의 유지보수 방법 + +etcd가 메모리와 파일시스템을 어떻게 사용하는지, 그리고 예기치 못한 사고를 대비하는 백업 및 복구 방법에 대해 알아보자. + +### 로그 리텐션(Log retention) + + + +Etcd 프로세스는 기본적으로 log entry를 메모리에 보관한다. 하지만 모든 log entry를 별다른 조치 없이 계속하여 메모리에 보관하면 서버 스펙이 아무리 훌륭하더라도 언젠가는 반드시 OOM이 발생할 것이므로 주기적으로 log entry를 파일시스템에 저장(snapshot) 하고, memory를 비우는 작업(truncate)을 수행한다. + +만약 truncate한 log를 사용해야 할 일이 발생하면(예를 들어, 특정 follower의 log catch 속도가 너무 느려서 leader의 메모리에 없는 log를 요구할 경우) leader는 최근의 snapshot 파일을 follower에게 전송한다(**etcd 멤버 추가** 참조). + + + +위 그림은 etcd의 log retention을 쉽게 관찰할 수 있도록 snapshot-count를 1000으로 변경하고 write 테스트를 수행했을 때의 로그를 보여준다. 1000번의 write가 일어날 때마다 메모리를 확인한뒤 snapshot을 만들고 메모리에 lastIndex–5,000까지의 log만 메모리에 보관하고 나머지는 비운다. (5,000은 옵션을 통해 조정할 수 없는 값이다). + +## 리비전 및 컴팩션(Revision and Compaction) + +Etcd 프로세스에 의해 commit된 데이터는 db(파일시스템)에 보관된다. (**로그 복제(Log replication)** 참조). Etcd는 하나의 key에 대응되는 value를 하나만 저장하고 있는 것이 아니라 etcd 클러스터가 생성된 이후 **key의 모든 변경사항**을 파일시스템에 기록하는데, 이것을 `revision`이라고 한다. + + + +Etcd는 db에 데이터를 저장할 때 revision 값과 함께 저장한다. 위 그림을 살펴보면, x라는 key에 대해서 value를 달리해서 여러 번 write를 했을 때 하나의 공간에 계속하여 덮어쓰는 것이 아니라 history를 계속하여 저장하는 구조이다. + +불필요한 revision의 과도한 리소스 사용을 피하고자 etcd는 `compaction`(압축) 기능을 제공한다. Compaction으로 삭제한 revision 데이터는 다시 조회가 불가능해진다. + +> Compactor records latest revisions every 5-minute, until it reaches the first compaction period +https://etcd.io/docs/v3.4/op-guide/maintenance/ + +공식 문서를 보면, 매 5분동안의 최신 revision만 남기고 삭제하는 것이 기본 설정이라는 것을 알 수 있다. + +## 자동 컴팩션(Auto compaction) + +Etcd는 운영자가 별도로 조치하지 않아도 일정 revision 또는 주기를 가지고 자동으로 revision을 정리하는 auto compaction을 지원한다. Auto compaction은 2가지 모드(revision, periodic)가 있으며 어떤 모드를 선택했느냐에 따라 auto-compaction-retention 옵션이 가지는 의미가 달라진다. + +### Revision 모드 + + +Auto-compaction-mode revision으로 지정하면 위에서 말했던 것처럼 5분마다 최신 revision – auto-compaction-retention까지만 db에 남기고 compaction한다. Auto-compaction-retention의 값이 1,000이고 5분마다 500 revision의 데이터가 생성된다고 가정하면 5분마다 500 revision이 계속하여 compaction된다. + +### Periodic 모드 + + +(https://etcd.io/docs/v3.4/op-guide/maintenance/#auto-compaction) + +Auto-compaction-mode를 periodic으로 지정하는 경우 auto-compaction-retention에 주어진 값을 시간으로 인식한다. Auto-compaction-retention이 8h일 때 이를 10으로 나눈 값이 1h가 적기 때문에, 1h 단위로 compaction이 일어난다. 1시간마다 100 revision이 생성된다고 가정하면 1시간마다 가장 오래된 100 revision을 compaction한다. + +## 단편화 제거(Defragmentation) + + + +Compaction을 통해 etcd 데이터베이스에서 revision을 삭제했다고 해서, 파일시스템의 공간까지 확보되는 것은 아니다. Revision 삭제로 인해 발생한 fragmentation(단편화)을 정리해 주어야 디스크 공간이 확보된다. RDB에서 TRUNCATE를 하지 않고 DELETE를 하는 것만으로는 disk 공간이 확보되지 않는 것과 비슷하다. Defragmentation(단편화 제거) 작업을 해야지만 revision 삭제로 인해 etcd 데이터베이스에 발생한 fragmentation을 정리해 디스크 공간을 확보할 수 있다. Defragmentation는 compaction과 달리 자동(auto) 기능이 제공되지 않고 있다. Defragmentation 시 주의해야 할 점은, 진행되는 동안 모든 read/write가 block 된다는 것이다. 하지만 몇 ms 단위의 시간에 일어나는 일이라, kubernetes를 위한 etcd 클러스터가 defragmentation으로 인해 read/write 작업이 timeout될 가능성은 적다. + +Etcd가 허용하는 db의 max size는 etcd_quota_backend_bytes라는 옵션으로 조절할 수 있으며, 디폴트 값은 2G이다. 만약 leader의 db 사이즈가 2G를 넘으면 “database space exceeded”라는 메시지와 함께 더 이상 write 요청을 받아들이지 않는 read-only 모드로 동작하게 된다. 따라서, etcd가 많은 key:value를 사용할 것으로 예상이 된다면 별도의 크론잡(CronJob)을 개발하여 defragmentation을 주기적으로 수행해 주거나, etcd_quota_backend_bytes 옵션을 넉넉하게 부여하여 (max 8G) etcd 클러스터의 가용성에 문제가 생기는 일이 없도록 해야 할 것이다. + +## 백업 및 복구(Backup and Restore) + + + +Etcd는 예기치 못한 사고로 인해 etcd 데이터베이스가 유실되는 경우를 대비하기 위한 backup API를 제공한다. 여기서의 snapshot은 로그 리텐션(Log retention)에서 다루었던 snapshot과는 다른 것이다. Database backup snapshot은 etcd 프로세스가 commit한 log가 저장된 데이터베이스 파일의 백업이다. 바꿔 말하면, etcd의 backup은 etcd 데이터 파일경로(data dir)에 존재하는 db 파일을 백업하는 것입니다. compaction이 일어나거나 defragmentation된 적 없는 db에 대해서 백업하면, 불필요하게 많은 용량을 백업에 사용하게 될 수도 있다. + +단순하게 etcd 데이터 파일 경로(data dir)에 존재하는 db 파일을 복제(copy)한 것과 etcd의 API를 이용한 백업 파일 간에는 차이가 있다. 위 그림과 같이 etcdctl snapshot save 명령으로 만들어진 db 파일은 무결성 해시(hash)를 포함하고 있어서 추후 etcdctl snapshot restore 명령으로 복구할 때 파일이 변조됐는지 체크가 가능하다. 만약 피치 못할 사정으로 단순 복사로 만들어진 백업 파일로부터 복구를 진행해야 한다면, –skip-hash-check 옵션을 추가하여 복구를 진행해야 한다. + + + +Backup 파일로부터 etcd를 복구하는 것은 두 단계로 나누어 진행할 수 있다. 먼저 db 파일을 호스트 OS의 특정 경로(dir)에 옮겨두고, 새로운 etcd를 시작시키면 된다. 단, etcd 서버와 클러스터가 새로운 메타데이터를 가지고 시작할 수 있게 정보를 주어야 하고, etcd 클러스터는 새로운 메타데이터로 db를 덮어쓰기(overwrite)하고 동작을 시작한다. + +etcd의 메타데이터에는 etcd 클러스터를 식별하기 위한 uuid와 etcd 클러스터에 속한 멤버의 uuid가 저장되어 있다. 만약 이전 메타데이터를 유지한 채 백업 파일로부터 새로운 etcd 클러스터를 생성한다면, 망가진 줄 알았던 etcd 서버가 다시 살아나게 될 때 구성이 꼬여 오동작할 가능성이 크다. + +--- + +참고 + +- https://etcd.io/docs/ +- https://tech.kakao.com/2021/12/20/kubernetes-etcd/ +- https://github.com/ongardie/dissertation +- https://raft.github.io/raft.pdf \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Annotation.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Annotation.md" new file mode 100644 index 00000000..e5867201 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Annotation.md" @@ -0,0 +1,35 @@ +--- +title: 'Annotation' +lastUpdated: '2024-03-02' +--- + +- You can use Kubernetes annotations to attach arbitrary non-identifying metadata to objects. Clients such as tools and libraries can retrieve this metadata. + +- You can use either labels or annotations to attach metadata to Kubernetes objects. Labels can be used to select objects and to find collections of objects that satisfy certain conditions. + +- In contrast, **annotations are not used to identify and select objects**. The metadata in an annotation can be small or large, structured or unstructured, and can include characters not permitted by labels. + +> The keys and the values in the map must be strings. In other words, you cannot use numeric, boolean, list or other types for either the keys or the values. + +## usecase + +Here are some examples of information that could be recorded in annotations: + +- Fields managed by a declarative configuration layer. Attaching these fields as annotations distinguishes them from default values set by clients or servers, and from auto-generated fields and fields set by auto-sizing or auto-scaling systems. + +- Build, release, or image information like timestamps, release IDs, git branch, PR numbers, image hashes, and registry address. + +- Pointers to logging, monitoring, analytics, or audit repositories. + +- Client library or tool information that can be used for debugging purposes: for example, name, version, and build information. + +- User or tool/system provenance information, such as URLs of related objects from other ecosystem components. + +- Lightweight rollout tool metadata: for example, config or checkpoints. + +- Phone or pager numbers of persons responsible, or directory entries that specify where that information can be found, such as a team web site. + +- Directives from the end-user to the implementations to modify behavior or engage non-standard features. + +Instead of using annotations, you could store this type of information in an external database or directory, but that would make it much harder to produce shared client libraries and tools for deployment, management, introspection, and the like. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/CRD.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/CRD.md" new file mode 100644 index 00000000..cfca0048 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/CRD.md" @@ -0,0 +1,25 @@ +--- +title: 'CRD' +lastUpdated: '2024-03-02' +--- + +Custom resources are extensions of the Kubernetes API. This page discusses when to add a custom resource to your Kubernetes cluster and when to use a standalone service. It describes the two methods for adding custom resources and how to choose between them. + +## Custom resources + +A resource is an endpoint in the Kubernetes API that stores a collection of API objects of a certain kind; for example, the built-in pods resource contains a collection of Pod objects. + +A custom resource is an extension of the Kubernetes API that is not necessarily available in a default Kubernetes installation. It represents a customization of a particular Kubernetes installation. However, many core Kubernetes functions are now built using custom resources, making Kubernetes more modular. + +Custom resources can appear and disappear in a running cluster through dynamic registration, and cluster admins can update custom resources independently of the cluster itself. Once a custom resource is installed, users can create and access its objects using kubectl, just as they do for built-in resources like Pods. + +## Custom controllers + +On their own, custom resources let you store and retrieve structured data. When you combine a custom resource with a custom controller, custom resources provide a true declarative API. + +The Kubernetes declarative API enforces a separation of responsibilities. You declare the desired state of your resource. The Kubernetes controller keeps the current state of Kubernetes objects in sync with your declared desired state. This is in contrast to an imperative API, where you instruct a server what to do. + +You can deploy and update a custom controller on a running cluster, independently of the cluster's lifecycle. Custom controllers can work with any kind of resource, but they are especially effective when combined with custom resources. The Operator pattern combines custom resources and custom controllers. You can use custom controllers to encode domain knowledge for specific applications into an extension of the Kubernetes API. + + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployments.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployments.md" new file mode 100644 index 00000000..2c9b4175 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployments.md" @@ -0,0 +1,146 @@ +--- +title: 'Deployments' +lastUpdated: '2024-03-02' +--- + +> Deplotment는 Pod와 ReplicaSets를 위한 선언적 업데이트를 제공한다. + +`Deployment`는 k8s의 핵심 개념중 하나인 `desired state`(목표 상태)를 설명하는 요소이다. Deployment에서 desired state를 정의하면 **배포 컨트롤러**가 원하는 상태로 복구한다. + +Deploy를 만들어 실습해보자! 더 자세하고 정확한 설명은 공식 를 참고하자. + +## Creating a Deployment + +```yml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +``` + +위 yml은 새로 만들 Deployment를 정의한다. 자세히 설명하자면 다음과 같다. + +- `nginx-deployment`라는 이름을 가진 Deployment를 생성한다. (`.metadata.name`) +- 세 개의 replicated Pod를 가진 ReplicaSet을 생성한다. (`.spec.replicas`) +- 생성될 ReplicaSet이 `nginx`라는 이름의 태그를 가진 Pod를 관리하도록 한다.(`.spec.selector`) + +> **Note:**
`.spec.selector.matchLabels` 필드는 {key,value} 두 쌍의 맵이다. 값은 배열로 이뤄져있으며, 해당 키의 리스트에 속해있는 value를 가지는 포드를 모두 가지고 온다. + +- `template` 필드는 Deployment에 포함될 포드또는 템플릿을 정의하며, 아래와 같은 의미의 sub-field들을 가지고있다. + - 포드에 `app: nginx` 라벨을 붙인다. (`.metadata.labels`) + - 컨테이너가 어떤 이미지를 가질지 결정한다. + - nginx라는 이름을 가진 하나의 Container를 만든다.(`.spec.template.spec.containers[0].name`) + +### 실습 + +1. yml로 Deployment를 생성한다 + +```bash +kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yml +``` + +2. `kubectl get deployments`로 Deployment가 생성되었는지 확인한다. + +```bash +NAME READY UP-TO-DATE AVAILABLE AGE +nginx-deployment 0/3 0 0 1s +``` + +각 정보는 아래와 같은 의미를 가지고 있다. + +- `name`: namespace안에 있는 Deplotment들의 이름 리스트를 나타낸다. +- `READY`: 배포를 작성할 때 정의한 응용프로그램의 복제본이 몇 개만큼 준비되었는지를 표시한다. +- `UP-TO-DATE`: 원하는 상태에 도달하도록 업데이트된 복제본 수를 표시한다. +- `AVAILABLE`: 사용자가 사용할 수 있는 애플리케이션의 복제본 수를 표시한다. +- `AGE`: 애플리케이션이 동작한 기간을 표시한다. + +`.spec.replicas`에 설정된 사항에 따르면, 3개의 replica가 있는 것이 목표 상태이다. + +3. Deployment rollout status를 보기 위해 `kubectl rollout status deployment/nginx-deployment`를 실행한다. + +``` +Waiting for rollout to finish: 2 out of 3 new replicas have been updated... +deployment "nginx-deployment" successfully rolled out +``` + +4. 몇 초를 기다린 후, `kubectl get deployments`를 다시 실행한다. + +``` +NAME READY UP-TO-DATE AVAILABLE AGE +nginx-deployment 3/3 3 3 18s +``` + +세 개의 모든 Replica들이 생성되었고, 사용 가능한 상태가 되었음을 알 수 있다. + +5. Deploy에 의해 생성된 ReplicaSet(rs)를 보기 위해 `rs`를 실행한다. + +``` +NAME DESIRED CURRENT READY AGE +nginx-deployment-75675f5897 3 3 3 18s +``` + +각 정보는 아래와 같은 의미를 가지고 있다. + +- `name`: namespace안에 있는 Deplotment들의 이름 리스트를 나타낸다. +- `DESIRED`: 원하는 Replica의 개수를 표시한다. +- `CURRENT`: 현재 실행중인 Replica의 개수를 표시한다. +- `READY`: 배포를 작성할 때 정의한 응용프로그램의 복제본이 몇 개만큼 준비되었는지를 표시한다. +- `AGE`: 애플리케이션이 동작한 기간을 표시한다. + +6. 각 포드에 자동으로 부여된 이름을 확인하기 위해서 `kubectl get pods --show-labels`를 실행한다. + +``` +NAME READY STATUS RESTARTS AGE LABELS +nginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453 +nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453 +nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453 +``` + +`nginx` pod에 대한 세개의 ReplicaSet이 만들어졌다. + +> **Note:**
You must specify an appropriate selector and Pod template labels in a Deployment (in this case, app: nginx).
Do not overlap labels or selectors with other controllers (including other Deployments and StatefulSets). Kubernetes doesn't stop you from overlapping, and if multiple controllers have overlapping selectors those controllers might conflict and behave unexpectedly. + +## Deployment 정보 수정하는법 + +> **Note:**
Deployment의 rollout은 Deployment의 Pod template(`.spec.template`)에 따라 자동으로 작동된다. (label이나 container의 image 등을 조건으로 함) Deployment를 scaling하는 것과 같은 다른 종류의 update는 rollout의 트리거가 되지 않는다. + +Deployment를 수정하는 방법을 알아보자. + +1. nginx Pod 버전을 `1.14.2`에서 `1.16.1`로 업데이트해보자. + +```java +kubectl set image deployment ${deployment_name} ${prev_image}=${next_image} --record +(kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1) +``` + +둘 중 하나의 명령어를 사용할 수 있다. + +2. rollout status를 보기 위해 `kubectl rollout status deployment/nginx-deployment`를 입력한다. + +``` +Waiting for rollout to finish: 2 out of 3 new replicas have been updated... +``` +또는 +``` +deployment "nginx-deployment" successfully rolled out +``` +가 결과로 출력될 것이다. + +그 외의 정보를 수정할때도 set 또는 edit 명령어를 사용할 수 있다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployment\342\200\205Status.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployment\342\200\205Status.md" new file mode 100644 index 00000000..7ec65850 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployment\342\200\205Status.md" @@ -0,0 +1,196 @@ +--- +title: 'Deployment Status' +lastUpdated: '2024-03-02' +--- + +A Deployment enters various states during its lifecycle. It can be progressing while rolling out a new ReplicaSet, it can be complate, or it can fail to progress. + +## Progressing Deployment + +Kubernetes marks a Deployment as progressing when one of the follwing tasks is performed: +- The Deployment creates a new ReplicaSet. +- The Deployment is scaling up its newest ReplicaSet. +- The Deployment is scaling down its older ReplicaSet(s). +- New Pords become ready or available (ready for at least [MinReadySeconds](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#min-ready-seconds)) + +When the rollout becomes "progressing", the Deployment controller adds a condition with the following attributes to teh Deployment's `.status.conditions`: +- `type: Progressing` +- `status: "True"` +- `reason: NewReplicaSetCreated` | `reason: FoundNewReplicaSet` | `eeason: ReplicaSetUpdated` + +You can monitor the progress for a Deployment by using `kubectl rollout status`. + +## Complete Deployment + +Kubernetes marks a Deployment as complete when it has the following characteristics: +- All of the replicas associated with the Deployment have been updated to the lates version you've specified, meaning any updates you've requested have been completed. +- All of replicas associated with the Deployment are available. +- No old replias for the Deployments are running. + +Whe the rollout becomes "complete", the Deployment controller sets a confition with the following attributes to the Deploymwnt's `.status.conditions`: + +- `type: Progressing` +- `status: "True"` +- `reason: NweReplicaSetAvailable` + +This `Progressing` condition will retain a status calue of "True" until a new rollout is initiated. The condition holes even when availability of replicas changes (which does instead affect the `Available` condition). + +You can check if a Deployment has completed by using `kubectl rollout status`. If the rollout completed successfully, `kubectl rollout status` returns a zero exit code. + +```bash +kubectl rollout status deployment/nginx-deployment +``` + +The output is similar to this: + +```bash +Waiting for rollout to finish: 2 of 3 updated replicas are available... +deployment "nginx-deployment" successfully rolled out +``` + +and the exit status from `kubectl rollout` is 0 (success): + +```bash +$echo $? +0 +``` + +## Failed Deployment + +Your Deployment may get stuck trying to deploy its newest ReplicaSet without ever completing. This can occur due to some of the following factors: + +- Insufficient quota +- Readiness probe failures +- Image pull errors +- Insufficient permissions +- Limit ranges +- Application runtime misconfiguration + +One way you can detect this condition is to specify a deadline parameter in your Deployment spec: ([`.spec.progressDeadlineSeconds`](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#progress-deadline-seconds)). `.spec.progressDeadlineSeconds` denotes the number of seconds the Deployment controller waits before indicating (in the Deployment status) that the Deployment progress has stalled. + +The following `kubectl` command sets the spec with progressDeadlineSeconds to make the controller report lack of progress of a rollout for a Deployment after 10 minutes: + +```bash +kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}' +``` + +The output is similar to this: + +```bash +deployment.apps/nginx-deployment patched +``` + +Once the deadline has been exceeded, the Deployment controller adds a DeploymentCondition with the following attributes to the Deployment's .status.conditions: + +- `type: Progressing` +- `status: "False"` +- `reason: ProgressDeadlineExceeded` + +This condition can also fail early and is then set to status value of "False" due to reasons as ReplicaSetCreateError. Also, the deadline is not taken into account anymore once the Deployment rollout completes. + +See the Kubernetes API conventions for more information on status conditions. + +> Note: Kubernetes takes no action on a stalled Deployment other than to report a status condition with reason: ProgressDeadlineExceeded. Higher level orchestrators can take advantage of it and act accordingly, for example, rollback the Deployment to its previous version. + +> Note: If you pause a Deployment rollout, Kubernetes does not check progress against your specified deadline. You can safely pause a Deployment rollout in the middle of a rollout and resume without triggering the condition for exceeding the deadline. + +You may experience transient errors with your Deployments, either due to a low timeout that you have set or due to any other kind of error that can be treated as transient. For example, let's suppose you have insufficient quota. If you describe the Deployment you will notice the following section: + +kubectl describe deployment nginx-deployment +The output is similar to this: + +```bash +<...> +Conditions: + Type Status Reason + ---- ------ ------ + Available True MinimumReplicasAvailable + Progressing True ReplicaSetUpdated + ReplicaFailure True FailedCreate +<...> +``` + +If you run kubectl get deployment nginx-deployment -o yaml, the Deployment status is similar to this: + +```yaml +status: + availableReplicas: 2 + conditions: + - lastTransitionTime: 2016-10-04T12:25:39Z + lastUpdateTime: 2016-10-04T12:25:39Z + message: Replica set "nginx-deployment-4262182780" is progressing. + reason: ReplicaSetUpdated + status: "True" + type: Progressing + - lastTransitionTime: 2016-10-04T12:25:42Z + lastUpdateTime: 2016-10-04T12:25:42Z + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: 2016-10-04T12:25:39Z + lastUpdateTime: 2016-10-04T12:25:39Z + message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota: + object-counts, requested: pods=1, used: pods=3, limited: pods=2' + reason: FailedCreate + status: "True" + type: ReplicaFailure + observedGeneration: 3 + replicas: 2 + unavailableReplicas: 2 +``` + +Eventually, once the Deployment progress deadline is exceeded, Kubernetes updates the status and the reason for the Progressing condition: + +```bash +Conditions: + Type Status Reason + ---- ------ ------ + Available True MinimumReplicasAvailable + Progressing False ProgressDeadlineExceeded + ReplicaFailure True FailedCreate +``` + +You can address an issue of insufficient quota by scaling down your Deployment, by scaling down other controllers you may be running, or by increasing quota in your namespace. + +If you satisfy the quota conditions and the Deployment controller then completes the Deployment rollout, you'll see the Deployment's status update with a successful condition (`status: "True"` and `reason: NewReplicaSetAvailable`). + +```bash +Conditions: + Type Status Reason + ---- ------ ------ + Available True MinimumReplicasAvailable + Progressing True NewReplicaSetAvailable +``` + +- `type: Available` with `status: "True"` means that your **Deployment has minimum availability**. Minimum availability is dictated by the parameters specified in the deployment strategy. + +- `type: Progressing` with `status: "True"` means that your Deployment is either in the middle of a rollout and it is progressing or that it has successfully completed its progress and the minimum required new replicas are available (see the Reason of the condition for the particulars - in our case `reason: NewReplicaSetAvailable` means that the Deployment is complete). + +You can check if a Deployment has failed to progress by using kubectl rollout status. kubectl rollout status returns a non-zero exit code if the Deployment has exceeded the progression deadline. + +```bash +kubectl rollout status deployment/nginx-deployment +``` + +The output is similar to this: + +```bash +Waiting for rollout to finish: 2 out of 3 new replicas have been updated... +error: deployment "nginx" exceeded its progress deadline +``` + +and the exit status from kubectl rollout is 1 (indicating an error): + +```bash +echo $? +1 +``` + +### Operating on a failed deployment + +All actions that apply to a complete Deployment also apply to a failed Deployment. You can scale it up/down, roll back to a previous revision, or even pause it if you need to apply multiple tweaks in the Deployment Pod template. + +--- +reference +- https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployment\342\200\205Strategy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployment\342\200\205Strategy.md" new file mode 100644 index 00000000..4850fb07 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Deployment\342\200\205Strategy.md" @@ -0,0 +1,37 @@ +--- +title: 'Deployment Strategy' +lastUpdated: '2024-03-02' +--- + +Deployment's `.spec.strategy` specifies the strategy used to replace old Pods by new ones. `.spec.strategy.type` can be "Recreate" or "RollingUpdate". "RollingUpdate" is the default value. + +## Recreate Deployment + +All existing Pods are killed before new ones are created when `.spec.strategy.type==Recreate`. + + + +> Note: This will only guarantee Pod termination previous to creation for upgrades. If you upgrade a Deployment, all Pods of the old revision will be terminated immediately. Successful removal is awaited before any Pod of the new revision is created. If you manually delete a Pod, the lifecycle is controlled by the ReplicaSet and the replacement will be created immediately (even if the old Pod is still in a Terminating state). If you need an "at most" guarantee for your Pods, you should consider using a StatefulSet. + +## Rolling Update Deployment + +The Deployment updates Pods in a rolling update fashion when `.spec.strategy.type=RollingUpdate`. You can specify maxUnavailable and maxSurge to control the rolling update process. + + + +### Max Unavailable + +`.spec.strategy.rollingUpdate.maxUnavailable` is an optional field that specifies the maximum number of Pods that can be unavailable during the update process. The value can be an absolute number (for example, 5) or a percentage of desired Pods (for example, 10%). The absolute number is calculated from percentage by rounding down. The value cannot be 0 if `.spec.strategy.rollingUpdate.maxSurge` is 0. The default value is 25%. + +For example, when this value is set to 30%, the old ReplicaSet can be scaled down to 70% of desired Pods immediately when the rolling update starts. Once new Pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of Pods available at all times during the update is at least 70% of the desired Pods. + +### Max Surge + +`.spec.strategy.rollingUpdate.maxSurge` is an optional field that specifies the maximum number of Pods that can be created over the desired number of Pods. The value can be an absolute number (for example, 5) or a percentage of desired Pods (for example, 10%). The value cannot be 0 if MaxUnavailable is 0. The absolute number is calculated from the percentage by rounding up. The default value is 25%. + +For example, when this value is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new Pods does not exceed 130% of desired Pods. Once old Pods have been killed, the new ReplicaSet can be scaled up further, ensuring that the total number of Pods running at any time during the update is at most 130% of desired Pods. + +--- +reference +- https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#recreate-deployment +- https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/ diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Events.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Events.md" new file mode 100644 index 00000000..eb1aaef3 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Events.md" @@ -0,0 +1,69 @@ +--- +title: 'Events' +lastUpdated: '2024-03-02' +--- + +Kubernetes Events는 하나의 Kubernetes 리소스 타입으로서 Kubernetes 리소스들의 state 변화, 에러 또는 시스템에 특정 메세지를 전파해야할 때 자동으로 만들어진다. 이러한 Kubernetes Events 리소스는 Kubernetes 개발 및 운영하며 디버깅시에 매우 유용하게 사용된다. + +## Events 조회 + +`kubectl describe pod pod-name`를 사용하면 아랫부분에 Events 항목을 볼 수 있다. 이것이 바로 해당 Pod와 관련된 Kubernetes Events들의 정보를 나타낸다. + +image + +특정 Pod 뿐 아니라 현재 namespace 에 발생하는 모든 Events를 조회하고 싶다면 `kubectl get events` 를 통해 조회할 수 있다. 하지만 모든 리소스들의 Events들이 조회되기 때문에 리소스가 많은 상황이라면 원하는 정보를 찾기 힘들 것이다. 만약 특정 셀렉터를 통해 원하는 정보만 찾아보고 싶다면 `--field-selector` 옵션을 사용하면 된다. + +```bash +# Warning 타입만 조회 +kubectl get events --field-selector type=Warning + +# Pod events를 제외한 다른 events 조회 +kubectl get events --field-selector involvedObject.kind!=Pod + +# minikube 라는 이름의 node에서 발생한 events만 조회 +kubectl get events --field-selector involvedObject.kind=Node,involvedObject.name=minikube +``` + +Kubernetes를 사용하다보면 Events가 계속 쌓이게 되는데 이걸 계속 유지하다보면 용량 및 성능에서 악영향이 있을 수 있다. 그래서 기본적으로는 1시간동안 유지되고 자동으로 제거됩니다. + +## Events 필드 (events.k8s.io v1 Events) + +위의 describe 부분의 사진에서 Events 정보를 보면 여러 필드를 가지고 있는 것을 볼 수 있다. Events의 모든 필드는 [Kubernetes Reference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#event-v1-events-k8s-io)에서 확인하실 수 있다. + +간략하게 위에서 출력된 필드만 살펴보자. + +- **type**: Normal, Warning 2가지의 값 중 하나를 가지며 추후 추가될 수 있습니다. 말 그대로 일반적인 작업에 의해 생겨난 Event인지, 아니면 어느 오류 및 실패로 인해 생겨난 Event인지 표현한다. +- **reason**: 왜 해당 Event가 발생했는지를 나타낸다. 딱히 정해진 형식은 없고, 128자 이하의 사람이 이해할 수 있는 짧은 명칭이다. kubelet에서 사용하는 Reason의 종류는 [여기](https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/events/event.go)서 볼 수 있다. +- **eventTime**: Age로 출력된 부분의 값을 나타낸다. Event 발생한 시각을 의미하며 MicroTime 타입의 데이터로 이루어져있다. +- **reportingController**: From으로 출력된 부분의 값을 나타낸다. 말 그대로 해당 Event를 발생시킨 Controller의 이름을 의미한다. +- **note**: Events가 발생된 작업. 그 작업의 상태(status)에 대한 설명을 의미한다. + +## Deprecated Events + +```bash +$ kubectl api-resources -o wide | grep event +NAME SHORTNAMES APIGROUP NAMESPACED KIND VERBS +events ev v1 true Event create,delete,deletecollection,get,list,patch,update,watch +events ev events.k8s.io/v1 true Event create,delete,deletecollection,get,list,patch,update,watch +``` + +Kubernetes 기본 리소스 중 Events는 api group이 `v1`인 것과 `events.k8s.io/v1`인 것 두가지가 있다. 그 이유는 무엇일까? + +기존에 core 그룹의 Events가 존재했다. 하지만 이 Events에는 2가지 문제점이 있었다. + +- Events는 어플리케이션 개발자에게 해당 어플리케이션에 무슨 일이 일어나고 있는지 명확하게 알려줄 수 있어야 한다. 하지만 core 그룹의 Events는 불명확한 의미를 가진 스팸성이었다. +- Events는 Kubernetes의 성능에 문제를 일으키거나 영향을 주어서는 안된다. 하지만 core 그룹의 Events는 알려진 성능문제가 존재했다. + +이를 비롯한 여러 문제들로 인해 계속해서 변화 요구가 있었고, 이에 새로운 `events.k8s.io` API GROUP의 events가 나오게 된 것이다. 현재까진 `core.Event`에서 `events.Event`로 점점 넘어가고 있는 추세라고 한다. 하위호환성을 지키기 위해 `core.Event` 에서 제거할 필드들은 제거 없이 앞에 Deprecated prefix가 붙은 상태로 유지되었다. + +`core.Event`와 `events.Event`의 필드는 조금씩 다르다. 모든 필드 차이들은 [여기](https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/383-new-event-api-ga-graduation/README.md#backward-compatibility)에서 확인할 수 있다. + +## Event와 Slack 연동 + +[BotKube](https://botkube.io/), [DataDog](https://docs.datadoghq.com/events/) 등의 툴을 사용해서 Event 정보를 slack 알림으로 전송할 수 있다. + +자세한 방법은 각 툴의 사이트를 방문하여 살펴보자 ㅇㅅㅇ + +--- +참고 +- https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/383-new-event-api-ga-graduation/README.md#motivation \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Labels\342\200\205and\342\200\205Selectors.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Labels\342\200\205and\342\200\205Selectors.md" new file mode 100644 index 00000000..750ad7b2 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Labels\342\200\205and\342\200\205Selectors.md" @@ -0,0 +1,60 @@ +--- +title: 'Labels and Selectors' +lastUpdated: '2024-03-02' +--- + +- Labels are key/value pairs that are attached to objects such as Pods. + +- Labels are intended to be used to specify identifying attributes of objects that are **meaningful and relevant to users**, but **do not directly imply semantics to the core system.** + +- Labels can be used to organize and to select subsets of objects. + +- Labels can be attached to objects at creation time and subsequently added and modified at any time. + +- Labels enable users to map their own organizational structures onto system objects in a loosely coupled fashion, without requiring clients to store these mappings. + +- Service deployments and batch processing pipelines are often multi-dimensional entities (e.g., multiple partitions or deployments, multiple release tracks, multiple tiers, multiple micro-services per tier). Management often requires cross-cutting operations, which breaks encapsulation of strictly hierarchical representations, especially rigid hierarchies determined by the infrastructure rather than by users. + +For example, here's a manifest for a Pod that has two labels `environment: production` and `app: nginx:` + +```yml +apiVersion: v1 +kind: Pod +metadata: + name: label-demo + labels: + environment: production + app: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +``` + +## Label selectors + +- Unlike names and UIDs, labels do not provide uniqueness. In general, we expect many objects to carry the same label(s). + +- Via a label selector, the client/user can identify a set of objects. The label selector is the core grouping primitive in Kubernetes. + +> For some API types, such as ReplicaSets, the label selectors of two instances must not overlap within a namespace, or the controller can see that as conflicting instructions and fail to determine how many replicas should be present. + +- The API currently supports two types of selectors + - equality-based (`=`, `==`, `!=`) + ```yml + environment = production # all resources with key equal to environment and value equal to production + tier != frontend # all resources with no labels with the tier key, frontend value. + ``` + - set-based. + ```yml + environment in (production, qa) # all resources with key equal to environment and value equal to production or qa. + tier notin (frontend, backend) # all resources with key equal to tier and values other than frontend and backend, and all resources with no labels with the tier key. + partition # including a label with key partition + !partition # without a label with key partition + ``` + +- A label selector can be made of multiple requirements which are comma-separated. In the case of multiple requirements, all must be satisfied so the comma separator acts as a logical AND (`&&`) operator. + +> For both equality-based and set-based conditions there is no logical OR (||) operator. Ensure your filter statements are structured accordingly. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/PV\342\200\205&\342\200\205PVC.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/PV\342\200\205&\342\200\205PVC.md" new file mode 100644 index 00000000..36007e02 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/PV\342\200\205&\342\200\205PVC.md" @@ -0,0 +1,125 @@ +--- +title: 'PV & PVC' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/2e466438-16d2-4a3d-8fa7-ca68e9e7029f) + +k8s에서 Volume을 사용하는 구조는 PV라고 하는 퍼시스턴트 볼륨(PersistentVolume)과 PVC라고 하는 퍼시스턴트 볼륨 클레임(PersistentVolumeClaim) 2개로 분리되어 있다. + +## PV/PVC + +PV는 Persistent Volume의 약자이다. pod와는 별개로 관리되며 별도의 생명 주기가 있다. PVC는 사용자가 PV에 하는 요청이다. 사용하고 싶은 용량은 얼마인지, 읽기/쓰기는 어떤 모드로 설정하고 싶은지 등을 정해서 요청한다. + +k8s 볼륨을 pod에 직접 할당하는 방식이 아니라 중간에 PVC를 두어 pod와 pod가 사용할 스토리지를 분리할 수 있다. 이런 구조는 pod 각각의 상황에 맞게 다양한 스토리지를 사용할 수 있게 한다. + +클라우드 서비스를 사용할 때는 본인이 사용하는 클라우드 서비스에서 제공해주는 볼륨 서비스를 사용할 수도 있고, 직접 구축한 스토리지를 사용할 수도 있다. 이렇게 다양한 스토리지를 PV로 사용할 수 있지만 pod에 직접 연결하는 것이 아니라 PVC를 거쳐서 사용하므로 pod는 어떤 스토리지를 사용하는지 신경 쓰지 않아도 된다. + +## 생명주기 + +PV와 PVC는 다음과 같은 생명주기가 있다. + +image + +### 1. Provisioning(프로비저닝) + +PV를 만드는 단계를 프로비저닝(Provisioning)이라고 한다. 프로비저닝 방법에는 두 가지가 있는데, PV를 미리 만들어 두고 사용하는 정적(static) 방법과 요청이 있을 때 마다 PV를 만드는 동적(dynamic) 방법입니다. + +- **정적(static) 프로비저닝** + - 정적으로 PV를 프로비저닝할 때는 클러스터 관리자가 미리 적정 용량의 PV를 만들어 두고 사용자의 요청이 있으면 미리 만들어둔 PV를 할당한다. 사용할 수 있는 스토리지 용량에 제한이 있을 때 유용하다. 사용하도록 미리 만들어 둔 PV의 용량이 100GB라면 150GB를 사용하려는 요청들은 실패한다. 1TB 스토리지를 사용하더라도 미리 만들어 둔 PV 용량이 150GB 이상인 것이 없으면 요청이 실패한다. + +- **동적(dynamic) 프로비저닝** + - 동적으로 프로비저닝 할 때는 사용자가 PVC를 거쳐서 PV를 요청했을 때 생성해 제공한다. 쿠버네티스 클러스터에 사용할 1TB 스토리지가 있다면 사용자가 원하는 용량만큼을 생성해서 사용할 수 있다. 정적 프로비저닝과 달리 필요하다면 한번에 200GB PV도 만들 수 있다. PVC는 동적 프로비저닝할 때 여러가지 스토리지 중 원하는 스토리지를 정의하는 스토리지 클래스(Storage Class)로 PV를 생성한다. + +### 2. Binding(바인딩) + +바인딩(Binding)은 프로비저닝으로 만든 PV를 PVC와 연결하는 단계이다. PVC에서 원하는 스토리지의 용량과 접근방법을 명시해서 요청하면 거기에 맞는 PV가 할당 됩니다. 이 때 PVC에서 원하는 PV가 없다면 요청은 실패한다. 하지만 PVC는 원하는 PV가 생길 때까지 대기하다가 바인딩한다. + +PV와 PVC의 매핑은 1:1 관계 이며, PVC 하나가 여러 개 PV에 할당될 수 없다. + +### 3. Using(사용) + +PVC는 pod에 설정되고 pod는 PVC를 볼륨으로 인식해서 사용한다. + +할당된 PVC는 pod를 유지하는 동안 계속 사용되며, 시스템에서 임의로 삭제할 수 없다. 이 기능을 'Storage Object in Use Protection' (사용 중인 스토리지 오브젝트 보호)라고 한다. 사용 중인 데이터 스토리지를 임의로 삭제하는 경우 치명적인 결과가 발생할 수 있으므로 이런 보호 기능을 사용하는 것이다. + +### 4. Reclaiming(반환) + +사용이 끝난 PVC는 삭제되고 PVC를 사용하던 PV를 초기화(reclaim)하는 과정을 거칩니다. 이를 Reclaiming(반환)이라고 한다. + +초기화 정책에는 Retain, Delete, Recycle이 있다. + +- **Retain** + - Retain은 PV를 그대로 보존한다. PVC가 삭제되면 사용 중이던 PV는 해제(released) 상태여서 다른 PVC가 재사용할 수 없다. 단순히 사용 해제 상태이므로 PV 안의 데이터는 그대로 남아있다. 이 PV를 재사용하려면 관리자가 다음 순서대로 직접 초기화해줘야한다. + 1. PV 삭제. 만약 PV가 외부 스토리지와 연결되었다면 PV는 삭제되더라도 외부 스토리지의 볼륨은 그대로 남아 있다. + 2. 스토리지에 남은 데이터를 직접 정리 + 3. 남은 스토리지의 볼륨을 삭제하거나 재사용하려면 해당 볼륨을 이용하는 PV 재생성 +- **Delete** + - PV를 삭제하고 연결된 외부 스토리지 쪽의 볼륨도 삭제한다. 프로비저닝할 때 동적 볼륨 할당 정책으로 생성된 PV들은 기본 반환 정책(Reclaim Policy)이 Delete 입니다. 상황에 따라 처음에 Delete로 설정된 PV의 반환 정책을 수정해서 사용해야 한다. +- **Recycle** + - Recycle은 PV의 데이터들을 삭제하고 다시 새로운 PVC에서 PV를 사용할 수 있도록 한다. 특별한 파드를 만들어 두고 데이터를 초기화하는 기능도 있지만 동적 볼륨 할당을 기본 사용할 것을 추천 + +## 예시 + +```yaml +apiVersion: v1 +kind: PersistentVolume +metadata: + name: pv-hostpath +spec: + capacity: + storage: 2Gi + volumeMode: Filesystem # ----------------- (1) + accessModes: # --------------------------- (2) + - ReadWriteOnce + storageClassName: manual # --------------- (3) + persistentVolumeReclaimPolicy: Delete # -- (4) + hostPath: + path: /tmp/k8s-pv # -------------------- (5) +``` + +1. `.spec.volumeMode` 필드는 쿠버네티스 1.9버전 부터 추가된 필드이다. 기본 필드 값은 FileSystem 으로 볼륨을 파일 시스템 형식으로 사용한다. + RAW 블록 디바이스 형식으로 사용하는 raw 라는 필드 값을 설정할 수 있다. 볼륨을 설정해서 사용할 수도 있다. + +2. `.spec.accessModes` 필드는 볼륨의 읽기/쓰기 옵션을 설정합니다. 볼륨은 한 번에 accessModes 필드를 하나만 설정할 수 있으며 필드 값은 세 가지가 있다. + - ReadWriteOnce: 노드 하나에만 볼륨을 읽기/쓰기하도록 마운트할 수 있음 + - ReadOnlyMany: 여러 개 노드에서 읽기 전용으로 마운트할 수 있음 + - ReadWriteMany: 여러 개 노드에서 읽기/쓰기 가능하도록 마운트할 수 있음 + +
+ 볼륨 플러그인 별로 지원가능한 옵션 +
+ + |Volume Plugin|ReadWriteOnce|ReadOnlyMany|ReadWriteMany| + |-|-|-|-| + |AWSElasticBlockStore|✓|-|-| + |AzureFile|✓|✓|✓| + |AzureDisk|✓|-|-| + |CephFS|✓|✓|✓| + |Cinder|✓|-|-| + |FC|✓|✓|-| + |FlexVolume|✓|✓|-| + |Flocker|✓|-|-| + |GCEPersistentDisk|✓|✓|-| + |Glusterfs|✓|✓|✓| + |HostPath|✓|-|-| + |iSCSI|✓|✓|-| + |Quobyte|✓|✓|✓| + |NFS|✓|✓|✓| + |RBD|✓|✓|-| + |VsphereVolume|✓|-|- (works when pods are collocated)| + |PortworxVolume|✓|-|✓| + |ScaleIO|✓|✓|-| + |StorageOS|✓|-|-| +
+
+ +1. `.spec.storageClassName` 은 스토리지 클래스(StorageClass)를 설정하는 필드이다. 특정 스토리지 클래스가 있는 PV는 해당 스토리지 클래스에 맞는 PVC만 연결된다. PV에 `.spec.storageClassName` 필드 설정이 없으면 오직 `.spec.storageClassName` 필드 설정이 없는 PVC와 연결될 수 있다. + +2. `.spec.persistentVolumeReclaimPolicy` 필드는 PV가 해제되었을 때 초기화 옵션을 설정한다. 앞에서 살펴본 것 처럼 Retain, Recycle, Delete 정책 중 하나이다. + +3. `.spec.hostPath` 필드는 해당 PV의 볼륨 플러그인을 명시한다. 필드 값을 hostPath로 설정했기 때문에 하위의 `.spec.hostPath.path` 필드는 마운트시킬 로컬 서버의 경로를 설정한다. + +--- +참고 +- https://kubernetes.io/docs/concepts/storage/persistent-volumes/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod.md" new file mode 100644 index 00000000..5a438cf0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod.md" @@ -0,0 +1,16 @@ +--- +title: 'Pod' +lastUpdated: '2024-03-02' +--- + +Pod는 **동일한 실행환경에서 실행되는 애플리케이션 컨테이너와 볼륨으로 구성된 집합체**다. 포드는 쿠버네티스 클러스터에서 배포 가능한 가장 작은 아티팩트(artipact)다. 즉, 포드에 있는 모든 컨테이너가 `동일한 머신에 있음`을 뜻한다. + +Pod에 있는 각 컨테이너는 각자의 `cgroup`을 운영하지만 몇가지 `Linux 네임스페이스`는 공유한다. + +Pod의 각 컨테이너는 각자의 cgroup 을 운영하지만 몇가지 리눅스 네임스페이스를 공유하며, 서로 다른 파드는 각 애플리케이션이 격리되어 있고 각기 다른 IP주소와 호스트네임을 갖는다. 또한 System V IPC나 POSIX 메시지 큐(IPC 네임스페이스)를 통해 기본 프로세스 간 통신 채널을 사용해 서로 통신할 수 있다. + +동일한 노드에서 동작하는 서로 다른 Pod의 컨테이너는 각기 다른 서버에 존재할 수도 있다. + +## 포드 설계 원칙 + +포드를 설계할때는 **간 컨테이너가 서로 다른 머신에 있어도 잘 동작할지 여부**를 고려해야한다. 만약 서로 다른 머신에 있을때 잘 동작하지 않을 것이라면 같은 포드로 묶어줘야하고, 그렇지 않다면 다른 포드로 분리해줘야한다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod\342\200\205Readiness\342\200\205and\342\200\205Probes.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod\342\200\205Readiness\342\200\205and\342\200\205Probes.md" new file mode 100644 index 00000000..b7b48886 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod\342\200\205Readiness\342\200\205and\342\200\205Probes.md" @@ -0,0 +1,170 @@ +--- +title: 'Pod Readiness and Probes' +lastUpdated: '2024-03-02' +--- + +Pord readiness is and additional indication of whether the pod is ready to serve traffic. Pod readiness determines whether the pod address shows up in the `Endpoints` object from an external source. Other Kubernetes resources that manage pods, like depolyments, take pod readiness into account for decision-making, such as advancing during a rolling update. + +During rolling deployment, a new pod becomes ready, but a service, network policy, or load balancer is not yet prepared for the new pod due to whatever reason. This may cause service disruptio or loss of backend capacity. It should be noted that if a pod spec does contain probes of any type, Kubernetes defaults to success for all three types. + +Users can specify pod readiness checks in the pod spec. From thare, the Kublet executes the specified check and updates the pod status based on successes or failures. + +Probes effect the `.Status.Phase` field of a pod. The following is a list of the pod phases and their descriptions: + +- **Pending** + - The pod has been accepted by the cluster, but one or more of the containers has not been set up and made ready to run. This includes the time a pod spends waiting to be scheduled as well as the time spent downloading container images over the network. + +- **Running** + - The pod has been scheduled to a node, and all the containers have been created. At least one container is still running or is in the process of starting or restarting. Note that some containers may be in a failed state, such as in a CrashLoopBackoff. + +- **Succeeded** + - All containers in the pod have terminated in success and will not be restarted. + +- **Failed** + - All containers in the pod have terminated, and at least one container has terminated in failure. That is, the container either exited with nonzero status or was terminated by the system. + +- **Unknown** + - For some reason the state of the pod could not be determined. This phase typically occurs due to an error in communicating with the Kubelet where the pod should be running. + +The Kubelet performs serveral types of health checks for individual containers in a pod: `livenessProbe`, `readinessProbe`, and `startupProbe`. The Kublet (and, by extension, the node it-self) must be able to connect to all containers running on that node in order to perfrom any HTTP health checks. + +- **Liveness Probe** + - Liveness probes determine **whether or not an application running in a container is in a healthy state.** If the liveness probe detects an unhealthy state, then Kubernetes kills the container and tries to redeploy it. + - The liveness probe is configured in the spec.containers.livenessprobe attribute of the pod configuration. + +- **Startup Probe** + - A startup probe verifies **whether the application within a container is started.** Startup probes run before any other probe, and, unless it finishes successfully, disables other probes. If a container fails its startup probe, then the container is killed and follows the pod’s restartPolicy. + - This type of probe is only executed at startup, unlike readiness probes, which are run periodically. + - The startup probe is configured in the spec.containers.startupprobe attribute of the pod configuration. + +- **Readiness Probe** + - Readiness probes determine **whether or not a container is ready to serve requests.** If the readiness probe returns a failed state, then Kubernetes removes the IP address for the container from the endpoints of all Services. + - Developers use readiness probes to instruct Kubernetes that a running container should not receive any traffic. This is useful when waiting for an application to perform time-consuming initial tasks, such as establishing network connections, loading files, and warming caches. + +Each probe has one of three results: + +- **Success**: The container passed the diagnostic. +- **Failure**: The container failed the diagnostic. +- **Unknown**: The diagnostic failed, so no action should be taken. + +The probes can be exec probes, which attempt to execute a binary within the container, TCP probes, or HTTP probes. If the probe fails more that the `faulureThreshold` number of times, Kubernetes will consider the check to have failed. The effect of this depends on type of probe. + +- **When the liveness probes fail**: + - the Kubelet will terminate the container. + - Liveness probes can easily cause unexpected failures if misused or misconfigured. + - The intended use case for liveness probes is to let the Kubelet know when to restart a container. However, as humans, we quickly learn that if “something is wrong, restart it” is a dangerous strategy. + - For example, suppose we create a liveness probe that loads the main page of our web app. Further, suppose that some change in the system, outside our container’s code, causes the main page to return a `404` or `500` error. There are frequent causes of such a scenario, such as a backend database failure, a required service failure, or a feature flag change that exposes a bug. In any of these scenarios, the liveness probe would restart the container. + - At best, this would be unhelpful; restarting the container will not solve a problem elsewhere in the system and could quickly worsen the problem. Kubernetes has container restart backoffs (`CrashLoopBackoff`), which add increasing delay to restarting failed containers + - With enough pods or rapid enough failures, the application may go from having an error on the home page to being hard-down. Depending on the application, **pods may also lose cached data upon a restart**; it may be strenuous to fetch or impossible to fetch during the hypothetical degradation. Because of this, use liveness probes with caution. When pods use them, they only depend on the container they are testing, with no other dependencies. + - Many engineers have specific health check endpoints, which provide minimal validation of criteria, such as “PHP is running and serving my API.” + - If a pod's `redinessProbe` fails, the pod's IP address wil not be in the endpoint object, and the service will not route traffic to it. + +- **A startup probe can provide a grace period before a liveness probe can take effect.** **Liveness probes will not terminate a container before the startup probe has succeeded**. An example use case is to allow a container to take many minutes to start, but to terminate a container quickly if it becomes unhealthy after starting. + +- **When a container's readiness probe fails**: + - the Kublet does not terminate it. Instead, the Kubelet writes the failure to the pod's status. + +Below example, the server has a liveness probe that perform an HTTP GET on port 8080 to the path `/healthz`, while the readiness probe uses `/` on the same port. + +```yaml +apiVersion: v1 +kind: Pod +metadata: + labels: + test: liveness + name: go-web +spec: + containers: + - name: go-web + image: go-web:v0.0.1 + ports: + - containerPort: 8080 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 +``` + +This status does not affect the pod itself, but other Kubernetes mechanisms reacy to it. One key example is ReplicaSets (and, by extension, deployments). A failing readiness probe cause the ReplicaSet controller to count the pod as unready, giving ride to a galtes deployment when too many new pods are unhealthy. The `Endpoins`/`EndpointsSlice` controllers also react to failing readiness probes. + +### Probe configurable options + +- **initialDelaySeconds** + - Amount of seconds after the container starts before liveness or readiness probes are initiates. Default 0; Minimum 0. +- **periodSeconds** + - How often probes are performed. Default 10; Minimum 1. +- **timeoutSeconds** + - Number of seconds after which the probe times out. Default 1; Minimum 1. +- **successThreshold** + - Minimum consecutive successes for the probe to be successful after failing. Default 1; must be 1 for liveness and startup probes; Minimum 1. +- **failureThreshold** + - When a probe fails, Kubernetes will try this many times before giving up. Giving up in the case of the liveness probe means the container will restart. For readiness probe, the pod will be marked Unready. Default 3; Minimum 1. + +### Readiness gates + +Application developers can also use readiness gates to help determine when the application inside the pod is ready. Available and stable since Kubernetes 1.14, to use `readiness gates`, manifest writers will add readiness gates in the pod’s spec to specify a list of additional conditions that the Kubelet evaluates for pod readiness. That is done in the `ConditionType` attribute of the readiness gates in the pod spec. The `ConditionType` is a condition in the pod’s condition list with a matching type. Readiness gates are controlled by the current state of `status.condition` fields for the pod, and if the Kubelet cannot find such a condition in the `status.conditions` field of a pod, the status of the condition is defaulted to False. + +As you can see in the following example, the feature-Y readiness gate is true, while feature-X is false, so the pod’s status is ultimately false: + +```yaml +kind: Pod +… +spec: + readinessGates: + - conditionType: www.example.com/feature-X + - conditionType: www.example.com/feature-Y +… +status: + conditions: + - lastProbeTime: null + lastTransitionTime: 2021-04-25T00:00:00Z + status: "False" + type: Ready + - lastProbeTime: null + lastTransitionTime: 2021-04-25T00:00:00Z + status: "False" + type: www.example.com/feature-X + - lastProbeTime: null + lastTransitionTime: 2021-04-25T00:00:00Z + status: "True" + type: www.example.com/feature-Y + containerStatuses: + - containerID: docker://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ready : true +``` + +Load balancers like the AWS ALB can use the readiness gate as part of the pod life cycle before sending traffic to it. + +--- + +The Kubelet must be able to connect to the Kubernetes API server, and communication between the pods and the Kubelet is made possible by the CNI. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/fe158dbc-addc-438d-b922-2e6ad0854b4a) + +In above figure, we can see all the connections made by all the components in a cluster: + +- **CNI** + - Network plugin in Kubelet that enables networking to get IPs for pods and services. + +- **gRPC** + - API to communicate from the API server to etcd. + +- **Kubelet** + - All Kubernetes nodes have a Kubelet that ensures that any pod assigned to it are running and configured in the desired state. + +- **CRI** + - The gRPC API compiled in Kubelet, allowing Kubelet to talk to container runtimes using gRPC API. The container runtime provider must adapt it to the CRI API to allow Kubelet to talk to containers using the OCI Standard (runC). CRI consists of protocol buffers and gRPC API and libraries. + +--- +reference +- https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +- https://developers.redhat.com/blog/2020/11/10/you-probably-need-liveness-and-readiness-probes +- https://medium.com/devops-mojo/kubernetes-probes-liveness-readiness-startup-overview-introduction-to-probes-types-configure-health-checks-206ff7c24487 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod\342\200\205\354\203\235\354\204\261\352\263\274\354\240\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod\342\200\205\354\203\235\354\204\261\352\263\274\354\240\225.md" new file mode 100644 index 00000000..f62f13f7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Pod\342\200\205\354\203\235\354\204\261\352\263\274\354\240\225.md" @@ -0,0 +1,33 @@ +--- +title: 'Pod 생성과정' +lastUpdated: '2024-03-02' +--- + + + +관리자가 애플리케이션을 배포하기 위해 ReplicaSet을 생성하면 다음과 같은 과정을 거쳐 Pod을 생성한다. + +흐름을 보면 각 모듈은 서로 통신하지 않고 **오직 API Server와만 통신**하는 것을 알 수 있다. API Server를 통해 etcd에 `저장된 상태를 체크`하고 `현재 상태와 원하는 상태가 다르면 필요한 작업을 수행`한다. 각 모듈이 하는 일을 보면 다음과 같다. + +### kubectl + +- ReplicaSet 명세를 yml파일로 정의하고 kubectl 도구를 이용하여 API Server에 명령을 전달 + +- API Server는 새로운 ReplicaSet Object를 etcd에 저장 + +### Kube Controller + +- Kube Controller에 포함된 ReplicaSet Controller가 ReplicaSet을 감시하다가 ReplicaSet에 정의된 Label Selector 조건을 만족하는 Pod이 존재하는지 체크 + +- 해당하는 Label의 Pod이 없으면 ReplicaSet의 Pod 템플릿을 보고 새로운 Pod(no assign)을 생성. 생성은 역시 API Server에 전달하고 API Server는 etcd에 저장 + +### Scheduler + +- 할당되지 않은(no assign) Pod가 있는지 체크 +- 할당되지 않은 Pod가 있으면 조건에 맞는 Node를 찾아 해당 Pod을 할당 + +### Kubelet + +- Kubelet은 자신의 Node에 할당되었지만 아직 생성되지 않은 Pod이 있는지 체크 +- 생성되지 않은 Pod이 있으면 명세를 보고 Pod을 생성 + Pod의 상태를 주기적으로 API Server에 전달 diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/RollingUpdate.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/RollingUpdate.md" new file mode 100644 index 00000000..0d5e9edc --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/RollingUpdate.md" @@ -0,0 +1,93 @@ +--- +title: 'RollingUpdate' +lastUpdated: '2024-03-02' +--- + +Rolling Update는 k8s의 업데이트 방법 중 하나로, 새로운 버전의 애플리케이션을 배포하고 기존 버전을 점진적으로 대체하는 과정으로 진행된다. 새로운 버전의 Pod로 트래픽이 전달되기 전까지 기존 버전이 유지되므로 무중단으로 애플리케이션을 업데이트 가능한 장점이 있다. 그러나 새로운 버전의 Pod와 기존 Pod가 함께 유지되는 기간이 존재하기 때문에 업데이트 중에 리소스를 더 사용할 수 있다. + +image + +기본적으로 Rolling Update는 다음과 같은 단계로 이뤄진다. + +1. 새로운 버전의 애플리케이션을 배포한다. 이때 기존 버전은 유지된 상태로 새로운 버전의 Pod가 함께 생성된다. +2. 새로운 버전의 Pod가 정상적으로 동작하고, 준비 상태가 되면, 이전 버전의 Pod을 하나씩 종료합니다. 이때 제거되는 Pod은 사용자 요청을 처리하는 중인 경우, 일정 시간 동안 대기한 후에 제거된다. (Graceful Shutdown) +3. 이전 버전의 Pod이 모두 종료되면, 새로운 버전의 Pod만 남게 되고, 이제는 새로운 버전의 애플리케이션이 모든 트래픽을 처리하게 된다. + +> Rolling Update는 Deployment 등의 리소스 업데이트 시 Pod를 어떻게 교체할지에 대한 방법을 선택하는 것이며, Rolling Update 설정만으로는 무중단 배포를 설정했다고 말할 수 없다.
Rolling Update를 수행하면서, 요청에 대한 에러 발생을 최소화하기 위한 설정은 Container Probe와 Graceful Shutdown에 대한 설정이 추가적으로 필요하다. + +## Rolling Update 전략 + +Rolling Update는 `maxSurge`/`maxUnavailable` 값을 적절히 설정하는 것이 중요하다. + +동시에 몇 개의 새로운 버전을 만들고, 몇 개의 기존 Pod를 삭제할지 서비스의 replica 수, 안정성을 고려해서 선택해야한다. 또한 이 값에 따라 다음과 같은 Rolling Update 전략이 가능하다. + +**새로운 버전 Pod 생성 후, 기존 버전 Pod를 종료하는 방식** + +```yaml +rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +``` + +- `maxSurge=1(또는 n)`, `maxUnavailable=0`으로 설정 +- maxUnavailable 값이 0이기 때문에 새로운 버전의 Pod가 생성되야만 종료가 발생한다. +- replica + maxSurge 만큼의 Pod가 동시에 유지될 수 있습니다. +- 위 예시에서는 새로운 Pod가 1개가 생성되고 난 후 1개의 기존 Pod가 종료된다. + - replica가 10개라면, 업데이트시 11개의 Pod가 동시에 유지될 수 있다. + + ![image](https://github.com/rlaisqls/TIL/assets/81006587/2fedf0ef-c578-4c65-bcdf-a5cfd9468d07) + +**기존 버전 Pod 종료 후, 새로운 버전 Pod를 생성하는 방식** + +```yaml +rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 +``` + +- `maxSurge=0`, `maxUnavailable=1(또는 n)`으로 설정 +- maxSurge 값이 0이기 때문에 기존 버전의 Pod가 종료되어야만 새로운 버전의 Pod가 생성된다. +- replica - maxUnavailable 만큼의 Pod가 동시에 유지될 수 있다. +- 위 예시에서는 기존 Pod가 1개가 종료되고 난 후 1개의 새로운 버전의 Pod가 생성된다. + - replica가 10개라면, 업데이트시 9개의 Pod가 동시에 유지될 수 있다. + + ![image](https://github.com/rlaisqls/TIL/assets/81006587/6b7a09ca-f695-467b-9b81-54addcabe7fa) + +## Example + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: springapp +spec: + replicas: 3 + revisionHistoryLimit: 1 + selector: + matchLabels: + app: springapp + + # -- Configure Deployment Strategy (Rolling Update) + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + + template: + metadata: + labels: + app: springapp + spec: + containers: + - name: springapp + image: + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: http +``` + +--- +참고 +- https://kubernetes.io/ko/docs/tutorials/kubernetes-basics/update/update-intro/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Service\354\231\200\342\200\205port.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Service\354\231\200\342\200\205port.md" new file mode 100644 index 00000000..b05779a1 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/Service\354\231\200\342\200\205port.md" @@ -0,0 +1,173 @@ +--- +title: 'Service와 port' +lastUpdated: '2024-03-02' +--- + +쿠버네티스 환경에서 **Service**는 Pod들을 통해 실행되고 있는 애플리케이션을 네트워크에 노출(expose)시키는 가상의 컴포넌트다. 쿠버네티스 내부의 다양한 객체들이 애플리케이션과, 그리고 애플리케이션이 다른 외부의 애플리케이션이나 사용자와 연결될 수 있도록 도와주는 역할을 한다. + +쿠버네티스에 Service가 있는 이유는, Pod들이 반영속적인 특성을 가지고 있기 때문이다. 쿠버네티스에서의 Pod는 무언가가 구동 중인 상태를 유지하기 위해 동원되는 **일회성 자원**으로 언제든 다른 노드로 옮겨지거나 삭제될 수 있다. 또한 Pod는 **생성될 때마다 새로운 내부 IP**를 받게 되므로, 이것만으로 클러스터 내/외부와의 통신을 계속 유지하기 어렵다. + +따라서 쿠버네티스는 Pod가 외부와 통신할 수 있도록 클러스터 내부에서 고정적인 IP를 갖는 Service를 이용하도록 하고 있다. 서비스는 Deployment나 StatefulSet처럼 같은 애플리케이션을 구동하도록 구성된 여러 Pod들에게 **단일한 네트워크 진입점**을 부여하는 역할도 한다. + +서비스를 정의하고 생성할 때에는 `spec.port` 아래에 연결하고자 하는 항목별로 각각 2개씩의 포트가 지정되어야 한다. + + +`port` : 서비스 쪽에서 해당 Pod를 향해 열려있는 포트를 의미한다. + +`targetPort` : Pod의 애플리케이션 쪽에서 열려있는 포트를 의미한다. Service로 들어온 트래픽은 해당 Pod의 <클러스터 내부 IP>:로 넘어가게 된다. + +```yml +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + name: MyApp + ports: + - protocol: TCP + port: 80 # Service가 줄 포트 + targetPort: 9376 # Pod가 받을 포트 +``` + +# 서비스의 유형 + +쿠버네티스에서 서비스의 유형은 크게 4가지로 분류된다. 명세(spec) 상에 type가 별도로 지정되지 않았다면 ClusterIP로 고정된다. + +|이름|설명| +|-|-| +|ClusterIP (Default)|Pod들이 클러스터 내부의 다른 리소스들과 통신할 수 있도록 해주는 가상의 클러스터 전용 IP| +|NodePort|외부에서 노드 IP의 특정 포트로 들어오는 요청을 감지하여, 해당 포트와 연결된 Pod로 트래픽을 전달| +|LoadBalancer|로드밸런서를 클러스터의 서비스로 프로비저닝할 수 있는 유형| +|ExternalName|selector 대신 DNS name을 직접 명시| + +## 1. ClusterIP + +ClusterIP는 Pod들이 클러스터 내부의 다른 리소스들과 통신할 수 있도록 해주는 가상의 클러스터 전용 IP다. 이 유형의 서비스는 ``로 들어온 클러스터 내부 트래픽을 해당 Pod의 `:`로 넘겨주도록 동작하므로, **오직 클러스터 내부에서만 접근 가능**하게 된다. 쿠버네티스가 지원하는 기본적인 형태의 서비스다. + + + +#### Selector를 포함하는 형태 + +TCP 포트 9376을 수신 대기(listen)하며 app=myapp, type=frontend라는 레이블을 공유하는 파드들에게 myapp-service라는 이름으로 접근할 수 있게 해주는 ClusterIP 유형의 서비스를 정의하면 다음과 같다. + +```yml +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + type: ClusterIP # 생략 가능 + selector: + app: myapp + ports: + - protocol: TCP + port: 80 # Service가 줄 포트 + targetPort: 9376 # Pod가 받을 포트 +``` + +`spec.selector`에서 지정된 레이블로 여러 파드들이 존재할 경우, 서비스는 그 파드들을 **외부 요청(request)을 전달할 엔드포인트(endpoints)로 선택하여 트래픽을 분배**하게 된다. 이를 이용하여 한 노드 안에 여러 파드, 또는 여러 노드에 걸쳐 있는 여러 파드에 동일한 서비스를 적용할 수 있다. + +때로는 여러 포트들의 연결이 필요할 때도 있는데, 그럴 땐 spec.ports에 리스트 형태로 name 값을 부여하여 각각 추가해주면 된다. + +#### Selector가 제외된 형태 + +필요에 따라 **엔드포인트(Endpoints)를 수동으로 직접 지정해줘야 할 때**가 있다. 테스트 환경과 상용 환경의 설정이 서로 다르거나, 다른 네임스페이스 또는 클러스터에 존재하는 파드와의 네트워크를 위해 서비스-서비스 간의 연결을 만들어야 하는 상황 등이 있다. + +이런 경우에는 `spec.selector` 없이 서비스를 만들고, 해당 서비스가 가리킬 엔드포인트(Endpoints) 객체를 직접 만들어 해당 서비스에 맵핑하는 방법이 있다. + +```yml +apiVersion: v1 +kind: Endpoints +metadata: + app: my-service # 연결할 서비스와 동일한 name을 메타데이터로 입력 +subsets: # 해당 서비스로 가리킬 endpoint를 명시 + - addresses: + - ip: 192.0.2.42 + ports: + - port: 9376 +``` + +이때 주의해야 할 점은, 엔드포인트로 명시할 IP는 **loopback(127.0.0.0/8) 또는 link-local(169.254.0.0/16, 224.0.0.0/24) 이어서는 안 된다**는 것이다. 이에 대한 자세한 내용은 쿠버네티스 공식문서에서 확인할 수 있다. + + +## 2. NodePort + +NodePort는 외부에서 노드 IP의 특정 포트(`:`)로 들어오는 요청을 감지하여, 해당 포트와 연결된 Pod로 트래픽을 전달하는 유형의 서비스다. 이때 클러스터 내부로 들어온 트래픽을 특정 Pod로 연결하기 위한 ClusterIP 역시 자동으로 생성된다. + + + +이 유형의 서비스에서 `spec.ports` 아래에 `nodePort`를 추가로 지정할 수 있다. `nodePort`는 외부에서 노드 안의 특정 서비스로 접근할 수 있도록 지정된 노드의 특정 포트응 의미한다. `nodePort`로 할당 가능한 포트 번호의 범위는 `30000`에서 `32767`사이이며, 미지정시 해당 범위 안에서 임의로 부여된다. + +```yml +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + type: NodePort + selector: # 이 서비스가 적용될 파드 정보를 지정 + app: myapp + ports: + - targetPort: 80 + port: 80 + nodePort: 30008 # 외부 사용자가 애플리케이션에 접근하기 위한 포트번호(선택) +``` + +## 3. LoadBalancer + +별도의 외부 로드밸런서를 제공하는 클라우드(AWS, Azure, GCP 등) 환경을 고려하여, 해당 로드밸런서를 클러스터의 서비스로 프로비저닝할 수 있는 유형이다. + + + +이 유형은 서비스를 **클라우드 제공자 측의 자체 로드밸런서**로 노출시키며, 이에 필요한 NodePort와 ClusterIP 역시 자동 생성된다. 이때 프로비저닝된 로드 밸런서의 정보는 서비스의 `status.loadBalancer` 필드에 게재된다. + +```yml +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + type:: LoadBalancer + clusterIP: 10.0.171.239 # 클러스터 IP + selector: + app: myapp + ports: + - targetPort: 80 + port: 80 + nodePort: 30008 +status: + loadBalancer: # 프로비저닝된 로드 밸런서 정보 + ingress: + - ip: 192.0.2.127 +``` + + +이렇게 구성된 환경에서는, 외부의 로드 밸런서를 통해 들어온 트래픽이 서비스의 설정값을 따라 해당되는 파드들로 연결된다. 이 트래픽이 어떻게 로드 밸런싱이 될지는 클라우드 제공자의 설정에 따르게 된다. + +만약 이러한 방식의 로드 밸런서 프로비저닝을 지원하지 않는 클라우드 환경(예: Virtualbox)일 경우, 이 유형으로 지정된 서비스는 **NodePort와 동일한 방식**으로 동작하게 된다. + +## 4. ExternalName + +서비스에 selector 대신 DNS name을 직접 명시하고자 할 때에 쓰인다. spec.externalName 항목에 필요한 DNS 주소를 기입하면, 클러스터의 DNS 서비스가 해당 주소에 대한 CNAME 레코드를 반환하게 된다. + +```yml +apiVersion: v1 +kind: Service +metadata: + name: my-service + namespace: prod +spec: + type: ExternalName + externalName: my.database.example.com +``` + +### CLI 명령어로 파드에 서비스 적용하기 + +서비스는 YAML 형태로 정의하는 것이 좋지만, 생성된 파드를 간단히 외부에 노출시키고자 할 때에는 CLI 명령어로 보다 간편하게 수행할 수도 있다. 특정 리소스에 한해 즉시 노출시키고자 한다면 kubectl expose 명령을 이용하여 서비스 배포와 노출을 동시에 진행 가능하다. + +(이렇게 해도 내부적으로 ClusterIP 타입의 service가 생성되긴 한다.) + +```js +kubectl expose pod redis --port=6379 --name redis-service +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/StatefulSets.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/StatefulSets.md" new file mode 100644 index 00000000..637d20a0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/StatefulSets.md" @@ -0,0 +1,86 @@ +--- +title: 'StatefulSets' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/4ad965a1-a37c-427b-a0af-36c650186c81) + +StatefulSets are a workload abstraction in Kubernetes to manage pods like you would a deployment. Unlike a deployment, StatefulSets add the following features for applications that require them: + +- Stable, unique network identifiers +- Stable, persistent storage +- Ordered, graceful deployment and scaling +- Ordered, automated rolling updates + +The deployment resource is better suited for applications that do not have these requirements (for example, a service that stores data in an external database). + +Our database for the Golang minimal web server uses a StatefulSet. The database has a service, a ConfigMap for the Postgres username, a password, a test database name, and a StatefulSet for the containers running Postgres. + +Let’s deploy it now: + +```bash +kubectl apply -f database.yaml +service/postgres created +configmap/postgres-config created +statefulset.apps/postgres created +``` + +Let’s examine the DNS and network ramifications of using a StatefulSet. + +To test DNS inside the cluster, we can use the `dnsutils` image; this image is `gcr .io/kubernetes-e2e-test-images/dnsutils:1.3` and is used for Kubernetes testing: + +```bash +kubectl apply -f dnsutils.yaml + +pod/dnsutils created + +kubectl get pods +NAME READY STATUS RESTARTS AGE +dnsutils 1/1 Running 0 9s +``` + +With the replica configured with two pods, we see the StatefulSet deploy `postgres-0` and `postgres-1`, in that order, a feature of StatefulSets with IP address 10.244.1.3 and 10.244.2.3, respectively: + +```bash +kubectl get pods -o wide +NAME READY STATUS RESTARTS AGE IP NODE +dnsutils 1/1 Running 0 15m 10.244.3.2 kind-worker3 +postgres-0 1/1 Running 0 15m 10.244.1.3 kind-worker2 +postgres-1 1/1 Running 0 14m 10.244.2.3 kind-worker +``` + +Here is the name of our headless service, Postgres, that the client can use for queries to return the endpoint IP addresses: + +```bash +kubectl get svc postgres +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +postgres ClusterIP 5432/TCP 23m +``` + +Using our dnsutils image, we can see that the DNS names for the StatefulSets will return those IP addresses along with the cluster IP address of the Postgres service: + +```bash +kubectl exec dnsutils -- host postgres-0.postgres.default.svc.cluster.local. +postgres-0.postgres.default.svc.cluster.local has address 10.244.1.3 + +kubectl exec dnsutils -- host postgres-1.postgres.default.svc.cluster.local. +postgres-1.postgres.default.svc.cluster.local has address 10.244.2.3 + +kubectl exec dnsutils -- host postgres +postgres.default.svc.cluster.local has address 10.105.214.153 +``` + +StatefulSets attempt to mimic a fixed group of persistent machines. As a generic solution for stateful workloads, specific behavior may be frustrating in specific use cases. + +A common problem that users encounter is an update requiring manual intervention to fix when using `.spec .updateStrategy.type: RollingUpdate, and .spec.podManagementPolicy: OrderedReady`, both of which are default settings. With these settings, a user must manually intervene if an updated pod never becomes ready. + +Also, StatefulSets require a service, preferably headless, to be responsible for the network identity of the pods, and end users are responsible for creating this service. + +Statefulsets have many configuration options, and many third-party alternatives exist (both generic stateful workload controllers and software-specific workload controllers). + +StatefulSets offer functionality for a specific use case in Kubernetes. They should not be used for everyday application deployments. Later in this section, we will discuss more appropriate networking abstractions for run-of-the-mill deployments. + +--- +reference +- https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +- https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/ingress.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/ingress.md" new file mode 100644 index 00000000..f2654595 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/object/ingress.md" @@ -0,0 +1,83 @@ +--- +title: 'ingress' +lastUpdated: '2024-03-02' +--- + +인그레스(ingress)는 클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리한다. + +인그레스는 부하 분산, SSL 종료, 명칭 기반의 가상 호스팅을 제공할 수 있다. + +인그레스는 클러스터 외부에서 클러스터 내부 서비스로 HTTP와 HTTPS 경로를 노출한다. 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다. + + + +[인그레스](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#ingress-v1-networking-k8s-io)는 외부에서 서비스로 접속이 가능한 URL, 로드 밸런스 트래픽, SSL / TLS 종료 그리고 이름-기반의 가상 호스팅을 제공하도록 구성할 수 있다. 인그레스 컨트롤러는 일반적으로 로드 밸런서를 사용해서 인그레스를 수행할 책임이 있으며, 트래픽을 처리하는데 도움이 되도록 에지 라우터 또는 추가 프런트 엔드를 구성할 수도 있다. + +인그레스는 임의의 포트 또는 프로토콜을 노출시키지 않는다. HTTP와 HTTPS 이외의 서비스를 인터넷에 노출하려면 [Service.Type=NodePort](https://kubernetes.io/ko/docs/concepts/services-networking/service/#type-nodeport) 또는 [Service.Type=LoadBalancer](https://kubernetes.io/ko/docs/concepts/services-networking/service/#loadbalancer) 유형의 서비스를 사용해야한다. + +## Ingress 리소스 구성하기 + +최소한의 사양으로 인그레스 컨트롤러를 구성하고싶다면, 아래와 같이 설정할 수 있다. + +```yml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: minimal-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx-example + rules: + - http: + paths: + - path: /testpath + pathType: Prefix + backend: + service: + name: test + port: + number: 80 +``` + +인그레스에는 `apiVersion`, `kind`, `metadata`, `spec` 필드가 명시되어야 하고, 인그레스 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다. 인그레스는 [어노테이션](https://github.com/kubernetes/ingress-nginx/blob/main/docs/examples/rewrite/README.md)을 이용해서 인그레스 컨트롤러에 따른 옵션을 구성하기도 한다. + +## Fan-out + +Fan-out이란, HTTP URI에서 요청된 것을 기반으로 단일 IP 주소에서 여러 서버로 트래픽을 라우팅하는것을 말한다. 인그레스를 사용하면 로드 밸런서의 수를 줄이면서, Fan-out을 구성할 수 있다. + + + +Fan-out을 설정하고 싶다면 아래와 같이 하면 된다! + +```yml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: simple-fanout-example +spec: + rules: + - host: foo.bar.com + http: + paths: + - path: /foo + pathType: Prefix + backend: + service: + name: service1 + port: + number: 4200 + - path: /bar + pathType: Prefix + backend: + service: + name: service2 + port: + number: 8080 +``` + +--- + +참고 + +https://kubernetes.io/ko/docs/concepts/services-networking/ingress/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/\352\260\200\354\203\201\342\200\205IP\354\231\200\342\200\205\354\204\234\353\271\204\354\212\244\342\200\205\355\224\204\353\241\235\354\213\234.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/\352\260\200\354\203\201\342\200\205IP\354\231\200\342\200\205\354\204\234\353\271\204\354\212\244\342\200\205\355\224\204\353\241\235\354\213\234.md" new file mode 100644 index 00000000..f7229f44 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/\352\260\200\354\203\201\342\200\205IP\354\231\200\342\200\205\354\204\234\353\271\204\354\212\244\342\200\205\355\224\204\353\241\235\354\213\234.md" @@ -0,0 +1,22 @@ +--- +title: '가상 IP와 서비스 프록시' +lastUpdated: '2024-03-02' +--- + +쿠버네티스 클러스터의 모든 노드는 `kube-proxy`를 실행한다. `kube-proxy`는 ExternalName 이외의 유형의 서비스에 대한 **"가상 IP"**의 역할을 한다. + +Screenshot 2023-02-02 at 18 59 36 + +service를 조회했을때 나오는 cluster IP가 바로 k8s의 프록시로 만들어진 가상 IP이다. 이 IP는 k8s 내부에서만 접근할 수 있다. + +## 쿠버네티스에서 가상 IP를 사용하는 이유 + +쿠버네티스가 프록시를 통해 가상 IP를 만드는 이유는, 실제 IP와 DNS를 사용하기 부적절하기 때문이다. k8s의 서비스 객체는 IP를 할당할 수 있는 기기가 아니고 잠시 생겼다가 사라질 수 있는 유한한 존재이다. + +하지만 서비스를 식별하고 호출할 수 있는 무언가가 필요하기 때문에 그 방법으로서 프록시로 만든 가상 IP를 사용하는 것이다. + +## kube-proxy의 특징 + +- kube-proxy의 구성은 컨피그맵(ConfigMap)을 통해 이루어진다. +- kube-proxy를 위한 컨피그맵은 기동 중 구성의 재적용(live reloading)을 지원하지 않는다. +- kube-proxy를 위한 컨피그맵 파라미터는 기동 시에 검증이나 확인을 하지 않는다. 예를 들어, 운영 체계가 iptables 명령을 허용하지 않을 경우, 표준 커널 kube-proxy 구현체는 작동하지 않을 것이다. 마찬가지로, `netsh`을 지원하지 않는 운영 체계에서는, 윈도우 유저스페이스 모드로는 기동하지 않을 것이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/\354\202\254\354\235\264\353\223\234\354\271\264\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/\354\202\254\354\235\264\353\223\234\354\271\264\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..3145d71a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\352\260\234\353\205\220/\354\202\254\354\235\264\353\223\234\354\271\264\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,30 @@ +--- +title: '사이드카 패턴' +lastUpdated: '2024-03-02' +--- + +사이드카 패턴이란 쿠버네티스와 같이 컨테이너 오케스트레이션 툴에서 구성할 수 있는 컨테이너 배치 패턴으로, 마치 오토바이 옆에 붙어 있는 사이드카와 비슷한 형태이다. + +image + +## 장점 + +#### 1. 기존 로직의 변경 없이 기능 추가 + +image + +사이드카 컨테이너를 통해 기존의 로직은 그대로 놔둔체 새로운 기능을 덧붙일 수 있다. 예를 들어 기존 http 프로토콜에 대해서만 서비스를 하는 웹서버에 tls layer를 추가하고 싶은 경우, 메인 컨테이너인 기존의 legacy 웹서버는 그대로 놔둔체 사이드카 컨테이너를 통해 https 서비스를 클라이언트에게 제공할 수 있다. + +#### 2. 컨테이너 재사용성 + +image + +사이드카 컨테이너를 단일한 기능을 하게 모듈화하면 다른 곳에서 재사용하기 수월해진다. 대부분의 app에서는 로깅, 실행 프로세스 정보 확인 등의 작업들이 필요하다. 이때, 미리 인터페이스만 잘 맞춰 놓으면 매번 컴포넌트를 개발할 필요 없이, 하나의 사이드카 컨테이너로 해결할 수 있다. + +예를 들어, 로그 수집 사이드카 컨테이너를 생각해 볼 수 있다. 메인 컨테이너에서 미리 지정된 디렉토리에 어플리케이션 로그를 쌓으면 동일한 사이드카 컨테이너로 해당 로그를 로그 저장소에 저장하여 따로 로그를 분석하거나 더 오랜 기간 로그를 확인을 할 수 있게 된다. + +#### 3. 간단한 PaaS 구현 + +사이드카 컨테이너를 비즈니스 로직을 제공 컨테이너로 활용하고, 메인 컨테이너에서는 단지 실행환경을 제공하는 역할만 담당하게 하는 PaaS 서비스를 구성할 수 있다. PaaS는 Platform을 제공해주고 그 안에 들어가는 application 로직은 사용자가 정의하는 서비스이다. + +사이드카 패턴에서 PaaS를 구현한다면 메인 컨테이너가 비즈니스 로직의 실행 환경을 제공해 주는 plaltform으로써 존재하고 사이드카 컨테이너가 사용자가 입맛에 따라 로직을 정의하여 플랫폼에 올리는 역할을 하게 된다. 이를 통해 비즈니스 상황에 따라 바뀌게 되는 비즈니스 로직을 손쉽게 업데이트할 수 있고 비교적 자주 바뀌지 않는 런타임 환경은 안정적으로 서비스할 수 있게 만들어 준다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/10\342\200\205most\342\200\205common\342\200\205mistakes\342\200\205using\342\200\205kubernetes.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/10\342\200\205most\342\200\205common\342\200\205mistakes\342\200\205using\342\200\205kubernetes.md" new file mode 100644 index 00000000..aa105007 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/10\342\200\205most\342\200\205common\342\200\205mistakes\342\200\205using\342\200\205kubernetes.md" @@ -0,0 +1,146 @@ +--- +title: '10 most common mistakes using kubernetes' +lastUpdated: '2024-03-02' +--- + +We had the chance to see quite a bit of clusters in our years of experience with Kubernetes (both managed and unmanaged - on GCP, AWS and Azure), and we see some mistakes being repeated. No shame in that, we've done most of these too! Let's try to show the ones we see very often and talk a bit about how to fix them. + +## 1. resources - requests and limits + +- CPU request are usually either **not set** or **sey very low** (so that we can fit a lot of pods on each node) and nodes are thus over commited. In time of high demand the CPUs of the node are fully utilized and our workload is getting only "what it had requested" and gets **CPU throttled**, causing increased application latency, timeouts, etc. + +- On the other hand, having a CPU limit can unnecessarily throttle pods even if the node's CPU is not fully utilized which again can cause increased latency. There is a an open discussion around CPU CFS quota in linux kernel and cpu throttling based on set spu limits and turning off the CFS queta. CPU limits can cause more problems than they solve. See more in the link below. + +- Memory overcommiting can get you in more trouble. Reaching a CPU limit result in throttling, reaching memory limit will get your pod killed(OOMKill). If you want to minimize how often it can happen, don't overcommit your memory and use Guaranteed Qos settign memory request equal to limit like in the example below. ([reference](https://www.slideshare.net/try_except_/optimizing-kubernetes-resource-requestslimits-for-costefficiency-and-latency-highload)) + +- Burstable (more likely to get OOMkilled more often): + ```yaml + resources: + requests: + memory: "128Mi" + cpu: "500m" + limits: + memory: "256Mi" + cpu: 2 + ``` + +- Guaranteed: + ```yaml + resources: + requests: + memory: "128Mi" + cpu: 2 + limits: + memory: "128Mi" + cpu: 2 + ``` + +- You can see the current cpu and memory usage of pods (and containers in them) using metrics-server. It can help you when setting resources. Simply run these: + ```bash + kubectl top pods + kubectl top pods --containers + kubectl top nodes + ``` + +- However these show just the current usage. That is great to get the rough idea about the numbers but you end up wanting to see these **usage metrics in time** (to answer questions like: what was the cpu usage in peak, yesterday morning, etc.). For that, you can use Prometheus, DataDog and many others. They just ingest the metrics from metrics-server and store them, then you can query & graph them. + +- [VerticalPodAutoscaler](https://cloud.google.com/kubernetes-engine/docs/concepts/verticalpodautoscaler) can help you **automate way** this manual process - looking at cpu/mem usage in time and setting new requests and limits based on that all over again. + +## 2. liveness and readiness probes + +- Liveness probe restarts your pod if the probe fails +- Readiness probe disconnects on fail the failing pod from the kubernetes service (you can check this in kubectl get endpoints) and no more traffic is being sent to it until the probe succeeds again. + +- By default there are no liveness and readiness probes specified. It stay that way, how else would your servie get restarted when there is an unrecoverable error? How does a loadbalancer know a specific pod can start handling traffic? Or handle more traffic? liveness and readiness probes are both run during the whole pod lifecycle, and that is very important for Important for recovering pods. + +- Readiness probes run not only at the start to tell when the pod is Ready and can start servicing traffic but also at during a pod's life the pod becomes too hot handling too much traffic (or an expensive computation) so that we don't send her more work to do and lat her cool down, then the readiness probe succeed and we start dending in more traffic again. + - In this case (when failing readiness probe) failing also liveness probe would be very counterproductive. Why would you restart a pod that is healthy and doing a lot of work? + - Sometimes not having either probe defined is better than having them defined wrong. As mentioned above, if liveness probe is equal to readiness probe, you are in a big trouble. You might want to start with defining only the readiness probe alone as liveness probes are dangerous. + +- Do not fail either of the probes if any of your shared dependencies is down, it would cause cascading failure of all the pods. You are shooting yourself in the foot. + +## 3. non-kubernetes-aware cluster autoscaling + +- When scheduling pods, you decide based on a log of **scheduling constraints** like pod & node affinities, taints and toleratoins, resource requests, QoS, etc. Having an external autoscaler that does not understand these constraints might be troublesome. + +- Imagine there is a new pod to be scheduled but all of the CPU available is requested and pod is **stuck in Pending state**. External autoscaler sees the average CPU surrently used (not requested) and won't scale out (will not add another node). The pod won't be scheduled. + +- Scaling-in (removing a node from the cluster) is always harder. Imagine you have a stateful pod (with persistent volume attached) and as **persistent volumes** are usually resources that **velong to a specific availability zone** and are not replicated in the region, your custom suto scaler removes a node with this pod on it and scheduler cannot schedule it onto a different node as it is very limited by the only availability zone with your persistent disk in it. Pod is again stuck in Pending state. + +- The community is widely using **[cluster-autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler)** which runs in your cluster and is integrated with most major public cloud vendors APIs, understands all these constraints and would scale-out in the mentioned cases. It will also figure out if it can gracefully scale-in without affecting any constraints we have set ans saves you money on compute. + +## 4. Not using the power of IAM/RBAC + +- Don't use IAM Users with permanent secrets for machines and applications rather than generating temporary ones using roles and service accounts. + +- We see it often - hardcoding access and secret keys in application configuration, never rotating the secrets when you have Cloud IAM at hand. Use IAM Roles and service accounts instead of users where suitable. like below: + ```yaml + apiVersion: v1 + kind: ServiceAccount + metadata: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role + name: my-serviceaccount + namespace: default + ``` + +- Also don’t give the service accounts or instance profiles admin and cluster-admin permissions when they don’t need it. That is a bit harder, especially in k8s RBAC, but still worth the effort. + +## 5. self anti affinities for pods + +- If you use many replicas in one deployment, you should define them explicitly. + +- like this: + ```yaml + spec: + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + ket: value + ``` + +- or like this: + ```yaml + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: "key" + operator: In + values: + - value + topologyKey: "kubernetes.io/hostname" + ``` + +- That's it. This will make sure the pods will be scheduled to different dones (this is being cheked only at scheduling time, not at execution time, hense the `requiredDuringSchedulingIgnoredDuringExecution`) + +- We are talking about podAntiAffiniry on different node names, not different availability zones. If you really need proper HA, dig a bit deeper in this topic. + +## 6. more tenants or envs in shared cluster + +- Kubernetes namespaces don't provide any strong isolation. + +- People seen to expect if they separated non-prod workload to one namespace and prod to prod namespace, one **workload won't ever affect the other**. It is possible to achieve some level of fairness - resource requests and limits, quotas, priorityClasses - and isolation - affinities, tolerations, taints (or nodeselectors) - to "physically" separate the workload in data plane but that separation is quite **complex**. + +- If you need to have both types of workloads in the same cluster, you'll have to bear the complexity. If you don't need it and having **another cluster** is relatively cheap for you (like in public cloud), put it in different cluster to achieve much stronger level of isolation. + +## 7. externalTrafficPolicy: Cluster + +- Seeing this very often, all traffic is routed inside the cluster to a NoderPort service which has, by default, `externalTrafficPolicy: cluster`. That means the NodePort is opened on every node in the cluster so that you can use any of them to communicate with the desired sevice (set of pods). + + + +- More often than not the actual pods that are targeted with the NodePort service **run only on a subset of those nodes.** That means if I talk to a node which does not have the pod running it will forward the traffic to a different node, causing **additional network hop** and increased latency (if the nodes are in different AZs/datacenters, the latency can be quite high and there is additional egress cost to it). + +- Setting `externalTrafficPolicy: Local` on the kubernetes service won't open that NodePort on every Node, but only on the nodes where the pods are actually running. If you use an external loadbalancer which is healthchecking its endpoints (like AWS ELB does) it will start to **send the traffic only to those nodes** where it is supposed to go, improving your latency, compute overhead, egress bill and anity. + +- Chances are, you have something like traefic or nginx-ingress-controller being exposed as NodePort (or LoadBalancer, which uses NodePort too) to handler your ingress http traffic routing and this setting can greatly reduce the latency on such requests. + +--- +reference +- https://blog.pipetail.io/posts/2020-05-04-most-common-mistakes-k8s/ +- https://medium.com/@SkyscannerEng/how-a-couple-of-characters-brought-down-our-site-356ccaf1fbc3 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Authenticating.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Authenticating.md" new file mode 100644 index 00000000..a1ec0816 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Authenticating.md" @@ -0,0 +1,92 @@ +--- +title: 'Authenticating' +lastUpdated: '2024-03-02' +--- + +k8s에서 kubectl과 같은 커맨드를 사용해 API를 요청하는 과정은 크게 3가지로 구성되어 있다. + +1. 'k8s 사용자가 맞느냐'를 판단하는 Authentication +2. 두 번째는 'API를 호출할 수 있는 권한이 있느냐'를 판단하는 Authorization +3. 마지막으로 그 요청이 적절한지를 판단하는 Admission Controller이다. + +Authorization와 Admission Controller는 k8s에 내장되어 있는 기능이기 때문에 사용 방법이 비교적 정해져 있는 반면, 첫 번째 단계인 Authentication은 이렇다 할 정답이 딱히 정해져 있지 않다. 물론 k8s의 자체 인증 기능인 ServiceAccount, 인증서 등을 사용할 수는 있지만, 사내에서 LADP, Google, Github 와 같은 별도의 인증 시스템을 이미 구축해 놓았다면 [기존 인증 시스템 / k8s 인증 시스템] 을 분리해 운영해야 하는 상황도 발생할 수 있다. 그렇게 되면 인증을 위한 관리 포인트 또한 두 곳이 되어 버리기 때문에, 클러스터 매니저의 입장에서 이러한 구조는 그다지 달갑지는 않을 것이다. + +k8s는 이러한 불편함을 해결하기 위해 OAuth (Open ID Connect) 로 Third-party에 인증을 위임하는 기능을 제공한다. 즉, 사내에서 별도의 인증 시스템 (LDAP, Google, Github) 을 이미 운영하고 있다면 해당 인증 시스템을 그대로 k8s로 가져올 수 있다. 따라서 Github 계정을 통해 k8s의 API (ex. kubectl) 를 사용할 수 있을 뿐만 아니라, Role 이나 ClusterRole 을 Github 계정에게 부여하는 것 또한 가능해진다. + +그렇지만 k8s가 서드 파티에 연결하는 모든 인터페이스를 제공하는 것은 아니며, 특정 Third-party (ex. Github) 에 연결하기 위한 모듈 또는 서버를 별도로 구축해야 한다. 이번 포스트에서는 User와 Group을 사용하는 방법을 먼저 설명한 뒤, Third-party에 인증을 위임하기 위한 도구로서 Guard, Dex 의 사용 방법을 다룬다. + +## User와 Group의 개념 + +k8s에서 가장 쉽게 사용할 수 있는 인증 기능은 바로 ServiceAccount이다. 그렇지만 ServiceAccount 라는 이름에서도 알 수 있듯이, ServiceAccount는 사람이 아닌 서비스 (Service), 즉 포드가 API를 호출하기 위한 권한을 부여하기 위해서 사용하는 것이 일반적이다. 물론 ServiceAccount를 사람에게 할당하는 것이 이론상으로 불가능한 것은 아니지만, Service Account의 설계 디자인을 조금만 생각해 보면 애초에 k8s 애플리케이션에 권한을 부여하기 위한 용도라는 것을 알 수 있다. + +그렇다면 실제 '사람' 이라는 Entity에 대응하는 추상화된 k8s object는 무엇일지 궁금할텐데, 결론부터 말하자면 k8s에서는 사람에 대응하는 object가 없다. k8s에는 오픈스택의 keystone과 같은 컴포넌트가 따로 존재하는 것도 아니기 때문에, 실제 '사람' 을 k8s에서 리소스로 관리하지 않는다. 그 대신, k8s에서는 User와 Group 이라는 이름으로 사용자를 사용할 수 있다. + +때문에 k8s 문서에서도 대부분 ServiceAccount 대신 User나 Group으로 설명하고 있다. 아래는 RBAC를 설명하는 k8s 공식 문서인데, RoleBinding의 대상(subject)이 kind: User로 되어 있는 것을 볼 수 있다. + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +# This role binding allows "jane" to read pods in the "default" namespace. +# You need to already have a Role named "pod-reader" in that namespace. +kind: RoleBinding +metadata: + name: read-pods + namespace: default +subjects: +# You can specify more than one "subject" +- kind: User + name: jane # "name" is case sensitive + apiGroup: rbac.authorization.k8s.io +roleRef: + # "roleRef" specifies the binding to a Role / ClusterRole + kind: Role #this must be Role or ClusterRole + name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to + apiGroup: rbac.authorization.k8s.io +``` + +그렇다면 저 'User'는 무엇에 대응하는 개념일까? 공식문서를 살펴보자. + +image + + +위 내용을 요약하자면 'User는 k8s object가 아니기 때문에 따로 관리되지는 않으며, 외부 인증 방법에 따라서 달라진다'는 뜻이다. 예를 들어 Github을 k8s 인증 시스템으로 사용한다면 Github 사용자 이름이 User가 되고, Organization 이름이 Group이 된다고 이해하면 쉽다. 따라서 test 라는 Github Organization에 rlaisqls 사용자가 소속되어 있다면 아래와 같이 ClusterRoleBinding을 생성할 수도 있다. + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: service-reader-clusterrolebinding + namespace: default +subjects: +- kind: Group + name: test +- kind: User + name: rlaisqls +rolRef: + kind: ClusterRole + name: service-reader-clusterrole + apiGroup: rbac.authorization.k8s.io +``` + +물론, Github가 아닌 다른 서드 파티를 통해 인증을 진행한다면 User와 Group의 단위가 달라질 수도 있으며, 원한다면 API 서버의 옵션에서 User와 Group의 단위를 직접 설정할 수도 있다. + +> Tip : k8s에서는 미리 정의된 몇 가지 종류의 User와 Group이 존재한다. 가장 대표적인 예시는 `system:` 접두어가 붙는 User나 Group 인데, 이는 다양한 용도로 사용될 수 있다. 예를 들어 rlaisqls 이라는 이름의 ServiceAccount는 `system:serviceaccount::rlaisqls` 이라는 User와 동일하게 사용할 수 있다. 즉, RoleBinding의 Subject에서 아래와 같이 사용해도 ServiceAccount에 권한이 부여된다. +> ```yaml +> apiVersion: rbac.authorization.k8s.io/v1 +> kind: ClusterRoleBinding +> metadata: +> name: service-reader-clusterrolebinding +> namespace: default +> subjects: +> - kind: User +> name: system:serviceaccount:default:rlaisqls +> ``` +>
그 외에도 `system:serviceaccount`는 모든 ServiceAccount를, `system:serviceaccount:default`는 default 네임스페이스의 모든 ServiceAccount를 의미하는 Group으로 사용될 수도 있다. 사용 가능한 pre-defined User와 Group에 대해서는 k8s 공식 문서를 참고하자. + +## 사용자 인증 방법 예시 +- [k8s 클러스터 root CA를 통한 사용자 인증](k8s 클러스터 root CA를 통한 사용자 인증.md) +- [Token Webhook with Guard](Token Webhook with Guard.md) +- [OIDC Authentication with Dex](OIDC Authentication with Dex.md) + +--- +참고 +- https://kubernetes.io/docs/reference/access-authn-authz/authentication/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/OIDC\342\200\205Authentication\342\200\205with\342\200\205Dex.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/OIDC\342\200\205Authentication\342\200\205with\342\200\205Dex.md" new file mode 100644 index 00000000..64b57d43 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/OIDC\342\200\205Authentication\342\200\205with\342\200\205Dex.md" @@ -0,0 +1,134 @@ +--- +title: 'OIDC Authentication with Dex' +lastUpdated: '2024-03-02' +--- + +OIDC (Open ID Connect) 인증 방법은 OAuth2 위에서 동작하는 인증으로, Github이나 Google과 같이 서드 파티에서 부여받은 OAuth 권한을 통해 쿠버네티스의 인증을 수행한다. 기존에 사용하던 OAuth 인증 방법을 쿠버네티스에 그대로 가져온다고 생각하면 이해하기 쉽다. OIDC 인증 방법은 OAuth + JWT 토큰을 사용한다는 점에서 Webhook Token 인증 방법과 차이점을 갖는다. + +OAuth 토큰을 관리하기 위한 중간 서버를 구현한 [Dex](https://github.com/dexidp/dex/)를 사용하여 OIDC 인증을 구현해보자. + +## Dex Concept + +Dex는 서드 파티로부터 OAuth 인증 토큰을 가져와 관리하는 인증 도구이다. OAuth를 사용하기 위해 반드시 Dex를 써야 하는 것은 아니지만, Dex는 OAuth 서드 파티와의 중간 매개체 역할을 해주기 때문에 OAuth의 인증 토큰의 발급, 저장 및 관리를 좀 더 수월하게 해결할 수 있다. Dex는 LDAP, Github, SAML 세 가지 종류의 서드 파티를 Stable로 지원하고 있다. + +우리가 직접 서비스를 개발하려 할 때 OAuth 로그인을 구현하고 싶다면 Dex를 사용해 OAuth 관리를 위임할 수도 있다. 즉, k8s가 아니더라도 Dex를 통해 OAuth 토큰을 관리하고 사용할 수 있으며, 그 기능을 k8s에서 OIDC로서 이용하는 것 뿐이다. 이는 Dex Github의 Getting Started 문서의 예제를 직접 따라해 보면 쉽게 이해할 수 있다. + +### Dex Workflow + +image + + +1. 애플리케이션 (여기서는 k8s) 사용자가 Dex 클라이언트 App에 접속한다. +2. Dex 클라이언트 App이 Dex의 웹 페이지로 Redirect 한다. +3. Dex 웹 페이지에서는 인증 수단을 선택할 수 있다. 예를 들어, Github을 선택하면 사용자는 Github로 Redirect 된다. +4. Github 웹 페이지에서 Dex에게 권한을 허가하면, Dex 클라이언트 App은 사용자에게 Access Token을 반환한다. +5. 사용자는 Access Token (JWT) 를 Bearer 헤더에 담아서 k8s API 서버로 전송한다. +6. k8s API 서버는 해당 토큰이 유효한지 검증하기 위해 Dex 서버와 통신한다. +7. 인증이 정상적으로 수행되면 API 서버는 API 요청을 수행한다. + +> Dex 클라이언트 App : Dex의 기능을 사용하기 위한 별도의 서버를 뜻하며, 사용자가 최초로 접속하는 Endpoint가 된다. 사용자가 Dex의 API를 직접 호출하는 대신, Dex를 사용할 수 있는 별도의 클라이언트 서버에 접근해 사용하는 것이다. Dex 클라이언트 App은 Dex의 웹 페이지로 Redirect 할 수 있어야 하기 때문에 가능하면 MVC와 같은 웹 페이지로 구현하는 것이 권장된다. + +## Dex 설치 + +### domain 준비 + +image + +Dex 서버는 쿠버네티스 클러스터 내부에서 구동되는데, 이 Dex 서버는 쿠버네티스 API 서버 뿐만 아니라 사용자의 웹 브라우저에서도 접근이 가능해야 한다. 환경에 맞게 doamin을 준비하여 연결할 수 있도록 하자. + +### Dex 소스코드 내려받기 & 인증서 생성하기 + +먼저 Dex를 내려 받은 뒤 컴파일한다. Go 1.7 버전 이상이 설치되어 있어야 하며, GOPATH가 설정되어 있어야 한다. + +```bash +$ go get github.com/dexidp/dex +$ cd $GOPATH/src/github.com/dexidp/dex +$ make +``` + +스크립트를 실행하면 `example/k8s/ssl` 디렉터리에 인증서가 생성된다. k8s secret으로 미리 등록해주자. + +```bash +$ kubectl create secret tls dex.example.com.tls -n oidc --cert=ssl/cert.pem --key=ssl/key.pem +``` + +### API 서버 실행 옵션 추가하기 + +API 서버를 dex와 이어주기 위해 아래 설정이 필요하다. + +```bash +# OIDC를 담당하는 서버의 URL, 자신의 domain으로 변경 +--oidc-ssuer-url=https://dex.example.com:32000 + +# Dex 클라이언트 App의 이름 +--oidc-client-id=example-app + +# JWT 토큰의 내용 중 User로 사용될 Claim 항목 입력 +# email이 아닌 다른 클레임(ex. `sub`)이 사용되는 경우 --oidc-issuer-url의 값으로 접두어가 붙는다. +--oidc-username-claim=email + +# JWT 토큰의 내용 중 Group으로 사용될 Claim 항목 +--oidc-groups-claim=groups + +# --oidc-ssuer-url에 대해 인증할 수 있는 존재하는 pem key로 변경 +--oidc-ca-file=/etc/ssl/certs/openid-ca.pem +``` + +### Secret 생성 및 Dex 서버 생성하기 + +Github에서 OAuth App 인증 키를 받아오자. Github의 Organization에서 상단의 [Settings]를 선택한 다음, 좌측 하단의 [Developser Settings]에서 [OAuth Apps]를 선택한다. + +image + +Application name에는 OAuth App 이름을 적절히 입력한다. Homepage URL은 OAuth와 그다지 상관 없는 단순 메타데이터인 것 같지만, Dex의 주소를 입력해 주었다. Authorization callback URL에는 위 그림과 같이 [`Dex 주소 + /callback`] 을 입력한 뒤, Register application 버튼을 클릭한다. Dex 주소는 반드시 HTTPS를 사용한다는 점에 유의한다. + +Client ID와 Client Secret이 출력되는데, 두 값을 잘 복사해 놓는다. 이 값으로 쿠버네티스에서 Secret을 생성해야 한다. + +image + +Client ID/Secret 값을 저장하는 쿠버네티스 Secret 리소스를 생성한다. 위에서 복사한 값을 client-id와 client-secret에 붙여넣는다. + +```bash +$ kubectl create secret \ + generic github-client \ + --from-literal=client-id=6d7109... \ + --from-literal=client-secret=ef5fc0a95028e37... +``` + +Dex 클라이언트 App이 Dex 서버의 공개 인증서를 사용할 수 있도록 시크릿을 생성한다. + +```bash +$ pwd +/root/go/src/github.com/dexidp/dex + +$ kubectl create secret generic -n oidc dex-k8s-app --from-file examples/k8s/ssl/ca.pem +secret/dex-k8s-app created +``` + +마지막으로, Dex 서버를 쿠버네티스에 생성하면 된다. [helm chart](https://github.com/dexidp/helm-charts/tree/master/charts/dex)에서 values를 적절하게 수정해서 install 해주자. (앞에서 생성했던 secret들을 명시해주어야 한다.) + +```bash +helm repo add dex https://charts.dexidp.io +helm install dex dex/dex -n oidc --create-namespace --version v0.15.2 +``` + +DNS 전파가 될 때까지 약 5분이 소요된다. 설정한 domain으로 접속해보면 Dex 클라이언트 App으로 접근할 수 있다. + +OAuth를 위한 몇 가지 정보를 입력할 수 있는데, 꼭 입력하지 않아도 된다. Login 버튼을 클릭하면 Dex로 Redirect 된다. + +image + +Github을 선택하면 Github 페이지로 Redirect 된다. Dex에게 권한을 부여해주고 Authorize 버튼을 클릭하면 Access Token이 반환됨과 동시에 redirectURIs로 설정했던 도메인으로 Redirect 된다. + +image + +여러 토큰들이 웹 페이지에 출력된다. + +OAuth 토큰에 관련된 데이터들은 Dex의 CRD에 저장되어 관리되기 때문에, 우리가 눈여겨 볼만한 부분은 ID Token과 Claim 부분이다. 이제 ID Token을 통해 쿠버네티스에 인증할 수 있으며, Claim의 email 항목이 User로 간주된다. 따라서 앞으로 Bearer 토큰 부분에 ID Token을 넣어서 API를 호출하면 되고, email이라는 User에 Role Binding 등을 부여해주면 된다. + +image + +--- +참고 +- https://dexidp.io/docs/kubernetes/ +- https://loft.sh/blog/dex-for-kubernetes-how-does-it-work/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Security\342\200\205Context\342\200\205for\342\200\205a\342\200\205Pod\342\200\205or\342\200\205Container.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Security\342\200\205Context\342\200\205for\342\200\205a\342\200\205Pod\342\200\205or\342\200\205Container.md" new file mode 100644 index 00000000..a99fc444 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Security\342\200\205Context\342\200\205for\342\200\205a\342\200\205Pod\342\200\205or\342\200\205Container.md" @@ -0,0 +1,271 @@ +--- +title: 'Security Context for a Pod or Container' +lastUpdated: '2024-03-02' +--- + +A security context defines privilege and access control settings for a Pod or Container. Security context settings include, but are not limited to: + +- Descretionary Access Control: Permission to access an object, like a file, is based on [user ID (UID) and group ID (GID)](https://wiki.archlinux.org/index.php/users_and_groups). +- [Security Enhanced Linux (SELinux)](https://en.wikipedia.org/wiki/Security-Enhanced_Linux): Objects are assigned security labels. +- Running as privileged or unprivileged. +- [Linux Capabilities](https://linux-audit.com/linux-capabilities-hardening-linux-binaries-by-removing-setuid/): Give a process some privileges, but not all the privileges of the root user. +- [AppArmor](https://kubernetes.io/docs/tutorials/security/apparmor/): Use program profiles to restrict the capabilities of individual programs. +- [Seccomp](https://kubernetes.io/docs/tutorials/security/seccomp/): Filter a process's system calls. +- `allowPrivilegeEscalation`: Controls whether a process can gain more privileges than its parent process. This bool directly controls whether the `[no_new_privs](https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt)` flag gets set on the container process. `allowPrivilegeEscalation` is always true when the container: + - is run as privileged, or + - has CAP_SYS_ADMIN +- `readOnlyRootFilesystem`: Mounts the container's root filesystem as read-only. + +--- + +# Set the security context for a Pod + +To specify security settings for a Pod, include the `securityContext` field in the Pod specification. The `securityContext` field is a [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#podsecuritycontext-v1-core) object. The security settings that you specify for a Pod apply to all Containers in the Pod. Here is a configuration file for a Pod that has a `securityContext` and an `emptyDir` volume: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: security-context-demo +spec: + securityContext: + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 2000 + volumes: + - name: sec-ctx-vol + emptyDir: {} + containers: + - name: sec-ctx-demo + image: busybox:1.28 + command: [ "sh", "-c", "sleep 1h" ] + volumeMounts: + - name: sec-ctx-vol + mountPath: /data/demo + securityContext: + allowPrivilegeEscalation: false +``` + +In the configuration file, the `runAsUser` field specifies that for any Containers in the Pod, all processes run with user ID 1000. The `runAsGroup` field specifies the primary group ID of 3000 for all processes within any containers of the Pod. + +If this field is omitted, the primary group ID of the containers will be root(0). Any files created will also be owned by user 1000 and group 3000 when `runAsGroup` is specified. Since fsGroup field is specified, all processes of the container are also part of the supplementary group ID 2000. The owner for volume `/data/demo` and any files created in that volume will be Group ID 2000. + +Create the Pod: + +```bash +$ kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml +``` + +Verify that the Pod's Container is running: + +```bash +$ kubectl get pod security-context-demo +``` + +In your shell, list the running processes. The output shows that the processes are running as user 1000, which is the value of runAsUser: + +```bash +$ ps +PID USER TIME COMMAND + 1 1000 0:00 sleep 1h + 6 1000 0:00 sh +... +``` + +In your shell, navigate to `/data`, and list the one directory. The output shows that the `/data/demo` directory has group ID 2000, which is the value of fsGroup. + +```bash +$ cd /data +$ ls -l +drwxrwsrwx 2 root 2000 4096 Jun 6 20:08 demo +``` + +In your shell, navigate to `/data/demo`, and create a file: + +```bash +$ cd demo +$echo hello > testfile +``` + +List the file in the `/data/demo` directory. The output shows that testfile has group ID 2000, which is the value of fsGroup. + +```bash +$ ls -l +-rw-r--r-- 1 1000 2000 6 Jun 6 20:08 testfile +``` +Run the following command, The output is similar to this: + +```bash +$ id +uid=1000 gid=3000 groups=2000 +``` + +From the output, you can see that `gid` is 3000 which is same as the `runAsGroup` field. If the `runAsGroup` was omitted, the `gid` would remain as 0(root) and the process will be able to interact with files that are owned by the root(0) group and groups that have the required group permissions for the root(0) group. + +Exit your shell: + +```bash +exit +``` + +### Configure volume permission and ownership change policy for Pods + +By default, Kubernetes recursively changes ownership and permissions for the contents of each volume to match the `fsGroup` specified in a Pod's `securityContext` when that volume is mounted. For large volumes, checking and changing ownership and permissions can take a lot of time, slowing Pod startup. You can use the `fsGroupChangePolicy` field inside a `securityContext` to control the way that Kubernetes checks and manages ownership and permissions for a volume. + +**fsGroupChangePolicy** - fsGroupChangePolicy defines behavior for changing ownership and permission of the volume before being exposed inside a Pod. This field only applies to volume types that support fsGroup controlled ownership and permissions. This field has two possible values: + +- OnRootMismatch: Only change permissions and ownership if the permission and the ownership of root directory does not match with expected permissions of the volume. This could help shorten the time it takes to change ownership and permission of a volume. +- Always: Always change permission and ownership of the volume when volume is mounted. +For example: + +```yaml +securityContext: + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 2000 + fsGroupChangePolicy: "OnRootMismatch" +``` + +> Note: This field has no effect on ephemeral volume types such as secret, configMap, and emptydir. + +### Delegating volume permission and ownership change to CSI driver + +If you deploy a [Container Storage Interface (CSI)](https://github.com/container-storage-interface/spec/blob/master/spec.md) driver which supports the `VOLUME_MOUNT_GROUP` `NodeServiceCapability`, the process of setting file ownership and permissions based on the fsGroup specified in the securityContext will be performed by the CSI driver instead of Kubernetes. + +In this case, since Kubernetes doesn't perform any ownership and permission change, `fsGroupChangePolicy` does not take effect, and as specified by CSI, the driver is expected to mount the volume with the provided fsGroup, resulting in a volume that is readable/writable by the fsGroup. + +--- + +## Set the security context for a Container + +To specify security settings for a Container, include the `securityContext` field in the Container manifest. The `securityContext` field is a [SecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#securitycontext-v1-core) object. Security settings that you specify for a Container apply only to the individual Container, and they override settings made at the Pod level when there is overlap. Container settings do not affect the Pod's Volumes. + +Here is the configuration file for a Pod that has one Container. Both the Pod and the Container have a `securityContext` field: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: security-context-demo-2 +spec: + securityContext: + runAsUser: 1000 + containers: + - name: sec-ctx-demo-2 + image: gcr.io/google-samples/node-hello:1.0 + securityContext: + runAsUser: 2000 + allowPrivilegeEscalation: false +``` + +Create the Pod: + +kubectl apply -f https://k8s.io/examples/pods/security/security-context-2.yaml +Verify that the Pod's Container is running: + +kubectl get pod security-context-demo-2 +Get a shell into the running Container: + +kubectl exec -it security-context-demo-2 -- sh +In your shell, list the running processes: + +ps aux +The output shows that the processes are running as user 2000. This is the value of runAsUser specified for the Container. It overrides the value 1000 that is specified for the Pod. + +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +2000 1 0.0 0.0 4336 764 ? Ss 20:36 0:00 /bin/sh -c node server.js +2000 8 0.1 0.5 772124 22604 ? Sl 20:36 0:00 node server.js +... +Exit your shell: + +exit +Set capabilities for a Container +With Linux capabilities, you can grant certain privileges to a process without granting all the privileges of the root user. To add or remove Linux capabilities for a Container, include the capabilities field in the securityContext section of the Container manifest. + +First, see what happens when you don't include a capabilities field. Here is configuration file that does not add or remove any Container capabilities: + +pods/security/security-context-3.yaml Copy pods/security/security-context-3.yaml to clipboard +apiVersion: v1 +kind: Pod +metadata: + name: security-context-demo-3 +spec: + containers: + - name: sec-ctx-3 + image: gcr.io/google-samples/node-hello:1.0 +Create the Pod: + +kubectl apply -f https://k8s.io/examples/pods/security/security-context-3.yaml +Verify that the Pod's Container is running: + +kubectl get pod security-context-demo-3 +Get a shell into the running Container: + +kubectl exec -it security-context-demo-3 -- sh +In your shell, list the running processes: + +ps aux +The output shows the process IDs (PIDs) for the Container: + +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +root 1 0.0 0.0 4336 796 ? Ss 18:17 0:00 /bin/sh -c node server.js +root 5 0.1 0.5 772124 22700 ? Sl 18:17 0:00 node server.js +In your shell, view the status for process 1: + +cd /proc/1 +cat status +The output shows the capabilities bitmap for the process: + +... +CapPrm: 00000000a80425fb +CapEff: 00000000a80425fb +... +Make a note of the capabilities bitmap, and then exit your shell: + +exit +Next, run a Container that is the same as the preceding container, except that it has additional capabilities set. + +Here is the configuration file for a Pod that runs one Container. The configuration adds the CAP_NET_ADMIN and CAP_SYS_TIME capabilities: + +pods/security/security-context-4.yaml Copy pods/security/security-context-4.yaml to clipboard +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"] +Create the Pod: + +kubectl apply -f https://k8s.io/examples/pods/security/security-context-4.yaml +Get a shell into the running Container: + +kubectl exec -it security-context-demo-4 -- sh +In your shell, view the capabilities for process 1: + +cd /proc/1 +cat status +The output shows capabilities bitmap for the process: + +... +CapPrm: 00000000aa0435fb +CapEff: 00000000aa0435fb +... +Compare the capabilities of the two Containers: + +00000000a80425fb +00000000aa0435fb +In the capability bitmap of the first container, bits 12 and 25 are clear. In the second container, bits 12 and 25 are set. Bit 12 is CAP_NET_ADMIN, and bit 25 is CAP_SYS_TIME. See capability.h for definitions of the capability constants. + +Note: Linux capability constants have the form CAP_XXX. But when you list capabilities in your container manifest, you must omit the CAP_ portion of the constant. For example, to add CAP_SYS_TIME, include SYS_TIME in your list of capabilities. + + +--- +reference +- https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +- https://sysdig.com/learn-cloud-native/kubernetes-security/security-contexts/ +- https://bcho.tistory.com/1275 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Token\342\200\205Webhook\342\200\205with\342\200\205Guard.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Token\342\200\205Webhook\342\200\205with\342\200\205Guard.md" new file mode 100644 index 00000000..c708285f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/Token\342\200\205Webhook\342\200\205with\342\200\205Guard.md" @@ -0,0 +1,244 @@ +--- +title: 'Token Webhook with Guard' +lastUpdated: '2024-03-02' +--- + +Webhook 서버에 인증 데이터를 전달한 뒤, 클라이언트가 유효한지 인증하여 Third party에 권한을 부여하는 Webhook Token 인증 방법에 대해 알아보자. + +1. 사용자는 미리 인증 데이터를 발급받아 놓고, +2. 사용자가 인증 데이터를 Bearer 헤더에 담아서 REST API 요청을 보내면 +3. API 서버는 클라이언트가 유효한지를 검증하기 위해 Webhook 서버에 인증 데이터를 전달한다. +4. Webhook 서버는 해당 데이터를 검사한 뒤, 인증 여부를 API 서버에 반환하는 간단한 방식으로 되어 있다 + +Token Webhook, OIDC 중 어느 방법을 선택하든지에 상관 없이 클라이언트는 HTTP의 Bearer 헤더에 인증 데이터를 담아서 보내기만 하면 된다. 클라이언트는 인증 방법이 무엇인지 알 필요가 없기 때문에 인증 과정은 API 서버에서 Transparent 하게 처리된다. + +Webhook 서버 구현 중 하나로 [Guard](https://github.com/kubeguard/guard)라는 오픈소스가 있다. Guard는 [단순한 토큰 파일, Gitlab, Google, Azure, LDAP, EKS] 등을 인증 수단으로 사용할 수 있다. Github Organization을 이용해 클라이언트를 인증하는 방법에 대해 다룬다. + +> 인증 데이터를 생성하기 위해 Github에서 Organization (조직)을 미리 만들어 두어야 한다. + +## Guard 설치 + +kubectl을 사용할 수 있는 환경에서 guard를 다운로드한다. 설치 방법은 kops, Kubespray, kubeadm에 따라 조금씩 다르니 [공식문서](https://appscode.com/products/guard/v0.7.1/welcome/)를 참고하자. 테스트 예시로는 Guard 0.7.1 버전과 kubeadm으로 EC2에 배포된 쿠버네티스 클러스터를 사용했다. + +```bash +$ wget -O guard https://github.com/appscode/guard/releases/download/0.7.1/guard-linux-amd64 \ + && chmod +x guard \ + && sudo mv guard /usr/local/bin/ +``` + +공식 문서에 나와있는대로 HTTPS 통신을 위한 인증서를 생성한다. `~/.guard` 디렉터리에 인증서 파일이 생성된다. + +```bash +$ guard init ca +Wrote ca certificates in /root/.guard/pki + +$ guard init server --ips=10.96.10.96 +Wrote server certificates in /root/.guard/pki +``` + +단, `kube init server` 명령어의 `--ips` 옵션을 환경에 맞게 조금씩 수정해야 한다. `--ips` 옵션에는 Guard를 Deploy한 뒤 생성될 Service의 IP를 입력하면 된다. 쉽게 말해서, kube-apiserver에 설정되어 있는 Service의 IP CIDR 에서 적절한 IP를 하나 선택하면 된다. Service의 IP Range는 API 서버의 실행 옵션에서 확인할 수 있다. + +```bash +$ ps aux | grep service-cluster-ip-range +root 3442 2.1 7.5 ... --service-cluster-ip-range=10.96.0.0/12 +``` + +위 예시에서는 kubeadm에 의해 `--service-cluster-ip-range` 옵션의 값이 `10.96.0.0/12` 로 설정되었으며, 그 범위에 속하는 `10.96.10.96`을 `--ips` 옵션에 사용했다. 각자의 환경에 맞는 IP를 적당히 선택해 사용하면 된다. + +그 다음으로 클라이언트 측의 인증서를 생성한다. + +```bash +$ guard init client {ORGANIZATION_NAME} -o github +Wrote client certificates in $HOME/.guard/pki +``` + +`~/.guard/pki` 디렉터리에 키 페어가 정상적으로 생성되어 있는지 확인한다. + +```bash +$ ls ~/.guard/pki/ +ca.crt ca.key ml-kubernetes@github.crt ml-kubernetes@github.key server.crt server.key +``` + +Guard 서버를 Deploy하기 위한 YAML 파일을 생성한 뒤, 이를 쿠버네티스에 적용한다. + +```bash +$ guard get installer \ + --auth-providers=github \ + > installer.yaml + +$ kubectl apply -f installer.yaml +``` + +## Kubernetes API 서버에 Token Webhook 옵션 추가하기 + +API 서버가 Guard 서버를 Token Webhook 인증 서버로서 사용하도록 설정해야 한다. 아래의 명령어를 사용해 Webhook을 위한 설정 파일을 생성한다. 빨간 색으로 강조된 부분은 여러분의 환경에 맞게 적절히 바꿔 사용한다. + +```bash +$ guard get webhook-config ml-kubernetes -o github --addr=10.96.10.96:443 > webhook-config.yaml +``` + +위 명령어로부터 생성된 파일에는 API 서버가 Guard 서버를 Token Webhook 인증 서버로서 사용하기 위한 설정 정보가 저장되어 있다. 이 설정 파일을 API 서버의 `--authentication-token-webhook-config-file` 옵션을 통해 로드하면 쿠버네티스 쪽 설정은 끝이 난다. 그런데, 문제는 이 설정 파일을 API 서버가 로드하려면 설정 파일이 API 서버 컨테이너 내부에 위치해 있어야 한다는 것이다. + +kops의 경우에는 위 설정 파일의 내용을 kops edit cluster에 통째로 복사하는 것이 가능한 것 같은데, kubeadm은 그런 방법이 딱히 존재하지 않는다. 따라서, 임시 방편이긴 하지만 kubeadm의 설정 파일에서 API 서버의 컨테이너에 설정 파일을 마운트한 다음, 이 파일을 로드하도록 구성했다. (설정 파일 전체 내용은 여기를 참고). hostPath 부분의 webhook-config.yaml 파일이 위치한 경로만 여러분 환경에 맞게 적절히 수정한다. + +```yaml +kind: ClusterConfiguration +apiVersion: kubeadm.k8s.io/v1beta1 +apiServer: + extraArgs: + authorization-mode: Node,RBAC + cloud-provider: aws + authentication-token-webhook-config-file: /srv/kubernetes/webhook-guard-config + extraVolumes: + - name: "guard-config-file" + hostPath: "/root/webhook-config.yaml" + mountPath: "/srv/kubernetes/webhook-guard-config" + readOnly: true + pathType: File + timeoutForControlPlane: 4m0s +certificatesDir: /etc/kubernetes/pki +... +``` + +물론 이 방법은 Single Master라서 가능한 것이며, HA Master로 구성된 클러스터라면 S3나 NFS 등을 통해 설정 파일을 동일하게 배포해야 한다. 또한 kubeadm이 아닌 다른 도구로 쿠버네티스를 설치했다면 해당 도구에 맞는 적절한 방법을 사용해야 한다. + +아래의 명령어를 입력해 kubeadm의 kube-apiserver를 다시 배포한다. + +```bash +$ kubeadm init phase control-plane apiserver --config master-config.yaml + +[config] WARNING: Ignored YAML document with GroupVersionKind kubeadm.k8s.io/v1beta1, Kind=ClusterStatus +[control-plane] Creating static Pod manifest for "kube-apiserver" +[controlplane] Adding extra host path mount "guard-config-file" to "kube-apiserver" +``` + +## Github에서 토근 발급 받기 & API 서버에 인증하기 + +다음으로는 Github 계정 설정에서 새로운 토큰을 발급받아야 한다. https://github.com/settings/tokens/new 에 접속한 뒤, Note에는 적절한 토큰 이름을 (ex. guard-access-token) 입력하고 `admin:org`의 `read:org` 에 체크해서 PAT를 생성한다. + +이 토큰을 이용해 kubeconfig에 새로운 User를 추가한다. 편의를 위해 kubernetes 클러스터 + 새로운 User (Github 계정) 조합으로 새로운 컨텍스트를 만들었다. + +```bash +$ kubectl config get-contexts # kubernetes 라는 이름의 클러스터 사용 중 +CURRENT NAME CLUSTER AUTHINFO NAMESPACE +* kubernetes-admin@kubernetes kubernetes kubernetes-admin + +$ export TOKEN=f392362f760fd8... # Github 토큰 임시 저장 + +$ kubectl config set-credentials rlaisqls-github --token $TOKEN # 토큰으로 User 등록 +User "rlaisqls-github" set. + +$ # 등록한 User로 새로운 컨텍스트 생성 (--cluster 옵션은 적절한 클러스터 이름 사용) +$ kubectl config set-context github-rlaisqls-context --cluster kubernetes --user rlaisqls-github +Context "github-rlaisqls-context" created. +``` + +새로운 컨텍스트로 변경한 뒤 kubectl로 API 요청을 전송해 보면, 권한이 없다는 에러가 반환된다. + +```bash +$ kubectl config get-contexts +CURRENT NAME CLUSTER AUTHINFO NAMESPACE + github-rlaisqls-context kubernetes rlaisqls-github +* kubernetes-admin@kubernetes kubernetes kubernetes-admin + +$ kubectl config use-context github-rlaisqls-context +Switched to context "github-rlaisqls-context". + +$ kubectl get po +Error from server (Forbidden): pods is forbidden: User "rlaisqls" cannot list resource "pods" in API group "" in the namespace "default" +``` + +권한이 없다는 에러가 출력되는 것이 정상이다. RBAC 권한 부여 (Authorization)에서 오류가 나는 것이기 때문에 제대로 Guard가 동작하고 있음을 뜻한다. [ `error: You must be logged in to the server (Unauthorized)` ] 에러가 출력되지만 않으면 된다. + +이제 Github 사용자에게 RBAC로 권한을 부여하기만 하면 되는데, 여기서 한 가지 알아둘 점은 Github 계정의 이름이 User로, Org Team의 이름이 Group으로 치환된다는 점이다. 따라서 RoleBinding에서 Subject를 `User: rlaisqls` 또는 `Group: test-team` 처럼 적어주면 된다 (물론 test-team 이라는 이름의 팀이 존재해야 한다). 만약 RoleBinding에서 Group에 권한을 부여하면 Org의 Team에 속해 있는 모든 사용자에 대해 권한이 동일하게 부여된다. + +Github 계정에게 권한을 부여하기 위해, 아래의 내용으로 `github-svc-reader.yaml` 파일을 작성한다. + +```bash +$ cat github-svc-reader.yaml + +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: service-reader-role +rules: +- apiGroups: [""] + resources: ["services"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: service-reader-rolebinding + namespace: default +subjects: +- kind: User # Group 을 사용해도 됨 + name: rlaisqls # kind: Group인 경우에는 ml-kubernetes-team (team 이름) 사용 가능 +roleRef: + kind: Role + name: service-reader-role + apiGroup: rbac.authorization.k8s.io +``` + +위의 YAML 파일을 적용해 Github 계정에 권한을 부여하고, 다시 API를 호출해 보았다. + +```bash +$ # 현재는 Github context이기 때문에, 잠시만 --context로 관리자 권한을 가져온다 (impersonate) +$ kubectl apply -f github-svc-reader.yaml --context kubernetes-admin@kubernetes +role.rbac.authorization.k8s.io/service-reader-role created +rolebinding.rbac.authorization.k8s.io/service-reader-rolebinding created + +$ kubectl get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.96.0.1 443/TCP 3d7h +``` + +정상적으로 명령어가 실행되었음을 확인할 수 있다. Github 계정에 권한이 제대로 부여되었다. + +## Guard의 동작 원리 + +클라이언트로부터 API 요청이 발생하면, Token Webhook 서버는 API 서버로부터 [TokenReview] 라는 JSON 데이터를 전달받는다. [TokenReview] 데이터의 예시는 아래와 같다. + +```json +{ + "apiVersion": "authentication.k8s.io/v1beta1", + "kind": "TokenReview", + "spec": { + "token": "(BEARERTOKEN)" + } +} +``` + +위 데이터 중, `.spec.token` 항목에는 Github에서 생성한 토큰이 들어가 있다. Guard는 이 토큰을 이용해 '클라이언트가 Github 사용자가 맞는지, 클라이언트가 Github의 Org에 소속되어 있는지'를 Github 으로부터 읽어 온다. 인증이 정상적으로 이루어지면 Guard는 아래와 같은 형식의 데이터를 API 서버에게 다시 반환한다. + +```json +{ + "apiVersion": "authentication.k8s.io/v1", + "kind": "TokenReview", + "status": { + "authenticated": true, + "user": { + "username": "", + "uid": "", + "groups": [ + "", + "" + ] + } + } +} +``` + +username이 User에, groups가 Group에 매칭되어 RoleBinding 등에서 사용된다. + +## API 서버의 Token Webhook 옵션 (캐시 TTL) + +Github의 Org에 새로운 멤버를 삭제해도 API 서버에서는 즉시 반영되지 않으며, 일정 시간이 지나야만 Github 사용자의 인증이 정상적으로 거부된다. 이는 기본적으로 한 번 인증된 사용자의 인증 확인 여부 데이터를 API 서버 내부에 캐시로 보관하기 때문인데, 캐시의 TTL이 만료되면 다시 Guard에 인증을 요청한다. 기본적으로 이 값은 2분으로 설정되어 있기 때문에, 2분이 지난 뒤에 다시 API 요청을 보내면 Guard에 다시 인증 Webhook 을 전송한다. 따라서 2분마다 API 요청의 응답 시간이 조금 느려질 수 있다. (새로운 사용자 인증에 대해서는 무조건 Guard로 요청을 전송한다) + +인증에 대한 캐시 TTL은 API 서버의 실행 옵션에서 `--authentication-token-webhook-cache-ttl` 를 통해 설정할 수 있다. 기본적으로는 2m 으로 설정되어 있다. + +--- +참고 +- https://github.com/kubeguard/guard +- https://appscode.com/products/guard/v0.7.1/welcome/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/k8s\342\200\205\355\201\264\353\237\254\354\212\244\355\204\260\342\200\205root\342\200\205CA\353\245\274\342\200\205\355\206\265\355\225\234\342\200\205\354\202\254\354\232\251\354\236\220\342\200\205\354\235\270\354\246\235.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/k8s\342\200\205\355\201\264\353\237\254\354\212\244\355\204\260\342\200\205root\342\200\205CA\353\245\274\342\200\205\355\206\265\355\225\234\342\200\205\354\202\254\354\232\251\354\236\220\342\200\205\354\235\270\354\246\235.md" new file mode 100644 index 00000000..f103451c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Auth/k8s\342\200\205\355\201\264\353\237\254\354\212\244\355\204\260\342\200\205root\342\200\205CA\353\245\274\342\200\205\355\206\265\355\225\234\342\200\205\354\202\254\354\232\251\354\236\220\342\200\205\354\235\270\354\246\235.md" @@ -0,0 +1,113 @@ +--- +title: 'k8s 클러스터 root CA를 통한 사용자 인증' +lastUpdated: '2024-03-02' +--- + +k8s는 기본적으로 root CA 인증서를 스스로 발급해 사용한다. 이 인증서를 이용하면 별도의 Third-party 연동 없이도 User와 Group을 사용할 수 있다. 단, 이 방법은 여러모로 한계점이 많기 때문에 가능하면 사용하지 않는 것이 좋다. 인증서가 유출되었을 때 revoke가 힘들기도 하고, 파일 단위로 인증 정보를 관리하는 것은 매우 비효율적이고 보안에 취약하기 때문이다. 따라서 k8s는 인증서 발급을 통한 사용자 인증은 극히 제한된 경우에만 사용하고 있다. 대표적으로는 관리자 권한을 가져야 하는 `system:master` 그룹의 인증 정보를 생성한다거나 (`/etc/kubernetes/admin.conf`), k8s의 핵심 컴포넌트에 인증서를 발급한다거나 할 때가 이에 해당한다. + +우선, k8s는 기본적으로 마스터 노드의 `/etc/kubernetes/pki` 디렉터리에 root 인증서를 저장해 놓는다. + +위의 `ca.crt`와 `ca.key` 파일이 root 인증서에 해당하는데, 이 root 인증서를 통해 생성된 하위 인증서는 k8s 인증에 사용할 수 있다. k8s는 csr이라는 object를 통해 간접적으로 root CA로부터 하위 인증서를 발급하는 기능을 제공하기 때문에, 이 기능을 사용해서 하위 인증서에 대한 비밀 키를 우선 생성해보자. + +```bash +$ openssl genrsa -out rlaisqls.key 2048 +$ openssl req -new -key rlaisqls.key -out rlaisqls.csr -subj "/O=helloworld/CN=rlaisqls" +``` + +위 명령어에서 눈여겨 봐야 할 부분은 CSR을 생성할 때 `-subj` 옵션으로 넘겨준 O (Organization) 와 CN (Common Name) 의 값이다. 이전에 k8s의 User와 Group은 인증 방법에 따라서 그 단위 또는 대상이 달라진다고 설명했었는데, 인증서를 통해서 인증을 수행할 경우 O가 Group이 되고 CN이 User가 된다. 따라서 위의 CSR로부터 생성된 인증서로 k8s에 인증할 경우, RoleBinding에서는 Group -> helloworld 또는 User -> rlaisqls 와 같은 방식으로 대상을 지정해야 한다. + +어쨌든, k8s의 root CA가 위 CSR 파일에 sign 하기 위해 아래의 YAML 파일을 작성한다. CertificateSigningRequest는 CSR에 root CA로 간접적으로 sign할 수 있도록 해주는 k8s object이다. + +```yaml +cat << EOF >> csr.yaml +apiVersion: certificates.k8s.io/v1beta1 +kind: CertificateSigningRequest +metadata: + name: rlaisqls-csr +spec: + groups: + - system:authenticated + request: $(cat rlaisqls.csr | base64 | tr -d '\n') + usages: + - digital signature + - key encipherment + - client auth +EOF +``` + +위의 YAML 파일에서는 usages 항목에서 client auth를 설정했기 때문에 클라이언트 전용 하위 인증서를 생성한다 (server auth로 설정하면 서버 전용 인증서를 생성할 수도 있다). 또한 groups 항목에서 `system:authenticated`로 설정했기 때문에 k8s의 인증된 사용자로 설정된다. request 항목에는 CSR 파일을 base64로 인코딩한 값이 들어간다. + +위 YAML 파일을 통해 k8s에서 CSR 리소스를 생성한다. + +```bash +$ kubectl apply -f csr.yaml +certificatesigningrequest.certificates.k8s.io/rlaisqls-csr created + +$ kubectl get csr +NAME AGE REQUESTOR CONDITION +rlaisqls-csr 5s kubernetes-admin Pending +``` +CSR의 목록을 확인해 보면 CONDITION 상태가 Pending으로 설정되어 있는데, 이는 아직 k8s 관리자에 의해 승인되지 않았음을 뜻한다. 즉, [CSR을 제출하는 것]과 [이를 승인해 sign하는 것]은 별도의 작업으로 취급된다. k8s 관리자가 이를 승인하는 API를 명시적으로 호출해야만 정상적으로 하위 인증서가 생성된다. + +> REQUESTOR는 어떠한 User가 해당 CSR을 제출했는지를 의미한다. 위 예시에서는 kubeadm을 설치하면 자동으로 사용하도록 설정된 kubeconfig의 인증서로 CSR을 제출했으며, 이 인증서의 CN은 kubernetes-admin으로 설정되어 있다. + +아래의 명령어를 입력해 CSR을 승인한다. + +```bash +$ kubectl certificate approve rlaisqls-csr +certificatesigningrequest.certificates.k8s.io/rlaisqls-csr approved + +$ kubectl get csr +NAME AGE REQUESTOR CONDITION +rlaisqls-csr 4m35s kubernetes-admin Approved,Issued +``` + +정상적으로 승인되었으니 하위 인증서를 파일에 저장한 뒤, 해당 인증서로 API 요청을 전송해 본다. kubeconfig에 User를 등록해도 되겠지만, 지금은 임시로 kubectl 옵션에 인증서 파일을 넣어 사용했다. + +```bash +$ kubectl get csr rlaisqls-csr -o jsonpath='{.status.certificate}' | base64 -D > rlaisqls.crt + +$ kubectl get po --client-certificate rlaisqls.crt --client-key rlaisqls.key +Error from server (Forbidden): pods is forbidden: User "rlaisqls" cannot list resource "pods" in API group "" in the namespace "default" +``` + +> error: You must be logged in to the server (Unauthorized) 에러가 출력되었다면 중간에 뭔가 빼먹었을 가능성이 높다. 이 에러는 k8s에서 해당 credential을 아예 인증하지 못했다는 것을 의미한다. + +rlaisqls 이라는 이름의 User가 포드를 list 할 수 없다는 에러가 반환되었다. 위 에러가 발생했다면 정상적으로 인증서 발급이 완료된 것이기 때문에, Role이나 ClusterRole을 `User: rlaisqls` 에 대해서 부여하면 된다. 또는 인증서의 O (Organization) 이름이 helloworld이므로 `Group: helloworld` 처럼 대상을 지정해도 된다. + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: service-reader-clusterrole +rules: +- apiGroups: [""] + resources: ["services"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: service-reader-clusterrolebinding + namespace: default +subjects: +- kind: User # Group 을 사용해도 된다. + name: rlaisqls # kind: Group 인 경우, helloworld 를 사용해도 된다. +roleRef: + kind: Role + name: service-reader-clusterrole + apiGroup: rbac.authorization.k8s.io +``` + +위 내용을 rbac.yaml 파일로 저장해 적용한 뒤, 다시 API를 호출해 보면 정상적으로 실행되는 것을 확인할 수 있다. + +```bash +$ kubectl apply -f rbac.yaml +role.rbac.authorization.k8s.io/service-reader-clusterrole created +rolebinding.rbac.authorization.k8s.io/service-reader-clusterrolebinding created + +$ kubectl get svc --client-certificate rlaisqls.crt --client-key rlaisqls.key +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kubernetes ClusterIP 10.96.0.1 443/TCP 2d3h +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Cert\342\200\205manager.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Cert\342\200\205manager.md" new file mode 100644 index 00000000..1b04bbaf --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Cert\342\200\205manager.md" @@ -0,0 +1,175 @@ +--- +title: 'Cert manager' +lastUpdated: '2024-03-02' +--- + +kubernetes 에서 ingress 상에서 https를 서비스하는데 지원을 해주는 모듈이다. + +## cert manager 설치 + +```bash +kubectl create namespace cert-manager +kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.13.1/cert-manager.yaml + +or + +kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/v0.13.1/deploy/manifests/00-crds.yaml +kubectl create namespace cert-manager +helm repo add jetstack https://charts.jetstack.io +helm repo update + +# Helm v3+ +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --version v0.13.1 + +# Helm v2 +helm install \ + --name cert-manager \ + --namespace cert-manager \ + --version v0.13.1 \ + jetstack/cert-manager +``` + +## 설치 확인 + +```bash +kubectl get pods --namespace cert-manager +``` + +## cert manager issuser example + +```bash +apiVersion: cert-manager.io/v1alpha2 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + # The ACME server URL + server: https://acme-staging-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: YOUR_EMAIL + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-staging + # Enable the HTTP-01 challenge provider + solvers: + # An empty 'selector' means that this solver matches all domains + - selector: {} + http01: + ingress: + class: nginx + +--- +apiVersion: cert-manager.io/v1alpha2 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + # The ACME server URL + server: https://acme-v02.api.letsencrypt.org/directory + # Email address used for ACME registration + email: YOUR_EMAIL + # Name of a secret used to store the ACME account private key + privateKeySecretRef: + name: letsencrypt-prod + # Enable the HTTP-01 challenge provider + solvers: + - http01: + ingress: + class: nginx +``` + +## kubernetes-dashboard에 적용 + +```yml +kind: Ingress +apiVersion: extensions/v1beta1 +metadata: + name: kubernetes-dashboard + namespace: kubernetes-dashboard + labels: + app: kubernetes-dashboard + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: "letsencrypt-staging" + nginx.ingress.kubernetes.io/backend-protocol: HTTPS +spec: + tls: + - hosts: + - YOUR_DOMAIN + secretName: www-test-com-tls + rules: + - host: YOUR_DOMAIN + http: + paths: + - path: / + backend: + serviceName: kubernetes-dashboard + servicePort: 443 +status: + loadBalancer: + ingress: + - {} +``` + +- 발급 테스트가 완료되면 + - `cert-manager.io/cluster-issuer: "letsencrypt-staging"` + - `cert-manager.io/cluster-issuer: "letsencrypt-prod"` +- 로 바꾸어 실제 인증서를 발급 받는다. + +## 확인 + +`Normal Issued cert-manager Certificate issued successfully`가 뜨면 성공이다. + +```bash +[root@kube1 11]# kubectl describe certificate -n nginx-ingress +Name: www.test.com +Namespace: nginx-ingress +Labels: +Annotations: +API Version: cert-manager.io/v1alpha2 +Kind: Certificate +Metadata: + Creation Timestamp: 2020-03-13T06:02:23Z + Generation: 1 + Owner References: + API Version: extensions/v1beta1 + Block Owner Deletion: true + Controller: true + Kind: Ingress + Name: www.test.com + UID: a7d05229-a8cb-405a-80f7-424b0d00a71b + Resource Version: 44540390 + Self Link: /apis/cert-manager.io/v1alpha2/namespaces/nginx-ingress/certificates/$$$$$$$$$ + UID: 2e762fbc-2111-4b72-ae75-319f8d018be9 +Spec: + Dns Names: + www.test.com + Issuer Ref: + Group: cert-manager.io + Kind: ClusterIssuer + Name: letsencrypt-prod + Secret Name: ########### +Status: + Conditions: + Last Transition Time: 2020-03-13T06:03:27Z + Message: Certificate is up to date and has not expired + Reason: Ready + Status: True + Type: Ready + Not After: 2020-06-11T05:03:26Z +Events: + Type Reason Age From Message + ---- ------ ---- ---- ------- + Normal Requested 52s cert-manager Created new CertificateRequest resource "cgitlab-p-exem-xyz-3450475095" + Normal Issued cert-manager Certificate issued successfully + +``` + +--- +참고 +- https://cert-manager.io/docs/installation/kubernetes/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/End\342\200\205user\342\200\205RBAC.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/End\342\200\205user\342\200\205RBAC.md" new file mode 100644 index 00000000..2bf3ddcc --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/End\342\200\205user\342\200\205RBAC.md" @@ -0,0 +1,295 @@ +--- +title: 'End user RBAC' +lastUpdated: '2024-03-02' +--- + +Let's set up role-based access control (RBAC) suitable for running the cluster in production. We will cover roles for using Calico. General RBAC for a production Kubernetes cluster is beyond the scope of this lab. + +### Using `calicoctl` + +In order for the `calicoctl` tool to perform version mismatch verification (to make sure the versions for both the cluster and `calicoctl` are the same), whoever is using it needs to have `get` access to `clusterinformations` at the cluster level, i.e., not in a namespace. The network admin role below already has such access, but we will see that we will need to add it to the service owner user we will create turther on. + +```yml +kubectl apply -f - <jib gradle plugin을 추가하면 된다. + +```groovy +plugins { + id 'com.google.cloud.tools.jib' version '3.2.0' +} +``` + +그리고 `./gradlew jib`을 통해 image를 빌드 할 수 있다. + +로컬 kubernetes 개발 환경에서 skaffold 를 사용한다면 아래와 같이 `jib: {}`만 추가하면 알아서 로컬 kubernetes에 빌드/배포해준다. + +```yml +apiVersion: skaffold/v2beta27 +kind: Config +build: + local: + push: false + artifacts: + - image: example/image-name + context: ./example-app + jib: {} +``` + +아래는 Github Actions를 사용하여 이미지를 build 하고 AWS ECR에 image를 push 하는 코드의 일부이다 + +```yml +- name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 +- name: Grant execute permission for gradlew + run: chmod +x gradlew +- name: Build and Push with Gradle + id: build-and-push-to-ecr + run: ./gradlew jib -x test --image $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG +``` + +> Java 11 이상 사용을 권장한다. Java 8 은 containerize 된 환경에 최적화되지 않아 JVM이 효율적으로 운영되지 못한다. + +## Health Check APIs + +Kubernetes 의 readiness, liveness 설정을 위해선 SpringBoot application의 **health check API**가 필요하다. SpringBoot Actuator 에서 이 기능을 제공한다. 아래와 같이 `build.gradle` 에 dependency로 추가만 하면 된다. + +```groovy +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-actuator' +} +``` + +> Spring Boot 2 부터는 Actuator의 /health 와 /info 를 제외한 모든 endpoint가 disable 되어 있다. 만약 다른 endpoint 도 사용하고 싶다면 application.properties 에 추가적인 설정이 필요하다. 그렇지 않다면 그냥 dependenty 만 추가하고 그 외 추가적인 설정이나 개발은 필요없다. + +kubernetes 의 readiness 설정을 하지 않으면 rolling update 시 또는 autoscale을 통해 새로 pod가 생길때 SpringBoot application이 완전히 뜨기 전에 request가 들어가고, 이렇게 들어온 request는 ingress가 `503`을 리턴하게 된다. 또한 liveness 설정이 없으면 예기치못한 상황으로 SpringBoot application이 죽었을 때 서비스가 **새로 시작하지 않고 계속 죽어있게 된다.** + +## Graceful shutdown + +운영환경에서 Pod가 termination 되는 상황은 rolling update로 배포를 하거나 Kubernetes autoscale을 통해 늘어난 Pod가 줄어들 때 등이 있을 수 있다. 이때 Kubernetes는 SIGTERM 시그널을 보내고 Pod안의 SpringBoot Application은 종료가 된다. 하지만 SpringBoot의 default 설정은 시그널을 받자마자 종료되도록 되어 있고, 만약 종료될 때 들어온 request가 완료되기 전에 SpringBoot Application이 내려가면 해당 request를 보낸 쪽에서는 HTTP STATUS `503`을 받게 된다. + +> 실제 운영환경(또는 load testing)에서 이 문제는 application log 에서는 확인하지 못하고 kubernetes ingress 에서 503 확인이 가능하다. + +이를 graceful하게 처리하기 위해서는 graceful shutsown 설정을 해야한다. 이것 또한 간단하다. `application.yml` 파일에 아래와 같이 추가하면 된다. (단, Spring Boot 2.3 부터 가능한 option이다.) + +```yml +server: + shutdown: graceful +``` + +해당 설정이 추가되면 tomcat이 종료 시그널을 받았을때 처리중인 request가 있다면 이를 모두 처리하고 application이 종료된다. 하지만 들어온 request가 종료될 때까지 무한정 기다리는 것은 아니다. default설정은 30초간 기다리고 그때까지 종료하지 못한다면 강제종료된다. (일반적인 경우는 default 설정이면 충분하다.) + +이 설정은 아해와 같이 변경 가능하다. + +```yml +spring: + lifecycle: + timeout-per-shutdown-phase: 1m +``` + +## Loading HikariCP + +SpringBoot 2부터는 default DataSouce 구현체가 **Hikari**로 되어있다. `spring-boot-starter-data-jpa`나 `spring-boot-starter-jdbc`를 사용한다면 별도의 설정없이 Hikari를 사용하게 된다. Hikari는 Connection Pool (HikariCP)을 사용하여 DB connection을 관리하는데, Spring Boot application이 설정/개발에 따라 Hikari connection pool을 Spring Boot 이 시작할 때 바로 만들지 않고, DB 관련 request가 처음 들어와서 처리 할 때 그제서야 Hikari를 initialize 하면서 connection pool을 생성하는 경우도 있다. + +사실 이러한 과정은 일반적인 상황에서는 문제가 되지 않는다. 하지만 요청이 폭발하는 상황에서 kubernetes가 autoscale을 통해 새로운 pod를 생성하고 이렇게 생성된 pod가 바로 많은 요청을 받는 상황에서는 몇초간 latency가 매우 높아진다. + +현재 Spring Boot application이 언제 connection pool을 생성하는지 확인하려면 `application.properties` 에 아래와 같이 설정을 추가하여 hikari log를 남기고, application을 실행해 보자. (테스트 후 반드시 해당 설정을 제거하자. 특히 운영환경에서는...) + +```yml +logging: + level: + com: + zaxxer: + hikari: DEBUG +``` + +만약 application이 시작하면서 Hikari 설정 관련 log가 나오면서 connection pool이 생성된다면 문제가 되지 않는다. 하지만 그렇지 않다면 아래와 같이 connection pool을 application이 시작할때 만들어주어 몇초동안 latency가 급격히 높아지는 현상을 줄일 수 있다. + +```java +@Component +public class HikariLoader implements ApplicationRunner { + + private final HikariDataSource hikariDataSource; + + public HikariLoader(HikariDataSource hikariDataSource) { + this.hikariDataSource = hikariDataSource; + } + + @Autowired + public void run(ApplicationArguments args) throws SQLException { + hikariDataSource.getConnection(); + } +} +```` + +> 메소드에 `@Autowired`를 붙이면 field injection이 끝난 후 빈을 객체화 할때 해당 메서드가 실행된다. 실행할때는 해당 메소드의 인자가 자동으로 주입된다. 이를 이용해 원하는 정보를 설정하도록 할 수 있다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/NetworkPolicy\342\200\205Cilium\342\200\205example.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/NetworkPolicy\342\200\205Cilium\342\200\205example.md" new file mode 100644 index 00000000..e9e63d45 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/NetworkPolicy\342\200\205Cilium\342\200\205example.md" @@ -0,0 +1,223 @@ +--- +title: 'NetworkPolicy Cilium example' +lastUpdated: '2024-03-02' +--- + +Using the same KIND cluster from the Cilium install, let's deploy the Postgres database([database.yaml](https://github.com/strongjz/Networking-and-Kubernetes/blob/master/chapter-4/database.yaml)) with the follwing `YAML` and `kubectl` + +```bash +$ kubectl apply -f database.yaml +service/postgres created +configmap/postgres-config created +statefulset.apps/postgres created +``` + +Here we deploy our web server as a Kubernetes deployment([web.yaml](https://github.com/strongjz/Networking-and-Kubernetes/blob/master/chapter-4/web.yaml)) to our KIND cluster: + +```bash +$ kubectl apply -f web.yaml +deployment.apps/app created +``` + +To run connectivity tests inside the cluster network, we will deploy and use a `dnsutils` pod([dnsutils.yaml](https://github.com/strongjz/Networking-and-Kubernetes/blob/master/chapter-4/dnsutils.yaml)) that has basic networking tools like `ping` and `curl`: + +```bash +$ kubectl apply -f dnsutils.yaml +pod/dnsutils created +``` + +Since we are not deploying a service with an ingress, we can use `kubectl port-forward` to test connectivity to our web server: + +```bash +kubectl port-forward app-5878d69796-j889q 8080:8080 +``` + +Now from our local terminal, we can reach our API: + +```bash +$ curl localhost:8080/ +Hello +$ curl localhost:8080/healthz +Healthy +$ curl localhost:8080/data +Database Connected +``` + +Let’s test connectivity to our web server inside the cluster from other pods. To do that, we need to get the IP address of our web server pod: + +```bash +$ kubectl get pods -l app=app -o wide +NAME READY STATUS RESTARTS AGE IP NODE +app-5878d69796-j889q 1/1 Running 0 87m 10.244.2.21 kind-worker3 +``` + +Now we can test L4 and L7 connectivity to the web server from the `dnsutils` pod: + +```bash +$ kubectl exec dnsutils -- nc -z -vv 10.244.2.21 8080 +10.244.2.21 (10.244.2.21:8080) open +sent 0, rcvd 0 +``` + +From our dnsutils, we can test the layer 7 HTTP API access: + +```bash +$ kubectl exec dnsutils -- wget -qO- 10.244.2.21:8080/ +Hello + +$ kubectl exec dnsutils -- wget -qO- 10.244.2.21:8080/data +Database Connected + +$ kubectl exec dnsutils -- wget -qO- 10.244.2.21:8080/healthz +Healthy +``` + +We can also test this on the database pod. First, we have to retrieve the IP address of the database pod, `10.244.2.25`. We can use `kubectl` with a combination of labels and options to get this information: + +```bash +$ kubectl get pods -l app=postgres -o wide +NAME READY STATUS RESTARTS AGE IP NODE +postgres-0 1/1 Running 0 98m 10.244.2.25 kind-worker +``` + +Again, let’s use `dnsutils` pod to test connectivity to the Postgres database over its default port 5432: + +```bash +$ kubectl exec dnsutils -- nc -z -vv 10.244.2.25 5432 +10.244.2.25 (10.244.2.25:5432) open +sent 0, rcvd 0 +``` + +The port is open for all to use since no network policies are in place. Now let’s restrict this with a Cilium network policy. The following commands deploy the network policies so that we can test the secure network connectivity. Let’s first restrict access to the database pod to only the web server. Apply the network policy([layer_3_net_pol.yaml](https://github.com/strongjz/Networking-and-Kubernetes/blob/master/chapter-4/layer_3_net_pol.yaml)) that only allows traffic from the web server pod to the database: + +```bash +$ kubectl apply -f layer_3_net_pol.yaml +ciliumnetworkpolicy.cilium.io/l3-rule-app-to-db created +``` + +The Cilium deploy of Cilium objects creates resources that can be retrieved just like pods with kubectl. With kubectl describe ciliumnetworkpolicies.cilium.io l3-rule-app-to-db, we can see all the information about the rule deployed via the YAML: + +```bash +$ kubectl describe ciliumnetworkpolicies.cilium.io l3-rule-app-to-db +Name: l3-rule-app-to-db +Namespace: default +Labels: +Annotations: +API Version: cilium.io/v2 +Kind: CiliumNetworkPolicy +Metadata: + Creation Timestamp: 2023-07-25T04:48:10Z + Generation: 1 + Resource Version: 597892 + UID: 61b41c9d-eba0-4aa1-96da-cf534637cbcd +Spec: + Endpoint Selector: + Match Labels: + App: postgres + Ingress: + From Endpoints: + Match Labels: + App: app +Events: +``` + +With the network policy applied, the `dnsutils` pod can no longer reach the database pod; we can see this in the timeout trying to reach the DB port from the `dnsutils` pods: + +```bash +$ kubectl exec dnsutils -- nc -z -vv -w 5 10.244.2.25 5432 +nc: 10.244.2.25 (10.244.2.25:5432): Operation timed out +sent 0, rcvd 0 +command terminated with exit code 1 +``` + +While the web server pod is still connected to the database pod, the /data route connects the web server to the database and the NetworkPolicy allows it: + +```bash +$ kubectl exec dnsutils -- wget -qO- 10.244.2.21:8080/data +Database Connected + +$ curl localhost:8080/data +Database Connected +``` + +Now let’s apply the layer 7 policy. Cilium is layer 7 aware so that we can block or allow a specific request on the HTTP URI paths. In our example policy, we allow HTTP GETs on `/` and `/data` but do not allow them on `/healthz`; let’s test that: + +```bash +$ kubectl apply -f layer_7_netpol.yml +ciliumnetworkpolicy.cilium.io/l7-rule created +``` + +We can see the policy applied just like any other Kubernetes objects in the API: + +```bash +$ kubectl get ciliumnetworkpolicies.cilium.io +NAME AGE +l7-rule 6m54s + +$ kubectl describe ciliumnetworkpolicies.cilium.io l7-rule +Name: l7-rule +Namespace: default +Labels: +Annotations: API Version: cilium.io/v2 +Kind: CiliumNetworkPolicy +Metadata: + Creation Timestamp: 2021-01-10T00:49:34Z + Generation: 1 + Managed Fields: + API Version: cilium.io/v2 + Fields Type: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + .: + f:kubectl.kubernetes.io/last-applied-configuration: + f:spec: + .: + f:egress: + f:endpointSelector: + .: + f:matchLabels: + .: + f:app: + Manager: kubectl + Operation: Update + Time: 2021-01-10T00:49:34Z + Resource Version: 43869 + Self Link:/apis/cilium.io/v2/namespaces/default/ciliumnetworkpolicies/l7-rule + UID: 0162c16e-dd55-4020-83b9-464bb625b164 +Spec: + Egress: + To Ports: + Ports: + Port: 8080 + Protocol: TCP + Rules: + Http: + Method: GET + Path: / + Method: GET + Path: /data + Endpoint Selector: + Match Labels: + App: app +Events: +``` + +As we can see, `/` and `/data` are available but not /healthz, precisely what we expect from the NetworkPolicy: + +```bash +$ kubectl exec dnsutils -- wget -qO- 10.244.2.21:8080/data +Database Connected + +$kubectl exec dnsutils -- wget -qO- 10.244.2.21:8080/ +Hello + +$ kubectl exec dnsutils -- wget -qO- -T 5 10.244.2.21:8080/healthz +wget: error getting response +command terminated with exit code 1 +``` + +These small examples show how powerful the Cilium network policies can enforce network security inside the cluster. We highly recommend that administrators select a CNI that supports network policies and enforce developers’ use of network policies. Network policies are namespaced, and if teams have similar setups, cluster administrators can and should enforce that developers define network policies for added security. + +We used two aspects of the Kubernetes API, labels and selectors; in our next section, we will provide more examples of how they are used inside a cluster. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Pulling\342\200\205images\342\200\205from\342\200\205ECR\342\200\205on\342\200\205Kubernetes.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Pulling\342\200\205images\342\200\205from\342\200\205ECR\342\200\205on\342\200\205Kubernetes.md" new file mode 100644 index 00000000..91cb6bb4 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/Pulling\342\200\205images\342\200\205from\342\200\205ECR\342\200\205on\342\200\205Kubernetes.md" @@ -0,0 +1,10 @@ +--- +title: 'Pulling images from ECR on Kubernetes' +lastUpdated: '2024-03-02' +--- + +If you are getting the HTTP 403 (Forbidden) error or the error message no basic auth credentials when trying to pull an image from ECR you are most likely doing so without logging into it first. + +This is going to happen if we are running a Kubernetes cluster on EC2 instances instead of using EKS. To be able to authenticate before pulling images on Kubernetes we need to use the imagePullSecrets attribute that's going to reference the secret containing the credentials. + +To get the ECR credentials (assuming our instance profile allow us to do it) we can use the following AWS CLI command: \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/SpringBoot\342\200\205\354\204\234\353\271\204\354\212\244\353\245\274\342\200\205\354\234\204\355\225\234\342\200\205Kubernetes\342\200\205\354\204\244\354\240\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/SpringBoot\342\200\205\354\204\234\353\271\204\354\212\244\353\245\274\342\200\205\354\234\204\355\225\234\342\200\205Kubernetes\342\200\205\354\204\244\354\240\225.md" new file mode 100644 index 00000000..abbbb5a7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/SpringBoot\342\200\205\354\204\234\353\271\204\354\212\244\353\245\274\342\200\205\354\234\204\355\225\234\342\200\205Kubernetes\342\200\205\354\204\244\354\240\225.md" @@ -0,0 +1,175 @@ +--- +title: 'SpringBoot 서비스를 위한 Kubernetes 설정' +lastUpdated: '2024-03-02' +--- + +출처: https://velog.io/@airoasis/Spring-Boot-서비스를-위한-Kubernetes-설정 + +## Deployment + +아래는 deployment설정의 예시이다. + +```yml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: main-server + labels: + app: main-server +spec: + selector: + matchLabels: + app: main-server + template: + metadata: + labels: + app: main-server + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - main-server + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - name: main-server + image: main-server:latest + env: + - name: SPRING_PROFILES_ACTIVE + value: develop + - name: JAVA_TOOL_OPTIONS + value: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=localhost:5005 -Duser.timezone=Asia/Seoul" + ports: + - containerPort: 8080 + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 20 + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: 8080 + initialDelaySeconds: 20 + periodSeconds: 20 + resources: + requests: + cpu: 1 + memory: 1.5Gi + limits: + memory: 1.5Gi +``` + +위 설정에서 SpringBoot에 특화한 중요한 부분은 총 5가지 부분이 있다. + +### 1. Pod Anti-affinity 설정 + +이 설정은 pod가 여러 node에 균일하게 배포되는 것을 보장한다. 만약 replicas를 3으로 설정하였는데 모두 하나의 node에 배포되고 해당 node가 장애로 다운된다면 해당 서비스 또한 당분간 아예 서비스가 되지 않는다. 하지만 Pod Anti-affinity 설정으로 최대한 동일한 pod가 같은 node에 배포되는 것을 방지하면 장애에 강한 서비스를 만들 수 있다. + +> 여기서 `preferredDuringSchedulingIgnoredDuringExecution` 대신 `requiredDuringSchedulingIgnoredDuringExecution`를 사용하면 node에는 해당 pod가 하나밖에 생성 될수 없고, 추가로 scheduling 되어야 하는 pod는 pending 상태가 되어 node가 cluster 에 추가되면 그제서야 배치될 수 있다. + +### 2. Spring Profile 설정 + +Spring profile을 kubernetes의 environment variable을 통해 설정한다. `SPRING_PROFILES_ACTIVE` 여기에 필요한 profile을 설정한다. + +### 3. JVM 설정 + +Kubernetes의 environment variable인 `JAVA_TOOL_OPTIONS`를 통해 JVM 설정을 한다. + +#### Remote debugging 설정 + +`-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=localhost:5005`를 통해 container 안에서 동작중인 spring boot application 을 debugging 할 수 있다. IntelliJ에서의 설정은 Tutorial: Remote debug를 참고할 수 있다. + +#### imezone 설정 + +Docker image의 default timezone은 UTC이다. 기본 설정으로는 log도 UTC로 남아 분석하기 힘들 수 있다. 따라서 `-Duser.timezone=Asia/Seoul` 설정으로 JVM의 timezone을 한국시간으로 변경한다. + +(참고로 timezone 설정은 application code TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); 로도 가능하다) + +#### 4. Readiness & Liveness 설정 + +1부에서 Spring Boot Actuator를 포함하여 Spring Boot Application 을 개발하여 위와 같이 /health endpoint 를 활용하여 kubernetes 의 readiness 설정 및 liveness 설정을 할 수가 있다. + +Readiness 설정으로 새로 시작하는 Spring Boot 서비스가 완전히 start 된 후에 request가 들어가도록 하여 무중단 배포를 위해 반드시 필요한 설정이다. 그리고 Liveness 설정으로 더이상 서비스가 불가능한 경우 해당 pod로의 request 유입을 막고 restart하게 하여 다시 서비스가 가능하게 한다. + +#### 5. Resource request/limit 설정 + +Request / Limit 설정은 application 의 성격에 따라 달리해야 하고, 실제 서비스를 운영하면서 적절한 설정을 찾아야 한다. Java의 일반적인 특징으로 처음 시작할때 CPU와 Memory의 사용량이 급격히 증가하는데, 이를 감안하여 request/limit을 설정해야한다. + +대부분의 경우 request/limit의 best practice는 memory의 request와 limit은 동일한 값으로 설정하고 cpu는 상대적으로 큰 limit이나 아예 설정을 하지않아 unbounded limit으로 설정하는 것이다. cpu는 compressible resource라 여러 pod가 cpu를 서로 사용하려고 할 때에 서비스는 단지 쓰로틀링되어 처리시간이 좀 더 걸리지만 memory는 incompressible resource라 memory가 부족하면 Pod가 종료되고 새롭게 scheduling 되어야 한다. 따라서 이렇게 종료되어 서비스의 불안정을 막기 위해 memory는 request와 limit을 동일하게 설정하여 되도록 이런 상황을 방지한다. + +Goldilocks를 이용하면 VPA의 추천값을 Web을 통해 확인 가능하다. + +## Autoscale & Fault tolerance + +아래는 HPA(Horizontal Pod Autoscale) 및 PDB(Pod Disruption Budget) 예시이다. + +```yml +apiVersion: autoscaling/v2beta2 +kind: HorizontalPodAutoscaler +metadata: + name: main-server-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: main-server + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 90 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 50 + periodSeconds: 30 + scaleUp: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 100 + periodSeconds: 15 + - type: Pods + value: 4 + periodSeconds: 15 + selectPolicy: Max +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: main-server-pdb +spec: + minAvailable: 1 + selector: + matchLabels: + app: main-server +--- +``` + +#### 1. Horizontal Pod Autoscale (HPA) + +❗️HPA를 사용할때는 deployment에서 replicas 설정을 하면 안된다. + +HPA를 통해 Autoscale을 할 때 주의할 점은 **averageUtilization 의 기준은 resource request**라는 점이다. 결국 위 설정은 배포된 pod의 평균 CPU 사용량이 0.9 코어일 때 scale out을 한다. + +그리고 behavior 설정을 통해 scale up은 즉각 반응하도록 하고 scale down은 서서히 하도록 설정하였다. 이것 또한 application이나 서비스의 특성에 따라 조절이 필요하다. + +#### 2. Pod Disruption Budget (PDB) + +PDB는 운영에서 반드시 필요한 설정이다. Pod는 항상 설정된 replica의 수 만큼 유지되지만 시스템 관리로 인해 특정 node를 다운 시켜야 하는 경우, 또는 cluster autoscaler 가 node의 수를 줄이는 경우 등과 같은 이유로 pod의 수가 줄어들어야 하는 경우가 있다. + +이런 경우 PDB를 통해 최소한 운영 가능한 pod의 비율/개수를 정하거나 최대 서비스 가능하지 않은 pod의 비율/개수를 정하여 서비스의 안정성을 보장한다. 위에서는 최소 1개의 pod가 항상 보장되게 설정하였다. 결국 node가 scale down 되어야 하는 상황에서 최소 보장되어야 하는 PDB 설정을 만족하지 못하는 해당 node는 다운되지 않고 기다리다가 만족하는 상황이 되면 그때 다운되어진다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/kubectl\342\200\205context.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/kubectl\342\200\205context.md" new file mode 100644 index 00000000..5c40652d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/kubectl\342\200\205context.md" @@ -0,0 +1,69 @@ +--- +title: 'kubectl context' +lastUpdated: '2024-03-02' +--- + +쿠버네티스 클러스터를 관리하는 cli 도구인 kubectl에는 환경을 바꿔가며 클러스터를 관리할 수 있도록 "context"라는 개념이 존재한다. context 는 kubectl 을 깔면 생성되는 파일인 `~/.kube/config` 파일에서 설정할 수 있다. + +```yml +apiVersion: v1 +clusters: +- cluster: + insecure-skip-tls-verify: true + server: https://localhost:6443 + name: local-cluster +- cluster: + certificate-authority-data: ~~~~ + server: https://xxx.xxx.xxx.xxx + name: gcp-cluster + +users: +- name: local-user + user: + blah blah +- name: gcp-user + user: + blah blah + +contexts: +- context: + cluster: gcp-cluster + user: gcp-user + name: gcp-context +- context: + cluster: local-cluster + user: local-user + name: local-context + +current-context: local-context + +kind: Config +preferences: {} +``` + +- **clusters** + - 쿠버네티스 클러스터의 정보이다. 내 PC에 설치된 쿠버네티스 클러스터와, GCP에 설치된 쿠버네티스 클러스터가 있음을 볼 수 있다. + +- **users** + - 클러스터에 접근할 유저의 정보이다 + +- **context** + - cluster와 user를 조합해서 생성된 값이다. local-context는 local-user 정보로 local-cluster에 접근하는 하나의 set이 되는 것이다 + +- **current-context** + - 현재 사용하는 context 를 지정하는 부분이다 + - 현재는 local-context 를 사용하라고 설정되어 있으므로, 터미널에서 kubectl 명령을 입력하면 로컬 쿠버네티스 클러스터를 관리하게 된다 + +### context 조회 및 변경 + +```bash +# gcp-context 로 변경 +$ kubectl config use-context gcp-context + +# context 조회 +$ kubectl config get-contexts + +CURRENT NAME CLUSTER AUTHINFO NAMESPACE +* gcp-context gcp-cluster gcp-user + local-context local-cluster local-user +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/minukube\342\200\205\354\213\234\354\236\221\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/minukube\342\200\205\354\213\234\354\236\221\355\225\230\352\270\260.md" new file mode 100644 index 00000000..3f089d70 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/minukube\342\200\205\354\213\234\354\236\221\355\225\230\352\270\260.md" @@ -0,0 +1,154 @@ +--- +title: 'minukube 시작하기' +lastUpdated: '2024-03-02' +--- + +### 1. kubectl을 설치한다. + +버전은 1.22로 통일한다. + +linux 기준 명령어는 다음과 같다. + +```bash +curl -o kubectl https://s3.us-west-2.amazonaws.com/amazon-eks/1.22.6/2022-03-09/bin/linux/amd64/kubectl +``` + +mac 기준 명령어는 다음과 같다. + +```bash +curl -o kubectl https://s3.us-west-2.amazonaws.com/amazon-eks/1.22.6/2022-03-09/bin/darwin/amd64/kubectl +``` + +window 기준 명령어는 다음과 같다. + +```bash +curl -o kubectl.exe https://s3.us-west-2.amazonaws.com/amazon-eks/1.22.6/2022-03-09/bin/windows/amd64/kubectl.exe +``` + +### 2. minikube 실행 파일을 다운로드받는다. + +linux 기준 명령어는 다음과 같다. + +```bash +curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 +sudo install minikube-linux-amd64 /usr/local/bin/minikube +``` + +mac 기준 명령어는 다음과 같다. + +```bash +curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64 +sudo install minikube-darwin-amd64 /usr/local/bin/minikube +``` + +window에선 PowerShell을 관리자 권한으로 열어 다음 두개의 명령어를 입력한다. + +```bash +New-Item -Path 'c:\' -Name 'minikube' -ItemType Directory -Force +Invoke-WebRequest -OutFile 'c:\minikube\minikube.exe' -Uri 'https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe' -UseBasicParsing +``` + +```bash +$oldPath = [Environment]::GetEnvironmentVariable('Path', [EnvironmentVariableTarget]::Machine) +if ($oldPath.Split(';') -inotcontains 'C:\minikube'){ ` + [Environment]::SetEnvironmentVariable('Path', $('{0};C:\minikube' -f $oldPath), [EnvironmentVariableTarget]::Machine) ` +} +``` + +minikube 실행 전, 터미널을 닫고 다시 열면 끝이다. + +### 3. 클러스터를 실행한다. + +``` +minikube start +``` + +만약 실행에 실패한다면, 드라이버 페이지에 가서 container나 VM manager를 사용해볼 수 있다. + +```bash +minikube start --vm-driver=docker --base-image="kicbase/stable:v0.0.32" --image-mirror-country='cn' --image-repository='registry.cn-hangzhou.aliyuncs.com/google_containers' --kubernetes-version=v1.23.8 --force-systemd=true +``` + +혹시 이상한 이유로 실행이 실패한다면 이 명령어를 써볼 수 있다. 본인은 위 명령어로 성공했다. (mac) + +```bash +minikube start --extra-config=kubeadm.ignore-preflight-errors=NumCPU --force --cpus=1 +``` + +만약 성능 제약을 무시하고 실행하길 원한다면 위와 같은 명령어를 사용할 수 있다. + +> 여담이지만 싱글코어인 AWS EC2 t2.micro에서 minikube를 사용하는 것은 그렇게 좋은 선택이 아닌 것 같다......
배포에 K8s가 꼭 필요한 상황이 아니라면 그냥 로컬에서 돌려보자. + +### 3. 클러스터와 상호작용해본다. + +kubectl로 클러스터에 액세스 해본다. 또는, minikube로 명령어를 실행해본다. : +```bash +kubectl get po -A +minikube kubectl -- get po -A +``` + +아래 명령어로 minikube가 기본으로 사용되도록 설정할 수 있다. +```bash +alias kubectl="minikube kubectl --" +``` + +처음에는 스토리지 프로비저너와 같은 일부 서비스가 아직 실행 상태가 아닐 수도 있다. 대시보드로 클러스터의 상태를 확인해본다 : + +```bash +minikube dashboard +``` + +### 4. 애플리케이션을 배포해본다. + +배포를 만들고 expose로 포트를 지정해준다 : +```bash +kubectl create deployment hello-minikube --image=docker.io/nginx:1.23 +kubectl expose deployment hello-minikube --type=NodePort --port=80 +``` + +위의 명령어 대신 이 명령어로 포트포워딩을 설정할 수도 있다 : +```bash +kubectl port-forward service/hello-minikube 8080:8080 +``` + +배포가 잘 완료되었는지 확인한다 : +```bash +kubectl get services hello-minikube +``` + +외부에서 접근할 수 있도록 launch한다. +```bash +minikube service hello-minikube --url +``` + +### 5. 클러스터를 관리한다. + +쿠버네티스 일시 중지 (애플리케이션에 영향 X): +```bash +minikube pause +``` + +재시작: +```bash +minikube unpause +``` + +클러스터 멈추기: +```js +minikube stop +``` + +기본 메모리 제한 변경 (재시작 필요): +```js +minikube config set memory 9001 +``` + +설치된 쿠버네티스 서비스 목록 보기: +```bash +minikube addons list +``` + +모든 클러스터 삭제: +```js +minikube delete --all +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/\355\231\230\352\262\275\353\263\200\354\210\230\342\200\205\354\204\244\354\240\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/\355\231\230\352\262\275\353\263\200\354\210\230\342\200\205\354\204\244\354\240\225.md" new file mode 100644 index 00000000..4c6b29fa --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Kubernetes/\354\213\244\354\212\265/\355\231\230\352\262\275\353\263\200\354\210\230\342\200\205\354\204\244\354\240\225.md" @@ -0,0 +1,32 @@ +--- +title: '환경변수 설정' +lastUpdated: '2024-03-02' +--- + +포드의 구성 파일 안에서 정의한 환경 변수는 파드의 컨테이너를 위해 설정하는 커맨드와 인자들과 같이, 구성 파일 안의 다른 곳에서 사용할 수 있다. 아래의 구성 파일 예시에서, GREETING, HONORIFIC, 그리고 NAME 환경 변수들이 각각 Warm greetings to, The Most honorable, 그리고 Kubernetes로 설정되어 있다. 이 환경 변수들은 이후 env-print-demo 컨테이너에 전달되어 CLI 인자에서 사용된다. + +```yml +apiVersion: v1 +kind: Pod +metadata: + name: print-greeting +spec: + containers: + - name: env-print-demo + image: bash + env: + - name: GREETING + value: "Warm greetings to" + - name: HONORIFIC + value: "The Most Honorable" + - name: NAME + value: "Kubernetes" + command: ["echo"] + args: ["$(GREETING) $(HONORIFIC) $(NAME)"] +``` + +컨테이너가 생성되면, `echo Warm greetings to The Most Honorable Kubernetes` 커맨드가 컨테이너에서 실행되어 결과가 출력된다. + +``` +Warm greetings to The Most Honorable Kubernetes +``` diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/Dashboad/kiali\342\200\205with\342\200\205prometheus.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/Dashboad/kiali\342\200\205with\342\200\205prometheus.md" new file mode 100644 index 00000000..c261eeab --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/Dashboad/kiali\342\200\205with\342\200\205prometheus.md" @@ -0,0 +1,52 @@ +--- +title: 'kiali with prometheus' +lastUpdated: '2024-03-02' +--- + +Kiali requires Prometheus to generate the topology graph, show metrics, calculate health and for several other features. If Prometheus is missing or Kiali can’t reach it, Kiali won’t work properly. + +By default, Kiali assumes that Prometheus is available at the URL of the form http://prometheus.:9090, which is the usual case if you are using the Prometheus Istio add-on. If your Prometheus instance has a different service name or is installed in a different namespace, you must manually provide the endpoint where it is available, like in the following example: + +```yaml +spec: + external_services: + prometheus: + # Prometheus service name is "metrics" and is in the "telemetry" namespace + url: "http://metrics.telemetry:9090/" +``` + +Kiali maintains an internal cache of some Prometheus queries to improve performance (mainly, the queries to calculate Health indicators). It would be very rare to see data delays, but should you notice any delays you may tune caching parameters to values that work better for your environment. ([Kiali CR reference page](https://kiali.io/docs/configuration/kialis.kiali.io/#example-cr)) + +## Compatibility with Prometheus-like servers + +Although Kiali assumes a Prometheus server and is tested against it, there are TSDBs that can be used as a Prometheus replacement despite not implementing the full Prometheus API. + +Community users have faced two issues when using Prometheus-like TSDBs: + +- Kiali may report that the TSDB is unreachable, and/or +- Kiali may show empty metrics if the TSBD does not implement the `/api/v1/status/config`. + +To fix these issues, you may need to provide a custom health check endpoint for the TSDB and/or manually provide the configurations that Kiali reads from the `/api/v1/status/config` API endpoint: + +```bash +spec: + external_services: + prometheus: + # Fix the "Unreachable" metrics server warning. + health_check_url: "http://custom-tsdb-health-check-url" + # Fix for the empty metrics dashboards + thanos_proxy: + enabled: true + retention_period: "7d" + scrape_interval: "30s" +``` + +## Prometheus Tuning + + + + + +--- +reference +- https://kiali.io/docs/configuration/p8s-jaeger-grafana/prometheus/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/Grok\342\200\205exporter.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/Grok\342\200\205exporter.md" new file mode 100644 index 00000000..968d2725 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/Grok\342\200\205exporter.md" @@ -0,0 +1,15 @@ +--- +title: 'Grok exporter' +lastUpdated: '2024-03-02' +--- + +[Grok](https://www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html) is a tool to parce crappy unstructured log data into something structured and queryable. Grok is heavily used in Logstash to provide log data as input for ElesticSearch. + +Grok ships with about 120 predefined patterns for syslog logs, apache and other webserver logs, mysql logs, etc. It is easy to extend Grok with custom patterns. + +The grok_exporter aims at porting Grok from the [ELK stack](https://www.elastic.co/webinars/introduction-elk-stack) to [Prometheus](https://prometheus.io/) monitoring. The goal is to use Grok patterns for extracting Prometheus metrics from arbitrary log files. + +--- +reference +- https://github.com/fstab/grok_exporter +- https://tech-en.netlify.app/articles/en508870/index.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog.md" new file mode 100644 index 00000000..fe4be820 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog.md" @@ -0,0 +1,81 @@ +--- +title: 'datadog' +lastUpdated: '2024-03-02' +--- + +> 💡 APM, log, Infrastructure를 통합적으로 모니터링·관리하는 클라우드 모니터링 솔루션** + +- 여러 클라우드 환경에 나뉘어있는 리소스들을 통합적으로 모니터링 가능하다. +- 클라우드의 상태를 지속적으로 감시하여 예기치 못한 상황과 오류를 대비, 대응할 수 있다. + +## 장점 + +- 에러를 빠르게 확인하여 **신속한 대응** 가능 +- 애플리케이션 정보(log, query 등) 축적하여 **데이터 기반 개선** +- 개발자, 운영팀, 비즈니스 유저간 **긴밀한 협업** +- 다양한 언어과 환경을 지원하기 때문에, 원하는 애플리케이션에 **확장** 가능 +- **커스텀 대시보드** 생성 가능 +- 공식 문서가 친절함 + +## 단점 + +- 비용이 많이 든다. +- 기능이 많아서 실무에 도입하기 위해 사전 지식이 필요함. + +--- + +# Datadog의 주요기능 + +### Integrations + +- 여러가지 서비스와 연계하여 모니터링을 할 수 있다. (docker, k8s, ec2, rds, nginx 등등…) +- 정말 많은 호환을 제공한다. + +![image](https://user-images.githubusercontent.com/81006587/234476253-10642ee8-6ac6-4a51-a9ee-108913c0f997.png) + +### Dashboards + +- 서버의 중요한 성능수치를 시각적으로 추적, 분석, 표시할 수 있다. (cpu, memory, disk 용량 등) +- Datadog에서 제공하는 디폴트 측량값을 사용하거나, 커스텀하여 대시보드를 만드는 것도 가능하다 + +> 일부 정보에 태그를 붙여서 그룹화할 수도 있다. (ex. 버전별, 서버별 등) +> + +![image](https://user-images.githubusercontent.com/81006587/234476294-e0cb8deb-e270-4d1e-a9b8-387911f96050.png) + +### **APM** + +- Application Performance Management +- 애플리케이션 내부에 심어, 애플리케이션의 성능을 분석하는 서비스이다. +- 추가 셋업이 필요하지만, 기존 모니터링보다 더 많은 정보를 수집할 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/234476316-83103bda-ccd0-4aaa-99e7-6fa79b58078f.png) + +### Logs + +- 로그 수집 및 모니터링 환경 구축 가능 + - 인프라 및 어플리케이션에서 발생한 대량의 로그를 효율적으로 수집, 처리, 저장, 검색할 수 있다. +- Logs의 설정(Pipelines)로 수집한 로그에 대한 처리를 어떻게 할 것인지 정의할 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/234476387-911fa610-bf86-4b57-ba0f-7a69867e632e.png) + +### Monitors + +- 수집한 정보로부터 특정 판단 기준에 도달한 경우, 경고 또는 통지할 수 있다 +- slack이나 메일로 전달되도록 설정하는 것도 가능하다. + +![image](https://user-images.githubusercontent.com/81006587/234476423-2d6f4789-4aa2-4beb-b533-62b224c33703.png) + +### ****Service Map**** + +- 데이터 흐름 및 클러스터 서비스 자동매핑 +- 글로벌알림을 통해 추적, 로그 및 인프라 메트릭으로 원클릭 탐색 + +![image](https://user-images.githubusercontent.com/81006587/234476440-ea8fc30f-9ffc-429b-9c39-1f4fa9bc15d8.png) + +### ****Collaboration**** + +- 외부사용자와 실시간 그래프 및 대시보드를 공유하기 위한 공개 URL 설정 및 공유 기능을 제공한다. +- 그래프에 코멘트 및 주석을 추가하여 이슈 공유 및 트래킹을 할 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/234476465-952feb71-1fd4-4e7b-86a8-d284bcfc92d8.png) diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog\342\200\205APM\342\200\205\352\270\260\353\212\245\342\200\205\354\202\254\354\232\251\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog\342\200\205APM\342\200\205\352\270\260\353\212\245\342\200\205\354\202\254\354\232\251\355\225\230\352\270\260.md" new file mode 100644 index 00000000..c5b892b5 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog\342\200\205APM\342\200\205\352\270\260\353\212\245\342\200\205\354\202\254\354\232\251\355\225\230\352\270\260.md" @@ -0,0 +1,78 @@ +--- +title: 'datadog APM 기능 사용하기' +lastUpdated: '2024-03-02' +--- + +서버에 datadog agent를 설치하면 CPU 점유율, Memory, Disk사용량 등의 중요한 성능 정보를 모니터링할 수 있다. 하지만 애플리케이션의 전반적인 LifeCycle에 대한 리포트 (ex: GC, JVM, I/O 등)를 바탕으로 에러나 병목현상에 더 빠르게 대응할 수 있도록 하고싶다면 **Datadog APM**을 연결해야한다. + +## APM 이란? + +Application Performance Monitoring 의 약자로 구동 중인 애플리케이션의 대한 성능측정과 에러탐지 등, 전반적인 애플리케이션 라이프사이클의 정보를 수집해 모니터링할 수 있게 해준다. 보다 편리성을 위해서 다양하게 시각화한 Metrics, 그리고 API 테스트도 지원한다. + +여러 대의 애플리케이션에 설치가 가능하며 이를 한꺼번에 같은 UI 상에 보여주기 때문에 마이크로서비스 아키텍처 에도 유용하게 사용될 수 있다고 한다. + +## APM 설정해보기 + +### 1. 환경에 맞게 agent 설치 + +이 내용에 대해서는 데이터독 사이트을 참고할 수 있다. + +설명대로 잘 따라하면 된다. + +### 2. datadog.yaml 수정 + +Agent의 모든 설정은 제목의 파일명에서 확인할 수 있다. 각각의 설치환경 별로 해당파일의 경로를 모두 소개해주고 있으니 APM Service setup docs를 확인하여 파일을 찾아보자. + +단, 컨테이너 Agent의 경우에는 실행시 `-e DD_APM_ENABLED=true`로 설정하고, helm으로 설치한 경우엔 메니페스트에 `datadog.apm.portEnabled`를 true로 만들면 된다. + +그 외의 경우에는 설정 파일을 열어 쭈욱 밑으로 내려 Traces Configuration에서 아래와 같이 되어있는 부분을 찾는다. + +```bash +*********************************** +Traces Configuration +*********************************** +# +...중략.... +# +# apm_config: +# enable: true +``` + +이 두개의 부분의 주석을 해제한 후, Agent를 restart 해준다. + +```bash +systemctl restart datadog-agent +``` + +### 3. Tracer 설치, 실행 + +데이터독 공식 Docs에 따라 Teacer를 설치하고 실행한다. + +도커의 경우엔 아래의 명령어를 입력해주면 된다. + +``` +docker run -d --cgroupns host \ + --pid host \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + -v /proc/:/host/proc/:ro \ + -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \ + -p 127.0.0.1:8126:8126/tcp \ + -e DD_API_KEY= \ + -e DD_APM_ENABLED=true \ + -e DD_SITE= \ + gcr.io/datadoghq/agent:latest +``` + +### 4. 모니터링 + +연결한 후 APM 탭에 들어가보면, 정말 많은 정보를 확인할 수 있다. jvm 상태와 모든 프로그램에 대한 트래이싱 데이터 등등을 꼼꼼히 살펴볼 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/204086332-2d092adc-36f4-4770-a8ed-7a91e02914e1.png) + +![image](https://user-images.githubusercontent.com/81006587/204086343-e169c32b-e0fc-40dc-8374-c6c42f89f2be.png) + +![image](https://user-images.githubusercontent.com/81006587/204086356-d8e06ce4-c786-459b-96e8-2dc56bec2c81.png) + +화면을 보면, 현재 실행 중인 앱에서 어떤 것이 가장 많은 요청을 받고 있는지, 어떤 동작을 얼마나 수행하는지 Code-Level 단위로 세세하게 나오고 있다. 더 자세한 화면들은 직접 설정하여 둘러보자! + +datadog은 한국어로 친절하게 설명되어있는 자료가 별로 없는 반면에, Docs 내용이 정말 유익하다. APM 기능으로 어떤 것들을 할 수 있고 볼 수 있는지 더 알아보고 싶다면 Docs에 직접 들어가서 보면 좋다. diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" new file mode 100644 index 00000000..29d32a7b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/datadog\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" @@ -0,0 +1,104 @@ +--- +title: 'datadog 아키텍처' +lastUpdated: '2024-03-02' +--- + +## 1. Datedog Agent가 하는 일 (Application에서 서버로) + + Datedog 사용은 아래와 같은 흐름으로 진행된다. + +> ☝🏻 **Datadog 사용 흐름 3단계**
1. 서버에 Datadog agent를 설치한다. (api키 입력)
2. agent가 서버나 애플리케이션의 정보를 수집하여 Datedog 서버로 보낸다.
3. 유저가 웹에서 대시보드를 확인한다. + +Datadog Agent가 어떤 일을 하는지, Agent는 어떤 구조로 구성되어있는지 알아보자. + +### Datadog agent + +- 서버에 설치된 *agent는* 해당 서버의 시스템 정보를 수집하여 Datadog 서버로 전송한다. +- 추가적인 설정을 통해 DB, 메모리 스토어 등에서 추가적인 메트릭을 수집할 수 있다. (APM) + +image + + +## SNMP + +**SNMP**(**Simple Network Management Protocol**)는 네트워크에서 관리되는 장치에 대한 정보를 수집하고 구성하고 해당 정보를 수정하여 장치 동작을 변경하기 위한 프로토콜이다. + +다시말해, **각 호스트로부터 여러 관리 정보를 수집하여 관리하는 프로토콜**이다. (모니터링) + +SNMP는 Datadog의 Agent와 아주 연관이 큰 개념이다. + +image + +### Datadog과 SNMP + +image + + +SNMP의 개념에 Datadog 구조를 대입해보면 위와 같다. 하위 Agent는 구동하는 Application, Master Agent는 Datadog Agent, Trap은 DogStatsD, Manager는 Datadsg Sass에 대응하는 것을 볼 수 있다. + +Datadog의 상세 구조에 맞추어 자세히 나타내자면 아래와 같다. + +image + +--- + +## 2. 서버, 유저 사이의 데이터 파이프라인 + +[https://www.infoq.com/presentations/datadog-metrics-db/#downloadPdf/](https://www.infoq.com/presentations/datadog-metrics-db/#downloadPdf/) + +image + +| 이름 | 설명 | 방향 | +| --- | --- | --- | +| Metrics Sources | 유저의 서버에서 DataStores로 보내야하는 메트릭 소스 | 유저→서버 | +| Slack/Email/PagerDuty etc | Slack, Email 등으로 받는 경고 또는 보고 알림 | 서버→유저 | +| Customer Browser | 유저가 브라우저를 접속했을때 보는 정보 | 서버→유저 | + +> Metric Sources가 DB로 Intake 될때는, 데이터를 수집할때마다 전송하는 것이 아니라 **1~2분 단위로 묶여**서 전송된다. 이 시간을 늘려 최적화하는 것도 가능하다. + +> Datadog 데이터는 **아주 많은 캐시 서버**를 가지고있다. + +image + + +## 3. 데이터를 저장하는 방법 + +앞서 Metrics Source는 세가지 유형으로 처리된다고 얘기했다. (DB, Tag 처리후 DB, S3에 저장) + +이때 Datadog에서 데이터를 S3, DB로 나눠 저장하는 이유는 저장 용량과 조회 속도 때문이다. + +Datadog에서는 **아주 많은 서버 정보와 로그 데이터**가 저장되어 매일 한 유저당 약 10TB정도의 용량이 필요하다. 이 많은 양의 데이터를 저장하기 위해선 일반적인 DB보다 조금 느리더라도 대용량 데이터를 견딜 수 있는 저장소가 필요했는데, 이를 위해 쓰이는 것이 S3이다. + +하지만 S3는 인덱싱을 통한 빠른 조회가 힘들기 때문에 일부 정보는 빠른 스토리지 저장소(DRAM, SSD)에 NoSQL을 사용하여 저장하는 방식을 택한다. + +**저장소별 장단점** + +image + +| 종류 | 특징| +| --- | --- | +| DRAM | 비싸지만 빠르고 처리량이 높다. | +| SSD | 덜 비싸지만 덜 빠르다. (DRAM과 S3의 중간) | +| S3 | 약간 느리지만 장기적으로 대용량의 데이터를 저장할 수 있다. | + +### Hybrid Data Storage + +Datadog의 저장 데이터 유형은 크게 5가지가 있고, 그에 따라 적합한 용도의 저장소에 저장된다. + +유형별 선택한 저장소와 DB목록은 다음과 같다. + +image + +> 연, 월, 일별로 유지할 데이터를 구분하여서 다른 저장소에 저장함 + +> [Level DB](https://github.com/google/leveldb)(Indexed Database), Redis, [RocksDB](http://rocksdb.org), [SQLite](https://www.sqlite.org/index.html), [Cassandra](https://cassandra.apache.org/_/index.html), [Parquet](https://parquet.apache.org/)와 같은 NoSQL DB를 많이 사용함 (주로 Open source기반) + +## 4. Datadog의 동기화 처리 + +서비스의 상태, 또는 경고에 대한 데이터를 전송하는 서비스에서는 동기화가 매우 중요하다. 많은 저장 시스템 중 하나가 몇 밀리초 동안 먹통이 된다면, 서버의 이벤트와 경고를 놓칠 수 있다. + +Datadog에서 이벤트를 놓치는 것은 큰 문제를 야기할 수 있다. + +메트릭 데이터베이스의 좋은 점은 동기화가 우리에게 중요하지만 전통적인 동기화 메커니즘으로 할 필요는 없다는 것이다. Datadog의 파이프라인 아키텍처는 마치 심장박동과 같은 방식으로 동기화를 체크한다. + +image diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/helmChart\353\241\234\342\200\205Agent\342\200\205\354\204\244\354\271\230.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/helmChart\353\241\234\342\200\205Agent\342\200\205\354\204\244\354\271\230.md" new file mode 100644 index 00000000..f64e8b7c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/datadog/helmChart\353\241\234\342\200\205Agent\342\200\205\354\204\244\354\271\230.md" @@ -0,0 +1,100 @@ +--- +title: 'helmChart로 Agent 설치' +lastUpdated: '2024-03-02' +--- + +### 1. helm을 설치한다. + +맥에서는 `brew install helm`을 통해 설치할 수 있고, 윈도우에서는 Chocolatey, 리눅스에서는 Snap에서 패키지를 다운받으면 된다. 또는 바이너리 릴리즈를 다운받아서 직접 설치하는 방법도 있다. + +자세한 것은 공식문서에서 확인해보자. + +https://helm.sh/ko/docs/intro/install/ + +### 2. Datadog Operator + +Datadog Operator를 Helm을 통해 설치하는 명령어는 다음과 같다. + +```bash +$ helm repo add datadog https://helm.datadoghq.com +$ helm install -n datadog --create-namespace --set fullnameOverride="dd-op" mwp-datadog-operator datadog/datadog-operator +``` + +### 3. Kubernetes Secret으로 Datadog credential 생성 + +Datadog API, APP Key를 이용하여 Kubernetes secret을 생성한다. + +```js +$ kubectl create secret generic datadog-secrets --from-literal api-key= --from-literal app-key= +``` + +### 4. Datadog Agent 및 Cluster Agent 설치 및 설정 + +생성한 Kubernetes secret을 사용하여 Agent를 생성하기 위한 매니페스트르 정의한다. + +파일이름은 `datadog-operator.yml`로 생성했다. + +```yml +apiVersion: datadoghq.com/v1alpha1 +kind: DatadogAgent +metadata: + namespace: datadog + name: datadog +spec: + credentials: + apiSecret: + secretName: datadog-secrets + keyName: api-key + appSecret: + secretName: datadog-secrets + keyName: app-key + agent: + image: + name: "gcr.io/datadoghq/agent:latest" + config: + hostPort: 8125 + collectEvents: true + tolerations: + - operator: Exists + env: + - name: DD_DOGSTATSD_NON_LOCAL_TRAFFIC # Java JVM Metrics를 받기 위해 필요 + value: "true" + log: + enabled: true + logsConfigContainerCollectAll: true + apm: + enabled: true + hostPort: 8126 + process: + enabled: true + processCollectionEnabled: true + systemProbe: + bpfDebugEnabled: true + features: + kubeStateMetricsCore: + enabled: true + networkMonitoring: + enabled: true + clusterAgent: + image: + name: "gcr.io/datadoghq/cluster-agent:latest" + config: + clusterChecksEnabled: true + replicas: 2 +``` + +위와 같이 설정하면 Datadog agent가 DaemonSet 형태로 각 node에 설치된다. + +Datadog cluster agent도 설치되어 효율적인 운영이 가능하다. + +설정을 적용한다. + +```bash +$ kubectl apply -f datadog-operator.yaml +``` + +이렇게 적용하면 Datadog agent가 생성되어 auto discovery를 통해 가능한 모든 metrics를 가져온 후, 서버로 전달하는 과정을 수행한다. 그리고 추가로 로깅, 모니터링, 그리고 application에 Datadog APM 을 적용하면 port 8126으로 받을 수 있도록 설정했다. + +Agent는 DaemonSet 형태로 각 노드에 하나씩 배포된다. 해당 Pod를 명령어 `kubectl describe pod <생성된 포드명> -n datadog`으로 확인해보면 agent, `trace-agent`, `process-agent`, `system-probe` 이렇게 4개의 `container`가 올라가 있는걸 볼 수 있다. + +Datadog에 로그인 하여 Integration tab으로 가서 Kubernetes 및 Istio를 추가해 주면 관련 모니터링을 위한 Dashboard가 추가된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/ELK\342\200\205Stack.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/ELK\342\200\205Stack.md" new file mode 100644 index 00000000..c7e5927a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/ELK\342\200\205Stack.md" @@ -0,0 +1,188 @@ +--- +title: 'ELK Stack' +lastUpdated: '2024-03-02' +--- + +ELK는 Elasticsearch, Logstash 및 Kibana, 이 오픈 소스 프로젝트 세 개를 뜻하는 약자이다. + +- Elasticsearch : 검색 및 분석 엔진 +- Logstash : 여러 소스에 동시에 데이터를 수집하여 변환한 후 Elasticsearch 같은 “stash”로 전송하는 서버 사이드 데이터 처리 파이프라인 +- Kibana : 사용자가 Elasticsearch에서 차트와 그래프를 이용해 데이터를 시각화 + +여기에 데이터 수집기인 Beats를 추가한 것을 ELK Stack이라고 한다. Beats를 추가하면 다른 서버에서 데이터를 가져오는 것도 가능해진다. + +image + +ubuntu 기준으로 elk를 구축해보겠다. + +## Elasticsearch 설치 + +```bash +# 설치 +wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.8.1-amd64.deb +sudo dpkg -i elasticsearch-8.8.1-amd64.deb + +# 메모리 수정 +sudo vi /etc/elasticsearch/jvm.options +-Xms256m +-Xmx256m +``` + +설치된 프로그램의 파일은 아래와 같은 경로에 저장된다. 로그나 명령어를 실행하기 위해선 아래 파일 경로에 접근해야 하니 알아두면 좋다. + +- 실행 파일 : `/usr/share/{프로그램명}` +- 로그 : `/var/log/{프로그램명}` +- 시스템 설정 파일 : `/etc/default/{프로그램명}` +- 설정 : `/etc/{프로그램명}` +- 데이터 저장 : `/var/lib/{프로그램명}` + +Elasticsearch는 기본적으로 설치된 서버에서만 통신이 가능하게 설정이 되어있어서 외부접속을 허용하기 위해서는 추가 설정이 필요하다. network.host와 cluster부분을 바꾸고 실행해주자. + +```bash +## Elasticsearch 외부 접속 허용 ## +# 수정 +sudo vi /etc/elasticsearch/elasticsearch.yml +network.host: 0.0.0.0 +cluster.initial_master_nodes: ["node-1", "node-2"] + +# 재시작 +sudo service elasticsearch restart + +# 서비스 등록 및 시작 +sudo systemctl daemon-reload +sudo systemctl enable elasticsearch.service +sudo systemctl start elasticsearch.service +curl -X GET "localhost:9200" + +# 상태 확인 +curl localhost:9200/_cat/indices?v +curl -X GET localhost:9200/_cat/health?v +curl -X GET localhost:9200/_cat/nodes?v +``` + +## Kibana + +- Elasticsearch와 함께 작동하도록 설계된 오픈 소스 분석 및 시각화 플랫폼 +- Elasticsearch 색인에 저장된 데이터를 검색, 보기 및 상호 작용 +- 고급 데이터 분석을 쉽게 수행하고 다양한 차트, 테이블, 맵에서 데이터를 시각화 +- 간단한 브라우저 기반의 인터페이스를 통해 실시간으로 Elasticsearch 쿼리의 변경 사항을 표시하는 동적 대시보드를 신속하게 만들고 공유 + +설치한 뒤 외부 트래픽을 허용하고, elasticSearch와 연결되도록 설정을 수정해준다. +```bash +# Kibana 설치 +wget https://artifacts.elastic.co/downloads/kibana/kibana-8.8.1-amd64.deb +sudo dpkg -i kibana-8.8.1-amd64.deb + +# 설정 변경 +vi /etc/kibana/kibana.yml +server.port: 5601 +server.host: "0.0.0.0" +elasticsearch.hosts: ["http://{your_es_ip}:9200"] + +# 서비스 등록 및 시작 +sudo systemctl daemon-reload +sudo systemctl enable kibana.service +sudo systemctl start kibana.service +``` + +서버에 웹으로 처음 접속해서 로그인 해보면 이런 화면이 보인다. +image + +## Logstash 설치 + +```bash +# 설치 +wget https://artifacts.elastic.co/downloads/logstash/logstash-8.8.1-amd64.deb +sudo dpkg -i logstash-8.8.1-amd64.deb + +# 설정 +vi /etc/logstash/logstash.yml + +http: + host: "0.0.0.0" + +# 서비스 등록 및 시작 +sudo systemctl daemon-reload +sudo systemctl enable logstash.service +sudo systemctl start logstash.service + +# 상태 확인 +tail -f /var/log/logstash/logstash-plain.log +``` + +정보를 받아와서 매핑할 형식을 `/etc/logstash/conf.d` 밑에 파일로 지정해준다. + +나는 로깅용으로 써볼 생각이라 grok으로 log 패턴을 정의했다. + +```bash +# grok 패턴 정의 +PATTERN_LOG_LINE %{TIMESTAMP_ISO8601:timestamp} :: %{IP:ip} \[%{WORD:httpmethod}\] %{NUMBER:status} path : %{URIPATH:path} query : %{DATA:query} body : %{DATA:body} + +input { + beats { + port => 5088 + type => "request" + } +} + +filter { + if [type] == "request" { + grok { + match => { "message" => "%{PATTERN_LOG_LINE}" } + } + date { + match => ["timestamp", "yyyy-MM-dd HH:mm:ss.SSS" ] + target => "@timestamp" + timezone => "Asia/Seoul" + locale => "ko" + } + } +} + +output { + if [type] == "request" { + elasticsearch { + hosts => [ "localhost:9200" ] + index => "satellite-request-%{+YYYY.MM.dd}" + } + } +} +``` + +## Beat 설치 +```bash +# 설치 +wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.15.1-amd64.deb +sudo dpkg -i filebeat-7.15.1-amd64.deb + +# 설정 + +filebeat.inputs: +- type: filestream + id: my-filestream-id + enabled: true + paths: + - /home/repo/satellite/log/*.log # 로그 파일이 저장될 경로로 지정해준다. + +# 서비스 등록 및 시작 +sudo systemctl daemon-reload +sudo systemctl enable filebeat.service +sudo systemctl start filebeat.service +``` + +## kibana index 추가 + +`Stack Management > Index Management`에 들어가서 Logstash에 설정해놨던 정보랑 같은 이름의 index를 매핑하도록 추가해준다. + +image + +그러면 이런식으로 Discover에서 데이터를 확인하는 것이 가능하다. + +이 데이터는 내 Spring 서버에서 파일로 출력한 로그 > Beat > Logstash > ElasticSearch > Kibana의 과정을 통해 보여지는 것이다. 지금은 기본적인 세팅만 해놨는데 다른 자세한 커스텀 설정을 많이 할 수 있는 것 같다. + +image + +--- +참고 +- https://discuss.elastic.co/t/best-practices-for-indexing-log-data/101891 +- https://www.elastic.co/guide/en/logstash/7.6/introduction.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/ElasticSearch\342\200\205\352\262\200\354\203\211\342\200\205\353\252\205\353\240\271\354\226\264.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/ElasticSearch\342\200\205\352\262\200\354\203\211\342\200\205\353\252\205\353\240\271\354\226\264.md" new file mode 100644 index 00000000..d9f5120e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/ElasticSearch\342\200\205\352\262\200\354\203\211\342\200\205\353\252\205\353\240\271\354\226\264.md" @@ -0,0 +1,337 @@ +--- +title: 'ElasticSearch 검색 명령어' +lastUpdated: '2023-08-16' +--- + +## Elasicsearch 검색 명령어 + +#### 클러스터 상태 (Health) +- 클러스터가 어떻게 진행되고 있는지 기본적인 확인 +- 클러스터 상태를 확인하기 위해 `_cat` API를 사용 +- curl를 사용하여 수행 가능 -> 노드 정보: GET `/_cat/nodes?v` 상태 정보 : GET `/_cat/health?v` + - Elasticsearch에서 _언더바가 붙은 것들이 API + - v는 상세하게 보여달라는 의미 +- 녹색 : 모든 것이 정상 동작 +- 노란색 : 모든 데이터를 사용 가능하지만 일부 복제본은 아직 할당되지 않음(클러스터는 완전히 동작) +- 빨간색 : 어떤 이유로든 일부 데이터를 사용 불가능(클러스터가 부분적으로만 동작) + +#### 데이터베이스(index)가 가진 데이터 확인하기 + +- index는 일반 RDB에서의 DB 역할 +- 모든 인덱스 항목을 조회 +- GET `/_cat/indices?v` + +#### 데이터 구조 + +- 인덱스와 도큐먼트의 단위로 구성 (타입은 deprecated) +- 도큐먼트는 엘라스틱서치의 데이터가 저장되는 최소 단위 +- 여러 개의 도큐먼트가 하나의 인덱스를 구성 + +- 입력 : PUT + - `ip:9200/index1/type1/1 -d ‘{“num”:1,”name:”test”}’` +- 조회 : GET + - `ip:9200/index1/type1/1` +- 삭제 : DELETE + - `ip:9200/index1/type1/1` +- 업데이트 : POST + - `ip:9200/index1/type1/1_update -d ‘{doc: {“age”:99}}’` +7.X부터는 PUT과 POST 혼용 가능 + +## 실습 + +`IP:5601` 으로 키바나에 접속한 후 왼쪽 탭에서 Devtools 탭을 클릭하여 테스트해 볼 수 있다. + +
+ api +
+ ```bash + # index와 doc 만들기 + # customer 는 인덱스명, 1은 doc의 id + POST /customer/_doc/1 + { + "name":"choi", + "age":25 + } + + # _update로 수정하기 + POST /customer/_doc/1/_update + { + "doc": { + "name":"change" + } + } + + # script 사용 + POST /customer/_doc/1/_update + { + "script" : { + "inline": "if(ctx._source.age==25) {ctx._source.age++}" + } + } + + # 조회하기 + GET /customer/_doc/1 + + # 삭제하기 + DELETE /customer + ``` +
+
+ +### 배치 프로세스 + +- 작업을 일괄적으로 수행할 수 있는 기능 +- 최대한 적은 네트워크 왕복으로 가능한 빨리 여러 작업을 수행할 수 있는 효율적인 매커니즘 +- 하나의 행동이 실패해도 그 행동의 나머지는 행동을 계속해서 처리 +- API가 반환되면 각 액션에 대한 상태가 전송된 순서대로 제공되므로 특정 액션이 실패했는지 여부를 확인 가능 + +
+ api +
+ ```bash + ## 키바나의 Devtools에서 진행 ## + + # 벌크 저장 + POST /customer/_bulk + {"index":{"_id":"1"}} + {"name":"choi"} + {"index":{"_id":"2"}} + {"name":"kim"} + + # 조회 + GET /customer/_doc/1 + GET /customer/_doc/2 + + # 수정 및 삭제 + POST /customer/_bulk + {"update":{"_id":"1"}} + {"doc":{"age":18}} + {"delete":{"_id":"2"}} + + # 조회 + GET /customer/_doc/1 + GET /customer/_doc/2 + ``` +
+
+ +### 검색 API + +- 검색 API는 요청 URI나 요청 본문을 통해 검색 매개 변수를 보내서 실행할 수 있다. +- 검색용 REST API는 _search 엔드 포인트에서 액세스할 수 있다. + +#### URI를 통해 검색하기 +```bash +GET /bank/_search?q=*&sort=account_number:asc&pretty +``` + +- bank 인덱스 +- q=*: q는 쿼리, *는 모든것을 의미 -> 인덱스의 모든 문서를 검색을 지시한 것, 특정 단어 검색을 원한다면 특정 단어를 명시 +- sort: 정렬 +- asc: 오름차순 +- pretty: 이쁘게 출력 + + +- took: 검색하는데 걸린 시간 (밀리 초) +- timed_out: 검색 시간이 초과되었는지 여부 알림 +- _shards: 검색된 파편의 수와 성공/실패한 파편의 수를 알림 +- hits: 검색 결과 +- hits.total: 검색 조건과 일치하는 총 문서 수 +- max_score: 가장 높은 점수를 취득한 doc 를 명시, null의 경우 없다는 뜻 +- hits.hits: 검색 결과의 실제 배열(기본값은 처음 10개) +- hits.sort: 결과 정렬키 + +
+ 명령어 +
+ ```bash + # 전체 인덱스의 title필드에서 time검색 + /_search?q=title:time + + # 다중 조건 검색 -> and 또는 or + /_search?q=title:time AND machine + + # 점수 계산에서 사용된 상세값 출력 + /_search?q=title:time&explain + + # doc 출력 생략 + /_search?q=title:time&_source=false + + # 특정 source 출력 + /_search?q=title:time&_source=title,author + + # 정렬 + /_search?q=title:time&sort=pages + /_search?q=title:time&sort=pages:desc + ``` +
+
+ +#### 본문을 통해 검색하기 - Query DSL + +Elasticsearch에서 쿼리를 실행하는데 사용할 수 있는 JSON 스타일 도메인 관련 언어이다. URI에 `q=*` 대신 JSON 스타일 쿼리 요청 본문을 제공하면 된다. + +
+ 명령어 +
+ ```bash + # match_all 쿼리는 지정된 색인의 모든 문서를 검색 + POST /bank/_search + { + "query": {"match_all": {}} + } + + # 1개만 조회 + # size dafult = 10 + POST /bank/_search + { + "query": {"match_all": {}}, + "size":1 + } + + # from 매개변수에서 시작하여 size만큼의 문서를 반환 + # 즉, 10 ~ 19 까지 + # from default = 0 + POST /bank/_search + { + "query": {"match_all": {}}, + "from": 10, + "size": 10 + } + + # balance 필드 기준 내림차순 정렬하고 상위 10개 + POST /bank/_search + { + "query": {"match_all": {}}, + "sort": {"balance":{"order":"desc"} + } + + # 특정 필드만 출력 + POST /bank/_search + { + "query": {"match_all": {}}, + "_source": ["account_number","balance"] + } + + # address가 mail lain인 것을 반환 + # 일치순으로 나옴 -> mail lane, mail, lane 이런 식 + POST /bank/_search + { + "query": {"match": {"address": "mail lane"}} + } + + # address가 mail lain과 완벽 일치 반환 + POST /bank/_search + { + "query": {"match_phrase": {"address": "mail lane"}} + } + + # address가 maill과 lane을 포함하는 모든 계정 반환 + POST /bank/_search + { + "query": { + "bool": { + "must": [ + {"match": {"address": "mill"}}, + {"match": {"address": "lane"}} + ] + } + } + } + + # mill은 포함하지만 lane은 포함하지 않는 모든 계정 반환 + POST /bank/_search + { + "query": { + "bool": { + "must": [ + {"match": {"address": "mill"}}, + ], + "must_not": [ + {"match": {"address": "lane"}} + ] + } + } + } + + # match_all 쿼리는 지정된 색인의 모든 문서를 검색 + POST /bank/_search + { + "query": {"match_all": {}} + } + + # 1개만 조회 + # size dafult = 10 + POST /bank/_search + { + "query": {"match_all": {}}, + "size":1 + } + + # from 매개변수에서 시작하여 size만큼의 문서를 반환 + # 즉, 10 ~ 19 까지 + # from default = 0 + POST /bank/_search + { + "query": {"match_all": {}}, + "from": 10, + "size": 10 + } + + # balance 필드 기준 내림차순 정렬하고 상위 10개 + POST /bank/_search + { + "query": {"match_all": {}}, + "sort": {"balance":{"order":"desc"} + } + + # 특정 필드만 출력 + POST /bank/_search + { + "query": {"match_all": {}}, + "_source": ["account_number","balance"] + } + + # address가 mail lain인 것을 반환 + # 일치순으로 나옴 -> mail lane, mail, lane 이런 식 + POST /bank/_search + { + "query": {"match": {"address": "mail lane"}} + } + + # address가 mail lain과 완벽 일치 반환 + POST /bank/_search + { + "query": {"match_phrase": {"address": "mail lane"}} + } + + # address가 maill과 lane을 포함하는 모든 계정 반환 + POST /bank/_search + { + "query": { + "bool": { + "must": [ + {"match": {"address": "mill"}}, + {"match": {"address": "lane"}} + ] + } + } + } + + # mill은 포함하지만 lane은 포함하지 않는 모든 계정 반환 + POST /bank/_search + { + "query": { + "bool": { + "must": [ + {"match": {"address": "mill"}}, + ], + "must_not": [ + {"match": {"address": "lane"}} + ] + } + } + } + ``` +
+
diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/Elastic\342\200\205Search.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/Elastic\342\200\205Search.md" new file mode 100644 index 00000000..07f8409e --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/Elastic\342\200\205Search.md" @@ -0,0 +1,36 @@ +--- +title: 'Elastic Search' +lastUpdated: '2024-03-02' +--- +- 확장성이 뛰어난 오픈 소스 전체 텍스트 검색 및 분석 엔진 +- 대량의 데이터를 신속하고 거의 실시간으로 저장, 검색 및 분석 +- 일반적으로 복잡한 검색 기능과 요구 사항이 있는 응용 프로그램을 구동하는 기본 엔진 / 기술 + +## 핵심 개념 +- Near Realtime (NRT) + - Elastic Search는 거의 실시간 검색 플랫폼 + - 문서를 색인할 때부터 검색 기능할 때까지 약간의 대기시간(일반적으로 1초)이 매우 짧음 +- 클러스터(Cluster) + - 전체 데이터를 함께 보유하고 모든 노드에서 연합 인덱싱 및 검색 기능을 제공하는 하나 이상의 노드(서버) 모음 -> 노드의 그룹이라고 생각 + - 클러스터는 기본적으로 elasticsearch 라는 고유한 이름으로 식별 + - 이 이름은 노드가 이름으로 클러스터에 참여하도록 설정된 경우 노드가 클러스터의 일부일 수 있기 때문에 중요 +- 노드(Node) + - 노드는 클러스터의 일부이며 데이터를 저장하고 클러스터의 인덱싱 및 검색 기능에 참여하는 단일 서버 + - 단일 클러스터에서 원하는 만큼 노드를 소유 가능 + - 현재 네트워크에서 실행중인 다른 Elasticsearch 노드가 없는 경우 단일 노드를 시작하면 기본적으로 elaticsearch라는 새로운 단일 노드 클러스터가 생성 +- 색인(index) + - 색인은 다소 유사한 특성을 갖는 문서의 컬렉션 + - 색인은 이름(모두 소문자여야함)으로 식별되며 이 이름은 색인 작성, 검색, 갱신 및 삭제할 때 색인을 참조하는데 사용 + - DB 관점에서 보면 DB(Schema)에 해당 +- Type + - 다른 종류의 data들을 같은 index에 저장하게 해주는 index의 ‘논리적인’ 부분을 의미 + - 7.x 버전부터 해당 개념이 전체 삭제 -> deprecated + - DB 관점에서 보면 Table에 해당 +- Documments + - 색인을 생성할 수 있는 기본 정보 단위 + - JSON으로 표현 + - DB 관점에서 Record + +--- +참고 +- https://esbook.kimjmin.net/01-overview \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/Logstash.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/Logstash.md" new file mode 100644 index 00000000..645e1fec --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/elk/Logstash.md" @@ -0,0 +1,225 @@ +--- +title: 'Logstash' +lastUpdated: '2024-03-02' +--- + +Logstash는 실시간 파이프라인 기능을 가진 데이터 수집 엔진 오픈소스이다. Logstash는 서로 다른 소스의 데이터를 동적으로 통합하고 원하는 대상으로 데이터를 정규화 할 수 있는 능력을 가진다. + +다양한 입력과 필터 및 출력 플러그인을 통해, 모든 유형의 이벤트를 보강하고 변환할 수 있으며, 많은 기본 코텍이 처리 과정을 단순화한다. 따라서 Logstash는 더 많은 양과 다양한 데이터를 활용하여 통찰력 있게 볼 수 있게 해 준다. + +image + +## Logtash 파이프라인 + +Logstash의 전체적인 파이프라인에는 INPUTS과 FILTERS, 그리고 OUTPUT이 있다. + +이 중에서 2가지의 필수적인 요소는 INPUTS과 OUTPUTS이고, 파싱 여부에 따라 필터는 선택적으로 사용이 가능하다. + +image + +### Logstash.yml + +Logstash 실행을 컨트롤하는 setting 파일이다. + +다른 설정 말고 초기 설정은 아래와 같이 2가지만 세팅되어 있다. + +```yml +path.data : /var/lib/logstash +path.logs : /var/log/logstash +``` + +### jvm.option + +이 파일을 통해 힙 사이즈를 조절할 수 있다. 초기 세팅은 아래와 같다 + +```bash +# 초기 total heapsize +-Xms1g + +# 최대 heap size +-Xmx1g +``` + +### Pipline.yml + +현재는 단일 파이프라인으로 구성되어 있는데, 하나의 인스턴스에서 여러 개의 파이프라인을 실행할 경우 추가해준다. + +`/etc/logstash/con.d`밑에 있는 `.conf` 파일로 연결되어있다. + +```bash +- pipeline.id: main + path.config: "/etc/logstash/conf.d/*.conf" +``` + +### conf.d/custom.conf +conf 파일명은 원하는 대로 설정해주면 된다. 위 pipline.yml 파일에서 직접 지정해서 작성하거나 *. conf로 작성한다. + +--- +### INPUT + +```bash +# input +input { + beats { + port => 5044 + host => "0.0.0.0" + client_inactivity_timeout => 86400 + } +} +``` + +- 파일 비트로부터 데이터를 받을 때는 input을 beats로 설정한다. + +- 파일비트로부터 데이터를 받을 포트 지정 (기본 포트 5044) + +- 호스트 상관없이 모든 데이터를 받을 경우 호스트는 0.0.0.0으로 작성 + +--- +### FILTER + +엘라스틱에 전달하기 전에 원하는 데이터의 타입, 형태 등으로 필터링/전처리하는 과정 + +#### [예제 1] 실제 사용하는 필터의 2가지 예제 + +```bash +filter { + if "IMP" in [log][file][path] { + mutate { + gsub => ["message", ", ", "| "] + } + grok { + match => { "message" => ["%{NUMBER:[imp][date]},%{NUMBER:[imp][h]},%{NUMBER:[imp][cu_id]},%{NUMBER:[imp][au_id]},%{NUMBER:[imp][pu_id]},%{WORD:[imp][c_key]},%{WORD:[imp][p_key]},%{GREEDYDATA:[imp][no_info]},%{NUMBER:[imp][place_id]},%{WORD:[imp][nation]},%{WORD:[imp][device]},%{NUMBER:[imp][no_info2]},%{NUMBER:[imp][user_key]},%{WORD:[imp][p_set_id]},%{GREEDYDATA:[imp][url]},\"%{TIMESTAMP_ISO8601:[imp][cre_tt]}\",%{GREEDYDATA:[imp][remote_addr]},%{NUMBER:[click][ar_id]}"]} + remove_field => ["message"] + } + grok { + match => { "message" => ["%{NUMBER:[imp][date]},%{NUMBER:[imp][h]},%{NUMBER:[imp][cu_id]},%{NUMBER:[imp][au_id]},%{NUMBER:[imp][pu_id]},%{WORD:[imp][c_key]},%{WORD:[imp][p_key]},%{GREEDYDATA:[imp][no_info]},%{NUMBER:[imp][place_id]},%{WORD:[imp][nation]},%{WORD:[imp][device]},%{NUMBER:[imp][no_info2]},%{NUMBER:[imp][user_key]},%{WORD:[imp][p_set_id]},%{GREEDYDATA:[imp][url]},\"%{TIMESTAMP_ISO8601:[imp][cre_tt]}\""] + remove_field => ["message"] + } + } + date { + match => [ "[imp][cre_tt]", "YYYY-MM-dd H:m:s" ] + target => "@timestamp" + timezone => "Asia/Seoul" + } + mutate { + gsub => [ '[imp][url]', '"', ''] + convert => ["[imp][au_id]","integer"] + convert => ["[imp][cu_id]","integer"] + convert => ["[imp][date]","integer"] + convert => ["[imp][h]","integer"] + convert => ["[imp][place_id]","integer"] + convert => ["[imp][pu_id]","integer"] + } + } +} +``` + +1. [log][file][path] : 로그의 위치 + - 노출 로그를 정제하는 과정으로 우선 log를 읽어오는 경로로 다른 로그와 구분해준다. + +2. mutate의 gsub + - 가장 상단에서 grok에 보낼 메시지를 미리 전처리할 작업이 있을 때 사용 + - 현재는 `,` 구분자를 `|` 로 변경하는 작업을 진행함 + - 사용법 : mutate { gsub => [필드명, 원래 값, 변경할 값] } + +3. grok 플러그인 + - 우선 읽어들인 로그는 message안에 담겨서 온다. + - grok을 여러 번 사용하면, multiple grok을 적용할 수 있다. + - 하나의 grok 패턴에서 파싱이 안되면 message가 그대로 다음 grok으로 넘어오게 되고 재시도를 한다. + - 하지만, 여러 개에 grok에 파싱이 되는 메시지의 경우 여러 번 grok이 적용되는 문제도 발생하니 주의해서 사용해야 한다. + - 사용법 : grok { match => {"message" => [grok 정규표현식] }, removed_field => [지우고 싶은 필드명] } + +4. date 플러그인 + - 엘라스틱에 데이터가 저장될 때, 엘라스틱에 데이터를 보내는 시간이 아닌 실제 로그 파일에 찍힌/적힌 시간으로 엘라스틱에 적재하기 위해 원하는 filed로 재 설정을 해줘야 한다. + - 사용법: `data { match => {변경할 필드명, 날짜 format }, target => "@timestamp", timezone=>"Asia/Seoul" }` + +5. mutate 플러그인 + - 위에서 설명한 gsub 외에도 다양한 기능이 존재 + - elastic에 로그스테이시에서 파싱한 데이터가 넘어갈 경우, 필드의 타입을 변경해주어야 원하는 타입으로 데이터가 들어간다. + - 데이터 타입을 변경하지 않고 적재하면 무조건 string으로 넘어간다. + - 사용법: `mutate { convert => [ 변경할 필드명, 데이터 타입 ] }` + +#### [예제 2] 실제 사용하는 필터의 2가지 예제 + +```bash +filter { + if "access" in [log][file][path] { + grok { + match => {"message" => ["%{IPORHOST:[nginx][access][remote_ip]} - \[%{HTTPDATE:[nginx][access][time]}\] \"%{WORD:[nginx][access][method]} %{GREEDYDATA:[nginx][access][url]}\?%{GREEDYDATA:[nginx][access][params]} HTTP/%{NUMBER:[nginx][access][http_version]}\" %{NUMBER:[nginx][access][response_code]} %{NUMBER:[nginx][access][body_sent][bytes]} \"%{DATA:[nginx][access][agent]}\" \"%{GREEDYDATA:[nginx][access][private_ip]}\" %{NUMBER:[nginx][access][request_time]} %{NUMBER:[nginx][access][request_length]}"]} + remove_field => ["message"] + } + kv { + field_split => "&?" + source => "[nginx][access][params]" + remove_field => "[nginx][access][params]" + } + mutate { + remove_field => ["agent"] + rename => { "@timestamp" => "read_timestamp" } + add_field => { "read_timestamp" => "%{@timestamp}" } + } + date { + match => [ "[nginx][access][time]", "dd/MMM/YYYY:H:m:s Z" ] + target => "@timestamp" + timezone => "Asia/Seoul" + } + } +} +``` + +#### 1. [log][file][path] : 로그의 위치 + - 로그를 정제하는 과정으로 우선 log를 읽어오는 경로로 다른 로그와 구분해준다. + - 여기서는 로그 경로에 "access"가 포함되어 있으면 이곳에서 필터링이 된다 + +#### 2. grok 패턴 적용 + - 작성한 grok 패턴이 나의 로그에 잘 적용되는지 확인하기 위해서는 다양한 툴이 존재하지만, kibana에 존재하는 툴을 사용하면 좋다. + - Dev Tools > Grok Debugger 에서 sample data와 grok pattern을 입력하고 simulate 버튼을 누르면 작성한 grok 패턴이 잘 적용되는지를 미리 알 수 있다. + +#### 3. kv 필터 플러그인 + - key "구분자" value 구조의 데이터를 분류하는데 특화되어있다. + - 여기서는 &? 구분자를 사용하여 nginx의 access param을 분리하는데 사용하였다. + - 사용법: `filter { kv { source ⇒ 필드명, field_split ⇒ 구분자로 필드분리, value_split ⇒ 구분자로 key value 분리 } }` + +#### 4. mutate 플러그인 + - 위에서 부터 자주 사용 된 플러그인으로 필드 삭제, 필드명 변경, 필드 추가 등 다양한 기능이 더 존재한다. + - 사용법: filter { mutate { remove_field ⇒ 삭제할 필드, rename ⇒ 필드명 변경, add_field ⇒ 필드추가 } } + +--- + +### OUTPUT + +- fileBeat로부터 데이터를 받아 로그스테이시에서 전처리를 한 데이터를 전송할 곳을 지정 +```bash +# 기본 틀 +output { + elasticsearch { + index => "%{[some_field][sub_field]}-%{+YYYY.MM.dd}" + } +} +``` + +- 여러개의 파일비트에서 하나의 로그스테이시로 보낼 경우, 파일에 따라 다른 인덱스명으로 엘라스틱에 적재를 해야할 경우는 아래와 같이 조건문을 사용하여 다양한 인덱스로 보내준다. + +```bash +output { + if "IMP_PRODUCT" in [log][file][path] { + elasticsearch { + hosts => ["ip 주소:9200"] + manage_template => false + index => "2020-imp-%{[@metadata][beat]}-%{[host][name]}" + } + } + else if "CLICK" in [log][file][path] { + elasticsearch { + hosts => ["ip 주소:9200"] + manage_template => false + index => "2020-click-%{[@metadata][beat]}-%{[host][name]}" + } + } +} +``` + + +--- +참고 +- https://www.elastic.co/guide/en/logstash/7.6/introduction.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/grafana/Loki\342\200\205Canary.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/grafana/Loki\342\200\205Canary.md" new file mode 100644 index 00000000..2ac24e92 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/grafana/Loki\342\200\205Canary.md" @@ -0,0 +1,62 @@ +--- +title: 'Loki Canary' +lastUpdated: '2024-03-02' +--- + +- Loki Canary is a standalone app that audits the log-capturing performance of a Grafana Loki cluster. + +- Loki Canary generates artificial log lines. These log lines are sent to the Loki cluster. + +- Loki Canary communicates with the Loki cluster to capture metrics about the artificial log lines, such that Loki Canary forms inforation about the performance of the Loki cluster. The information is available as Prometheus time series metrics. + +image + +- Loki Canary writes a log to a file and stores the timestamp in an internal array. The contents look something like this. + ``` + 1557935669096040040 ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp + ``` + +- The relevant part of the log entry is the timestamp; the `p`s are just filler bytes to make the size of the log configurable. + +- An agent (like Promtail) should be configured to read the log file and ship it to Loki. + +- Meanwhile, Loki Canary will open a WebSocket connection to Loki and will tail the logs it creates. When a log is received on the WebSocker, the timestamp in the log message is compared to the internal array. + +- If the received log is: + - The next in the array to be received, it is removed from the array and the (current time - log timestamp) is recorded in the `response_latency` histogram. This is the expected behavior for well behaving logs. + - Not the next in the array to be received, it is removed from the array, the response time is recorded in the `response_latency` histogram, and the `out_of_order_entries` counter is incremented. + - Not in the array at all, it is checked against a separate list of received logs to either increment the `duplicate_entries` counter or the `unexpected_entries` counter. + +- In the background, Loki Canry also runs a timer which iterates through all of the entries in the internal array. + - If any of the entries are older than the duration specified by the `-wait` flag (defaulting to 50s), they are removed from the array and the `websocket_missing_entries` counter is incremented. + - An additional query is then made directly to Loki for any missing entries to determine if they are truly missing or only missing from the WebSocket. If missing entries are not found in the direcy query, the `missing_entries` counter is incremented. + +--- + +## Additional Queries + +### Spot Check + +- The canary will spot check certain result over time to make sure they are present in Loki, this is helpful for testing the transition of inmemory logs in the ingesyer to the store to make sure nothing is lost. + +- `-spot-check-interval`(default `15m`) and `-spot-check-max`(default `4h`) are used to tune this feature, `-spot-check-interval` will pull a log entry from the stream at this interval and save it in a separate list up to `-spot-check-max`. + +- Every `-spot-check-query-rate`, Loki will be queried for each entry in this list and `loki_canary_spot check entries_total` will be incremented. + +> NOTE: if you are using `out-of-order-percentage` to test ingestion of out-of-order log lines be sure not to set the two out of order time range flags too far in the past. The defaults are already enough to test this functionality properly, and setting them too far in the past can cause issues with the spot check test. +> When using `out-of-order-percentage` you also need to make use of pipeline stages in your Promtail configuration in order to set the timestamps correctly as the logs are pushed to Loki. The client/promtail/pipelines docs have examples of how to do this. + +### Metric Test + +- Loki Canary will run a metric query `count_over_time` to verify that the rate of logs being stored in Loki corresponds to the rate they are being created by Loki Canary. + +- `-metric-test-interval` and `-metric-test-range` are used to tume this feature, but by default every `15m` the canary will run a `count_over_time` instant-query to Loki for a range fo `24h`. + +- If the canary has not run for `-metric-test-range` (`24h`) the query range is adjusted to the amount of time the canary has been running such that the rate can be calculated since the canary wes started. + +- The canary calculates what the expected count of logs would be for the range (also adjusting this based on canary runtime) and compares the expected result with the actual result returned from Loki. The difference is stored as the value in the guage `loki_canary_metric_test_deviation` + +--- +reference +- https://grafana.com/docs/loki/latest/operations/loki-canary/ + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus.md" new file mode 100644 index 00000000..21a6f79c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus.md" @@ -0,0 +1,37 @@ +--- +title: 'prometheus' +lastUpdated: '2024-03-02' +--- + +Prometheus is an open-source systems monitoring and alerting toolkit. Prometheus collects and stores its metrics as time series data, i.e. metrics information is stored with the timestamp at which it was recorded, alongside optional key-value pairs called labels. + +Prometheus work well for recording any purely numeric time series. It fits both machine-centric monitoring of highly dynamic service-oriented architectures. In a world of microservices, its supports for multi-dimensionl data collection and quering is a particular strength. + +Prometheus is designed for reliability, to be the system you go to during an outage to allow you to quickly diagnose problems. Each prometheus server is standalone, not depending on network storage or other remote services. You can rely on it when other parts of your infrastructure are broken, and you do not need to setup extensive infrastructure to use it.s + +## Features + +Prometheus's main features are: + +- a multi-dimensional data model with time series data identifies by metric name and key/value pairs. +- PromQL, a flexible query language to leverage this dimensionality. +- no reliance on distributed storage' single server nodes are autonomous +- time series collection happens via a pull model over HTTP +- pushing time series is supported via an intermediary gateway +- targets are discovered via service discovery or static configuration +- multiple modes of graphing and dashboarding support/ + +## Architecture + +This diagram illustrates the architecture of Prometeus and some of its ecosstem components. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/42f0f8a0-f205-4814-b475-728dc28e1132) + +Prometheus scrapes metrics from instrumented jobs, either directly or via an intermediary push gateway for short-lived jobs. It stores all scraped samples locally and runs rules over this data to wither aggregate and record new time series from existing data or generate alerts. Grafaba ir itger API consumers can be used to visualize the collected data. + +You can get more information in this link + +--- +reference +- https://prometheus.io/docs/introduction/overview/ +- https://github.com/prometheus/prometheus \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205agent\342\200\205mode.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205agent\342\200\205mode.md" new file mode 100644 index 00000000..d73ba28d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205agent\342\200\205mode.md" @@ -0,0 +1,80 @@ +--- +title: 'prometheus agent mode' +lastUpdated: '2024-03-02' +--- + +- The core design of Prometheus is inpired by Google's [Borgmon monitoring system](https://sre.google/sre-book/practical-alerting/#the-rise-of-borgmon), you can deploy a Prometheus server alongside the applications you want to monitor, tell Prometheus how to reach them, and allow to scrape the current values of their metrics at regular intervals. + +- Such a collection method, which is often referred to as the "pull model", is the core principle that allow Prometheus to be lightweight and reliable. + +- Futhermore, it enables application instrumentation and exporters to be dead simple, as they only need to provide a simple human-readable HTTP endpoint with the current value of all tracked metrics (in OpenMetrics format). All without complex push infrastructure and non-trivial client libraries. Overall, a simplified typical Prometheus monitoring deployment look as below: + ![image](https://github.com/rlaisqls/TIL/assets/81006587/c8c0743b-0379-4046-9856-e28c86679936) + +- This works great, and we have seen millions of successful deployments like this over the years that process dozens of milions of active series. + +## How to get the Global View? + +- However, with the advent of the concept of edge clusters or networks, we can see much smaller clusters with limited amounts of resources. So, we need to monitoring data has to be somehow aggregated, presented to users and sometimes even stored on the `Global-View` feature. + +- Naively, we could think about implementing this by either putting Prometheus on that global level and scraping metrics across remote networks or pushing metrics directly from the application to the central location for monitoring purposes. + - Scraping across network boundaries can be a challenge if it adds new unknowns in a monitoring pipeline. The local pull model allows Prometheus to know why exactly the metric target has problems and when. Maybe it's down, misconfigured, restarted, too slow to give us metrics (e.g. CPU saturated), not discoverable by service discovery, we don't have credentials to access or just DNS, network, or the whole cluster is down. + - Pushing metrics directly from the application to some central location is equally bad. Especially when you monitor a larger fleet, you know literally nothing when you don't see metrics from remote applications. + +- Prometheus introduced three ways to support the global view case, each with its own pros and cons. Let's brify go through those. They arr shown in orange color in the diagram below: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/31f91957-ca61-4525-a40f-80a7f6bd2c3d) + +1. **Federation** + - Federation was introduced as the fist feature for aggregation purposes. It allows a global-level Prometheus server to scrape a subset of metrics from a leaf Prometheus. + - Such a "federation" scrape reduces some unknowns across networks because metrics exposed by federation endpoints include the original samples' timestamps. Yet, it usually suffers from the inability to federate all metrics and not lose data during longer network partitions (minutes). +2. **Prometheus Remote Read** + - Prometheus Remote Read allows selecting raw metrics from a remote Prometheus server's database without direct PromQL query. You can deploy Prometheus or other solutions (e.g. Thanos) on the global level to perform PromQL queries on this data while fetching the required metrics from multiple remote locations. + - Last but not least, certain security guidelines are not allowing ingress traffic, only egress one. +3. **Prometheus Remote Write** + - Finally, we have Prometheus Remote Write, which seems to be the most popular choice nowadays. Since the **agent mode** focuses on remote write use case. + - Remote Write protocol allows us to forward (stream) all or a subset of metrics collected by Prometheus to th remote location. You can configure Prometheus to forwatd some metrics to one or more locations that support the Remote Wriate API. + - Streaming data from such a scraper enables Global View use cases by allowing you to store metrics data in a centralized location. This also enables separation of concerns, which is useful when applications are managed by different teams than the observability or monitoring pipelines. + - The amazing part is that, even with Remote Write, Prometheus still uses a pull model to gather metrics from applications, which gives us an understanding of those different failure modes. After that, we batch samples and series and export, replicate (push) data to the Remote Write endpoints. + +## Prometheus Agent Mode + +- The Agent mode optimizes Prometheus for the remote write usecase. Is disables querying, alerting, and local storage, and replaces it with a customized TSDB WAL. Everything else stays the same: scraping logic, service discovery ans related configuration. +- It can be used as a drop-in replacement for Prometheus if you want to just forward your data to a remote Prometheus server or any other Remote-Write-compliant project. In essence it looks like this: + ![image](https://github.com/rlaisqls/TIL/assets/81006587/935a2dac-35d3-4811-977d-13c3e192826c) + +- What are the benefits of using the Agent mode if you plan not to query or alert on data locally and stream metrics outside? There are a few: + 1. **Efficiency**: + - Prometheus customized Agent TSDB WAL removes the data immediately after successful writes. If it cannot reach the remote endpoint, it persists the data temporarily on the disk until the remote endpoint is back online. + 2. It is enables easier horizontal scalability for ingestion. A true auto-scalable solution for scraping would need to be based on the amount of metric targets and the number of metrics they expose. The more data we have to scrape, the more instances of Prometheus we deploy automatically. If the number of targets or their number of metrics goes down, we could scale down and remove coupe of instances. + +## How to Use Agent Mode in Detail + +- From now on, if you show the help output of Prometheus (--help flag), you should see more or less the following: + +```bash +usage: prometheus [] + +The Prometheus monitoring server + +Flags: + -h, --help Show context-sensitive help (also try --help-long and --help-man). + (... other flags) + --storage.tsdb.path="data/" + Base path for metrics storage. Use with server mode only. + --storage.agent.path="data-agent/" + Base path for metrics storage. Use with agent mode only. + (... other flags) + --enable-feature= ... Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, remote-write-receiver, + extra-scrape-metrics, new-service-discovery-manager. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. +``` + +- Since the Agent mode is behind a feature flag, as mentioned previously, use the `--enable-feature=agent` flag to run Prometheus in the Agent mode. Now, the rest of the flags are either for both server and Agent or only for a specific mode. You can see which flag is for which mode by checking the last sentence of a flag's help string. "Use with server mode only" means it's only for server mode. If you don't see any mention like this, it means the flag is shared. + +- The Agent mode accepts the same scrape configuration with the same discovery options and remote write options. + +- It also exposes a web UI with disabled query capabitilies, but showing build info, configuration, targets and service discovery information as in a normal Prometheus server. + +--- +reference +- https://prometheus.io/blog/2021/11/16/agent/ +- https://katacoda.com/thanos/courses/thanos/3-receiver \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205glossary.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205glossary.md" new file mode 100644 index 00000000..29a2f859 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205glossary.md" @@ -0,0 +1,97 @@ +--- +title: 'prometheus glossary' +lastUpdated: '2024-03-02' +--- + + + +### Core + +- **Prometheus** + - Prometheus usually refers to the core binary of the Prometheus system. It may also refer to the Prometheus monitoring system as a whole. + +- **Target** + - A target is the definition of an object to scrape. For example, what labels to apply, any authentication required to connect, or other information that defines how the scrape will occur. + +- **Endpoint** + - A source of metrics that can be scraped, usually corresponding to a single process. + +- **Sample** + - A sample is a single value at a point in time in a time series. In Prometheus, each sample consists of a float64 value and a millisecond-precision timestamp. + +### Alert + +- **Alert** + - An alert is the outcome of an alerting rule in Prometheus that is actively firing. Alerts are sent from Prometheus to the Alertmanager. + +- **Notification** + - A notification represents a group of one or more alerts, and is sent by the Alertmanager to email, Pagerduty, Slack etc. + +- **Alertmanager** + - The Alertmanager takes in alerts, aggregates them into groups, de-duplicates, applies silences, throttles, and then sends out notifications to email, Pagerduty, Slack etc. + +- **Silence** + - A silence in the Alertmanager prevents alerts, with labels matching the silence, from being included in notifications. + +### Exporter + +- **Exporter** + - An exporter is a binary running alongside the application you want to obtain metrics from. The exporter exposes Prometheus metrics, commonly by converting metrics that are exposed in a non-Prometheus format into a format that Prometheus supports. + +- **Collector** + - A collector is a part of an exporter that represents a set of metrics. It may be a single metric if it is part of direct instrumentation, or many metrics if it is pulling metrics from another system. + +### Job + +- **Job** + - A collection of targets with the same purpose, for example monitoring a group of like processes replicated for scalability or reliability, is called a job. + +- **Instance** + - An instance is a label that uniquely identifies a target in a job. + +- **Pushgateway** + - The Pushgateway persists the most recent push of metrics from batch jobs. This allows Prometheus to scrape their metrics after they have terminated. + +### Query + +- **Promdash** + - Promdash was a native dashboard builder for Prometheus. It has been deprecated and replaced by Grafana. + +- **PromQL** + - PromQL is the Prometheus Query Language. It allows for a wide range of operations including aggregation, slicing and dicing, prediction and joins. + + +### Integration + +- **Bridge** + - A bridge is a component that takes samples from a client library and exposes them to a non-Prometheus monitoring system. For example, the Python, Go, and Java clients can export metrics to Graphite. + +- **Client library** + - A client library is a library in some language (e.g. Go, Java, Python, Ruby) that makes it easy to directly instrument your code, write custom collectors to pull metrics from other systems and expose the metrics to Prometheus. + +- **Direct instrumentation** + - Direct instrumentation is instrumentation added inline as part of the source code of a program, using a client library. + +### Remote + +- **Remote Read** + - Remote read is a Prometheus feature that allows transparent reading of time series from other systems (such as long term storage) as part of queries. + +- **Remote Read Endpoint** + - A remote read endpoint is what Prometheus talks to when doing a remote read. + +- **Remote Read Adapter** + - Not all systems directly support remote read. A remote read adapter sits between Prometheus and another system, converting time series requests and responses between them. + +- **Remote Write** + - Remote write is a Prometheus feature that allows sending ingested samples on the fly to other systems, such as long term storage. + +- **Remote Write Endpoint** + - A remote write endpoint is what Prometheus talks to when doing a remote write. + +- **Remote Write Adapter** + - Not all systems directly support remote write. A remote write adapter sits between Prometheus and another system, converting the samples in the remote write into a format the other system can understand. + +--- +refernce +- https://prometheus.io/docs/introduction/glossary/ diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205storage.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205storage.md" new file mode 100644 index 00000000..873b5ed4 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/prometheus/prometheus\342\200\205storage.md" @@ -0,0 +1,56 @@ +--- +title: 'prometheus storage' +lastUpdated: '2024-03-02' +--- + +Prometheus includes a local on-disk time series database, but also optionally integrates with remote storage systems. + +## Local storage + +Prometheus's local time series database stores data in a custom, highly efficient format on local storage. + +### On-disk layout + +Ingested samples are grouped into **blocks of two hours**. Each two-hour block consists of a directory containing a chunks subdirectory containing all the time series samples for that window of time, a metadata file, and an index file (which indexes metric names an labels to time series in the chunks directory). + +The samples in the chunks directory are grouped together into one or more segment files of up to 512MB each by default. When series are deleted via the API, deletion records are stored in separate tombstone files (instead of deleting the data immediately from the chunk segments). + +The current block for inconming samples is kept in memory and is not fully persisted. It is **secured against craches by a write-ahead log files** are stored in the `wal` directory in 128MB segments. + +These files contain raw data that has not yet been compacted; thus they are significantly larger that regular block files. Prometheus will retain a minimum of three write-ahead log files. High-traffic servers may retain more that three WAL files in order to keep at least two hours of raw data. + +A Prometheus server's data directory looks something like this: + +```bash +./data +├── 01BKGV7JBM69T2G1BGBGM6KB12 +│ └── meta.json +├── 01BKGTZQ1SYQJTR4PB43C8PD98 +│ ├── chunks +│ │ └── 000001 +│ ├── tombstones +│ ├── index +│ └── meta.json +├── 01BKGTZQ1HHWHV8FBJXW1Y3W0K +│ └── meta.json +├── 01BKGV7JC0RY8A6MACW02A2PJD +│ ├── chunks +│ │ └── 000001 +│ ├── tombstones +│ ├── index +│ └── meta.json +├── chunks_head +│ └── 000001 +└── wal + ├── 000000002 + └── checkpoint.00000001 + └── 00000000 +``` + +Note that a limitation of local storage is that is not clustered or replicated. Thus, it is not arbitrily scalable or durable in the face of drive or node outages and should be managed like any other single node database. The use of **RAID** is suggested for strage availabiliry, and [snapshots](https://prometheus.io/docs/prometheus/latest/querying/api/#snapshot) are recommanded for backups. With proper architecture, it is possible to retain years of data in local storage. + +Alternatively, external storage may be used via the [remote read/write APIs](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage). Careful evaluation is required for these systems as they vary greatly in durability, performance, and efficiency. + +--- +reference +- https://prometheus.io/docs/prometheus/latest/storage/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/telemetry.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/telemetry.md" new file mode 100644 index 00000000..b6ffd025 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/telemetry.md" @@ -0,0 +1,47 @@ +--- +title: 'telemetry' +lastUpdated: '2024-03-02' +--- + +Telemetry automatically **collects**, **transmits** **and measures data from remote sources**, using sensors and other devices to collect data. It uses communication systems to transmit the data back to a central location. Subsequently, the data is analyzed to monitor and control the remote system. + +Collecting telemetry data is essential for administering and managing various IT infrastructres. This data is used to monotor the performance of different systems and provide actionable insight. Telemetry data helps improve customer experiences and monitor security, application health, quality and performance. + +## Key takeaways + +- Collecting telemetry data is essential for administering and managing varius IP infrastructures. +- Measure telemetry through monitoring tools, which measure everything from server performance to utilization. +- If a hacking attempt or security breach occurs, holisting monitoring helps you determine if critical data was lost or stolen or if systems are compromised. +- A telemetry software vendor helps implement a sound monitoring strategy and ensures that it evolves and becomes more comprehensive over time. + +## How Does Telemetry Work? + +Telemetry for application monitoring is a four-step process: + +### 1. Specifying the Metrics + +This step involves **planning how the telemetry is going to take place**. Metrics are specified for the developer’s process to track to get the performance of their application. The developers need to place a plan of action that if the given hypothesis is validated, what should be happening. It helps prioritize the work. + +Then the developer needs to **specify the lifetime of the telemetric run**. It depends on the cost and returns on the investment of the run. Once you have specified the life, you will give the formula the telemetrics would calculate. The developer needs to mention the necessary data points here. + +### 2. Data Transmission + +Telemetry requires **keeping track** of the user’s activity on your application. That makes it important for the developer to stay aware of the privacy laws and regulations. For that purpose, the best course of action is to get the end-user to sign up for telemetry data analysis. + +To make the process safe and secure, you must also **ensure that data transmission and safety**. It should not be able to easily fall into the hands of hackers or any third-party users. In some situations, when the device is connected to a metered network, it may incur charges. That is why it is better to wait until the device is connected to an unmetered connection. + +### 3. Data Processing + +Once the data is transmitted and stored, it needs to be **processed before it can be used**. For that purpose, various mathematical formulas are run to make data digestible for the analysis. The method adopted for the data processing may depend upon the type of metrics set by the developer. + +For the type of metrics that involve a formula, the step is mainly to apply the data to simplify the results. If required, the results are merged with other data sets. For exploratory metrics, a data mining approach is adopted. In this method, computer algorithms are run to find the hidden pattern in the data. + +### 4. Data Analysis + +The final step of the telemetric process is to perform the analysis of the data. Once the data has been collected and filtered into their respective data sets, the data is analyzed to see the performance of the software. In this step, all the issues and bugs with the application are reported and rectified by the developer. + +Once the developer implements the action plan, the identified problem is removed from the application. It helps improve the user experience of the application making it better and easier for the user. + +--- +reference +- https://www.logicmonitor.com/blog/what-is-telemetry diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/thanos/Rule.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/thanos/Rule.md" new file mode 100644 index 00000000..fa3b9504 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/thanos/Rule.md" @@ -0,0 +1,130 @@ +--- +title: 'Rule' +lastUpdated: '2024-03-02' +--- + +> **NOTE:** It is recommended to keep deploying rules inside the relevant Prometheus servers locally. Use ruler only on specific cases. Read details below why. + +The rule component should in particular not be used to circumvent solving rule deployment properly at the configuration management level. + +The thanos rule command evaluates Prometheus recording and alerting rules against chosen query API via repeated `--query` (or FileSD via `--query.sd`). If more than one query is passed, round robin balancing is performed. + +By default, rule evaluation results are written back to disk in the Prometheus 2.0 storage format. Rule nodes at the same time participate in the system as source store nodes, which means that they expose StoreAPI and upload their generated TSDB blocks to an object store. + +Rule also has a stateless mode which sends rule evaluation results to some remote storages via remote write for better scalability. This way, rule nodes only work as a data producer and the remote receive nodes work as source store nodes. It means that Thanos Rule in this mode does not expose the StoreAPI. + +You can think of Rule as a simplified Prometheus that does not require a sidecar and does not scrape and do PromQL evaluation (no QueryAPI). + +The data of each Rule node can be labeled to satisfy the clusters labeling scheme. High-availability pairs can be run in parallel and should be distinguished by the designated replica label, just like regular Prometheus servers. Read more about Ruler in HA [here](https://thanos.io/tip/components/rule.md/#ruler-ha) + +```bash +thanos rule \ + --data-dir "/path/to/data" \ + --eval-interval "30s" \ + --rule-file "/path/to/rules/*.rules.yaml" \ + --alert.query-url "http://0.0.0.0:9090" \ # This tells what query URL to link to in UI. + --alertmanagers.url "http://alert.thanos.io" \ + --query "query.example.org" \ + --query "query2.example.org" \ + --objstore.config-file "bucket.yml" \ + --label 'monitor_cluster="cluster1"' \ + --label 'replica="A"' +``` + +## Risk + +Ruler has conceptual tradeoffs that might not be favorable for most use cases. The main tradeoff is its dependence on query reliability. For Prometheus it is unlikely to have alert/recording rule evaluation failure as evaluation is local. + +For Ruler the **read path is distributed**, since most likely Ruler is querying Thanos Querier which gets data from remote Store APIs. + +This means that **query failure** are more likely to happen, that’s why clear strategy on what will happen to alert and during query unavailability is the key. + +## Configuring Rules + +Rule files use YAML, the syntax of a rule file is: + +```yaml +groups: + [ - ] +``` + +A simple example rules file would be: + +```yaml +groups: + - name: example + rules: + - record: job:http_inprogress_requests:sum + expr: sum(http_inprogress_requests) by (job) + + +# The name of the group. Must be unique within a file. +name: + +# How often rules in the group are evaluated. +[ interval: | default = global.evaluation_interval ] + +rules: + [ - ... ] +Thanos supports two types of rules which may be configured and then evaluated at regular intervals: recording rules and alerting rules. +``` + +## Recording Rules + +Recording rules allow you to **precompute frequently needed** or **computationally expensive expressions and save** their result as a new set of time series. Querying the precomputed result will then often be much faster than executing the original expression every time it is needed. This is especially useful for dashboards, which need to query the same expression repeatedly every time they refresh. + +Recording and alerting rules exist in a rule group. Rules within a group are run sequentially at a regular interval. + +The syntax for recording rules is: + +```yaml +# The name of the time series to output to. Must be a valid metric name. +record: + +# The PromQL expression to evaluate. Every evaluation cycle this is +# evaluated at the current time, and the result recorded as a new set of +# time series with the metric name as given by 'record'. +expr: + +# Labels to add or overwrite before storing the result. +labels: + [ : ] +``` + +Note: If you make use of recording rules, make sure that you expose your Ruler instance as a store in the Thanos Querier so that the new time series can be queried as part of Thanos Query. One of the ways you can do this is by adding a new `--store ` command-line argument to the Thanos Query command. + +## Alerting Rules + +The syntax for alerting rules is: + +```yaml +# The name of the alert. Must be a valid metric name. +alert: + +# The PromQL expression to evaluate. Every evaluation cycle this is +# evaluated at the current time, and all resultant time series become +# pending/firing alerts. +expr: + +# Alerts are considered firing once they have been returned for this long. +# Alerts which have not yet fired for long enough are considered pending. +[ for: | default = 0s ] + +# Labels to add or overwrite for each alert. +labels: + [ : ] + +# Annotations to add to each alert. +annotations: + [ : ] +``` + +## Partial Response + +See this + + +--- +reference +- https://thanos.io/tip/components/rule.md/ + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/thanos/Thanos.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/thanos/Thanos.md" new file mode 100644 index 00000000..ba57eb60 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Monitoring/thanos/Thanos.md" @@ -0,0 +1,38 @@ +--- +title: 'Thanos' +lastUpdated: '2024-03-02' +--- + +Thanos is a set of components that can be composed into a highly available metric system with unlimited storage capacity, which can be added seamlessly on top of existing Prometheus deployments that included in CNCF Incubating project. + +Thanos leverages the Prometheus 2.0 storage format to cost-efficiently store historical metric data in any object storage while retaining fast query latencies. Additionally, it provides a global query view across all Prometheus installations and can merge data from Prometheus HA pairs on the fly. + +Concretely the aims of the project are: + +1. Global query view of metrics. +2. Unlimited retention of metrics. +3. High availability of components, including Prometheus. + +## Features + +- Global querying view across all connected Prometheus servers +- Deduplication and merging of metrics collected from Prometheus HA pairs +- Seamless integration with existing Prometheus setups +- Any object storage as its only, optional dependency +- Downsampling historical data for massive query speedup +- Cross-cluster federation +- Fault-tolerant query routing +- Simple gRPC "Store API" for unified data access across all metric data +- Easy integration points for custom metric providers + +## Architecture + +Deployment with Sidecar for Kubernetes: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/cad6a570-e180-40cd-b161-11af7b0e6543) + +Deployment with Receive in order to scale out or implement with other remote write compatible sources: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/aef440a3-a1e7-43f3-9faa-1acf22603a41) + + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Contour.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Contour.md" new file mode 100644 index 00000000..e347d9d2 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Contour.md" @@ -0,0 +1,198 @@ +--- +title: 'Contour' +lastUpdated: '2024-03-02' +--- + +**Contour** is an Envoy based ingress controller. + +And it is an open source Kubernetes ingress controller providing the control plane for the Envoy edge and service proxy.​ + +Contour supports dynamic configuration updates and multi-team ingress delegation out of the box while maintaining a lightweight profile. + +## Getting Started with Contour + +Let's look at three ways to install Contour + +- using Contour’s example YAML +- using the Helm chart for Contour +- using the Contour gateway provisioner (beta) + +## Install Contour and Envoy + +### 1. YAML + +```bash +$ kubectl apply -f https://projectcontour.io/quickstart/contour.yaml +``` + +Verify the Contour pods are ready by running the following: + +```bash +$ kubectl get pods -n projectcontour -o wide +``` + +You should see the following: + +- 2 Contour pods each with status Running and 1/1 Ready +- 1+ Envoy pod(s), each with the status Running and 2/2 Ready + +### 2. Helm + +This option requires Helm to be installed locally. + +Add the bitnami chart repository (which contains the Contour chart) by running the following: + +```bash +$ helm repo add bitnami https://charts.bitnami.com/bitnami +``` + +Install the Contour chart by running the following: + +```bash +$ helm install my-release bitnami/contour --namespace projectcontour --create-namespace +``` + +Verify Contour is ready by running: + +```bash +$ kubectl -n projectcontour get po,svc +``` + +You should see the following: + +- 1 instance of pod/my-release-contour-contour with status Running and 1/1 Ready +- 1+ instance(s) of pod/my-release-contour-envoy with each status Running and 2/2 Ready +- 1 instance of service/my-release-contour +- 1 instance of service/my-release-contour-envoy + +### 3. Contour Gateway Provisioner (beta) + +The Gateway provisioner watches for the creation of Gateway API (`Gateway`) resources, and dynamically provisions Contour+Envoy instances based on the `Gateway's` spec. + +Note that although the provisioning request itself is made via a Gateway API resource (`Gateway`), this method of installation still allows you to use any of the supported APIs for defining virtual hosts and routes: `Ingress`, `HTTPProxy`, or Gateway API’s `HTTPRoute` and `TLSRoute`. + +In fact, below code will use an Ingress resource to define routing rules, even when using the Gateway provisioner for installation. + +Deploy the Gateway provisioner: + +```bash +$ kubectl apply -f https://projectcontour.io/quickstart/contour-gateway-provisioner.yaml +``` + +Verify the Gateway provisioner deployment is available: + +```bash +$ kubectl -n projectcontour get deployments +NAME READY UP-TO-DATE AVAILABLE AGE +contour-gateway-provisioner 1/1 1 1 1m +``` + +Create a GatewayClass: + +```bash +kubectl apply -f - < The Helm install configures Contour to filter Ingress and HTTPProxy objects based on the contour IngressClass name. If using Helm, ensure the Ingress has an ingress class of contour with the following: + + +```bash +$ kubectl patch ingress httpbin -p '{"spec":{"ingressClassName": "contour"}}' +``` + +Now we’re ready to send some traffic to our sample application, via Contour & Envoy. + +Note, for simplicity and compatibility across all platforms we’ll use kubectl port-forward to get traffic to Envoy, but in a production environment you would typically use the Envoy service’s address. + +Port-forward from your local machine to the Envoy service: + +```bash +# If using YAML +$ kubectl -n projectcontour port-forward service/envoy 8888:80 + +# If using Helm +$ kubectl -n projectcontour port-forward service/my-release-contour-envoy 8888:80 + +# If using the Gateway provisioner +$ kubectl -n projectcontour port-forward service/envoy-contour 8888:80 +``` + +In a browser or via curl, make a request to `http://local.projectcontour.io:8888 `(`local.projectcontour.io` is a public DNS record resolving to 127.0.0.1 to make use of the forwarded port). You should see the httpbin home page. + +Congratulations, you have installed Contour, deployed a backend application, created an Ingress to route traffic to the application, and successfully accessed the app with Contour! + +--- +reference +- https://projectcontour.io/getting-started/ +- https://projectcontour.io/docs/v1.10.0/ +- https://github.com/projectcontour/contour \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Contour\342\200\205CRD\342\200\205\354\204\244\354\271\230.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Contour\342\200\205CRD\342\200\205\354\204\244\354\271\230.md" new file mode 100644 index 00000000..e45c52ea --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Contour\342\200\205CRD\342\200\205\354\204\244\354\271\230.md" @@ -0,0 +1,42 @@ +--- +title: 'Contour CRD 설치' +lastUpdated: '2023-08-10' +--- +- Install Contour: + - Use the appropriate command for your cluster manager to install Contour. For example, using kubectl for Kubernetes: + +```bash +kubectl apply -f https://projectcontour.io/quickstart/contour.yaml +``` + - This command downloads and applies the Contour manifest file, which includes the necessary resources for Contour's installation. + +- Verify Contour installation: + + - Check that the Contour resources are running and ready: + +```bash +kubectl get pods -n projectcontour +``` + + - Ensure that all the Contour pods are in a running state. + +- Install the HTTPProxy resource: + + - Download and apply the HTTPProxy CRD manifest: + +```bash +kubectl apply -f https://projectcontour.io/quickstart/http-proxy.yaml +``` + + - This manifest installs the HTTPProxy CRD, which enables you to define HTTP routing rules using the HTTPProxy resource. + +- Verify HTTPProxy installation: + + - Check that the HTTPProxy CRD is successfully installed: + +```bash +kubectl get crd +``` + +Look for projectcontour.io/HTTPProxy in the list of available CRDs. +Once you have completed these steps, you should have Project Contour installed with the HTTPProxy resource available for use. You can now create HTTPProxy objects to define your HTTP routing rules and configure your application's ingress behavior. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Reverse\342\200\205Proxy\342\200\205vs.\342\200\205Ingress\342\200\205Controller\342\200\205vs.\342\200\205API\342\200\205Gateway.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Reverse\342\200\205Proxy\342\200\205vs.\342\200\205Ingress\342\200\205Controller\342\200\205vs.\342\200\205API\342\200\205Gateway.md" new file mode 100644 index 00000000..9f76ba2c --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/Reverse\342\200\205Proxy\342\200\205vs.\342\200\205Ingress\342\200\205Controller\342\200\205vs.\342\200\205API\342\200\205Gateway.md" @@ -0,0 +1,75 @@ +--- +title: 'Reverse Proxy vs. Ingress Controller vs. API Gateway' +lastUpdated: '2023-07-09' +--- +## Reverse Proxy + +You can think of the reverse proxy as that old-school phone operator, you know, back when there used to be call centers and phone operators. Back then, when someone was picking up the phone, they were connected to a call center, the caller stated the name and the address of the person they wanted to call, and the phone operator connected them. A reverse proxy does a similar job by receiving user requests and then forwarding said requests to the appropriate server, as you can see in the diagram below. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/60b223c5-7550-4e37-b31e-1940f625933c) + +Reverse proxies have a very simple function — they are used in front of an application or group of applications and act as the middleman between the client and the application. + +As I mentioned earlier, reverse proxies route user requests to the appropriate server, assuming that you are utilizing multiple servers. So, naturally, those of you using a single server are probably wondering whether or not it makes sense for you to even implement a reverse proxy. + +In fact, reverse proxies are useful even in single-server scenarios where you can take advantage of features like rate limiting, IP filtering and access control, authentication, request validation, and caching. + +## Ingress Controller + +In a nutshell, an ingress controller is a reverse proxy for the Kubernetes universe. It acts as a reverse proxy, routing traffic from the outside world to the correct service within a Kubernetes cluster, and allows you to configure an HTTP or HTTPS load balancer for the said cluster. + +To better understand this, let’s take a step back first and look at the Ingress itself. A Kubernetes Ingress is an API object that determines how incoming traffic from the internet should reach the internal cluster Services, which then in turn send requests to groups of Pods. The Ingress itself has no power over the system — it is actually a configuration request for the ingress controller. + +The ingress controller accepts traffic from outside the Kubernetes platform and load balances it to Pods running inside the platform, this way adding a layer of abstraction to traffic routing. Ingress controllers convert configurations from Ingress resources into routing rules recognized and implemented by reverse proxies. + +Ingress controllers are used to expose multiple services from within your Kubernetes cluster to the outside world, using a single endpoint — for example, a DNS name or IP address — to access them. Specifically, ingress controllers are used to: + +- Expose multiple services under a single DNS name +- Implement path-based routing, where different URLs map to different services +- Implement host-based routing, where different hostnames map to different services +- Implement basic authentication or other access control methods for your applications +- Implement rate limiting for your applications +- Offload SSL/TLS termination from your applications to the ingress controller + +## API gateway + +Deployed at the edge of your infrastructure, an API gateway acts as a single entry point that routes client API requests to your backend microservices. Essentially, an API gateway is a reverse proxy handling incoming user requests and, although it includes many of the functionalities commonly found in reverse proxies, there is a key difference between the two. + +Contrary to reverse proxies, API gateways have the ability to address cross-cutting, or system-wide, concerns. Concerns refer to the parts of your system's architecture that have been branched based on its functionality. Cross-cutting concerns are concerns that are shared among a number of different system components or APIs and include, among others, configuration management, security, auditing, exception management, and logging. + +API gateways are commonly used in architectures where you need to expose multiple microservices or serverless functions to the outside world through a set of APIs, and they handle a number of tasks. On top of the functions we already saw as part of a typical reverse proxy, API gateways can handle: + +- **Load balancing**: Distributing incoming traffic across multiple servers to improve performance and availability. +- **Rate limiting**: Matching the flow of traffic to your infrastructure’s capacity. +- **Access control**: Adding an extra layer of security by authenticating incoming connections before they reach the web servers, and by hiding the internal IP addresses and network structure of the web servers from external clients. +- **SSL/TLS termination**: Offloading the task of handling SSL/TLS connections from the web servers to the reverse proxy, allowing the web servers to focus on handling requests. +- **Caching**: Improving performance by caching frequently-requested content closer to the client. +- **Request/response transformation**: Modifying incoming requests or outgoing responses to conform to specific requirements, such as adding or removing headers, compressing/decompressing, and encrypting/decrypting content. +- **Logging and monitoring**: Collecting API usage and performance data. + +API Gateways also come with a few extra handy functionalities, namely service discovery, circuit breaker, and request aggregation. + +## Reverse proxy vs. ingress controller vs. API gateway + +- An ingress controller does the same job as a reverse proxy or an API gateway when it comes to handling incoming traffic and routing it to the appropriate server/Service. However, the ingress controller operates at a different level of the network stack. + +- Ingress controller operates in a Kubernetes environment. In that sense, the ingress controller is a specific type of reverse proxy designed to operate within Kubernetes clusters. + +- The ingress controller sits at the edge of the cluster listening for incoming traffic, and then routes it to the appropriate Kubernetes Service within the cluster, based on the rules defined in the Ingress resource, as we saw earlier. + +- The API gateway sits at the edge of your infrastructure. API gateway is a specific type of reverse proxy, too — a reverse proxy on "steroids” if you will — while the service mesh is a network proxy tailored for microservices + +## Usecase + +- You can set up a reverse proxy in front of your ingress controller and API gateway to handle SSL/TLS termination, caching, load balancing, and request/response transformation. + +- You can set up an ingress controller to handle the routing of incoming traffic from your reverse proxy to the appropriate Kubernetes Service within your cluster. + +- You can set up an API gateway to handle authentication, rate limiting, and request/response transformation for your microservices within the cluster. + +- You can set up a service mesh to handle internal communication (i.e. load balancing, traffic shaping, and service discovery) between your microservices. + +--- +reference +- https://projectcontour.io/docs/v1.18.0/config/fundamentals/ +- https://traefik.io/blog/reverse-proxy-vs-ingress-controller-vs-api-gateway/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/Envoy.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/Envoy.md" new file mode 100644 index 00000000..eff5c764 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/Envoy.md" @@ -0,0 +1,199 @@ +--- +title: 'Envoy' +lastUpdated: '2024-03-02' +--- + +Envoy is a high performance C++ distributed proxy designed for single services and applications, as well as a communication bus and **“universal data plane”** designed for large microservice **“service mesh”** architectures. + +Built on the learnings of solutions such as NGINX, HAProxy, hardware load balancers, and cloud load balancers, Envoy runs alongside every application and abstracts the network by providing common features in a platform-agnostic manner. + +When all service traffic in an infrastructure flows via an Envoy mesh, it becomes easy to visualize problem areas via consistent observability, tune overall performance, and add substrate features in a single place. + +## Features + +- OUT OF PROCESS ARCHITECTURE + - Envoy is a self contained, high performance server with a small memory footprint. It runs alongside any application language or framework. +- HTTP/2 AND GRPC SUPPORT + - Envoy has first class support for HTTP/2 and gRPC for both incoming and outgoing connections. It is a transparent HTTP/1.1 to HTTP/2 proxy. +- ADVANCED LOAD BALANCING + - Envoy supports advanced load balancing features including automatic retries, circuit breaking, global rate limiting, request shadowing, zone local load balancing, etc. +- APIS FOR CONFIGURATION MANAGEMENT + - Envoy provides robust APIs for dynamically managing its configuration. +- OBSERVABILITY + - Deep observability of L7 traffic, native support for distributed tracing, and wire-level observability of MongoDB, DynamoDB, and more. + +## Terminology + +Envoy uses the following terms through its codebase and documentation: + +- **Cluster**: a logical service with a set of endpoints that Envoy forwards requests to. +- **Downstream**: an entity connecting to Envoy. This may be a local application (in a sidecar model) or a network node. In non-sidecar models, this is a remote client. +- **Endpoints**: network nodes that implement a logical service. They are grouped into clusters. Endpoints in a cluster are upstream of an Envoy proxy. +- **Filter**: a module in the connection or request processing pipeline providing some aspect of request handling. An analogy from Unix is the composition of small utilities (filters) with Unix pipes (filter chains). +- **Filter chain**: a series of filters. +- **Listeners**: Envoy module responsible for binding to an IP/port, accepting new TCP connections (or UDP datagrams) and orchestrating the downstream facing aspects of request processing. +- **Upstream**: an endpoint (network node) that Envoy connects to when forwarding requests for a service. This may be a local application (in a sidecar model) or a network node. In non-sidecar models, this corresponds with a remote backend. + +## Network topology + +How a request flows through the components in a network (including Envoy) depends on the network’s topology. Envoy can be used in a wide variety of networking topologies. We focus on the inner operation of Envoy below, but briefly we address how Envoy relates to the rest of the network in this section. + +Envoy originated as a service mesh sidecar proxy, factoring out load balancing, routing, observability, security and discovery services from applications. In the service mesh model, requests flow through Envoys as a gateway to the network. Requests arrive at an Envoy via either ingress or egress listeners: + +- Ingress listeners take requests from other nodes in the service mesh and forward them to the local application. Responses from the local application flow back through Envoy to the downstream. + +- Egress listeners take requests from the local application and forward them to other nodes in the network. These receiving nodes will also be typically running Envoy and accepting the request via their ingress listeners. + +image + +Envoy is used in a variety of configurations beyond the service mesh. For example, it can also act as an internal load balancer: + +image + +Or as an ingress/egress proxy on the network edge: + +image + +Envoy may be configured in multi-tier topologies for scalability and reliability, with a request first passing through an edge Envoy prior to passing through a second Envoy tier: + +image + +In all the above cases, a request will arrive at a specific Envoy via TCP, UDP or Unix domain sockets from downstream. Envoy will forward requests upstream via TCP, UDP or Unix domain sockets. We focus on a single Envoy proxy below. + +## Configuration + +Envoy is a very extensible platform. This results in a combinatorial explosion of possible request paths, depending on: + +- L3/4 protocol, e.g. TCP, UDP, Unix domain sockets. +- L7 protocol, e.g. HTTP/1, HTTP/2, HTTP/3, gRPC, Thrift, Dubbo, Kafka, Redis and various databases. +- Transport socket, e.g. plain text, TLS, ALTS. +- Connection routing, e.g. PROXY protocol, original destination, dynamic forwarding. +- Authentication and authorization. +- Circuit breakers and outlier detection configuration and activation state. +- Many other configurations for networking, HTTP, listener, access logging, health checking, tracing and stats extensions. + +It’s helpful to focus on one at a time, so this example covers the following: +- An HTTP/2 request with TLS over a TCP connection for both downstream and upstream. +- The HTTP connection manager as the only network filter. +- A hypothetical CustomFilter and the router filter as the HTTP filter chain. +- Filesystem access logging. +- Statsd sink. +- A single cluster with static endpoints. + +We assume a static bootstrap configuration file for simplicity: + +```yml +static_resources: + listeners: + # There is a single listener bound to port 443. + - name: listener_https + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 443 + # A single listener filter exists for TLS inspector. + listener_filters: + - name: "envoy.filters.listener.tls_inspector" + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector + # On the listener, there is a single filter chain that matches SNI for acme.com. + filter_chains: + - filter_chain_match: + # This will match the SNI extracted by the TLS Inspector filter. + server_names: ["acme.com"] + # Downstream TLS configuration. + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: {filename: "certs/servercert.pem"} + private_key: {filename: "certs/serverkey.pem"} + filters: + # The HTTP connection manager is the only network filter. + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + use_remote_address: true + http2_protocol_options: + max_concurrent_streams: 100 + # File system based access logging. + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/var/log/envoy/access.log" + # The route table, mapping /foo to some_service. + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["acme.com"] + routes: + - match: + path: "/foo" + route: + cluster: some_service + # CustomFilter and the HTTP router filter are the HTTP filter chain. + http_filters: + # - name: some.customer.filter + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: some_service + # Upstream TLS configuration. + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + load_assignment: + cluster_name: some_service + # Static endpoint assignment. + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 10.1.2.10 + port_value: 10002 + - endpoint: + address: + socket_address: + address: 10.1.2.11 + port_value: 10002 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: + max_concurrent_streams: 100 + - name: some_statsd_sink + # The rest of the configuration for statsd sink cluster. +# statsd sink. +stats_sinks: +- name: envoy.stat_sinks.statsd + typed_config: + "@type": type.googleapis.com/envoy.config.metrics.v3.StatsdSink + tcp_cluster_name: some_statsd_sink +``` + +## High level architecture + +The request processing path in Envoy has two main parts: + +- **Listener subsystem** which handles downstream request processing. It is also responsible for managing the downstream request lifecycle and for the response path to the client. The downstream HTTP/2 codec lives here. + +- **Cluster subsystem** which is responsible for selecting and configuring the upstream connection to an endpoint. This is where knowledge of cluster and endpoint health, load balancing and connection pooling exists. The upstream HTTP/2 codec lives here. + +The two subsystems are bridged with the HTTP router filter, which forwards the HTTP request from downstream to upstream. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/6505b212-8fe5-4836-ae65-22e2058eb1cd) + +--- +reference +- https://www.envoyproxy.io/ +- https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/LDS.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/LDS.md" new file mode 100644 index 00000000..ae1522f1 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/LDS.md" @@ -0,0 +1,22 @@ +--- +title: 'LDS' +lastUpdated: '2024-03-02' +--- + +The listener discovery service (LDS) is an optional API that Envoy will call to dynamically fetch listeners. Envoy will reconcile the API response and add, modify, or remove known listeners depending on that is required. + +The semantics of listener updates are as follows: + +- Every listener must have a unique name. If a name is not provided, Envoy will create a UUID. Listeners that are to be synamically updated should have a unique name supplied by the management server. + +- When a listener is added, it will be "warmed" before taking traffic For example, if the listener references an [RDS](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/rds#config-http-conn-man-rds) configuration, that configuration will resolved and fetched before the listener is moved to "active". + +- Listeners are effetively constant once created. Thus, when a listener is updated, an entirely new listener is created (if the listener's address is unchanged, the new one uses the same listen socker). This listener is removed, the old listener will be placed into a "draining" state much like when the entire server is drained for restart. Connections woened by the listener will be grace fully closed (if possible) for some period of time before the listener is removed and any remaining connections are closed. The drain time is set via the `--drain-time-s` option. + +- When a tcp listener is updated, if the enw listener contains a subset of filter chains in the old listener, the connections owned by these overlapping filter chains remain open. Only the connections owned by the removed filter chains will be drained following the above pattern. Note that is any global listener attributes are changed, the entire listener (and all filter chains) are drained similar to removal above. See [filter chain only update](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/listeners/listener_filters#filter-chain-only-update) for detailed rules to reason about the impacted filter chains. + +> Any listeners that are statically defined within the Envoy configuration cannot be modified or removed via the LDS API. + +--- +reference +- https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/lds#config-listeners-lds \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/xDS\342\200\205configuration.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/xDS\342\200\205configuration.md" new file mode 100644 index 00000000..00329711 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/envoy/xDS\342\200\205configuration.md" @@ -0,0 +1,71 @@ +--- +title: 'xDS configuration' +lastUpdated: '2024-03-02' +--- + +- Envoy is architected such that different types of configuration management approaches are possible. The approach taken in a deployment will be dependent on the needs of the implementor. +- Simple deployments are possible with a fully static configuration. More complicated deployments can incrementally add more complex dynamic configuration, the downside being that the implementor must provide one or more external gRPC/REST based configuration provider APIs. +- These APIs are collectively known as "xDS(* [Discovery service](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/service_discovery#arch-overview-service-discovery))". Let's explore overview of the options currently available. + +### Fully static + +- In a fully static configuration, the implementor provides a set of listeners (and filter chains), clusters, etc. Dynamic host discovery is only possible via DNS based service discovery. Configuration reloads must take place via the built in hor restart mechanism. + +- Though simplistic, fairly complicated deployments can be created using static configurations and graceful hot restarts. + +### EDS + +- The [Endpoint Descovery Service(EDS) API](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/service_discovery#arch-overview-service-discovery-types-eds) provides a more advanced mechanism by which Envoy can **discover members of an upstream cluster.** +- Layered on top of a static configuration, EDS allows an Envoy deployment to circumvent the limitations of DNS (maximum records in a response, etc.) as well as consume more information used in load balancing and routing (e.g., canary status, zone, etc.) + +### CDS + +- The [Cluster Discovery Service(CDS)](https://www.envoyproxy.io/docs/envoy/latest/configuration/upstream/cluster_manager/cds#config-cluster-manager-cds) API layers on a mechanism by which Envoy can discover **upstream clusters used during routing**. +- Envoy will gracefully add, update, and remove clusters as specified by the API. This API allows implementors to build a topology in which Envoy does not need to be aware of all upstream clusters at initial configuration time. Typically, when doing HTTP routing along with CDS (but without route discovery service), implementors will make use of the router's ability to forward requests to a cluster specified in an [HTTP request header](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-cluster-header). +- Although it is possible to use CDS without EDS by specifying fully static clusters, It is recommended still using the EDS API for clusters specified via CDS. Internally, when a cluster drained and reconnedted. EDS does not suffer from this limitation. When hosts are added and removed via EDS, the existing hosts in the cluster are unaffected. + +### RDS + +- The [Route Discovery Service(RDS) API](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/rds#config-http-conn-man-rds) layers on a mechanism by which Envoy can **discover the entire route configuration** for an HTTP connection manager filter at runtime. +- The route configuration will be gracefully swapped in without affecting existing requests. This API, when used alongside EDS and CDS, allows implementors to build a complex routing topology (traffic shifting, blue/green deployment, etc). + +### VHDS + +- The [Virtual Host Discovery Service](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/vhds#config-http-conn-man-vhds) allows **the virtual hosts belonging to a route configuration** to be requested as needed separately from the route configuration itself. +- This API is typically used in deployments in which there are a large number of virtual hosts in a route configuration. + +### SRDS + +- The [Scoped Rout Discovery Service (SRDS) API](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_routing#arch-overview-http-routing-route-scope) allows a **route table to be broken up into multiple pieces**. This API is typically used in deployments of HTTP routing with massive route tables in which simple linear searches are not feasible. + +### LDS + +- The [Listener Discovery Service (LDS) API](https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/lds#config-listeners-lds) layers on a mechanisom by which Envoy can discover **entire listeners at runtime**. This includes all filter stacks, up to and including HTTP filters with embedded references to RDS. +- Adding LDS into the mix allows almost every adpect of Envoy to be dynamically configured. Hot restart should only be required for very rare configuration changes (admin, tracing draver, etc.), certificate rotation, or binary updates. + +### SDS + +- The [Secret Discovery Service(SDS) API](https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret#config-secret-discovery-service) layers on a mechanism by which Envoy can **discover cryptographic secrets** (certificate + private key, TLS session ticket keys) for its listeners, as well as configuration of peer certificate validation logic (trusted root certs, revocations, etc.) + +### RTDS + +- The [RunTime Discovery Service (RTDS) API](https://www.envoyproxy.io/docs/envoy/latest/configuration/operations/runtime#config-runtime-rtds) allows runtime layers to be fetched via an xDS API. This may be favorable to, or augmented by, file system layers. + +### ECDS + +- The [Extension Config Discovery Service (ECDS) API](https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/extension#config-overview-extension-discovery) allows **extension configurations** (e.g. HTTP filter configuration) to be served independently from the listener. This is useful when building systems that are more appropriately split from the primary control plane such as WAF, fault testing, etc. + +### Aggregated xDS (ADS) + +EDS, CDS, etc are each separate services, with different REST/gRPC service names, e.g. StreamListeners, StreamSecrets. For users looking to enforce the order in which resources of different types reach Envoy, there is aggregated xDS, a single gRPC service that carries all resource types in a single gRPC stream. (ADS is only supported by gRPC). [More details about ADS.](https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/xds_api#config-overview-ads) + +### Delta gRPC xDS + +Standard xDS is "state-of-the-world": every update must contain every resource, with the absence of a resource from an update implying that the resoruce is gone. Envoy supports a "delta" variant of xDS (including ADS), where updated only contain resources added//changed/removed. Delta xDS is a new protocol, with request/response APIs different from SotW. [More details about delta.](https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/xds_api#config-overview-delta) + +### xDS TTL +Certain xDS updates might want to set a TTL to guard against control plane unavailability, read more [here](https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/xds_api#config-overview-ttl). + +--- +reference +- https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/NGINX\342\200\205Ingress\342\200\205Basic\342\200\205Auth.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/NGINX\342\200\205Ingress\342\200\205Basic\342\200\205Auth.md" new file mode 100644 index 00000000..bd9cb0bd --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/NGINX\342\200\205Ingress\342\200\205Basic\342\200\205Auth.md" @@ -0,0 +1,120 @@ +--- +title: 'NGINX Ingress Basic Auth' +lastUpdated: '2024-03-02' +--- + +NGINX Ingress에서 설정을 해주면 어플리케이션 레벨에서, 혹은 Ingress 레벨에서 서비스 인증과정을 거치도록 할 수 있다. ingress를 통해 인증을 설정하는 방법을 두가지 알아보자. + +![image](https://user-images.githubusercontent.com/81006587/215693937-ad54131b-6e70-448e-b137-b58d222f7579.png) + +# Static User + +> https://kubernetes.github.io/ingress-nginx/examples/auth/basic/ + +Static User는 미리 basic auth로 인증할 유저 리스트를 생성하고 해당 리스트에 포함된 인원만 인증될 수 있게 하는 방법이다. 별다른 추가 작업 없이 사용자 인증을 할 수 있는 장점이 있는 반해 동적으로 사용자를 추가/삭제하지 못한다는 단점이 있다. + +#### 1. auth 파일 생성 + +먼저 `htpasswd`를 통해 basic auth 사용자 파일을 생성한다. + +```yml +# htpasswd 설치 +sudo apt-get install apache2-utils + +# auth 파일에 bar라는 비밀번호를 가진 foo라는 사용자를 생성 +$ htpasswd -cb auth foo bar +``` + +#### 2. auth 파일을 이용한 Secret 생성 + +생성한 htpasswd를 k8s에 적용할 수 있는 secret으로 만든다. + +```yml +# basic-auth라는 secret 생성 +$ kubectl create secret generic basic-auth --from-file=auth + +$ kubectl get secret basic-auth -o yaml +# apiVersion: v1 +# data: +# auth: Zm9vOiRhcHIxJE9GRzNYeWJwJGNrTDBGSERBa29YWUlsSDkuY3lzVDAK +# kind: Secret +# metadata: +# name: basic-auth +# namespace: default +# type: Opaque +``` + +#### 3. Ingress 생성 + +서비스의 Ingress 설정시 annotations 프로퍼티에 다음과 같은 설정을 해준다. + +```bash +$ kubectl create -f ingress.yml +ingress "external-auth" created + +$ kubectl get ing external-auth +NAME HOSTS ADDRESS PORTS AGE +external-auth external-auth-01.sample.com 172.17.4.99 80 13s +``` + +```yml +# auth-ingress.yaml +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: ingress-with-auth + annotations: + # 인증 방법 설정: basic auth + nginx.ingress.kubernetes.io/auth-type: basic + # basic auth 사용자가 들어있는 secret 설정 + nginx.ingress.kubernetes.io/auth-secret: basic-auth + # 인증 요청시 반환할 메세지 설정 + nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo' +spec: + rules: + - host: foo.bar.com + http: + paths: + - path: / + backend: + serviceName: http-svc + servicePort: 80 +``` + +# External Basic Auth + +> https://kubernetes.github.io/ingress-nginx/examples/auth/external-auth/ + +External Basic Auth는 외부 Basic Auth 서비스를 이용하여 인증을 하는 방식이다. 사용자가 직접 개발한 custom authentication 서버에 연동할 수도 있고, 외부 LDAP 서버를 통하여 인증 체계를 구성하는 등 유연하게 사용할 수 있다. + +```yml +# ingress.yml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/auth-url: https://httpbin.org/basic-auth/user/passwd + creationTimestamp: 2023-01-31T13:50:35Z + generation: 1 + name: external-auth + namespace: default + resourceVersion: "2068378" + selfLink: /apis/networking/v1/namespaces/default/ingresses/external-auth + uid: 5c388f1d-8970-11e6-9004-080027d2dc94 +spec: + rules: + - host: external-auth-01.sample.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: http-svc + port: + number: 80 +status: + loadBalancer: + ingress: + - ip: 172.17.4.99 +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/location\342\200\205block.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/location\342\200\205block.md" new file mode 100644 index 00000000..5307cb6a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/location\342\200\205block.md" @@ -0,0 +1,94 @@ +--- +title: 'location block' +lastUpdated: '2024-03-02' +--- + +location 블록은 Nginx에서 정의되어 있는 웹사이트의 특정 URL 을 조작하는데 사용되는 블록이다. Server 블록마다 Location 을 지정해줄 수 있으며, Location 을 여러 번 정의할 수 있다. + +location은 URI 경로의 일부인 prefix string이거나 정규식 표현이 될 수 있다. + +### 예시 + +```bash +// nginx.conf +server { + location /images/ { + root /data; + } + + location / { + proxy_pass http://www.example.com; + } +} +``` + +위와 같이 server 내부에 표시해준다. 위의 코드는 + +- 첫 번째 location context와 일치하는 패턴은 /data 디렉토리에서 파일들을 보여주고 +- 두 번째 location context와 일치하는 패턴은 www.example.com 도메인으로 요청을 전송하도록 한다. + +### 문법 + +location 문법은 위치 조정 부호와 패턴으로 구성된다. + +위치 조정 부호는 특수 기호로 지정하고 패턴은 정규식 문자열을 사용할 수 있다. 부호는 선택사항이고, 넣지 않는 경우엔 해당 문자로 시작하는 url을 뜻하게 된다. + +```js +Syntax: location [ = | ~ | ~* | ^~ ] [pattern] { ... } +location @name { ... } +Default: — +Context: server, location +``` + +```bash +# 모든 요청과 일치 +location / { + [ configuration A ] +} + +# 정확하게 일치 +# matches with /images +# does not matches with /images/index.html or /images/ +location = /images { + [ configuration B ] +} + +# 지정한 패턴으로 시작 +# /static/ 으로 시작하는 요청과 일치 +location /static/ { + [ configuration C ] +} + +# 지정한 패턴으로 시작, 패턴이 일치 하면 다른 패턴 탐색 중지(정규식 아님) +# matches with /images or /images/logo.png +location ^~ /images { + [ configuration D ] +} + +# 정규식 표현 일치 - 대소문자 구분 +location ~ \.(gif|jpg|jpeg)$ { + [ configuration E ] +} + +# 정규식 표현 일치 - 대소문자 구분 안함 +location ~* \.(gif|jpg|jpeg)$ { + [ configuration F ] +} +``` + +### 우선순위 + +다수의 Location 블록이 정의되어 있을 경우 Location 블록에 정의되어 있는 패턴에 따라 우선순위가 달라진다. + +```md +1. = : 정확하게 일치 +2. ^~ : 앞부분이 일치 +3. ~ : 정규식 대/소문자 일치 +4. ~* : 대/소문자를 구분하지 않고 정규식 일치 +5. / : 하위 일치 +``` + +--- +참고 +- http://nginx.org/en/docs/beginners_guide.html +- https://www.digitalocean.com/community/tutorials/nginx-location-directive \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205certbot.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205certbot.md" new file mode 100644 index 00000000..c53da9ed --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205certbot.md" @@ -0,0 +1,144 @@ +--- +title: 'nginx certbot' +lastUpdated: '2024-03-02' +--- + +nginx에서 https를 적용하기 위해 certbot과 Let's encrypt를 사용해보자. + +일단 nginx를 아래와 같이 설정해준다. + +```bash +server { + listen 80; + server_name your.domain.com; + + location / { + proxy_pass http://192.168.XXX.XXX; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + } +} +``` + +1. certbot 설치 + +```bash +wget https://dl.eff.org/certbot-auto +``` + +snapd를 이용하여 설치할 수도 있다. +```bash +# certbot 설치 +$ sudo snap install --classic certbot + +# certbot 명령을 로컬에서 실행할 수 있도록 snap의 certbot 파일을 로컬의 cerbot과 링크(연결) 시켜준다. -s 옵션은 심볼릭링크를 하겠다는 것. +$ ln -s /snap/bin/certbot /usr/bin/certbot +``` + +2. cerbot으로 nginx 설정 + +아래 명령을 수행하고, 알맞은 설정값을 입력해준다. domain을 입력하라는 질문에서는 본인이 사용할 Domain을 입력해주면 된다. + +```bash +$ sudo certbot --nginx +``` + +위 명령을 잘 수행하고 아래처럼 뜨면 잘 된 것이다. + +```bash +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Your existing certificate has been successfully renewed, and the new certificate +has been installed. + +The new certificate covers the following domains: https://domain.com # https가 설정된 도메인을 알려준 것. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Subscribe to the EFF mailing list (email: woorimprog@gmail.com). + +IMPORTANT NOTES: + - Congratulations! Your certificate and chain have been saved at: + /etc/letsencrypt/live/domain.com/fullchain.pem # 공개키 경로이므로 기억해두자. + Your key file has been saved at: + /etc/letsencrypt/live/domain.com/privkey.pem # 비밀키 경로이므로 기억해두자. + Your certificate will expire on 2021-08-15. To obtain a new or + tweaked version of this certificate in the future, simply run + certbot again with the "certonly" option. To non-interactively + renew *all* of your certificates, run "certbot renew" + - If you like Certbot, please consider supporting our work by: + + Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate + Donating to EFF: https://eff.org/donate-le +``` + +이 과정을 거치면 Certbot은 Let's Encrypt를 통해 자동으로 SSL 인증서를 발급해온다. + +위 출력을 잘보면 https가 적용된 도메인과 공개키와 비밀키의 경로를 알려준다. 정리하자면 아래와 같다. + +```bash +1. https가 설정된 도메인 +https://domain.com + +1. 공개키 경로 +/etc/letsencrypt/live/domain.com/fullchain.pem + +1. 비밀키 경로 + /etc/letsencrypt/live/domain.com/privkey.pem +``` + +또한 우리가 작성한 Nginx의 default.conf를 확인해보면 HTTPS를 위한 설정이 자동으로 추가된 것을 볼 수 있다. + +```bash +# 443 포트로 접근시 ssl을 적용한 뒤 3000포트로 요청을 전달해주도록 하는 설정. +server { + server_name your.domain.com; + + location / { + proxy_pass http://192.168.XXX.XXX:8080; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + } + + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + +} + + +# 80 포트로 접근시 443 포트로 리다이렉트 시켜주는 설정 +server { + if ($host = your.domain.com) { + return 301 https://$host$request_uri; + } # managed by Certbot + + listen 80; + server_name your.domain.com; + return 404; # managed by Certbot +} +``` + +certbot을 이용하여 ssl인증서를 발급할 경우 3개월 마다 갱신을 해줘야 한다. 아래 명령어로 갱신해줄 수 있다. + +``` +$ certbot renew +``` + +인증서의 유효기간이 끝나가면 본인이 certbot을 통해 ssl인증서를 받아올 때 입력했던 이메일로 알림이 오게된다. 해당 메일을 받으면 위 명령을 통해 갱신해주면 된다. + +crontab을 써서 3개월 마다 자동 갱신되도록 해줄 수도 있다. + +--- + +도메인 목록 보기 + +```js +sudo certbot certificates +``` + +도메인 추가 +``` +certbot --cert-name (도메인 대표명) -d ~~~~ -d ~~~~ +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205docker.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205docker.md" new file mode 100644 index 00000000..57227671 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205docker.md" @@ -0,0 +1,39 @@ +--- +title: 'nginx docker' +lastUpdated: '2023-07-03' +--- +1. nginx 컨테이너 실행 + +```jsx +sudo docker run --name nginx -d -p 80:80 nginx +``` + +1. nginx bash 들어가서 conf 파일 열기 + +```jsx +docker exec -it nginx bash +vi /etc/nginx/conf.d/default.conf +``` + +1. 원하는 도메인, 포트 설정해넣기 + +예시 + +```jsx +server { + listen 80; + listen [::]:80; + + server_name domain.aliens-dms.com; + + location / { + proxy_pass http://ip:8080; + } +} +``` + +1. nginx reload로 적용 + +```jsx +nginx -s reload +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205\353\252\205\353\240\271\354\226\264.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205\353\252\205\353\240\271\354\226\264.md" new file mode 100644 index 00000000..e9d22fad --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205\353\252\205\353\240\271\354\226\264.md" @@ -0,0 +1,64 @@ +--- +title: 'nginx 명령어' +lastUpdated: '2024-03-02' +--- + +#### Nginx 시작 +```bash +sudo systemctl start nginx +``` + +#### Nginx 시작 (systemd 없이 Linux 배포를 실행하는 경우) +```bash +sudo service nginx start +``` + +#### Nginx 중지 +```bash +sudo systemctl stop nginx +sudo service nginx stop +``` + +#### Nginx 다시 시작 +```bash +sudo systemctl restart nginx +sudo service nginx restart +``` + +#### Nginx reroad (새 설정을 적용해야할 떄) +```bash +sudo systemctl reload nginx +sudo service nginx reload +``` + +#### Nginx 구성 테스트 + +설정 파일 혹은 실행에 문제가 있는지 테스트하기 위해 사용한다 + +```bash +sudo nginx -t + +# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok +# nginx: configuration file /etc/nginx/nginx.conf test is successful +``` + +#### Nginx 상태 보기 +상세한 상태정보를 반환한다. + +```bash +sudo systemctl status nginx + +# nginx.service - A high performance web server and a reverse proxy server +# Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) +# Active: active (running) since Sun 2019-04-21 13:57:01 PDT; 5min ago +# Docs: man:nginx(8) +# Process: 4491 ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid (code=exited, status=0/SUCCESS) +# Process: 4502 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS) +# Process: 4492 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS) +# Main PID: 4504 (nginx) +# Tasks: 3 (limit: 2319) +# CGroup: /system.slice/nginx.service +# |-4504 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; +# |-4516 nginx: worker process +# `-4517 nginx: worker process +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205\354\204\244\354\240\225.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205\354\204\244\354\240\225.md" new file mode 100644 index 00000000..d77b3782 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/nginx\342\200\205\354\204\244\354\240\225.md" @@ -0,0 +1,37 @@ +--- +title: 'nginx 설정' +lastUpdated: '2024-03-02' +--- + +Nginx를 설치한다. 원하면 이미지로 띄울 수도 있다. + +```bash +$ sudo apt update +$ sudo apt install nginx +``` + +#### 설정 파일 생성 + +``` +$ cd /etc/nginx/conf.d +$ vim default.conf +``` + +default.conf 파일을 생성하고 아래와 같이 원하는 설정 내용을 적어준다. + +```bash +server { + listen 80; + server_name your.domain.com; + + location / { + proxy_pass http://192.168.XXX.XXX; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + } +} +``` + +`server_name`엔 SSL을 적용할 자신의 도메인을 입력해주고, `proxy_pass`에는 프록시 서버가 클라이언트 요청을 전달할 서버의 주소를 적는다. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/sites\342\200\205available.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/sites\342\200\205available.md" new file mode 100644 index 00000000..4603911a --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/sites\342\200\205available.md" @@ -0,0 +1,61 @@ +--- +title: 'sites available' +lastUpdated: '2024-03-02' +--- + +```bash +ubuntu@:~/ cd /etc/nginx +``` + +`/etc/nginx`의 경로에서 nginx에 대한 기본적인 설정을 진행할 수 있다. + +그 중 프록시 관련 설정을 할 때 + +```bash +/etc/nginx/sites-enabled +``` + +라는 폴더에서 직접적으로 설정이 가능하고 + +유저는 저 폴더에 있는 설정파일을 직접적으로 수정하지 않고 + +```bash +/etc/nginx/sites-available +``` + +의 폴더에서 여러 설정파일들을 생성한 뒤 + +symlink 기능을 이용해서 그 파일들 중 원하는 설정을 선택적으로 `sites-enabled`폴더에 동기화해서 적용할 수 있다. + +다음은 sites-available에 만든 설정파일을 sites-enabled에 symlink 시킬 수 있는 명령어이다. + +먼저 sites-available에 'proxy-setting1'이라는 설정파일을 하나 만들었다고 해보자. + +```bash +ubuntu@:~/ cd /etc/nginx/sites-available +ubuntu@:~/etc/nginx/sites-available$ ls + proxy-setting1 +``` + +이렇게 만들어 졌으면 다음 symlink 명령어를 입력한다. + +```bash +ubuntu@:~/etc/nginx/sites-available$ sudo ln -s /etc/nginx/sites-available/proxy-setting1 +/etc/nginx/sites-enabled/ +``` + +symlink가 잘 되었는지 확인하기 위해 sites-enabled폴더로 들어가보자 + +```bash +ubuntu@:~/etc/nginx/sites-available$ cd /etc/nginx/sites-enabled +ubuntu@:~/etc/nginx/sites-enabled$ ls + proxy-setting1 +``` + +symlink가 잘 된 것을 확인할 수 있다. + +마지막으로 세팅변화가 nginx에 적용될 수 있게 재실행시켜준다. + +```bash +ubuntu@:~/etc/nginx/sites-enabled$ sudo service nginx restart +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/\353\246\254\353\262\204\354\212\244\342\200\205\355\224\204\353\241\235\354\213\234.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/\353\246\254\353\262\204\354\212\244\342\200\205\355\224\204\353\241\235\354\213\234.md" new file mode 100644 index 00000000..1baf7a20 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/Proxy/nginx/\353\246\254\353\262\204\354\212\244\342\200\205\355\224\204\353\241\235\354\213\234.md" @@ -0,0 +1,22 @@ +--- +title: '리버스 프록시' +lastUpdated: '2024-03-02' +--- + +클라이언트 요청을 대신 받아 내부 서버로 전달해주는 것을 리버스 프록시(Reverse Proxy)라고 한다. + +## 리버스 프록시가 필요한 이유 + +- 로드 밸런싱 : Nginx는 클라이언트의 요청을 프록시 서버에 분산하기 위해 로드 밸런싱을 수행하여 성능, 확장성 및 신뢰성을 향상시킬 수 있다. + +- 캐싱 : Nginx를 역방향 프록시로 사용하면 미리 렌더링된 버전의 페이지를 캐시하여 페이지 로드 시간을 단축할 수 있다. 서버의 응답에서 수신한 콘텐츠를 캐싱하고 이 콘텐츠를 사용하여 매번 동일한 콘텐츠를 프록시 서버에 연결할 필요 없이 클라이언트에 응답하는 방식으로 구현하는게 가능해진다. + +- SSL 터미네이션 : Nginx는 클라이언트와의 연결에 대한 SSL endpoint 역할을 할 수 있다. + +- 압축 : 프록시 서버가 압축된 응답을 보내지 않는 경우 클라이언트로 보내기 전에 응답을 압축하도록 Nginx를 구성할 수 있다. + +- DDoS 공격 완화 : 수신 요청과 단일 IP 주소당 연결 수를 일반 사용자에게 일반적인 값으로 제한할 수 있다. 또한 Nginx를 사용하면 클라이언트 위치와 "User-에이전트" 및 "Referer"와 같은 요청 헤더 값을 기준으로 액세스를 차단하거나 제한할 수 있다. + +그래서 보통 아래와 같이 사용자 -> nginx -> 웹서버로 구성해서 사용자의 요청을 nginx가 대신 웹서버로 전달해주도록 구성한다. + + \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Clium.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Clium.md" new file mode 100644 index 00000000..6b1ed1a2 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Clium.md" @@ -0,0 +1,151 @@ +--- +title: 'Clium' +lastUpdated: '2024-03-02' +--- + +- Cilium is open source software for **transparently securing** the network connectivity between application services deployed using Linux container management platforms like Docker and Kubernetes. + +- At the foundation of Cilium is a new Linux kernel technology called [eBPF](https://github.com/rlaisqls/TIL/blob/main/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%E2%80%85Operating%E2%80%85System/linux/BPF/BPF.md), which enables the dynamic insertion of powerful security visibility and control logic within Linux itself. + +- Because eBPF runs inside the Linux kernel, Cilium security policies can be applied and updated without any changes to the application code or container configuration. + +### What is Hubble? + +- Hubble is a fully distributed networking and security observability platform. + +- It is built on top of Cilium and eBPF to enable deep visibility into the communication and behavior of services as well as the networking infrastructure in a completely transparent manner. + +- By building on top of Cilium, Hubble can leverage eBPF for visibility. By relying on eBPF, all visibility is programmable and allows for a dynamic approach that minimizes overhead while providing deep and detailed visibility as required by users. + +- Hubble has been created and specifically designed to make best use of these new eBPF powers. + +### Why Cilium & Hubble? + +- This shift toward highly dynamic microservices presents both a challenge and an opportunity in terms of securing connectivity between microservices. Traditional Linux network security approaches (e.g., iptables) filter on IP address and TCP/UDP ports, but IP addresses frequently churn in dynamic microservices environments. + +- The highly volatile life cycle of containers causes these approaches to struggle to scale side by side with the application as load balancing tables and access control lists carrying hundreds of thousands of rules that need to be updated with a continuously growing frequency. + +- By leveraging Linux eBPF, Cilium retains the ability to transparently insert security visibility + enforcement, but does so in a way that is based on service / pod / container identity (in contrast to IP address identification in traditional systems) and can filter on application-layer (e.g. HTTP). + +- As a result, Cilium not only makes it simple to apply security policies in a highly dynamic environment by decoupling security from addressing, but can also provide stronger security isolation by operating at the HTTP-layer in addition to providing traditional Layer 3 and Layer 4 segmentation. + +- The use of eBPF enables Cilium to achieve all of this in a way that is highly scalable even for large-scale environments. + +## Functionality Overview + +### Protect and secure APIs transparently + +- Ability to secure modern application protocols such as REST/HTTP, gRPC and Kafka. +- Traditional firewalls operates at Layer 3 and 4. A protocol running on a particular port is either completely trusted or blocked entirely. Cilium provides the ability to filter on individual application protocol requests such as: + + - Allow all HTTP requests with method `GET` and path `/public/.*`. Deny all other requests. + + - Allow `service1` to produce on Kafka topic `topic1` and `service2` to consume on `topic1`. Reject all other Kafka messages. + + - Require the HTTP header `X-Token: [0-9]+` to be present in all REST calls. + +- https://docs.cilium.io/en/stable/security/policy/language/#layer-7-examples + +### Secure service to service communication based on identities + +- Modern distributed applications rely on technologies such as application containers to facilitate agility in deployment and scale out on demand. This results in a large number of application containers to be started in a short period of time. + +- Typical container firewalls secure workloads by filtering on source IP addresses and destination ports. This concept requires the firewalls on all servers to be manipulated whenever a container is started anywhere in the cluster. + +- In order to avoid this situation which limits scale, Cilium assigns a security identity to groups of application containers which share identical security policies. + +- The identity is then associated with all network packets emitted by the application containers, allowing to validate the identity at the receiving node. Security identity management is performed using a key-value store. + +### Secure access to and from external services + +- Label based security is the tool of choice for cluster internal access control. In order to secure access to and from external services, traditional CIDR based security policies for both ingress and egress are supported. This allows to limit access to and from application containers to particular IP ranges. + +### Simple Networking + +- A simple flat Layer 3 network with the ability to span multiple clusters connects all application containers. IP allocation is kept simple by using host scope allocators. This means that each host can allocate IPs without any coordination between hosts. + +- The following multi node networking models are supported: + +- **Overlay**: Encapsulation-based virtual network spanning all hosts. Currently VXLAN and Geneve are baked in but all encapsulation formats supported by Linux can be enabled. + When to use this mode: This mode has minimal infrastructure and integration requirements. It works on almost any network infrastructure as the only requirement is IP connectivity between hosts which is typically already given. + +- **Native Routing**: Use of the regular routing table of the Linux host. The network is required to be capable to route the IP addresses of the application containers. + When to use this mode: This mode is for advanced users and requires some awareness of the underlying networking infrastructure. This mode works well with: + - Native IPv6 networks + - In conjunction with cloud network routers + - If you are already running routing daemons + +### Load Balancing + +- Cilium implements distributed load balancing for traffic between application containers and to external services and is able to fully replace components such as kube-proxy. + +- he load balancing is implemented in eBPF using efficient hashtables allowing for almost unlimited scale. + +- For north-south type load balancing, Cilium’s eBPF implementation is optimized for maximum performance, can be attached to XDP (eXpress Data Path), and supports direct server return (DSR) as well as Maglev consistent hashing if the load balancing operation is not performed on the source host. + +- For east-west type load balancing, Cilium performs efficient service-to-backend translation right in the Linux kernel’s socket layer (e.g. at TCP connect time) such that per-packet NAT operations overhead can be avoided in lower layers. + +### Bandwidth Management + +- Cilium implements bandwidth management through efficient EDT-based (Earliest Departure Time) rate-limiting with eBPF for container traffic that is egressing a node. + +- This allows to significantly reduce transmission tail latencies for applications and to avoid locking under multi-queue NICs compared to traditional approaches such as HTB (Hierarchy Token Bucket) or TBF (Token Bucket Filter) as used in the bandwidth CNI plugin, for example. + +### Monitoring and Troubleshooting + +- The ability to gain visibility and to troubleshoot issues is fundamental to the operation of any distributed system. This includes tooling to provide: + + - **Event monitoring with metadata**: When a packet is dropped, the tool doesn’t just report the source and destination IP of the packet, the tool provides the full label information of both the sender and receiver among a lot of other information. + + - **Metrics export via Prometheus**: Key metrics are exported via Prometheus for integration with your existing dashboards. + + - **[Hubble](https://github.com/cilium/hubble/)**: An observability platform specifically written for Cilium. It provides service dependency maps, operational monitoring and alerting, and application and security visibility based on flow logs. + +## Clium Componenet + +image + +### Agent + +- The Cilium agent (cilium-agent) runs on each node in the cluster. At a high-level, the agent accepts configuration via Kubernetes or APIs that describes networking, service load-balancing, network policies, and visibility & monitoring requirements. + +- The Cilium agent listens for events from orchestration systems such as Kubernetes to learn when containers or workloads are started and stopped. It manages the eBPF programs which the Linux kernel uses to control all network access in / out of those containers. + +### Client (CLI) + +- The Cilium CLI client (cilium) is a command-line tool that is installed along with the Cilium agent. +- It interacts with the REST API of the Cilium agent running on the same node. The CLI allows inspecting the state and status of the local agent. It also provides tooling to directly access the eBPF maps to validate their state. + +> The in-agent Cilium CLI client described here should not be confused with the command line tool for quick-installing, managing and troubleshooting Cilium on Kubernetes clusters, which also has the name cilium. That tool is typically installed remote from the cluster, and uses kubeconfig information to access Cilium running on the cluster via the Kubernetes API. + +### Operator + +- The Cilium Operator is responsible for **managing duties in the cluster** which should logically be handled once for the entire cluster, rather than once for each node in the cluster. The Cilium operator is not in the critical path for any forwarding or network policy decision. A cluster will generally continue to function if the operator is temporarily unavailable. However, depending on the configuration, failure in availability of the operator can lead to: + +- Delays in IP Address Management (IPAM) and thus delay in scheduling of new workloads if the operator is required to allocate new IP addresses. + +- Failure to update the kvstore heartbeat key which will lead agents to declare kvstore unhealthiness and restart. + +### CNI Plugin + +- The CNI plugin (cilium-cni) is invoked by Kubernetes when a pod is scheduled or terminated on a node. It interacts with the Cilium API of the node to trigger the necessary datapath configuration to provide networking, load-balancing and network policies for the pod. + +## Hubble Componenet + +### Server +- The Hubble server runs on each node and retrieves the eBPF-based visibility from Cilium. It is embedded into the Cilium agent in order to achieve high performance and low-overhead. It offers a gRPC service to retrieve flows and Prometheus metrics. + +### Relay +- Relay (hubble-relay) is a standalone component which is aware of all running Hubble servers and offers cluster-wide visibility by connecting to their respective gRPC APIs and providing an API that represents all servers in the cluster. + +### Client (CLI) +- The Hubble CLI (hubble) is a command-line tool able to connect to either the gRPC API of hubble-relay or the local server to retrieve flow events. + +### Graphical UI (GUI) +- The graphical user interface (hubble-ui) utilizes relay-based visibility to provide a graphical service dependency and connectivity map. + + + +--- +reference +- https://docs.cilium.io/en/stable/overview/intro/#what-is-cilium diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Dex.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Dex.md" new file mode 100644 index 00000000..4b319f9b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Dex.md" @@ -0,0 +1,60 @@ +--- +title: 'Dex' +lastUpdated: '2024-03-02' +--- + +Dex is an identity service that uses OpenID Connect to drive authentication for other apps. + +Dex acts as a portal to other identity providers through "connectors." This lets dex defer authentication to LDAP servers, SAML providers, or established identity providers like GitHub, Google, and Active Directory. Clients write their authentication logic once to talk to dex, then dex handles the protocols for a given backend. + +## ID Tokens + +ID Tokens are an OAuth2 extension introduced by OpenID Connect and dex's primary feature. ID Tokens are JSON Web Tokens (JWTs) signed by dex and returned as part of the OAuth2 response that attest to the end user's identity. An example JWT might look like: + +```yaml +eyJhbGciOiJSUzI1NiIsImtpZCI6IjlkNDQ3NDFmNzczYjkzOGNmNjVkZDMyNjY4NWI4NjE4MGMzMjRkOTkifQ.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjU1NTYvZGV4Iiwic3ViIjoiQ2djeU16UXlOelE1RWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTQ5Mjg4MjA0MiwiaWF0IjoxNDkyNzk1NjQyLCJhdF9oYXNoIjoiYmk5NmdPWFpTaHZsV1l0YWw5RXFpdyIsImVtYWlsIjoiZXJpYy5jaGlhbmdAY29yZW9zLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiYWRtaW5zIiwiZGV2ZWxvcGVycyJdLCJuYW1lIjoiRXJpYyBDaGlhbmcifQ.OhROPq_0eP-zsQRjg87KZ4wGkjiQGnTi5QuG877AdJDb3R2ZCOk2Vkf5SdP8cPyb3VMqL32G4hLDayniiv8f1_ZXAde0sKrayfQ10XAXFgZl_P1yilkLdknxn6nbhDRVllpWcB12ki9vmAxklAr0B1C4kr5nI3-BZLrFcUR5sQbxwJj4oW1OuG6jJCNGHXGNTBTNEaM28eD-9nhfBeuBTzzO7BKwPsojjj4C9ogU4JQhGvm_l4yfVi0boSx8c0FX3JsiB0yLa1ZdJVWVl9m90XmbWRSD85pNDQHcWZP9hR6CMgbvGkZsgjG32qeRwUL_eNkNowSBNWLrGNPoON1gMg +``` + +ID Tokens contains standard claims assert which client app logged the user in, when the token expires, and the identity of the user. + +```json +{ + "iss": "http://127.0.0.1:5556/dex", + "sub": "CgcyMzQyNzQ5EgZnaXRodWI", + "aud": "example-app", + "exp": 1492882042, + "iat": 1492795642, + "at_hash": "bi96gOXZShvlWYtal9Eqiw", + "email": "jane.doe@coreos.com", + "email_verified": true, + "groups": [ + "admins", + "developers" + ], + "name": "Jane Doe" +} +``` +Because these tokens are signed by dex and contain standard-based claims other services can consume them as service-to-service credentials. Systems that can already consume OpenID Connect ID Tokens issued by dex include: + +- Kubernetes +- AWS STS + +For details on how to request or validate an ID Token, see ["Writing apps that use dex"](https://dexidp.io/docs/using-dex/). + +## Kubernetes and Dex +Dex runs natively on top of any Kubernetes cluster using Custom Resource Definitions and can drive API server authentication through the OpenID Connect plugin. Clients, such as the `[kubernetes-dashboard](https://github.com/kubernetes/dashboard)` and `kubectl`, can act on behalf of users who can login to the cluster through any identity provider dex supports. + +- More docs for running dex as a Kubernetes authenticator can be found here. +- You can find more about companies and projects, which uses dex, here. + +## Connectors + +When a user logs in through dex, the user's identity is usually stored in another user-management system: a LDAP directory, a GitHub org, etc. Dex acts as a shim between a client app and the upstream identity provider. The client only needs to understand OpenID Connect to query dex, while dex implements an array of protocols for querying other user-management systems. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/fc23e33b-616b-4ecc-a0d7-5ae658da23ca) + +A "connector" is a strategy used by dex for authenticating a user against another identity provider. Dex implements connectors that target specific platforms such as GitHub, LinkedIn, and Microsoft as well as established protocols like LDAP and SAML. + +Depending on the connectors limitations in protocols can prevent dex from issuing refresh tokens or returning group membership claims. For example, because SAML doesn't provide a non-interactive way to refresh assertions, if a user logs in through the SAML connector dex won't issue a refresh token to its client. Refresh token support is required for clients that require offline access, such as `kubectl`. + +oidc-proxy \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Dex\342\200\205K8s\342\200\205Authenticator.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Dex\342\200\205K8s\342\200\205Authenticator.md" new file mode 100644 index 00000000..678e7333 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Dex\342\200\205K8s\342\200\205Authenticator.md" @@ -0,0 +1,61 @@ +--- +title: 'Dex K8s Authenticator' +lastUpdated: '2024-03-02' +--- + +[Dex K8s Authenticator](https://github.com/mintel/dex-k8s-authenticator/tree/master) is a helper web-app which talks to one or more Dex Identity services to generate kubectl commands for creating and modifying a kubeconfig. + +The Web UI supports generating tokens against multiple cluster such as Dev / Staging / Production. + +## Screen shots + +![image](https://github.com/rlaisqls/TIL/assets/81006587/12411967-a645-4a7e-81cc-bbed93022629) + +![image](https://github.com/rlaisqls/TIL/assets/81006587/6c72bfcc-489b-4a6f-b736-9b4b14ed26a7) + +## Running on AWS EKS + +Running `dex-k8s-authenticator` on AWS EKS is a bit tricky. The reason is, AWS EKS does not allow you to set custom API server flags. [1] This is required to set DEX as an endpoint for OIDC provider for kubernetes API server. + +The good news is, you can still get DEX working properly with EKS, however, this will make your auth-flow a bit more complicated than the one on not-managed regular kubernetes setups. This will also introduce an additional service to your auth-flow. kube-oidc-proxy. [2] + +This diagrams is provided to show how end-to-end auth works with `dex-k8s-authenticator` on both regular kubernetes clusters and on managed kubernetes clusters. + +**Auth flow on self-setup kubernetes cluster** +- ![image](https://github.com/rlaisqls/TIL/assets/81006587/f9f8b241-d8c5-40f5-91b0-472cbca8712c) + +**Auth flow on managed kubernetes cluster** +- ![image](https://github.com/rlaisqls/TIL/assets/81006587/ce821af2-4753-447c-8485-b28ad673c590) + +### How to Setup + +Thankfully, setting all these up is not as difficult as it seems. Mostly because, all the required services have a proper helm-chart. You just need to pass correct values to these charts, depending on your environment. + +**Required Charts** + +- nginx-ingress-controller - https://github.com/helm/charts/tree/master/stable/nginx-ingress +- dex - https://github.com/helm/charts/tree/master/stable/dex +- kube-oidc-proxy - https://github.com/jetstack/kube-oidc-proxy/tree/master/deploy/charts/kube-oidc-proxy +- dex-k8s-authenticator - https://github.com/mintel/dex-k8s-authenticator/tree/master/charts + +You should also setup a DNS record that points to your nginx controller (load-balancer), and setup an AWS ACM certificate. + +**Dex Setup** + +- Enable ingress on the chart values. Set your ingress host as "dex.example.com" +- Setup your connectors. (actual identity providers) +- Setup a static client, for redirect urls, provide "login.example.com" + +**kube-oidc-proxy Setup** + +- Enable ingress on the chart values. Set your ingress host as "oidc-proxy.example.com" +- Setup OIDC configuration. For issuer URL, provide ""dex.example.com"" + +**dex-k8s-authenticator Setup** + +- Enable ingress on the chart values. Set your ingress host as "login.example.com" +- Setup your clusters. +- for issuer; provide "dex.example.com" +- for k8s-master-uri; provide "oidc-proxy.example.com", instead of actual k8s-master-uri. +- provide your client_secret as you set it up on your static_client during Dex setup. +- for k8s-ca-pem; you need to provide the certificate for the ELB that serves "oidc-proxy.example.com", in PEM format. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/GPG.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/GPG.md" new file mode 100644 index 00000000..3a3ce5bf --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/GPG.md" @@ -0,0 +1,202 @@ +--- +title: 'GPG' +lastUpdated: '2024-03-02' +--- + +GPG(GNU Privacy Cuard)는 GNU에서 제공하는 OpenPGP(RFC4880)의 오픈소스 구현이다. + +### PGP란? +- 메시지나 파일을 암호화하여 전송할 수 있는 툴이나 소스를 배포하는 각종 프로그램의 변조 유무를 검사할 수 있는 프로그램이다. +- 누군가 악의적인 목적으로 소스에 해킹툴이나 바이러스를 내포하여 원본과 다른 소스를 배포할 수 있는데, 배포자의 서명과 서명된 파일을 제공하여 소스에 대한 무결성 검사를 할 수 있도록 한다. +- 메일이나 중요한 데이터에 대해 서명과 함께 전송함으로써 허가된 사용자만 해당 데이터를 볼 수 있는 권한을 부여할 수 있다. +- 보안 메일, 전자 서명 시스템에서 응용 가능하다. + +정리하자면 GPG(PGP)는 개인간, 머신간 또는 개인 - 머신간에 교환되는 메시지나 파일을 암호화 하거나 서명을 추가 하여 작성자를 확인하고 변조 유무를 식별할 수 있게 해주는 도구다. + +기본적으로 RSA와 같은 공개 키 암호화 방식을 사용하여 종단간 파일이나 메시지를 암호화 하거나 서명 하는 기능을 제공한다. + +## GPG 사용하기 + +### Key Pair 생성 +```bash +gpg --full-gen-key +> Please select what kind of key you want: 1 +> What keysize do you want? 4096 +> Key is valid for? y +> Is this correct? (y/N) y +> Real name: rlaisqls +> Email address: rlaisqls@gmail.com +> Comment: N/A +> Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O +``` + +아래는 키 생성에 대한 전체 예제이다. + +```bash +$ gpg --full-gen-key +gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Please select what kind of key you want: + (1) RSA and RSA (default) + (2) DSA and Elgamal + (3) DSA (sign only) + (4) RSA (sign only) + (14) Existing key from card +Your selection? 1 +RSA keys may be between 1024 and 4096 bits long. +What keysize do you want? (3072) 4096 +Requested keysize is 4096 bits +Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +Key is valid for? (0) 2y +Key expires at Thu 16 Mar 2023 11:12:08 AM KST +Is this correct? (y/N) y + +GnuPG needs to construct a user ID to identify your key. + +Real name: rlaisqls +Email address: rlaisqls@gmail.com +Comment: ACME Inc. +You selected this USER-ID: + "rlaisqls (ACME Inc.) " + +Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. +gpg: key B821C2E8600096BE marked as ultimately trusted +gpg: revocation certificate stored as '/home/euikook/.gnupg/openpgp-revocs.d/EFD634321C5A23B17A74AB6DB821C2E8600096BE.rev' +public and secret key created and signed. + +pub rsa4096 2021-03-16 [SC] [expires: 2023-03-16] + EFD634321C5A23B17A74AB6DB821C2E8600096BE +uid rlaisqls (ACME Inc.) +sub rsa4096 2021-03-16 [E] [expires: 2023-03-16] +``` + +### 키 확인 하기 +```bash +gpg --list-key +gpg --list-keys +gpg: checking the trustdb +gpg: marginals needed: 3 completes needed: 1 trust model: pgp +gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u +gpg: next trustdb check due at 2023-03-16 +/home/euikook/.gnupg/pubring.kbx +-------------------------------- +pub rsa4096 2021-03-16 [SC] [expires: 2023-03-16] + EFD634321C5A23B17A74AB6DB821C2E8600096BE +uid [ultimate] rlaisqls (ACME Inc.) +sub rsa4096 2021-03-16 [E] [expires: 2023-03-16] +``` + +### Key에 서명하기 + +```bash +gpg --sign-key rlaisqls@gmail.com +``` + +### GPG 키 편집 +`--edit-key `옵션으로 생성된 키의 정보를 수정할 수 있다. + +```bash +gpg --edit-key rlaisqls@gmail.com +``` + +위 명령을 수행 하면 `gpg>` 프롬프트가 뜬다. `?`를 입력하면 입력 가능한 명령이 나온다. + +adduid 명령으로 uid를 추가 할 수 있다. + +편집이 완료 되었으면 quit 명령으로 프로그램을 종료 한다. + +개인 키에서 암호 변경/제거하기 + +```bash +gpg --list-keys +/home/john/.gnupg/pubring.kbx +-------------------------------- +pub rsa3072 2021-03-16 [SC] + AA1AC070A86C0523A867C0261D3E87647AD3517E +uid [ultimate] rlaisqls +sub rsa3072 2021-03-16 [E] +gpg --edit-key AA1AC070A86C0523A867C0261D3E87647AD3517E +``` + +`gpg>` 프롬프트가 뜨면 passwd를 입력하여 비밀번호를 변경한다. + +기존 비밀번호를 입력 하고 새로운 비밀번호를 입력한다. 암호를 제거 하려면 암호를 입력 하지 않고 OK를 선택한다. + +암호를 입력 하지 않으면 입호 입력을 권하는 경고 메시지가 뜬다. 암호 없이 키를 생성 하려면 Yes, protection is not nedded를 선택한다. + +### GPG 폐기 인증서(Revocation Certificate) 생성 +Key Pair를 생성한 후 폐기 인증서를 만들어야 한다. 명시적으로 키를 폐기 하고자 할때 만들어도 되지만 개인 키가 손상되었거나 분실하였을 경우 폐기 인증서를 만들 수 없기 때문에 미리 만들어서 안전한 곳에 보관한다. + +> 폐기 인증서는 키 생성 후 바로 생성 하는 것이 좋다. 폐기 인증서를 만든다고 키가 바로 폐기 되는것이 아니고, 이렇게 만들어 놓은 폐기 인증서는 암호를 잊어 버리거나 키를 분실한 경우 키를 안전하게 폐기 할 수 있는 방법을 제공한다. + +```bash +gpg --output john.revoke.asc --gen-revoke rlaisqls@gmail.com +``` + +아래는 폐기 인증서를 만드는 전체 과정이다. + +```bash +gpg --output euikook.revoke.asc --gen-revoke rlaisqls@gmail.com + +sec rsa4096/B821C2E8600096BE 2021-03-16 rlaisqls (ACME Inc.) + +Create a revocation certificate for this key? (y/N) y +Please select the reason for the revocation: + 0 = No reason specified + 1 = Key has been compromised + 2 = Key is superseded + 3 = Key is no longer used + Q = Cancel +(Probably you want to select 1 here) +Your decision? 0 +Enter an optional description; end it with an empty line: +> +Reason for revocation: No reason specified +(No description given) +Is this okay? (y/N) y +ASCII armored output forced. +Revocation certificate created. + +Please move it to a medium which you can hide away; if Mallory gets +access to this certificate he can use it to make your key unusable. +It is smart to print this certificate and store it away, just in case +your media become unreadable. But have some caution: The print system of +your machine might store the data and make it available to others! +``` + +폐기 인증서만 있다면 누구든지 공개키를 폐기할 수 있으므로 인증서는 안전한 곳에 보관 하여야 한다. + +### 공개 키 내보내기 + +타인에게 공개할 공개 키를 공유 하기 위해서는 키를 내보야 한다. `--export` 옵션을 사용하여 키를 내보낸다. 기본적으로 키를 바이너리 형식으로 내보내지만 이를 공유 할 때 불편할 수 있다. `--armor` 옵션을 사용하여 키를 ASCII형식으로 출력한다. + +```bash +gpg --export --armor --output test.pub rlaisqls@gmail.com +``` + +다른 사람들이 공개 키를 검증 하는 것을 허용 하려면 공개 키의 지문(fingerprint)도 같이 공유 한다. + +```bash +gpg --fingerprint rlaisqls@gmail.com +``` + +--- +참고 +- https://gnupg.org/ +- https://en.wikipedia.org/wiki/GNU_Privacy_Guard +- https://librewiki.net/wiki/%EC%8B%9C%EB%A6%AC%EC%A6%88:%EC%95%94%ED%98%B8%EC%9D%98_%EC%95%94%EB%8F%84_%EB%AA%B0%EB%9D%BC%EB%8F%84_%EC%89%BD%EA%B2%8C_%ED%95%98%EB%8A%94_GPG \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Keycloak.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Keycloak.md" new file mode 100644 index 00000000..57410eae --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Keycloak.md" @@ -0,0 +1,66 @@ +--- +title: 'Keycloak' +lastUpdated: '2024-03-02' +--- + +[Keycloak](https://www.keycloak.org/)은 ID 및 액세스 관리 솔루션을 제공하고, 인증(Authentication)과 인가(Authorization)을 쉽게 해주고 SSO(Single-Sign-On)을 가능하게 해주는 오픈소스이다. + +### SSO란? + +SSO는 Single-Sign-On의 약자로 한번의 로그인을 통해 그와 연결된 여러가지 다른 사이트들을 자동으로 접속하여 이용할 수 있도록 하는 방법이다. + +일반적으로는 여러 사이트를 사용할 떄 각각의 DB에 각각의 사용자 정보가 있고 그 정보를 통해서 관리를 하게 되는데, 필요에 따라서는 사용자 정보를 연동하여 사용해야 하는 경우가 발생한다. 이런 경우에 SSO 기능을 사용하게 되며 하나의 시스템에서 인증을 할 경우 그와 연결된 다른 시스템에서는 인증 정보가 있는지 없는지를 확인하여 이후 처리를 하도록 만들면 되는 것이다. + +즉, 하나의 아이디와 패스워드를 통해 여러 시스템에 접근할 수 있도록 하는 통합 인증 솔루션인 것이다. + +예를 들어 하나의 회사 내부에서 다양한 시스템을 운영하고 있는 경우, 시스템 각각에 대해 사원 정보가 중복으로 존재할 필요가 없기에 SSO 인증 방식으로 사용하는게 적합하다. + +### 기능 +- 다중 프로토콜 지원 + - OpenID Connect, OAuth 2.0 및 SAML 2.0의 세가지 프로토콜을 지원한다. (OIDC는 인증, OAuth 2.0은 인가에 목적을 둔 프로토콜이다) +- SSO + - 싱글 사인온 및 싱글 사인아웃을 지원한다. +- 관리자 콘솔 + - 웹 기반 GUI로 관리자 콘솔을 제공한다. +- 사용자 ID 및 액세스 + - 사용자 지정 역할 및 그룹으로 사용자 데이터베이스를 생성할 수 있도록 하여 독립 실행형 사용자 ID 및 액세스 관리자로 사용할 수 있다. + - 애플리케이션 내에서 사용자를 인증하고 사전 정의된 역할을 기반으로 애플리케이션의 일부를 보호하는데 추가적으로 사용될 수 있다. +- 외부 ID 소스 동기화 + - 현재 어떤 유형의 사용자 데이터베이스가 있는 경우 해당 데이터베이스와 동기화할 수 있다. +- 신원 중개 + - 사용자와 일부 외부 ID 공급자 또는 공급자간의 프록시로 작동할 수 있다. +- 소셜 로그인 + - 관리자 패널에서 설정을 통해 소셜 ID를 사용할 수 있다. (google, twitter, stack overflow ..) +- 페이지 사용자 정의 + - 사용자에게 표시되는 모든 페이지에 대해 사용자 지정을 할 수 있다. + +### keycloak 사용해보기 + +지원하는 도커 이미지를 실행시켜보자 + +```bash +docker run -d --name keycloak -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin jboss/keycloak:10.0.0 +``` + +keycloak을 실행시킨 뒤 `localhost:8080/auth`로 접속하면 관리자 콘솔에 접속할 수 있다. + +docker로 실행시킬 때 설정해준 아이디와 패스워드를 사용하면 관리자 계정으로 로그인할 수 있다. + + +### keycloak 용어 정리 +**Realm** +- keycloak에서 Realm은 인증, 권한 부여가 적용되는 범위의 단위이다. SSO 기능을 적용한다고 했을 때 SSO가 적용되는 범위가 하나의 Realm 단위인 것이다. +처음 접속을 하면 보이는 Master realm은 첫번째 keycloak을 만들면 자동으로 생성되는 realm이며 add realm 버튼을 통해 쉽게 다른 realm을 추가할 수 있다. + +**Client** +- keycloak에서 Client는 인증, 권한 부여 행위를 수행할 어플리케이션을 나타내는 단위이다. +하나의 realm 안에는 여러 개의 client가 들어갈 수 있으며 realm의 관리자가 각각의 client를 관리할 수 있다. +client는 보통 서비스 단위로 생성하고 관리하면 된다! + +**User** +- keycloak에서 User는 인증을 필요로하는 사용자를 나타낸다. +기본적으로 User 정보는 username, email, firstname, lastname으로 구성되어 있지만 custom user attribute를 사용하여 원하는 속성을 추가할 수 있다. (다만, 추가된 항목이 사용자 등록 및 관리 화면에도 출력되도록 하기 위해서는 커스텀 테마 등록 및 수정이 필요하다) + +**Role** +- keycloak에서 Role은 User에게 부여할 권한의 내용을 나타낸다. 단어 뜻 그대로 User에게 어떤 역할을 부여할 것인지에 대한 내용이라고 생각하면 된다. + diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Mortar.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Mortar.md" new file mode 100644 index 00000000..b915b4b7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Mortar.md" @@ -0,0 +1,247 @@ +--- +title: 'Mortar' +lastUpdated: '2024-03-02' +--- + +> https://github.com/noahbliss/mortar + +Framework to join Linux's physical security bricks. Mortar is essentially Linux-native TPM-backed Bitlocker. Virtually all linux districutions are critically vulnerable to physical bootloader attacks and potential disk key interception. Mortar fixes that. + +Mortar is an attempt to take the headache and fragmented processes out of joining Secureboot, TPM keys, and LUKS. + +Through the "Mortar Model" everything on disk that is used is **either encrypted, signed, or hashed**. The only location cleartext secrets are stored is in the TPM module, which is purpose-built to protect these keys against physical and virtual theft. + +The TPM is used to effectively whitelist certain boot states and Mortar configures it to only release the key when an untampered system is observed. Since this validation and unlocking process is completely automated, intact systems fully restart without human interaction. This makes full-disk encryption dramatically more convenient for end-users and finally viable on servers. + +## How it works + +image + +Only 2 partitions on your primary disk are used: your UEFI ESP, and your encrypted LUKS partition. (You can leave your unencrypted boot partition if you like, but I'd highly recommend removing or disabling its automatic mount so that kernels and initram filesystems can reside encrypted on your LUKS partition.) + +You generate your own Secureboot keys. Only efi files you sign will successfully boot without modifying the BIOS (and breaking PCR1 validation). + +## Detail Procedure + +### 1. Initial setup + +Mortar has env file(`/etc/mortar/mortar.env`), cmdline file(`/etc/mortar/cmdline.conf`). And work in `/etc/mortar`. You can see the env file's default value in [here](https://github.com/noahbliss/mortar/blob/master/mortar.env). + +When installing the motor, [os-release](https://www.freedesktop.org/software/systemd/man/latest/os-release.html) including operating system identification data is first executed. + +```bash +# Figure out our distribuition. +source /etc/os-release + +# Install prerequisite packages. +if [ -f "res/$ID/prereqs.sh" ]; then + source "res/$ID/prereqs.sh"; +else + echo "Could not find a prerequisite installer for $ID. Please only press enter if you want to continue at your own risk." + read -p "Press enter to continue." asdf +fi +``` + +And run a prerequisite script to install prequisite packages. + +```bash +apt-get update +apt-get install \ + binutils \ + efitools \ + uuid-runtime +``` + +`KEY_UUID` key that has random UUID value is added to env file. + +```bash +# Install the env file with a random key_uuid if it doesn't exist. +if ! (command -v uuidgen >/dev/null); then echo "Cannot find uuidgen tool."; exit 1; fi +if ! [ -f "$ENVFILE" ]; then echo "Generating new KEY_UUID and installing mortar.env to $WORKING_DIR"; KEY_UUID=$(uuidgen --random); sed -e "/^KEY_UUID=.*/{s//KEY_UUID=$KEY_UUID/;:a" -e '$!N;$!ba' -e '}' mortar.env > "$ENVFILE"; else echo "mortar.env already installed in $WORKING_DIR"; fi +``` + +Istall `cmdline.conf` + +```bash +if ! [ -f "$CMDLINEFILE" ]; then echo "No CMDLINE options file found. Using currently running cmdline options from /proc/cmdline"; cat /proc/cmdline > "$CMDLINEFILE"; else echo "cmdline.conf already installed in $WORKING_DIR"; fi +echo "Make sure to update the installed mortar.env with your TPM version and ensure all the paths are correct." +if grep " splash" "$CMDLINEFILE" >/dev/null; then echo "WARNING - \"splash\" detected in "$CMDLINEFILE" this this may hide boot-time mortar output!"; fi +if grep " quiet" "$CMDLINEFILE" >/dev/null; then echo "WARNING - \"quiet\" detected in "$CMDLINEFILE" this this may hide boot-time mortar output!"; fi +if grep " rhgb" "$CMDLINEFILE" >/dev/null; then echo "WARNING - \"rhgb\" detected in "$CMDLINEFILE" this this may hide boot-time mortar output!"; fi +``` + +Install the efi signing script. + +```bash +cp bin/mortar-compilesigninstall /usr/local/sbin/mortar-compilesigninstall +if ! command -v mortar-compilesigninstall >/dev/null; then + echo "Installed mortar-compilesigninstall to /usr/local/sbin but couldn't find it in PATH. Please update your PATH to include /usr/local/sbin" +fi +``` + +### 2. Generate And Install Securebootkeys + +Generate Securebootkeys by openssl x509. + +```bash +set -e +source mortar.env +echo "Generating secureboot keys..." +openssl req -new -x509 -newkey rsa:2048 -subj "/CN=PK$SECUREBOOT_MODIFIER/" -keyout "$SECUREBOOT_PK_KEY" -out "$SECUREBOOT_PK_CRT" -days 7300 -nodes -sha256 +openssl req -new -x509 -newkey rsa:2048 -subj "/CN=KEK$SECUREBOOT_MODIFIER/" -keyout "$SECUREBOOT_KEK_KEY" -out "$SECUREBOOT_KEK_CRT" -days 7300 -nodes -sha256 +openssl req -new -x509 -newkey rsa:2048 -subj "/CN=db$SECUREBOOT_MODIFIER/" -keyout "$SECUREBOOT_DB_KEY" -out "$SECUREBOOT_DB_CRT" -days 7300 -nodes -sha256 +# Adding der versions of keys to private dir. +openssl x509 -in "$SECUREBOOT_PK_CRT" -outform der -out "$SECUREBOOT_PK_CRT".der +openssl x509 -in "$SECUREBOOT_KEK_CRT" -outform der -out "$SECUREBOOT_KEK_CRT".der +openssl x509 -in "$SECUREBOOT_DB_CRT" -outform der -out "$SECUREBOOT_DB_CRT".der +``` + +And make that keys efi sig using [cert-to-efi-sig-list](https://github.com/mjg59/efitools/blob/master/cert-to-efi-sig-list.c) command. + +```bash +# Generate secureboot specific file variants. +cert-to-efi-sig-list -g "$KEY_UUID" "$SECUREBOOT_PK_CRT" "$SECUREBOOT_PK_ESL" +sign-efi-sig-list -g "$KEY_UUID" -k "$SECUREBOOT_PK_KEY" -c "$SECUREBOOT_PK_CRT" PK "$SECUREBOOT_PK_ESL" "$SECUREBOOT_PK_AUTH" +cert-to-efi-sig-list -g "$KEY_UUID" "$SECUREBOOT_KEK_CRT" "$SECUREBOOT_KEK_ESL" +sign-efi-sig-list -g "$KEY_UUID" -k "$SECUREBOOT_PK_KEY" -c "$SECUREBOOT_PK_CRT" KEK "$SECUREBOOT_KEK_ESL" "$SECUREBOOT_KEK_AUTH" +cert-to-efi-sig-list -g "$KEY_UUID" "$SECUREBOOT_DB_CRT" "$SECUREBOOT_DB_ESL" +sign-efi-sig-list -g "$KEY_UUID" -k "$SECUREBOOT_KEK_KEY" -c "$SECUREBOOT_KEK_CRT" db "$SECUREBOOT_DB_ESL" "$SECUREBOOT_DB_AUTH" + +echo "You now need to generate/install a signed efi file Before installing the keys and enabling secureboot!" +echo "Run bin/mortar-compilesigninstall FULLPATHTOKERNELIMAGE FULLPATHTOINITRDIMAGE" +``` + +And install by [efi-updatevar](https://github.com/mjg59/efitools/blob/master/efi-updatevar.c) command. + +```bash +chattr -i /sys/firmware/efi/efivars/{PK,KEK,db,dbx}-* 2>/dev/null +if (efi-updatevar -f "$SECUREBOOT_DB_AUTH" db); then thing="db"; installed $thing; else failed $thing; exit 1; fi +if (efi-updatevar -f "$SECUREBOOT_KEK_AUTH" KEK); then thing="KEK"; installed $thing; else failed $thing; exit 1; fi +if (efi-updatevar -f "$SECUREBOOT_PK_AUTH" PK); then thing="PK"; installed $thing; else failed $thing; exit 1; fi +``` + +### 3. Prepluk sand install hooks + +Testing if secure boot is on and working. + +```bash +MORTAR_FILE="/etc/mortar/mortar.env" +OLD_DIR="$PWD" +source "$MORTAR_FILE" + +od --address-radix=n --format=u1 /sys/firmware/efi/efivars/SecureBoot-* +read -p "ENTER to continue only if the last number is a \"1\" and you are sure the TPM registers are as you want them." asdf +``` + +Remove tmpramfs from failed runs if applicable. + +```bash +if [ -d tmpramfs ]; then + echo "Removing existing tmpfs..." + umount tmpramfs + rm -rf tmpramfs +fi +``` + +Create tmpramfs for generated mortar key and read user luks password to file. + +```bash +if mkdir tmpramfs && mount tmpfs -t tmpfs -o size=1M,noexec,nosuid tmpramfs; then + echo "Created tmpramfs for storing the key." + trap "if [ -f tmpramfs/user.key ]; then rm -f tmpramfs/user.key; fi" EXIT + echo -n "Enter luks password: "; read -s PASSWORD; echo + echo -n "$PASSWORD" > tmpramfs/user.key + unset PASSWORD +else + echo "Failed to create tmpramfs for storing the key." + exit 1 +fi + +if command -v luksmeta >/dev/null; then + echo "Wiping any existing metadata in the luks keyslot." + luksmeta wipe -d "$CRYPTDEV" -s "$SLOT" +fi +``` + +Wiping any old luks key in the keyslot. + +```bash +cryptsetup luksKillSlot --key-file tmpramfs/user.key "$CRYPTDEV" "$SLOT" +read -p "If this is the first time running, do you want to attempt taking ownership of the tpm? (y/N): " takeowner +case "$takeowner" in + [yY]*) tpm_takeownership -z ;; +esac +``` + +Generate the key. + +```bash +dd bs=1 count=512 if=/dev/urandom of=tmpramfs/mortar.key +chmod 700 tmpramfs/mortar.key +cryptsetup luksAddKey "$CRYPTDEV" --key-slot "$SLOT" tmpramfs/mortar.key --key-file tmpramfs/user.key +``` + +Sealing key to TPM. Using `tpm_nvwrite` that writes data to an NVRAM area. + +> NVRAM (non-volatile random-access memory) refers to computer memory that can hold data even when power to the memory chips has been turned off. + +```bash +if [ -z "$TPMINDEX" ]; then echo "TPMINDEX not set."; exit 1; fi +PERMISSIONS="OWNERWRITE|READ_STCLEAR" +read -s -r -p "Owner password: " OWNERPW + +# Wipe index if it is populated. +if tpm_nvinfo | grep \($TPMINDEX\) > /dev/null; then tpm_nvrelease -i "$TPMINDEX" -o"$OWNERPW"; fi + +# Convert PCR format... +PCRS=`echo "-r""$BINDPCR" | sed 's/,/ -r/g'` + +# Create new index sealed to PCRS. +if tpm_nvdefine -i "$TPMINDEX" -s `wc -c tmpramfs/mortar.key` -p "$PERMISSIONS" -o "$OWNERPW" -z $PCRS; then + # Write key into the index... + tpm_nvwrite -i "$TPMINDEX" -f tmpramfs/mortar.key -z --password="$OWNERPW" +fi +``` + +Get rid of the key in the ramdisk. + +```bash +echo "Cleaning up luks keys and tmpfs..." +rm tmpramfs/mortar.key +rm tmpramfs/user.key +umount -l tmpramfs +rm -rf tmpramfs +``` + +Adding new sha256 of the luks header to the mortar env file. + +```bash +if [ -f "$HEADERFILE" ]; then rm "$HEADERFILE"; fi +cryptsetup luksHeaderBackup "$CRYPTDEV" --header-backup-file "$HEADERFILE" + +HEADERSHA256=`sha256sum "$HEADERFILE" | cut -f1 -d' '` +sed -i -e "/^HEADERSHA256=.*/{s//HEADERSHA256=$HEADERSHA256/;:a" -e '$!N;$!b' -e '}' "$MORTAR_FILE" +if [ -f "$HEADERFILE" ]; then rm "$HEADERFILE"; fi +``` + +Defer to tpm and distro-specific install script. + +```bash +source /etc/os-release +tpmverdir='tpm1.2' + +if [ -d "$OLD_DIR/""res/""$ID/""$tpmverdir/" ]; then + cd "$OLD_DIR/""res/""$ID/""$tpmverdir/" + echo "Distribution: $ID" + echo "Installing kernel update and initramfs build scripts with mortar.env values..." + bash install.sh # Start in new process so we don't get dropped to another directory. +else + echo "Distribution: $ID" + echo "Could not find scripts for your distribution." +fi +``` + +--- +reference +- https://github.com/noahbliss/mortar +- https://www.unix.com/man-page/centos/8/tpm_nvwrite/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/OpenHistorian.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/OpenHistorian.md" new file mode 100644 index 00000000..2b0a02e0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/OpenHistorian.md" @@ -0,0 +1,37 @@ +--- +title: 'OpenHistorian' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/99a9ce84-a556-40c6-9e90-c8f29f216a26) + +- openHistorian is a back-office system developed by the GridProtectionAlliance that is designed to efficiently integrate and archive process control data, such as SCADA, synchrophasor, digital fault recorder, or any other time-series data used to support process operations. + +- It is optimized to store and retrieve large volumes of time-series data quickly and efficiently, including high-resolution sub-second information. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/4c88de55-bd56-42fe-a210-dd68bb17d337) + +### Overview + +The openHistorian 2 is built using the GSF SNAPdb Engine - a key/value pair archiving technology developed to significantly improve the ability to archive extremely large volumes of real-time streaming data and directly serve the data to consuming applications and systems. + +Through use of the SNAPdb Engine, the openHistorian inherits very fast performance with very low lag-time for data insertion. The openHistorian 2 is a time-series implementation of the SNABdb engine where the "key" is a tuple of time and measurement ID, and the "value" is the stored data - which can be most any data type and associated flags. + +The system comes with a high-speed API that interacts with an in-memory cache for very high speed extraction of near real-time data. The archive files produced by the openHistorian are ACID Compliant which create a very durable and consistent file structure that is resistant to data corruption. Internally the data structure is based on a B+ Tree that allows out-of-order data insertion. + +The openHistorian service also hosts the GSF Time-Series Library (TSL), creating an ideal platform for integrating streaming time-series data processing in real-time: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/665d4e2d-2b04-44b7-9376-ac58d56cf88b) + +### Integrate with Grafana + +By using the openHistorian plugin for Grafana, users can connect Grafana to their openHistorian instance and leverage its capabilities to query and visualize historical data within Grafana's intuitive and customizable dashboards. + +Building a metric query using the openHistorian Grafana data source begins with the selection of a query type, one of: Element List, Filter Expression or Text Editor. The Element List and Filter Expression types are query builder screens that assist with the selection of the desired series. The Text Editor screen allows for manual entry of a query expression that will select the desired series. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/3b916bc4-cee5-49f3-94d7-f68071bf240c) + +--- +reference +- https://github.com/GridProtectionAlliance/openHistorian +- https://grafana.com/grafana/plugins/gridprotectionalliance-openhistorian-datasource/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Packer.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Packer.md" new file mode 100644 index 00000000..c44300a6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Packer.md" @@ -0,0 +1,113 @@ +--- +title: 'Packer' +lastUpdated: '2024-03-02' +--- + +Packer (https://www.packer.io/) 는 HashiCorp에서 개발한 가상 머신 이미지를 만들어주는 오픈소스이다. + +예를 들어서, 아마존 클라우드 AMI이미지나, 구글 클라우드 이미지를 스크립트를 이용하여 생성이 가능하다. + +하나의 스크립트를 이용하여, 구글 클라우드, VMWare, 아마존 클라우드 등 여러 클라우드 환경 (가상화 환경)과 컨테이너 엔진용 이미지를 생성할 수 있다. + +Chef, Puppet, Ansible과 같은 Configuration management 툴과 혼동이 될 수 있지만, Packer는 OS 이미지를 만들어주는 역할을 하고, Configuration management 툴들은 이렇게 만들어진 이미지 위에 소프트웨어를 설치하고, 이를 설정하는 상호 보완적인 역할을 하게 된다. + +특히 [피닉스 서버](./IaC/Phoenix%E2%80%85Server.md) 패턴에서 VM 이미지를 생성하는데 매우 유용하게 사용될 수 있다. + +## 특징 + +- 패커는 특정 플랫폼의 이미지를 만드는 도구가 아니라, 다양한 플랫폼에 대한 이미지 생성 과정을 추상화해주는 역할을 한다. (VM의 이미지 뿐만 아니라 도커 이미지 등 여러 종류의 이미지를 다룸) +- 이러한 특징 덕분에 플랫폼 간 이동을 쉽게 해주며, 인스턴스 기반 환경에서 컨테이너 기반으로 이동하는 가교 역할을 하는 것도 가능하다. +- 또한 이 전체를 코드로서 관리할 수 있기 때문에, 재현 가능성도 높아지고 관리 역시 쉬워진다. + +## 템플릿 + +전체 컨셉은 VM의 설정을 JSON 파일에 정의해놓고, packer 툴을 이용하여 이미지를 생성하는 방식이다. + +VM의 설정을 정의한 파일을 템플릿 파일이라고 하는데, 다음과 같은 구조를 가지고 있다. + +- **Variable :** 변수를 정의하는 섹션으로, 동적으로 변경될 수 있는 클라우드 프로젝트명, 리전명등을 정의하는 부분이다. 메인 템플릿내에 섹션으로 정의할 수 도 있고, 또는 환경 변수나 별도의 변수만 지정한 파일 또는 CLI 옵션으로도 변수값을 전달할 수 있다. +- **Builder :** 가장 핵심이 되는 부분으로 OS 버전등 VM 설정에 대한 부분을 정의한다. +- **Provisioner :** 이미지에서 OS 설정이 끝난후에, 소프트웨어 스택을 설치하는 부분을 정의한다. 앞에서도 언급하였지만 Packer는 다양한 가상환경에 대한 이미지 생성에 최적화 되어 있지 소프트웨어 설치를 용도로 하지 않기 때문에, Provisioner에서는 다른 configuration management 툴과의 연계를 통해서 소프트웨어를 설치하도록 지원한다. 간단한 쉘을 이용하는것에서 부터, ansible,chef,puppet,salt stack등 다양한 configuration management 도구를 지원하도록 되어 있다. https://www.packer.io/docs/provisioners/index.html +이 과정에서 OS 설치 후, 소프트웨어 스택 설치 뿐만 아니라, 패치 및 기타 OS 설정 작업을 진행할 수 있다. +- **Post-Processor :** Builder와 Provisioner에 의한 이미지 생성이 끝나면 다음으로 실행되는 명령이다. + +```json +{ + "variables":{ + // ... + }, + "builders": [{ + // ... + }], + "provisioners": [{ + // ... + }], + "post-processors": [{ + // ... + }] +} +``` + +## Ansible Playbook으로 AMI 빌드 + +예시를 알아보기 위해서 앤서블로 AMI를 빌드해보자. + +```json +{ + "builders": [{ + "type": "amazon-ebs", + "access_key": "", + "secret_key": "", + "region": "ap-northeast-1", + "source_ami": "ami-cbf90ecb", + "instance_type": "m3.medium", + "ssh_username": "ec2-user", + "ami_name": "CustomImage {{isotime | clean_ami_name}}" + }], + "provisioners": [{ + "type": "ansible-local", + "playbook_file" : "ansible/playbook.yml", + "playbook_dir": "/Users/../ansible" + }] +} +``` + +- builders + - 먼저 `type`에는 amazon-ebs를 지정했다. 그리고 `access_key`와 `secret_key`에는 아마존 API 인증 정보를, `region`은 이미지가 생성되는 지역, `source_ami`는 이미지를 생성할 베이스 이미지(ami-cbf90ecb는 아마존 리눅스이다), `instance_type`은 이미지를 빌드할 때 사용할 인스턴스 타입, `ssh_username`에는 SSH 사용자 이름, 마지막으로 `ami_name`에는 새로 생성될 이미지 이름을 지정한다. + - 패커의 JSON에서는 몇 가지 미리 정의되어있는 변수들을 사용할 수 있다. 이미지 이름에서 사용하는 `isotime`은 시간을 출력하며, `|` 다음의 `clean_ami_name` 필터를 통해서 이미지에서 사용할 수 없는 기호들을 미리 제거할 수 있다. +- provisioners + - 앤서블을 프로비저너로 사용하고자 하는 경우에는 앤서블 플레이북을 미리 작성해야한다. `type`에는 ansible-local을 지정하고, 플레이북의 경로를 지정한다. + +완성된 템블릿으로 빌드해보자. + +```bash +$ packer build ./template.json +amazon-ebs output will be in this color. + +==> amazon-ebs: Inspecting the source AMI... +==> amazon-ebs: Creating temporary keypair: packer 55e9b978-5a49... +==> amazon-ebs: Creating temporary security group for this instance... +==> amazon-ebs: Authorizing SSH access on the temporary security group... +==> amazon-ebs: Launching a source AWS instance... + amazon-ebs: Instance ID: i-12345678 +==> amazon-ebs: Waiting for instance (i-12345678) to become ready... + +... + +==> amazon-ebs: Stopping the source instance... +==> amazon-ebs: Waiting for the instance to stop... +==> amazon-ebs: Creating the AMI: CustomImage 2015-09-04T15-32-08Z + amazon-ebs: AMI: ami-12345678 +==> amazon-ebs: Waiting for AMI to become ready... +==> amazon-ebs: Terminating the source AWS instance... +==> amazon-ebs: Deleting temporary security group... +==> amazon-ebs: Deleting temporary keypair... +Build 'amazon-ebs' finished. + +==> Builds finished. The artifacts of successful builds are: +--> amazon-ebs: AMIs were created: + +ap-northeast-1: ami-12345678 +``` + +성공적으로 AMI가 만들어졌다. 이제 이 AMI를 가지고 새로운 인스턴스를 실행할 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Vault.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Vault.md" new file mode 100644 index 00000000..edc51dbe --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/Vault.md" @@ -0,0 +1,93 @@ +--- +title: 'Vault' +lastUpdated: '2024-03-02' +--- + +- HashiCorp Vault is an identity-based secrets and encryption management system. + +- A secret is anything that you want to tightly control access to, such as API encryption keys, passwords, and certificates. + +- Vault provides encryption services that are gated by authentication and authorization methods. Using Vault’s UI, CLI, or HTTP API, access to secrets and other sensitive data can be securely stored and managed, tightly controlled (restricted), and auditable. + +image + +## Barrier + +image + +```go +// SecurityBarrier is a critical component of Vault. It is used to wrap +// an untrusted physical backend and provide a single point of encryption, +// decryption and checksum verification. + +// The goal is to ensure that any +// data written to the barrier is confidential and that integrity is preserved. + +// As a real-world analogy, this is the steel and concrete wrapper around +// a Vault. The barrier should only be Unlockable given its key. + +type SecurityBarrier interface { + ... + + // keyring is used to maintain all of the encryption keys, including + // the active key used for encryption, but also prior keys to allow + // decryption of keys encrypted under previous terms. + Keyring() (*Keyring, error) + + // SecurityBarrier must provide the storage APIs + logical.Storage + + // SecurityBarrier must provide the encryption APIs + BarrierEncryptor + ... +} +``` + +- Vault’s encryption layer, referred to as the **barrier**, is responsible for **encrypting and decrypting Vault data.** + - When the Vault server starts, it writes data to its storage backend. + - **Since the storage backend resides outside the barrier, it’s considered untrusted so Vault will encrypt the data before it sends them to the storage backend**. + - This mechanism ensures that if a malicious attacker attempts to gain access to the storage backend, the data cannot be compromised since it remains encrypted, until Vault decrypts the data. The storage backend provides a durable data persistent layer where data is secured and available across server restarts. + +--- + +## Seal/Unseal + +- When a Vault server is started, it starts in a sealed state. In this state, Vault is configured to know where and how to access the physical storage, but doesn't know how to decrypt any of it. + +- Unsealing is the process of **obtaining the plaintext root key necessary to read the decryption key to decrypt the data**, allowing access to the Vault. + +- The data stored by Vault is encrypted. Vault needs the encryption key in order to decrypt the data. **The encryption key is also stored with the data** (in the keyring), but encrypted with another encryption key known as the root key. + +- Therefore, to decrypt the data, Vault must **decrypt the encryption key which requires the root key**. Unsealing is the process of getting access to this root key. The root key is stored alongside all other Vault data, but is encrypted by yet another mechanism: the unseal key. + +- To recap: + - most Vault data is encrypted using the 'encryption key in the keyring'; + - the keyring is encrypted by the root key; + - and the root key is encrypted by the unseal key. + +image + + +```go +// Keyring is used to manage multiple encryption keys used by +// the barrier. New keys can be installed and each has a sequential term. +// The term used to encrypt a key is prefixed to the key written out. + +// All data is encrypted with the latest key, but storing the old keys +// allows for decryption of keys written previously. Along with the encryption +// keys, the keyring also tracks the root key. + +// This is necessary so that when a new key is added to the keyring, +// we can encrypt with the root key and write out the new keyring. +type Keyring struct { + rootKey []byte + keys map[uint32]*Key + activeTerm uint32 + rotationConfig KeyRotationConfig +} +``` + +--- +**reference** +- https://developer.hashicorp.com/vault/docs/concepts/seal#seal-unseal +- https://github.com/hashicorp/vault \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/minio.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/minio.md" new file mode 100644 index 00000000..210c033d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/minio.md" @@ -0,0 +1,273 @@ +--- +title: 'minio' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/e22783af-abce-40a5-9b35-2d40adc17425) + +MinIO is a **High Performance Object Storage** released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads. + +## Architecture + +Each MinIO Tenant represents an independent MinIO Object Store within the Kubernetes cluster. The following diagram describes the architecture of a MinIO Tenant deployed into Kubernetes: + +![image](https://github.com/rlaisqls/TIL/assets/81006587/0a8a17d5-d6e3-4481-bb0f-7bf0e025dc35) + +MinIO provides multiple methods for accessing and managing the MinIO Tenant: + +## MinIO Console + +The MinIO Console provides a graphical user interface (GUI) for interacting with MinIO Tenants. The MinIO Operator installs and configures the Console for each tenant by default. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/48b6fbf5-4550-4189-b708-4d744108dc82) + +Administrators of MinIO Tenants can perform a variety of tasks through the Console, including user creation, policy configuration, and bucket replication. The Console also provides a high level view of Tenant health, usage, and healing status. + +## Deploy the MinIO Operator and Create a Tenant + +This procedure installs the MinIO Operator and creates a 4-node MinIO Tenant for supporting object storage operations in +a Kubernetes cluster. + +### Prerequisites + +#### MinIO Tenant Namespace + +MinIO supports no more than *one* MinIO Tenant per Namespace. The following `kubectl` command creates a new namespace +for the MinIO Tenant. + +```sh +kubectl create namespace minio-tenant-1 +``` + +The MinIO Operator Console supports creating a namespace as part of the Tenant Creation procedure. + +### Tenant Storage Class + +The MinIO Kubernetes Operator automatically generates Persistent Volume Claims (`PVC`) as part of deploying a MinIO +Tenant. + +The plugin defaults to creating each `PVC` with the `default` +Kubernetes [`Storage Class`](https://kubernetes.io/docs/concepts/storage/storage-classes/). If the `default` storage +class cannot support the generated `PVC`, the tenant may fail to deploy. + +MinIO Tenants *require* that the `StorageClass` sets `volumeBindingMode` to `WaitForFirstConsumer`. The +default `StorageClass` may use the `Immediate` setting, which can cause complications during `PVC` binding. MinIO +strongly recommends creating a custom `StorageClass` for use by `PV` supporting a MinIO Tenant. + +The following `StorageClass` object contains the appropriate fields for supporting a MinIO Tenant using +[MinIO DirectPV-managed drives](https://github.com/minio/directpv): + +```yaml +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: directpv-min-io +provisioner: kubernetes.io/no-provisioner +volumeBindingMode: WaitForFirstConsumer +``` + +### Tenant Persistent Volumes + +The MinIO Operator generates one Persistent Volume Claim (PVC) for each volume in the tenant *plus* two PVC to support collecting Tenant Metrics and logs. The cluster *must* have sufficient [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) that meet the capacity +requirements of each PVC for the tenant to start correctly. For example, deploying a Tenant with 16 volumes requires 18 (16 + 2). If each PVC requests 1TB capacity, then each PV must also provide *at least* 1TB of capacity. + +MinIO recommends using the [MinIO DirectPV Driver](https://github.com/minio/directpv) to automatically provision Persistent Volumes from locally attached drives. This procedure assumes MinIO DirectPV is installed and configured. + +For clusters which cannot deploy MinIO DirectPV, use [Local Persistent Volumes](https://kubernetes.io/docs/concepts/storage/volumes/#local). The following example YAML describes a local persistent volume: + +The following YAML describes a `local` PV: + +```yaml +apiVersion: v1 +kind: PersistentVolume +metadata: + name: +spec: + capacity: + storage: 1Ti + volumeMode: Filesystem + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: local-storage + local: + path: + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - +``` + +Replace values in brackets `` with the appropriate value for the local drive. + +You can estimate the number of PVC by multiplying the number of `minio` server pods in the Tenant by the number of drives per node. For example, a 4-node Tenant with 4 drives per node requires 16 PVC and therefore 16 PV. + +MinIO *strongly recommends* using the following CSI drivers for creating local PV to ensure best object storage +performance: + +- [MinIO DirectPV](https://github.com/minio/directpv) +- [Local Persistent Volume](https://kubernetes.io/docs/concepts/storage/volumes/#local) + +### Procedure + +#### 1) Install the MinIO Operator + +Run the following commands to install the MinIO Operator and Plugin using the Kubernetes ``krew`` plugin manager: + +```sh +kubectl krew update +kubectl krew install minio +``` + +See the ``krew`` [installation documentation](https://krew.sigs.k8s.io/docs/user-guide/setup/install/) for instructions on installing ``krew``. + +Run the following command to verify installation of the plugin: + +```sh +kubectl minio version +``` + +As an alternative to `krew`, you can download the `kubectl-minio` plugin from the [Operator Releases Page](https://github.com/minio/operator/releases). Download the `kubectl-minio` package appropriate for your operating system and extract the contents as `kubectl-minio`. Set the `kubectl-minio` binary to be executable (e.g. `chmod +x`) and place it in your system `PATH`. + +For example, the following code downloads the latest stable version of the MinIO Kubernetes Plugin and installs it to the system ``$PATH``. The example assumes a Linux operating system: + +```sh +wget -qO- https://github.com/minio/operator/releases/latest/download/kubectl-minio_linux_amd64_v1.zip | sudo bsdtar -xvf- -C /usr/local/bin +sudo chmod +x /usr/local/bin/kubectl-minio +``` + +Run the following command to verify installation of the plugin: + +```sh +kubectl minio version +``` + +Run the following command to initialize the Operator: + +```sh +kubectl minio init +``` + +Run the following command to verify the status of the Operator: + +```sh +kubectl get pods -n minio-operator +``` + +The output resembles the following: + +```sh +NAME READY STATUS RESTARTS AGE +console-6b6cf8946c-9cj25 1/1 Running 0 99s +minio-operator-69fd675557-lsrqg 1/1 Running 0 99s +``` + +The `console-*` pod runs the MinIO Operator Console, a graphical user +interface for creating and managing MinIO Tenants. + +The `minio-operator-*` pod runs the MinIO Operator itself. + +#### 2) Access the Operator Console + +Run the following command to create a local proxy to the MinIO Operator +Console: + +```sh +kubectl minio proxy -n minio-operator +``` + +The output resembles the following: + +```sh +kubectl minio proxy +Starting port forward of the Console UI. + +To connect open a browser and go to http://localhost:9090 + +Current JWT to login: TOKENSTRING +``` + +Open your browser to the provided address and use the JWT token to log in +to the Operator Console. + +![Operator Console](docs/images/operator-console.png) + +Click **+ Create Tenant** to open the Tenant Creation workflow. + +#### 3) Build the Tenant Configuration + +The Operator Console **Create New Tenant** walkthrough builds out a MinIO Tenant. The following list describes the basic configuration sections. + +- **Name** - Specify the *Name*, *Namespace*, and *Storage Class* for the new Tenant. + + The *Storage Class* must correspond to a [Storage Class](#default-storage-class) that corresponds + to [Local Persistent Volumes](#local-persistent-volumes) that can support the MinIO Tenant. + + The *Namespace* must correspond to an existing [Namespace](#minio-tenant-namespace) that does *not* contain any other + MinIO Tenant. + + Enable *Advanced Mode* to access additional advanced configuration options. + +- **Tenant Size** - Specify the *Number of Servers*, *Number of Drives per Server*, and *Total Size* of the Tenant. + + The *Resource Allocation* section summarizes the Tenant configuration + based on the inputs above. + + Additional configuration inputs may be visible if *Advanced Mode* was enabled + in the previous step. + +- **Preview Configuration** - summarizes the details of the new Tenant. + +After configuring the Tenant to your requirements, click **Create** to create the new tenant. + +The Operator Console displays credentials for connecting to the MinIO Tenant. You *must* download and secure these credentials at this stage. You cannot trivially retrieve these credentials later. + +You can monitor Tenant creation from the Operator Console. + +#### 4) Connect to the Tenant + +Use the following command to list the services created by the MinIO +Operator: + +```sh +kubectl get svc -n NAMESPACE +``` + +Replace `NAMESPACE` with the namespace for the MinIO Tenant. The output +resembles the following: + +```sh +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) +minio LoadBalancer 10.104.10.9 443:31834/TCP +myminio-console LoadBalancer 10.104.216.5 9443:31425/TCP +myminio-hl ClusterIP None 9000/TCP +myminio-log-hl-svc ClusterIP None 5432/TCP +myminio-log-search-api ClusterIP 10.102.151.239 8080/TCP +myminio-prometheus-hl-svc ClusterIP None 9090/TCP +``` + +Applications *internal* to the Kubernetes cluster should use the `minio` service for performing object storage operations on the Tenant. + +Administrators of the Tenant should use the `minio-tenant-1-console` service to access the MinIO Console and manage the Tenant, such as provisioning users, groups, and policies for the Tenant. + +MinIO Tenants deploy with TLS enabled by default, where the MinIO Operator uses the Kubernetes `certificates.k8s.io` API to generate the required x.509 certificates. Each certificate is signed using the Kubernetes Certificate Authority (CA) configured during cluster deployment. While Kubernetes mounts this CA on Pods in the cluster, Pods do *not* trust that CA by default. You must copy the CA to a directory such that the `update-ca-certificates` utility can find and add it to the system trust store to +enable validation of MinIO TLS certificates: + +```sh +cp /var/run/secrets/kubernetes.io/serviceaccount/ca.crt /usr/local/share/ca-certificates/ +update-ca-certificates +``` + +For applications *external* to the Kubernetes cluster, you must configure +[Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) or a +[Load Balancer](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer) to +expose the MinIO Tenant services. Alternatively, you can use the `kubectl port-forward` command +to temporarily forward traffic from the local host to the MinIO Tenant. + +--- +reference +- https://github.com/minio/operator/blob/master/README.md \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/openssl\353\241\234\342\200\205pemKey\342\200\205\353\247\214\353\223\244\352\263\240\342\200\205\354\240\221\354\206\215\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/openssl\353\241\234\342\200\205pemKey\342\200\205\353\247\214\353\223\244\352\263\240\342\200\205\354\240\221\354\206\215\355\225\230\352\270\260.md" new file mode 100644 index 00000000..e63bbef0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/tools/openssl\353\241\234\342\200\205pemKey\342\200\205\353\247\214\353\223\244\352\263\240\342\200\205\354\240\221\354\206\215\355\225\230\352\270\260.md" @@ -0,0 +1,33 @@ +--- +title: 'openssl로 pemKey 만들고 접속하기' +lastUpdated: '2024-03-02' +--- + +### 1. openssl 설치 + +```js +sudo apt install rpm +rpm -qa openssl +``` + +### 2. rsa secret key text 파일 생성 + +그냥 vi로 파일 만들어서 넣어줘도 된다. + +``` +vi (원하는 경로) +``` + +### 3. pem key 생성 + +아래 명령어로, 텍스트 파일 이름과 pem 키를 저장할 위치와 이름을 지정해준다. + +```js +openssl rsa -in {key text 파일} -text > {pem key 저장할 경로} +``` + +### 4. ssh로 접속 + +```js +ssh -i {pem key 경로} {username}@{url} -p {port} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/\353\215\260\353\270\214\354\230\265\354\212\244.md" "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/\353\215\260\353\270\214\354\230\265\354\212\244.md" new file mode 100644 index 00000000..945acebf --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\353\270\214\354\230\265\354\212\244\342\200\205DevOps/\353\215\260\353\270\214\354\230\265\354\212\244.md" @@ -0,0 +1,129 @@ +--- +title: '데브옵스' +lastUpdated: '2024-03-02' +--- + +**DevOps**는 `development`와 `operations`가 합쳐진 단어이며, 소프트웨어 개발과 배포 프로세스를 자동화하고 통합하는 개발방법론, 철학이다. 팀 지원, 팀 간 커뮤니케이션 및 공동 작업, 기술 자동화를 강조한다. + +DevOps는 소프트웨어 제공 프로세스를 효율적으로 수행하는 것을 목표로 한다. 이를 위해 워크플로를 자동화하고 신속하게 피드백하여 프로젝트를 더 잘 제어할 수 있도록 하는 여러 방법을 사용한다. + + + +## **DevOps 문화 철학** + +DevOps에서는 개발과 운영이라는 두 팀간의 장벽을 없애 개발자의 생산성과 운영의 안정성을 모두 최적화한다. 두 팀은 자주 **소통**하고, **효율성**을 높이고, 고객에게 제공하는 **서비스의 품질을 향상**하기 위해 최선을 다한다. 최종 고객의 요구와 자신이 어떻게 이러한 요구를 해결하는 데 공헌할 수 있는지 생각함으로써 일반적으로 명시된 역할 또는 직책의 범위를 넘어 서비스에 대한 완전한 주인의식을 갖는다. + +DevOps 모델을 사용하는 조직은 어떻게 구성되어 있든, 전체 개발 및 인프라 수명 주기를 스스로의 책임으로 간주하는 팀들로 구성돤다. + +## ****DevOps과 Agile**** + +DevOps는 Agile과 연장선에 있다. Agile의 목표가 Lean Manufacturing에 기반하여 빠른 소비자 피드백을 가질 수 있게 해준다면, DevOps는 분리되어있던 개발자와 운영자의 역할을 합쳐버리니 더욱 빠른 개발 및 배포 사이클을 가질 수 있다. + +Agile과 DevOps는 변화에 기민하게 대응하고, 시장에 신속히 제품을 릴리즈하기 위하여 짧은 배포(Release) 주기를 가진다는 점에서 동일한 지향점을 가진다. + +## **DevOps의 장점** + +데브옵스는 개발과 운영을 통합하여 제품 출시 및 조직의 효율성을 끌어올리기 위한 문화이다. DevOps를 잘 적용한다면, 기존의 소프트웨어 개발 및 인프라 관리 프로세스를 사용하는 조직보다 제품을 더 빠르게 혁신하고 개선할 수 있습니다. DevOps는 여러 측면에서 이점을 가지고있다. + + + +## **DevOps 생명주기** + +DevOps의 지속적인 특성때문에, DevOps를 따르는 조직은 일련의 과정을 계속해서 반복하게 된다. 이 과정의 사이클을 **DevOps 생명 주기**라고 부른다. DevOps 생명 주기는 개발 및 운영에 필요한 프로세스, 기능 및 도구를 나타내는 8단계로 구성된다. 각 단계에서 팀은 계속적으로 커뮤니케이션하여 의견을 조정하고, 속도 및 품질을 유지 관리한다. (Collaboraion And Communication) + + + + +(DevOps 생명주기. 왼쪽은 개발, 오른쪽은 운영에 관련된 프로세스이다.) + +## Plan + +계획 단계에서 DevOps 팀은 빌드하려는 애플리케이션 및 시스템의 기능과 기능을 아이디어, 정의하고 설명한다. 관계자와 고객으로부터 수집된 요구사항과 피드백을 정리하여 로드맵을 작성하고, 전체적인 애플리케이션을 여러 세부적인 단계로 나누어 계획한다. Plan 단계에서는 아래와 같은 활동들을 할 수 있다. + +- 백로그를 만든다. +- 버그를 추적한다. +- 스크럼을 사용하여 Agile 소프트웨어 개발을 관리한다. +- Kanban 보드를 사용한다. +- 대시보드를 사용하여 진행률을 시각화한다. + +**사용 Tool** : `Jira`, `Git` + +## Code + +Code는 개발 환경을 준비하고 코드를 작성하는 단계이다. 이 단계에서 코드를 작성할 때는 일관된 코드 스타일을 적용하며 일반적인 보안 결함 및 코드 안티 패턴을 방지한다. + +이는 코드베이스에 어느 정도 일관성을 제공하여 협업을 지원하면서 개발자에게 좋은 코딩 방법을 가르치는 데 도움이 된다. 또한 이러한 도구는 나중에 파이프라인에서 테스트에 실패할 수 있는 문제를 해결하여 빌드 실패를 줄이는 데 도움된다. + +**사용 Tool** : `GitHub`, `GitLab`, `Bitbucket`, `Stash` + +## Build + +Build는 DevOps가 실제로 시작되는 단계이다. 버전 제어 툴을 사용하여 코드를 공동으로 작업하고, 그 코드를 합쳐 빌드한다. 새로운 코드 기능을 기존 소스 코드와 지속적으로 통합하는 CI(지속적 통합)와 같은 의미를 가진다. + +**사용 Tool** : `Docker`, `Ansible`, `Puppet`, `Chef`, `Gradle`, `Maven`, *`JFrog Artifactory`* + +## Test + +Test는 코드에 버그 및 오류가 포함되어있는지 확인하는 단계다. 여기서 품질 분석(QA)은 개발된 소프트웨어의 사용성을 확인하는 중요한 역할을 한다. QA 프로세스의 성공적인 완료는 소프트웨어가 클라이언트의 사양을 충족하는지 여부를 결정하는 데 중요하다. + +Test 단계에서는 JUnit, Selenium 및 TestNG와 같은 자동화 도구를 사용할 수 있다. 이렇게 하면 개발된 소프트웨어에 결함이 없는지 빠르게 확인하는 것이 가능한다. + +**사용 Tool** : `JUnit`, `Codeception`, `Selenium`, `Vagrant`, `TestNG`, `BlazeMeter` + +## Release + +Release는 빌드된 코드가 프로덕션 환경에 배포할 준비가 되었다고 말하는 시점으로, DevOps 파이프라인의 이정표이다. 이 단계에서 각 코드 변경은 일련의 수동 및 자동화 테스트를 통과했으며 큰 버그가 일어날 가능성이 낮다고 확신할 수 있다. + +조직의 DevOps 성숙도에 따라 파이프라인의 이 단계에 도달하는 모든 빌드를 자동으로 배포하도록 선택할 수 있다. 조직이 여러 기능을 빠르게 배포하기 위해선 이 단계에서 CI/CD를 적용한다. + +## Deploy + +Deploy는 코드가 실제로 프로덕션에 배포되는 단계이다. 릴리스 프로세스를 자동화하여 중단 기간 없이 릴리스를 안정적으로 만들 수 있는 도구나 프로세스를 사용할 수 있다. + +테스트 환경을 구축한 것과 동일한 코드로서의 인프라를 프로덕션 환경을 구축하도록 구성할 수 있다. 테스트 환경이 성공적으로 구축되었다는 것을 이미 알고 있으므로 프로덕션 릴리스가 차질 없이 진행될 것이라고 안심할 수 있다. + +**사용 Tool** : `Puppet`, `Chef`, `Ansible`, `Jenkins`, `Kubernetes`, `Docker`, `Jira` + +## Operate + +새 릴리스가 출시되어 고객이 프로덕트를 사용할 수 있는 단계이다. 이 단계에서 Devops 관리자는 해당 서비스를 운영하고 관리해야한다. + +조직은 서비스에 대한 고객의 피드백을 받고, 이 피드백을 수집·분류하여 제품의 향후 개발을 구체화할 수 있도록 여러 도구를 사용하기도 한다. + +**사용 Tool** : `Anabilities`, `Puppet`, `PowerShell`, `Chef`, `Salt`, `Otter` + +## Monitor + +Monitor는 시스템이 잘 돌아가고 있는지 감시하는 단계이다. + +만약 서비스의 장애가 비즈니스에 영향을 준다는 생각이 든다면 애플리케이션 모니터링을 통해 MTTR(mean time to recovery, 복구에 들어가는 시간)을 줄여나가야한다. 애플리케이션 모니터링 서비스를 사용하지 않는 상황에서는 장애 발생 확인이 실시간일 확률이 낮다. + +애플리케이션(및 환경)의 추세와 전반적인 상태를 이해하려면 연중무휴 24시간 데이터를 리스닝하고 기록하는 소프트웨어가 필요하다. Mornitor 단계에서는 장애 시간의 동시 사용자 수, 초당 트랜잭션의 개수, 평균 응답시간, CPU 부하율, 사용된 커넥션의 개수, 쿼리의 응답시간 등의 정보를 항상 확인하여 지속적인 가시성은 성공적인 DevOps 팀을 위한 핵심 기능이다. + +**사용 Tool** : `Anabilities`, `Puppet`, `PowerShell`, `Chef`, `Salt`, `Otter` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Change\342\200\205Date\342\200\205Capture.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Change\342\200\205Date\342\200\205Capture.md" new file mode 100644 index 00000000..cc18ff30 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Change\342\200\205Date\342\200\205Capture.md" @@ -0,0 +1,106 @@ +--- +title: 'Change Date Capture' +lastUpdated: '2024-03-02' +--- + +Traditionally, businesses used batch-based approaches to move data once or several times a day, However, vaych movement introduces latency and reduces the operational value to the organization. + +But for now, Change Data Capture (CDC) has emerged as an ideal solutino for near real-time movement of data from relational databases to data warehouses, data lakes or other databases. + +Change Data Capture is a software process **that identifies and tracks changes to data in a database**. **CDC provides real-time or near-real-time movement of data by moving and processing data continuously as new database events occur.** + +image + +In high-velocity data environments where time-sensitive decisions are made, Change Data Capture is an excellent fit to achieve low-latency, relable, and scalable data replication. Change Data Capture is also ideal for zero downtime migrations to the cloud. + +## Change Data Capture Methods + +There are multiple common Change Data Capture methods that you can implement depending on your application requirements and tolerance for performance overhead. Here are the common methods, how they work, and their advantages as well as shortcomings. + +### Audit Columns + +By using existing `“LAST_UPDATED”` or `“DATE_MODIFIED”` columns, or by adding them if not available in the application, you can create your own change data capture solution at the application level. This approach retrieves only the rows that have been changed since the data was last extracted. + +The CDC logic for the technique would be: + +1. Get the maximum value of both the target (blue) table’s `Created_Time` and `Updated_Time` columns + +2. Select all the rows from the data source with `Created_Time` greater than (>) the target table’s maximum `Created_Time`, which are all the newly created rows since the last CDC process was executed. + +image + +3. Select all rows from the source table that have an `Updated_Time` greater than (>) the target table’s maximum `Updated_Time` but less than (<) its maximum `Created_Time`. + The reason for the exclusion of rows less than the maximum target create date is that they were included in step 2. + +4. Insert new rows from step 2 or modify existing rows from step 3 in the target. + +Pros of this method + +- It can be built with native application logic +- It doesn’t require any external tooling + +Cons of this method + +- Adds additional overhead to the database +- DML statements such as deletes will not be propagated to the target without additional scripts to track deletes +- Error prone and likely to cause issues with data consistency +- This approach also requires CPU resources to scan the tables for the changed data and maintenance resources to ensure that the DATE_MODIFIED column is applied reliably across all source tables. + +### Table Deltas + +You can use table delta or ‘tablediff’ utilities to compare the data in two tables for non-convergence. Then you can use additional scripts to apply the deltas from the source table to the target as another approach to change data capture. There are [several examples of SQL scripts](https://www.mssqltips.com/sqlservertip/2779/ways-to-compare-and-find-differences-for-sql-server-tables-and-data/) that can find the difference of two tables. + +image + +Advantages of this approach: + +- It provides an accurate view of changed data while only using native SQL scripts + +Disadvantage of this approach: + +- Demand for storage significantly increases because you need three copies of the data sources that are being used in this technique: the original data, previous snapshot, and current snapshot +- It does not scale well in applications with heavy transactional workloads + +Although this works better for managing deleted rows, the CPU resources required to identify the differences are significant and the overhead increases linearly with the volume of data. The diff method also introduces latency and cannot be performed in real time. + +Some log-based change data capture tools come with the ability to [analyze different tables](https://www.striim.com/docs/en/creating-a-data-validation-dashboard.html) to ensure replication consistency. + +### Trigger-based CDC + +Another method for building change data capture at the application level is defining database triggers and creating your own change log in shadow tables. Triggers fire before or after INSERT, UPDATE, or DELETE commands (that indicate a change) and are used to create a change log. Operating at the SQL level, some users prefer this approach. Some databases even have [native support for triggers](https://docs.microsoft.com/en-us/sql/t-sql/statements/create-trigger-transact-sql?view=sql-server-ver15#:~:text=a%20trigger%20is%20a%20special,on%20a%20table%20or%20view.). + +However, triggers are required for each table in the source database, and they have greater overhead associated with running triggers on operational tables while the changes are being made. In addition to having a significant impact on the performance of the application, maintaining the triggers as the application change leads to management burden. + +Advantages of this approach: + +- Shadow tables can provide an immutable, detailed log of all transactions +- Directly supported in the SQL API for some databases + +Disadvantage of this approach: + +- Significantly reduces the performance of the database by requiring multiple writes to a database every time a row is inserted, updated, or deleted +- Many application users do not want to risk the application behavior by introducing triggers to operational tables. DBAs and data engineers should always heavily test the performance of any triggers added into their environment and decide if they can tolerate the additional overhead. + +### Log-Based Change Data Capture + +Databases contain transaction logs (also called redo logs) that store all database events allowing for the database to be recovered in the event of a crash. With [log-based change data capture](https://www.striim.com/blog/log-based-change-data-capture/), new database transactions – including inserts, updates, and deletes – are read from source databases’ native transaction logs. + +image + +The changes are captured without making application level changes and without having to scan operational tables, both of which add additional workload and reduce source systems’ performance. + +Advantages of this approach + +- **Minimal impact on production database system** – no additional queries required for each transaction +- Can maintain ACID reliability across multiple systems +- No requirement to change the production database system’s schemas or the need to add additional tables + +Challenges of this approach + +- Parsing the internal logging format of a database is complex – most databases do not document the format nor do they announce changes to it in new releases. This would potentially require you to change your database log parsing logic with each new database release. +- Would need system to manage the source database change events metadata +- Additional log levels required to produce scannable transaction logs can add marginal performance overhead + +--- +reference +- https://www.striim.com/blog/change-data-capture-cdc-what-it-is-and-how-it-works/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Column,\342\200\205Row\352\270\260\353\260\230\342\200\205DB.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Column,\342\200\205Row\352\270\260\353\260\230\342\200\205DB.md" new file mode 100644 index 00000000..050daafe --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Column,\342\200\205Row\352\270\260\353\260\230\342\200\205DB.md" @@ -0,0 +1,46 @@ +--- +title: 'Column, Row기반 DB' +lastUpdated: '2024-03-02' +--- + +Row(행) 기반방식은 **전통적인 데이터베이스에서 사용하는 레코드 단위 기반의 저장방식**이다. 반면 Column(열) 기반방식은 **대용량 데이터를 처리하는 데이터베이스에서 사용하는 열 단위 기반의 저장 방식**이다. 그러면 이 두 가지 방식이 어떤 차이점이 있는지 자세히 살펴보자. + +​아래와 같은 데이터가 들어있는 테이블이 있다고 가정했을 때에 전통적인 방식의 Row 기반방식은 각각의 행이 고유한 ID 값을 가지고 있게 된다. 데이터베이스가 특정 쿼리에 의해서 데이터를 조회하면 레코드 별로 부여되어 있는 고유한 ID를 찾고 그 레코드의 하나의 행에 종속되어 있는 모든 열의 데이터를 모두 불러온다. 그러면 **정작 실제로는 필요하지 않은 열까지도 모두 읽게 되는 비효율적인 동작이 발생**하는 것이다. + +|student_id|name|class_id|gender| +|-|-|-|-| +|1|홍길동|1|남자| +|2|고양이|1|남자| +|3|강아지|2|남자| +|4|김은빈|3|여자| + +|class_id|class_name| +|-|-| +|1|맑음반| +|2|햇살반| +|3|구름반| + +그에 비해서 Column 기반 처리방식의 데이터베이스에서는 **테이블의 각각의 열들이 모두 고유한 파일 안에 저장**된다. + +예를 들어서 학생 테이블 안에서 `student_id` 정보는 하나의 파일에 저장이 되고, 각각의 `class_id`는 또 다른 하나의 파일에 묶여서 저장이 되는 것이다. + +이렇게 열별로 묶어서 데이터를 저장하게 되면, 대량의 테이블에 쿼리를 사용하게 될 때에 고유한 레코드 Id에 종속되어 있는 모든 열을 가져올 필요가 없고 쿼리에서 요구 되는 열들만 읽어들일 수 있기 때문에 Row 기반 처리 방식에 비해서 **높은 성능을 발휘**할 수 있다. + +## 성능 + +각 방식의 성능상 특징을 살펴보면 다음과 같다. + +**Row기반 DB** +- 매일 발생하는 대량의 트랜잭션을 지연 없이 처리하기 위해 데이터 추가를 행 단위로 효율적으로 하는 구조이다. +- 데이터 검색을 고속화하기 위해 인덱스(index)를 사용한다. 인덱스가 없다면 모든 데이터를 로드해야 원하는 레코드를 찾을 수 있기 때문에 테이블 풀스캔이 발생하여 성능이 저하될 수 있다. +- 레코드 단위로 데이터가 저장되어 있기 때문에 필요 없는 열까지 디스크로부터 로드될 수 있다. + +**Column기반 DB** +- 칼럼 단위의 읽고 쓰기에 최적화되어 있다. +- 열별로 데이터가 저장되어 있기 때문에, 데이터 분석시 필요한 열만을 로드 하여 디스크 I/O를 줄일 수 있다. +- 같은 열에는 비교적 유사한 데이터가 반복되기 때문에, 매우 작은 용량으로 압축 할 수 있다. +- 압축되지 않은 행 지향 DB와 비교하여 1/10 이하로 압축 가능하다. + +데이터를 어떻게 사용하는지, 데이터의 양의 어느정도인지에 따라(레코드 수에 따라) 다른 DB를 쓰는게 좋다. + +정확한 비교는 테이블 구조 및 트랜잭션 유형에 따라 다르겠지만, 통상적으로는 수천만개정도의 데이터가 있다면 **RDB**, 수억개 이상의 데이터가 있다면 MPP(Column 지향) DB를 쓰는게 더 좋다고 한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/DBMS\354\231\200\342\200\202RDBMS.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/DBMS\354\231\200\342\200\202RDBMS.md" new file mode 100644 index 00000000..dd9b56f6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/DBMS\354\231\200\342\200\202RDBMS.md" @@ -0,0 +1,22 @@ +--- +title: 'DBMS와 RDBMS' +lastUpdated: '2022-11-22' +--- +## 💾 DBMS(Database Management System) +

+넓은 의미에서의 데이터베이스는 일상적인 정보들을 모아 놓은 것 자체를 의미한다. 일반적으로 DB라고 말할 떄는 특정 기업이나 조직 또는 개인이 필요한 데이터를 일정한 형태로 저장해 놓은 것을 의미한다. +

+

+사용자들은 보다 효율적인 데이터 관리뿐만 아니라 예기치 못한 사건으로 인한 데이터의 손상을 피하고, 필요할 때 데이터를 복구하기 위한 강력한 기능의 소프트웨어를 필요로 한다. 이러한 요구사항을 만족시켜주는 시스템을 데이터베이스 관리 시스템(DBMS)이라고 한다. +

+ +## 💾 RDBMS(Relational Database Management System) +

+관계형 데이터베이스는 정규화 이론에 근거한 합리적인 데이터 모델링을 통해 데이터 이상(Anomaly) 현상 및 불필요한 데이터 중복 현상을 피할 수 있다. 이러한 RDB를 관리하는 시스템 소프트웨어를 관계현 데이터베이스 관리 시스템(RDBMS)이라고 한다. +

+ +### RDBMS의 주요 기능 +- 동시성 관리 및 병행 제어를 통해 많은 사용자들이 동시에 데이터를 공유 및 조작할 수 있는 기능을 제공한다. +- 메타 데이터를 총괄 관리할 수 있기 때문에 데이터의 성격, 속성 또는 표현 방법 등을 체계화 할 수 있고, 데이터 표준화를 통한 데이터 품질을 확보할 수 있는 장점이 있다. +- 인증된 사용자만이 참조할 수 있도록 보안 기능을 제공하고, 테이블 생성 시에 사용할 수 있는 다양한 제약조건을 이용하여 사용자 실수로 인한 잘못된 데이터 입력 및 관계성이 있는 중요 데이터의 삭제를 방지하여 데이터 무결성을 보장한다. +- 시스템의 갑작스러운 장애로부터 사용자가 입력/수정/삭제하는 데이터가 데이터베이스에 제대로 반영될 수 있도록 보장해주는 기능과, 시스템 ShutDown, 재해등의 상황에서도 데이터를 화복/복구할 수 있는 기능을 제공한다. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\215\260\354\235\264\355\204\260\353\252\250\353\215\270\353\247\201.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\215\260\354\235\264\355\204\260\353\252\250\353\215\270\353\247\201.md" new file mode 100644 index 00000000..09f427a7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\215\260\354\235\264\355\204\260\353\252\250\353\215\270\353\247\201.md" @@ -0,0 +1,36 @@ +--- +title: '데이터모델링' +lastUpdated: '2024-03-02' +--- + +모델링이란 복잡한 현실세계를 추상화, 단순화, 명확화하기 위해 일정한 표기법으로 모델을 표현하는 기법이다. + +마찬가지로 데이터 모델링은 비즈니스를 IT 시스템으로 구축하기 위해 데이터 관점으로 업무를 분석하는 기법으로써, 약속된 표기법으로 데이터의 구조를 표현하는 과정이다. 즉 IT 시스템의 근간이 되는 데이터베이스를 구축하기 위한 분석 및 설계의 과정이라고 할 수 있다. + +#### 데이터 모델이 중요한 이유 + +|이유|설명| +|-|-| +|파급효과
(Leverage)|데이터 설계 과정에서 비효율적인 데이터 설계 및 업무 요건을 충족하지 못하는 데이터 설계를 한다면 개발/테스트/오픈/운영의 전 과정에 걸쳐서 엄청난 비용이 발생할 수 있다.| +|복잡한 정보 요구사항의 간결한 표현
(Conciseness)|좋은 데이터 모델 설계를 통해 IT 시스템에서 구현해야 할 정보 요구사항을 명확하고, 간결하게 표현할 수 있다.| +|데이터 품질(Data Quality)|데이터 모델의 잘못된 설계로 인해 데이터 중복, 비유연성, 비일관성이 발생하여 데이터 품질이 저하될 수 있다.| + +## 데이터 모델링 단계 + + + +데이터 모델링은 3단계에 걸쳐서 진행된다. + +1. 개념적 데이터 모델링 + +IT 시스템에서 구현하고자 하는 대상에 대해 포괄적 수준의 데이터 모델링을 진행한다. + +2. 논리적 데이터 모델링 + +IT 시스템에서 구현하고자 하는 비즈니스를 만족하기 위한 기본키, 속성, 관계, 외래키 등을 정확하게 표현하는 단계이다. + +3. 논리적 데이터 모델링 + +논리 데이터 모델을 기반으로 실제 물리 DB 구축을 위해 성능, 저장공간 등의 물리적인 특성을 고려하여 설계하는 단계이다. + + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\254\264\352\262\260\354\204\261\342\200\202\354\240\234\354\225\275\354\241\260\352\261\264.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\254\264\352\262\260\354\204\261\342\200\202\354\240\234\354\225\275\354\241\260\352\261\264.md" new file mode 100644 index 00000000..617071cb --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\254\264\352\262\260\354\204\261\342\200\202\354\240\234\354\225\275\354\241\260\352\261\264.md" @@ -0,0 +1,18 @@ +--- +title: '무결성 제약조건' +lastUpdated: '2024-03-02' +--- +## 무결성이란? + 테이블에 중복된 데이터가 존재하거나, 부모와 자식 데이터 간의 논리적 관계가 깨지면 프로그램에 큰 장애가 발생할 수 있다. 데이터 무결성은 이러한 일이 일어나지 않도록 데이터의 정확성, 일관성, 유효성을 유지되는 것을 의미하며, DBMS에서 꼭 신경써야 할 사항이다. 그렇기 때문에 DBMS는 무결성을 지키기 위해 제약조건이라는 기능을 기본적으로 제공한다. + +## 무결성 제약조건의 종류 + +### 1. 개체 무결성(Entity integrity) : 기본키는 null 값이 될 수 없음 + 각 튜플에 접근하기 위해 정의된 기본키가 null값이 되면 튜플의 유일성을 판단할 수 없기 때문에 기본키는 null이 되면 안된다. + +### 2. 참조 무결성(Referential integrity) : 외래키는 참조할 수 있는 값을 가져야함 + 존재하지 않는 값을 참조하는 외래키가 존재하면 두 릴레이션의 관계를 표현할 수 없다. 그렇기 때문에 외래키는 다른 테이블에 존재하는 유효한 값을 참조해야한다. + +### 3. 도메인 무결성(Domain integrity) : 속성의 값은 정의된 사항에 맞아야함 + DB의 정보를 알맞게 읽고 사용하기 위해선 각각의 속성이 갖는 도메인에 맞는 데이터를 삽입해야만 한다. 도메인은 숫자, 또는 문자 등 데이터형을 범위로 가질 수도 있고, '남', '여' 등 특정 값을 범위로 가질 수도 있다. + \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\266\204\354\202\260\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\266\204\354\202\260\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244.md" new file mode 100644 index 00000000..b85c9528 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\353\266\204\354\202\260\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244.md" @@ -0,0 +1,46 @@ +--- +title: '분산데이터베이스' +lastUpdated: '2024-03-02' +--- + +분산 데이터베이스는 여러 곳으로 분산되어 있는 데이터베이스를 하나의 가상 시스템으로 사용할 수 있도록 한 데이터베이스이다. 논리적으로는 동일한 시스템에 속하지만, 컴퓨터 네트워크를 통해 물리적으로 분산하여 저장되어있는 데이터들의 모임을 지칭한다. + +## 투명성 + +분산 데이터베이스의 투명성(Transparency)은 해당 데이터베이스를 사용하는 사용자가 데이터베이스 시스템이 분산되어있는 것을 인식하지 못하고 자신만의 데이터베이스 시스템을 사용하는 것으로 인식하도록 만드는 것이다. 네트워크로 치면 다중 서버환경을 떠올리면 된다. + +분산 데이터베이스의 투명성은 여러가지 의미를 지닐 수 있다. 외울 필요는 없고, 분산 데이터베이스의 투명성이 어떤 측면에서, 어떻게 지켜져야하는지에 대한 대략적인 그림을 그릴 수 있으면 충분하다. + +1. 분할 투명성(단편화)
+하나의 논리적 Relation(테이블)이 여러 단편으로 분할되어 각 단편의 사본이 여러 Site에 저장된다. 하지만 사용하는 한 곳에 위치하는 것으로 인식해야한다. +2. 위치 투명성
+사용하려는 데이터의 저장 장소를 명시할 필요가 없다. 위치정보가 System Catalog에 유지되기 때문에, 유저는 DB의 위치를 몰라도 요청을 보낼 수 있다. +
또한, 지역 DBMS(서브로 사용되는 DB)와 물리적 DB 사이의 Mapping을 보장한다. +3. 장애 투명성
+구성요소(DBMS, Computer)의 장애에 무관한 Transaction의 원자성을 유지한다. 분산 DB에서 에러가 나더라도 트랜잭션의 조건이 깨지지 말아야한다. +4. 병행 투명성
+다수 Transaction이 동시에 수행 시 결과의 일관성을 유지해야 한다. Locking과 TimeStamp 방법을 주로 이용한다. + +## 장단점 + +#### 장점 +분산데이터베이스는 시스템의 용량을 Scale Out으로 확장시킬 수 있도록 하고, 규모를 조절하기 쉽다. 더 많은 데이터를 저장하여 각 지역 사용자의 요구를 수용할 수 있다. + +또한 다른 DB에서 장애가 발생했을때 다른 지역에 저장되어있는 정보로 복구할 수 있기 때문에 데이터를 보다 안전하게 지킬 수 있다. + +#### 단점 +하지만 소프트웨어 개발 비용이 증가하고, 설계 및 관리가 어려우며 DB의 응답 속도가 불규칙적이어질 수 있다는 단점이 있다. + +그리고 데이터베이스가 여러 곳에 있기 때문에 데이터 무결성이 깨질 위험도 더 크다. 이로 인해 시스템에 오류가 생길 수도 있다. + +## 적용 기법 + +분산 데이터베이스의 적용 기법은 테이블 위치 분산, 테이블 분할 분산, 테이블 복제 분산, 테이블 요약 분산 4가지가 있다. + +|기법|설명| +|-|-| +|테이블 위치 (Location) 분산|설계된 테이블의 위치를 각각 다르게 위치시키는 것이다.
(ex. 유저는 본사 DB에, 상품은 지사 DB에 저장)| +|테이블 분할 (Fragmentation) 분산|각각의 테이블을 쪼개어 분산하는 방법이다. 수평분할과 수직분할, 두가지의 방법을 사용할 수 있다.| +|테이블 복제 (Replication) 분산|동일한 테이블을 다른 지역이나 서버에서 동시에 생성하여 관리하는 유형이다.
- 부분복제: 통합된 테이블을 한 군데(본사)에 가지고 있으면서 각 지사별로는 **지사에 해당하는(자주 사용되는) Row**를 가지고 있는 형태이다.
- 광역복제: 통합된 테이블을 한 군데(본사)에 가지고 있으면서 각 지사에도 본사와 **동일한 데이터**를 모두 가지고 있는 형태이다.| +|테이블 요약 (Summarization) 분산|지역 또는 서버간에 서로 다른 유형의 데이터를 적절한 형태로 요약하여 저장하는 경우이다.
- 분석요약: 각 지사별로 존재하는 **요약정보**를 본사에 통합하고, 전체를 모은 정보에 대한 요약을 산출하는 분산방법이다.
- 통합요약: 각 지사별로 존재하는 **다른 내용의 정보**를 본사에 통합하여 다시 전체에 대해 요약정보를 산출하는 방법이다.| + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\354\212\244\355\202\244\353\247\210.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\354\212\244\355\202\244\353\247\210.md" new file mode 100644 index 00000000..c522c7ab --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\354\212\244\355\202\244\353\247\210.md" @@ -0,0 +1,33 @@ +--- +title: '스키마' +lastUpdated: '2024-03-02' +--- + +스키마는 데이터베이스의 구조와 제약 조건에 관한 전반적인 명세를 기술한 메타데이터의 집합이다. + +스키마는 데이터베이스를 구성하는 데이터 개체(Entity), 속성(Attribute), 관계(Relationship) 및 데이터 조작 시 데이터 값들이 갖는 제약 조건 등에 관해 전반적으로 정의한다. + +사용자의 관점에 따라 외부 스키마, 개념 스키마, 내부 스키마로 나눠진다. + +![image](https://user-images.githubusercontent.com/81006587/199009979-df44bf3e-97da-45d2-b5fc-03bec89d9059.png) + + +## 외부 스키마(External Schema) + +외부스키마는 사용자나 응용프로그래머 각 **개인의 입장에서 필요로 하는 데이터베이스의 논리적 구조**를 정의한 것이다. 외부스키마는 전체 데이터베이스의 한 논리적인 부분으로 볼 수 있으므로 서브 스키마(Sub Schema)라고도 한다. + +하나의 데이터베이스 시스템에는 여러개의 외부 스키마가 존재할 수 있으며 하나의 외부 스키마를 여러개의 응용 프로그램이나 사용자가 같이 사용할 수도 있다. + +같은 데이터베이스에 대해서도 서로 다른 관점을 정의할 수 있도록 허용한다. + +## 개념 스키마(Conceptual Schema) + +개념 스키마는 데이터베이스의 **전체적인 논리적 구조**로서, 모든 응용 프로그램이나 사용자들이 필요로 하는 데이터를 종합한 조직 전체의 데이터베이스로 딱 하나만 존재한다. + +개념스키마는 개체간의 관계와 제약 조건을 나타내고 데이터베이스의 접근 권한, 보안 및 무결성 규칙에 관한 명세를 정의한다. 데이터베이스 파일에 저장되는 데이터의 형태를 나타내는 것으로, 단순히 스키마(Schema)라고 하면 개념 스키마를 의미한다. + +## 내부 스키마(Internal Schema) + +내부 스키마는 **물리적 저장장치의 입장에서 본 데이터베이스 구조**로, 물리적인 저장장치와 밀접한 계층이다. + +내부스키마는 실제로 데이터베이스에 저장될 레코드의 물리적인 구조를 정의하고, 저장 데이터 항목의 표현방법, 내부 레코드의 물리적 순서 등을 나타낸다. 시스템 프로그래머나 시스템 설계자가 보는 관점의 스키마이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\354\240\225\352\267\234\355\231\224\354\231\200\342\200\202\353\260\230\354\240\225\352\267\234\355\231\224.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\354\240\225\352\267\234\355\231\224\354\231\200\342\200\202\353\260\230\354\240\225\352\267\234\355\231\224.md" new file mode 100644 index 00000000..60d6ce68 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\354\240\225\352\267\234\355\231\224\354\231\200\342\200\202\353\260\230\354\240\225\352\267\234\355\231\224.md" @@ -0,0 +1,57 @@ +--- +title: '정규화와 반정규화' +lastUpdated: '2022-11-22' +--- +## 💾 정규화(Normalization)란? + - 정규화는 데이터의 일관성을 지키고 중복을 최소화하기 위하여 데이터를 분해하는 과정이다. + - 데이터베이스 변경시의 이상현상을 제거하고, 구조를 확장하기 쉽도록 하는 것이 목표이다. + + +## 정규형 +|정규형|설명| +|-|-| +|제1정규형|속성이 원자성을 가진다. 한 속성이 여러 개의 속성값을 갖거나 같은 유형의 속성이 여러 개 인 경우 해당 속성을 분리한다.| +|제2정규형|복합 후보키가 있을때, 후보키에 속하지 않는 속성을 결정하기 위해서 후보키의 전체를 참조해야한다. **부분 함수 종속성**을 제거한다.| +|제3정규형|일반 속성들 간의 함수 종속 관계가 존재하지 않는다. **이행 함수 종속성**을 제거한다.| +|BCNF|식별자로 쓰이는 속성이 일반속성에 종속되지 않는다. 후보키가 기본키를 종속시키면 분해한다.| +|제4정규형|여러 칼럼들이 하나의 칼럼을 종속시키지 않는다. 다치종속성을 제거한다.| +|제5정규형|조인에 의한 종속성이 발생되지 않는다.| + +--- + +## 💾 반정규화(De-Normalization)란? + - 데이터베이스의 성능 향상을 위하여, 데이터 중복을 허용하고 조인을 줄이는 데이터베이스 성능 향상 방법이다. + - 반정규화는 조회(select) 속도를 향상시키지만, 데이터 모델의 유연성은 낮아진다. + + +## 가. 테이블 반정규화 + +|기법|설명| +|-|-| +|테이블 통합|조인으로 연결되어 있는 두 테이블이 자주 묶여서 사용될때 고려한다.| +|테이블 수평분할|레코드별로 사용 빈도의 차이가 클때 사용빈도에 따라 테이블을 분할한다.| +|테이블 수직분할|하나의 테이블에 속성이 너무 많을때 속성을 기준으로 테이블을 분할한다.| +|중복 테이블 추가|다른 업무나 서버에서 같은 데이터를 써야할때 똑같은 테이블을 여러개 만들어서 조인 횟수를 줄인다.| +|통계테이블 추가|SUM, AVG등 자주 쓰이는 값을 미리 계산해서 저장한다.| +|이력 테이블 추가|변경 이력, 발생 이력 정보를 위한 최신 정보 컬럼을 추가한다.| +|부분 테이블 추가|자주 이용되는 칼럼들을 모아놓은 별도의 테이블을 생성한다.| + + +## 나. 컬럼 반정규화 + +|기법|설명| +|-|-| +|중복 컬럼추가|갱신보다 조회 성능이 더 중요할때 중복된 데이터를 추가한다.| +|파생 컬럼추가|자주 계산되는 SUM이나 AVG값들을 미리 칼럼에 보관한다.| +|이력테이블 컬럼추가|대량의 이력 데이터를 처리할때의 성능 저하를 예방하기 위해 기능성 컬럼(최근값 여부, 시작과 종료일자 등)을 추가한다.| +|PK에 의한 컬럼 추가|PK안에 데이터가 존재하더라도 성능향상을 위해 일반속성으로 같이 저장한다.| +|응용시스템 오작동 대비 컬럼 추가|데이터 처리 중 원래 값으로 복구하기를 원할 경우를 대비하여 이전 데이터를 임시적으로 중복으로 보관한다.| + + +## 다. 관계 반정규화 + +|기법|설명| +|-|-| +|중복관계 추가|여러 경로를 거쳐 조인이 가능하지만 추가적인 관계를 더 맺어 빠르게 접근할 수 있게 한다.| + + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\355\205\214\354\235\264\353\270\224\353\266\204\355\225\240.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\355\205\214\354\235\264\353\270\224\353\266\204\355\225\240.md" new file mode 100644 index 00000000..58784ec7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\355\205\214\354\235\264\353\270\224\353\266\204\355\225\240.md" @@ -0,0 +1,31 @@ +--- +title: '테이블분할' +lastUpdated: '2024-03-02' +--- + +한 테이블에 대량의 데이터가 저장된다면, 용량(storage)의 한계와 성능(performence)의 저하가 발생한다. + +수평/수직 분할 설계는 테이블 구조를 행또는 열을 기준으로 분할함으로써 로우체이닝, 로우마이그레이션 등의 성능 저하를 예방하는 기법이다. + +|현상|설명| +|-|-| +|로우체이닝
(Row Chaining)|Row의 길이가 너무 길어서 데이터 블록 하나에 데이터가 모두 저장되지 않고, 2개 이상의 블록에 걸쳐 하나의 로우가 저장되어 있는 형태이다. 하나의 행을 읽을 때 여러개의 데이터 블록을 읽어야 하기 때문에 과정에서 성능이 저하된다.| +|로우 마이그레이션
(Row Migration)|데이터 블록에서 수정이 발생하면 수정된 데이터를 해당 데이터 블록해서 저장하지 못하는 것을 의미한다. | + +## 수평분할(Sharding) + +수평분할은 테이블을 행 단위로 분할하여 Input/Output을 감소시키는 방법이다. + +수평분할의 대표적인 기법으로는 행을 데이터의 값 범위를 기준으로 나누는 Range Partitioning과 PK를 모듈러 연산한 결과로 DB를 특정하는 Modular Partitioning, 해시값을 기준으로 나누는 Hash Partitioning 세가지가 있다. + +## 수직분할 + +칼럼이 많은 테이블의 컬럼을 조회하면 그 테이블에 있는 모든 칼럼을 읽게되고, 그 중 조회대상이 아닌 칼럼은 버려지게 되어 불필요한 블록 I/O와 Disk I/O의 수가 많아진다. + +이 경우 테이블을 수직분할을 통해 성능을 향상시킬 수 있다. 같은 트랜잭션 안에서 동시에 조회되는 경우가 많은 컬럼들, 또는 수정이 자주 발생되는 컬럼들을 모아 1:1 관계로 별도의 테이블을 만드는 식으로 수행한다. + +블록 I/O에 소요되는 시간이 join을 하는 것 보다도 오래 걸릴 정도로 컬럼 수가 극단적으로 많은 경우에 좋은 효과를 볼 수 있다. + + + + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\202ACID\354\231\200\342\200\202\352\262\251\353\246\254\354\210\230\354\244\200.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\202ACID\354\231\200\342\200\202\352\262\251\353\246\254\354\210\230\354\244\200.md" new file mode 100644 index 00000000..f06f79d7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/DB\354\204\244\352\263\204/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\202ACID\354\231\200\342\200\202\352\262\251\353\246\254\354\210\230\354\244\200.md" @@ -0,0 +1,48 @@ +--- +title: '트랜잭션 ACID와 격리수준' +lastUpdated: '2024-03-02' +--- +

트랜잭션이란 DB에서 처리되는 논리적인 연산 단위를 의미한다. 1개의 트랜잭션에는 1개 이상의 SQL문이 포함된다. 또한 트랜잭션은 분할할 수 없는 최소의 단위로, 전부 적용하거나 전부 취소하는 ALL OR NOTHING의 개념이다.

+

트랜잭션은 데이터베이스 서버에 여러 개의 클라이언트가 동시에 액세스 하거나 응용프로그램이 갱신을 처리하는 과정에서 중단될 수 있는 경우 등 데이터 부정합을 방지하고자 할 때 사용한다. 트랜잭션의 목적을 달성하며 각각의 트랜잭션을 안전하게 수행하기 위해선 ACID 조건을 충족해야 한다.

+ +## ACID +1. ### 원자성 Atomicity + - 트랜잭션은 DB에 모두 반영되거나, 전혀 반영되지 않아야 한다. +2. ### 일관성 Consistency + - 트랜잭션이 실행을 성공적으로 완료하면 데이터베이스의 상태가 일관성을 유지해야한다. +3. ### 독립성 Isolation + - 트랜잭션 수행 시에는 다른 트랜잭션의 연산 작업이 끼어들지 않고 독립적으로 실행돼야 한다. +4. ### 지속성 Durability + - 성공적으로 수행된 트랜잭션은 영원히 반영되어야 한다. 모든 트랜잭션은 로그로 남겨, 시스템 장애 발생 시 이전 상태로 되돌릴 수 있어야 한다. + +## 💾 트랜잭션 격리수준 + 같은 데이터에 여러명이 동시에 접근하려 하는 경쟁 상태(Race Condition)에서는 아래와 같은 문제들이 생길 수 있다. + + Dirty read 커밋되지 않은 데이터 읽기 + Dirty write 커밋되지 않은 데이터 덮어쓰기 + read skew 읽는동안 데이터가 변경 + Lost update 변경 유실 + +이런 상황에서 트랜잭션의 독립성을 지키려면 트랜잭션을 격리시켜줘야한다. +트랜잭션의 격리 수준은 대표적으로 네가지가 있다. + +1. ### Read uncommitted + - 커밋되지 않은 데이터도 읽을 수 있다. + - 사실상 격리를 하지 않고 그냥 냅둔 것이다. + - 정합성에 문제가 많기 때문에 거의 쓰지 않는다. + +2. ### Read committed + - 커밋된 데이터만 읽을 수 있다. + - 대부분의 RDB에서 기본적으로 사용한다. + - 하지만 어떤 트랜잭션이 실행되는 중간에 누가 커밋을 하면
같은 데이터를 조회해도 다른 값이 조회되는 문제가 생긴다.(Non-Repeatable Read) + +3. ### Repeatable read + - 트랜잭션마다 트랜잭션ID를 부여해서, 나의 트랜잭션 ID보다 작은 번호에서 변경한 것만 읽게 한다. + - 하지만 어떤 트랜잭션이 실행되는 중간에 작은 번호에서 커밋을 하면 존재하지 않는 값을 읽을 수도 있는 문제가 생긴다.(Phantom Read) + +4. ### Serializable + - 하나의 트랜잭션이 끝난 다음에 다음 트랜잭션을 실행한다. + - 가장 단순하지만 동시 처리성능이 많이 떨어진다. + +트랜잭션을 확실히 격리하여 구분할 수록 동시처리 성능은 떨어진다. +이 네가지의 단계중 하나를 선택해서 성능과 독립성을 적당히 조정해야한다. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Data\342\200\205Lake\354\231\200\342\200\205Warehouse.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Data\342\200\205Lake\354\231\200\342\200\205Warehouse.md" new file mode 100644 index 00000000..654c1435 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Data\342\200\205Lake\354\231\200\342\200\205Warehouse.md" @@ -0,0 +1,48 @@ +--- +title: 'Data Lake와 Warehouse' +lastUpdated: '2024-03-02' +--- + +데이터 레이크는 구조화되거나 반구조화되거나 구조화되지 않은 대량의 데이터를 저장, 처리, 보호하기 위한 중앙 집중식 저장소이다. 데이터 레이크는 데이터를 기본 형식으로 저장할 수 있으며, 크기 제한을 무시하고 다양한 데이터를 처리할 수 있다. + +저장되기 전에 구조화되지 않기 때문에 데이터 웨어하우스보다 훨씬 빠르게 광범위한 데이터에 액세스 할 수 있다. + +### 장점 + +- **Agility:** 사전 계획 없이 쿼리, data models 또는 applications을 쉽게 구성할 수 있다. SQL 쿼리 외에도 data lake strategy은 real-time analytics, big data analytics 및 machine learning을 지원하는 데 적합하다 + +- **Real-time:** 실시간으로 여러 소스에서 원본 형식의 데이터를 가져올 수 있다. 이를 통해 real-time analytics 및 machine learning을 수행하고 다른 애플리케이션에서 작업을 trigger할 수 있다. + +- **Scale:** structure가 없기 때문에 Data lake는 ERP 트랜잭션 및 call log과 같은 대량의 정형 및 비정형 데이터를 처리할 수 있다. + +- **Speed:** 데이터를 원시 상태로 유지하면 해결해야 하는 비즈니스 질문을 정의할 때까지 ETL 및 Schema 정의와 같은 시간 집약적인 작업을 수행할 필요가 없으므로 훨씬 빠르게 사용할 수 있다. + +- **Better insights:** 보다 광범위한 데이터를 새로운 방식으로 분석하여 예상치 못한 이전에 사용할 수 없었던 통찰력을 얻을 수 있다. + +- **Cost savings:** Data lake는 관리하는 데 시간이 덜 걸리므로 운영 비용이 더 낮다. 또한 스토리지 관리에 사용하는 대부분의 도구가 오픈 소스이고 저렴한 하드웨어에서 실행되기 때문에 스토리지 비용은 기존 데이터 웨어하우스보다 저렴하다. + +# Data Warehouse + +데이터 웨어하우스는 POS 트랜잭션, 마케팅 자동화, 고객 관계 관리 시스템 등의 여러 소스에서 가져온 구조화된 데이터와 반구조화된 데이터를 분석하고 보고하는 데 사용되는 엔터프라이즈 시스템이다. 데이터 웨어하우스는 임시 분석과 커스텀 보고서 생성에 적합하다. 데이터 웨어하우스는 현재 데이터와 과거 데이터를 모두 한 곳에 저장할 수 있으며, 시간 흐름에 따른 장기간의 데이터 동향을 확인할 수 있도록 설계되었다. + +엔터프라이즈 Data warehouse를 사용하면 서로 다른 데이터 저장소에 직접 액세스하지 않고 통계형 데이터를 빠르게 뽑아낼 수 있기 때문에 조직 전체에서 의사 결정을 더 빠르게 수행할 수 있다. + +- **Better data quality. More trust:** 데이터가 필요한 형태로 가공, 표준화되어 저장되고, 단일 소스로 운영되기 때문에 신뢰성이 있다. + +- **Complete picture. Better, faster analysis:** Data warehouse는 운영 데이터베이스, 트랜잭션 시스템 및 플랫 파일과 같은 다양한 소스의 데이터를 통합하고 조화시킨다. 비즈니스에 대한 정확하고 완전한 데이터를 더 빨리 사용할 수 있으므로 정보를 유용한 정보로서 사용하기 쉽다. + +# Data Lake와 Warehouse의 차이 + +| 구분 | Data Lake | Data Warehouse | +|------------------|---------------------------------------------------|-------------------------------------------------------| +| 데이터 저장 방식 | 구조화되지 않은 Raw Data 형식으로 무기한 저장 | 사전 정의된 비즈니스 요구사항 기반으로 전략적 분석이 가능한 정재 및 처리된 구조화 데이터 저장 | +| 사용자 | 대량의 비정형 데이터를 통해 새로운 Insight를 얻기 위해 데이터를 연구하는 데이터 과학자 혹은 엔지니어가 사용 | 일반적으로 비즈니스 KPI에서 Insight를 얻으려는 관리자와 비즈니스 최종 사용자가 사용 | +| 분석 | Predictive analytics, machine learning, data visualization, BI, big data analytics. | Data visualization, BI, data analytics. | +| 스키마 | 비정형 데이터를 저장하기 위해서 Schema 정의하지 않고 ETL 과정에서 Schema 정의하는 "Schema on Read" | 비즈니스 요구사항 기반으로 정형화된 데이터를 저장하기위해 Schema 정의 및 저장할 때 Schema를 정의하는 "Schema on Write" | +| 처리 | Raw Data를 바로 저장 및 필요시 ETL 과정에서 Schema 정의 ("Schema on Read") | 저장하는 과정해서 ETL를 통한 Schema 정의 ("Schema on Write") | +| 비용 | Storage cost가 낮을 뿐만 아니라, 관리하는 cost가 낮음 | Storage cost가 높을 뿐만 아니라, 관리하는 cost도 높음 | + +--- +참고 +- https://link.coupang.com/a/DPWfc +- https://aws.amazon.com/lake-formation/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/Docker\353\241\234\342\200\205Kafka\342\200\205\354\213\244\355\226\211.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/Docker\353\241\234\342\200\205Kafka\342\200\205\354\213\244\355\226\211.md" new file mode 100644 index 00000000..2d987c68 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/Docker\353\241\234\342\200\205Kafka\342\200\205\354\213\244\355\226\211.md" @@ -0,0 +1,126 @@ +--- +title: 'Docker로 Kafka 실행' +lastUpdated: '2024-03-02' +--- + +kafka는 보통 zookeeper라는 모듈과 함꼐 실행된다. 최근엔 의존성을 분리해나가고 있다고는 하지만, 우선은 zookeeper와 함께 구성해보자. + +(그래야 레퍼런스가 많기 때문이다.) + +kafka와 zookeeper 이미지를 각각 올려야 하니까 docker compose를 활용해서 올려준다. 테스트용이므로 zookeeper와 kafka는 각 하나의 클러스터를 지니도록 할 것이다. + +```yml +version: '2' + +services: + zookeeper: + image: confluentinc/cp-zookeeper:latest + environment: + ZOOKEEPER_SERVER_ID: 1 + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ZOOKEEPER_INIT_LIMIT: 5 + ZOOKEEPER_SYNC_LIMIT: 2 + ports: + - "22181:2181" + + kafka: + image: confluentinc/cp-kafka:latest + depends_on: + - zookeeper + ports: + - "29092:29092" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 +``` + +### zookeeper + +- ZOOKEEPER_SERVER_ID + - zookeeper 클러스터에서 유일하게 주키퍼를 식별할 아이디를 지정한다. (다중 클러스터 설정) + +- ZOOKEEPER_CLIENT_PORT + - 컨테이너 내부에서zookeeper와 kafka가 통신할 port_client_port (defailt 2181) + +- ZOOKEEPER_TICK_TIME + - zookeeper 동기화를 위한 기본 틱 타임 + +- ZOOKEEPER_INIT_LIMIT: + - 주키퍼 초기화를 위한 제한 시간 + - 주키퍼 클러스터는 쿼럼이라는 과정을 통해서 마스터를 선출하게 된다. 이때 주키퍼들이 리더에게 커넥션을 맺을때 지정할 초기 타임아웃 시간이다. + - 타임아웃 시간은 이전에 지정한 ZOOKEEPER_TICK_TIME 단위로 설정된다. (다중 클러스터 설정) + +- ZOOKEEPER_SYNC_LIMIT: + - 주키퍼 리더와 나머지 서버들의 최대 싱크횟수 + - 이 횟수내에 싱크응답이 들어오는 경우 클러스터가 정상으로 구성되어 있는 것으로 인식된다. + +### kafka + +- depends_on + - kafka는 zookeeper에 의존하고 있어서, zookeeper가 먼저 올라와 있어야 잘 작동하기 때문에 depends_on 설정을 해준다. + +- KAFKA_BROKER_ID + - kafka 브로커 아이디를 지정한다. (다중 클러스터 설정) + +- KAFKA_ZOOKEEPER_CONNECT + - kafka가 zookeeper에 커넥션하기 위한 대상을 지정한다. + - 여기서는 zookeeper(서비스이름):2181(컨테이너내부포트) 로 대상을 지정했다. + +- KAFKA_ADVERTISED_LISTENERS + - 외부에서 접속하기 위한 리스너 설정을 한다. + +- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP + - 보안을 위한 프로토콜 매핑이디. + - KAFKA_ADVERTISED_LISTENERS 과 함께 key/value로 매핑된다. + +- KAFKA_INTER_BROKER_LISTENER_NAME + - 도커 내부에서 사용할 리스너 이름을 지정한다. + - 이전에 매핑된 PLAINTEXT가 사용되었다. + +- KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS + - 카프카 그룹이 초기 리밸런싱할때 컨슈머들이 컨슈머 그룹에 조인할때 대기 시간이다. + +## 명령어 + +작성한 명령어로 컨테이너를 올린다. + +```js +docker-compose up +``` + +토픽을 생성한다. + +```js +kafka-topics --create --topic test --bootstrap-server localhost:29092 +``` + +생성된 토픽의 정보를 확인한다. + +```js + kafka-topics --describe --topic test --bootstrap-server localhost:29092 +``` + +producer로 생성한 토픽에 메세지를 보낸다. `first`와 `second`라는 text를 적어주었다. + +```js +kafka-console-producer --topic test --bootstrap-server localhost:29092 +>first +>second +``` + +만든 서버의 "test" topic에 대한 consumer에 접속하면 두 메세지가 성공적으로 전달된 것을 볼 수 있다. + +```js +kafka-console-consumer --topic test --from-beginning --bootstrap-server localhost:29092 +``` + +``` +first +second +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/RabbitMQ.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/RabbitMQ.md" new file mode 100644 index 00000000..8c32451b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/RabbitMQ.md" @@ -0,0 +1,32 @@ +--- +title: 'RabbitMQ' +lastUpdated: '2024-03-02' +--- + +래빗MQ(RabbitMQ)는 오픈소스인 범용 메시지 브로커 소프트웨어이다. + +RabbitMQ는 온라인 거래 또는 결제 처리와 같이 처리량이 많은 경우에 성능을 개선하는데 사용될 수 있다. 일반적으로 백그라운드 및 cron 작업을 처리하거나 마이크로서비스 간의 메시지 브로커로 사용된다. + +RabbitMQ는 가볍기 떄문에 클라우드에 배포하기 쉽다. 여러 메시징 프로토콜을 지원하고 분산형 및 혼합형 구성에서 배포를 지원하여 다양한 상황에 적용이 가능하다. + +## 특징 + +### 안정성 및 성능 +- RabbitMQ를 사용하면 메세지를 신뢰성 있고 빠르게 큐 형태로 전달할 수 있다. + +### 유연한 라우팅 +- 메시지는 큐에 도착하기 전에 교환을 통해 라우팅되므로 복잡한 라우팅이 가능하다. + +### 트레이싱 +- 추적 지원을 제공하여 메시징 시스템이 잘못 작동할 경우 발생하는 현상을 디버그하고 발견할 수 있습니다. + +### 플러그인 시스템 +- RabbitMQ는 다양한 방식으로 확장할 수 있는 플러그인을 제공합니다. 사용자 지정 플러그인을 작성할 수도 있다. + +## RabitMQ는 언제 사용할까? + +- RabbitMQ는 topic에 대한 메시지를 한번 처리해줘야 할 때 유용하다. 그렇기 때문에 위에서도 언급했듯, 백그라운드 및 cron 작업을 처리하거나 마이크로서비스 간의 메시지 브로커로 주로 사용된다. +- RabbitMQ는 Kafka보다 유연하며, 라우팅 기능을 사용하여 구조의 변화에 빠르게 대응할 수 있다. + - consumer를 동적으로 추가할 때, RabbitMQ에서는 Publisher를 변경하지 않아도 된다고 한다. +- AMQP 1.0, AMQP 0-9-1, STOMP, MQTT 같은 레거시 프로토콜을 사용하는 경우에도 적용할 수 있다. +- 여러 마이크로서비스간의 복잡한 상호작용이 있는 경우 사용할 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/Spring\342\200\205with\342\200\205Kafka.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/Spring\342\200\205with\342\200\205Kafka.md" new file mode 100644 index 00000000..8b4fd688 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/Spring\342\200\205with\342\200\205Kafka.md" @@ -0,0 +1,120 @@ +--- +title: 'Spring with Kafka' +lastUpdated: '2024-03-02' +--- + +Kafka를 설치하여 실행한 뒤([예시](https://github.com/rlaisqls/TIL/blob/984bd2b023d378b4d5879592fbd6115508613072/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%E2%80%85DataBase/MQ/Docker%EB%A1%9C%E2%80%85Kafka%E2%80%85%EC%8B%A4%ED%96%89.md)) Spring과 연동하여 애플리케이션을 만들어보자. + +우선 server host와 port를 지정해주자. + +```yml +spring: + kafka: + bootstrap-servers: localhost:29092 +``` + +그리고 kafkaConfig를 만들어 Producer와 Comsumer의 bootstrapServer 정보, group, client ID, serializer와 desirializer를 설정해준다. 아래의 코드는 거의 최소 설정으로 구성한 것인데, 보안이나 timeout, partition 등의 설정은 원하는대로 추가해주면 된다. + +```kotlin +@EnableKafka +@Configuration +class KafkaConfig { + + @Value("\${spring.kafka.bootstrap-servers}") + lateinit var bootstrapServer: String + + @Bean + fun producerFactory(): ProducerFactory { + val config = mapOf( + ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to this.bootstrapServer, + ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.qualifiedName, + ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to JsonSerializer::class.qualifiedName + ) + return DefaultKafkaProducerFactory(config) + } + + @Bean + fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory { + val config = mapOf( + ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to this.bootstrapServer, + ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.qualifiedName, + ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.qualifiedName, + ConsumerConfig.GROUP_ID_CONFIG to "test-group", + ConsumerConfig.CLIENT_ID_CONFIG to "test-client" + ) + val consumerFactory = DefaultKafkaConsumerFactory(config) + + val factory = ConcurrentKafkaListenerContainerFactory() + factory.setMessageConverter(JsonMessageConverter()) + factory.consumerFactory = consumerFactory + factory.setConcurrency(1) + factory.containerProperties.ackMode = ContainerProperties.AckMode.MANUAL + + return factory + } + + @Bean + fun kafkaTemplate(): KafkaTemplate { + return KafkaTemplate(producerFactory()) + } +} +``` + +큐에 데이터를 발행해줄 api를 하나 만들어준다. 실행한 서버의 이 url에 요청을 보내면 TestPayload 객체가 메세지로 변환되어 test 토픽에 쌓이게 될 것이다. + +위의 config에서 `ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG`를 `JsonSerializer`로 설정해주었기 때문에 객체를 넣으면 Json 데이터로 변환되어 저장된다. Json이기 때문에 inner class가 있는 복잡한 객체도 바로 전달할 수 있다. + +단, payload로 전송할 객체는 kafka에 저장될 형태로 역직렬화, 직렬화하는 과정이 필요하기 때문에 `Serializable`을 상속받거나 (kotlin에선) data class로 정의해줘야한다. + +> 여기서 사용할 test topic은, kafka에서 따로 생성해주어야한다. 하지만 원한다면 config 코드상에서 topic을 Bean으로 정의하는 방법도 있다. + +```kotlin +@RestController +class KafkaController( + val kafkaTemplate: KafkaTemplate +) { + + @PostMapping("/kafka/send") + fun sendKafkaMessage() { + val message = MessageBuilder + .withPayload(TestPayload()) + .setHeader(KafkaHeaders.TOPIC, "test") + .build() + this.kafkaTemplate.send(message) + } + + data class TestPayload( + val name: String = "hello", + val age: Int = 18, + val data: TestData = TestData() + ) + + data class TestData( + val address: String = "earth", + val phone: Int = 12345678 + ) +} +``` + +test 토픽에 저장된 메시지들을 소비할 consumer 클래스이다. `@KafkaListener` 어노테이션을 달아주면 해당 topic의 메시지를 받아 처리한다. + +group은 config에서 설정한 groupId를 그대로 적은 것인데, 다중 group을 관리하는 것이 목적이 아니기 때문에 큰 의미는 없다. + +```kotlin +@Component +class KafkaConsumer { + + @KafkaListener(topics = ["test"], groupId = "test-group") + fun listener(@Payload request: KafkaController.TestPayload, ack: Acknowledgment) { + println(request) + ack.acknowledge() + } +} +``` + +클래스 형태 그대로 잘 받아지는 것을 확인할 수 있다. + +```log +2023-01-23T12:32:31.018+09:00 INFO 59221 --- [ad | producer-1] org.apache.kafka.clients.Metadata : [Producer clientId=producer-1] Resetting the last seen epoch of partition test-0 to 0 since the associated topicId changed from null to _zuSLWdTRK2uv0vul3nAxw +TestPayload(name=hello, age=18, data=TestData(address=earth, phone=12345678)) +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/\353\251\224\354\213\234\354\247\200\355\201\220.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/\353\251\224\354\213\234\354\247\200\355\201\220.md" new file mode 100644 index 00000000..38be60f5 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MQ/\353\251\224\354\213\234\354\247\200\355\201\220.md" @@ -0,0 +1,64 @@ +--- +title: '메시지큐' +lastUpdated: '2024-03-02' +--- + +- 동기식 통신 방식은 사용자로부터 요청을 받아서 요청을 다 처리할때까지 서버가 다른 행동을 취하지 못하지만, 메세지큐를 사용해 외부에 요청을 맡긴다면 서버가 보다 많은 사람들의 요청을 빠르게 처리할 수 있다. + +--- + +# MQ 사용 구조 +- 클라이언트는 서버에 직접 요청하는 것이 아닌 MQ에 전달한다. +- 그럼 서버는 MQ로 부터 요청 데이터를 수신해서 처리한다. +- 만약 서버가 요청을 받을 수 없을 수 없는 상황이라면 해당 요청은 서버가 받을 때까지 MQ에 머무르게 된다. +- 이런 상황에서 MQ에 다운타임이 발생하면 무용지물이 되어버리기 때문에 많은 MQ가 고가용성을 위해 클러스터링 등을 지원한다. + +--- + +# Pub/Sub 모델 + +- Pub/Sub 모델은 Publish/Subscribe의 줄임말로 메세지 기반의 미들웨어 시스템을 말한다. +- 일반적으로 메시지를 전송할 때는 Publisher(sender)가 Subscriber(receiver)에게 직접 메시지를 전송하는데, Pub/Sub 모델에서 Publisher는 어떤 Subscriber가 있는지 모르는 상태에서 메시지를 전송하고 Subscriber는 Publisher에 대한 정보 없이 자신의 Interest에 맞는 메시지만을 전송받는다. +- 대표적인 Pub/Sub 서비스로는 Kafka와 Redis가 있다. 하지만 구체적인 발행/구독 방식은 조금씩 다르다. + +--- + +# Kafka +- Kafka에서의 Producer/Consumer는, 각각 Publisher/Consumer와 기능이 동일하다. +- Producer는 Topic에 이벤트를 보내고, 이 이벤트는 Topic의 각 partition에 분산되어 '저장'된다. +- Topic을 구독하고 있는 Consumer group 내의 Consumer는 각각 1개 이상의 partition으로부터 이벤트를 가져온다. + +## Kafka의 주요 특징 +### 높은 확장성 +- kafka의 Producer와 Consumer는 완전 별개로 동작한다. +- Producer는 Broker의 Topic에 메시지를 게시하기만 하면 되고, Consumer는 그걸 가져와서 처리하기만 하면 된다. +- Producer와 Consumer가 직접적으로 연관을 가지고 있지 않기 때문에, 확장 또는 축소시에 번거롭게 연결·해제할 필요가 없다. + +### Pull모델 + +- Kafka의 Consumer는 Pull모델을 기반으로 메시지 처리를 진행한다. +- 즉, Broker가 Consumer에게 메시지를 전달하는 것이 아닌, Consumer가 필요할때, Broker로 부터 메시지를 가져와 처리하는 형태이다. +- 이러한 형태는 다양한 소비자의 처리 형태와 속도를 고려하지 않아도 된다는 장점이 있다. +- Push 모델에서는 Broker가 데이터 전송 속도를 제어하기 때문에 다양한 메시지 스트림의 소비자를 다루기 어렵지만, Pull 모델은 Consumer가 처리 가능할때 알아서 가져와 처리하는 것이기 때문에 다양한 소비자를 다루기 쉽다. + +--- + +# Redis + +- Redis에는 그룹이라는 개념이 존재하지 않고, 각 subscriber가 channel을 구독하고 있는 구조이다. +- 이때 중요한 점은, Channel이 이벤트를 저장하지 않는다는 것이다. +- 만일 Channel에 이벤트가 도착했을 때, 해당 채널의 Subscriber가 존재하지 않는다면, 이벤트는 사라진다. + +--- + +# MQ를 사용하는 경우 + +- **MQ를 사용하면 좋은 경우** + - 처리할 데이터가 많을 때 부하분산을 위해 MQ를 사용할 수 있다. + - 여러 대의 서버가 하나의 큐를 바라보도록 구성하면 각 서버는 자신의 처리량에 맞게 태스크를 가져와 처리할 수 있다. + - 이러한 구조는 horizontal scaling에 유리하다. + +- **MQ가 어울리지 않는 경우** + - 요청 결과를 즉시 응답해야할 때는 MQ가 적절하지 않다. + - MQ는 요청과 별개로 처리할 수 있는 비동기 처리에 어울린다. + - 서버에서 간단하게 처리할 수 있는 일을 MQ를 통해 전달하는 건 필요없는 오버헤드가 될 수도 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MySQL\342\200\205Replication.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MySQL\342\200\205Replication.md" new file mode 100644 index 00000000..893adb79 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/MySQL\342\200\205Replication.md" @@ -0,0 +1,95 @@ +--- +title: 'MySQL Replication' +lastUpdated: '2024-03-02' +--- + +데이터베이스에서 데이터를 복제하는 방식은 크게 동기 방식과 비동기 방식이 있다. + +- 동기 방식: Master 노드에 데이터 변경이 발생할 경우 Slave 노드까지 (동시에) 적용되는 것을 보장한다. + - 따라서 Master 노드에 장애가 발생하더라도 (데이터 정합성 문제 없이) Slave 노드를 이용하여 서비스를 이어갈 수 있다. +- 비동기 방식: Master 노드의 변경과 Slave 노드로의 적용이 시차를 두고 동기화됨 + - Master 노드에서 변경된 데이터가 아직 Slave에 반영되지 못했을 가능성이 있다. 곧 바로 Slave 노드를 이용하여 서비스를 이어갈 경우 데이터 정합성에 문제가 발생할 수 있다. + +이러한 두 가지 방식은 성능과 데이터 정합성(정확성)이라는 두 가지 요소 중 어느 것을 중요하게 취급할 것인지에 따라 선택하여 사용하게 된다. 동기와 비동기 방식의 장점을 적절히 취하여 [Semi-Sync](https://hoing.io/archives/3633) 방식을 사용하는 경우도 있다. +(MySQL 도 Semi-Sync Replication 을 Plug-in 방식으로 사용할 수 있다.) + +이렇게 동기/비동기의 관점 뿐 아니라 Replication을 구현하는 방식은 아주 다양할 수 있다. + +같은 동기 방식이라도 모든 변경 데이터마다 Slave의 적용에 대한 응답을 수신하는 방식이 있을 수 있고, 또는 트랜잭션 도중 발생되는 변경에 대해서는 비동기로 작동하다가 Commit 단계에서만 Slave의 응답을 수신하도록 할 수도 있다. + +비동기 방식의 경우에도 파일의 로그를 별도의 스레드(프로세스)가 읽어서 Slave 로 전송하는 방식이 있을 수 있고, 트랜잭션을 수행하는 스레드가 직접 Slave 로 변경 사항을 전송하도록 구현될 수도 있을 것이다. + +## MySQL Replication 동작 원리 + +MySQL의 Replication은 기본적으로 비동기 복제 방식을 사용하고 있다. + +Master 노드에서 변경되는 데이터에 대한 이력을 로그(Binary Log)에 기록하면, Replication Master Thread가 (비동기적으로) 이를 읽어서 Slave 쪽으로 전송하는 방식이다. + +MySQL에서 Replication을 위해 반드시 필요한 요소는 다음과 같다. + +- Master에서의 변경을 기록하기 위한 Binary Log +- Binary Log를 읽어서 Slave 쪽으로 데이터를 전송하기 위한 Master Thread +- Slave에서 데이터를 수신하여 Relay Log에 기록하기 위한 I/O Thread +- Relay Log를 읽어서 해당 데이터를 Slave에 Apply(적용)하기 위한 SQL Thread + +위의 구성 요소들은 아래 그림에서 보는 Flow 대로 데이터 복제를 수행한다. + +image + +1. 클라이언트(Application)에서 Commit을 수행한다. +2. Connection Thead는 스토리지 엔진에게 해당 트랜잭션에 대한 Prepare(Commit 준비)를 수행한다. +3. Commit 을 수행하기 전에 먼저 Binary Log 에 변경사항을 기록한다. +4. 스토리지 엔진에게 트랜잭션 Commit 을 수행한다. +5. Master Thread는 시간에 구애받지 않고(비동기적으로) Binary Log 를 읽어서 Slave 로 전송한다. +6. Slave의 I/O Thread는 Master 로부터 수신한 변경 데이터를 Relay Log 에 기록한다. (기록하는 방식은 Master 의 Binary Log 와 동일하다) +7. Slave 의 SQL Thread는 Relay Log에 기록된 변경 데이터를 읽어서 스토리지 엔진에 적용한다. + +데이터를 다른 노드로 복제해야 하는 상황에서 과연 SQL을 전송하여 Replay 하는 방식으로 복제할 것인가, 또는 변경되는 Row 데이터를 전송하여 복제할 것인가를 고민해볼 수 있다. 전자를 SBR(Statement Based Replication)이라고 하고, 후자를 RBR(Row Based Replication)이라고 한다. SBR은 로그의 크기가 작을 것이고, RBR은 데이터 정합성에 있어서 유리할 것이다. 사용자는 SQL의 성격이나 변경 대상 데이터 양에 따라 SBR 또는 RBR를 선택하여 사용할 수 있다. + +SBR 과 RBR 을 자동으로 섞어서 사용할 수 있는 방식은 MBR(Mixed Based Replication)이라고 한다. 평상 시에는 SBR 로 동작하다가 [비결정성(Non-Deterministic) SQL](https://mariadb.com/kb/en/library/unsafe-statements-for-statement-based-replication/#unsafe-statements)을 만나면 자동으로 RBR 방식으로 전환하여 기록하는 방식이다. Binary Log 의 크기와 데이터의 정합성에 대한 장점을 모두 취한 방식이라고 보면 된다. + +## Master Thread + +- MySQL Replication에서는 Slave Thread가 Client이고 Master Thread가 Server 이다. 즉, **Slave Thread가 Master Thread 쪽으로 접속을 요청하기 때문에 Master에는 Slaver Thread가 로그인할 수 있는 계정과 권한(REPLICATION_SLAVE)이 필요하다.** + +- Master 쪽으로 동시에 다수의 Slave Thread가 접속할 수 있으므로 Slave Thread 당 하나의 Master Thread가 대응되어 생성된다. Master Thread는 한가지 역할만을 수행하는데, **이는 Binary Log 를 읽어서 Slave 로 전송하는 것이다. 이 때문에 Binlog Sender 또는 Binlog Dump 라고도 불린다.** + +- Master 입장에서 Slave의 접속은 여느 Client 의 접속과 다를 바가 없다. 따라서, 해당 접속이 Replication Slave Thread 로부터의 접속인지 일반 Application 의 접속인지 구분할 수 있는 방법이 없다. 로그인 과정도 일반 Client와 동일하게 처리되기 때문이다. + +- Master가 특정 접속을 Slave Thread 로 인식하여 Binary Log 를 전송하려면, Slave 로부터의 특정 명령 Protocol을 통해 '난 다른 Client랑 다르게 Replication Slave 야' 와 같이 알려주어야 한다. Slave Thread는 Master에 접속 후 Binary Log 의 송신을 요청하는 명령어(Protocol)를 전송하는데 이는 `COM_BINLOG_DUMP` 와 `COM_BINLOG_DUMP_GTID` 이다. 전자는 Binary Log 파일명과 포지션에 의해, 후자는 GTID에 의해 Binary Log 의 포지션을 결정한다. (GTID 는 MySQL 5.6 에 추가된 기능) + +- Slave는 위의 Protocol을 통한(실제 SQL 은 아님) 소통 이후에 COM_QUERY 라는 Protocol을 통해 실제 데이터(SQL) 송신을 요청하게 된다. + +## Slave I/O Thread + +- Slave I/O Thread는 Master로부터 연속적으로 수신한 데이터를 Relay Log 라는 로그 파일에 순차적으로 기록한다. Relay Log 파일의 Format 은 Master 측의 Binary Log Format 과 정확하게 일치한다. 인덱스 파일도 똑같이 존재하고 파일 명에 6 자리 숫자가 붙는 것도 동일하다. + +- Relay Log 는 Replication 을 시작하면 자동으로 생성된다. Relay Log 의 내용을 확인하기 위해서는 SHOW RELAYLOG EVENTS 명령어를 사용한다. + +- Relay Log 파일의 이름은 기본적으로 `'호스트명-relay-bin'` 이며, 이는 호스트 이름이 변경될 경우 오류가 발생할 수 있으므로 relay_log 옵션을 이용하여 사용자가 의도한대로 정하는 편이 좋다. + +## Slave SQL Thead + +- Slave SQL Thread는 Relay Log 에 기록된 변경 데이터 내용을 읽어서 스토리지 엔진을 통해 Slave 측에 Replay(재생)하는 Thread 이다. 아무래도 Relay Log 를 기록하는 I/O Thread 보다는 실제 DB 의 내용을 변경하는 SQL Thread가 처리량과 연산이 많게 마련이다. + +- **이는 SQL Thread가 Replication 처리의 병목 지점이 될 수 있다는 것을 의미한다.** + +- Master 측에서는 많은 수의 Thread가 변경을 발생시키고 있는데 반해, Slave 에서는 하나의 SQL Thread가 DB 반영 작업을 수행한다면 병목이 되는 것은 당연하다. 이의 해결을 위해 등장한 것이 MySQL 5.7 에서 대폭 개선된 MTS(Multi Thread Slave)이다. 이는 Slave 에서의 SQL Thread가 병렬로 데이터베이스 갱신을 수행할 수 있도록 개선된 기능이다. (해당 기능에 대한 자세한 내용은 향후 별도 주제로 다루도록 하겠다.) + +## MySQL Replication 을 이용한 다양한 구성 + +- MySQL의 Replication이 사용하는 복제 방식을 이용하면 아주 다양한 방식으로 시스템을 구성할 수 있다. 아래 그림들은 실제로 사용 가능한 다양한 구성 예들을 모아본 것이다. +MySQL Replication 은 다음의 몇 가지 특징을 가지기 때문에 좀 더 다양한 방식으로 구성할 수 있다는 것을 알 수 있다. + + - Slave는 또 다른 MySQL 서버의 Master 가 될 수 있다. + - 하나의 Master 가 가질 수 있는 Slave 는 다수일 수 있다. (다단 구성 가능) + - 두 개의 MySQL 서버가 서로의 Master 또는 Slave 가 될 수 있다. + - 하나의 Slave 가 Multi Master 를 가질 수 있다. (N:1 - MySQL 5.7 이상) + +- 단, 아래의 구성 중 Dual Master의 경우 양쪽 서버에서 데이터 변경이 가능하고 서로 복제가 가능한 방식이지만, 어쨌건 비동기 방식의 복제이므로 데이터의 부정합이 발생할 가능성은 여전히 존재한다는 것을 주의하자. (Application 작성 시 이 부분에 대한 대응 방안이 필요하다.) + +image + +--- +참고 +- https://dev.mysql.com/doc/refman/5.7/en/replication.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/Cassandra.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/Cassandra.md" new file mode 100644 index 00000000..f39dca78 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/Cassandra.md" @@ -0,0 +1,76 @@ +--- +title: 'Cassandra' +lastUpdated: '2024-03-02' +--- + +아파치 카산드라(Apache Cassandra)는 자유 오픈 소스 분산형 NoSQL 데이터베이스 관리 시스템(DBMS)의 하나로, 단일 장애점 없이 고성능을 제공하면서 수많은 서버 간의 대용량의 데이터를 관리하기 위해 설계되었다. + +카산드라는 여러 데이터센터에 걸쳐 클러스터를 지원하며 masterless 비동기 레플리케이션을 통해 모든 클라이언트에 대한 낮은 레이턴시 운영을 허용하며, 성능 면에서 높은 가치를 보인다. + +Amazon의 Dynamo 분산 스토리지 및 복제 기술과 Google의 Bigtable 데이터 및 스토리지 엔진 모델이 결합된 모델로 처음에 단계적 이벤트 기반 아키텍처 (SEDA)를 사용하여 Facebook에서 설계되었다. + +## 특징 + +- 여러개의 데이터 베이스가 복제된다. (마스터기준) +- 짧은 지연 시간 +- 프로세스가 추가될 때마다 선형 처리량 증가 +- 온라인 부하분산 +- 분할된 키 지향 쿼리 +- 유연한 스키마 + +카산드라는 SQL과 비슷한 Cassandra Query Language (CQL)을 이용한다. + +- **Keyspace**: 데이터세트를 어떻게 구성할 것인지에 대한 정의를 한다. 예를들어 데이터의 사본 수 +- **Table**: 데이터 스키마를 정의한다. +- **Partition**: 모든 행에 있어야 하는 기본 키의 필수 부분을 정의한다. +- **Row**: 파티션 키 및 선택적으로 추가 클러스터링 키로 구성된 고유한 기본 키로 식별되는 열 모음 +- **Column**: 해당 테이블에서 행에 속하는 열을 말한다. + +## 장점 + +### 분산화와 집중화 + +카산드라는 분산형이므로 여러 머신에서 동작하지만, 사용자에게는 통합된 하나로 보인다. 물리적으로 떨어져 있는 데이터센터 간에도 단일 카산드라 클러스터를 운영할 수 있다. + +MySQL, 빅테이블과 같은 데이터 저장소를 확장하려면 일부 노드는 마스터로 설정해서 슬레이브로 설정한 다른 노드를 조직화해야한다. 그러나 카산드라는 "비집중화"이므로 모든 노드가 같다. 그러므로 조직화 연산을 수행하는 마스터가 없다. 대신 카산드라는 피투피(P2P) 프로토콜이며 활성 노드와 비활성 노드의 목록을 동기화하고 유지관리한다. + +### 탄력적인 확장성 + +탄력적인 확장성(elastic scalability)은 수평 확장의 특별한 속성으로, 클러스터의 중단 없이 규모를 확대하거나 축소할 수 있다는 뜻이다. + + 하나의 카산드라를 추가했을 때, 카산드라는 새로 추가된 머신을 자동으로 찾아내고 작업을 할당한다. 애플리케이션의 일부를 다른 플랫폼으로 이전하거나 애플리케이션의 사용자가 없어져 클러스터를 줄여야 하는 상황이라 규모 축소를 하더라도 전체 구성을 건드릴 필요가 없다. + +### 고가용성과 결함 허용 + +고가용성 시스템을 구축하려면 일반적으로 다중네트워크로 구성된 컴퓨터, 클러스터에서 운용할 수 있는 소프트웨어, 노드 장애를 인식하고 시스템의 다른 파티션으로 요청을 대체 할 수 있는 기능을 갖춰야 한다. + +카산드라는 고가용성을 지원한다. 클러스터에서 시스템을 중단하지 않고 장애가 발생한 노드를 교체 할 수 있고, 다중 데이터 센터에 데이터를 복제해 로컬 성능을 개선할 수 있으며, 한 데이터 센터가 화재나 홍수 같은 치명적인 재앙을 당하더라도 다중 데이터 센터에 데이터를 복제해서 서비스 중단을 예방할 수 있다. + + + +## 단점 + +### 복잡한 조건의 검색 불가 + +로우 키와 칼럼 두가지에 대한 인덱스만 가능하기 때문에 복잡한 조건의 검색이 불가능하다. 키 값을 통한 범위 검색은 데이터 분산 방식을 OrderPresservingPartitioner로 설정하여 키 값을 통해 데이터를 서버에 분배했을 경우에만 가능하다. + +### 데이터 입력시 자동화 처리가 어려움 + +데이터를 입력 시에도 자동화 처리가 어렵다. 데이터에 대한 락을 사용하려면 주키퍼(Zookeeper)와 같은 전체분산 서버 관리 프로그램을 추가해서 별도로 설정해야 한다. 데이터에 대한 동시 갱신 요청이 발생할 가능성이 높거나 자동화한 트랜잭션이 필요한 서비스에서는 다른 데이터베이스를 고려해야 한다. + +## 데이터 구조 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/2b25ccb2-b302-4923-8941-fd1f6487826f) + +카산드라의 데이터 구조 최상위에는 논리적 Data 저장소인 Key space가 있고, Key space 아래에는 Table이 존재한다. Table은 다수의 row들로 구성되어있으며 각 row는 Key Value로 이루어진 칼럼들로 구성된다. 관계형 데이터베이스 관리 시스템의 DB-Table-Row-Column의 형태와 유사한 구조를 하고 있다는 걸 알 수 있다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/ace00657-570b-40d3-89ea-3ef260cefa83) + + +카산드라는 기본적으로 링(Ring) 구조를 띄고 있다. 그리고 링을 구성하는 각 노드에 데이터(Data)를 분산하여 저장한다. + +파티션 키라고 불리는 데이터의 해시(hash)값을 기준으로 데이터를 분산하게 된다. 처음 각 노드가 링에 참여하게 되면, 카산드라의 `conf/cassandra.yaml`에 정의된 각 설정을 통하여 각 노드마다 고유의 해시 값 범위를 부여받는다. + +그런 뒤에, 외부에서 데이터의 요청이 오게 되면 해당 데이터의 파티션 키의 해시 값을 계산하여 해당 데이터가 어느 노드에 저장되어 있는지 알고 접근할 수 있는 것이다. 그리고, 카산드라는 이렇게 계산된 hash의 값을 토큰(token)이라고 부른다. + + \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/Memcached\342\200\205VS\342\200\205Redis.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/Memcached\342\200\205VS\342\200\205Redis.md" new file mode 100644 index 00000000..7a4f4379 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/Memcached\342\200\205VS\342\200\205Redis.md" @@ -0,0 +1,53 @@ +--- +title: 'Memcached VS Redis' +lastUpdated: '2024-03-02' +--- + +## 공통점 +1. in-memory cache이다. +2. key-value 저장을 지원한다. (redis는 다른 구조의 데이터 저장 또한 지원한다.) +3. NoSQL이다. + +## Memcached 장점 + +### 1. 정적 데이터 캐싱에 효과적 + +Memcached는 HTML같은 작은, 정적 데이터를 캐싱할 때 효율적이다. Redis만큼 정교하지는 않지만 내부 메모리관리는 단순한 경우에 매우 뛰어나다. (metadata에 더 적은 작원을 소모하기 때문이다) Strings(유일한 지원 데이터 타입)은 추가처리가 필요없어 읽기 전용에 적합하다. + +큰 규모의 직렬화된 데이터는 큰 저장공간이 필요하다. Redis 데이터 구조는 데이터의 모든 형태를 그대로 저장할 수 있습니다. Memcached는 직렬화된 형태로 데이터 저장하도록 제한적이므로 효과적이다. 따라서 Memcached를 사용할 때 좀 더 직렬화 오버헤드를 줄일 수 있습니다. + +### 2. 멀티 쓰레드 기능 지원 + +Memcached는 멀티쓰레드이기 때문에, Redis에 비해 스케일링에 유리하다. 컴퓨팅 자원을 추가함으로 스케일 업을 할 수 있습니다. 하지만 캐시된 데이터를 유실 할 확률이 높아지기도 한다. Redis는 단일 쓰레드이기 때문에, 데이터 손실없이 수평으로 스케일링할 수 있습니다. + +## Redis 장점 + +### 1. 다양한 자료구조 및 용량 지원 + +Memcached는 key 이름을 250 byte까지 제한하고, 단순히 string만 사용하는 반면, Redis는 keys, value 이름을 512mb까지 지원한다. hash, set, list, string 등 다양한 데이터 구조를 사용할 수 있어서 개발자들이 캐싱 및 캐시된 데이터 조작에 편리성을 제공한다. + +### 2. 다양한 삭제(eviction) 정책 지원 + +Cache는 메모리에 오래된 데이터를 삭제해서 새로운 데이터 공간을 확보하는 data eviction(데이터 삭제)라는 방식을 사용하다. Memcached의 데이터 방식은 LRU이고 새로운 데이터와 크기가 비슷한 데이터를 임의 제거하다. Redis는 사용자가 6가지의 다른 데이터 삭제 정책을 제공하다. 또한 메모리 관리와 데이터 삭제 선택에 더 정교한 접근법을 제공한다. 또한 lazy, active 삭제를 지원한다. + + +### 3. 디스크 영속화(persistence) 지원 + +Memcached와 달리, Redis는 디스크 영구 저장이 가능하다. 레디스의 데이터베이스에 있는 데이터들은 서버 충돌이나 재부팅 시에도 복구될 수 있다. (물론 유형에 따라서 수초에서 수분 사이에 데이터가 변경 될 수도 있다.) AOF, RDB Snapshot 2가지 방식이 있다. + + +### 4. 복제(replication) 지원 + +복제는 데이터의 복제본을 또다른 인스턴스에 두기 위해, 하나의 인스턴스로부터 또다른 레플리카 인스턴스를 복사하는 것이다. 또한 레디스는 하나 이상의 레플리카를 가질 수 있다. Memcached는 써드 파티를 사용하지 않고서는 복제본을 가질 수 없다. + +### 5. 트랜잭션(Transaction) 지원 + +Membercached는 원자적으로 동작하지만, 트랜잭션을 지원하지 않는다. Redis는 여러 명령을 실행하기 위한 트랜잭션을 지원한다. MULTI 커맨드를 통해서 트랜잭션을 시작하며 EXEC로 추가 명령어를 실행하고 WATCH를 통해서 트랜잭션을 종료한다. + +## 결론 + +Memcached는 replica에 대한 지원이 redis에 비해 미흡하고 지원하는 자료구조 및 기능의 수가 적다. 하지만 Redis는 더 많은 메모리를 사용하며, 싱글 쓰레드라 Memcached보다 성능이 느릴 수 있다. 상황에 따라 적절한 데이터베이스를 사용하자. + +--- +참고 +- https://redis.com/comparisons/redis-vs-memcached/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/BinData.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/BinData.md" new file mode 100644 index 00000000..6aab0811 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/BinData.md" @@ -0,0 +1,80 @@ +--- +title: 'BinData' +lastUpdated: '2024-03-02' +--- + +BinData is binary data object that use in mongoDB. BinData consists of `subType` and `base64str`. + +```bash +help misc +b = new BinData(subtype,base64str) create a BSON BinData value +``` + +The BSON BinData datatype is represented via class BinData in the shell, each value represent specipic kind. like below : + +```js +binary ::= int32 subtype (byte*) Binary - The int32 is the number of bytes in the (byte*). +subtype ::= "\x00" Generic binary subtype + | "\x01" Function + | "\x02" Binary (Old) + | "\x03" UUID (Old) + | "\x04" UUID + | "\x05" MD5 + | "\x80" User defined +``` + +### example + +**Insert a `BinData()` Object** + +Use the `BinData()` constructor to create the bdata variable. + +```js +var bdata = BinData(0, "gf1UcxdHTJ2HQ/EGQrO7mQ==") +``` + +Insert the object into the testbin collection. + +```js +db.testbin.insertOne( { _id : 1, bin_data: bdata } ) +``` + +Query the testbin collection for the inserted document. + +```js +db.testbin.find() +``` + +You can see the binary buffer stored in the collection. + +```js +{ + _id: 1, + bin_data: Binary(Buffer.from("81fd547317474c9d8743f10642b3bb99", "hex"), 0) +} +``` + +**Get the Length of BinData() Object** + +Use the `BinData()` constructor to create the bdata variable. + +```js +var bdata = BinData(0, "gf1UcxdHTJ2HQ/EGQrO7mQ==") +``` + +Use `.length()` to return the bit length of the object. + +```js +bdata.length() +``` + +The returned value is: + +```js +16 +``` + +--- +reference +- http://bsonspec.org/#/specification +- http://docs.mongodb.org/manual/reference/mongodb-extended-json/#binary \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB.md" new file mode 100644 index 00000000..9cf32a41 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB.md" @@ -0,0 +1,63 @@ +--- +title: 'MongoDB' +lastUpdated: '2024-03-02' +--- + +mongoDB는 C++로 짜여진 오픈소스 데이터베이스이다. **문서지향(Document-Oriented)적이며 뛰어난 확장성과 성능을 자랑**한다. + +일반 RDBMS에서의 Tuple/Row 대신, Document라는 용어를 사용한다. 각 Document는 키과 값으로 이뤄져있으며 Join이라는 개념을 Embedded Documents로 대체한다. + +## 특징 + +MongoDB의 특성으로는 다음과 같은 것들이 있다. + +- NoSQL +- 스키마 프리(Schema-Free) +- 비 관계형 데이터베이스 + + + +|특징|설명| +|-|-| +|**Document-oriented storage**|MongoDB는 database > collections > documents 구조로 document는 key-value형태의 BSON(Binary JSON)으로 되어있다.| +|**Full Index Support**|다양한 인덱싱을 제공한다.
  - Single Field Indexes : 기본적인 인덱스 타입
  - Compound Indexes : RDBMS의 복합인덱스 같은 거
  - Multikey Indexes : Array에 매칭되는 값이 하나라도 있으면 인덱스에 추가하는 멀티키 인덱스
  - Geospatial Indexes and Queries : 위치기반 인덱스와 쿼리
  - Text Indexes : String에도 인덱싱이 가능
  - Hashed Index : Btree 인덱스가 아닌 Hash 타입의 인덱스도 사용 가능| +|**Replication& High Availability**|간단한 설정만으로도 데이터 복제를 지원. 가용성 향상.| +|**Auto-Sharding**|MongoDB는 처음부터 자동으로 데이터를 분산하여 저장하며, 하나의 컬렉션처럼 사용할 수 있게 해준다. 수평적 확장 가능| +|**Querying(documented-based query)**|다양한 종류의 쿼리문 지원. (필터링, 수집, 정렬, 정규표현식 등)| +|**Fast In-Pace Updates**|고성능의 atomic operation을 지원| +|**Map/Reduce**|맵리듀스를 지원.(map과 reduce 함수의 조합을 통해 분산/병렬 시스템 운용 지원, 하지만 하둡같은 MR전용시스템에 비해서는 성능이 떨어진다)| +|**GridFS**|분산파일 저장을 MongoDB가 자동으로 해준다. 실제 파일이 어디에 저장되어 있는지 신경 쓸 필요가 없고 복구도 자동이다.| +|**Commercial Support**|10gen에서 관리하는 오픈소스| + +## 장점 + +|장점|설명| +|-|-| +|Flexibility|Schema-less라서 어떤 형태의 데이터라도 저장할 수 있다.| +|Performance|Read & Write 성능이 뛰어나다. 캐싱이나 많은 트래픽을 감당할 때 써도 좋다.| +|Scalability|스케일 아웃 구조를 채택해서 쉽게 운용 가능하고, Auto Sharding이 지원된다.| +|Deep Query ability|문서지향적 Query Language를 사용하여 SQL만큼 강력한 Query 성능을 제공한다.| +|Conversion/Mapping|JSON과 비슷한 형태로 저장이 가능해서 직관적이고 개발이 편리하다.| + +## 단점 + +- join이 필요없도록 설계해야한다. +- memory mapped file을 사용한다. 따라서 메모리에 의존적이고, 메모리 크기가 성능을 좌우한다. +- SQL을 완전히 이전할 수 없다. +- B트리 인덱스를 사용하기 때문에 크기가 커질수록 새로운 데이터를 입력하거나 삭제할 때 성능이 저하된다. 이런 B트리의 특성 때문에 데이터를 넣어두면 잘 변하지않는 정보를 조회하는 데에 적합하다. + +## MongoDB의 Physical 데이터 저장구조 + +MongoDB를 구성할 때, 가장 많이 이슈되는 부분 중 하나는 메모리량과 디스크 성능이다. + +MongoDB는 기본적으로 **memory mapped file**(OS에서 제공되는 mmap을 사용)을 사용한다. 데이터를 쓰기할때, 디스크에 바로 쓰기작업을 하는 것이 아니라 논리적으로 memory 공간에 쓰기를 하고, 그 block들을 주기적으로 디스크에 쓰기를 하며, 이 디스크 쓰기 작업은 OS에 의해서 이루어 진다. + +만약 메모리가 부족하다면 가상 메모리를 사용하게 된다. 가상 메모리는 페이지(Page)라는 블럭 단위로 나뉘어지고, 이 블럭들은 디스크 블럭에 매핑되고, 이 블럭들의 집합이 하나의 데이터 파일이 된다. + +![image](https://user-images.githubusercontent.com/81006587/206588762-f4103a3d-a146-4d41-a26d-60cd14cdddb5.png) + +메모리는 실제 데이터 블록과, 인덱스가 저장된다. MongoDB에서는 인덱스가 메모리에 상주하고 있어야 제대로 된 성능을 낼 수 있다. + +물리 메모리에 해당 데이터 블록이 없다면, 페이지 폴트가 발생하게 되고, 디스크에서 그 데이터 블록을 로드하게 된다. 물론 그 데이터 블록을 로드하기 위해서는 다른 데이터 블록을 디스크에 써야한다. + +즉, 페이지 폴트가 발생하면, 페이지를 메모리와 디스카 사이에 스위칭하는 현상이 일어나기 때문에 디스크IO가 발생하고 성능 저하를 유발하게 된다. 하지만 메모리 용량이 크다면 페이지 폴트를 예방할 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205Document\353\241\234\342\200\205POJO\342\200\205\354\203\201\354\206\215\353\260\233\352\270\260.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205Document\353\241\234\342\200\205POJO\342\200\205\354\203\201\354\206\215\353\260\233\352\270\260.md" new file mode 100644 index 00000000..dd109753 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205Document\353\241\234\342\200\205POJO\342\200\205\354\203\201\354\206\215\353\260\233\352\270\260.md" @@ -0,0 +1,220 @@ +--- +title: 'MongoDB Document로 POJO 상속받기' +lastUpdated: '2024-03-02' +--- + +REPO 코드를 짜던중, Document로 설정할 객체가 POJO를 상속받는 구조가 필요해졌다. + +Querydsl과 mongoDB 설정에 대한 이해 부족으로 긴 삽질을 경험했다. + +## 첫번째 시도 : 생성자로 받기 + +```kotlin +class DocumentJpaEntity( + id: UUID, + year: Int, + writer: WriterInfoElement, + status: DocumentStatus, + introduce: IntroduceElement, + skillSet: MutableList, + projectList: MutableList, + awardList: MutableList, + certificateList: MutableList, + isDeleted: Boolean +) : Document( + id = id, + year = year, + writer = writer, + status = status, + introduce = introduce, + skillSet = skillSet, + projectList = projectList, + awardList = awardList, + certificateList = certificateList, + isDeleted = isDeleted +) +``` + +mongoDB에서 Document 필드 인식할때 그냥 이런식으로 생성자로만 받도록 짜면 부모의 필드까지 스캔해서 만들어주기 때문에 이렇게 하려고 했는데 queryDSL 쪽에서 문제가 생겼다. + +Qclass가 embeded된 별도 객체로 생성이 돼야하는데 SimplePath로 만들어져서 dsl을 통한 퀴리가 불가능해졌다. 필드가 @Document 어노테이션을 단 클래스랑 다른 곳에 정의되어 있어서 스캔이 잘못 되는 것 아닐까 싶었다. + +## 두번째 시도 : override + +두번째로 `override val`로 `@Document` 어노테이션이 달린 클래스에서 재정의 하는 방법을 생각했다. + +이렇게 하는 경우 Querydsl Qclass는 잘 생성되지만 `MongoPersistentProperty`를 생성하는 과정에서 부모 필드와, 자식에서 override한 필드를 또 다시 스캔하는 문제가 생겼다. + +Kotlin tool을 사용해 decompile해보면 실제로는 아래와 같이 생성된다. + +image + +사실 곰곰히 생각해보니까 필드 재정의라는 것은 자바에 존재하지 않는 개념이었다. `override val`을 사용하면 필드가 따로 생겨서 super 생성자를 호출할때 똑같이 넣어주는건 맞지만, 내부를 살펴보면 그냥 동일한 이름의 필드가 생성되는 것이다. + +하지만 Querydsl Qclass 상에는 필드가 정상적으로 하나만 생긴다. 굉장히 모순적인 일이 아닐 수가 없다. + +자세히 살펴보니, Querydsl에서는 도메인인 부모 클래스만 스캔해서 필드를 넣고, Spring data mongoDB에선 부모와 자식 클래스의 필드를 모두 스캔해서 필드를 넣기 때문에 문제가 생기는 것 같았다. Domain에 `@Transient`를 붙이면 Querydsl에 writer에 대한 정보가 등록되지 않고 있었다. + +```kotlin + @field:Transient + val writer: WriterInfoElement, +``` + +이에 착안하여, 다른 방법을 생각해보기로 했다. + +## 세번째 시도 : @QueryEmbedded + +> https://stackoverflow.com/questions/41391206/querydsl-4-stringexpression-of-a-field-inside-a-simplepath + +SimplePath에 대해 검색하다 `@QueryEmbedded`와 `@QueryEmbeddable`에 대한 글을 발견했다. 해당 어노테이션을 써주면 SimplePath로 등록되지 않고 별도의 Qclass로 생성되어 쿼리가 가능해진다는 것 이었다. + +하지만 이 어노테이션을 사용하려면 Entity 클래스에서 필드를 override 해야 했기 때문에 재정의 오류는 동일하게 발생했다. + +머리가 아파지기 시작했다.. 그냥 POJO Domain을 상속 받는 구조를 포기하면 문제가 없을 것이다. Querydsl은 상위 필드를 스캔하고, spring data는 상하위의 모든 필드를 스캔하는데, 이 구조를 유지할 수 있는 방법이 있을까? + +지금 상태에서 찾을 수 있는 방향은 두가지가 있다. + +### 1. 필드를 `override` 하고 `@QueryEmbedded` 붙이기. 그리고 필드 재정의 막기. + - 하지만 막을 수 있는 방법이 과연 있을지 모르겠다. + - mongoDB에서 프로퍼티 등록하는 프로세스를 보니, Getter와 Setter가 둘다 없으면 프로퍼티로 등록하지 않는 것 같아 보였다. 근데 Document 도메인에 Getter Setter가 있으면 그걸 상속받는 엔티티에서도 Getter Setter를 가질 수 밖에 없디. + +```java + /** + * Adds {@link PersistentProperty} instances for all suitable {@link PropertyDescriptor}s without a backing + * {@link Field}. + * + * @see PersistentPropertyFilter + */ + public void addPropertiesForRemainingDescriptors() { + + remainingDescriptors.values().stream() // + .filter(Property::supportsStandalone) // + .map(it -> Property.of(entity.getTypeInformation(), it)) // + .filter(PersistentPropertyFilter.INSTANCE::matches) // + .forEach(this::createAndRegisterProperty); + } +``` + +```kotlin + /** + * Returns whether the given {@link PropertyDescriptor} is one to create a {@link PersistentProperty} for. + * + * @param property must not be {@literal null}. + * @return + */ + public boolean matches(Property property) { + + Assert.notNull(property, "Property must not be null"); + + if (!property.hasAccessor()) { + return false; + } + + return !UNMAPPED_PROPERTIES.stream()// + .anyMatch(it -> it.matches(property.getName(), property.getType())); + } +``` + +```kotlin + public boolean hasAccessor() { + return getGetter().isPresent() || getSetter().isPresent(); + } +``` + +2. 필드를 `override` 하지 않고 스캔을 통해 `@QueryEmbedded`이나 `@QueryEmbeddable` 어노테이션을 간접적으로 주입하기. +- 스프링에서 빈 주입하는 부분을 `ComponentScanConfig`로 처리해주는 것 처럼, 이 친구로 runtime에 어노테이션을 동적으로 삽입 혹은 주입해줄 수 있다면 되지 않을까? 라는 생각이 떠올랐다. +- 하지만 빈 주입은 스프링에서 자동으로 처리해주는 작업이고, 그냥 어노테이션을 runtime에 주입해주려면 또 다른 라이브러리를 사용해야했다. + +3. 그냥 Entity용 Element를 따로 만들기 + +기존에 Document가 아닌 Element들은 도메인 모듈에서 구현했던 클래스를 그대로 사용했었는데, 이걸 엔티티처럼 분리해주면 되는거 아닐까? 하는 생각이 들었다. + +## 네번째 시도 : 클래스 분리 + +최종적으로 짠 코드는 아래와 같다. + +사실 논리적으로는 MongoDB Entity로 들어가는 class를 따로 정의한다는 것이 이상하진 않아서, 나름 타협할만한 방안이라고 생각한다. + +spring data mongoDB는 모든 필드를 스캔하고, Querydsl은 일부 필드만 스캔하는 이유가 무엇인지, 그리고 `@QueryEmbeddable`을 붙였을떄 Querydsl에서 스캔하는 필드 대상이 달라지는 것인지 여부는 아직 잘 모르겠다. 정확한 원인에 대한 해결 없이 넘어가는 느낌이라 찝찝하다. + +```kotlin +@org.springframework.data.mongodb.core.mapping.Document(collection = "documents") +@Where(clause = "is_deleted is false") +class DocumentJpaEntity( + id: UUID, + year: Int, + writer: WriterInfoJpaElement, + status: DocumentStatus, + introduce: IntroduceJpaElement, + skillSet: MutableList, + projectList: MutableList, + awardList: MutableList, + certificateList: MutableList, + isDeleted: Boolean +) : Document( + id = id, + year = year, + writer = writer as WriterInfoElement, + status = status, + introduce = introduce as IntroduceElement, + skillSet = skillSet, + projectList = projectList as MutableList, + awardList = awardList as MutableList, + certificateList = certificateList as MutableList, + isDeleted = isDeleted +) { + companion object { + fun of(document: Document) = document.run { + DocumentJpaEntity( + id = id, + year = year, + writer = WriterInfoJpaElement.of(writer), + status = status, + introduce = IntroduceJpaElement.of(introduce), + skillSet = skillSet, + projectList = projectList.map { ProjectJpaElement.of(it) }.toMutableList(), + awardList = awardList.map { AwardJpaElement.of(it) }.toMutableList(), + certificateList = certificateList.map { CertificateJpaElement.of(it) }.toMutableList(), + isDeleted = isDeleted + ) + } + } +} +``` + +```kotlin +@QueryEmbeddable +class WriterInfoJpaElement( + + elementId: UUID, + + studentId: UUID, + name: String, + email: String, + profileImagePath: String, + + grade: String, + classNum: String, + number: String, + + majorId: UUID, + majorName: String + +) : WriterInfoElement( + elementId, studentId, name, email, profileImagePath, grade, classNum, number, majorId, majorName +) { + companion object { + fun of(writer: WriterInfoElement) = writer.run { + WriterInfoJpaElement( + elementId, studentId, name, email, profileImagePath, grade, classNum, number, majorId, majorName + ) + } + } +} +``` + +## 느낀점 + +처음에 도메인을 나누고 Hexagonal Architecture로 책임을 분리하면서 똑같은 필드를 매핑하는 코드를 줄이고 싶었는데, 결국 상속과 클래스 변환 때문에 똑같이 이런 코드를 작성하게 되었다. 오류를 해결하게 되어서 기쁘긴 하지만, 뭔가 더 깔끔하게 할 수 있는 방법이 있지 않았을까 하는 생각이 든다. + +low level 원리에 대한 이해가 더 있다면 보다 근본적인 접근이 가능했을텐데 우회책만 찾은 것 같아 아쉽다. 나중에 더 생각해볼 부분이 있는 것 같다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205\354\212\244\355\202\244\353\247\210\354\204\244\352\263\204\342\200\205\352\263\240\353\240\244\354\202\254\355\225\255.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205\354\212\244\355\202\244\353\247\210\354\204\244\352\263\204\342\200\205\352\263\240\353\240\244\354\202\254\355\225\255.md" new file mode 100644 index 00000000..4e4d2a8d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205\354\212\244\355\202\244\353\247\210\354\204\244\352\263\204\342\200\205\352\263\240\353\240\244\354\202\254\355\225\255.md" @@ -0,0 +1,62 @@ +--- +title: 'MongoDB 스키마설계 고려사항' +lastUpdated: '2024-03-02' +--- + +> The best approach to design is to represent the data **the way your application sees it**
"당신의 어플리케이션이 바라보는 관점에서 설계하는 것이 가장 좋은 접근(설계) 방법이다."
- Kristina Chodorow, (2019) MongoDB: the Definitive Guide: O'Reily + +RDB에서의 스키마 설계는 (application이나 query와는 무관하게) entity를 정의하고 정규화를 통해 중복을 없애는 정형화된 프로세스를 따른다. 이에 비해, MongoDB는 application 관점에서 수행되는 query의 성능을 고려하여 유연한 설계를 필요로 한다. + +mongoDB 설계에서 고려해야 할 사항들을 살펴보자. + +## Access Pattern + +Application이 데이터에 접근하는 패턴을 파악하여 collection을 정의한다. 아래와 같은 것들을 고려할 수 있다. + +- Application이 어떤 query들을 수행하는가? +- 어떤 query를 가장 빈번하게 수행하는가? +- Application은 주로 DB에서 데이터를 읽는가? 아니면 쓰는가? + +함께 조회되는 경우가 빈번한 데이터들은 **같은 collection에 담아 query의 횟수를 줄이고**, 주로 읽기만 하는 데이터와 자주 업데이트하는 데이터는 **별개의 collection에 담아 최적화** 할 수 있다. + +## Relation + +Access Pattern을 분석하여 collection들이 정의된 후에는, collection 간의 관계를 파악한다. + +만약에, `Product`와 `Category`라는 두 collection이 DB에 존재한다고 해보자. 정형적인 RDB 설계에서는 Product Table에 category_id라는 칼럼을 두어 Category Table과 Join 하여 카테고리 정보를 가져오도록 설계될 것이다. + +![image](https://user-images.githubusercontent.com/81006587/206883612-1cd727cf-ea9c-42c1-9ab5-6e4dcc8d5035.png) + +이런 entity간의 relation을 MongoDB에서는 collection 간에 **reference**할지, **embed**할지 선택하여 나타낼 수 있다. 여기서, reference란 collection 간 참조할 수 있도록 id를 저장하는 것이고, embed는 관계된 document를 통째로 저장하는 것이다. + + + + + +위 그림들은 순서대로 reference 방식, embed 방식을 나타낸다. + +두 방식을 선택하는 기준은, 해당 collection이 application에서 어떻게 사용되느냐에 따라 다르다. 예를 들어, 상품 페이지에 카테고리 정보가 함께 보인다면 두 정보는 대부분 함께 조회된다고 봐야 할 것이다. 따라서 query 한 번에 모두 가져올 수 있도록 embed 하는 것이 바람직한 선택이다. + +반면에, 카테고리 정보가 끊임없이 변경되는 상황이라면 어떨까? embed 방식의 경우, 해당 카테고리의 모든 상품 document를 찾아서 embed된 카테고리 정보를 하나하나 수정해야 한다. 하지만 reference 방식의 경우 별도로 관리되는 카테고리 collection에서 하나의 document만 찾아 수정하면 된다. 이렇게 잦은 수정이 예상되는 경우, reference 방식이 더 바람직하다고 볼 수 있다. + +Reference는 데이터를 정규화하여 쓰기를 빠르게 하고, embed는 데이터를 비정규화하여 읽기를 빠르게 한다. 일반적으로 최대한 정규화하여 중복을 제거하는 것이 바람직하다고 여겨지는 RDB와 달리, MongoDB는 적절한 수준의 비정규화가 필요한 경우가 많다. NoSQL의 경우 RDB처럼 복잡한 Join 연산이 불가능하기 때문에, 정규화를 수행하여 collection을 많이 쪼개놓은 경우에 필요한 복잡한 데이터로 재구성하는 것이 어려울 수도 있다. + +embed와 reference가 사용되기 좋은 상황을 정리하여 나타내자면 아래 표와 같다. + +|embed 권장|reference| +|-|-| +|변경이 (거의)없는 정적인 데이터|변경이 잦은 데이터| +|함께 조회되는 경우가 빈번한 데이터|조회되는 경우가 많지 않은 데이터| +|빠른 읽기가 필요한 경우|빠른 쓰기가 필요한 경우| +|결과적인 일관성이 허용될 때|즉각적으로 일관성이 충족되어야 할 때| + +## Cardinality + +서로 관계된 collection 간에 공유 필드가 여러 document에 걸쳐 반복적으로 존재할 수 있다. + +온라인 북스토어를 운영한다고 가정해면 책마다 제목은 하나씩이니 One-to-One 관계이고, 책은 여러 리뷰를 가질 수 있으니 책과 리뷰는 One-to-Many관계이다. 그리고 하나의 책은 여러 태그를 가질 수 있고, 하나의 태그는 여러 책을 포함하므로 책과 태그는 Many-to-Many 관계이다. Cardinality는 이렇듯 One-to-One, One-to-Many, Many-to-Many가 존재한다. + +mongoDB 설계를 할때는 단순히 many에서 끝나지 않고, how many를 고려하는 것도 필요하다. 책 한 권이 갖는 태그는 기껏해야 10개 미만이지만, 태그 하나에 포함되는 도서는 수백~수천 권이 될 것이니. 책과 태그는 Many-to-Few 관계라고 볼 수 있다. + +책과 리뷰, 책과 주문기록의 관계는 둘 다 One-to-Many이지만, 리뷰보다는 주문기록의 훨씬 많을 것으로 예상할 수 있는데, 일반적으로 적은 many는 embed, 많은 many는 개별 collection을 두어 reference 하는 것이 바람직하다. + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205\354\234\240\354\240\200\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205\354\234\240\354\240\200\352\264\200\353\246\254.md" new file mode 100644 index 00000000..dd66e5df --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/MongoDB/MongoDB\342\200\205\354\234\240\354\240\200\352\264\200\353\246\254.md" @@ -0,0 +1,82 @@ +--- +title: 'MongoDB 유저관리' +lastUpdated: '2023-12-20' +--- +## 계정 관리 +사용자 계정은 MongoDB 내부에 생성 된 Database마다 별도로 관리된다. +Database의 계정 정보는 db.system.users 컬렉션에 저장된다. + +계정의 전체 목록을 확인하고 싶다면 mongosh 명령어를 사용하거나 getUsers 메소드를 사용할 수 있다. + +```bash +# mongosh 명령어 +> show users + +# MongoDB 메소드 +> db.getUsers() +``` + +getUsers 메소드는 db.system.users 컬렉션을 쿼리해서 반환한다. +show users 명령어는 getUsers가 반환한 결과를 가공하여 출력한다. +하단에 ok: 1의 유무로 구분할 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/226539680-c71e2ab0-c26c-4bc9-b50d-5151ccb91bdb.png) + +따라서 계정 정보를 관리하기 위해서는 사용할 Database로 전환해야 한다. +Database를 전환하는 방법은 다음과 같다. + +```bash +# Database 전환 +> use Database이름 + +# 유저를 볼 수 있는 database +> use admin + +``` + +관리자 계정을 생성해보자. + +```js +db.createUser( { + user: "", + pwd: passwordPrompt(), // 원하는 텍스트를 입력해도 된다. + roles: [ + { role: "userAdminAnyDatabase", db: "admin" }, + { role: "readWriteAnyDatabase", db: "admin" } + ] +} ) +``` + +관리자가 아닌 일반 계정을 생성해보자. + +```js +db.createUser( { + user: "", + pwd: "", + roles: [{ role:"readWrite", db: "DB명"}] +} ) +``` + +이렇게 로그인 했을때 OK가 뜨면 성공이다. +```js +db.auth('계정명','패스워드') +``` + +### 계정 정보 수정 + +계정의 정보가 변경 된 경우 수정해야 한다. 계정 자체의 권한이나 정보, 비밀번호 등을 수정할 수 있다. + +```js +db.updateUser("수정할계정", update: { 수정할정보 } ) +``` + +### 계정 삭제 + +특정 데이터베이스에서 지정한 계정을 삭제한다. + +삭제 명령을 실행하면 경고나 확인 메시지 없이 삭제하기 때문에 주의해야 한다. + +```js +> db.dropUser("삭제할계정") +{ ok: 1 } +``` diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/NoSQL\342\200\205\353\215\260\354\235\264\355\204\260\354\234\240\355\230\225.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/NoSQL\342\200\205\353\215\260\354\235\264\355\204\260\354\234\240\355\230\225.md" new file mode 100644 index 00000000..5ee172fa --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/NoSQL\342\200\205\353\215\260\354\235\264\355\204\260\354\234\240\355\230\225.md" @@ -0,0 +1,46 @@ +--- +title: 'NoSQL 데이터유형' +lastUpdated: '2024-03-02' +--- + +NoSQL이라는 용어는 관계형 데이터베이스(RDBMS) 유형에서 벗어난 경량 DBMS류를 부르게 위한 용어이다. + +NoSQL에는 다양한 DB들이 있는데, 각 DB에서 데이터를 저장하는 유형은 Key–value store, Document store, Graph 세가지 유형으로 나뉠 수 있다. + +## Key–value store + +Key–value store(KV) 모델은, 데이터는 키와 값의 쌍으로 컬렉션을 표현하는 모델이다. 연관배열(map, dictionary라고도 함)을 기본 자료구조로 가지고있다. + +Key–value store는 가장 단순한 non-trivial 데이터 모델이며, **선택적인 키 범위를 효율적으로 검색할 수 있다**는 강력한 장점을 가지고 있다. 이 모델은 사전 순서로 키를 유지하는 개별적으로 정렬된 모델로 확장될 수 있다. + +## Document store + +Document store의 핵심개념은 Document이다. 이 유형을 문서지향 DBMS라고 부르기도 한다. + +Document는 일부 표준 형식이나 인코딩으로 데이터(또는 정보)를 캡슐화하고 인코딩된다. XML, YAML, JSON와 같은 형식을 사용하기도 한다. + +Document store는 내용을 기반으로 문서를 검색하는 API 또는 쿼리 언어를 사용하는 것이 또다른 특징인데, 구현에 따라 문서 구성 및/또는 그룹화 방법은 다를 수 있다. + +아래와 같은 것들이 기준으로 사용될 수 있다. + +- 컬렉션 +- 태그 +- 보이지 않는 메타데이터 +- 디렉토리 계층 + +RDBMS의 Table과 Document store의 collection이 유사한 것으로 간주될 수 있다. 그러나 Table의 모든 레코드는 동일한 필드 시퀀스를 가지는 반면, 컬렉션의 문서는 완전히 다른 필드를 가질 수 있다. + +## Graph + +Graph 데이터베이스는 관계가 유한한 수의 관계로 연결된 요소로 구성된 그래프로 잘 표현된 데이터를 위해 설계되었다. 그러한 데이터의 예로는 사회 관계, 대중 교통 링크, 도로 지도, 네트워크 토폴로지 등이 있다. + +## 특성 비교 + +각 NoSQL DB 유형과 RDBMS의 특성을 비교하면 다음 표와 같다. + +|이름|성능|확장성|유연성|복잡성| +|-|-|-|-|-| +|Key–value store|높음|높음|높음|없음| +|Document-oriented store|높음|상이함(높음)|높음|낮음| +|Graph database|상이함|상이함|높음|높음| +|Relational database|상이함|상이함|낮음|보통| diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/NoSQL\354\227\220\353\212\224\342\200\205ACID\352\260\200\342\200\205\354\227\206\353\213\244\352\263\240\357\274\237.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/NoSQL\354\227\220\353\212\224\342\200\205ACID\352\260\200\342\200\205\354\227\206\353\213\244\352\263\240\357\274\237.md" new file mode 100644 index 00000000..9a1e04db --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/NoSQL\354\227\220\353\212\224\342\200\205ACID\352\260\200\342\200\205\354\227\206\353\213\244\352\263\240\357\274\237.md" @@ -0,0 +1,26 @@ +--- +title: 'NoSQL에는 ACID가 없다고?' +lastUpdated: '2024-03-02' +--- + +관계형 데이터베이스는
트랜잭션 ACID 원칙을 철저히 지켜서 데이터의 무결성을 지키려 한다. + + + +관계형 데이터베이스는 위의 ACID 원칙을 지키기 위해 위와 같은 절차를 진행하게된다. 각 비율은 수행 작업의 비중을 의미한다. + +그래프를 보면 정보유지를 위한 자원을 정말 많이 사용한다는것을 알 수 있다 실질적으로 데이터를 넣고 빼고 하는 부분은 오직 12프로인 Useful Work 만 사용하면되는데 말이다. + +따라서 RDBMS가 아닌 NoSQL은, 이러한 전통적인 ACID 원칙을 철저하게 지키지 않는 대신 다른 방법을 통해 속도를 향상시키고 데이터 안전성을 챙긴다. + +# BASE 속성 + +이러한 NoSQL의 특성과 원칙을 나타내는 BASE원칙이라는 것이 있다. Basically Available, Soft state, Eventually Consistence의 약자로, 가용성과 성능을 중시하는 분산 시스템의 NoSQL 특성을 얘기한다. + +자세한 설명은 다음과 같다. + +|속성|특성|세부 설명| +|-|-|-| +|Basically
Available|가용성|– 데이터는 항상 접근 가능하다.
– 다수 스토리지에 복사본 저장| +|Soft-state|독립성|– 즉각적인 일관성이 없기 때문에 데이터 값은 시간이 지남에 따라 변경될 수 있다.| +|Eventually
Consistency|일관성|– 데이터의 일관성이 깨지더라도, 일정 시간 경과 시 데이터의 일관성 복구| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/PostgreSQL/PostgreSQL.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/PostgreSQL/PostgreSQL.md" new file mode 100644 index 00000000..3c6796ff --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/PostgreSQL/PostgreSQL.md" @@ -0,0 +1,45 @@ +--- +title: 'PostgreSQL' +lastUpdated: '2024-03-02' +--- + +PostgreSQL(포스트그레스큐엘)은 객체-관계형 데이터베이스 시스템(ORDBMS)이다. 테이블 상속, 함수 오버로딩 등의 기능을 갖추고있으며 복잡한 쿼리와 대규모 데이터베이스를 다룰 수 있도록 한다. + +PostgreSQL은 현재 세계에서 가장 많이 쓰이는 DBMS 중 하나이다. 다양한 프로그래밍 언어 및 어플리케이션을 지원하여 여러 DBMS 중에서도 특히 개발자들이 선호해 충성도가 높은 편이고, 오픈소스 커뮤니티 또한 상당히 활성화되어 있다. 전 세계에서 개최되는 컨퍼런스나 세미나도 꾸준한 편이다. + +PostgreSQL은 이러한 장점들을 가지고 있다. + +### 1. 최다 SQL 기능 지원 + +가장 오랜 기간 개발을 거친 PostgreSQL은 관계형 DB 중에서 최다 SQL을 지원한다. + +### 2. 최다 SQL 표준 지원 + +SQL : 2016 또는 ISO/IEC 9075:2016은 SQL 데이터베이스 쿼리 언어에 대한 ISO 및 ANSI 표준의 8번째 개정판으로 2016년 12월에 공식적으로 채택되었는데, PostgreSQL은 전체의 179 항목 중 170 항목인 약 95%의 SQL 표준을 지원한다. + +### 3. 풍부한 데이터 유형 지원 + +일부 NoSQL을 포함한 다양한 데이터 유형을 지원한다. +- Key-Value, XML +- JSON, JSONB +- Columnar Store +- Graph + +### 4. 대량 데이터 처리 +PostgreSQL은 다음과 같은 기능덕에 대량 데이터 처리가 가능하다. +- Table Partitioning +- Parallel query & multiple processes +- analytic & aggregate functions +- indexing & JOIN + +# PostgreSQL의 성능? + +MySQL과 PostgreSQL은 모두 현재 제공되고 있는 DBMS 중 가장 빠른 시스템으로 잘 알려져 있다. 그러나 둘 중 어느 것이 더 빠른가에 대한 대답은 명확하지 않다. 각 DB의 성능은 주로 어떤 트랜잭션을 사용하는지, 어떤 유형의 쿼리가 많이 발생되는지에 따라 달라질 수 있다. TechTarget에서 Scott Noves가 설명한 바에 따르면, + +> 하드웨어, 구성 등의 환경이 일정하다면 따라 어느 한 데이터베이스를 다른 데이터베이스보다 좋다라고 할 만한 기준은 쉽게 찾을 수 있다. 한 데이터베이스는 메모리가 거의 없는 싱글 코어 프로세서에서 더 나은 성능을 발휘하는 반면, 다른 데이터베이스는 멀티 코어 프로세서로 확장되는 이점을 더 잘 활용할 수 있습니다. 한 쪽은 읽기에 우수하고 다른 하나는 쓰기에 유리하다. + +TechTarget에 따르면, 속력 테스트에서 상반되는 결과가 도출된다. 예를 들어 Windows Skills에서는 MySQL이 속도가 더 빠르다고 하고 Benchw는 PostgreSQL이 더 빠르다. 즉, 속도는 데이터베이스를 사용하는 방식에 따라 달라진다. + +현재로서 PostgreSQL은 **대량의 데이터 집합, 복잡한 쿼리, 읽기-쓰기 작업**을 처리할 때 더 빠른 것으로 알려져 있고, 그에 반해 MySQL은 읽기 전용 명령을 사용할때 더 빠르다고 알려져 있다. + +복잡한 쿼리와 대규모 데이터베이스를 다룰 수 있는 기능이 풍부한 데이터베이스가 필요하다면 PostgreSQL를, 설치와 관리가 비교적 쉽고, 빠르고, 안정적이며, 레퍼런스가 많은 간단한 데이터베이스가 필요하다면 MySQL을 사용할 수 있다. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/PostgreSQL/PostgreSQL\353\252\205\353\240\271\354\226\264.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/PostgreSQL/PostgreSQL\353\252\205\353\240\271\354\226\264.md" new file mode 100644 index 00000000..ab2dcd54 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/PostgreSQL/PostgreSQL\353\252\205\353\240\271\354\226\264.md" @@ -0,0 +1,81 @@ +--- +title: 'PostgreSQL명령어' +lastUpdated: '2024-03-02' +--- + +#### 유저 생성 +``` +CREATE ROLE name [ [ WITH ] option [ ... ] ] +``` + +``` +where option can be: + + SUPERUSER | NOSUPERUSER + | CREATEDB | NOCREATEDB + | CREATEROLE | NOCREATEROLE + | INHERIT | NOINHERIT + | LOGIN | NOLOGIN + | REPLICATION | NOREPLICATION + | BYPASSRLS | NOBYPASSRLS + | CONNECTION LIMIT connlimit + | [ ENCRYPTED ] PASSWORD 'password' | PASSWORD NULL + | VALID UNTIL 'timestamp' + | IN ROLE role_name [, ...] + | IN GROUP role_name [, ...] + | ROLE role_name [, ...] + | ADMIN role_name [, ...] + | USER role_name [, ...] + | SYSID uid +``` + +#### example +``` +CREATE ROLE rladmsqls WITH LOGIN PASSWORD ‘password’; +ALTER ROLE rladmsqls CREATEDB; +``` + +#### 테이블 리스트 보기 +``` + \dt + ``` + + #### 유저 리스트 보기 + ``` + \du + ``` + + #### database 목록 보기 +``` + \l +``` + +#### database에 연결하기 + +``` + \connect study +``` + +## pg_ctl + +#### 사용법 + ``` +pg_ctl start [-w] [-t SECS] [-D DATADIR] [-s] [-l FILENAME] [-o "OPTIONS"] +pg_ctl stop [-W] [-t SECS] [-D DATADIR] [-s] [-m SHUTDOWN-MODE] +pg_ctl restart [-w] [-t SECS] [-D DATADIR] [-s] [-m SHUTDOWN-MODE] + [-o "OPTIONS"] +pg_ctl reload [-D DATADIR] [-s] +pg_ctl status [-D DATADIR] +pg_ctl kill 시그널이름 PID +pg_ctl register [-N SERVICENAME] [-U USERNAME] [-P PASSWORD] [-D DATADIR] + [-w] [-t SECS] [-o "OPTIONS"] +pg_ctl unregister [-N 서비스이름] +``` + +#### brew + +``` +brew services start postgresql +brew services stop postgresql +``` + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/redis/Redis.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/redis/Redis.md" new file mode 100644 index 00000000..cf2d91d4 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/redis/Redis.md" @@ -0,0 +1,10 @@ +--- +title: 'Redis' +lastUpdated: '2024-03-02' +--- + +Redis는 고성능 키-값 저장소로서 문자열, 리스트, 해시, 셋, 정렬된 셋 형식의 데이터를 지원하는 NoSQL이다. 데이터 저장소로 디스크가 아닌 메모리를 사용한다는 것이 특징이다. + +캐시, 메시지 브로커 및 스트리밍 엔진으로도 사용되며, 문자 , 해시, 목록, 집합, 비트맵, 하이퍼로그 로그, 지리 공간 인덱스 및 스트림과 같은 아주 다양한 데이터 구조를 제공한다. +Redis에 모든 데이터를 메모리에 저장하는 빠른 DB이다. + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/redis/Spring\342\200\205Redis\342\200\205Phantomkey.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/redis/Spring\342\200\205Redis\342\200\205Phantomkey.md" new file mode 100644 index 00000000..644f5ba0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/NoSQL/redis/Spring\342\200\205Redis\342\200\205Phantomkey.md" @@ -0,0 +1,16 @@ +--- +title: 'Spring Redis Phantomkey' +lastUpdated: '2024-03-02' +--- + +**spring**에서 @RedisHash로 refreshToken 등을 저장하면, 일반 키와 `phantom`키가 함께 저장되는 것을 볼 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/197697420-7e8f520a-c468-4566-9d9f-67844d6a0f6d.png) + +여기서 Phantom Key는 영속성 설정을 위한 복사본으로, 원본 복사본이 만료되고 5분 후에 만료되도록 설정된다. + +Spring에서 영속성 설정을 위해서 임의적으로 생성되는 것이다. 원래 해시가 만료되면 Spring Data Redis는 팬텀 해시를 로드하여 보조 인덱스에서 참조 제거 등의 정리를 수행한다. + +자세한 것은 Spring Redis의 공식문서에서 확인할 수 있다. + +> When the expiration is set to a positive value the according EXPIRE command is executed. Additionally to persisting the original, a phantom copy is persisted in Redis and set to expire 5 minutes after the original one. This is done to enable the Repository support to publish RedisKeyExpiredEvent holding the expired value via Springs ApplicationEventPublisher whenever a key expires even though the original values have already been gone. Expiry events will be received on all connected applications using Spring Data Redis repositories. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/OLAP.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/OLAP.md" new file mode 100644 index 00000000..8be72676 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/OLAP.md" @@ -0,0 +1,29 @@ +--- +title: 'OLAP' +lastUpdated: '2024-03-02' +--- + +OLAP(Online Analytical Processing)는 데이터 웨어하우스, 데이터 마트 또는 기타 중앙화된 통합 데이터 저장소의 대용량 데이터를 고속으로 다차원 분석하는 소프트웨어이다. + +대부분의 비즈니스 데이터에는 여러 차원, 즉 프레젠테이션, 추적 또는 분석을 위해 데이터를 분류하는 기준인 여러 범주가 있다. 예를 들어 매출 수치에는 위치(지역, 국가, 주/도, 매장), 시간(연, 월, 주, 일), 제품(의료, 남성/여성/아동, 브랜드, 유형)과 관련된 여러 차원이 있을 수 있는데, 일반적인 데이터베이스로는 2차원 데이터 밖에 저장할 수 없어 저장 성능이나 구현이 복잡할 수 있다. + +OLAP는 여러 관계형 데이터 세트에서 데이터를 추출한 후 매우 빠른 처리와 분석을 위해 다차원 형식으로 재구성하여 유용한 인사이트를 도출한다. + +image + +## OLAP 큐브란? + +대다수 OLAP 시스템의 핵심인 OLAP 큐브는 다차원 배열 데이터베이스로, 기존의 관계형 데이터베이스보다 훨씬 빠르고 효율적으로 여러 데이터 차원을 처리하고 분석할 수 있다. + +관계형 데이터베이스 테이블은 개별 레코드를 2차원(행x열) 형식으로 저장하는 스프레드시트와 같은 구조이다. 데이터베이스의 각 데이터 "팩트"는 지역 및 총 매출과 같은 두 차원(행과 열)의 교차점에 있다. + +SQL 및 관계형 데이터베이스 보고 툴은 분명히 테이블에 저장된 다차원 데이터를 쿼리, 보고 및 분석할 수 있지만 데이터 볼륨이 증가하면 성능이 저하된다. 그리고 다른 차원에 초점을 맞추기 위해 결과를 재구성하는 데 많은 작업이 필요하다. + +여기서 OLAP 큐브가 사용된다. OLAP 큐브는 추가 레이어를 사용해 테이블을 확장하는데, 각 레이어에 추가 차원(일반적으로 차원의 "개념 계층 구조"에서 다음 수준)을 추가할 수 있다. 예를 들어 큐브의 최상위 레이어는 지역별로 매출을 구성할 수 있으며, 추가 레이어는 국가, 주/도, 시/군/구 및 특정 매장일 수 있다. + +이론상 하나의 큐브는 무한한 수의 레이어를 포함할 수 있다. (3차원 이상을 나타내는 OLAP 큐브를 하이퍼큐브라고도 한다.) 또한 레이어 내에 더 작은 큐브가 존재할 수 있다. 예를 들어 각 매장 레이어에는 영업 사원 및 제품별로 매출을 정렬하는 큐브가 포함될 수 있다. 실제로 데이터 분석가는 최적의 분석 및 성능을 위해 필요한 레이어만 포함하는 OLAP 큐브를 생성한다. + +--- +참고 +- https://www.ibm.com/kr-ko/topics/olap +- https://aws.amazon.com/ko/what-is/olap/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/Alias.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/Alias.md" new file mode 100644 index 00000000..3b01a74d --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/Alias.md" @@ -0,0 +1,76 @@ +--- +title: 'Alias' +lastUpdated: '2024-03-02' +--- + +SQL에서 Alias(별칭)은 컬럼이나 테이블에 임시 이름을 주는 용도로 사용한다. + +컬럼이나 테이블 뒤에 한번 띄어쓰기를 한 후 적거나, 컬럼 뒤에 `AS`를 붙인 뒤 적어주면 된다. Alias를 지정하면 컬럼/테이블명 대신 해당 별칭을 사용할 수 있다. + +(FROM절에서서 테이블블 앨리어스를를 지정할떄는 AS를 사용하지 못한다.) + +```sql +SELECT 별칭1.컬럼1 AS '컬럼 별칭1', + 별칭1.컬럼2 AS '컬럼 별칭2', + 별칭1.컬럼3 AS '컬럼 별칭3' +FROM 테이블1 '별칭1' +``` + +`AS`를 생략하고 띄어쓰기만 해줘도 Alias를 지정할 수 있다. + +```sql +SELECT 컬럼1 '컬럼 별칭1', + 컬럼2 '컬럼 별칭2', + 컬럼3 '컬럼 별칭3' +FROM 테이블1 '별칭1' +``` + +### 컬럼을 지칭하는 방법 + +쿼리문에서 컬럼을 지칭하는 방법은 크게 세가지가 있다. + +#### 1. 컬럼의 소유주를 표기하지 않는 경우 + +```sql +SELECT 컬럼1, 컬럼2, 컬럼3 +FROM 테이블1 +``` + +장점 +- 중복되는 컬럼을 분류하기 어렵다. +단점 +- 어느 테이블의 컬럼인지 구분하기 힘들다. +- 만약 동일한 컬럼명을 갖은 테이블을 추가적으로 JOIN을 걸게 되는 경우 해당 컬럼의 소유주가 명확하지 않아 SQL문에서 오류가 발생할 수 있다. + +#### 2. 컬럼의 소유주를 테이블명으로 표기한 경우 + +```sql +SELECT 테이블1.컬럼1, 테이블1.컬럼2, 테이블1.컬럼3 +FROM 테이블1 +``` + +장점 +- 컬럼의 소유주를 명확하게 구분할 수 있다. +- 중복된 컬럼명을 갖는 테이블을 JOIN하여도 오류가 발생하지 않는다. +단점 +- 테이블명이 길어지면 컬럼 하나를 사용할 때도 불필요하게 많은 타이핑을 해야한다. +- 컬럼은 그대로이고 테이블이 변경된 경우 (ex. 2020년 매출 테이블 → 2021년 매출 테이블로 변경) 각각의 테이블 명을 컬럼명에 연결된 소유주 명으로 변경해야한다. + +#### 3. 컬럼의 소유주에 대한 Alias를 표기한 경우 + +```sql +SELECT 별칭1.컬럼1, 별칭1.컬럼2, 별칭1.컬럼3 +FROM 테이블1 AS 별칭1 +``` + +장점 +- 컬럼의 소유주를 명확하게 구분할 수 있다. +- 중복된 컬럼명을 갖는 테이블을 JOIN하여도 오류가 발생하지 않는다. +- 컬럼은 그대로이고 테이블이 바뀌어야 하는 경우 Alias의 대상이 되는 테이블만 수정하면 되기 때문에 간편하다. +단점 +- 복잡한 SQL문에 대해 각각의 alias까지 숙지해야 하는 번거로움이 있다. (별칭을 잘 지어야한다.) +- 별칭이 바뀌면 각각의 컬럼에 대해 붙은 별칭을 바꿔줘야 함 + +Alias를 사용하면 컬럼의 소유주를 구분하기 쉽고, 컬럼몀 중복을 걱정하지 않아도 되며 쿼리를 일부 수정하기도 쉬워진다. + + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/FK\354\230\265\354\205\230.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/FK\354\230\265\354\205\230.md" new file mode 100644 index 00000000..f973bd0b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/FK\354\230\265\354\205\230.md" @@ -0,0 +1,20 @@ +--- +title: 'FK옵션' +lastUpdated: '2024-03-02' +--- + +## Delete(/Modify) Action + +1. Cascade: Master 삭제 시 Child 같이 삭제 +2. Set Null: Master 삭제 시 Child의 FK 필드 Null +3. set Default: Master 삭제 시 Child FK 필드 Default 값으로 설정 +4. Restrict: Child 테이블에 PK 값이 없는 경우만 Master 삭제 허용 +5. No Action: 참조 무결성을 위반하는 삭제/수정 액션을 취하지 않음(MySQL에서는 Restrict와 동일함) + +## Insert Action + +1. Automatic: Master 테이블에 PK가 없는 경우 Master PK를 생성 후 Child +2. Set Null: Master 테이블에 PK가 없는 경우 Child 외부키를 Null 값으로 처리 +3. Set Default: Master 테이블에 PK가 없는 경우 Child 외부키를 지정된 기본값으로 입력 +4. Dependant: Master 테이블에 PK가 존재할 때만 Child 입력 허용 +5. No Action: 참조무결성을 위반하는 입력 액션을 취하지 않음 \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/GROUPING\342\200\205SETS\354\231\200\342\200\205GROUPING.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/GROUPING\342\200\205SETS\354\231\200\342\200\205GROUPING.md" new file mode 100644 index 00000000..00dbedde --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/GROUPING\342\200\205SETS\354\231\200\342\200\205GROUPING.md" @@ -0,0 +1,40 @@ +--- +title: 'GROUPING SETS와 GROUPING' +lastUpdated: '2024-03-02' +--- + +## GROUPING SETS + +인자별 소계를 계산한다. + +```sql +SELECT 종, 성별, SUM(*) AS 마릿수 +FROM 반려동물 +GROUP BY GROUPING SETS(종, 성별); +``` + +#### 결과 + +|종|성별|마릿수| +|-|-|-| +|강아지|(null)|10| +|고양이|(null)|12| +|앵무새|(null)|7| +|(null)|F|15| +|(null)|M|14| + + +## GROUPING + +GROUPING은 ROLLUP이나 CUBE, GROUPING SETS 등의 그룹함수에 의해 컬럼 값이 소계나 총합과 같이 집계된 데이터일 경우 1을 리턴하고 만약 집계된 데이터가 아니면 0을 리턴하는 함수이다. + +`WHEN THEN`문과 같이 사용하여 아래와 같이 응용할 수 있다. + +```sql +SELECT 종, + 성별, + SUM(*) AS 마릿수, + WHEN GROUPING(d.DEPARTMENT_NAME) = 1 THEN '합계 TOTAL' +FROM 반려동물 +GROUP BY ROLLUP(종); +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/GROUP\342\200\205BY\354\231\200\342\200\205HAVING\354\240\210.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/GROUP\342\200\205BY\354\231\200\342\200\205HAVING\354\240\210.md" new file mode 100644 index 00000000..f4ede6f6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/GROUP\342\200\205BY\354\231\200\342\200\205HAVING\354\240\210.md" @@ -0,0 +1,51 @@ +--- +title: 'GROUP BY와 HAVING절' +lastUpdated: '2024-03-02' +--- +# GROUP BY + +GROUP BY절은 GROUP BY절에 기재한 컬럼을 기춘으로 결과 집합을 그룹화한다. 이 GROUP BY절을 사용하면 소그룹별 기준 칼럼을 정한 후, 집계 함수를 사용하여 그룹별 통계를 계산할 수 있다. + +### GROUP BY절 쿼리 예제 +코드 +```sql +SELECT A.species, + SUM(A.count) AS "마릿수" +FROM animal A +WHERE A.birth_day = '2022%' +GROUP BY A.species; +``` +결과 +``` +species | 마릿수 +--------|-------- +강아지 | 45 +고양이 | 40 +앵무새 | 15 +``` + +# HAVING절 +

+HAVING절은 그룹을 나타내는 결과집합의 행에 조건이 적용된다. HAVING절에는 GROUP BY절의 기준 항목이나 소그룹의 집계함수가 조건으로 사용되고, 만들어진 소그룹 중 HAVING절 조건에 만족되는 내용만 출력한다. +

+

+WHERE절과 유사한 기능을 하지만, 테이블에서 추출할 행을 제한하는 WHERE절과 다르게 HAVING절은 그룹핑한 경과집합에 대한 조건을 주어 추출할 집계 데이터를 필터링한다는 차이점이 있다. +

+ +### HAVING절 쿼리 예제 +코드 +```sql +SELECT A.species, + SUM(A.count) AS "마릿수" +FROM animal A +WHERE A.birth_day = '2022%' +GROUP BY A.species; +HAVING SUM(A.count) > 20; +``` +결과 +``` +species | 마릿수 +--------|-------- +강아지 | 45 +고양이 | 40 +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/INNER\342\200\205JOIN\352\263\274\342\200\205OUTER\342\200\205JOIN.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/INNER\342\200\205JOIN\352\263\274\342\200\205OUTER\342\200\205JOIN.md" new file mode 100644 index 00000000..20177d01 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/INNER\342\200\205JOIN\352\263\274\342\200\205OUTER\342\200\205JOIN.md" @@ -0,0 +1,100 @@ +--- +title: 'INNER JOIN과 OUTER JOIN' +lastUpdated: '2024-03-02' +--- + +JOIN은 두 테이블의 데이터를 합치는 것을 의미한다. INNER JOIN과 OUTER JOIN은 그 방식에 차이가 있다. 중복 컬럼을 가지고 있지 않은 A와 B 테이블을 JOIN한다고 가정할때,
일반적으로 INNER JOIN은 A와 B의 교집합, 즉 벤다이어그램으로 그렸을 떄 교차되는 부분이 결과로 추출되고,
OUTER JOIN은 A와 B의 합집합, 즉 벤타이어그램으로 그렸을 때 두 부분의 전체가 결과로 추출된다. (단, Left Outer Join과 Right Outer Join은 완전히 합집합은 아니다. 밑에서 예제로 더 알아보자) + + + +--- + +두개의 테이블이 있다고 가정해보자 + +``` +A B +- - +1 3 +2 4 +3 5 +4 6 +``` + +### Inner Join + +Inner join을 하면 두 집합에서 공통되는 열만 남는다. + +```sql +SELECT * +FROM a +INNER JOIN b +ON a.a = b.b +``` + +``` +a | b +--+-- +3 | 3 +4 | 4 +``` + +### Left Outer Join + +Left outer join을 하면 왼쪽에 있는 테이블(A) 전체와, B 테이블에서 공통되는 부분이 남는다. 공통되지 않는 부분은 null이 된다. + +```sql +select * from a LEFT OUTER JOIN b on a.a = b.b; +``` + +``` +a | b +--+----- +1 | null +2 | null +3 | 3 +4 | 4 +``` + +### Right Outer Join + +Right outer join을 하면 오른쪽에 있는 테이블(B) 전체와, A 테이블에서 공통되는 부분이 남는다. 공통되지 않는 부분은 null이 된다. + +```sql +SELECT * +FROM a +RIGHT OUTER JOIN b +ON a.a = b.b +``` + +``` +a | b +-----+---- +3 | 3 +4 | 4 +null | 5 +null | 6 +``` + +### Full outer join + +Full outer join을 하면 A와 B의 합집합을 얻게 됩니다. B에는 있는데 A에 없는 부분은 A에서는 해당 부분이 null 이 되고, A에는 있는데 B에 없는 부분에서는 B에서는 해당 부분이 null 이 된다. + +```sql +SELECT * +FROM a +FULL OUTER JOIN b +ON a.a = b.b +``` + +``` + a | b +-----+----- + 1 | null + 2 | null + 3 | 3 + 4 | 4 +null | 6 +null | 5 +``` + +용어가 조금 헷갈리지만, 제대로 숙지해야한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ON\354\240\210.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ON\354\240\210.md" new file mode 100644 index 00000000..0ba56fb6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ON\354\240\210.md" @@ -0,0 +1,103 @@ +--- +title: 'ON절' +lastUpdated: '2024-03-02' +--- + +On절은 조인문을 사용할때 조인할 조건을 설정하기 위한 명령어이다. 조회 결과를 필터링한다는 점에서 Where절과 비슷하지만, 실행 시기와 용도에 차이가 있다. + +--- + + + +
+ +On절은 Where절과 다르게 테이블 조인 이전에 실행된다. 그렇기 때문에 Join할 테이블의 일부 컬럼을 가져오고 싶을 때 ON절을 사용한다. + +그러나 Inner join을 사용하는 경우엔 조건의 위치와 테이블의 순서에 무관하게 사용할 수 있다. 왜냐하면 조인하는 두 테이블의 위치관계나, 쿼리 순서에 상관없이 결과가 똑같이 교집합으로 나오기 때문이다. + +On절은, Outer join을 사용할 떄 의미를 가진다. + +두개의 테이블이 있다고 가정해보자. + +``` +A B +- - +1 3 +2 4 +3 5 +4 6 +``` + +아래의 쿼리로 Left Outer Join을 실행했다. + +```sql +select * +from a +LEFT OUTER JOIN b; +``` + +``` +a | b +--+----- +1 | null +2 | null +3 | 3 +4 | 4 +``` + +On절이나 Where절이 없기 떄문에 전체가 출력된다. + +여기서 나는 B 테이블의 값이 3인 경우에만 조인하여 값을 나타내고 싶다. 원하는 결과값은 다음과 같다. + +``` +a | b +--+----- +1 | null +2 | null +3 | 3 +4 | null +``` + +--- + +근데 여기서, Where절로 B 테이블의 값이 3인 경우를 출력하도록 하면 어떤 결과값이 나올까? + +```sql +SELECT * +FROM a +LEFT OUTER JOIN b +ON a.a = b.b +WHERE b.b = 3; +``` + +``` +a | b +--+----- +3 | 3 +``` + +이런 결과값이 나왔다. B 테이블의 값이 3인 경우에 조인한 것이 아니라 모두 조인한 후 B 테이블의 값이 3인 컬럼만 출력되었다. + +내가 원하는 결과를 내기 위해선, Join을 실행하기 전에 미리 B 테이블의 컬럼을 필터링 한 후 A 테이블과 출력해야 한다. + +그렇게 하기 위해서 On절에 조건을 추가했다. + +```sql +select * +from a +LEFT OUTER JOIN b +on a.a = b.b AND b.b = 3; +``` + +``` +a | b +--+----- +1 | null +2 | null +3 | 3 +4 | null +``` + +원하는 결과를 출력할 수 있었다. + +Join문에서 ON절은 필수적으로 필요하며, Where과는 사용하는 상황에서 차이가 있다는 것을 알 수 있었다. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ORDER\342\200\205BY\354\240\210.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ORDER\342\200\205BY\354\240\210.md" new file mode 100644 index 00000000..e8d70c8f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ORDER\342\200\205BY\354\240\210.md" @@ -0,0 +1,17 @@ +--- +title: 'ORDER BY절' +lastUpdated: '2024-03-02' +--- +

+ORDER BY절은 SELECT문에서 조회한 데이터 집합을 특정 칼럼 기준으로 정렬한 후 데이터를 출력하는 역할을 한다. 오름차순(ASC) 또는 내림차순(DESC)로 정렬방식을 지정할 수 있다. 정렬방식을 지정하지 않으면 기본적으로 오름차순이 적용된다. +

+

+정렬은 일반적으로 숫자에서는 작은 숫자가 먼저, 날짜형 데이터에서는 이른 시간이 먼저, 문자형 데이터에서는 사전순으로 앞서는 문자열이 먼저로 간주된다. Oracle DBMS에서는 NULL값을 가장 큰 값으로 간주하여 오름차순으로 정렬했을 경우에는 가장 마지막에, 내림차순으로 정렬헀을 경우에는 가장 먼저 위치한다는 특징이 있다. +

+ +### ORDER BY절 쿼리 예제 +```sql +SELECT * +FROM 테이블명1 A +ORDER BY A.컬럼명1 +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ROLLUP\352\263\274\342\200\205CUBE.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ROLLUP\352\263\274\342\200\205CUBE.md" new file mode 100644 index 00000000..0fd87499 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/ROLLUP\352\263\274\342\200\205CUBE.md" @@ -0,0 +1,58 @@ +--- +title: 'ROLLUP과 CUBE' +lastUpdated: '2024-03-02' +--- + +## ROLLUP + +전체 학생 테이블에서 + +```sql +SELECT COUNT(*) +FROM student +GROUP BY (grade) +``` + +와 같이 데이터를 조회하면 1학년, 2학년, 3학년 각 학생의 인원 수가 조회되는데, 여기서 GROUP BY를 `GROUP BY ROLLUP (grade)`로 변경하면 각 학년별 인원수와 전체 인원수까지 모두 집계해준다. + +```sql +SELECT student.grade AS 학년, + COUNT(*) AS 인원수 +FROM student +GROUP BY ROLLUP(grade) +``` + +#### 결과 + +|학년|인원수| +|-|-| +|1학년|72| +|2학년|80| +|3학년|80| +|(null)|232| + +ROLLUP에 여러 인자를 넣는 경우에는, 인자를 넣는 순서에 따라 결과가 바뀔 수 있다. + +## CUBE + +CUBE 함수는 그룹핑 컬럼이 가질 수 있는 모든 경우의 수에 대하여 소계(SUBTOTAL)과 총계(GRAND TOTAL)을 생성한다. 컬럼간 카타시안 곱으로 통계를 낸다고 생각하면 된다. ROLLUP 함수와는 다르게 CUBE함수는 인자의 순서가 달라도 결과는 같다. + +```sql +SELECT student.grade AS 학년, + student.sex AS 성별, + COUNT(*) AS 인원수 +FROM student +GROUP BY CUBE(grade, sex) +``` + +#### 결과 + +|학년|성별|인원수| +|-|-|-| +|1학년|남자|62| +|1학년|여자|10| +|2학년|남자|71| +|2학년|여자|9| +|3학년|남자|67| +|3학년|여자|13| +|(null)|(null)|232| diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/SELECT\354\277\274\353\246\254\342\200\205\354\213\244\355\226\211\354\210\234\354\204\234.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/SELECT\354\277\274\353\246\254\342\200\205\354\213\244\355\226\211\354\210\234\354\204\234.md" new file mode 100644 index 00000000..52208d61 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/SELECT\354\277\274\353\246\254\342\200\205\354\213\244\355\226\211\354\210\234\354\204\234.md" @@ -0,0 +1,6 @@ +--- +title: 'SELECT쿼리 실행순서' +lastUpdated: '2024-03-02' +--- + +![image](https://user-images.githubusercontent.com/81006587/198918916-5a48486e-eb32-4767-ace3-7a63f8e14066.png) diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\352\263\204\354\270\265\355\230\225\342\200\205\354\247\210\354\235\230.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\352\263\204\354\270\265\355\230\225\342\200\205\354\247\210\354\235\230.md" new file mode 100644 index 00000000..4d4fef68 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\352\263\204\354\270\265\355\230\225\342\200\205\354\247\210\354\235\230.md" @@ -0,0 +1,38 @@ +--- +title: '계층형 질의' +lastUpdated: '2024-03-02' +--- + +계층형 질의란 테이블에 계층형 데이터가 존재하는 경우 데이터를 조회하기 위한 구문이다. + +계층형 데이터는 동일 테이블에 상위와 하위 데이터가 포함된 데이터를 뜻하고, 엔티티를 순환관계 모델로 설계하는 경우 발생한다. + +#### 계층형 질의 사용 방법 + +|쿼리구문|설명| +|-|-| +|START WITH|계층구조 전개의 시작 위치를 지정하는 구문이다. 루트 데이터를 지정한다.| +|CONNECT BY|다음에 전개될 자식 데이터를 지정한다. 자식 데이터는 CONNECT BY 절에 주어진 조건을 만족해야한다.(JOIN)| +|PRIOR|CONNECT BY 절에 사용되며 현재 읽은 칼럼을 지정한다.| +|PRIOR 자식|부모형태를 사용하면 계층구조에서 부모 -> 자식 방향으로 순방향 전개| +|PRIOR 부모|자식 형태를 사용하면 자식 -> 부모 방향으로 역방향 전개| +|NOCYCLE|데이터를 전개하면서 이미 나타났던 동일한 데이터가 전개중에 다시 나타나는 경우 CYCLE이 생성되어 무한루프에 빠진다. NOCYCLE 구문을 사용하면 CYCLE이 발생하는 경우 이후 데이터 전개를 방지한다.ㅣ +|ORDER SIBLINGS BY|형제노드(동일 LEVEL) 사이 데이터 정렬| +|WHERE|모든 전개를 수행한 뒤 지정조건을 통해 데이터 필터링| + +#### 계층형 질의 가상 컬럼 + +|이름|설명| +|-|-| +|LEVEL|루트데이터이면 1, 그보다 하위 데이터이면 2를 반환하며 리프노드까지 1씩 증가
*MAX(LEVEL)은 리프노드를 반환| +|CONNECT_BY_LEAF|전개 과정에서 리프데이터이면 1, 그렇지 않으면 0을 반환| +|CONNECT_BY_CYCLE|전개 과정에서 자식을 갖는데 해당 데이터가 조상으로 존재하면 1을 반환, 그렇지 않으면 0을 반환| + +#### 계층형 질의 함수 + +|이름|설명| +|-|-| +|SYS_CONNECT_BY_PATH|루트데이터부터 현재 전개할 데이터까지의 경로를 표시
+(SYS_CONNECT_BY_PATH(칼럼명, 경로 분리자))| +|CONNECT_BY_ROOT|현재 전개할 데이터의 루트 데이터를 표시(단항 연산자)
+(CONNECT_BY_ROOT(칼럼))| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\204\234\353\270\214\354\277\274\353\246\254.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\204\234\353\270\214\354\277\274\353\246\254.md" new file mode 100644 index 00000000..f7cf4ee0 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\204\234\353\270\214\354\277\274\353\246\254.md" @@ -0,0 +1,51 @@ +--- +title: '서브쿼리' +lastUpdated: '2024-03-02' +--- + +서브쿼리란 하나의 SQL문 안에 포함되어 있는 또 다른 SQL문을 말한다. + +조인은 조인에 참여하는 모든 테이블이 대등한 관계에 있기 때문에, 조인에 참여하는 모든 테이블의 컬럼을 어디에서든 자유롭게 사용(참조)할 수 있지만, 서브퀴리는 메인쿼리에서 따로 파생되어 생긴 쿼리이기 떄문에 자유로운 참조가 불가능하다. + +**서브쿼리는 메인쿼리의 컬럼을 모두 사용할 수 있지만, 메인 쿼리는 서브쿼리의 컬럼을 사용할 수 없다.** 메인쿼리는 서브쿼리에게 자신의 컬럼을 줄 수 있지만, 서브쿼리의 컬럼을 사용할 순 없다. 반대로 서브쿼리는 메인쿼리가 가지고 있는 컬럼을 사용할 수 있지만, 컬럼을 줄 순 없다. + +## 서브쿼리 사용 위치 + +서브쿼리 사용이 가능한 위치는 다음과 같다. 똑같은 서브쿼리라도 위치에 따라 다른 명칭으로 불리기도 한다. + +|위치|명칭| +|-|-| +|SELECT절|스칼라 서브쿼리| +|FROM절|인라인뷰 서브쿼리 (한 라인으로 일회용 뷰를 생성하는 것과 비슷한 느낌이다.)| +|WHERE절|서브쿼리| +|HAVING절|서브쿼리| +|INSERT문의 VALUES절|서브쿼리| +|UPDATE문의 SET절|서브쿼리| + +## 동작 방식에 따른 서브쿼리 분류 + +서브쿼리는 동작 방식에 따라 아래 두가지로 분류할 수 있다. + +|동작방식|설명| +|-|-| +|비연관 서브쿼리|- 서브쿼리가 메인쿼리의 컬럼을 가지고 있지 않은 형태의 서브쿼리이다.
- 메인쿼리에 값을 제공하기 위한 목적으로 주로 사용한다.
(ex. `SELECT절`, `FROM절`, `VALUES절` 등)| +|연관 서브쿼리|- 서브쿼리가 메인쿼리의 컬럼을 가지고 있는 형태의 서브쿼리다.
- 일반적으로 메인쿼리를 수행하여 읽은 데이터의 조건을 확인하기 위해 사용된다.
(ex. `WHERE절`, `HAVING절` 등... 단, 절대적인 것은 아니다!)| + +## 동작 방식에 따른 서브쿼리 분류 + +서브쿼리는 반환 형태에 따라 아래 세가지로 분류할 수 있다. + +|반환 형태|설명| +|-|-| +|단일행 서브쿼리|- 서브쿼리의 실행 결과가 **항상 1건 이하**인 서브쿼리를 의미한다.
- 항상 비교 연산자과 함께 사용된다(=, <, <+, >, >=, <>)| +|다중행 서브쿼리|- 서브쿼리의 실행 결과가 **여러 건**인 서브쿼리를 의미한다.
- 다중행 비교 연산자와 함께 사용된다.(IN, ALL, ANY, SOME, EXISTS)| +|다중컬럼 서브쿼리|- 서브쿼리의 실행 결과로 **여러(다중) 컬럼**을 반환한다.
- 메인쿼리의 조건 절에 여러 컬럼을 동시에 비교할 수 있다.
- 서브쿼리와 메인쿼리의 컬럼 수와 컬럼 순서가 동일해야한다.| + +서브쿼리는 특정 값을 메인쿼리에 반환하게 된다. 이때 1건 혹은 0건을 반환하는 서브쿼리를 단일행 서브쿼리라고 부르고, 여러거느이 행을 반환하는 서브쿼리를 다중행 서브쿼리라고 한다. 또한 단일행이든 다중행이든 반환하는 컬럼의 개수가 2개 이상인 경우 다중컬럼 서브쿼리라고 한다. + +## 서브쿼리 사용시 주의점 + +서브쿼리를 사용할 때는 다음과 같은 사항을 유의해야한다. + +- 서브쿼리는 꼭 소괄호`()`로 감싸서 사용해야한다. +- 서브쿼리 내에서는 ORDER BY 절을 사용하지 못한다. ORDER BY절은 전체 SQL문 내에서 오직 1개, 맨 마지막 SQL문 아래에만 올 수 있다. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\234\210\353\217\204\354\232\260\342\200\205\355\225\250\354\210\230.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\234\210\353\217\204\354\232\260\342\200\205\355\225\250\354\210\230.md" new file mode 100644 index 00000000..49e21eae --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\234\210\353\217\204\354\232\260\342\200\205\355\225\250\354\210\230.md" @@ -0,0 +1,76 @@ +--- +title: '윈도우 함수' +lastUpdated: '2024-03-02' +--- + +SELECT문을 통한 작업을 하다보면 결과집합의 각 행과 행의 관계에서 다양한 연산처리를 해야할 떄가 있다. (누적합계, 누적비율, 누적등수 등) + +이러한 연산처리를 할 수 있게 하는 것이 바로 윈도우 함수(Window Function)이다. 분석함수라고 불리기도 한다. + +## 윈도우 함수의 종류 + +### 순위 + +|함수명|설명| +|-|-| +|`RANK`|지정한 기준에 따라 순위를 구하고, 동일한 순위가 있다면 **건너뛰고 다음 순위로 산출**한다. (1, 2, 2, 4)| +|`DENSE_RANK`|지정한 기준에 따라 순위를 구하고, 동일한 순위가 있다면 **건너뛰지 않고 다음 순위로 산출**한다. (1, 2, 2, 3)| +|`ROW_NUMBER`|지정한 기준에 따라 순위를 구하고, 동일 순위가 있어도 **무조건 순위를 산출**한다. (1, 2, 3, ,4)| + +### 집계 + +|함수명|설명| +|-|-| +|`SUM`|지정한 기준에 따라 **합계**를 구한다.| +|`MAX`|지정한 기준에 따라 **최댓값**을 구한다.| +|`MIN`|지정한 기준에 따라 **최솟값**을 구한다.| +|`AVG`|지정한 기준에 따라 **평균값**을 구한다.| +|`COUNT`|지정한 기준에 따라 **갯수**를 구한다.| + + +### 행 순서 + +|함수명|설명| +|-|-| +|`FIRST)VALUE`|지정한 기준에 따라 **가장 먼저** 나오는 값을 구한다.| +|`LAST_VALUE`|지정한 기준에 따라 **가장 나중에** 나오는 값을 구한다.| +|`LAG`|지정한 기준에 따라 **이전값**을 구한다.| +|`LEAD`|지정한 기준에 따라 **다음값**을 구한다.| + +### 그룹 내 비율 + +|함수명|설명| +|-|-| +|`CUME_DIST`|지정한 기준에 따라 누적 백분융을 구한다. 지속적으로 누적되다가 최종행은 1이 된다.| +|`PERCENT_RANK`|지정한 기준에 따라 각 행의 순서별 백분율을 구한다. 제일 먼저 나오는 것을 0, 가장 늦게 나오는 것을 1로 한다.| +|`NTILE`|지정한 기준에 따라 특정 값으로 N등분한 결과를 구한다.| +|`RATIO_TO_REPORT`|지정한 기준에 따라 각 행이 차지하는 비율을 나타낸다.| + + +## 윈도우 함수 문법 + +윈도우 함수는 SELECT 절에서 사용되며, 기본적인 문법은 아래와 같다. + +```sql +SELECT + 윈도우함수(인자) OVER(PARTITION BY 컬럼 ORDER BY 컬럼) + 윈도우절(ROWS|RANGE BETWEEN + UNBOUND PRECEDING|CURRENT ROW + AND UNBOUND FOLLOWING|CURRENT ROW) + FROM 테이블명 +; +``` + +인자는 윈도우 함수에 따라 객수가 달라질 수 있다. + +OVER문 안의 `PARTITION BY`는 집계를 내는, 또는 등수를 매기는 컬럼을 그룹화하는 속성이고, `ORDER BY`는 정렬 기준을 기재하는 속성이다. 등수를 매기는 윈도우 함수의 경우 ORDER BY의 내용에 따라 등수를 매기는 기준이 정해진다. + +윈도우절은 윈도우 함수가 연산을 처리하는 대상이 되는 행의 범위를 지정하는 절이다. 만약, 전체 결과집합의 연산을 하고싶다면 윈도우절을 사용하지 않아도 되지만, 특정 행의 범위를 지정하고 싶다면 윈도우 절 부분에 아래와 같이 작성해야한다. + +```sql +ROWS BETWEEN A AND B 또는 +RANGE BETWEEN A AND B +``` + + + diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\240\234\354\225\275\354\241\260\352\261\264.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\240\234\354\225\275\354\241\260\352\261\264.md" new file mode 100644 index 00000000..74587a3b --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\240\234\354\225\275\354\241\260\352\261\264.md" @@ -0,0 +1,13 @@ +--- +title: '제약조건' +lastUpdated: '2024-03-02' +--- +제약조건은 테이블에 입력되는 데이터가 사용자가 원하는 조건을 만족하는 데이터만 입력되는 것을 보장한다. 제양조건은 데이터의 무결정을 유지하기 위한 DBMS의 보편적인 방법이다. +|제약조건|설명| +|-|-| +|PRIMARY KEY|테이블에 저장된 행들 중에서 특정 행을 고유하게 식별하기 위해서 사용한다.
한 테이블에는 하나의 기본키만 정의할 수 있다.
기본키 생성 시 DBMS는 유일 인덱스(Unique index)를 자동으로 생성한다.
기본키 칼럼에는 NULL 입력이 불가능하다.
기본키는 UNIQUE제약조건과 NOT NULL 제약조건을 만족해야한다.| +|UNIQUE KEY|테이블에 저장된 행들 중에서 특정 행을 고유하게 식별하기 위해 생성한다.
기본키와 다르게 NULL 입력이 가능하다.| +|NOT NULL|NULL 입력을 금지하는 제약조건이다.| +|CHECK|입력할 수 있는 값의 종류 혹은 범위를 제한한다.| +|FOREIGN KEY(REFERENCES)|다른(부모 혹은 참조) 테이블의 기본키를 외래키로 지정하는 경우 생성한다.
참조 무결성 제약조건이라고도 한다.| +|DEFALT|해당 칼럼에 아무런 값도 입력하지 않았을 때 지정한 디폴트 값으로 데이터가 입력된다.| diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\247\221\352\263\204\355\225\250\354\210\230.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\247\221\352\263\204\355\225\250\354\210\230.md" new file mode 100644 index 00000000..c641f071 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\247\221\352\263\204\355\225\250\354\210\230.md" @@ -0,0 +1,16 @@ +--- +title: '집계함수' +lastUpdated: '2024-03-02' +--- +집계함수를 이용하면 그룹별 집계 결과를 계산하여 한 행으로 나타낼 수 있다.
+대표적인 집계함수는 아래와 같은 것들이 있다. +|항목|결과| +|-|-| +|COUNT(*)|NULL 값을 포함한 행의 수를 출력한다.| +|COUNT(표현식)|표현식의 값이 NULL이 아닌 행의 수를 출력한다.| +|SUM(표현식)|표현식이 NULL 값인 것을 제외한 합계를 출력한다.| +|AVG(표현식)|표현식이 NULL 값인 것을 제외한 평균을 출력한다.| +|MAX(표현식)|표현식이 NULL 값인 것을 제외한 최대값을 출력한다.| +|MIN(표현식)|표현식이 NULL 값인 것을 제외한 최솟값을 출력한다.| +|STDDEV(표현식)|표현식이 NULL 값인 것을 제외한 출력한다.| +|VARIAN(표현식)|표현식이 NULL 값인 것을 제외한 출력한다.| diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\247\221\355\225\251\354\227\260\354\202\260\354\236\220.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\247\221\355\225\251\354\227\260\354\202\260\354\236\220.md" new file mode 100644 index 00000000..ad5823cd --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\247\221\355\225\251\354\227\260\354\202\260\354\236\220.md" @@ -0,0 +1,13 @@ +--- +title: '집합연산자' +lastUpdated: '2024-03-02' +--- + +일반적으로 수학에서 사용되는 집합연산으로는 합집합, 교칩한, 차집합, 곱집합등이 있다. SQL문에서도 각각의 일반집합연산에 해당하는 명령어가 존재한다. + +|일반집합연산자|SQL문|설명| +|-|-|-| +|합집합
(UNION 연산)|UNION, UNION ALL|- UNION 연산은 수학적으로 합집합을 하는 연산이다.
- UNION은 교집합의 중복을 제거한 결과를 나타내기 때문에, 정렬 작업으로 인한 시스템 부하가 일어날 수 있다.
- UNION ALL을 쓰면 중복 결과를 그대로 보여준다.
- 만일 UNION과 UNION ALL의 출력 결과가 같다면 응답속도 향상, 자원 효율화 측면에서 UNION ALL을 쓰는것이 더 낫다.| +|교집합
(INTERSECTION 연산)|INTERSECT|- INTERSECTION은 수학의 교집합을 제공하기 위한 함수이다.
- 두 집합의 공통 집합(공통된 행)을 추출한다.| +|차집합
(DIFFERENCE 연산)|EXCEPT, (Oracle) MINUS|- DIFFERNCE는 수학의 차집합으로서 첫 번쨰 집합에서 두 번째 집합을 제외한 부분이다.
- Oracle 외 대다수의 DBMS 제품은 EXCEPT를 사용하고 오라클은 MINUS라는 용어를 사용한다.| +|PRODUCT 연산|CROSS JOIN|- PRODUCT 연산은 CROSS(ANSI/ISO 표준) PRODUCT라고 불리는 곱집합으로 JOIN 조건이 없는 경우 생길 수 있는 모든 데이터의 조합을 말한다.
- 양쪽 집합의 M*N 건의 데이터 조합이 발생한다.
- 카테시안 곱(CARTESIAN PRODUCT)라고도 불린다.| \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/DB\342\200\205\354\273\244\353\204\245\354\205\230\342\200\205\355\222\200.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/DB\342\200\205\354\273\244\353\204\245\354\205\230\342\200\205\355\222\200.md" new file mode 100644 index 00000000..bd6967e4 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/DB\342\200\205\354\273\244\353\204\245\354\205\230\342\200\205\355\222\200.md" @@ -0,0 +1,77 @@ +--- +title: 'DB 커넥션 풀' +lastUpdated: '2024-03-02' +--- + +일반적인 데이터 연동과정은 웹 어플리케이션이 필요할 때마다 데이터베이스에 연결하여 작업하는 방식이다. 하지만 이런 식으로 필요할 때마다 연동해서 작업할 경우 데이터베이스 연결에 시간이 많이 걸리는 문제가 발생한다. + +예를들어 거래소의 경우, 동시에 몇천명이 동시에 거래 및 조회 기능을 사용하는데 매번 데이터베이스와 커넥션을 맺고 푸는 작업을 한다면 굉장히 비효율적일 것이다. + +이 문제를 해결하기 위해 현재는 웹 어플리케이션이 실행됨과 동시에 연동할 데이터베이스와의 연결을 미리 설정해 두고, 필요할 때마다 미리 연결해 놓은 상태를 이용해 빠르게 데이터베이스와 연동하여 작업하는 방식을 사용한다. + +이렇게 미리 데이터베이스와 연결시킨 상태를 유지하는 기술을 커넥션 풀(Connection Pool, CP)이라고 한다. + +## 커넥션 풀 사이즈 설정 + +이론적으로 필요한 최소한의 커넥션 풀 사이즈를 알아보면 다음과 같다. + +> PoolSize = Tn × ( Cm -1 ) + 1

Tn : 전체 Thread 갯수
Cm : 하나의 Task에서 동시에 필요한 Connection 수 + +위와 같은 식으로 설정을 한다면 데드락을 피할 수는 있겠지만 여유 커넥션풀이 하나 뿐이라 성능상 좋지 못하다. + +따라서 커넥션풀의 여유를 주기위해 아래와 같은 식을 사용하는것을 권장한다. + +> PoolSize = Tn × ( Cm - 1 ) + ( Tn / 2 )

thread count : 16
simultaneous connection count : 2
pool size : 16 * ( 2 – 1 ) + (16 / 2) = 24 + +## Spring에서의 커넥션 풀 + +자바에서는 기본적으로 DataSource 인터페이스를 사용하여 커넥션풀을 관리한다. + +Spring에서는 사용자가 직접 커넥션을 관리할 필요없이 자동화된 기법들을 제공하는데 SpringBoot 2.0 이전에는 tomcat-jdbc를 사용하다, 2.0 이후 부터는 **HikariCP**를 기본옵션으로 채택하고있다. + +## Hikari CP + +![image](https://user-images.githubusercontent.com/81006587/230904793-ca2415c1-8dc6-425e-9fab-5e8975c7e591.png) + +[HikariCP 벤치마킹 페이지](https://github.com/brettwooldridge/HikariCP-benchmark)를 보면 다른 커넥션풀 관리 프레임워크보다 성능이 월등히 좋음을 알 수 있다. HikariCP가 빠른 성능을 보여주는 이유는 커넥션풀의 관리 방법에 있다. + +히카리는 Connection 객체를 한번 Wrappring한 `PoolEntry`로 Connection을 관리하며, 이를 관리하는 `ConcurrentBag`이라는 구조체를 사용하고 있다. + +`ConcurrentBag`은 `HikariPool.getConnection() -> ConcurrentBag.borrow()`라는 메서드를 통해 사용 가능한(idle) Connection을 리턴하도록 되어있다. + +이 과정에서 커넥션생성을 요청한 스레드의 정보를 저장해두고 다음에 접근시 저장된 정보를 이용해 빠르게 반환을 해준다. + +## Spring 설정 + +스프링에서는 yml 파일로 hikari CP의 설정 값을 조정해줄 수 있다. + +```yml +spring: + datasource: + url: jdbc:mysql://localhost:3306/world?serverTimeZone=UTC&CharacterEncoding=UTF-8 + username: root + password: your_password + hikari: + maximum-pool-size: 10 + connection-timeout: 5000 + connection-init-sql: SELECT 1 + validation-timeout: 2000 + minimum-idle: 10 + idle-timeout: 600000 + max-lifetime: 1800000 + +server: + port: 8000 +``` + +각 설정의 의미는 아래와 같다. + +options +- maximum-pool-size: 최대 pool size (defailt 10) +- connection-timeout: (말 그대로) +- connection-init-sql: SELECT 1 +- validation-timeout: 2000 +- minimum-idle: 연결 풀에서 HikariCP가 유지 관리하는 최소 유휴 연결 수 +- idle-timeout: 연결을위한 최대 유휴 시간 +- max-lifetime: 닫힌 후 pool 에있는 connection의 최대 수명(ms). +- auto-commit: auto commit 여부 (default true) \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/\354\230\265\355\213\260\353\247\210\354\235\264\354\240\200.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/\354\230\265\355\213\260\353\247\210\354\235\264\354\240\200.md" new file mode 100644 index 00000000..c54ca081 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/\354\230\265\355\213\260\353\247\210\354\235\264\354\240\200.md" @@ -0,0 +1,24 @@ +--- +title: '옵티마이저' +lastUpdated: '2024-03-02' +--- + +옵티마이저는 사용자가 요청한 SQL문에 대한 최적의 실행 방법을 결정하는 역할을 수행한다. 이떄, 옵티마이저가 도출한 실행 방법을 실행계획이라고 한다. + +사용자의 요구사항을 만족하는 결과를 추출할 수 있는 다양한 실행 방법들을 도출한 후, 그중에서 최적의 실행 방법을 결정하는 것이 옵티마이저의 역할이다. 비절차형 언어를 사용하는 경우 필요한 요소중 하나이다. + +옵티마이저가 실행 계획을 모색하는 방법에는 룰 기반과 비용 기반이 있다. 룰 기반은 정해진 규칙에 따라 SQL문을 도출하는 방식이고, 비용기반은 다양한 DBMS의 객체정보 및 통계정보를 활용하여 최적의 실행 계획을 도출하는 방법이다. 룰 기반에 비해선 비용기반이 상황에 맞게 효율적인 쿼리를 생성할 수 있기 때문에, 오라클 10 이후 버전부터는 공식적으로 비용 기반 옵티마이저만 사용한다. + +옵티마이저의 구성 요소로는 다음과 같은 것들이 있다. + +|이름|설명| +|-|-| +|Query Transformer|파싱된 SQL을 좀 더 일반적이고 표준적인 형태로 변환한다.| +|Estimator|오브젝트 및 시스템 통계정보를 이용해 쿼리 수행 각 단계의 선택도, 카디널리티, 비용을 계산하고, 궁극적으로는 실행계획 전체에 대한 총 비용을 계산해 낸다.| +|Plan Generator|하나의 쿼리를 수행하는 데 있어, 후보군이 될만한 실행계획들을 생성해낸다.| + +### 주의 + +옵티마이저의 성능은 점차 향상되고 있지만, 만능은 아니다. 칼럼의 통계 정보만 가지고는 조건절에서 사용된 조건을 만족하는 데이터의 양이 어느정도인지 알 수가 없기에 비용 계산 결과가 정확하지 않다. + +쿼리 튜닝을 할 때에는 쿼리 문의 실행계획을 꼭 보고, 옵티마이저가 비효율적으로 작동하고 있다면 오라클의 힌트 같은 부가적인 장치를 통해 올바르게 작동될 수 있도록 유도해야한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/\354\241\260\354\235\270\342\200\205\354\210\230\355\226\211\354\233\220\353\246\254.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/\354\241\260\354\235\270\342\200\205\354\210\230\355\226\211\354\233\220\353\246\254.md" new file mode 100644 index 00000000..32466b26 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\265\234\354\240\201\355\231\224/\354\241\260\354\235\270\342\200\205\354\210\230\355\226\211\354\233\220\353\246\254.md" @@ -0,0 +1,65 @@ +--- +title: '조인 수행원리' +lastUpdated: '2024-03-02' +--- + +조인이란 2개 이상의 테이블을 하나의 집합으로 만드는 연산이다. `FROM` 절에 2개 이상의 테이블 혹은 집합이 존재할 경우 조인이 수행된다. + +조인은 3개 이상의 테이블을 조인한다고 하더라도 **특정 시점에는 2개의 테이블 단위로 조인**된다. A, B, C 테이블을 조인한다고 하면 A, B를 조인한 후 해당 결과로 나온 집합을 C와 조인하는 방식이다. 각각의 조인 단계에서는 서로 다른 조인 기법이 사용될 수 있다. + +## 조인 기법 + +조인 기법의 종류에는 NL 조인, 소트 머지 조인, 해시 조인이 있다. + +### NL 조인 (Nested Loops Join) + +NL 조인은 첫 번째 집합의 대상 건수만큼 반복하면서 두 번째 집합을 조회하여 매칭되는 행을 리턴하는 조인 기법입니다. 한 레코드(행)씩 순차적으로 진행하기 때문에 처리 범위가 넓은 경우에는 비효율적이다. + +```sql +begin + for outer in (select deptno, empno, rpad(ename, 10) ename from emp) + loop -- outer 루프 + for inner in (select dname from dept where deptno = outer.deptno) + loop -- inner 루프 + dbms_output.put_line(outer.empno||' : '||outer.ename||' : '||inner.dname); + end loop; + end loop; +end; +``` + +위는 NL조인 과정을 표현한 PL/SQL문이다. 2중 for문을 사용한 모습이다. + +NL 조인은 특성상 인덱스 유무, 인덱스 컬럼 구성에 따라 성능이 좌우된다. 하지만 Random 액세스 위주의 조인방식이기 떄문에 인덱스 구성이 완벽하더라도 대량의 데이터 조인시에는 비효율적이다. + +NL 조인은 **부분 범위 처리가 가능한 OLTP(Online Transaction Processing) 환경**에 적합하다. 조인을 한 레코드씩 순차적으로 진행하기 때문에 데이터의 양이 많지않은 OLTP 환경에선 매우 극적인 응답속도를 낼 수 있다. + +### 소트 머지 조인 (SortMerge Join) + +소트 머지 조인은 2개의 테이블(집합)을 조인 컬럼 기준으로 모두 정렬한 후 두 집합을 병합하면서 결과 집합을 도출한다. + +소트 머지 조인은 인덱스 유무에 영향을 받지 않는다. 어차피 2개의 집합을 몯두 정렬하기 때문이다. 단, 필요한 인덱스가 이미 있는 경우에는 해당 인덱스를 이용해서 정렬 작업을 생략하기도 한다. + +스캔 위주의 액세스 방식이기 떄문에 양쪽 소스 집합에서 정렬 대상 레코드를 찾는 작업은 인덱스틑 이용하여 랜덤 액세스 방식으로 처리할 수 있다. 랜덤 액세스가 많아지는 경우에는 소트 머지 조인의 이점이 사라진다. + +소트머지 조인은 Outer루프와 Inner루프가 Sort Area에 미리 정렬해둔 데이터를 이용할 뿐, 실제 조인과정은 NL조인과 동일하다. 하지만, Sort Area가 PGA영역에 할당되어 래치획득과정이 없기 때문에, SGA를 경유하는 것보다 훨씬 빠르다. + +소트 머지 조인은 다음과 같은 경우 사용할 수 있다. + +- First 테이블에 소트연산을 대체할 인덱스가 있을 때 +- 조인할 First 집합이 **이미 정렬**되어 있을 때 +- 조인 조건식이 **등치(=)조건이 아닐 때** + +### 해시 조인 (Hash Join) + +해시 조인은 첫 번째 테이블(집합)을 기준으로 해시 테이블을 생성하고 두 번째 집합을 조회하면서 해시 테이블과의 해시 FUNCTION 비교를 통해 매칭되는 건들을 결과집합에 포함한다. + +해시 조인은 첫 번째 집합의 용량이 PGA에 할당되는 HACK AREA 메모리 공간에 전부 다 담길 수 있을 정도의 크기라면 성능상 매우 유리해진다. + +해시 조인은 소트 머지 조인처럼 정렬 부하가 있지도 않고, NL 조인처럼 랜덤 엑세스에 대한 부하도 존재하지 않는다. 하지만 해시테이블은 단 하나의 쿼리를 위해 생성하고 조인이 끝나면 바로 소멸하는 자료구조이므로, 수행빈도가 높은 쿼리에서 사용하면 CPU와 메모리 사용률을 크게 증가시키고, 래치 경합이 발생하여 시스템 동시성을 떨어뜨린다. + +그러므로 해시조인은 **수행빈도가 낮고, 쿼리의 수행시간이 오래 걸리는 대용량 테이블을 조인할 때** 주로 사용해야 한다. 배치 프로그램, DW, OLAP설 쿼리 등에서 유리한 조인 기법이다. + +### 정리 + +- 대량의 테이블에서 소량의 범위만 부분 범위처리로 가져올 때, 혹은 대량의 테이블에서 극소량의 데이터를 가져올 때는 NL 조인이 유리하고, 대량의 배치 프로그램 처리를 할 때는 해시 조인이 유리하다. +- 소트 머지 조인은 NL조인보다 성능이 좋긴 하지만, 해시조인보다는 부족하기 때문에 잘 쓰이지 않는다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DCL.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DCL.md" new file mode 100644 index 00000000..4ac1b4b6 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DCL.md" @@ -0,0 +1,12 @@ +--- +title: 'DCL' +lastUpdated: '2024-03-02' +--- +

+DCL은 데이터 제어어를 뜻한다. 데이터베이스에 접근하고 객체들을 사용하도록 권한을 부여하고 회수하는 데 사용한다. +

+ +|구분|설명| +|-|-| +|GRANT|데이터베이스에 접근하고 객체를 사용하는 권한을 줄 때 사용한다.| +|REVOKE|GRANT로 준 권한을 회수할 때 사용한다.| diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DDL.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DDL.md" new file mode 100644 index 00000000..7023aa10 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DDL.md" @@ -0,0 +1,87 @@ +--- +title: 'DDL' +lastUpdated: '2024-03-02' +--- +

+DDL은 데이터 정의어를 뜻한다. DB를 구성하는 물리적 객체(사용자, 테이블, 인덱스, 뷰, 트리거, 프로시저, 사용자 정의 함수 등)을 정의/변경/제거하는 데 사용한다. +

+

+테이블의 무결성 보장을 위해 제약조건을 설정할 수도 있다. +

+ +|구분|설명| +|-|-| +|CREATE|객체를 생성할때 사용한다.| +|ALTER|객체를 수정할때 사용한다.| +|DROP|객체를 삭제할때 사용한다.| + +## DDL 쿼리 예제 +--- +### 테이블 생성 +CREATE문을 통해 테이블을 생성하는 쿼리이다. +```sql +CREATE TABLE 테이블명1( + 컬럼명1 DATATYPE, + 컬럼명2 DATATYPE, + CONSTRAINT 제약조건명1 PRIMARY KEY(컬럼명1) +); +``` +SELECT문으로 테이블을 생성하는 방법도 있다. (테이블 구조와 일부 데이터는 똑같이 복사되지만 제약조건은 복사되지 않는다.) +```sql +CREATE TABLE 테이블명2 +AS SELECT * FROM 테이블명1; +``` +### 외래키 생성 +생성된 테이블에 외래키(FK)를 생성하는 쿼리이다. +```sql +ALTER TABLE 테이블명1 +ADD CONSTRAINT 제약조건명2 +FOREIGN KEY (컬럼명1) +REFERENCES 테이블명2 (컬럼명3); +``` +--- +### 칼럼 추가 +```sql +ALTER TABLE 테이블명1 ADD (컬럼명 VARCHAR(5) NULL); +``` +### 칼럼 삭제 +```sql +ALTER TABLE 테이블명1 DROP 컬럼명; +``` +### 데이터형 및 제약조건 변경 +``` sql +ALTER TABLE 테이블명1 MODIFY(컬럼명 NUMBER(1) + DEFALT 0 NOT NULL + NOVALIDATE); +``` +### 제약조건 삭제 +```sql +ALTER TABLE 테이블명1 DROP CONSTRAINT 제약조건명1; +``` +### 칼럼명 변경 +```sql +ALTER TABLE 테이블명1 +RENAME COLUMN 컬럼명 TO 새컬럼명; +``` +--- +### 테이블명 변경 +```sql +ALTER TABLE 테이블명1 +RENAME TO 새테이블명2; +``` +### 테이블 내 데이터 제거 +```sql +TRUNCATE TABLE 테이블명1; +``` +### 테이블 제거 +```sql +DROP TABLE 테이블명1; +``` +RESTRICT 명령어를 붙이면 삭제할 테이블이 참조 중인 경우에 테이블을 삭제하지 않는다. +```sql +DROP TABLE 새테이블명1 RESTRICT; +``` +CASCADE 명령어를 붙이면 삭제할 테이블을 참조 중인 모든 요소를 전부 삭제한다. +```sql +DROP TABLE 새테이블명1 CASCADE; +``` diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DML.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DML.md" new file mode 100644 index 00000000..397903b7 --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/DML.md" @@ -0,0 +1,60 @@ +--- +title: 'DML' +lastUpdated: '2024-03-02' +--- +

+DML은 데이터 조작어를 뜻한다. 테이블의 데이터를 입력/수정/삭제/조회하는 데 사용한다. +

+

+호스트 프로그램속에 삽입되어 사용되는 DML 명령어는 데이터 부속어(DSL, Data SubLanguage)라고 부르기도 한다. +

+ +|구분|설명| +|-|-| +|INSERT|테이블에 데이터를 신규로 입력할 때 사용한다.| +|UPDATE|테이블 내 행의 칼럼값을 수정할 때 사용한다.| +|DELETE|테이블 내의 행을 삭제할 때 사용한다.| +|SELECT|테이블에서 데이터를 조회할 때 사용한다.| + +## DML 쿼리 예제 +--- +### 신규 데이터 입력 +```sql +INSERT INTO 테이블명1 +(컬럼명1, 컬럼명2) +VALUES +('데이터1','데이터2'); +``` +### 데이터 조회 +```sql +SELECT 테이블명1.컬럼명1, + 테이블명1.컬럼명2 +FROM 테이블명1 +WHERE 컬럼명1 = '데이터1'; +``` +DISTINCT라는 조건을 지정해서 중복 행 없이 출력할 수도 있다. +```sql +SELECT DIStiNCT 테이블명1.컬럼명1, + 테이블명1.컬럼명2 +FROM 테이블명1 +WHERE 컬럼명1 = '데이터1'; +``` +앨리어스(Alias)를 지정해 칼럼에 접근하는 테이블명, 출력하는 칼럼명을 임시로 바꿀 수도 있다. +```sql +SELECT A.컬럼명1 AS COLUMNNAME1 + , A.컬럼명2 AS COLUMNNAME2 +FROM 테이블명1 A +WHERE 컬럼명1 = '데이터1'; +``` +### 데이터 수정 +```sql +UPDATE 테이블명1 +SET 컬럼명1 = '새데이터1' +WHERE 컬럼명1 = '데이터1'; +``` +### 데이터 삭제 +```sql +DELETE +FROM 테이블명1 +WHERE 컬럼명1 = '새데이터1'; +``` diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/Procedural\357\274\217Nonprocedural\342\200\202DML.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/Procedural\357\274\217Nonprocedural\342\200\202DML.md" new file mode 100644 index 00000000..69a9a0ff --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/Procedural\357\274\217Nonprocedural\342\200\202DML.md" @@ -0,0 +1,24 @@ +--- +title: 'Procedural/Nonprocedural DML' +lastUpdated: '2024-03-02' +--- + +DML은 사용자가 DB에서 원하는 데이터를 처리할 수 있도록 명세하기 위한 도구이다. 간단하게 말하면 테이블의 데이터를 입력/수정/삭제/조회하는 데 쓰이는 쿼리 명령어라고 할 수 있다. + +DML은 데이터 처리를 명세하는 방법에 따라 두가지 유형으로 나눌 수 있다. + +## 절차적 데이터 조작어(Procedural DML) + +절차적 데이터 조작어는 사용자가 무슨 데이터(What)를 원하며, 그것을 어떻게(How) 접근하여 처리할지 명세해야하는 초급 데이터 언어이다. + +이런 데이터 조작어는 데이터 베이스로부터 한 번에 하나의 레코드(One-record-at-a-time)를 검색해서 호스트 언어(해당 응용 프로그램을 작성하는데 사용된 범용 프로그래밍 언어)와 함께 처리하는 특성을 가지고 있다. + +## 비절차적 데이터 조작어(Nonprocedural DML) + +비정차적 데이터 조작어는 사용자가 무슨(What) 데이터를 원하는지만 명세하고, 그것을 어떻게(How) 접근하여 처리할 것인가에 대해서는 명세할 칠요 없이 DBMS에 위임하는 고급 데이터 언어이다. + +어떤 데이터를 DBMS에게 요청하면 (ex.`이름이 홍길동인 사람`) 해당 데이터를 어떤 방식으로 찾을지 사용자가 지정하지 않아도 알아서 탐색하여 쿼리에 맞는 작업을 자동으로 수행한다. + +사용자가 원하는 데이터만 선언하는 것이기 때문에 선언적 언어(declarative language)라고도 한다. + +Oracle이나 MySQL등 현대의 DBMS는 비절차적 데이터 조작어를 사용한다. diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/TCL.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/TCL.md" new file mode 100644 index 00000000..7bb9081f --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/SQL/\354\277\274\353\246\254\354\242\205\353\245\230/TCL.md" @@ -0,0 +1,32 @@ +--- +title: 'TCL' +lastUpdated: '2024-03-02' +--- +

+TCL은 트랜잭션 제어어를 뜻한다. 트랜잭션이란 DB에서 처리되는 논리적인 연산 단위를 뜻하는데, TCL은 데이터의 변경 후 커밋, 롤백으로 트랜잭션을 완료, 취소하는 작업을 수행할 때 사용한다. +

+ +### 주의점 +단, 아래의 경우에는 TCL 명령어와 상관없이 트랜잭션 적용 처리가 일어난다. +- DDL문이 실행되었을 경우(이전에 실행됐던 DML문도 함께 커밋된다) +- DB에 대한 접속을 정상적으로 종료한 경우 +위와 같은 상황에서는 트랜잭션이 비정상적으로 처리될 위험이 있으므로 데이터베이스 사용 시 꼭 주의해야한다. + +## TCL 쿼리 예제 +--- +### 커밋 +```sql +COMMIT; +``` +### 롤백 +```sql +ROLLBACK; +``` +### 세이브포인트 +```sql +SAVEPOINT SVPT; +``` +### 세이브포인트까지 롤백 +```sql +ROLLBACK TO SVPT; +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Two\342\200\205Phase\342\200\205commit.md" "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Two\342\200\205Phase\342\200\205commit.md" new file mode 100644 index 00000000..b6661dee --- /dev/null +++ "b/src/content/docs/TIL/\353\215\260\354\235\264\355\204\260\353\262\240\354\235\264\354\212\244\342\200\205DataBase/Two\342\200\205Phase\342\200\205commit.md" @@ -0,0 +1,57 @@ +--- +title: 'Two Phase commit' +lastUpdated: '2024-03-02' +--- + +image + +> Two-phase commit is an algorithm for achieving atomic transaction commit across multiple nodes—i.e., to ensure that either all nodes commit or all nodes abort. + +Two phase commit은 **분산되어 있는 DB들 간에 atomic한 transaction commit 처리**를 위해 사용되는 알고리즘이다. + +단일 DB에 transaction 처리의 경우에는 DBMS에 구현되어 있는 방식으로 처리할 수 있지만, 복수의 DB에 같은 transaction 처리를 실행 할 경우에, 어떻게 atomic한 처리를 할 수 있을까? + +간단하게 얘기하면, 위의 그림대로 transaction을 각 db에 실행되도록 한다. 해당 transaction이 read, write, lock, unlock등의 operation을 수행할 것이고, 최종적으로 이를 commit 할지 말지를 Coordinator가 결정하도록 한다. + +Coordinator가 각 DB에 해당 transaction이 문제 없이 commit이 가능한 상태인지 확인하고(각 DB는 해당 transaction을 commit 만 남겨놓고 수행했으므로), 모든 DB로 부터 성공응답을 받게 되면 Coordinatior 가 commit 처리하도록 DB에 요청하고, 어느 하나의 DB라도 commit 할 수 없는 상태이면, 모든 db에서 rollback되도록 한다. + +즉, 복수의 각 db에 해당 transaction을 수행하고, 모든 db에서 성공적으로 실행되었는지 체크하는 것이 phase1, 이를 최종적으로 적용되게 하는 phase2라서 Two Phase Commit이라 할 수 있다. + +### 동작 과정 + +image + +1. client가 각 DB에 transaction을 수행할 때, 해당 transaction에 고유한 unique ID를 coordinator로부터 발급받은 뒤 수행한다. + +2. client는 coordinator에게 해당 transaction이 commit되도록 요청한다. + +3. coordinator는 해당 transaction이 commit 가능한 상태인지 각 db에 체크한다. + +4. 각 db는 해당 transaction을 disk에 update하고 lock을 hold하고 있는 상태에서 integraity constraint 체크등을 하고 해당 prepare request에 응답한다. + +5. 모든 db가 ok시 commit 되도록 하며, 하나의 db라도 commit이 불가능하거나 응답이 없으면 전부 abort되도록 한다. + +### 사용하는 경우 + +- **two(or more) databases from diffrent vendors** + - 특정 처리의 결과를 두개의 이상의 DB에 저장하며 사용하는 경우 2PC를 적용할 수 있다. (2PC 대신 uow 디자인 패턴을 이용하는 경우도 있다.) + +- **Exactly-once message processing** + - 메세지큐와 해당 메세지를 처리하는 분산된 node에서는 메세지가 제대로 처리되었다는 commit이 필요하다. 메세지 큐에서는 해당 메세지를 가져갔다고 commit이 되었는데 메세지를 처리하는 쪽에서는 받아서 처리되었다는 commit이 되지 않을 경우에는 메세지가 분실된다. + - 따라서 둘 다 처리되었다고 commit 하거나, 둘 다 처리되지 않게 처리하여 메세지가 다시 처리될 수 있도록 해야한다. + - 메세지 처리결과가 외부 회사에서 진행이 되는 경우, 현실적으로 구현이 불가능할 수 있다. 이럴 경우 Exactly-once가 아닌 at-least-most 방식으로 처리를 하게 되며 이 경우 메세지 중복현상이 발생한다. + +### 주의해야할 부분 + +transaction의 commit, abort 여부를 결정하는 coordinator에 장애가 발생하거나 failure되면 어떻게 될까? + +해당 transaction에 대한 commit/abort 여부를 disk에 안전하게 write 해두더라도, 각 db에 commit/abort 응답을 주지 않는다면 각 db입장에서는 lock을 걸어둔 상태에서 coordinator가 정상동작할 때 까지 대기하여하는 상황이 발생한다. + +이런 상황을 피하기 위해 **consensus algorithm, total order broadcast protocol** 이 사용된다. 각 db들이 coordinator에게 commit 가능 여부를 보내는 것이 아니라 각 db들 간에 이 정보를 주고 받는 것이다. + +2PC은 단일 db에 비해서 (Mysql)로 비교하면 10배가량의 performance 차이가 난다고 한다. 해당 commit의 가능 여부를 network io를 이용하여 판단하기에 io로 인한 퍼포먼스 저하 및 crash recovery를 위한 추가적인 `fsync()`도 퍼포먼스 저하의 원인이 된다. + +--- +참고 +- https://en.wikipedia.org/wiki/Two-phase_commit_protocol +- https://martinfowler.com/articles/patterns-of-distributed-systems/two-phase-commit.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/@Cacheable.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/@Cacheable.md" new file mode 100644 index 00000000..11e0b4bf --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/@Cacheable.md" @@ -0,0 +1,83 @@ +--- +title: '@Cacheable' +lastUpdated: '2024-03-02' +--- +This method-level annotation lets Spring Boot know that the return value of the annotated method can be cached. Each time a method marked with this @Cacheable is called, the caching behavior will be applied. In particular, Spring Boot will check whether the method has been already invoked for the given arguments. This involves looking for a key, which is generated using the method parameters by default. If no value is found in the cache related to the method for the computed key, the target method will be executed normally. Otherwise, the cached value will be returned immediately. + +`@Cacheable` comes with many parameters, but the simplest way to use it is to annotate a method with the annotation and parameterize it with the name of the cache where the results are going to be stored. + +```java +@Cacheable("authors") +public List getAuthors(List ids) { ... } +``` + +You can also specify how the key that uniquely identifies each entry in the cache should be generated by harnessing the key attribute. + +```java +@Cacheable(value="book", key="#isbn") +public Book findBookByISBN(String isbn) { ... } + +@Cacheable(value="books", key="#author.id") +public Books findBooksByAuthor(Author author) { ... } +``` + +Lastly, it is also possible to enable conditional caching as in the following example: + +```java +// caching only authors whose full name is less than 15 carachters +@Cacheable(value="authors", condition="#fullName.length < 15") +public Authors findAuthorsByFullName(String fullName) { ... } +``` + +## @CachePut + +This method-level annotation should be used when you want to update (put) the cache without avoiding the method from being executed. This means that the method will always be executed — according to the `@CachePut` options — and its result will be stored in the cache. + +The main difference between `@Cacheable` and `@CachePut` is that the first might avoid executing the method, while the second will run the method and put its results in the cache, even if there is already an existing key associated with the given parameters. Since they have different behaviors, annotating the same method with both `@CachePut` and `@Cacheable` should be avoided. + +## @CacheEvict + +This method-level annotation allows you to remove (evict) data previously stored in the cache. By annotating a method with `@CacheEvict` you can specify the removal of one or all values so that fresh values can be loaded into the cache again. If you want to remove a specific value, you should pass the cache key as an argument to the annotation, as in the following example: + +```java +@CacheEvict(value="authors", key="#authorId") +public void evictSingleAuthor(Int authorId) { ... } +``` + +While if you want to clear an entire cache you can the parameter allEntries in conjunction with the name of cache to be cleared: + +```java +@CacheEvict(value="authors", allEntries=true) +public String evictAllAuthorsCached() { ... } +``` + +`@CacheEvict` is extremely important because size is the main problem of caches. + +A possible solution to this problem is to compress data before caching, as explained here. On the other hand, the best approach should be to avoid keeping data that you are not using too often in the caches. In fact, since caches can become large very quickly, you should update stale data with @CachePut and remove unused data with `@CacheEvict`.` + +In particular, having a method to clean all the caches of your Spring Boot application easily can become essential. It can be called in type of method like this : + +- when the data changes +- When you want to periodically empty the cache + +## @CacheConfig +This class-level annotation allows you to specify some of the cache configurations in one place, so you do not have to repeat them multiple times: + +```java +@CacheConfig(cacheNames={"authors"}) +public class AuthorDAO { + @Cacheable + publicList findAuthorsByFullName(String fullName) { ... } + + @Cacheable + public List findAuthorsByBook(Book book) { ... } + + // ... +} +``` + +--- +reference +- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html +- https://auth0.com/blog/spring-boot-caching-101/ +- https://www.baeldung.com/spring-cache-tutorial \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/AdviceAnnotation.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/AdviceAnnotation.md" new file mode 100644 index 00000000..9ef2793e --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/AdviceAnnotation.md" @@ -0,0 +1,30 @@ +--- +title: 'AdviceAnnotation' +lastUpdated: '2024-03-02' +--- + +AOP란 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 한다. AOP를 사용하면 기존 코드를 수정하지 않고 기존 코드에 동작을 추가할 수 있다. + +AOP를 지정하기 위해선 AOP 동작을 메서드에 작성해야한다. 아래 어노테이션들을 이용해서 메서드의 실행 시점을 지정할 수 있다. + +## Advice Annotation + +|Annotation|설명| +|-|-| +|`@Before`|핵심관심사 시작 전에 실행| +|`@After`|메서드가 끝난 후 실행 (성공여부 상관 X)| +|`@AfterThrowing`|메서드 실행이 예외를 throw하여 종료될 때 실행| +|`@AfterReturning`|메서드 실행이 정상적으로 반환될 때 실행| +|`@Around`|핵심관심사의 메서드 실행 전 후로 실행 (성공여부 상관 X)| + +## pointCut 지정 + +```java +@Around("execution(* com.xyz.service.*.*(..))") +``` + +Advice Annotation에는 value라는 이름의 String값을 입력해줘야한다. + +Pointcut이 들어가는 자리이다. 저 자리에 표현식을 입력해줘야, 어떤 클래스의 어떤 메서드에 AOP를 적용할 것인지가 결정된다. + +Pointcut은 내용이 생각보다 많고, 헷갈리는 부분이 있으므로 다른 문서로 나누었다. 자세한 내용을 알고싶다면 해당 문서를 읽어보면 좋다. diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Pointcut.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Pointcut.md" new file mode 100644 index 00000000..c6301ca3 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Pointcut.md" @@ -0,0 +1,174 @@ +--- +title: 'Pointcut' +lastUpdated: '2024-03-02' +--- + +Pointcut은 Advice가 부가 기능을 제공할 대상을 특정하는 정규표현식이다. 즉, AOP를 적용할 대상을 정한다. + +포인트컷에는 다양한 명시자를 이용할 수 있는데, 각 명시자마다 어떤 범위에서 Target을 정할지 어떤 것을 기준으로 할지 등의 의미가 조금씩 다르다. + +아래 영어로 된 PointCut 명시자 설명이 있으니, 참고하면 좋을 것 같다. + +|명시자|설명| +|-|-| +|execution| for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP| +|within|limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)| +|this|limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type| +|target|limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type| +|args|limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types| +|@target|limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type| +|@args|limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)| +|@within|limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)| +|@annotation|limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation| + +# `execution` + +```js +execution([접근지정자] 리턴타입 [클래스경로].메서드이름(파라미터) +``` + +**접근지정자* : public, private 등 접근지정자를 명시한다. (생략 가능)
+**리턴타입** : 리턴 타입을 명시한다.
+**클래스경로 및 메서드이름** : 클래스경로와 메서드 이름을 명시한다. (클래스 경로은 풀 패키지 경로로 명시해야한다. 생략 가능)
+**파라미터** : 메서드의 파라미터를 명시한다. + +`execution`은 접근지정자, 리턴타입, 클래스경로 등의 여러 속성을 정의하여 AOP를 적용할 대상을 구체적으로 지정한다. 원하는대로 표현식을 작성하면 원하는 클래스를 상세하게 특정할 수 있기 +때문에 범용적으로 사용할 수 있다. + +### Example + +- 모든 public 메서드: +```js +execution(public * *(..)) +``` + +- 'set'으로 시작하는 모든 메서드: +```js +execution(* set*(..)) +``` + +- `AccountService` 인터페이스에 의해 정의된 모든 메서드: +```js +execution(* com.xyz.service.AccountService.*(..)) +``` + +- service 패키지에 정의된 모든 메서드: +```js +execution(* com.xyz.service.*.*(..)) +``` + +- service 패키지 또는 그 하위 패키지에 정의된 모든 메서드중, 두개의 인자를 가지고 있고 두번째 인자가 String 타입인 메서드: +```js +execution(* com.xyz.service..*.*(*,String)) +``` + +# `within` + +```js +within(패키지경로) +``` + +within은 특정 패키지 안에 있는 클래스에 AOP를 적용하도록 한다. + +### Example + +- service 패키지에 있는 모든 joinPoint(메서드): +```js + +``` + +- service 패키지 또는 그 하위 패키지에 있는 joinPoint(메서드): +```js +within(com.xyz.service..*) +``` + +# `this` + +```js +this(클래스경로) +``` + +`this`는 경로로 명시한 클래스와, 그 하위 클래스에 모두 AOP를 적용하도록 한다. `this instanceof AType`에 해당하는 경우를 뜻한다. + +`this`는 바인딩 형식에서 더 일반적으로 사용된다. + +### Example + +- 프록시가 `AccountService` 인터페이스를 구현하는 joinPoint(메서드): +```js +this(com.xyz.service.AccountService) +``` + +# `target` + +```js +target(클래스경로) +``` + +`target`은 딱 경로로 명시한 그 클래스인 객체에 대해 AOP를 적용하도록 한다. + +### Example + +- `AccountService` 클래스 안에 있는 joinPoint(메서드): +```js +target(com.xyz.service.AccountService) +``` + +# `args` + +```js +args(클래스경로) +``` + +특정한 타입의 파라미터를 받는 joinPoint(메서드)에 대해 AOP를 적용한다. 콤마(`,`)를 찍고 여러 파라미터를 표현하는 것도 가능하다. + +하지만 `args(클래스)`는 `execution(* *(클래스))`와 다르게 동작한다. args는 런타임에 전달된 인수가 해당 클래스인 경우 실행되고, execution는 해당 클래스를 인수로 받는다고 선언되어있는 메서드가 호출되었을 때 실행된다. + +### Example + +- `Serializable` 한개를 파라미터로 받는 메서드: +```js +args(java.io.Serializable) +``` + +- `Serializable`와 `Int`두개를 파라미터로 받는 메서드: +```js +args(java.io.Serializable, Int) +``` + +# `@target`, `@within`, `@annotation`, `@args` + +위 네개의 명시자는 특정 어노테이션을 가지고 있는 클래스, 혹은 메서드에 AOP를 적용하도록 하는 명시자이다. 각각 상세한 의미를 가지고있지만 서로 비슷하여 헷갈릴 수 있으니 비교하며 알아보자. + +|명시자|설명| +|-|-| +|@target|대상 객체에 해당 어노테이션이 직접 붙어있는 객체의 JoinPoint| +|@within|간접적으로라도 해당 어노테이션을 가지고있는 객체의 JoinPoint| +|@annotation|해당 어노테이션이 붙어있는 메서드 | +|@args|인수의 런타입 유형이 특정 어노테이션을 가지고있는 메서드| + +크게 더 설명할 부분이 없으므로 예시는 생략한다. + +# `bean` + +```js +bean(빈 이름) +``` + +bean은 스프링에 등록된 빈의 이름으로 AOP 적용 대상을 명시한다. + +### Example + +- 이름이 `tradeService`인 빈: +```js +bean(tradeService) +``` + +- 이름이 `Service`로 끝나는 빈: +```js +bean(*Service) +``` + +--- + +참고: [https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html#aop-pointcuts](https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html#aop-pointcuts) diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/ProxyFactoryBean.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/ProxyFactoryBean.md" new file mode 100644 index 00000000..36470712 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/ProxyFactoryBean.md" @@ -0,0 +1,66 @@ +--- +title: 'ProxyFactoryBean' +lastUpdated: '2024-03-02' +--- + +스프링은 서비스 추상화를 프록시 기술에도 동일하게 적용한다. 따라서 스프림은 일관된 방법으로 프록시를 만들 수 있게 도와주는 추상 레이어를 제공한다. **ProxyFactoryBean**은 프록시를 생성해서 빈 오브젝트로 등록하게 해주는 팩토리 빈이다. ProxyFactoryBean은 순수하게 프록시를 생성하는 작업만을 담당하고 프록시를 통해 제공해줄 부가기능은 별도의 빈에 둘 수 있다. + +ProxyFactoryBean이 생성하는 프록시에서 사용할 부가기능은 **MethodInterceptor** 인터페이스를 구현해서 만든다. MethodInterceptor는 InvocationHanler와 비슷하지만 한가지 다른점이 있다. + +**InvocationHandler의 `invoke()` 메소드는 오브젝트에 대한 정보를 제공하지 않는다**.
따라서 타깃은 InvocationHandler를 구현한 클래스가 직접 알고 있어야 한다. + +반면에 **MethodInterceptor의 `invoke()` 메소드는 ProxyFactoryBean으로부터 타깃 오브젝트에 대한 정보까지도 함께 제공**받는다. + +그 차이 덕분에 MethodInterceptor는 `타깃 오브젝트에 상관없이 독립적`으로 만들어질 수 있다. 따라서 MethodInterceptor 오브젝트는 타깃이 다른 여러 프록시에서 함께 사용할 수 있고 싱글톤 빈으로도 등록 가능하다. + +```java +@FunctionalInterface +public interface MethodInterceptor extends Interceptor { + @Nullable + Object invoke(@Nonnull MethodInvocation invocation) throws Throwable; +} +``` + +```java +public interface InvocationHandler extends Callback { + Object invoke(Object var1, Method var2, Object[] var3) throws Throwable; +} +``` + +--- + +## 어드바이스: 타깃이 필요 없는 순수한 부가기능 + +- InvocationHandler를 구현했을 때와 달리 MethodInterceptor로는 메소드 정보와 함께 타깃 오브젝트가 담긴 `MethodInvocation` 오브젝트가 전달된다. MethodInvocation은 타깃 오브젝트의 **메소드를 실행할 수 있는 기능**이 있기 때문에 MethodInterceptor는 부가기능을 제공하는 데만 집중할 수 있다. + +- MethodInvocation은 일종의 콜백 오브젝트로 `proceed()` 메소드를 실행하면 타겟 오브젝트의 메소드를 내부적으로 실행해주는 기능이 있다. 그렇다면 MethodInvocation 구현 클래스는 `일종의 공유 가능한 템플릿`처럼 동작하는 것이다. 바로 이 점이 JDK의 다이내믹 프록시를 직접 사용하는 코드와 스프링이 제공하는 프록시 추상화 기능인 ProxyFactoryBean을 사용하는 코드의 가장 큰 차이점이자 ProxyFactoryBean의 장점이다. + +- ProxyFactoryBean은 작은 단위의 템플릿/콜백 구조를 응용해서 적용했기 때문에 템플릿 역할을 하는 **MethodInterceptor 구현체를 싱글톤으로 두고 공유**할 수 있다. 마치 SQL 파라미터 정보에 종속되지 않는 JdbcTemplate이기 때문에 수많은 DAO 메소드가 하나의 JdbcTemplate 오브젝트를 공유할 수 있는 것과 마찬가지다. + +- 또한 ProxyFactoryBean에는 **여러 개의 부가기능을 제공해주는 프록시**를 만들 수 있다. 즉 여러 개의 MethodInterceptor 구현체를 ProxyFactoryBean에 추가해줄 수 있다. 따라서 **새로운 기능을 추가하더라도 새 프록시나 팩토리 빈을 추가해줄 필요가 없다**. + +--- + +## 포인트컷 : 부가기능 적용 대상 메소드 선정 방법 + +- MethodInterceptor 오브젝트는 여러 프록시가 공유해서 사용할 수 있다. 그러기 위해서 MethodInterceptor 오브젝트는 타깃 정보를 갖고 있지 않도록 만들었고, 그 덕분에 MethodInterceptor를 스프링의 싱글톤 빈으로 등록할 수 있었다. + +- 하지만, 적용 대상 정보를 갖고 있지 않기 때문에 그 Advice를 어느 곳에다 적용할지를 알 수 없다. + +- 스프링의 ProxyFactoryBean 방식은 두 가지 확장 기능인 부가기능(Advice)과 메소드 선정 로직(Pointcut)을 아래와 같은 유연한 구조로 설계하여, 기존의 장점을 그대로 가져갈 수 있도록 하였다. + + ![image](https://user-images.githubusercontent.com/81006587/201669169-0c2d51c2-3748-4494-be57-cb3231e86793.png) + +- Advice와 Pointcut은 모두 Proxy에 DI로 주입되어 사용된다. 두 가지 모두 여러 프록시에서 공유가 가능하도록 만들어지기 때문에 싱글톤 빈으로 등록 가능하다. + + 1. Proxy는 클라이언트로부터 요청을 받으면 먼저 Pointcut에게 부가기능을 부여할 메소드인지 확인한다. + + 2. Pointcut으로부터 부가기능을 적용할 대상 메소드인지 확인받으면, MethodInterceptor 타입의 Advice를 호출한다. + +- 여기서 중요한 점은 MethodInterceptor가 타깃을 직접 호출하지 않는다는 것이다. 자신은 여러 타깃에 공유되어야하고 타깃 정보라는 상태를 가질 수 없기 때문에 일종의 템플릿 구조로 설계되어 있다. 어드바이스가 부가기능을 부여하는 중에 `타깃 메소드의 호출`이 필요하면 **프록시로부터 전달받은 `MethodInvocation` 타입 콜백 오브젝트의 `proceed()` 메소드를 호출**해주기만 하면 된다. + +- 실제 위임 대상인 타깃 오브젝트의 레퍼런스를 갖고 있고, 이를 이용해 타깃 메소드를 직접 호출하는 것은 프록시가 메소드 호출에 따라 만드는 MethodInvocation 콜백의 역할이다. **재사용 가능한 기능**을 만들어두고 **바뀌는 부분(콜백 오브젝트 - 메소드 호출정보)만 외부에서 주입**해서 이를 작업 흐름(부가기능) 중에 사용하도록 하는 전형적인 템플릿/콜백 구조이다. + +--- + +출처: 토비의 스프링 3.1 diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Spring\342\200\205AOP.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Spring\342\200\205AOP.md" new file mode 100644 index 00000000..8762a820 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Spring\342\200\205AOP.md" @@ -0,0 +1,90 @@ +--- +title: 'Spring AOP' +lastUpdated: '2024-03-02' +--- + +AOP란 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 한다. 여기서 Aspect(관점)이란 흩어진 관심사들을 하나로 모듈화 한 것을 의미한다. + +객체 지항 프로그래밍(OOP)에서는 주요 관심사에 따라 클래스를 분할한다. 이 클래스들은 보통 SRP(Single Responsibility Principle)에 따라 하나의 책임만을 갖게 설계된다. 하지만 클래스를 설계하다보면 로깅, 보안, 트랜잭션 등 여러 클래스에서 공통적으로 사용하는 부가 기능들이 생긴다. 이들은 주요 비즈니스 로직은 아니지만, 반복적으로 여러 곳에서 쓰이는 데 이를 흩어진 관심사(Cross Cutting Concerns)라고 한다. + +AOP 없이 흩어진 관심사를 처리하면 다음과 같은 문제가 발생한다. + +- 여러 곳에서 반복적인 코드를 작성해야 한다. +- 코드가 변경될 경우 여러 곳에 가서 수정이 필요하다. +- 주요 비즈니스 로직과 부가 기능이 한 곳에 섞여 가독성이 떨어진다. +- 따라서 흩어진 관심사를 별도의 클래스로 모듈화하여 위의 문제들을 해결하고, 결과적으로 OOP를 더욱 잘 지킬 수 있도록 도움을 주는 것이 AOP이다. + +### AOP의 주요 개념 + +- **Aspect :** Advice + PointCut로 AOP의 기본 모듈 + +- **Advice :** Target에 제공할 부가 기능을 담고 있는 모듈 + +- **Target :** Advice가 부가 기능을 제공할 대상 (Advice가 적용될 비즈니스 로직) + +- **JointPoint :** Advice가 적용될 위치. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용 가능 + +- **PointCut :** Target을 지정하는 정규 표현식 + +### Spring AOP + +- Spring AOP는 기본적으로 프록시 방식으로 동작한다. 프록시 패턴이란 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조 하는 것이 아니라, 해당 객체를 대행(대리, proxy)하는 객체를 통해 대상객체에 접근하는 방식을 말한다. + +- Spring AOP는 왜 프록시 방식을 사용하는가? Spring은 왜 Target 객체를 직접 참조하지 않고 프록시 객체를 사용할까? + + - 프록시 객체 없이 Target 객체를 사용하고 있다고 생각해보자. Aspect 클래스에 정의된 부가 기능을 사용하기 위해서, 우리는 **원하는 위치에서 직접 Aspect 클래스를 호출**해야 한다. 이 경우 Target 클래스 안에 부가 기능을 호출하는 로직이 포함되기 때문에, AOP를 적용하지 않았을 때와 동일한 문제가 발생한다. 여러 곳에서 반복적으로 Aspect를 호출해야 하고, 그로 인해 유지보수성이 크게 떨어진다. + + - 그래서 Spring에서는 Target 클래스 혹은 그의 상위 인터페이스를 상속하는 프록시 클래스를 생성하고, 프록시 클래스에서 부가 기능에 관련된 처리를 한다. 이렇게 하면 Target에서 Aspect을 알 필요 없이 순수한 비즈니스 로직에 집중할 수 있다. + + +- 예를 들어 다음 코드의 `logic()` 메서드가 Target이라면, + + ```java + public interface TargetService{ + void logic(); + } + + @Service + public class TargetServiceImpl implements TargetService{ + @Override + public void logic() { + ... + } + } + ``` +- Proxy에서 Target 전/후에 부가 기능을 처리하고 Target을 호출한다. + + ```java + @Service + public class TargetServiceProxy implements TargetService{ + // 지금은 구현체를 직접 생성했지만, 외부에서 의존성을 주입 받도록 할 수 있다. + TargetService targetService = new TargetServiceImpl(); + ... + + @Override + public void logic() { + // Target 호출 이전에 처리해야하는 부가 기능 + // Target 호출 + targetService.logic(); + // Target 호출 이후에 처리해야하는 부가 기능 + } + } + ``` + +- 사용하는 입장에서는 Target 객체를 사용하는 것처럼 일반적으로 사용할 수 있다. + + ```java + @Service + public class UseService { + // 지금은 구현체를 직접 생성했지만, 외부에서 의존성을 주입 받도록 할 수 있다. + TargetService targetService = new TargetServiceProxy(); + .. + + public void useLogic() { + //Target 호출하는 것처럼 부가 기능이 추가된 Proxy를 호출한다. + targetService.logic(); + } + } + ``` + +- Spring AOP에서는 AOP를 적용할 클래스를 지정하면 내부적으로 프록시가 생성되어 작동한다. diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Spring\354\227\220\354\204\234\342\200\205aspectj\342\200\205weaving\354\202\254\354\232\251\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Spring\354\227\220\354\204\234\342\200\205aspectj\342\200\205weaving\354\202\254\354\232\251\355\225\230\352\270\260.md" new file mode 100644 index 00000000..d7b124e2 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/Spring\354\227\220\354\204\234\342\200\205aspectj\342\200\205weaving\354\202\254\354\232\251\355\225\230\352\270\260.md" @@ -0,0 +1,140 @@ +--- +title: 'Spring에서 aspectj weaving사용하기' +lastUpdated: '2024-03-02' +--- + +Spring은 객체지향적으로 AOP 기술을 구현하기 위해 프록시 패턴의 관점을 선택했다. 이러한 패턴의 추상적인 관점을 구체화하기 위해, Java에서 기본적으로 제공해주고 있는 JDK Dynamic Proxy(프록시 패턴의 관점의 구현체)를 기반으로 추상적인 AOP 기술을 객체 모듈화하여 설계하였다. + +또한 Spring은 성숙한 AOP 기술을 제공해주기 위해 Spring 2.0 부터 `@AspectJ`애노테이션 방식을 지원하였고, Aspect를 구현하는데 있어 `AspectJ5` 라이브러리에 포함된 일부 애노테이션을 사용할 수 있다. AspectJ의 강력한 AOP 기술을 Spring에서 쉽게 구현할 수 있기 때문에 개발자는 비즈니스 개발에 보다 집중할 수 있다. + +물론, `@AspectJ` 애노테이션 방식을 통해 구현된 Aspect는 IoC 컨테이너에서 Bean으로 자동으로 관리해주고 있고 Bean 또한 가능하다. + +## weaving을 바꿔야하는 이유 + +Spring AOP가 사용하는 프록시 메커니즘엔 크게 두가지의 단점이 있다. + +### 자기호출 + +Spring AOP는 클라이언트가 특정 메소드를 호출할 시 호출된 메소드를 포인트컷으로 검증하고, 해당 타겟인 경우에 어드바이스 코드를 작동시킨다. + +![image](https://github.com/team-aliens/DMS-Backend/assets/81006587/e593f675-c7f2-4eb4-8597-69a4ce56ff60) + +여기서 중요한 점은 타깃(Proxy Bean)에 대한 메소드가 수행할 시점엔 어떠한 Aspect가 동작하지 않는다는 것이다. 이는 프록시 메커니즘을 기반으로 한 AOP 기술에서 발생할 수 있는 공통적인 문제로써, 이를 **자기 호출 문제**라 한다. Spring AOP에서 제공하는 CGLib(바이트 조작 기반)도 마찬가지로 JDK Dynamic Proxy를 기반으로 설계된 구조를 통해 동작하기 때문에 자기 호출 문제가 발생한다. + +Spring에서 자기 호출의 문제에 대한 여러 해결 방안이 존재한다. 하지만 기존 코드에 다른 코드를 덧붙이거나 구조에 영향을 준다. + +### 성능 + +두 번째 문제는 성능에 관련된 문제이다. + +Spring AOP는 런타임 시점(메소드 호출 시점)에 타깃에 대한 메소드 호출을 가로채고 내부적인 AOP 프로세스에 의해 어드바이스를 타깃의 메소드와 하나의 프로세스로 연결한다. + +![image](https://github.com/team-aliens/DMS-Backend/assets/81006587/5e39b829-806e-4877-a6f2-7681a81764ea) + +이러한 형태를 사슬(Chain) 모양을 띄고 있다하여 어드바이스 체이닝이라 한다. 이 체이닝은 런타임시 순차적으로 어드바이스 코드를 실행을 하고 다음 클라이언트가 원하는 로직(타깃의 메소드)이 수행된다. 이 점은 프록시 패턴의 특징인 타깃에 대한 안정성을 보장 받을 수 있다는 측면이라 볼 수 있다. + +하지만 Aspect가 늘어날수록 자연스레 실질적으로 수행될 타깃의 메소드는 늦어질 수밖에 없고, 이는 결과적으로 성능에 관한 문제로 직결된다. + +## AspectJ의 바이트 코드 조작 + +프록시 메커니즘을 가지고 있던 문제는 AspectJ 위빙 방식으로 전환하면 모든 문제를 해결할 수 있다. 기본적으로 AspectJ는 바이트 코드을 기반으로 위빙하기 때문에 성능 문제와 더불어 자기 호출에 대한 문제를 해결할 수 있다. + +## AspectJ weaving + +Spring에서 AspectJ로 전환하기 위해선 4가지 조건이 필요하다. + +- AspectJ 형식의 Aspect +- AspectJ Runtime +- AspectJ Weaver +- AspectJ Compiler(AJC) + +### AspectJ 형식의 Aspect +우선 첫 번째는 AspectJ 형식으로 구현된 Aspect가 필요합니다. + +- `.aj` 파일로 구현된 Aspect +- Java 파일로 구현된 Aspect(@AspectJ 애노테이션 기반) + +`*.aj` 확장자를 띈 파일로 구현된 Aspect는 순수한 AspectJ의 Aspect이다. + +```java +public aspect OriginalAspect{ + // 포인트컷 + pointcut pcd() : call(* ..*Service.method(..)); + + // 어드바이스 + void around() : pcd() {// 포인트컷 정의 + proceed(); + } +} +``` + +이 `*.aj` 파일의 코드 스타일은 Java와 비슷하면서도 다르고, AspectJ의 모든 기능을 사용할 수 있습니다. 커스텀마이징이 가능한 만큼 고려할 사항도 많고, 초기 학습에 대한 진입 장벽이 높고 어렵다. + +하지만 Java 형식으로도 AspectJ 형식의 Aspect를 구현할 수 있습니다. + +흔히 Spring AOP에서 흔히 사용하고 있는 `@AspectJ` 애노테이션 스타일의 Aspect는 AspectJ5 라이브러리의 일부 애노테이션을 사용하는 방식으로 전형적인 AspectJ의 형식이다. + +```java +@Aspect +@Component +public class SpringAOPAspect{ + ... +} +``` + +### AspectJ Runtime + +그 다음 AspectJ Runtime이 필요하다. + +AspectJ Runtime를 구성하기 위해선 AspectJ Runtime 라이브러리인 `aspectjrt.jar` 의존성만 추가시켜주면 된다. AspectJ로 구성된 애플리케이션을 실행하게 되면, `AspectJ Compiler(AJC)`엔 위빙할 객체의 정보가 포함되어 있고, AspectJ Runtime은 AJC에 포함된 객체의 정보를 토대로 위빙된 코드를 타깃에게 적용한다. + +```kotlin +implementation("org.arpectj:aspectjrt:1.9.4") +``` + +### AspectJ Weaver + +그리고 Aspect Weaver 라이브러리인 `aspectjweaver.jar`를 추가해줘야 한다. + +Aspect Weaver 라이브러리는 `@Aspect`, `@Pointcut`, `@Before`, `@Around` 등 AspectJ 5 라이브러리에 속한 애노테이션들을 포함하고 있다. + +AspectJ Weaver는 Aspect와 타깃의 바이트 코드를 위빙하고, 위빙된 바이트 코드를 컴파일러에게 제공하는 역할을 한다. + + + +### AspectJ Compiler + +마지막으로 `AspectJ Compiler(AJC)`라는 컴파일러가 필요하다. + +AJC는 Java Compiler를 확장한 형태의 컴파일러로써, AspectJ는 AJC를 통해 Java 파일을 컴파일하며, 컴파일 과정에서 타깃의 바이트 코드 조작(어드바이스 삽입)을 통해 위빙을 수행한다. + +대표적으로 두가지 종류가 있다. + +- AspectJ Development Tool(AJDT) : Eclipse에서 지원하는 툴(AJC가 내장되어있음) +- Mojo : AspectJ Maven Plugin + +## Aspectj의 Weaving + +AspectJ는 3 가지 위빙 방식을 지원한다. + +- CTW(Compile Time Weaving) + - AJC(AspectJ Compiler)를 이용해서, 소스 코드가 컴파일할 때 위빙한다. + - 타깃의 코드가 JVM 상에 올라갈 때 바이트 코드를 직접 조작하여, 타깃의 메소드 내에 어드바이스 코드를 삽입시켜 주기 때문에 성능이 가장 좋다. + - 컴파일 시점에만 바이트 코드를 조작하여 호출된 타깃의 메소드는 조작된 코드가 수행되기 때문에 정적인 위빙 방식이라 한다. + +- PCW(Post Compile Weaving) + - CTW가 컴파일 시점에 위빙을 한다면, PCW는 컴파일 직후의 바이너리 클래스에 위빙한다. + - PCW는 주로 JAR에 포함된 소스에 위빙하는 목적으로 사용된다. + +- LTW(Load Time Weaving) + - LTW는 JVM에 클래스가 로드되는 시점에 위빙을 한다. + - LTW는 RTW(Runtime Weaving)처럼 바이트 코드에 직접적으로 조작을 가하지 않기 때문에 컴파일 시간은 상대적으로 CTW와 PCW보다 짧지만, 오브젝트가 메모리에 올라가는 과정에서 위빙이 일어나기 때문에 런타임 시 위빙 시간은 상대적으로 느리다. + - Weaving Agent가 꼭 필요하다. + +--- + +참고 +- https://www.baeldung.com/aspectj +- https://www.baeldung.com/spring-aop-vs-aspectj +- https://www.eclipse.org/lists/aspectj-users/msg02750.html +- https://gmoon92.github.io/spring/aop/2019/05/24/aspectj-of-spring.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/TransactionAttributeSource.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/TransactionAttributeSource.md" new file mode 100644 index 00000000..bf35cca2 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/TransactionAttributeSource.md" @@ -0,0 +1,143 @@ +--- +title: 'TransactionAttributeSource' +lastUpdated: '2024-03-02' +--- + +위 두개의 클래스는 모두 `TransactionAttributeSource` 인터페이스의 구현체이다 + +`TransactionAttributeSource`는 TransactionInterceptor의 메타 데이터 검색에 사용되는 전략 인터페이스인데, 트렌잭션의 메타데이터나 속성을 소싱하는 역할을 한다. + +```java +public interface TransactionAttributeSource { + + default boolean isCandidateClass(Class targetClass) { + return true; + } + + @Nullable + TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass); + +} +``` + +코드를 보면 트랜잭션 속성의 후보인지(트랜잭션을 적용할 클래스인지) 여부를 반환하는 `isCandidateClass`와, 트랜잭션의 속성(TracsactionAttribute)을 반환하는 `getTransactionAttribute`라는 메서드가 있는 것을 볼 수 있다. + +그렇다면 각각의 구현 클래스는 어떤 특징을 가지고 있을까? + +# `MatchAlwaysTransactionAttributeSource` + +TransactionAttributeSource의 매우 간단한 구현으로, 공급된 모든 메서드에 대해 항상 동일한 TransactionAttribute를 반환하는 구현체이다. TransactionAttribute를 지정할 수 있지만 그렇지 않으면 PRAGATION_REQUERED로 기본 설정된다. 트랜잭션 인터셉터에 의해 처리되는 모든 메서드와 동일한 트랜잭션 속성을 사용하려는 경우에 사용할 수 있다. + +```java +public class MatchAlwaysTransactionAttributeSource implements TransactionAttributeSource, Serializable { + + private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(); + + public void setTransactionAttribute(TransactionAttribute transactionAttribute) { + if (transactionAttribute instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) transactionAttribute).resolveAttributeStrings(null); + } + this.transactionAttribute = transactionAttribute; + } + + @Override + @Nullable + public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass) { + return (ClassUtils.isUserLevelMethod(method) ? this.transactionAttribute : null); + } + + ... +} +``` + +## `NameMatchTransactionAttributeSource` + +등록된 이름으로 속성을 일치시킬 수 있는 간단한 구현체이다. 여러개의 트랜잭션 규칙을 이름을 통해 구분짓는다. + +```java +public class NameMatchTransactionAttributeSource + implements TransactionAttributeSource, EmbeddedValueResolverAware, InitializingBean, Serializable { + + protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class); + + /** Keys are method names; values are TransactionAttributes. */ + private final Map nameMap = new HashMap<>(); + + @Nullable + private StringValueResolver embeddedValueResolver; + + public void setNameMap(Map nameMap) { + nameMap.forEach(this::addTransactionalMethod); + } + + public void setProperties(Properties transactionAttributes) { + TransactionAttributeEditor tae = new TransactionAttributeEditor(); + Enumeration propNames = transactionAttributes.propertyNames(); + while (propNames.hasMoreElements()) { + String methodName = (String) propNames.nextElement(); + String value = transactionAttributes.getProperty(methodName); + tae.setAsText(value); + TransactionAttribute attr = (TransactionAttribute) tae.getValue(); + addTransactionalMethod(methodName, attr); + } + } + + public void addTransactionalMethod(String methodName, TransactionAttribute attr) { + if (logger.isDebugEnabled()) { + logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); + } + if (this.embeddedValueResolver != null && attr instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver); + } + this.nameMap.put(methodName, attr); + } + + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + + @Override + public void afterPropertiesSet() { + for (TransactionAttribute attr : this.nameMap.values()) { + if (attr instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver); + } + } + } + + @Override + @Nullable + public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class targetClass) { + if (!ClassUtils.isUserLevelMethod(method)) { + return null; + } + + // Look for direct name match. + String methodName = method.getName(); + TransactionAttribute attr = this.nameMap.get(methodName); + + if (attr == null) { + // Look for most specific name match. + String bestNameMatch = null; + for (String mappedName : this.nameMap.keySet()) { + if (isMatch(methodName, mappedName) && + (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) { + attr = this.nameMap.get(mappedName); + bestNameMatch = mappedName; + } + } + } + + return attr; + } + + protected boolean isMatch(String methodName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, methodName); + } + + ... +} +``` + + diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/\355\212\270\353\236\234\354\236\255\354\205\230.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/\355\212\270\353\236\234\354\236\255\354\205\230.md" new file mode 100644 index 00000000..60957d97 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/\355\212\270\353\236\234\354\236\255\354\205\230.md" @@ -0,0 +1,96 @@ +--- +title: '트랜잭션' +lastUpdated: '2024-03-02' +--- + +포괄적인 트랜잭션 지원은 Spring Framework를 사용하는 가장 강력한 이유 중 하나이다. Spring Framework는 다음과 같은 이점을 제공하는 트랜잭션 관리를 위한 일관된 추상화를 제공한다. + +- JTA(Java Transaction API), JDBC, Hibernate 및 JPA(Java Persistence API)와 같은 다양한 트랜잭션 API에서 일관된 프로그래밍 모델 +- 선언적 트랜잭션 관리 지원 +- 프로그래밍 방식 트랜잭션 관리 지원 +- Spring의 데이터 접근 추상화와의 뛰어난 통합 + +--- + +## 이전의 Transaction 지원 모델...... + +Spring Transaction 지원 모델에 대해 알아보기 전에, 이전의 Transaction 처리 방식에 대해 알아보자. + +전통적인 Java EE 개발자는 트랜잭션 관리를 하기 위해선 두가지중 하나를 선택해야했다. + +- Global Transactions +- Local Transactions + +### Global Transactions + +- Global Transactions는 RDB나 MQ 등의 여러 리소스에서 작업할 수 있는 통용되는 트랜잭션 관리 방법이다. 주로 JTA를 통해 글로벌 트랜잭션을 관리한다. 또한 JTA의 UserTransaction은 일반적으로 JNDI에서 제공되어야 한다. 즉, JTA를 사용하려면 JNDI도 사용해야 한다. + +- JTA는 일반적으로 응용 프로그램 서버 환경에서만 사용할 수 있으므로 전역 트랜잭션을 사용하면 응용 프로그램 코드의 잠재적인 재사용이 제한된다. 그렇기 때문에 JTA는 햔재 잘 쓰이지 않는 기술이다. + +- 이전에 Global Transactions를 사용하는데 선호되었던 방법은 EJB CMT (Container Managed Transaction)였다. CMT는 선언적 트랜잭션 관리의 한 형태인데, 이 CMT는 JTA 및 응용 프로그램 서버 환경에 묶여 있어 EJB와 꼭 함께 사용해야만 한다. + +하지만 EJB는 너무나도 많은 단점을 가지고 있기 때문에, 현재는 CMT 또한 잘 쓰이지 않는다. + +### Local Transactions + +- Local Transactions는 각 데이터나 기술에 맞는 트랜잭션 방식을 사용하는 것이다. 각 기술에 특화되어있기 때문에 그에 따라 코드를 다 따로 작성해줘야한다. 또, 애플리케이션 서버는 트랜잭션 관리에 관여하지 않기 때문에 여러 리소스에 대한 정확성을 보장하는 데 도움이 되지 않는다. + +## Spring Transaction 지원 모델 + +- Spring은 Global Transactions와 Local Transactions의 문제를 해결한다! Spring은 개발자가 개발 환경에 구애받지 않고 일관된 프로그래밍 모델을 사용할 수 있도록 해준다. + +- 프로그래밍 방식의 트랜잭션 관리를 통해 개발자는 기본 트랜잭션 인프라에서, 실행할 수 있는 Spring 트랜잭션 추상화로 작업할 수 있다. 트랜잭션 관리라는 구체적인 부분과 다른 로직이 구분되기 때문에 DIP적이다. + +- Spring Framework는 선언적 트랜잭션 관리와 프로그래밍 트랜잭션 관리를 모두 제공하며, 대부분의 경우에는 선언적 트랜잭션 관리가 선호된다. + +--- + +## Spring Framework가 트랜잭션을 추상화하는 방식 + +Spring 트랜잭션 추상화에서 가장 중요한 것은, 트랜잭션 전략이라는 개념이다. + +이 트랜잭션 전략은 `TransactionManager`에 의해 정의된다. + +트랜잭션 전략중 주로 쓰이는 것은, `PlatformTransactionManager` 인터페이스가 있다. 인터페이스이기 때문에 상황에 따라 맞는 구현체를 선택할 수 있다. + +```java +public interface TransactionManager { +} + +public interface PlatformTransactionManager extends TransactionManager { + + TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; + + void commit(TransactionStatus status) throws TransactionException; + + void rollback(TransactionStatus status) throws TransactionException; +} + +``` + +여기서 `getTransaction(...)` 메소드는 `TransactionDefinition`을 파라미터로 받아 `TransactionStatus`를 반환한다. + +`TransactionStatus`는 새 트랜잭션, 혹은 현재 쓰레드에 이미 존재하는 트랜잭션의 정보를 나타내며, +`TransactionDefinition` 인터페이스는 트랜잭션의 다양한 속성을 지정할 수 있다. + +`TransactionDefinition`에서 지정할 수 있는 속성으로는 다음과 같은 것들이 있다. + +- 전파 : + 트랜잭션 범위 안에 있는 모든 코드는 트랜잭션에 묶여 실행된다. 하지만 **트랜잭션 컨텍스트가 이미 존재할 때는 트랜잭션 메소드가 동작하는 방식을 따로 지정**할 수 있다. + + 일반적인 경우에는 코드가 기존 트랜잭션에서 계속 실행되지만, 원하는 경우에는 기존 트랜잭션을 일시 중단하고 새 트랜잭션을 생성할 수 있다. 트랜잭션 전파 문서에서 더 알아보자 + +- 격리수준 : + 트랜잭션이 다른 트랜잭션의 작업과 격리되는 정도이다. `READ_UNCOMMITTED`, `READ_COMMITTED`, `READ_COMMITTED`, `SERIALIZABLE` 등의 설정값이 있다. 또는, RDBMS의 기본값에 따를 수도 있다. + +- 시간 제한 : + 트랜잭션이 일정 시간 이상 수행되면, 자동으로 롤백되도록 설정할 수 있다. + +- 읽기 전용 여부 : + 트랜잭션을 Read-Only로 설정하여 일부 기능을 최적화할 수 있다. Hibernate를 사용하는 경우에 유용하게 사용될 수 있다. + +이러한 설정은 표준 트랜잭션 개념을 반영한다. 트랜잭션에 대해 자세히 알고 싶다면 이 문서를 참조할 수 있다. + +--- + +출처: https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\202\354\240\204\355\214\214.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\202\354\240\204\355\214\214.md" new file mode 100644 index 00000000..35f10141 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOP/\355\212\270\353\236\234\354\236\255\354\205\230/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\202\354\240\204\355\214\214.md" @@ -0,0 +1,20 @@ +--- +title: '트랜잭션 전파' +lastUpdated: '2024-03-02' +--- + +트랜잭션 전파란 트랜잭션 경계에서 이미 진행 중인 트랜잭션이 있을 때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식을 말한다. + +### 1. PROPAGATION_REQUIRED + +- 가장 많이 사용되는 트랜잭션 속성이다. 진행 중인 트랜잭션이 없으면 새로 시작하고, 이미 시작된 트랜잭션이 있으면 이에 참여한다. DefaultTransactionDefinition의 트랜잭션 전파 속성은 바로 이 PROPAGATION_REQUIRED이다. + +### 2. PROPAGATION_REQUIRED_NEW + +- 항상 새로운 트랜잭션을 시작한다. 즉. 앞에서 시작된 트랜잭션이 있든 없든 상관없이 새로운 트랜잭션을 만들어서 독자적으로 동작하게 한다. + +### 3. PROPAGATION_NOT_SUPPORTED + +- 트랜잭션이 없이 동작하도록 만들 수 있다. 진행 중인 트랜잭션이 있어도 무시한다. + +- 트랜잭션 경계설정은 보통 AOP를 이용해 한 번에 많은 메소드에 동시에 적용하는 방법을 사용한다. 그 중에서 특별한 메소드만 트랜잭션 적용에서 제외하려면 이 속성을 이용해서 제외시킬 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOT.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOT.md" new file mode 100644 index 00000000..27b5998c --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/AOT.md" @@ -0,0 +1,55 @@ +--- +title: 'AOT' +lastUpdated: '2024-03-02' +--- + +Spring AOT 엔진은 **빌드 시 스프링 애플리케이션을 분석하고 최적화**하는 도구이다. 또한 AOT 엔진은 GraalVM Native Configuration이 필요로 하는 `reflection configuration`을 생성해준다. 이것은 Spring native 실행 파일로 컴파일 하는데 사용되고 이후에 애플리케이션의 시작 시간과 메모리 사용량을 줄일 수 있게 한다. + +그러한 변환은 Maven과 Gradle 스프링 AOT 플러그인에 의해 수행된다. + + + +AOT 엔진은 최적화된 애플리케이션 컨텍스트와 애플리케이션을 위해 특별히 제작된 스프링 팩토리(Spring Boot 뒤의 플러그인 시스템)를 생성하기 위해 빌드 시 조건을 평가한다. 이를 통해 아래와 같은 효과를 볼 수 있다. + +- 런타임시 필요한 스프링 인프라가 줄어든다. +- 런타임에 계산해야하는 조건이 감소한다. +- reflection의 방식을 줄이고, [프로그래밍 방식](https://github.com/rlaisqls/TIL/blob/main/%EC%8A%A4%ED%94%84%EB%A7%81%E2%80%85Spring/%EA%B8%B0%EB%B3%B8%EC%9B%90%EB%A6%AC/Programmatic%EA%B3%BC%E2%80%85Declarative.md)의 빈 등록을 사용한다. + +AOT 엔진은 식별된 빈과 Spring 프로그래밍 모델, Spring Native에 있는 Native 힌트를 기반으로 애플리케이션을 돌리기 위해 필요한 `native configuration`을 추론한다. + + + + +## 장점 + +### 적은 메모리 사용량 (Reduced Memory Footprint) + +AOT 엔진의 주요 장점은 더 정확한 네이티브 구성을 사용하고 reflection이 덜 필요하며 런타임에 스프링 인프라가 덜 필요하기 때문에 네이티브 실행 파일에 더 작은 메모리를 사용한다는 것이다. + +Spring Native 0.11는 Spring Native 0.10에 비해 사용하는 메모리의 양이 20%~26% 줄어들었다고 한다. + + + + +### 빠른 스타트업 (Faster Startup) + +일부 처리가 런타임에서 빌드 시간으로 이동했기 때문에 스프링 네이티브 0.11에서 시작 시간이 0.10에 비해 16%에서 35% 더 빠르다. 이 마이너 버전 업데이트에서는 스프링 부트 및 스프링 프레임워크의 내부 아키텍처를 미세 조정할 수 없었기 때문에 여전히 개선의 여지가 있다. + + + +### 향상된 호환성 + +AOT 엔진은 스프링 annotation 등의 다양한 유형을 분석하여, 실행 시 스프링이 수행하는 작업을 복제하지 않기 때문에 훨씬 더 정확하다. 대신에, 애플리케이션 컨텍스트를 (시작하지 않고) 빌드 시에 생성하고 내성적으로 만드는 새로운 프로세스를 시작한다. 이를 통해 Spring Framework가 런타임에 수행하고 빈 정의 수준에서 작동하는 부분 집합을 사용할 수 있으며, 이는 훨씬 더 정확하다. + + +## 단점 + +### 런타임 유연성 + +빌드 시 이러한 최적화를 수행하면 일반 Spring Boot 자동 구성 모델보다 런타임 유연성이 떨어진다. 이미 컴파일된 Spring Boot 응용 프로그램을 실행하는 경우에도 응용 프로그램의 HTTP 포트 또는 로그 수준을 변경할 수 있지만, 프로파일을 사용하여 런타임에 새 빈을 추가할 수는 없다. + +그렇기 때문에 JVM에서 AOT 모드는 선택 사항이다. 이는 필요에 따라 사용할 수 있는 최적화다. 하지만 네이티브(설계상 런타임에 훨씬 덜 동적임)에서는 필수이다. 또한 현재로서는 구축 시 조건이 평가되지만, 향후에는 대부분의 사용 사례에 적합하도록 유연성을 높일 것이라고 한다. + +--- +참고 +- https://spring.io/blog/2021/12/09/new-aot-engine-brings-spring-native-to-the-next-level \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Event/@TransactionalEventListener.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Event/@TransactionalEventListener.md" new file mode 100644 index 00000000..463eb126 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Event/@TransactionalEventListener.md" @@ -0,0 +1,41 @@ +--- +title: '@TransactionalEventListener' +lastUpdated: '2024-03-02' +--- + +Event를 사용할 때 기본적으로 사용하는 `@EventListener`는 **event를 publishing 하는 코드 시점에 바로 publishing*한다. 하지만 우리가 퍼블리싱하는 event는 대부분 메인 작업이 아닌 서브의 작업이 많고 비동기로 진행해도 되는 경우도 많다. 다른 도메인 로직인 경우도 있다. 이럴 경우 조금 애매해지기도 한다. + +아래 예를 보자. 아래코드는 `@Transactional`로 메서드가 하나의 트랜잭션으로 묶여있다. 이 메서드를 실행했을때, 1번과 2번이 정상적으로 마무리되고 3번이 발생하는 도중에 예외처리가 발생하면 어떻게 될까? + +3번이 실패했으면 같은 트랜잭션으로 묶여있는 1번도 함께 롤백될 것이다. 하지만 2번은 발행된 이벤트를 listen하는 별도의 구현체가 이후의 동작을 수행하기 때문에, rollback이 이루어지지 않고 결과적으로 불일치가 발생할 수 밖에 없게 된다. + +```java +@Transactional +public void function() { + + aaaRepository.save() // 1. A 저장 + + applicationEventPublisher.publishEvent(); // 2. A에 의한 이벤트 발생 + + bbbRepository.save() // 3. B 저장 + +} +``` + +이러한 문제를 해결하기 위해서 @TransactionEventListener가 나온 것이다. @TransactionEventListener는 Event의 실질적인 발생을 트랜잭션의 종료를 기준으로 삼는것이다. + +## @TransactionalEventListener 옵션 + +@TransactionalEventListener를 이용하면 트랜잭션의 어떤 타이밍에 이벤트를 발생시킬 지 정할 수 있다. 옵션을 사용하는 방법은 TransactionPhase을 이용하는 것이며. 아래와 같은 옵션을 사용할 수 있다. + +- AFTER_COMMIT (기본값) : 트랜잭션이 성공적으로 마무리(commit)됬을 때 이벤트 실행 +- AFTER_ROLLBACK : 트랜잭션이 rollback 됐을 때 이벤트 실행 +- AFTER_COMPLETION : 트랜잭션이 마무리 됬을 때(commit or rollback) 이벤트 실행 +- BEFORE_COMMIT : 트랜잭션의 커밋 전에 이벤트 실행 + +--- + +참고 + +- https://www.baeldung.com/transaction-configuration-with-jpa-and-spring +- https://stackoverflow.com/questions/51097916/transactionaleventlistener-doesnt-works-where-as-eventlistener-works-like-cha \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Event/ApplicationEventPublisher.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Event/ApplicationEventPublisher.md" new file mode 100644 index 00000000..bac7dfa9 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Event/ApplicationEventPublisher.md" @@ -0,0 +1,80 @@ +--- +title: 'ApplicationEventPublisher' +lastUpdated: '2024-03-02' +--- + +ApplicationEventPublisher는 Spring의 ApplicationContext가 상속하는 인터페이스 중 하나이다. + +옵저버 패턴의 구현체로 이벤트 프로그래밍에 필요한 기능을 제공해준다. 이벤트 기반의 방법을 사용하면, 서비스간 강결합 문제를 해결힐 수 있다. + +## 1. ApplicationEvent를 상속하는 이벤트 클래스 만들기 + +```java +import org.springframework.context.ApplicationEvent; + +public class MyEvent extends ApplicationEvent { + + private int data; + + public MyEvent(Object source) { + super(source); + } + + public MyEvent(Object source, int data) { + super(source); + this.data = data; + } + + public int getData() { + return data; + } +} +``` + +## 2. ApplicationConext로 이벤트 발생시키기 + +```java +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@Component +public class AppRunner implements ApplicationRunner { + + @Autowired + ApplicationEventPublisher eventPublisher; + + @Override + public void run(ApplicationArguments args) throws Exception { + eventPublisher.publishEvent(new MyEvent(this, 100)); // 이벤트 발생시키기 + } +} +``` + +ApplicationContext(ApplicationEventPublisher)의 publishEvent() 메소드를 호출해서 이벤트를 발생시킬 수 있다. + +ApplicationContext 타입으로 주입받아도 되지만 이벤트 발생 기능을 사용할 것이므로 ApplicationEventPublisher 타입으로 선언하였다. + +## 3. 이벤트 핸들링(처리) 하기 + +```java +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +public class MyEventHandler implements ApplicationListener { + + @Override + public void onApplicationEvent(MyEvent event) { + System.out.println("First event handling, data: " + event.getData()); + } +} +``` + +ApplicationEventPublisher가 발생시킨 이벤트를 처리할 핸들러 MyEventHandler 클래스를 생성하고 위와 같이 작성한다. + +이벤트 핸들러는 spring이 발생한 이벤트를 누구에게 전달해야하는지 알아야 하기 때문에 빈으로 등록해야 한다. + +onApplicationEvent() 안에 이벤트에 대해 필요한 작업을 처리하는 코드를 작성하면 된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/@GeneratedValue\342\200\205\354\275\224\353\223\234\353\263\264\352\270\260.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/@GeneratedValue\342\200\205\354\275\224\353\223\234\353\263\264\352\270\260.md" new file mode 100644 index 00000000..d6341c6f --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/@GeneratedValue\342\200\205\354\275\224\353\223\234\353\263\264\352\270\260.md" @@ -0,0 +1,558 @@ +--- +title: '@GeneratedValue 코드보기' +lastUpdated: '2024-03-02' +--- + +GeneratedValue는 jakarta에 정의되어있고, Id에 새로운 값을 자동으로 생성해줄 전략을 지정하기 위한 어노테이션이다. + +```java +@Target({METHOD, FIELD}) +@Retention(RUNTIME) + +public @interface GeneratedValue { + + /** + * (Optional) The primary key generation strategy + * that the persistence provider must use to + * generate the annotated entity primary key. + */ + GenerationType strategy() default AUTO; + + /** + * (Optional) The name of the primary key generator + * to use as specified in the {@link SequenceGenerator} + * or {@link TableGenerator} annotation. + *

Defaults to the id generator supplied by persistence provider. + */ + String generator() default ""; +} +``` + +실제로 `@GeneratedValue`가 등록되는 것은 +- `org.hibernate.cfg.AnnotationBinder`의 `bindClass`라는 public method에서 호출하는 +- `processIdPropertiesIfNotAlready`라는 private method에서 호출하는 +- `processElementAnnotations`에서 호출하는 `processId`에서 호출하는 +- `BinderHelper.makeIdGenerator`이다. + +그 외에도 `bindClass`에서는 Entity를 등록하고 여러 어노테이션을 적용하기 위한 아주 많은 동작들이 이뤄진다.. 코드량이 워낙 방대해서 전부 보긴 힘들지만 한번 보면 괜찮을 것 같은 부분만 추려보았다. + +우리의 목적인 **`GeneratedValue`가 어디서 어떻게 나오는지에 대해 유의**하며 코드를 읽어보자. [(javadoc 링크)](https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/cfg/AnnotationBinder.html) + + +```java +// AnnotationBinder.java + public static void bindClass( + XClass clazzToProcess, + Map inheritanceStatePerClass, + MetadataBuildingContext context) throws MappingException { + //@Entity and @MappedSuperclass on the same class leads to a NPE down the road + if ( clazzToProcess.isAnnotationPresent( Entity.class ) + && clazzToProcess.isAnnotationPresent( MappedSuperclass.class ) ) { + throw new AnnotationException( "An entity cannot be annotated with both @Entity and @MappedSuperclass: " + + clazzToProcess.getName() ); + } + + // entity의 거의 모든 정보를 담아서 관리하는 객체. + EntityBinder entityBinder = new EntityBinder( + entityAnn, + hibEntityAnn, + clazzToProcess, + persistentClass, + context + ); + + // superEntity 가져옴 + + if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) { + discriminatorColumn = processSingleTableDiscriminatorProperties( + clazzToProcess, + context, + inheritanceState, + entityBinder + ); + } else if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { + discriminatorColumn = processJoinedDiscriminatorProperties( + clazzToProcess, + context, + inheritanceState, + entityBinder + ); + } else { + discriminatorColumn = null; + } + + // Proxy, BatchSize, Where 어노테이션이 있으면 등록해준다. + entityBinder.setProxy( clazzToProcess.getAnnotation( Proxy.class ) ); + entityBinder.setBatchSize( clazzToProcess.getAnnotation( BatchSize.class ) ); + entityBinder.setWhere( clazzToProcess.getAnnotation( Where.class ) ); + applyCacheSettings( entityBinder, clazzToProcess, context ); + + // Lazy, DynamicInsert, DynamicUpdate 등의 정보를 등록한다. + entityBinder.bindEntity(); + + if ( inheritanceState.hasTable() ) { + Check checkAnn = clazzToProcess.getAnnotation( Check.class ); + String constraints = checkAnn == null + ? null + : checkAnn.constraints(); + + EntityTableXref denormalizedTableXref = inheritanceState.hasDenormalizedTable() + ? context.getMetadataCollector().getEntityTableXref( superEntity.getEntityName() ) + : null; + + // 스키마, Unique 제약조건 등의 table 정보도 entityBinder에 binding 된다. + entityBinder.bindTable( + schema, + catalog, + table, + uniqueConstraints, + constraints, + denormalizedTableXref + ); + } + + // todo : sucks that this is separate from RootClass distinction + final boolean isInheritanceRoot = !inheritanceState.hasParents(); + final boolean hasSubclasses = inheritanceState.hasSiblings(); + + if ( InheritanceType.JOINED.equals( inheritanceState.getType() ) ) { + ... + } + + Set idPropertiesIfIdClass = new HashSet<>(); + boolean isIdClass = mapAsIdClass( + inheritanceStatePerClass, + inheritanceState, + persistentClass, + entityBinder, + propertyHolder, + elementsToProcess, + idPropertiesIfIdClass, + context + ); + + if ( !isIdClass ) { + entityBinder.setWrapIdsInEmbeddedComponents( elementsToProcess.getIdPropertyCount() > 1 ); + } + + // 여기가 GeneratedValue와 연관있는 부분이다. + processIdPropertiesIfNotAlready( + inheritanceStatePerClass, + context, + persistentClass, + entityBinder, + propertyHolder, + classGenerators, + elementsToProcess, + subclassAndSingleTableStrategy, + idPropertiesIfIdClass + ); + + ... + //add process complementary Table definition (index & all) + entityBinder.processComplementaryTableDefinitions( clazzToProcess.getAnnotation( org.hibernate.annotations.Table.class ) ); + entityBinder.processComplementaryTableDefinitions( clazzToProcess.getAnnotation( org.hibernate.annotations.Tables.class ) ); + entityBinder.processComplementaryTableDefinitions( tabAnn ); + + bindCallbacks( clazzToProcess, persistentClass, context ); // entity bind가 끝났음을 알려준다. + } +``` + +```java +// AnnotationBinder.java + private static void processIdPropertiesIfNotAlready( + Map inheritanceStatePerClass, + MetadataBuildingContext context, + PersistentClass persistentClass, + EntityBinder entityBinder, + PropertyHolder propertyHolder, + HashMap classGenerators, + InheritanceState.ElementsToProcess elementsToProcess, + boolean subclassAndSingleTableStrategy, + Set idPropertiesIfIdClass) { + + // IdClass에 지정된 Id List를 저장하고, 하나하나 지워가면서 마지막에 남은게 있으면 AnnotationException을 던진다. + Set missingIdProperties = new HashSet<>( idPropertiesIfIdClass ); + + for ( PropertyData propertyAnnotatedElement : elementsToProcess.getElements() ) { + + String propertyName = propertyAnnotatedElement.getPropertyName(); + + if ( !idPropertiesIfIdClass.contains( propertyName ) ) { + + // id인 필드들에 대해서 process 진행 + processElementAnnotations( + propertyHolder, + subclassAndSingleTableStrategy + ? Nullability.FORCED_NULL + : Nullability.NO_CONSTRAINT, + propertyAnnotatedElement, + classGenerators, + entityBinder, + false, + false, + false, + context, + inheritanceStatePerClass + ); + } + else { + missingIdProperties.remove( propertyName ); + } + } + + if ( missingIdProperties.size() != 0 ) { + StringBuilder missings = new StringBuilder(); + for ( String property : missingIdProperties ) { + missings.append( property ).append( ", " ); + } + throw new AnnotationException( + "Unable to find properties (" + + missings.substring( 0, missings.length() - 2 ) + + ") in entity annotated with @IdClass:" + persistentClass.getEntityName() + ); + } + } +``` + +```java +// AnnotationBinder.java + private static void processElementAnnotations( + PropertyHolder propertyHolder, + Nullability nullability, + PropertyData inferredData, + HashMap classGenerators, + EntityBinder entityBinder, + boolean isIdentifierMapper, + boolean isComponentEmbedded, + boolean inSecondPass, + MetadataBuildingContext context, + Map inheritanceStatePerClass) throws MappingException { + ... + if ( isId ) { + //components and regular basic types create SimpleValue objects + final SimpleValue value = ( SimpleValue ) propertyBinder.getValue(); + if ( !isOverridden ) { + processId( // Id에 대한 부분을 precess한다. + propertyHolder, + inferredData, + value, + classGenerators, + isIdentifierMapper, + context + ); + } + } + ... + } +``` + +```java +// AnnotationBinder.java + private static void processId( + PropertyHolder propertyHolder, + PropertyData inferredData, + SimpleValue idValue, + HashMap classGenerators, + boolean isIdentifierMapper, + MetadataBuildingContext buildingContext) { + ... + //manage composite related metadata + //guess if its a component and find id data access (property, field etc) + final boolean isComponent = entityXClass.isAnnotationPresent( Embeddable.class ) + || idXProperty.isAnnotationPresent( EmbeddedId.class ); + + GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class ); + + String generatorType = generatedValue != null + ? generatorType( generatedValue, buildingContext, entityXClass ) + : "assigned"; + + String generatorName = generatedValue != null + ? generatedValue.generator() + : BinderHelper.ANNOTATION_STRING_DEFAULT; + + if ( isComponent ) { + //a component must not have any generator + generatorType = "assigned"; + } + if ( isComponent ) { + //a component must not have any generator + generatorType = "assigned"; + } + + if ( isGlobalGeneratorNameGlobal( buildingContext ) ) { // global 설정이 있는 경우 + buildGenerators( idXProperty, buildingContext ); + SecondPass secondPass = new IdGeneratorResolverSecondPass( + idValue, + idXProperty, + generatorType, + generatorName, + buildingContext + ); + buildingContext.getMetadataCollector().addSecondPass( secondPass ); + } + else { // 일반적인 경우 + //clone classGenerator and override with local values + HashMap localGenerators = (HashMap) classGenerators + .clone(); + localGenerators.putAll( buildGenerators( idXProperty, buildingContext ) ); + BinderHelper.makeIdGenerator( // BinderHelper를 통해 idGenerator를 만들고 등록시킨다. + idValue, + idXProperty, + generatorType, + generatorName, + buildingContext, + localGenerators + ); + } + } +``` + +```java +// BinderHelper.java + public static void makeIdGenerator( + SimpleValue id, // Any value that maps to columns. + XProperty idXProperty, // reflection을 위한 타입 + String generatorType, + String generatorName, + MetadataBuildingContext buildingContext, + Map localGenerators) { + log.debugf( "#makeIdGenerator(%s, %s, %s, %s, ...)", id, idXProperty, generatorType, generatorName ); + + Table table = id.getTable(); + table.setIdentifierValue( id ); + //generator settings + id.setIdentifierGeneratorStrategy( generatorType ); + + Properties params = new Properties(); + + //always settable + params.setProperty( + PersistentIdentifierGenerator.TABLE, table.getName() + ); + + if ( id.getColumnSpan() == 1 ) { + params.setProperty( + PersistentIdentifierGenerator.PK, + ( (org.hibernate.mapping.Column) id.getColumnIterator().next() ).getName() + ); + } + // YUCK! but cannot think of a clean way to do this given the string-config based scheme + params.put( PersistentIdentifierGenerator.IDENTIFIER_NORMALIZER, buildingContext.getObjectNameNormalizer() ); + params.put( IdentifierGenerator.GENERATOR_NAME, generatorName ); + + if ( !isEmptyAnnotationValue( generatorName ) ) { + //we have a named generator + IdentifierGeneratorDefinition gen = getIdentifierGenerator( // generator를 찾아옴 + generatorName, + idXProperty, + localGenerators, + buildingContext + ); + if ( gen == null ) { + throw new AnnotationException( "Unknown named generator (@GeneratedValue#generatorName): " + generatorName ); + } + //This is quite vague in the spec but a generator could override the generator choice + String identifierGeneratorStrategy = gen.getStrategy(); // stratge를 가쳐옴 + + //yuk! this is a hack not to override 'AUTO' even if generator is set + final boolean avoidOverriding = + identifierGeneratorStrategy.equals( "identity" ) + || identifierGeneratorStrategy.equals( "seqhilo" ) + || identifierGeneratorStrategy.equals( MultipleHiLoPerTableGenerator.class.getName() ); + + if ( generatorType == null || !avoidOverriding ) { + id.setIdentifierGeneratorStrategy( identifierGeneratorStrategy ); + } + + //checkIfMatchingGenerator(gen, generatorType, generatorName); + for ( Object o : gen.getParameters().entrySet() ) { + Map.Entry elt = (Map.Entry) o; + if ( elt.getKey() == null ) { + continue; + } + params.setProperty( (String) elt.getKey(), (String) elt.getValue() ); + } + } + if ( "assigned".equals( generatorType ) ) { + id.setNullValue( "undefined" ); + } + id.setIdentifierGeneratorProperties( params ); + } +``` + +위에서 `SimpleValue id`에 set했던 부분은 field로 저장된다. id가 값 세팅받는 부분들을 보면, 중요한 정보를 많이 담고있다는 것을 알 수 있다. + +- `Table table = id.getTable(); table.setIdentifierValue( id );` +- `id.setIdentifierGeneratorStrategy( generatorType );` +- `id.setIdentifierGeneratorStrategy( identifierGeneratorStrategy );` +- `id.setIdentifierGeneratorProperties( params );` + +```java +// SimpleValue.java +public class SimpleValue implements KeyValue { + private static final CoreMessageLogger log = CoreLogging.messageLogger( SimpleValue.class ); + + public static final String DEFAULT_ID_GEN_STRATEGY = "assigned"; + + private final MetadataImplementor metadata; + + private final List columns = new ArrayList<>(); + private final List insertability = new ArrayList<>(); + private final List updatability = new ArrayList<>(); + + private String typeName; + private Properties typeParameters; + private boolean isVersion; + private boolean isNationalized; + private boolean isLob; + + private Properties identifierGeneratorProperties; + private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; + private String nullValue; + private Table table; + private String foreignKeyName; + private String foreignKeyDefinition; + private boolean alternateUniqueKey; + private boolean cascadeDeleteEnabled; + + private ConverterDescriptor attributeConverterDescriptor; + private Type type; + ... +} +``` + +이렇게 생성된 SimpleValue는 `SessionFactoryImpl`의 생성자에서 가져오고, `IdentifierGenerator`의 `identifierGeneratorProperties`에 SimpleValue param들이 넣어져서 `identifierGenerators`로 등록된다. 이렇게 생성된 SessionFactory는 애플리케이션 전체에서 Session을 만들때 사용되므로, 전체에 적용된다 생각할 수 있다. + +```java +// SessionFactoryImpl.java + public SessionFactoryImpl( + final MetadataImplementor metadata, + SessionFactoryOptions options, + QueryPlanCache.QueryPlanCreator queryPlanCacheFunction) { + LOG.debug( "Building session factory" ); + + this.sessionFactoryOptions = options; + this.settings = new Settings( options, metadata ); + ... + try { + for ( Integrator integrator : serviceRegistry.getService( IntegratorService.class ).getIntegrators() ) { + integrator.integrate( metadata, this, this.serviceRegistry ); + integratorObserver.integrators.add( integrator ); + } + //Generators: + this.identifierGenerators = new HashMap<>(); + metadata.getEntityBindings().stream().filter( model -> !model.isInherited() ).forEach( model -> { + // 이 createIdentifierGenerator라는 함수에서 SimpleValue에 있던 param들을 IdentifierGenerator의 identifierGeneratorProperties로 등록시킨다. + IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator( + metadata.getIdentifierGeneratorFactory(), + jdbcServices.getJdbcEnvironment().getDialect(), + (RootClass) model + ); + generator.initialize( sqlStringGenerationContext ); + identifierGenerators.put( model.getEntityName(), generator ); + } ); + metadata.validate(); + } + ... + } +``` + +이렇게 저장된 `identifierGenerators`는 엔티티에서 사용될 기본 정보인 `EntityMetamodel`에 `IdentifierProperty`(ID에 대한 설정 정보 클래스) 형태로 담긴다. + +원래 String으로 저장되어있던 설정 정보가 `createIdentifierGenerator`에서 클래스 참조로 바뀐다. `IdentifierGenerator`를 상속받아서 구현한 경우, 저 안에서 스캔되어서 들어간다고 생각하면 된다. + +

+`IdentifierGenerator` 코드 보기 + +```java +/** + * The general contract between a class that generates unique + * identifiers and the Session. It is not intended that + * this interface ever be exposed to the application. It is + * intended that users implement this interface to provide + * custom identifier generation strategies.
+ *
+ * Implementors should provide a public default constructor.
+ *
+ * Implementations that accept configuration parameters should + * also implement Configurable. + *
+ * Implementors must be thread-safe + * + * @author Gavin King + * + * @see PersistentIdentifierGenerator + */ +public interface IdentifierGenerator extends Configurable, ExportableProducer { + + String ENTITY_NAME = "entity_name"; + String JPA_ENTITY_NAME = "jpa_entity_name"; + String GENERATOR_NAME = "GENERATOR_NAME"; + + @Override + default void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException { + } + + @Override + default void registerExportables(Database database) { + } + + default void initialize(SqlStringGenerationContext context) { + } + + Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException; + + default boolean supportsJdbcBatchInserts() { + return true; + } +} +``` + +
+ +그 entity metadata는 `EntityPersister`라는 곳에 담겨서 `StatelessSession`에서 `insert`가 호출될때, 같이 generate된다. + +```java +public abstract class AbstractEntityPersister + implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable, + SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { + ... + public IdentifierGenerator getIdentifierGenerator() throws HibernateException { + return entityMetamodel.getIdentifierProperty().getIdentifierGenerator(); + } + ... +} +``` + +```java +// StatelessSessionImpl.java + @Override + public Serializable insert(String entityName, Object entity) { + checkOpen(); + EntityPersister persister = getEntityPersister( entityName, entity ); + Serializable id = persister.getIdentifierGenerator().generate( this, entity ); // 호출되는 부분 + + Object[] state = persister.getPropertyValues( entity ); + if ( persister.isVersioned() ) { + boolean substitute = Versioning.seedVersion( + state, + persister.getVersionProperty(), + persister.getVersionType(), + this + ); + if ( substitute ) { + persister.setPropertyValues( entity, state ); + } + } + if ( id == IdentifierGeneratorHelper.POST_INSERT_INDICATOR ) { + id = persister.insert( state, entity, this ); + } + else { + persister.insert( id, state, entity, this ); + } + persister.setIdentifier( entity, id, this ); + return id; + } +``` diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Cascade.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Cascade.md" new file mode 100644 index 00000000..e0cda61f --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Cascade.md" @@ -0,0 +1,50 @@ +--- +title: 'Cascade' +lastUpdated: '2024-03-02' +--- + +cascade 옵션은 jpa를 사용할때 @OneToMany나 @ManyToOne에 옵션으로 줄 수 있는 값이다. cacade 옵션을 사용하면 부모 엔티티에 상태 변화가 생길 때 그 엔티티와 연관되어있는 엔티티에도 상태 변화를 전이시킬 수 있다. 즉, 자식 엔티티의 생명주기를 관리할 수 있다. + +## cascade 타입의 종류 + +### PERSIST + +- 부모 엔티티를 저장하면 자식 엔티티까지 함께 저장한다. + +- 다시말해, 명시적으로 부모엔티티와 연관관계를 가진 자식 엔티티 영속화시킬때 따로 명시할 필요 없이 `부모.자식 = 자식 인스턴스` 과 같이 적으면 자식 엔티티도 데이터베이스에 자동으로 저장된다. + +### MERGE + +- 데이터베이스에서 가져온 부모 객체를 통해 자식 엔티티의 정보를 수정하여 병합했을때 변경 결과가 자식 엔티티에 반영된다. + +### REMOVE + +- 부모 엔티티를 삭제하면 그 엔티티를 참조하고 있는 자식 엔티티(해당 부모 엔티티를 Foreign Key로 가지고있는 엔티티)도 함께 삭제된다. + +### REFRESH + +- 부모 엔티티를 `refresh()` (DB에 저장되어있는 상태로 다시 가져옴) 할때 자식 엔티티도 함께 가져온다. + +### DETACH + +- 부모 엔티티가 `detach()`를 수행하게 되면, 연관된 엔티티도 `detach()` 상태가 되어 변경사항이 반영되지 않는다. + +### ALL + +- 위에 있는 상태 전이가 모두 적용된다. + +--- + +다음과 같이 연관관계 매핑 어노테이션에 속성으로 지정해주면 된다. + +```java +public class Card { + ... + @OneToMany(mappedBy = "card", cascade = CascadeType.REMOVE) + private List userCards; + ... +} +``` + +
연관 개념:
+엔티티의 생명주기 https://gmlwjd9405.github.io/2019/08/08/jpa-entity-lifecycle.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/GenerateValue\342\200\205Column\354\227\220\342\200\205\352\260\222\354\235\204\342\200\205\353\204\243\353\212\224\353\213\244\353\251\264.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/GenerateValue\342\200\205Column\354\227\220\342\200\205\352\260\222\354\235\204\342\200\205\353\204\243\353\212\224\353\213\244\353\251\264.md" new file mode 100644 index 00000000..c590952e --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/GenerateValue\342\200\205Column\354\227\220\342\200\205\352\260\222\354\235\204\342\200\205\353\204\243\353\212\224\353\213\244\353\251\264.md" @@ -0,0 +1,103 @@ +--- +title: 'GenerateValue Column에 값을 넣는다면' +lastUpdated: '2024-03-02' +--- + +Hexagonal architecture를 사용해서 구현하다 문제가 생겼다. + +해당 [프로젝트](https://github.com/team-aliens)에선 기본 PK로 UUID를 사용했고, 새로 생성한 도메인 모델은 id 값을 `UUID(0, 0)`으로 설정해서 사용했다. (여기서 중요하진 않지만 생성 전략은 `IdentifierGenerator`로 TimeBasedUUID를 생성할 수 있도록 직접 정의된 상태였다.) + +`UUID(0, 0)`으로 설정해주다 보니 기존에는 새 insert문을 날려야하는 경우 `isNew`가 Persistable 기본 전략에 따라 false로 들어갔고, `merge`가 호출되었다. [여기](./Persistable.md)에서 알아봤던 것 처럼 영속성 컨텍스트에 등록되지 않은 객체에 대해 `merge`를 호출하면, 어떤 `update`문(혹은 insert문)을 날려야하는지를 알아야 하기 때문에 `select` 쿼리를 한번 날리게 되는데, 이것을 막아주기 위해 BaseEntity에 `Persistable`을 상속받고 변수를 정의해서 새로운 객체인지 여부를 표시하도록 했다. + +즉, 새 객체 생성시 merge가 아닌 persist를 호출하려고 했다. + +```kotlin +@MappedSuperclass +abstract class BaseUUIDEntity( + + @get:JvmName("getIdValue") + @Id + @GeneratedValue(generator = "timeBasedUUID") + @GenericGenerator(name = "timeBasedUUID", strategy = "team.aliens.dms.persistence.TimeBasedUUIDGenerator") + @Column(columnDefinition = "BINARY(16)", nullable = false) + val id: UUID? + +): Persistable { + + @Transient + private var isNew = true + + override fun getId() = id + override fun isNew() = isNew + + @PrePersist + @PostLoad + fun markNotNew() { + isNew = false + } +} +``` + +그랬는데 jpa crudRepository를 호출하는 경우 아래와 같은 에러가 발생했다. + +``` +org.hibernate.PersistentObjectException: detached entity passed to persist: team.aliens.dms.persistence.studyroom.entity.StudyRoomJpaEntity +``` + +detach 상태인데 persist를 호출했기 때문에 에러가 난다고 한다. 실제로 `org.hibernate.event.internal.DefaultPersistEventListener`의 `onPersist` 메서드에는 이러한 내용이 있었다. + +```java +// DefaultPersistEventListener.java + public void onPersist(PersistEvent event, Map createCache) throws HibernateException { + final SessionImplementor source = event.getSession(); + final Object object = event.getObject(); + + ... + + final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity ); + EntityState entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true ); + if ( entityState == EntityState.DETACHED ) { + // JPA 2, in its version of a "foreign generated", allows the id attribute value + // to be manually set by the user, even though this manual value is irrelevant. + // The issue is that this causes problems with the Hibernate unsaved-value strategy + // which comes into play here in determining detached/transient state. + // + // Detect if we have this situation and if so null out the id value and calculate the + // entity state again. + + // NOTE: entityEntry must be null to get here, so we cannot use any of its values + EntityPersister persister = source.getFactory().getEntityPersister( entityName ); + if ( ForeignGenerator.class.isInstance( persister.getIdentifierGenerator() ) ) { + if ( LOG.isDebugEnabled() && persister.getIdentifier( entity, source ) != null ) { + LOG.debug( "Resetting entity id attribute to null for foreign generator" ); + } + persister.setIdentifier( entity, null, source ); + entityState = EntityState.getEntityState( entity, entityName, entityEntry, source, true ); + } + } + ... + } +``` + +해석해보자면, id 속성값이 유저로부터 임의로 설정되는 경우 detached, transient 상태를 결정하는 Hibernate unsaved-value 전략에 문제가 발생하기 때문에 id 값을 무효화하고 엔티티 상태를 다시 계산한다는 것이다. + +그리고 여기서 계산한 state가 `detech`인 경우에는 예외를 던진다. + +```java + ... + switch ( entityState ) { + case DETACHED: { + throw new PersistentObjectException( + "detached entity passed to persist: " + + EventUtil.getLoggableName( event.getEntityName(), entity ) + ); + } + ... + } +``` + + +--- +참고 +- https://stackoverflow.com/questions/73136683/detached-entity-passed-to-persist-when-setting-id-explicitly-and-usage-of-gen +- https://www.inflearn.com/questions/121326 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Hibernate\342\200\205dialect.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Hibernate\342\200\205dialect.md" new file mode 100644 index 00000000..ca583abd --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Hibernate\342\200\205dialect.md" @@ -0,0 +1,27 @@ +--- +title: 'Hibernate dialect' +lastUpdated: '2024-03-02' +--- + +- 하이버네이트가 데이터베이스와 통신을 하기 위해 사용하는 언어를 Dialect라고 한다. +- 모든 데이터베이스에는 각자의 고유한 SQL언어가 있는데, 관계형 데이터베이스끼리 형태나 문법이 어느정도 비슷하긴 하지만, 완전히 똑같지는 않다. + - 예를 들어 Oracle 쿼리 구문과 MySQL 쿼리구문은 다르다. +- 하지만, 하이버네이트는 한 데이터베이스관리시스템(DBMS)에 국한되지않고, 다양한 DBMS에 사용 가능하다. + - 즉 내부적으로 각자 다른 방법으로 처리하고 있는 것이다. + - 그렇기 때문에특정 벤더(DBMS)에 종속적이지 않고, 얼마든지 대체가능하다. +- JPA에서는 아래와 같이 Dialect라는 추상화된 언어 클래스를 제공하고 각 벤더에 맞는 구현체를 제공하고 있다. + +![image](https://user-images.githubusercontent.com/81006587/209959785-be3c3467-c9bb-4bb2-ba94-c4a2005cd86d.png) + +```yml +spring: + datasource: + url: jdbc:mysql://localhost:3306/test + username: username + password: password + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect # 여기 +``` + +yml 파일에서 설정하는 저 코드가 dialect를 설정하는 부분이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Hibernate\342\200\205\354\277\274\353\246\254\354\213\244\355\226\211\354\210\234\354\204\234.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Hibernate\342\200\205\354\277\274\353\246\254\354\213\244\355\226\211\354\210\234\354\204\234.md" new file mode 100644 index 00000000..b8c6c0c8 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Hibernate\342\200\205\354\277\274\353\246\254\354\213\244\355\226\211\354\210\234\354\204\234.md" @@ -0,0 +1,34 @@ +--- +title: 'Hibernate 쿼리실행순서' +lastUpdated: '2024-03-02' +--- + +1. OrphanRemovalAction +2. AbstractEntityInsertAction +3. EntityUpdateAction +4. QueuedOperationCollectionAction +5. CollectionRemoveAction +6. CollectionUpdateAction +7. CollectionRecreateAction +8. EntityDeleteAction + +## performExecutions + +```java +protected void performExecutions(EventSource session) +``` + +Execute all SQL (and second-level cache updates) in a special order so that foreign-key constraints cannot be violated: +1. Inserts, in the order they were performed +2. Updates +3. Deletion of collection elements +4. Insertion of collection elements +5. Deletes, in the order they were performed + +> delete 가 늦게 실행되는 이유(entity 일 때) +foreign key 제약 조건에 영향을 받을 우려가 있기때문에 이 상황을 고려하여 늦게 실행된다. + +> insert 가 일찍 실행되는 이유 +auto increment 일 수 있기때문에 이 상황을 고려하여 가장 먼저 실행된다. + +https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/event/internal/AbstractFlushingEventListener.html diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Id\353\241\234\342\200\205\354\227\260\352\264\200\352\264\200\352\263\204\342\200\205\352\260\235\354\262\264\342\200\205\354\240\200\354\236\245.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Id\353\241\234\342\200\205\354\227\260\352\264\200\352\264\200\352\263\204\342\200\205\352\260\235\354\262\264\342\200\205\354\240\200\354\236\245.md" new file mode 100644 index 00000000..778701b5 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Id\353\241\234\342\200\205\354\227\260\352\264\200\352\264\200\352\263\204\342\200\205\352\260\235\354\262\264\342\200\205\354\240\200\354\236\245.md" @@ -0,0 +1,86 @@ +--- +title: 'Id로 연관관계 객체 저장' +lastUpdated: '2022-12-29' +--- +다대일 연관관계를 맺고 있는 Member와 Team이라는 엔티티가 있다고 하자. + +```java +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id") + private Team team; + + public Member(Team team) { + this.team = team; + } +} +``` + +```java +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Team { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; +} +``` + +이때, Member 엔티티를 조회하기 위해선 Team 엔티티를 가져와서 save 해줘야한다. + +(Team을 조회하는 쿼리가 하나 필요하다.) + +```java +Team team = teamRepository.findById(teamId).get(); +Member member = new Member(team); +memberRepository.save(member); +``` + +객체지향적으로는 객체들끼리 연관관계를 맺는 위와 같은 코드가 좋다. + +하지만 여러 Member를 저장해야하는 상황이라면 각각의 Team을 select하는 것이 부담이 될 수 있다. save() 작업이 1,000건, 10,000 건 이상 이뤄지는 경우라면 select 쿼리도 1,000건, 10,000건 만큼 더 나가는 것이다. + +하지만 Member를 생성할때 Team 엔티티의 정보를 모두 가져와 주입하는 것이 아니라, Team의 Id만 넣어서 저장한다면 이러한 문제를 해결할 수 있을 것이다. + +Member 엔티티를 다음과 같이 수정하면 된다. + +```java +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(targetEntity = Team.class, fetch = FetchType.LAZY) + @JoinColumn(name = "team_id", insertable = false, updatable = false) + private Team team; + + @Column(name = "team_id") + private Long teamId; + + public Member(Long teamId) { + this.teamId = teamId; + } +} +``` + +Member엔티티와 연관관계가 맺어져있는 Team 필드를 주입하거나 수정할 수 없게 하고, 조인한 컬럼과 같은 이름으로 또 하나의 필드 변수를 만들어줬다. + +이렇게 하면 Long 타입의 id를 주입하는 것만으로 team과의 연관관계를 만들 수 있다. + +Member에서 Team을 조회하는 경우(`Member.getTeam()`) 하이버네이트가 team_id컬럼을 통해 엔티티를 알아서 조회해주기 때문에 Member를 가져와서 사용할때는 기존과 같이 사용할 수 있다. 하지만 Team 엔티티를 Member에 직접 주입할 수 없게 되고, 데이터 무결성이 깨질 수 있기 때문에 (해당 TeamId를 가진 엔티티가 존재하지 않는 경우) 해당 부분을 조심하면서 사용해야한다. + +참고: https://stackoverflow.com/questions/27930449/jpa-many-to-one-relation-need-to-save-only-id \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JDBC\342\200\205Object\342\200\205Mapping\342\200\205Fundamentalsentity.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JDBC\342\200\205Object\342\200\205Mapping\342\200\205Fundamentalsentity.md" new file mode 100644 index 00000000..b35e1584 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JDBC\342\200\205Object\342\200\205Mapping\342\200\205Fundamentalsentity.md" @@ -0,0 +1,63 @@ +--- +title: 'JDBC Object Mapping Fundamentalsentity' +lastUpdated: '2024-03-02' +--- + +스프링 데이터가 객체를 매핑할 떄 담당하는 핵심 역할은 도메인 객체 인스턴스를 생성하고, 여기에 저장소 네이티브 데이터 구조를 매핑하는 일이다. 여기에는 두 가지의 핵심적인 단계가 있다. + +1. 노출된 생성자중 하나로 인스턴스 생성하기. +2. 나머지 프로퍼티 채우기. + +## 엔티티(Entity) 생성 + +Spring Data JDBC에서 엔티티 객체를 생성하는 알고리즘은 3가지이다. + +1. 기본 생성자가 있는 경우 기본생성자를 사용한다. +2. 다른 생성자가 존재해도 무시하고 우선적으로 기본생성자를 사용한다. +3. 매개변수가 존재하는 생성자가 하나만 존재한다면 해당 생성자를 사용한다. + +매개변수가 존재하는 생성자가 여러개 있으면 `@PersistenceConstructor` 어노테이션이 적용된 생성자를 사용한다. 만약 `@PersistenceConstructor`가 존재하지 않고, 기본 생성자가 없다면 `org.springframework.data.mapping.model.MappingInstantiationException`을 발생시킨다. + +여기서 기본 생성자를 private으로 선언해도 정상적으로 잘 작동한다. + +## 엔티티 내부 값 주입 과정 + +Spring Data JDBC는 생성자로 인해 채워지지 않은 필드들에 대해 자동으로 생성된 프로퍼티 접근자(Property Accessor)가 다음과 같은 순서로 멤버변수의 값을 채워넣는다. + +1. 엔티티의 식별자를 주입한다. +2. 엔티티의 식별자를 이용해 참조중인 객체에 대한 값을 주입한다. +3. transient 로 선언된 필드가 아닌 멤버변수에 대한 값을 주입한다. + +엔티티 맴버변수는 다음과 같은 방식으로 주입된다. + +### 1. 멤버변수에 final 예약어가 있다면(immutable) wither 메서드를 이용하여 값을 주입한다. + +```java + // wither method + public Sample withId(Long id) { + // 내부적으로 기존 생성자를 이용하며 imuttable한 값을 매개변수로 가진다. + return new Sample(id, this.sampleName); + } +``` + +해당 wither메서드가 존재하지 않다면 `java.lang.UnsupportedOperationException: Cannot set immutable property ...` 를 발생시킨다. + +### 2. 해당 멤버변수의 @AccessType이 PROPERTY라면 setter를 사용하여 주입한다. + +setter가 존재하지 않으면 java.lang.IllegalArgumentException: No setter available for persistent property이 발생한다. + +나머지 경우에는 기본적으로는 직접 멤버변수에 주입한다. (Field 주입) + +## witherMethod + +withMethod를 정의하는 경우는 final예약어가 있는 immutable한 멤버변수가 존재 할 때이다. 이때 주의해야할 점이 있다. + +만약 immutable한 멤버변수가 n개라면 witherMethod또한 n개 작성해 주어야 합니다. 단순히 witherMethod 한개의 매개변수에 여러개의 immutable 필드를 주입한다면 `java.lang.UnsupportedOperationException: Cannot set immutable property`를 보게 된다. + +또한 witherMethod의 이름을 잘못 작성해서는 안된다. 멤버변수의 이름이 createdAt 이라면 witherMethod의 이름은 멤버변수의 이름을 뒤에 붙힌 withCreatedAt으로 작성해야 한다. 테이블 컬럼 이름이 아닌 멤버 변수명을 따라서 작성해야한다. + +--- + +참고 + +- https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#mapping.fundamentals \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPA.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPA.md" new file mode 100644 index 00000000..bd5edb35 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPA.md" @@ -0,0 +1,33 @@ +--- +title: 'JPA' +lastUpdated: '2024-03-02' +--- + +JPA는 자바의 ORM(Object-Relational Mapping) 기술의 표준으로, 객체와 테이블을 매핑해 패러다임의 불일치를 개발자 대신 해결하는 기술이다. 객체는 객체대로 생성하고, 데이터베이스는 데이터베이스에 맞게 설계할 수 있도록 해준다. + +## Repository interface +JPA는 Repository라는 interface를 통해 지속성 저장소에 대한 데이터 액세스 계층을 구현하는 데 필요한 상용구 코드의 양을 크게 줄일 수 있도록 해준다. Repository를 상속받은 하위 인터페이스의 종류로는 CrudRepository, PagingAndSortingRepository, JpaRepository 등이 있고 각 인터페이스가 적절한 기본 메서드를 지정하고 있다. + +```java +//ex) CrudRepository +public interface CrudRepository extends Repository { + + S save(S entity); + + Optional findById(ID primaryKey); + + Iterable findAll(); + + long count(); + + void delete(T entity); + + boolean existsById(ID primaryKey); + + // … more functionality omitted. +} +``` + +이 Repository interface를 상속받은 interface에서 JPQL(`@Query ` 어노테이션)을 사용해 DAO(Data Access Object)의 메서드를 직접 선언할 수도 있고, Spring Data 리포지토리 인프라에 구축된 쿼리 빌더 메커니즘을 통해 쿼리를 생성할 수도 있다. + +JPA를 사용하여 복잡한 동적 쿼리를 생성할 떄 타입 안정성(type-safe)을 지킬 수 있도록 하는 Querydsl이라는 프레임워크도 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPQL/FetchJoin.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPQL/FetchJoin.md" new file mode 100644 index 00000000..2869de82 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPQL/FetchJoin.md" @@ -0,0 +1,55 @@ +--- +title: 'FetchJoin' +lastUpdated: '2024-03-02' +--- + +fetch join은 JPQL에서 성능 최적화를 위해 제공하는 기능이다. 페치 조인은 연관된 엔티티나 컬렉션을 한 번에 같이 조회해준다. + +일반적으로 연관관계가 맺어진 엔티티를 조회할때, N+1문제를 해결하기 위해서 `FetchType.LAZY`를 사용하는 경우가 있다. 하지만 두 엔티티를 모두 조회해야하는 경우엔 두개의 쿼리를 날려야하기 때문에 N+1문제가 동일하게 발생한다. 이럴 때 fetch join을 사용하면 하나의 쿼리로 두 엔티티를 한 번에 조회할 수 있다. + +1:N 조인에서 fetch join을 사용하면 결과의 갯수가 늘어날 수 있다.('1'쪽에서 조회해야하는 엔티티의 총 갯수가 아닌, '1'쪽의 엔티티 갯수만큼의 결과가 조회된다.) 이 경우 JPQL의 DISTINCT를 사용해서 결과의 갯수를 줄일 수 있다. + +## fetch join과 일반 join의 차이 + +```sql +select t +from Team t join t.members m +where t.name='팀A' +``` + +위와 같은 JPQL 쿼리를 실행할 경우, Member는 조회되지 않고 Team의 정보만 조회된다. (사실상 join하는 의미가 없다.) + +```sql +select t +from Team t join fetch t.members m +where t.name='팀A' +``` + +fetch join으로 바꿔주면 출력되는건 Team의 정보 뿐이지만, 영속성 컨텍스트에는 member의 정보까지 전부 저장된다. fetch join은 일반 join과 다르게 연관된 엔티티를 함께 조회한다. (FetchType.LAZY를 설정한 경우엔 명시적으로 join한 엔티티만 조회한다.) + +fetch join은 객체 그래프를 전부 조회하는 개념이라고 생각할 수 있다. + +## fetch join의 특징과 한계 + +### 1. fetch join은 전체를 끌고와야한다. +fetch join 대상에는 별칭을 줄 수 없고, 별칭으로 조건을 거는 것도 불가능하다. fetch join은 하나의 쿼리를 연관된 엔티티를 모두 가져올 때 사용해야 한다. WHERE절 등을 통해서 필터링한다는 것은 객체 그래프 전체를 가져온다는 fetch join의 의의와 반대되는 행위이다. 연관된 객체의 일부 정보만 필요하다면 별도의 쿼리를 실행해야한다. + +### 2. 둘 이상의 컬렉션은 fetch join 할 수 없다. +위에서 말했다시피, 1:N 조인에서 fetch join을 사용하면 결과의 갯수가 늘어날 수 있다. 둘 이상의 컬렉션을 fetch join 한다면 1:N:M 관계가 되는데, 이렇게 되면 더 많은 양의 데이터가 반환되거나 데이터가 꼬일 수 있다. + +### 3. 1 or N:M 관계에서 fetch join 결과로 페이징 API를 사용할 수 없다. +1:1, N:1 같은 단일 값 연관 필드들은 페이징이 가능하다. 왜냐하면 하나의 데이터에 연관 필드가 딱 하나씩 대입되기 때문이다. 하지만 N:M이나 1:N 관계에서는 페이징을 사용한 경우 잘못된 데이터가 반환될 수 있다. + +이런 상황이 있다고 가정해보자. + + 1. Team과 Member가 1:n 관계이고, Team 데이터가 3개, Member 데이터가 10개 있다. + 2. Team을 기준으로 Member를 가져오면 총 10개의 행이 생긴다. + +이때, Team을 기준으로 페이징을 한다면 결과값의 갯수가 일정하지 않기 때문에 페이징을 하는 의미가 없다. + +그리고 행을 기준으로 페이징을 한다면 한 Team에 속해있는 유저가 페이지에 반만 포함되는 경우 잘못된 데이터가 출력된다. + +그렇기 때문에 fetch join 결과로 페이징을 하고 싶으면 역으로 뒤집어서 조회하거나(1:n -> n:1) @BatchSize를 사용해서 총 결과값의 수를 제한하는 방법을 사용할 수 있다. + +## 정리 +fetch join은 객체 그래프를 유지하며 엔티티를 조회해야 할 때 사용할 수 있는 최적화 방법이다. N+1문제를 해결하기 위해 자주 쓰이지만, 모든 것을 해결할 수는 없다. 상황에 따라 적절히 사용하면 좋을 것 같다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPQL/\352\262\275\353\241\234\355\221\234\355\230\204\354\213\235.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPQL/\352\262\275\353\241\234\355\221\234\355\230\204\354\213\235.md" new file mode 100644 index 00000000..d53836de --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/JPQL/\352\262\275\353\241\234\355\221\234\355\230\204\354\213\235.md" @@ -0,0 +1,22 @@ +--- +title: '경로표현식' +lastUpdated: '2024-03-02' +--- + +자바에서 인스턴스화된 객체로 클래스의 변수나 메서드에 접근할 때 .을 이용해 접근하는 것 처럼, jpql 쿼리에서 .으로 객체의 값에 점을 찍어 객체 그래프를 탐색하는 것을 경로 표현식이라고 한다. + +## 경로 유형별 동작 + +### 상태 필드(State Field) +상태 필드는 일반적인 값을 저장하기 위한 필드이다. 즉, int, varchar등의 자료형을 가지는 기본적인 데이터를 저장한다. 상태필드는 더 나아갈 경로가 존재하지 않으므로 jpql에서 부가적인 조인 등의 탐색 또한 일어나지 않는다. + +### 연관 필드(Association field) +연관 필드는 연관관계가 맺어진 외래 테이블의 값을 위한 필드이다. 단일 값 연관 필드와 컬렉션 값 연관 필드로 나뉜다. + +- ### 단일 값 연관 필드 +

@ManyToOne, @OneToOne처럼 엔티티를 대상으로 하는 필드이다.

+

jpql에서 단일 값 연관 필드를 경로로 표현하여 조회하게 되면, 묵시적 내부 조인이 실행된다. 즉, 조인 쿼리를 직접 쓰지 않더라도, 단일 값 연관 필드를 조회하면 조인 쿼리가 날아간다. 조인한 엔티티에서 다른 필드를 더 탐색할 수도 있다.

+ +- ### 컬렉션 값 연관 필드 +

@OneToMany, @ManyToMany처럼 엔티티들의 컬렉션을 대상으로 하는 필드이다. 단일 값 연관 필드와 마찬가지로 묵시적 내부 조인이 발생하지만, 엔티티의 정보를 더 탐색하는 것은 불가능하다.

+

하지만 FROM 절에서 명시적 조인을 통해 별칭을 얻어 별칭을 통해 탐색하는 것은 가능하다.

\ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/N+1\342\200\205\353\254\270\354\240\234.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/N+1\342\200\205\353\254\270\354\240\234.md" new file mode 100644 index 00000000..a461dd07 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/N+1\342\200\205\353\254\270\354\240\234.md" @@ -0,0 +1,87 @@ +--- +title: 'N+1 문제' +lastUpdated: '2024-03-02' +--- + +어떤 엔티티를 조회할때, 그 엔티티의 하위 엔티티들을 가져오기 위해 별도의 쿼리가 실행되는 것을 N+1 문제라고 한다. N+1 문제는 DB 조회 성능에 악영향을 끼치기 때문에 JPA 사용시 꼭 주의해야 한다. + +예를 들어 아래와 같은 두 엔티티가 있다고 해보자. +```java +@Entity +public class User { + + @Id + @Column(name = "user_id") + private Long id; + + @OneToMany(mappedBy = "user") + private List orders = new ArrayList<>(); +} + +@Entity +public class Order { + + @Id + @Column(name = "order_id") + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; +} +``` + +User 테이블에 총 100개의 컬럼이 있을때, User를 전체조회하면 총 몇개의 쿼리가 생길까? + +101개의 쿼리가 생긴다. 왜냐하면 User를 조회하는 쿼리 하나가 실행된 후에, (`select * from User`) 각 User와 연관된 Order를 조화하는 쿼리(`select * from Order where user_id = ?`)가 User의 수만큼 실행되기 때문이다. + +```java +public class Order { + ... + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; +} +``` + +그렇다면, 아래처럼 FetchType을 LAZY로 설정했을 때는 어떻게 될까? + +이렇게 하면 User를 조회했을때 Order가 함께 조회되지 않기 때문에 하나의 쿼리만 실행된다. + +하지만 N+1 문제가 해결된 것은 아니다. User만 조회할 때는 쿼리가 하나만 실행되지만, Order의 정보를 조회하기 위해선 별도의 쿼리를 날려야만 하기 때문이다. 그렇기 때문에 N+1 문제를 해결하려면 User조회 쿼리와 Order조회 쿼리를 하나로 만들기 위한 다른 방법을 사용해야한다. + +--- + +## 해결법 + +### 1. 페치 조인(Fetch Join) + +가장 일반적인 방법이다. 페치 조인은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하도록 하는 JPQL의 문법이다. Order 테이블을 조인하여 하나의 쿼리로 정보를 가져오기 떄문에 N+1 문제가 해결된다. + +### 2. @BatchSize + +하이버네이트가 제공하는 org.hibernate.annotations.BatchSize 어노테이션을 사용하면 연관된 엔티티를 조회할 때 지정된 size 만큼 SQL의 IN절을 사용해서 조회된다. +```sql +SELECT * FROM +order WHERE user_id IN( + ?, ?, ?, ?, ? +) +``` +IN절에 들어갈 수 있는 최대 인자 갯수를 따로 설정할 수도 있다. 최대 인자 수를 넘으면 여러개의 쿼리로 나뉘어서 실행된다. +```java +@BatchSize(size=10) +``` + +### 3. @Fetch(FetchMode.SUBSELECT) +연관된 데이터를 조회할 때 서브쿼리를 사용해서 조회된다. +```sql +SELECT * FROM user; +SELECT * FROM order + WHERE user IN( + SELECT user_id + FROM user + ) +``` +페치조인과 다르게 두개의 쿼리가 실행된다. + +통상적으로는 지연 로딩을 설정하고 페치조인을 사용하는 것이 권장된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/OrphanRemoval.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/OrphanRemoval.md" new file mode 100644 index 00000000..e7fdee7b --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/OrphanRemoval.md" @@ -0,0 +1,75 @@ +--- +title: 'OrphanRemoval' +lastUpdated: '2024-03-02' +--- + +- cascade 옵션을 사용하면 부모 엔티티에 상태 변화가 생길 때 그 엔티티와 연관되어있는 엔티티에도 상태 변화를 전이시킬 수 있다. +- 그러나 cascade 옵션을 사용한다고 해서 부모 엔티티에서 자식 엔티티의 생명주기를 완전히 통제할 수 있는 것은 아니다. + +- cascade의 `REMOVE` 옵션은, 부모 엔티티를 삭제했을때 그 엔티티를 참조하고 있는 자식 엔티티(해당 부모 엔티티를 Foreign Key로 가지고있는 엔티티)도 함께 삭제해준다. + - 하지만 부모 엔티티에서 자식 엔티티를 삭제했을때에는 그 참조를 끊어줄 뿐, 그 자식 엔티티를 같이 삭제해주진 않는다. (심지어 FK에 NotNull 제약조건을 걸어놨으면 아무 변화도 생기지 않는다) + +- 그럴 때 사용할 수 있는 것이 OrphanRemoval이다. + - OrphanRemoval은 말 그대로 고아 객체를 삭제해준다는 뜻인데, 이걸 설정에 추가해주면 부모 엔티티에서 자식 엔티티를 삭제하여 참조를 끊었을때, 고아 객체가 된 자식 객체에 대한 DELETE 쿼리를 날려준다. + +- `cascadeType.ALL`과 orphanRemoval를 모두 적용하면 부모 엔티티에서 자식 엔티티의 생명주기를 전부 통제할 수 있게 된다. 부모 엔티티에 자식 엔티티를 추가하면 새 엔티티가 저장되고, 부모에서 자식 엔티티와의 연관관계를 끊으면 그 자식 엔티티는 삭제되니까 말이다. + +- 부모 엔티티에서 자신을 참조하는 엔티티에 대한 강력한 통제를 할 수 있는 설정이기 때문에 양쪽 다 FK를 가지고 있는 연관관계거나 자식 엔티티가 다른 엔티티를 참조하고 있을 때에는 큰 문제가 생길 수 있다. (이건 cascade 설정도 마찬가지이다.) + +- 그러므로 cascade와 orphanRemoval은 부모 엔티티가 자식 엔티티를 **완전히 개인적으로 소유하는 경우**에 사용하는 것이 좋다. + +--- + +

나의 경우엔 유저가 소유하는 엔티티를 유저 클래스에서 관리하기 위해 orphanRemoval을 처음으로 사용하게 되었다.

+ +자세히 말하자면 DB에 고정적으로 저장되어있는 카드가 있어서 그 카드를 유저가 여러개 가질 수 있는 구조였다. 그 두 테이블을 다대다로 연결하기 위해서 UserCard라는 중간 테이블을 만들었는데, 여기서 User(부모)와 UserCard(자식)에 `cascadeType.ALL`과 `orphanRemoval`를 적용했다. + +```java +//User와 UserCard 1:N관계로 매핑하고, 그 카드를 지우는 메서드를 User 클래스 내부에 구현했다. +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class User { + ... + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List userCardList; + + ... + public void removeCard(Card card, int cardCount) { + + this.cardCount.removeCount(card.getGrade(), cardCount); + + List userCardListToDelete = this.userCardList + .stream() + .filter(o -> o.getCard() == card) + .limit(cardCount) + .collect(Collectors.toList()); + + for (UserCard userCard : userCardListToDelete) { + this.userCardList.remove(userCard); + } + } + ... +} +``` + +- 더 자세히 말하자면, 프로젝트의 요구사항에 따라서 User의 Card를 추가하거나 삭제하는 동작이 필요했는데, 그럴 때 서비스 클래스에서 UserCardRepository를 상속받아서 삭제할 UserCard를 조회하여 직접 UserCard를 삭제하는 것 보다는 User에서 userCardList 필드에 접근해서 `.remove()`하는 것이 유저 객체의 캡슐화를 유지하기 위해 더 나은 방법이라고 생각했기 때문에 User 클래스에 내부 메서드를 만들어 UserCard를 삭제하고, 추가할 수 있도록 하고싶었다. + +- 그렇게 구현하기 위해선 cascade와 orphanRemove을 설정하여 영속성 컨텍스트에 저장되어있는 부모 엔티티에서 자식 엔티티에 접근하여 데이터를 삽입/삭제할 수 있어야 했다. + +- UserCard는 User가 소유하고 있는 Card들의 정보를 담고있으니 UserCard의 리스트를 관리하고 조회하는 것은 유저밖에 없는 상황이다. 그러므로 부모 엔티티에서 자식 엔티티의 생성/삭제를 모두 통제할 수 있도록 설정해도 문제가 생기지 않을 것이라 판단했고, 두 옵션을 활용해 기능을 구현하게 되었다. + +- 그래서 결과적으론 User 엔티티에서 UserCard의 엔티티의 생명주기를 전부 통제할 수 있게 되었고, 원했던 구조대로 코드를 작성할 수 있었다. + +- cascade만 설정해줘도 부모 엔티티에서 자식 엔티티의 삭제까지 전부 관리할 수 있다고 생각했는데, 부모 엔티티에서 자식 엔티티를 삭제한다는 것이 자식 엔티티의 참조를 끊는 것 뿐이라는 사실을 알게되었다. + +- 테이블간의 관계를 맺고, 데이터를 관리하는 것이 생각보다 복잡한 일인 것 같다. 이번 기회를 통해 JPA와 DB에 대해 아는 것이 얼마나 중요한지 깨닫게 되었다. + +

위에서 설명한 코드는 이 링크에서 자세히 볼 수 있다.

+ + +참고:
+ https://www.baeldung.com/jpa-cascade-remove-vs-orphanremoval
+https://velog.io/@banjjoknim/JPA에서-Cascade-orphanRemoval을-사용할-때-주의해야할-점 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Persistable.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Persistable.md" new file mode 100644 index 00000000..a9bc4451 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/Persistable.md" @@ -0,0 +1,97 @@ +--- +title: 'Persistable' +lastUpdated: '2024-03-02' +--- + +`CrudRepository`의 기본 구현인 `SimpleJpaRepositoryy`의 save 메서드는 이렇게 구현되어있다. + +```java +// SimpleJpaRepository의 save method + @Transactional + @Override + public S save(S entity) { + + Assert.notNull(entity, "Entity must not be null."); + + if (entityInformation.isNew(entity)) { + em.persist(entity); + return entity; + } else { + return em.merge(entity); + } + } +``` + +new인 경우에는 insert 쿼리를 날리고, 그렇지 않은 경우에는 update 쿼리를 날린다. R2DBC도 똑같다. + +```java +// SimpleR2dbcRepository의 save method + @Override + @Transactional + public Mono save(S objectToSave) { + + Assert.notNull(objectToSave, "Object to save must not be null!"); + + if (this.entity.isNew(objectToSave)) { + return this.entityOperations.insert(objectToSave); + } + + return this.entityOperations.update(objectToSave); + } +``` + +`AbstractEntityInformation`의 isNew에서는 + +- 타입이 null이거나 +- Number이면서 값이 0 +인 경우 true를 반환하고, + +- primitive 타입이 아니면서 값이 존재하면 +false를 반환하며, primitive 타입 필드면 에러를 던지는 것이 기본 전략이다. + +```java + @Override + public boolean isNew(Object entity) { + + Object value = valueLookup.apply(entity); + + if (value == null) { + return true; + } + + if (valueType != null && !valueType.isPrimitive()) { + return false; + } + + if (value instanceof Number) { + return ((Number) value).longValue() == 0; + } + + throw new IllegalArgumentException( + String.format("Could not determine whether %s is new; Unsupported identifier or version property", entity)); + } +``` + +여기서, ID를 직접 지정하기 위해 자동 생성 전략을 선택하지 않았을 경우엔 insert이전에 select 쿼리가 한번 나가는 것을 확인할 수 있다. `ID` 필드에 값을 세팅해주면 `isNew()`에서 (ID 필드가 null이 아니라) `false`를 반환하고, 그 결과 `merge()`가 호출되기 때문이다. + +변경을 위해선 변경 감지(dirty-checking)를, 저장을 위해선 `persist()`만이 호출되도록 유도해야 실무에서 성능 이슈 등을 경험하지 않을 수 있다. 위와 같이 `merge`가 호출되지 않도록 하려면 enitty에서 `Persistable` 인터페이스를 상속받게 하고, overriding해주는 방법이 있다. + +```java +public interface Persistable { + + /** + * Returns the id of the entity. + * + * @return the id. Can be {@literal null}. + */ + @Nullable + ID getId(); + + /** + * Returns if the {@code Persistable} is new or was persisted already. + * + * @return if {@literal true} the object is new. + */ + boolean isNew(); +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/Paging.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/Paging.md" new file mode 100644 index 00000000..bf465523 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/Paging.md" @@ -0,0 +1,98 @@ +--- +title: 'Paging' +lastUpdated: '2024-03-02' +--- + +Querydsl에서 페이징하는 방법을 알아보자. + +Page는 인터페이스이기 떄문에, Page를 반환하기 위해선 Page를 구현한 구체클래스를 생성해야한다. 그렇기 때문에 아래 코드에선 스프링 데이터에 있는 PageImpl 클래스를 사용하여 return 하도록 한다. + +fetchResults()를 사용하여 total count쿼리와 결과 리스트를 한 코드로 조회하도록 할 수도 있지만, fetchResults()와 fetchCount()가 특정 상황에서 제대로 동작하지 않는 이슈때문에 depercated 되었으므로 따로 count를 조회하여 반환하는 방식을 택했다. + +원하는 컬럼을 Dto로 만들어서 조건에 따라 조회한 후 반환하는 예제 코드이다. + +```java + public Page searchPage(MemberSearchCondition condition, Pageable pageable) { + List results = queryFactory + .select(new QMemberTeamDto( + member.id.as("memberId"), + member.username, + member.age, + team.id.as("teamId"), + team.name.as("teamName") + )) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long count = queryFactory + .select(member.count()) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ) + .fetchOne(); + + return new PageImpl<>(results, pageable, count); + } +``` + +## 최적화 + +PageImpl 대신에 스프링 데이터의 PageableExecutionUtils의 getPage() 메서드를 사용하면 Count 쿼리를 생략해도 되는 경우에 최적화를 해줄 수 있다. 최적화 할 수 있는 경우는 두가지가 있다. + +1. 첫번째 페이지이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때 + ex) 하나의 페이지에 100개의 컨텐츠를 보여주는데, 총 데이터가 100개가 안되는 경우 + +2. 마지막 페이지일 때 (offset + 컨텐츠 사이즈를 더해서 전체 사이즈를 구할 수 있기 때문) + +극적인 성능 개선 효과를 누릴 순 없지만, 일부 상황에서 조금이라도 쿼리를 아끼고 싶은 경우 사용할 수 있는 방법이다. count 조회 쿼리를 즉시 실행하지 않고, JPAQuery 상태로 파라미터에 넣으면 된다. + +```java + public Page searchPage(MemberSearchCondition condition, Pageable pageable) { + List results = queryFactory + .select(new QMemberTeamDto( + member.id.as("memberId"), + member.username, + member.age, + team.id.as("teamId"), + team.name.as("teamName") + )) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(member.count()) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ); + + return PageableExecutionUtils.getPage(results, pageable, countQuery::fetchOne); + } +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/Projection.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/Projection.md" new file mode 100644 index 00000000..99952b8d --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/Projection.md" @@ -0,0 +1,120 @@ +--- +title: 'Projection' +lastUpdated: '2024-03-02' +--- + +Projection은 엔티티를 그냥 그대로 가지고 오지 않고, 필요한 정보만 추출해오는 것을 의미한다. Querydsl 에서는 프로젝션 대상이 하나면 명확한 타입을 지정할 수 있지만 프로젝션 대상이 둘 이상이라면 Tuple 이나 DTO 로 조회해야 한다. + +### 순수 JPA에서의 Projection +``` java + @Test + void projectionWithJpa() { + //given + //when + List results = em.createQuery( + "select new com.study.querydsl.dto.MemberDto(m.username, m.age)" + + "from Member m", MemberDto.class + ) + .getResultList(); + //then + results.forEach(dto -> System.out.println(dto.toString())); + } +``` + +순수 JPA 에서 DTO 를 조회할 때는 new 키워드를 이용한 생성자를 통해서만 가능했고, package 이름을 모두 명시해야해서 불편했다. + +Querydsl에서는 Projections클래스의 메서드(`bean()`, `fields()`, `constructor()`)를 사용하는 방법과, Dto를 Q-Class로 등록하여 사용하는 방법이 있다. + +### Projections + +```java + @Test + public void projectionWithQuerydsl1() { + //given + //when + //Setter 사용해 생성 + List results1 = queryFactory + .select(Projections.bean(MemberDto.class, + member.username, + member.age)) + .from(member) + .fetch(); + + //필드에 직접 접근해 생성 + List results2 = queryFactory + .select(Projections.fields(MemberDto.class, + member.username, + member.age)) + .from(member) + .fetch(); + + //생성자를 사용해 생성 + List results3 = queryFactory + .select(Projections.constructor(MemberDto.class, + member.username, + member.age)) + .from(member) + .fetch(); + //then + } +``` + +Projections클래스에서 Dto형식으로 입력받을 수 있도록 해주는 위의 세 메서드는, 출력 결과는 거의 똑같지만 내부적으로 처리하는 방법이 다르다. + +`bean()`은 Dto 클래스의 기본 생성자와 Setter를 사용해 객체를 생성한다. + +`fields()`은 Dto 클래스의 필드에 리플렉션으로 접근하여 객체를 필드에 주입한다. + +`constructor()`는 생성자를 통해 Dto 객체를 생성한다. + +상황에 따라서 적절히 사용하면 될 것 같다. + +### Q-Class로 등록 + +생성자에 `@QueryProjection`를 붙이면 Querydsl에서 쿼리를 생성할 때 사용할 수 있는 Q-Class를 만들어준다. + +```java +@NoArgsConstructor +@Getter +public class MemberDto { + private String username; + private int age; + + @QueryProjection + public MemberDto(String username, int age) { + this.username = username; + this.age = age; + } +} +``` + +```java +@Generated("com.querydsl.codegen.DefaultProjectionSerializer") +public class QMemberDto extends ConstructorExpression { + + private static final long serialVersionUID = -1034590129L; + + public QMemberDto(com.querydsl.core.types.Expression username, com.querydsl.core.types.Expression age) { + super(MemberDto.class, new Class[]{String.class, int.class}, username, age); + } + +} +``` + +하지만 이렇게 생성된 Q-Class에는 Entity의 Q-Class와는 달리 static 인스턴스가 없기 때문에 조회를 할 때 새 인스턴스를 만들어서 사용해야한다. + +```java + @Test + public void projectionWithQuerydsl2() { + //given + //when + List results = queryFactory + .select(new QMemberDto(member.username, member.age)) + .from(member) + .fetch(); + //then + results.forEach(System.out::println); + } +``` + +제일 깔끔한 방법이고, type-safe하다는 장점이 있지만 Dto가 Querydsl 에 대한 의존성을 가지기 떄문에 라이브러리를 바꿀 때 많은 코드를 수정해야한다는 단점이 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/QuerydslJpa\354\231\200\342\200\205QClass.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/QuerydslJpa\354\231\200\342\200\205QClass.md" new file mode 100644 index 00000000..bafe4256 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/QuerydslJpa\354\231\200\342\200\205QClass.md" @@ -0,0 +1,37 @@ +--- +title: 'QuerydslJpa와 QClass' +lastUpdated: '2024-03-02' +--- + + + +
+ + Spring Data JPA가 기본적으로 제공해주는 CRUD 메서드 및 쿼리 메서드 기능을 사용하더라도 원하는 조건의 데이터를 수집하기 위해선 JPQL을 작성해야한다.JPQL로 간단한 로직을 작성하는데는 큰 문제가 없지만, 복잡한 로직의 경우 쿼리 문자열이 상당히 길어진다. 또, 정적 쿼리인 경우엔 어플리케이션 로딩 시점에 JPQL 문자열의 오타나 문법적인 오류를 발견할 수 있지만, 그 외는 런타임 시점에서 에러가 발생한다는 문제가 있다. + + 이러한 문제를 해결하기 위한 프레임워크가 바로 QueryDSL-jpa이다. QueryDSL을 사용하면 문자가 아닌 코드로 쿼리를 작성할 수 있기 때문에 컴파일 시점에 문법 오류를 쉽게 확인할 수 있고, 타입 안정성(type-safe)을 지키면서 동적인 쿼리를 편리하게 작성할 수 있다. + + QueryDSL-jpa는 JPQL을 생성해주는 역할을 하고, QueryDSL 자체는 QueryDSL-SQL, QueryDSL-MongoDB 등등 여러 종류로 나뉘어있지만 여기에서 말하는 QueryDSL은 QueryDSL-jpa를 뜻하는 것으로 간주한다. + +--- + +# Q-Class + +Q-Class는 컴파일 시점에 JPAAnnotationProcessor가 `@Entity`, `@Data`같은 Annotation이 붙어있는 클래스를 분석하여 자동으로 생성되는 클래스이다. + +Q 클래스 인스턴스를 사용하는 방법은 총 2가지가 있다. + +#### 1. 직접 인스턴스 생성(alias) + +```java +QMember qMember = new QMember("m"); +``` +별칭(alias)을 지정하면서 새 인스턴스를 생성하여 사용하는 방법이다. 프로그램 실행시 `select m From Member m ~~`와 같이 쿼리가 나간다. 같은 테이블을 Join하거나 서브쿼리를 사용하는 것이 아니라면 잘 사용하지 않는다. + +#### 2. 기본 static 인스턴스 사용 + +```java +QMember qMember = QMember.member; +``` + +Querydsl에서 생성해주는 Q클래스에 기본으로 있는 static 인스턴스를 사용하는 방법이다. diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/QuerydslPredicateExecutor.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/QuerydslPredicateExecutor.md" new file mode 100644 index 00000000..36c76a0d --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/QuerydslPredicateExecutor.md" @@ -0,0 +1,51 @@ +--- +title: 'QuerydslPredicateExecutor' +lastUpdated: '2024-03-02' +--- + +QuerydslPredicateExecutor는 스프링 데이터에서 제공하는 Querydsl 기능이다. (공식 문서) + +```java +public interface QuerydslPredicateExecutor { + + Optional findById(Predicate predicate); + + Iterable findAll(Predicate predicate); + + long count(Predicate predicate); + + boolean exists(Predicate predicate); + + // … more functionality omitted. +} +``` + +Where절의 조건에 해당하는 내용(Predicate)을 파라미터로 받아 해당 조건에 따른 결과를 반환하는 인터페이스이다. + +```java +@Test +void querydslPredicateExecute() { + QMember member = QMember.member; + Iterable result = memberRepository.findAll(member.age.between(10, 40).and(member.username.eq("member1"))); + for (Member m : result) System.out.println("m = " + m); +} +``` + +이 코드를 실행하면 아래와 같은 쿼리가 나간다. + +```sql +select + member1 +from + Member member1 +where + member1.age between ?1 and ?2 + and member1.username = ?3 +``` + +Predicate의 구현체인 BooleanExpression을 사용하면 and나 or 조건을 걸 수 있다. + +하지만 QuerydslPredicateExecutor는 아래와 같은 한계점이 있다. + +1. join이 불가능하다.(묵시적 조인만 가능하다.) +2. 클라이언트가 Querydsl에 의존해야 한다. 서비스 클래스가 Querydsl이라는 구현 기술에 의존해야 한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/fetchResults\352\260\200\342\200\205deprecated\353\220\234\342\200\205\354\235\264\354\234\240.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/fetchResults\352\260\200\342\200\205deprecated\353\220\234\342\200\205\354\235\264\354\234\240.md" new file mode 100644 index 00000000..280cf994 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/fetchResults\352\260\200\342\200\205deprecated\353\220\234\342\200\205\354\235\264\354\234\240.md" @@ -0,0 +1,52 @@ +--- +title: 'fetchResults가 deprecated된 이유' +lastUpdated: '2024-03-02' +--- + +### # 공식 Docs 설명 +``` +fetchResults() : Get the projection in QueryResults form. Make sure to use fetch() instead if you do not rely on the QueryResults.getOffset() or QueryResults.getLimit(), because it will be more performant. Also, count queries cannot be properly generated for all dialects. For example: in JPA count queries can’t be generated for queries that have multiple group by expressions or a having clause. Get the projection in QueryResults form. Use fetch() instead if you do not need the total count of rows in the query result. +``` + +fetchCount와 fetchResult는 querydsl 작성한 select 쿼리를 기반으로 count 쿼리를 만들어낸다. 쿼리를 만들어내는 방식은 단순히 기존 쿼리의 결과를 감싸 쿼리를 생성하는 식이다. + +`SELECT COUNT(*) FROM ().` + +단순한 쿼리에선 위와 같은 방식으로 count 쿼리를 날려도 괜찮지만, 복잡한 쿼리(group by having 절 등을 사용하는 다중그룹 쿼리)에서는 잘 작동하지 않는다고 한다. 그렇기 때문에 Page로 결과를 반환하고 싶다면 별도의 카운트 쿼리를 날린 후 직접 생성자를 통해 생성해야한다. + +```java + public Page searchPage(MemberSearchCondition condition, Pageable pageable) { + List results = queryFactory + .select(new QMemberTeamDto( + member.id.as("memberId"), + member.username, + member.age, + team.id.as("teamId"), + team.name.as("teamName") + )) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(Wildcard.count) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ); + + return PageableExecutionUtils.getPage(results, pageable, countQuery::fetchOne); + } +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\352\270\260\353\263\270\353\254\270\353\262\225.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\352\270\260\353\263\270\353\254\270\353\262\225.md" new file mode 100644 index 00000000..96973b86 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\352\270\260\353\263\270\353\254\270\353\262\225.md" @@ -0,0 +1,624 @@ +--- +title: '기본문법' +lastUpdated: '2024-03-02' +--- + +## 엔티티 조회 + +```java + @Test + public void searchWithQuerydsl() { + //given + Member findMember = queryFactory + .select(member) + .from(member) + .where(member.username.eq("member1")) + .fetchOne(); + //when + //then + assertEquals("member1", findMember.getUsername()); + } +``` + +Querydsl에서는 Q클래스 인스턴스를 사용해 쿼리를 작성할 수 있다. + +기본 인스턴스를 static import하면 편리하게 사용할 수 있다. + +application.yml에 다음 설정을 추가하면 실행되는 JPQL을 볼 수 있다. + +```yml +spring.jpa.properties.hibernate.use_sql_comments: true +``` + +--- + +### 검색 조건 + +```java + @Test + public void search() { + Member findMember = queryFactory + .select(member) + .from(member) + .where(member.username.eq("member1") + .and(member.age.eq(10))) + .fetchOne(); + + assertThat(findMember.getUsername()).isEqualTo("member1"); + } +``` +BooleanExpression을 파라미터로 넣어 Where절에 조건을 줄 수 있다. `and()`,`or()` 메서드를 체인으로 연결하는 것도 가능하다. + + +```java + @Test + public void search() { + Member findMember = queryFactory + .selectFrom(member) + .where(member.username.eq("member1") ,member.age.eq(10)) + .fetchOne(); + + assertThat(findMember.getUsername()).isEqualTo("member1"); + } +``` +where 절에 콤마(,)로 구분하여 여러 파라미터를 넣으면 and 조건이 추가된다.
+또,`select()`와 `from()`값이 같은 경우 `selectFrom()`를 사용할 수 있다. + +Querydsl은 JPQL이 제공하는 모든 검색조건을 제공한다. + +```java + member.username.eq("member1") // username = 'member1' + member.username.ne("member1") //username != 'member1' + member.username.eq("member1").not() // username != 'member1' + + member.username.isNotNull() //username is not null + + member.age.in(10, 20) // age in (10,20) + member.age.notIn(10,20) // age not in (10, 20) + member.age.between(10,30) // between 10, 230 + + member.age.goe(30) // greater or equa,l age >= 30 + member.age.gt(30) // greater, age > 30 + member.age.loe(30) // lower or equal, age <= 30 + member.age.lt(30) // lower, age < 30 + + member.username.like("member%") // like 검색 + member.username.contains("member") // like %member% 검색 + member.username.startswith("member") // like member% 검색 +``` + +--- + +## 결과 조회 + +#### fetch 명령어 + +```java + //List + List fetch = queryFactory + .selectFrom(member) + .fetch(); + + //단 건 + Member fetchOne = queryFactory + .selectFrom(member) + .fetchOne(); + + //처음 한 건 조회 + Member fetchFirst = queryFactory + .selectFrom(member) + .fetchFirst(); +``` + +fetch() 메소드로 리스트를 조회할 수 있다. 데이터가 없으면 빈 리스트가 조회된다. + +fetchOne()은 한건의 결과를 조회한다. 데이터가 없으면 Null이 조회되고, 결과가 둘 이상이면 NonUniqueResultException이 발생한다. + +`fetchFirst()`는 첫번쨰로 조회되는 결과를 조회한다. 결과가 여러개인 경우에도 하나만 반환한다. `limit(1).fetchOne()`과 동일하다. + +--- + +## 정렬 + +#### OrderBy 예시 + +```java + @Test + public void sort() { + //given + em.persist(new Member(null, 100)); + em.persist(new Member("member5", 100)); + em.persist(new Member("member6", 100)); + //when + List results = queryFactory + .selectFrom(member) + .where(member.age.goe(100)) + .orderBy(member.age.desc(), member.username.asc().nullsLast()) + .fetch(); + Member member5 = results.get(0); + Member member6 = results.get(1); + Member memberNull = results.get(2); + //then + assertEquals("member5", member5.getUsername()); + assertEquals("member6", member6.getUsername()); + assertNull(memberNull.getUsername()); + } +``` + +orderBy() 를 통해서 정렬을 시작할 수 있다. `desc()`를 사용하면 내림차순으로, `asc()`를 사용하면 오름차순으로 정렬할 수 있다. + +지정하지 않을 경우 오름차순이 기본 설정이다. + +`nullLast()`나 `nullFirst()`로 null 데이터에 순서를 부여할 수 있다. + +--- + +## 집계 함수 + +count, sum, avg, min, max등의 집계 함수가 들어간 쿼리도 작성할 수 있다. + +#### 집계함수 예시 + +```java + @Test + void aggregation(){ + //given + + //when + List results = queryFactory + .select( + member.count(), + member.age.sum(), + member.age.avg(), + member.age.max(), + member.age.min() + ) + .from(member) + .fetch(); + //then + Tuple tuple = results.get(0); + assertEquals(4, tuple.get(member.count())); + assertEquals(100, tuple.get(member.age.sum())); + assertEquals(25, tuple.get(member.age.avg())); + assertEquals(40, tuple.get(member.age.max())); + assertEquals(10, tuple.get(member.age.min())); + } +``` + +해당 집계 함수의 기능대로 잘 동작하는 것을 볼 수 있다. + + +여기서 추가로, select 에서 내가 원하는 데이터를 타입이 여러개인 경우에는 Tuple 로 결과를 조회할 수 있고, `get()`으로 데이터를 가져올 수 있다. + +하지만 실무에서는 Tuple로 뽑기보다는 DTO를 사용하는 경우가 많다. + +--- + +## GroupBy, Having 절 + +#### GroupBy절 예시 + +```java + @Test + public void group() { + //given + //when + List results = queryFactory + .select(team.name, member.age.avg()) + .from(member) + .join(member.team, team) + .groupBy(team.name) + .fetch(); + + Tuple resultA = results.get(0); + Tuple resultB = results.get(1); + //then + assertEquals("TeamA", resultA.get(team.name)); + assertEquals(15, resultA.get(member.age.avg())); + assertEquals("TeamB", resultB.get(team.name)); + assertEquals(35, resultB.get(member.age.avg())); + } +``` + +#### Having절 예시 + +```java + @Test + public void having() { + //given + //when + List results = queryFactory + .select(team.name, member.age.avg()) + .from(member) + .join(member.team, team) + .groupBy(team.name) + .having(member.age.avg().gt(20)) + .fetch(); + + Tuple teamB = results.get(0); + //then + assertEquals("TeamB", teamB.get(team.name)); + assertEquals(35, teamB.get(member.age.avg())); + } + +``` + +--- + +## 조인과 ON절 + +#### Join절 예시 + +```java + @Test + public void join() { + //given + //when + List results = queryFactory + .selectFrom(member) + .join(member.team, team) + .where(team.name.eq("TeamA")) + .fetch(); + //then + assertThat(results) + .extracting("username") + .containsExactly("member1", "member2"); + } +``` + +`join()`을 사용하게 되면 기본적으로 inner join이 실행된다. + +`leftJoin()`과 `rightJoin()`도 동일한 방법으로 사용하면 된다. + +#### On절 + +```java + @Test + void join_On(){ + //given + //when + List results = queryFactory + .select(member, team) + .from(member) + .leftJoin(member.team, team) + .on(team.name.eq("TeamA")) + .fetch(); + + List teamAList = results + .stream() + .filter(tuple -> tuple.get(team) != null && Objects.equals(tuple.get(team).getName(), "TeamA")) + .map(tuple -> tuple.get(member).getUsername()) + .collect(Collectors.toList()); + + List teamBList = results + .stream() + .filter(tuple -> tuple.get(team) == null) + .map(tuple -> tuple.get(member).getUsername()) + .collect(Collectors.toList()); + //then + assertThat(teamAList).contains("member1", "member2"); + assertThat(teamBList).contains("member3", "member4"); + } +``` + +On절도 사용할 수 있다. + +--- + +## 세타조인 + +세타조인은 연관관계가 없는 테이블을 조인할때 사용할 수 있는 방법 중 하나이다. 세타조인은 교차조인으로 두 테이블의 카테시안 곱을 만든 뒤, =, <, > 등의 비교 연산자로 구성된 조건을 만족하는 튜플을 선택하여 반환하고, 비교연산자가 =인 경우에는 동등 조인(equi join)이라고 부르기도 한다. + +즉, 두 테이블에 튜플을 하나하나, 모든 경우의 수를 다 조합하여 비교한 결과를 반환하는 것이다. 조건을 안넣으면 교차조인(Cross Join)이라고 생각할 수 있다. + +#### 세타조인 예시 + +```java + @Test + public void thetaJoin() { + //given + em.persist(new Member("m1",20)); + em.persist(new Member("m2",20)); + //when + List results = queryFactory + .select(member) + .from(member, team) + .where(member.username.length().lt(team.name.length())) + .fetch(); + //then + Member memberA = results.get(0); + Member memberB = results.get(1); + + assertEquals("m1", memberA.getUsername()); + assertEquals("m1", memberB.getUsername()); + } +``` +```sql + --Cross Join 쿼리가 나가는 모습이다. + select + member0_.member_id as member_i1_0_, + member0_.age as age2_0_, + member0_.team_id as team_id4_0_, + member0_.username as username3_0_ + from + member member0_ cross + join + team team1_ + where + length(member0_.username)페치 조인은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하여 성능을 최적화하는 기능이다. + +#### 페치 조인 예시 + +```java + @Test + public void fetchJoin() { + //given + em.flush(); + em.clear(); + //when + Member findMember = queryFactory + .selectFrom(member) + .join(member.team, team) + .fetchJoin() + .where(member.username.eq("member1")) + .fetchOne(); + //then + boolean isLoaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam()); + assertTrue(isLoaded); + } +``` + +Member에서 Team에 join`FetchType.LAZY`가 설정되어있기 때문에 Member의 정보만 select한 경우에는 Team이 load되지 않은 상태여야 하는데, `fetchJoin()`를 사용하여 연관된 Team을 한번에 가져오도록 설정했기 때문에 테스트가 성공하는 것을 볼 수 있다. +``` +@Entity +public class Member { + ... + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "team_id") + private Team team; + ... +} +``` + +#### 실제 쿼리 + +```sql +-- Fetch Join인 경우 실행되는 쿼리 + select + member0_.member_id as member_i1_0_0_, + team1_.team_id as team_id1_1_1_, + member0_.age as age2_0_0_, + member0_.team_id as team_id4_0_0_, + member0_.username as username3_0_0_, + team1_.member_id as member_i3_1_1_, + team1_.name as name2_1_1_ + from + member member0_ + inner join + team team1_ + on member0_.team_id=team1_.team_id + where + team1_.name=? +``` +```sql +-- Fetch Join가 아닌 경우 실행되는 쿼리 + select + member0_.member_id as member_i1_0_, + member0_.age as age2_0_, + member0_.team_id as team_id4_0_, + member0_.username as username3_0_ + from + member member0_ + inner join + team team1_ + on member0_.team_id=team1_.team_id + where + member0_.username=? +``` + +--- + +## 서브 쿼리 + +서브 쿼리란 SELECT 문 안에 다시 SELECT 문이 기술된 형태의 쿼리로, 안에 있는 쿼리의 결과를 조회한 후에 그 결과로 메인 쿼리가 실행되는 구조를 가지고있다. 단일 SELECT 문으로 조건식을 만들기가 복잡한 경우, 또는 완전히 다른 별개의 테이블에서 값을 조회하여 메인쿼리로 이용하고자 하는 경우에 사용된다. + +서브쿼리는 주로 Where절에서 사용되고, Select절에서도 사용할 수 있다. Querydsl-jpa에서 From절은 지원되지 않는다. + +같은 테이블을 두번 사용하기 때문에 별도의 별칭(alias)이 필요하여 m이라는 앨리어스를 가진 새 인스턴스를 생성해줬다. + +#### 서브 쿼리 예시 + +```java + @Test + public void subQuery() { + //given + QMember qMember = new QMember("m"); + //when + Member findMember = queryFactory + .selectFrom(member) + .where(member.age.eq( + JPAExpressions + .select(qMember.age.max()) + .from(qMember) + )) + .fetchOne(); + //then + assertEquals(40, findMember.getAge()); + } +``` + +age의 max값을 조회하여 해당 나이를 가진 member를 조회하는 쿼리를 생성한다. + +#### In절 + +```java + @Test + public void subQueryIn() { + //given + QMember qMember = new QMember("m"); + //when + List findMembers = queryFactory + .selectFrom(member) + .where(member.age.in( + JPAExpressions + .select(qMember.age) + .from(qMember) + .where(qMember.age.in(10)) + )) + .fetch(); + //then + assertEquals(1, findMembers.size()); + assertEquals(10, findMembers.get(0).getAge()); + } +``` + +In절에도 서브쿼리를 활용할 수 있다. 하지만 성능상 별로 좋지 않기 떄문에 가급적이면 사용하지 않는 것이 좋다. (참고) + +--- + +## Case 문 + +Case문은 조건에 따라서 값을 지정해줄 수 있다. Select, Where, OrderBy 에서 사용이 가능하다. + +좋은 구조라면 어플리케이션에서 비지니스 로직을 처리해야하기 때문에 쿼리에서 Case 문을 사용하는 것은 안티패턴으로 여겨지기도 한다. 하지만 그 방법이 어려울 경우 사용하면 좋을 것이다. + + + Case 문 예시 +```java + @Test + public void basicCase() { + //when + List results = queryFactory + .select(member.age + .when(10).then("열살") + .when(20).then("스무살") + .otherwise("기타")) + .from(member) + .fetch(); + //then + results.forEach(System.out::println); + } +``` + +### CaseBuilder + +```java + @Test + public void complexCase() { + //when + List results = queryFactory + .select(new CaseBuilder() + .when(member.age.between(0, 20)).then("0-20세") + .when(member.age.between(21, 30)).then("21-30세") + .otherwise("기타")) + .from(member) + .fetch(); + //then + results.forEach(System.out::println); + } +``` + +CaseBuilder를 사용하면 더 편리하게 생성할 수 있다. (여러 엔티티에 대한 조건 생성하기 편함) + +### NumberExpression으로 정렬 + +```java + @Test + public void orderByCase() { + //given + NumberExpression rankCase = new CaseBuilder() + .when(member.age.between(0, 20)).then(2) + .when(member.age.between(21,30)).then(1) + .otherwise(3); + //when + List results = queryFactory + .select(member.username, member.age, rankCase) + .from(member) + .orderBy(rankCase.asc()) + .fetch(); + //then + results.forEach(System.out::println); + } +``` +``` +[member3, 30, 1] +[member1, 10, 2] +[member2, 20, 2] +[member4, 40, 3] +``` + +NumberExpression에 CaseBuilder()를 넣어서, 위와 같이 사용할 수 있다. Case에 따라 결정되는 값으로 정렬까지 가능하다. + +#### 실행되는 쿼리 + +```sql + select + member0_.username as col_0_0_, + member0_.age as col_1_0_, + case + when member0_.age between ? and ? then ? + when member0_.age between ? and ? then ? + else 3 + end as col_2_0_ + from + member member0_ + order by + case + when member0_.age between ? and ? then ? + when member0_.age between ? and ? then ? + else 3 + end asc +``` + +쿼리가 위와 같이 실행된다. 신기하다. + +--- + +## 상수 + +`Expressions.constant()`를 사용해 상수를 사용할 수 있다. + +```java + @Test + public void addConstant() { + //given + //when + List results = queryFactory + .select(member.username, constant("A")) + .from(member) + .fetch(); + //then + results.forEach(System.out::println); + } +``` + +--- + +## 문자 더하기 + +문자를 더하기 위해선 `concat`을 사용할 수 있다. + +```java + @Test + public void concat() { + //given + //when + String result = queryFactory + .select(member.username.concat("_").concat(member.age.stringValue())) + .from(member) + .where(member.username.eq("member1")) + .fetchOne(); + //then + System.out.println(result); + } +``` + +https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84 + +김영한님의 강의 내용을보고 공부하며 정리한 글입니다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\353\217\231\354\240\201\354\277\274\353\246\254.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\353\217\231\354\240\201\354\277\274\353\246\254.md" new file mode 100644 index 00000000..a28445c5 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\353\217\231\354\240\201\354\277\274\353\246\254.md" @@ -0,0 +1,120 @@ +--- +title: '동적쿼리' +lastUpdated: '2024-03-02' +--- + +Querydsl에서 동적인 쿼리를 생성해보자. + +동적쿼리를 작성하는 방법은 +``` +1. BooleanBuilder를 작성하는 방법 +2. Where 절과 Predicate를 이용하는 방법 +3. Where 절과 피라미터로 Predicate 를 상속한 BooleanExpression을 사용하는 방법 +``` +총 세가지가 있다. + +그렇다면 세가지 방법을 모두 비교해보자. + +모든 코드에서 MemberSearchCondition를 파라미터로 받아 username, teamName, ageGoe, ageLoe를 조건으로 걸어 조회하고, 값이 null인경우 where절에 적용하지 않도록 했다. + +```java +@Getter +@NoArgsConstructor +public class MemberSearchCondition { + private String username; + private String teamName; + private Integer ageGoe; + private Integer ageLoe; +} +``` + +## 1. BooleanBuilder + +```java +public List searchByBuilder(MemberSearchCondition condition){ + BooleanBuilder builder = new BooleanBuilder(); + + if (hasText(condition.getUsername())) { + builder.and(member.username.eq(condition.getUsername())); + } + + if(hasText(condition.getTeamName())){ + builder.and(team.name.eq(condition.getTeamName())); + } + + if(condition.getAgeGoe() != null) { + builder.and(member.age.goe(condition.getAgeGoe())); + } + + if(condition.getAgeLoe() != null){ + builder.and(member.age.loe(condition.getAgeLoe())); + } + + return queryFactory + .select(new QMemberTeamDto( + member.id.as("memberId"), + member.username, + member.age, + team.id.as("teamId"), + team.name.as("teamName") + )) + .from(member) + .leftJoin(member.team, team) + .where(builder) + .fetch(); +} +``` +if문으로 해당 필드에 값이 존재하는지 확인한 후에 필요한 부분을 BooleanBuilder에 추가하여 조회하는 방법이다. 사실 이 방법도 아래의 방법과 똑같이 BooleanExpression을 사용하고 있지만, BooleanBuilder에 한번에 모은다음 조건에 넣는다는 점이 다르다. Where문의 조건들을 한눈에 보기 어렵고, 어떤 쿼리가 나가는지 예측하기 힘들다는 단점이 있다. + +## 2. Where 절과 BooleanExpression + +```java +public List searchByBooleanExpression(MemberSearchCondition condition){ + return queryFactory + .select(new QMemberTeamDto( + member.id.as("memberId"), + member.username, + member.age, + team.id.as("teamId"), + team.name.as("teamName") + )) + .from(member) + .leftJoin(member.team, team) + .where( + usernameEq(condition.getUsername()), + teamNameEq(condition.getTeamName()), + ageGoe(condition.getAgeGoe()), + ageLoe(condition.getAgeLoe()) + ) + .fetch(); +} + +private BooleanExpression usernameEq(String username) { + return hasText(username) ? member.username.eq(username) : null; +} + +private BooleanExpression teamNameEq(String teamName) { + return hasText(teamName) ? team.name.eq(teamName) : null; +} + +private BooleanExpression ageGoe(Integer ageGoe) { + return ageGoe != null ? member.age.goe(ageGoe) : null; +} + +private BooleanExpression ageLoe(Integer ageLoe) { + return ageLoe != null ? member.age.loe(ageLoe) : null; +} + +private BooleanExpression ageBetween(Integer ageLoe, Integer ageGoe) { + return ageLoe(ageLoe).and(ageGoe(ageGoe)); +} +``` +BooleanExpression 은 and 와 or 같은 메소드들을 이용해서 BooleanExpression 을 조합해서 새로운 BooleanExpression 을 만들 수 있기 때문에 재사용성이 높고, null일경우 Where 절에서 자동으로 무시되기 때문에 안전하다는 장점이 있다. (하지만 모든 조건이 null일 경우에는 장애가 발생할 수 있으니 주의해야한다.) + +그렇기 때문에 동적 쿼리를 써야할때는 BooleanExpression을 사용하는 것이 좋다고 한다. + + + + +출처:
+[우아콘2020] 수십억건에서 QUERYDSL 사용하기 https://www.youtube.com/watch?v=zMAX7g6rO_Y&t=656s diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\353\262\214\355\201\254\354\227\260\354\202\260.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\353\262\214\355\201\254\354\227\260\354\202\260.md" new file mode 100644 index 00000000..4aba6e4f --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/QuerydslJpa/\353\262\214\355\201\254\354\227\260\354\202\260.md" @@ -0,0 +1,23 @@ +--- +title: '벌크연산' +lastUpdated: '2024-03-02' +--- + +JPA에서 벌크 연산이란, 여러 건(대량의 데이터)을 한 번에 수정하거나 삭제하는 것을 뜻한다. 자세한 설명이나 사용시 주의사항에 대해 더 알고싶다면 위의 하이퍼링크를 참고하길 바란다. + +사실 Querydsl에서 따로 특별하게 벌크 연산을 생성하는 방법은 따로 없고, Where 조건에 여러 튜플이 해당할 수 있는 조건을 넣는다면 벌크 연산이 수행된다. + +```java +@Test +void bulkUpdate(){ + //given + //when + long count = queryFactory + .update(member) + .set(member.username, "미성년자") + .where(member.age.lt(20)) + .execute(); + //then + assertEquals(2, count); +} +``` diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/ReadOnlyQuery\342\200\205\354\265\234\354\240\201\355\231\224.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/ReadOnlyQuery\342\200\205\354\265\234\354\240\201\355\231\224.md" new file mode 100644 index 00000000..aed0bfa0 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/ReadOnlyQuery\342\200\205\354\265\234\354\240\201\355\231\224.md" @@ -0,0 +1,42 @@ +--- +title: 'ReadOnlyQuery 최적화' +lastUpdated: '2024-03-02' +--- + +JPA의 영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관하는 특징이 있다. 하지만 엔티티를 단순 조회하는 경우에는 컨텍스트에 있는 엔티티를 다시 꺼내오거나 수정할 필요가 없기 때문에 스냅샷 인스턴스를 저장하는 것이 메모리적으로 낭비이다. + +이럴 경우 아래의 방법으로 메모리 사용량을 최적화할 수 있다. + +### 스칼라 타입으로 조회 + +- 스칼라 타입은 영속성 컨텍스트가 관리하지 않기 떄문에, 엔티티가 아닌 스칼라 타입으로 조회하면 읽기 전용 쿼리에서 메모리를 절약할 수 있다. + + ```sql + SELECT u.id, u.name, u.age FROM user u + ``` + +### 읽기 전용 쿼리 힌트 사용 + +- 하이버네이트 전용 힌트인 `org.hibernate.readOnly`를 사용하면 엔티티를 읽기 전용으로 조회할 수 있다. 읽기 전용이므로 영속성 컨텍스트가 스냅샷을 저장하지 않는다. + +- 하지만 1차 캐시에는 저장되기 때문에 똑같은 식별자로 2번 조회했을 경우 반환되는 주소가 같다. + + ```java + Member member = em.createQuery("SELECT m FROM Member m", Member.class) + .setHint("org.hibernate.readOnly", true) + .getSingleResult(); + ``` + +### 읽기 전용 트랜잭션 사용 + +- 스프링 프레임워크를 사용하면 트랜잭션을 읽기 전용 모드로 설정할 수 있다. 트랜잭션을 읽기 전용으로 설정하면 스프링 프레임워크가 하이버네이트 세션의 플러시 모드가 MANUAL로 바뀌기 때문에, 강제로 플러시 호출을 하지 않으면 플러시가 일어나지 않는다. + + ```java + @Transactional(readOnly = true) + ``` + +- flush를 할 필요가 없기 때문에 Dirty Checking을 할 필요가 없고, 엔티티의 스냅샷(딥카피)또한 만들지 않는다. 스냅샷 비교와 같은 무거운 로직들이 실행되지 않으므로 성능이 향상된다. + +### 트랜잭션 밖에서 읽기 + +- 트랜잭션을 실행하지 않고 EntityManager를 직접 호출하여 플러시 없이 엔티티를 읽으면 스냅샷을 만들지 않고 엔티티를 빠르게 조회할 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\353\262\214\355\201\254\354\227\260\354\202\260.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\353\262\214\355\201\254\354\227\260\354\202\260.md" new file mode 100644 index 00000000..931afb44 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\353\262\214\355\201\254\354\227\260\354\202\260.md" @@ -0,0 +1,37 @@ +--- +title: '벌크연산' +lastUpdated: '2024-03-02' +--- +JPA에서 벌크 연산이란, 여러 건(대량의 데이터)을 한 번에 수정하거나 삭제하는 것을 뜻한다. + +벌크 연산은 `executeUpdate()` 메소드를 사용하고, 해당 메서드는 영향을 받은 엔티티 건수를 반환한다. + +```java +//재고가 10개 이하인 상품의 가격을 10% 증가하는 벌크 연산 + +String queryString = + "update Product p" + + "set p.price = p.price * 1.1" +  + "where p.stockAmount < :stockAmount"; + +int resultCount = em.createQuery(queryString) + .setParameter("stockAmount", 10) + .executeUpdate(); +``` + +```java +//나이가 70세 이상인 Member 삭제 + +String queryString =     + "delete from Product p" +  + "where p.price < :price"; + +int resultCount = em.createQuery(queryString)                     + .setParameter("price", 100)       + .excuteUpdate(); +``` + +## 주의할 점 +- 벌크 연산은 영속성 컨텍스트와 2차 캐시를 무시하고 데이터베이스에 직접 쿼리를 날린다. +- 그렇기 때문에 기존에 영속성 컨텍스트에 있던 엔티티를 벌크 연산으로 수정하게 되면, DB는 갱신되지만 영속성 컨텍스트는 수정 전 상태에 남아있어 문제가 생길 수 있다. +- 이를 해결하기 위해선 벌크 연산 후에 해당 엔티티를 사용하지 않거나 `em.clear()`를 통해 영속성 컨텍스트 초기화, 또는 `em.refresh()`로 재조회하여 사용해야 한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/1\354\260\250\354\272\220\354\213\234.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/1\354\260\250\354\272\220\354\213\234.md" new file mode 100644 index 00000000..efe0c59c --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/1\354\260\250\354\272\220\354\213\234.md" @@ -0,0 +1,40 @@ +--- +title: '1차캐시' +lastUpdated: '2024-03-02' +--- + +### 캐시란 무엇일까? + +Cache는 간단히 말해서 나중의 요청에 대한 결과를 미리 저장했다가 빠르게 사용하는 것이다. + + + +컴퓨터에서 중요한 메인 데이터들은 전원이 꺼져도 저장되는 SSD, HDD 등의 Secondary Memory에 저장된다. Secondary Memory에 저장된 데이터들은 장기적으로 안정적이게 저장될 수 있지만, 기술 한계상 데이터를 IO 하는 속도가 느리다. + +그렇기 때문에 컴퓨터에서는 데이터 처리에 바로 필요한 데이터를 Main Memory인 RAM에 끌어놓거나, CPU에서 빠르게 접근할 수 있는 CPU Registers, Cache Memory의 형태로 저장하여 더 빠르고 쉽게 접근할 수 있도록 한다. + +애플리케이션 서버(Spring JPA?)가 DB에 접근할 때를 예로 들어보면, DB에 연결하여 데이터를 가져오는 것을 Secondary Memory로, 서버의 메모리나 가까운 캐시서버에서 데이터를 가져오는 것을 Main Memory, Cache Memory로 비유할 수 있다. + +JPA의 Cache는 Database보다 더 빠른 Memory에 더 자주 접근하고 덜 자주 바뀌는 데이터를 저장할때 적합하다. + +네트워크를 통해 데이터베이스에 접근하는 시간 비용은 애플리케이션 서버에서 내부 메모리에 접근하는 시간 비용보다 수만에서 수십만 배 이상 비싸기 때문에, **조회한 데이터를 메모리에 캐시해서 데이터베이스 접근 횟수를 줄이면 애플리케이션 성능을 획기적으로 개선할 수 있다.** + +### 1차 캐시란 무엇일까? + +영속성 컨텍스트 내부에는 **엔티티를 보관하는 저장소**가 있는데, 이를 1차 캐시라고 한다. + + + +1차 캐시는 영속성 컨텍스트 내부에 있으며, `entityManager`로 조회하거나 변경하는 모든 엔티티는 1차 캐시에 저장된다. +1차 캐시는 끄고 켤 수 있는 옵션이 아니며, 사실상 영속성 컨텍스트 자체가 1차 캐시라고 할 수 있다. + +일반적인 웹 애플리케이션 환경에서 1차 캐시는 트랜잭션이 시작하고 종료할 때 까지만 유효하다. OSIV를 사용해도 클라이언트의 필터로 요청이 들어오고 나갈 때까지만 남아있는다. + +1차 캐시의 동작 과정을 간단하게 표현하면 다음과 같다. + +1. 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회한다. +2. 엔티티를 1차 캐시에 보관한다. +3. 1차 캐시에 보관된 결과를 반환한다. +4. 이후 같은 엔티티를 조회하면 1차 캐시에 같은 엔티티가 있으므로 데이터베이스를 조회하지 않고 1차 캐시의 엔티티를 그대로 반환한다. + +> 같은 엔티티가 있으면 객체 동일성을 보장한다. (==) \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/2\354\260\250\354\272\220\354\213\234.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/2\354\260\250\354\272\220\354\213\234.md" new file mode 100644 index 00000000..51c61d7a --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/2\354\260\250\354\272\220\354\213\234.md" @@ -0,0 +1,57 @@ +--- +title: '2차캐시' +lastUpdated: '2024-03-02' +--- + +**애플리케이션에서 공유하는 캐시**를 JPA는 공유 캐시(Shared Cache)또는 2차 캐시 (Second Level Cache, L2 Cache)라 부른다. 분산 캐시나 클러스터링 환경의 캐시는 애플리케이션보다 더 오래 유지 될 수도 있다. + +2차 캐시를 적절히 활용하면 데이터베이스 조회 횟수를 획기적으로 줄일 수 있다. + +## 1차캐시는 못하고 2차캐시는 할 수 있는 것 + +1차캐시는 영속성 컨텍스트에서 관리되는 캐시로, 트랜잭션이 시작하고 종료할 때 까지만 유지된다. 그렇기 때문에 같은 트랜잭션에서 한 엔티티를 여러번 가져와야할때 효과를 볼 수 있다. + +하지만 만약에 여러 유저가 한 엔티티의 정보를 필요로 한다면 어떨까? 여러 요청들에서 공통적으로 같은 엔티티의 데이터가 필요한 경우에는, 1차캐시가 효용을 미치지 못한다. (한 요청 안에서만 유지되기 때문) + +그렇기 때문에 여러 요청에서 캐싱을 적용하기 위해선 2차 캐시를 사용해야 한다. + +## 동작과정 + + + +엔티티 매니저를 통해 엔티티를 조회하기 전 영속성 컨텍스트를 우선 찾은 다음에, 2차 캐시에서 엔티티를 찾고 2차 캐시에도 없는 경우엔 DB에 직접 연결해서 데이터를 찾는다. + +2차 캐시의 동작을 간단하게 나타내면 다음과 같다. + +1. 영속성 컨텍스트에 엔티티가, 없으면 2차 캐시를 조회한다. +2. 2차 캐시에 엔티티가 없으면, DB를 조회해서 결과를 2차 캐쉬에 보관한다. +3. 2차 캐시에 엔티티가 있으면, 2차 캐시는 자신이 가지고 있는 엔티티를 복사해서 반환한다. + + +## 특징 + +2차 캐시는 **동시성**을 극대화하기 위해 자신이 가지고 있는 엔티티를 깊은 복사로 반환한다. 만약 캐시한 객체를 그대로 반환하면 멀티 쓰레드 환경에서 동시성 이슈가 발생할 수 있다. +- 일반적으로 객체를 대입하거나 반환하는 경우에는, 얕은 복사(같은 메모리 참조)가 수행되기 떄문이다. +- 예를 들어, 동시에 요청을 받은 두 스레드가 함께 실행된다고 해보자. + - 한 요청을 수행하는 스레드에서 객체의 A라는 데이터를 B로 바꿨다. + - 그렇다면 그 객체가 참조하는 포인터가 가르키는 값이 바뀐다. + - 다른 스레드는 본인이 값을 바꾸지 않았더라도 해당 객제의 값을 B로 조회하게 된다. +- 깊은 복사로 반환하여 Read Commited의 개념을 구현하도록 한다고 볼 수 있다. + +2차 캐시는 PK를 기준으로 캐시하지만, 객체를 복사해서 반환하기 때문에 영속성 컨텍스트가 다르면 객체 동일성을 보장하지 않는다. + +## 상황별 성능 + +2차캐시를 사용할떄도, 객체의 정보가 변경되었다면 캐시를 지우고 이후 요청에서 DB에 다시 접근해야한다. 그렇기 떄문에 수정이 많은 테이블에서는 2차 캐시가 큰 성능 향상을 줄 수 없을지도 모른다. + +하지만 조회 요청이 더 많은 경우에는 캐시 데이터를 수정하거나 삭제할 필요가 없기 때문에 2차 캐시의 성능 개선 효과를 확실하게 느낄 수 있다. 특히 한 유저가 어떤 데이터를 수정하는 중이어서 해당 Row에 Lock이 걸려있는 상황에도, 2차 캐시가 있다면 단순 조회 요청에선 그 캐시 데이터를 바로 반환하면 되기 때문에 조회 성능을 더욱 높일 수 있다. + +## 실무 + +엔티티는 영속성 컨텍스트에서 상태를 관리하기 때문에 최적화를 위해선 엔티티를 DTO로 변환해서 변환한 DTO를 캐시에 저장해서 관리해야한다. + +하지만 JPA(Hibernate)의 2차캐시는 사용자 지정 DTO를 정의하는 것이 어렵고, 설정이 복잡하고, 지원하는 캐시 라이브러리도 없다. + +그에 반해 스프링을 사용하면 이 DTO를 효과적으로 캐시할 수 있고, 지원하는 캐시 라이브러리도 풍부합니다. 그런데 2차 캐시는 단순히 엔티티 조회(쿼리포함)와 관련된 부분만 캐시가 지원되기 때문에 JPA 2차캐시보다는 스프링에서 지원하는 캐시를 사용하는 것이 더 좋다고 한다. + +참고 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/\354\230\201\354\206\215\354\204\261\342\200\205\354\273\250\355\205\215\354\212\244\355\212\270.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/\354\230\201\354\206\215\354\204\261\342\200\205\354\273\250\355\205\215\354\212\244\355\212\270.md" new file mode 100644 index 00000000..832079c2 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\354\272\220\354\213\261/\354\230\201\354\206\215\354\204\261\342\200\205\354\273\250\355\205\215\354\212\244\355\212\270.md" @@ -0,0 +1,72 @@ +--- +title: '영속성 컨텍스트' +lastUpdated: '2024-03-02' +--- + +영속성 컨텍스트랑 말 그대로 엔티티를 영속화(영구저장) 시켜주는 환경이다. 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스(1차캐시) 역할을 한다. 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다. + +영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어지고, 엔티티 매니저를 통해 영속성 컨텍스트에 접근하고 관리할 수 있다. + +영속성 컨텍스트는 엔티티를 여러가지 상태로 저장한다. + +--- + +## 엔티티의 생명주기 + +- 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태 +- 영속(managed): 영속성 컨텍스트에 저장된 상태 +- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태 +- 삭제(removed): 삭제된 상태 + + +![image](https://user-images.githubusercontent.com/81006587/210162755-aa528eb8-1f63-4593-bf2f-df5a2267f3a6.png) + +### 비영속 +엔티티 객체를 생성했지만 아직 영속성 컨텍스트에 저장하지 않은 상태를 비영속(new/transient)라 한다. + +```java +Member member = new Member(); +``` + +### 영속 + +엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장한 상태를 말하며 영속성 컨텍스트에 의해 관리된다는 뜻이다. + +```java +em.persist(member); +``` + +### 준영속 + +영속성 컨텍스트가 관리하던 영속 상태의 엔티티 더이상 관리하지 않으면 준영속 상태가 된다. 특정 엔티티를 준영속 상태로 만드려면 `em.datach()`를 호출하면 된다. + +```java +// 엔티티를 영속성 컨텍스트에서 분리해 준영속 상태로 만든다. +em.detach(member); +// 영속성 콘텍스트를 비워도 관리되던 엔티티는 준영속 상태가 된다. +em.claer(); +// 영속성 콘텍스트를 종료해도 관리되던 엔티티는 준영속 상태가 된다. +em.close(); +``` + +**준영속 상태의 특징** +- 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다. +- 하지만 식별자 값은 가지고 있다. + +### 삭제 + +엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다. + +``` +em.remove(member); +``` + +--- + +## 영속성 컨텍스트의 특징 + +- 영속성 컨텍스트의 식별자 값 + - 영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다. + +- 영속성 컨텍스트와 데이터베이스 저장 + - JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터 베이스에 반영하는데 이를 flush라 한다. diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\354\240\204\355\214\214\342\200\205\354\204\244\354\240\225.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\354\240\204\355\214\214\342\200\205\354\204\244\354\240\225.md" new file mode 100644 index 00000000..192a56b5 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/JPA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\354\240\204\355\214\214\342\200\205\354\204\244\354\240\225.md" @@ -0,0 +1,62 @@ +--- +title: '트랜잭션 전파 설정' +lastUpdated: '2024-03-02' +--- + +Spring에서 사용하는 어노테이션 `@Transactional`은 해당 메서드를 하나의 트랜잭션 안에서 진행할 수 있도록 만들어주는 역할을 한다. 이때 트랜잭션 내부에서 트랜잭션을 또 호출한다면 스프링에서는 어떻게 처리하고 있을까? 새로운 트랜잭션이 생성될 수도 있고, 이미 트랜잭션이 있다면 부모 트랜잭션에 합류할 수도 있을 것이다. 진행되고 있는 트랜잭션에서 다른 트랜잭션이 호출될 때 어떻게 처리할지 정하는 것을 '트랜잭션의 전파 설정'이라고 부른다. + +## 전파 설정 옵션 +트랜잭션의 전파 설정은 `@Transactional`의 옵션 `propagation`을 통해 설정할 수 있다. 각 옵션은 아래와 같다. + +### REQUIRED (default) + +부모 트랜잭션이 존재한다면 부모 트랜잭션으로 합류하고, 부모 트랜잭션이 없다면 새로운 트랜잭션을 생성한다. + +중간에 롤백이 발생한다면 모두 하나의 트랜잭션이기 때문에 진행사항이 모두 롤백된다. + +image + +### REQUIRES_NEW + +무조건 새로운 트랜잭션을 생성한다. 각각의 트랜잭션이 롤백되더라도 서로 영향을 주지 않는다. + + image + +### MANDATORY + +부모 트랜잭션에 합류한다. 만약 부모 트랜잭션이 없다면 예외를 발생시킨다. + +image + +### NESTED + +부모 트랜잭션이 존재한다면 중첩 트랜잭션을 생성한다. 중첩된 트랜잭션 내부에서 롤백 발생시 해당 중첩 트랜잭션의 시작 지점 까지만 롤백된다. 중첩 트랜잭션은 부모 트랜잭션이 커밋될 때 같이 커밋된다. + +부모 트랜잭션이 존재하지 않는다면 새로운 트랜잭션을 생성한다. + +image + +> 중첩 트랜잭션은 JDBC 3.0 이후 버전의 savepoint기능을 사용하는데, JPA를 사용하는 경우, 변경감지를 통해서 업데이트문을 최대한 지연해서 발행하는 방식을 사용하기 때문에 중첩된 트랜잭션 경계를 설정할 수 없어 지원하지 않는다고 한다. JPA를 사용하면 NESTED는 사용하지 말아야겠다. + +### NEVER + +트랜잭션을 생성하지 않는다. 부모 트랜잭션이 존재한다면 예외를 발생시킨다. + + image + + +### SUPPORTS + +부모 트랜잭션이 있다면 합류한다. 진행중인 부모 트랜잭션이 없다면 트랜잭션을 생성하지 않는다. + + +### NOT_SUPPORTED + +부모 트랜잭션이 있다면 보류시킨다. 진행중인 부모 트랜잭션이 없다면 트랜잭션을 생성하지 않는다. + + +--- + +### 참고 + +- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/SpringSecurity/CORS.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/SpringSecurity/CORS.md" new file mode 100644 index 00000000..654e7e0f --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/SpringSecurity/CORS.md" @@ -0,0 +1,40 @@ +--- +title: 'CORS' +lastUpdated: '2024-03-02' +--- + +CORS란Cross-Origin Resource Sharing, 교차 출처 리소스 공유의 약자이다. 서로 출처가 다른 웹 애플리케이션에서 자원을 공유하는 것을 말한다. + +보안상의 이유로 브라우저에서는 이 교차 출처 요청을 제한하는 경우가 많다. (포스트맨은 개발 도구이기 때문에 CORS에 신경쓰지 않는다.) + +Spring Security에서도 기본적으로 CORS가 제한되어있다. 이때 특정 도메인, 또는 전체 도메인에서의 요청을 허용하려면 아래와 같이 설정해주면 된다. + +```java +@Bean +public class SecurityFilterChain filterChain(HttpSecurity: httpSecurity) { + + return httpSecurity + .cors().and() // cors 설정을 적용하겠다는 뜻 + .csrf().disable() + .formLogin().disable() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .build(); +} + +``` + +```java +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedHeaders("*") + .allowedOrigins("http://localhost:3000"); //원하는 Origin을 적어준다 + } + +} +``` diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/SpringSecurity/CSRF.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/SpringSecurity/CSRF.md" new file mode 100644 index 00000000..587883a7 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/SpringSecurity/CSRF.md" @@ -0,0 +1,33 @@ +--- +title: 'CSRF' +lastUpdated: '2024-03-02' +--- + +CSRF(Cross site Request forgery)는 웹 애플리케이션의 취약점 중 하나로, 이용자가 의도하지 않은 요청을 통한 공격을 의미한다. 즉, 인터넷 사용자가 자신의 의지와 무관하게 공격자가 의도한 특정 행위를 웹사이트의 요청하도록 하여 간접적으로 해킹하도록 하는 것이다. + + + +따라서, 스프링 시큐리티는 CSRF 공격을 방지하기 위한 기능을 가지고있다. + +@EnableWebSecurity 어노테이션을 붙이면 Referrer 검증, CSRF Token 사용 등의 기능이 활성화된다. + +각 기능에 대해 간단하게 알아보자 + +- Referrer 검증 + +서버단에서 request의 referrer을 확인하여 domain이 일치하는지 검증하는 방법이다. + +- Spring Security CSRF Token + +임의의 토큰을 발급한 후 자원에 대한 변경 요청일 경우 Token 값을 확인한 후 클라이언트가 정상적인 요청을 보낸것인지 확인하는 방법이다. 만약 CSRF Token이 존재하지 않거나, 기존의 Token과 일치하지 않는 경우 4XX 상태코드를 반환하도록 한다. +(타임리프 템플릿 및 jsp의 spring:form 태그를 사용한다면 기본적으로 csrf token을 넣어준다) + +### 왜 비활성화할까? + +CSRF(Cross-Site Request Forgery)는 "사이트 간 요청"이 발생하기 쉬운 웹에 대해 요청할 때 필요하다. + +이러한 애플리케이션은 보통 템플릿 엔진(Thymeleaf, JSP)등을 사용하여 서버 측에서 전체 HTML을 생성하는 구조이다. + +하지만 최신의 애플리케이션은 주로 REST API의 앤드포인트에 의존하며, HTTP 형식에 따라 무상태로 통신하도록 한다. 이러한 `REST API`는 서버쪽의 세션이나 브라우저 쿠키에 의존하지 않기 때문에 CSRF 공격의 대상이 될 수 없다. + +따라서 API만 노출하는 `REST API`를 만드는 경우에는 CSRF를 비활성화해도 된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Spring\342\200\2056.0\352\263\274\342\200\205Spring\342\200\205Boot\342\200\2053.0.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Spring\342\200\2056.0\352\263\274\342\200\205Spring\342\200\205Boot\342\200\2053.0.md" new file mode 100644 index 00000000..aa3c9405 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Spring\342\200\2056.0\352\263\274\342\200\205Spring\342\200\205Boot\342\200\2053.0.md" @@ -0,0 +1,44 @@ +--- +title: 'Spring 6.0과 Spring Boot 3.0' +lastUpdated: '2024-03-02' +--- + +## Spring 6.0에서 달라지는 점 + +- Java 17기반으로 변경 +- 일부 Java EE API 지원 종료 (javax등) +- XML이 점차적으로 Spring에서는 사라지게 될 것 +- RPC 지원 종료 +- 새로운 AOT 엔진 도입 +- @Inject 같은 JSR에서 지원하던 어노테이션들이 jakarta.annotation 패키지의 어노테이션으로 변경 +- HttpMethod가 enum에서 class로 변경 +- Jakarta EE 9+로의 마이그레이션으로 인한 변경 + - Hibernate ORM 5.6.x 버전부터 hibernate-core-jakarta 사용 + - javax.persistence에서 jakarta.persistence로 변경 + - Tomcat 10, Jetty 11, Undertow 2.2.14 (undertow-servlet-jakarta도 포함)으로 업그레이드 필요 + - javax.servlet에서 jakarta.servlet으로 변경 필요 (import) + +- Commons FileUpload, Tiles, FreeMarker JSP support 같은 서블릿 기반 기능이 지원 종료됨 + - multipart file 업로드 혹은 FreeMarker template view는 StandardServletMultipartResolver 사용을 권장 + - 이외에는 Rest 기반 웹 아키텍처 사용 + +- Spring MVC와 Spring WebFlux에서 더 이상 type 레벨에서의 @RequestMapping을 자동 탐색하지 않음 + - interface의 경우에는 @RequestMapping을 붙여도 더 이상 탐색되지 않음 + - 따라서 Class에 붙이거나 interface에도 사용하고 싶으면 @Controller도 붙여야 함 + - spring-cloud-openfeign에서도 이것 때문에 interface레벨 @RequestMapping 지원 종료(Git Issue) + +- URL에서 마지막으로 나오는 / 매칭해주는 trailing slash matching configuration 기본적으로 지원하지 않음 (옵션 추가 시 사용 가능) + + +## Spring Boot 2.x -> 3.0 달라지는 점 +- 최소 요구사항 변경 (M4 기준) + - Gradle 7.5 + - Groovy 4.0 + - Jakarta EE 9 + - Java 17 + - Kotlin 1.6 + - Hibernate 6.1 + - Spring Framework 6 사용 + +- AOT maven, gradle 플러그인 제공 +- native 지원 기능 확대 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Validation/@GroupSequence.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Validation/@GroupSequence.md" new file mode 100644 index 00000000..e1aa810e --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Validation/@GroupSequence.md" @@ -0,0 +1,63 @@ +--- +title: '@GroupSequence' +lastUpdated: '2024-03-02' +--- + +보통 WebRequest를 받을때, null이거나 비어있는 등의 유효하지 않은 값을 미리 걸러내기 위해 Sprign validation을 사용한다. 설정해둔 `@NotNull`, `@Size`, `@Pattern` 등 조건에 부합하지 못하면 MethodArgumentNotValidException이 발생하고, 이 에러를 적절히 처리하여 반환하는 방식이 많이 사용된다. + +하지만 한 필드에 여러 검증을 넣게 되면, 그 검증에 순서가 부여되지 않아 무작위로 먼저 걸리는 조건의 에러가 반환된다. 그렇다면 이 에러 처리의 순서를 정해줘야 한다면 어떻게 해야할까? 즉, Null 체크를 먼저하고, 그다음 Size를 체크하고, 그다음 Pattern을 체크하는 방식으로 흐름을 지정하려면 어떻게 해야할까? + +그런 경우 `@GroupSequence`를 사용해주면 된다. `@GroupSequence`는 검증 어노테이션을 그룹으로 묶어서 각 그룹의 순서를 지정해줄 수 있도록 한다. + +사용을 위해선 우선 그룹을 지정해야한다. + +```java +public class ValidationGroups { + interface NotBlankGroup {}; + interface NotEmptyGroup {}; + interface NotNullGroup {}; + interface SizeCheckGroup {}; + interface PatternCheckGroup {}; +} +``` + +검증 종류로만 그룹을 나누고 싶다면 이런식으로 그룹을 나눌 수 있다. 다른 방식으로 그룹을 묶어주고 싶다면 코드를 바꾸면 된다. + +그리고 `@GroupSequence`를 사용하여 원하는 순서대로 정리해준다. + +`@GroupSequence`를 사용하여 원하는 순서대로 정리해준다. +왼쪽(위쪽)부터 유효성 검사를 체크해서 없으면 다음 유효성 검사를 실시하게 된다. + +```java +@GroupSequence( + Default.class, + ValidationGroups.NotBlankGroup.class, + ValidationGroups.NotEmptyGroup.class, + ValidationGroups.NotNullGroup.class, + ValidationGroups.SizeCheckGroup.class, + ValidationGroups.PatternCheckGroup.class +) +public interface ValidationSequence { +} +``` + +dto에 선언되어있는 어노테이션에서 각각 groups = "인터페이스명"을 추가한다. + +```java +@Size(min = 4, max = 30, message = "아이디는 4글자에서 30글자 사이로 입력해주세요.", groups = ValidationGroups.SizeCheckGroup.class) +@NotBlank(message = "아이디를 입력해주세요.", groups = ValidationGroups.NotNullGroup.class) +@Pattern(regexp = "^([a-z가-힣0-9]){4,30}$", message = "대문자, 특수문자는 입력할 수 없습니다.", groups = ValidationGroups.PatternCheckGroup.class) + private String userId; +``` + +Controller에서 `@Valid`가 있었던 부분을 `@Validated`로 바꾸어준다. + +`@Validated`는 `@Valid`의 동작을 대체하면서, 순서 정의 기능을 추가해주기 때문에 `@Valid`를 완전히 지워줘도 괜찮다. + +```java + @PostMapping + public ResponseEntity createUser(@Validated(ValidationSequence.class) @RequestBody User signUpInfo) { + accountManager.createUser(signUpInfo); + return new ResponseEntity<>(HttpStatus.OK); + } +``` diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Validation/@Valid\354\231\200\342\200\205@Validated.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Validation/@Valid\354\231\200\342\200\205@Validated.md" new file mode 100644 index 00000000..68e95213 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Validation/@Valid\354\231\200\342\200\205@Validated.md" @@ -0,0 +1,65 @@ +--- +title: '@Valid와 @Validated' +lastUpdated: '2024-03-02' +--- + +`@Valid`는 JSR-303 표준 스펙(자바 진영 스펙)으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다. JSR 표준의 빈 검증 기술의 특징은 객체의 필드에 달린 어노테이션으로 편리하게 검증을 한다는 것이다. + +Spring에서는 일종의 어댑터인 `LocalValidatorFactoryBean`이 제약 조건 검증을 처리한다. 이를 이용하려면 LocalValidatorFactoryBean을 빈으로 등록해야 하는데, SpringBoot에서는 아래의 의존성만 추가해주면 해당 기능들이 자동 설정된다. + +```kotlin +// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation +implementation(" group: org.springframework.boot:spring-boot-starter-validation") +``` + +예를 들어 `@NotNull` 어노테이션은 필드의 값이 null이 아님을 체크하고, @Min은 해당 값의 최솟값을 지정할 수 있도록 한다. + +```java + @NotNull + private final UserRole userRole; + + @Min(12) + private final int age; +``` + +그리고 컨트롤러의 메소드에 `@Valid`를 붙여주면, 유효성이 검증된다. + +### @Valid의 동작 원리 + +모든 요청은 프론트 컨트롤러인 dispatcherServlet을 통해 컨트롤러로 전달된다. 전달 과정에서는 컨트롤러 메소드의 객체를 만들어주는 `ArgumentResolver`가 동작하는데, @Valid 역시 ArgumentResolver에 의해 처리된다. + +예를 들어 `@RequestBody`를 사용한다고 하면, Json 메세지를 객체로 변환해주는 작업은 ArgumentResolver의 구현체인 `RequestResponseBodyMethodProcessor`가 처리하며, 이 내부에서 `@Valid`로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다. + +그리고 검증에 오류가 있다면 `MethodArgumentNotValidException` 예외가 발생하게 되고, 디스패처 서블릿에 기본으로 등록된 예외 리졸버(Exception Resolver)인 `DefaultHandlerExceptionResolver`에 의해 400 BadRequest 에러가 발생한다. + +이러한 이유로 `@Valid`는 기본적으로 컨트롤러에서만 동작한다. + +```log +org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity com.example.testing.validator.UserController.addUser(com.example.testing.validator.AddUserRequest) with 2 errors: [Field error in object 'addUserRequest' on field 'email': rejected value [asdfad]; codes [Email.addUserRequest.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addUserRequest.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@18c5ad90,.*]; default message [올바른 형식의 이메일 주소여야 합니다]] [Field error in object 'addUserRequest' on field 'age': rejected value [5]; codes [Min.addUserRequest.age,Min.age,Min.int,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addUserRequest.age,age]; arguments []; default message [age],12]; default message [12 이상이어야 합니다]] + at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141) ~[spring-webmvc-5.3.15.jar:5.3.15] + at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-5.3.15.jar:5.3.15] +``` + +## `@Validated` + +`@Validated`는 JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션 및 기능이다. `@Validated`와 `@Valid`를 시영히먄 컨트롤러가 아니더라도 유효성 검증을 할 수 있다. + +유효성 검증에 실패하면 에러가 발생하는데, 로그를 확인해보면 `MethodArgumentNotValidException` 예외가 아닌 `ConstraintViolationException` 예외가 발생하는 것을 확인할 수 있다. 이는 앞서 잠깐 설명한대로 동작 원리가 다르기 때문이다. + +```log +javax.validation.ConstraintViolationException: getQuizList.category: 널이어서는 안됩니다 + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120) ~[spring-context-5.3.14.jar:5.3.14] + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14] + at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14] + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14] + at com.mangkyu.employment.interview.app.quiz.controller.QuizController$$EnhancerBySpringCGLIB$$b23fe1de.getQuizList() ~[main/:na] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~ +``` + +## @Validated의 동작 원리 + +특정 ArgumnetResolver가 유효성을 검사하던 `@Valid`와 달리, `@Validated`는 **AOP 기반**으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위한 AOP의 어드바이스 또는 인터셉터(MethodValidationInterceptor)가 등록되고, 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행한다. + +이러한 이유로 `@Validated`를 사용하면 컨트롤러, 서비스, 레포지토리 등 계층에 무관하게 스프링 빈이라면 유효성 검증을 진행할 수 있다. + +Validated를 사용하면 group으로 [검증 순서를 지정](@GroupSequence.md)하는 것도 가능하다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/@Controller.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/@Controller.md" new file mode 100644 index 00000000..4cba99b0 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/@Controller.md" @@ -0,0 +1,222 @@ +--- +title: '@Controller' +lastUpdated: '2024-03-02' +--- + +**@Controller**로 webFlux를 사용해보자. + +### 프로젝트 생성 + +스프링부트 프로젝트를 생성하여 `build.gradle`에 Dependencies를 설정한다. 필요한 의존성과, webflux 의존성을 설정해준다. + +```groovy +implementation 'org.springframework.boot:spring-boot-starter-webflux' +``` + +### Flux 반환 유형 + +`Flux`는 Reactive Streams의 Publisher를 구현한 N개 요소의 스트림을 표현하는 Reactor 클래스이다. 기본적으로 text/plain으로 응답이 반환되지만, **Server-Sent Event**나 **JSON Stream**으로 반환할 수도 있다. + +Flux의 반환 유형은 **클라이언트가 헤더에 응답 유형을 어떻게 설정하느냐에 따라** 달라진다. + +아래같은 코드가 있다고 해보자. + +```java +@RestController +public class HelloController { + + @GetMapping("/") + Flux hello() { + return Flux.just("Hello", "World"); + } +} +``` + +일반적으로 요청을 보내면 `text/plain`으로 반환이 온다. + +```c +//text/plain +$ curl -i localhost:8080 + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 10 0 10 0 0 909 0 --:--:-- --:--:-- --:--:-- 1000HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: text/plain;charset=UTF-8 + +HelloWorld +``` + +Accept 헤더에 `text/event-stream`를 지정하면 Server-Sent Event, `application/stream+json`를 지정하면 JSON Stream으로 반환된다. (하지만 위 컨트롤러 코드에서는 단순 문자열을 반환했기 때문에, JSON과 plain text의 차이가 없다.) + +```c +//text/event-stream +$ curl -i localhost:8080 -H 'Accept: text/event-stream' + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 24 0 24 0 0 1846 0 --:--:-- --:--:-- --:--:-- 2000HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: text/event-stream;charset=UTF-8 + +data:Hello + +data:World +``` + +```c +//application/stream+json +$ curl -i localhost:8080 -H 'Accept: application/stream+json' + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 10 0 10 0 0 714 0 --:--:-- --:--:-- --:--:-- 769HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: application/stream+json;charset=UTF-8 + +HelloWorld +``` + +### 무한 Stream + +Flux 반환을 `java.util.stream.Stream`형으로 주는 것도 가능하다. 다음은 stream 메소드를 작성하여, 무한 Stream을 작성하고, 그 중에 10건을 Flux로 변환하여 반환해 보자. + +```java +@RestController +public class HelloController { + + @GetMapping("/") + Flux hello() { + return Flux.just("Hello", "World"); + } + + @GetMapping("/stream") + Flux> stream() { + Stream stream = Stream.iterate(0, i -> i + 1); // Java8의 무한Stream + return Flux.fromStream(stream.limit(10)) + .map(i -> Collections.singletonMap("value", i)); + } +} +``` + +`/stream`에 대한 세 가지 응답은 각각와 아래와 같다. + +#### 일반 JSON + +```c +$ curl -i localhost:8080/stream + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 121 0 121 0 0 11000 0 --:--:-- --:--:-- --:--:-- 12100HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: application/json + +[{"value":0},{"value":1},{"value":2},{"value":3},{"value":4},{"value":5},{"value":6},{"value":7},{"value":8},{"value":9}] +``` + +#### Server-Sent Event + +```c +$ curl -i localhost:8080/stream -H 'Accept: text/event-stream' + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 180 0 180 0 0 12000 0 --:--:-- --:--:-- --:--:-- 12000HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: text/event-stream;charset=UTF-8 + +data:{"value":0} + +data:{"value":1} + +data:{"value":2} + +data:{"value":3} + +data:{"value":4} + +data:{"value":5} + +data:{"value":6} + +data:{"value":7} + +data:{"value":8} + +data:{"value":9} +``` + +### JSON Stream + +```c +$ curl -i localhost:8080/stream -H 'Accept: application/stream+json' + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 120 0 120 0 0 7500 0 --:--:-- --:--:-- --:--:-- 7500HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: application/stream+json + +{"value":0} +{"value":1} +{"value":2} +{"value":3} +{"value":4} +{"value":5} +{"value":6} +{"value":7} +{"value":8} +{"value":9} +```` + +`application/json`과 `application/stream+json`의 차이를 볼 수 있다. + +만약 코드에서 limit을 붙이지 않고 코드를 아래와 같이 작성한다면 무한 Stream을 받을 수도 있다. (단 `application/json`의 경우에는 응답이 반환되지 않을 것이다.) + +```c + @GetMapping("/stream") + Flux> stream() { + Stream stream = Stream.iterate(0, i -> i + 1); // Java8의 무한Stream + return Flux.fromStream(stream) + .map(i -> Collections.singletonMap("value", i)); + } +``` + +### 요청인자를 비동기로 + +요청을 받는 것 또한 비동기적으로 처리할 수 있다. + +`@RequestBody`으로 요청 본문으로 받아 대문자로 변환하는 map의 결과 Mono를 그대로 반환하는 메소드를 추가해보자. 일반적으로 String으로 요청을 받는다면 NonBlocking으로 동기화 처리되지만, Mono에 감싸서 받으면 **`chain/compose`로 비동기처리**할 수 있게 된다. + +Mono는 **1개 또는 0개의 요소**를 가지도록 한다. + +```java +@RestController +public class HelloController { + + @GetMapping("/") + Flux hello() { + return Flux.just("Hello", "World"); + } + + @GetMapping("/stream") + Flux> stream() { + Stream stream = Stream.iterate(0, i -> i + 1); + return Flux.fromStream(stream).zipWith(Flux.interval(Duration.ofSeconds(1))) + .map(tuple -> Collections.singletonMap("value", tuple.getT1() /* 튜플의 첫 번째 요소 = Stream 요소 */)); + } + + @PostMapping("/echo") + Mono echo(@RequestBody Mono body) { + return body.map(String::toUpperCase); + } +} +``` + +```c +$ curl -i localhost:8080/echo -H 'Content-Type: application/json' -d rlaisqls + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed +100 14 100 7 100 7 1166 1166 --:--:-- --:--:-- --:--:-- 2800HTTP/1.1 200 OK +Content-Type: text/plain;charset=UTF-8 +Content-Length: 7 + +RLAISQLS +``` + +1건만 처리해야 한다면 Mono를 사용하는 것이 명시적이지만, **여러 건수의 Stream을 처리**하고 싶다면 `Flux`로 해야 한다. diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/R2DBC/R2DBC.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/R2DBC/R2DBC.md" new file mode 100644 index 00000000..8e386930 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/R2DBC/R2DBC.md" @@ -0,0 +1,34 @@ +--- +title: 'R2DBC' +lastUpdated: '2024-03-02' +--- + +R2DBC는 Reactive Relational Database Connectivity의 줄임말이다. R2DBC는 관계형 데이터베이스 접근을 위해 구현해야 할 리액티브 API를 선언하는 스펙이다. + +JDBC는 완전한 블로킹 API이었고, RDBMS는 NoSQL에 비해 자체 리액티브 클라이언트를 가지고 있는 경우가 적어서 비동기 통신을 하기 힘들었다. + +반면에 R2DBC는 Non-Blocking 관계형 데이터베이스 드라이버와 잘 동작하도록 하는 것을 목적으로 만들어졌다. 쿼리를 소켓에 기록하고 스레드는 응답이 수신될 때까지 다른 작업을 계속 처리하여 리소스 오버헤드를 줄이는 방식으로 적은 스레드, 하드웨어 리소스로 동시 처리를 제어할 수 있도록 한다. + +하지만, R2DBC는 개념적으로 쉬운 것을 목표로 하므로, 기존에 JDBC나 JPA에서 쉽게 사용했던 여러 기능을 제공하지 않는다고 한다. Spring Data의 Overview를 보면 다음과 같은 문구가 있다. + +> Spring Data R2DBC aims at being conceptually easy. In order to achieve this, it does NOT offer caching, lazy loading, write-behind, or many other features of ORM frameworks. This makes Spring Data R2DBC a simple, limited, opinionated object mapper. + +## 장점 + +- reactive한 비동기 처리로, 성능을 높일 수 있다. +- Spring에서 R2DBC를 위한 Spring Data R2DBC를 공식적으로 지원해준다. + +## 단점 + +- 기술측면에서 숙련도가 높지 않다. JPA만큼 커뮤니티가 크지 않다. +- Type safe한 쿼리를 작성할 방법이 많이 없다. (jdsl 등 여러 라이브러리들이 만들어지고는 있다.) +- Hibernate/Spring data jpa는 정말 잘 만들어져있으며 제공하는 기능도 많다. 반면 spring data r2dbc는 부가 기능이 많이 없고, 관련 라이브러리나 데이터가 부족한 편이다. + +관련 글 : [R2DBC 사용](R2DBC%E2%80%85%EC%82%AC%EC%9A%A9.md) + +--- + +참고 + +- https://spring.io/projects/spring-data-r2dbc +- https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/R2DBC/R2DBC\342\200\205\354\202\254\354\232\251.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/R2DBC/R2DBC\342\200\205\354\202\254\354\232\251.md" new file mode 100644 index 00000000..fe1f4f17 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/R2DBC/R2DBC\342\200\205\354\202\254\354\232\251.md" @@ -0,0 +1,104 @@ +--- +title: 'R2DBC 사용' +lastUpdated: '2024-03-02' +--- + +R2DBC를 사용하기 위해서는 의존성을 먼저 추가해주어야 한다. 데이터베이스에 맞는 R2DBC driver와 그 구현체의 의존성을 추가해주자. + +```kotlin +implementation("org.springframework.boot:spring-boot-starter-webflux") +implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") +implementation("com.github.jasync-sql:jasync-r2dbc-mysql:2.0.8") +``` + +그리고 yml 설정을 해준다. + +```yml +spring: + r2dbc: + url: r2dbc:pool:mysql://localhost:3306/test + username: user + password: user +``` + +## Configurations + +R2DBC에서 중요한 인터페이스를 간략하게 살펴보고 넘어가자. + +Spring의 특징인 auto configuration을 통해 자동으로 등록되는 bean이기도 하다. + +### io.r2dbc.spi.ConnectionFactory + +데이터베이스 드라이버와 Connection을 생성하는 인터페이스이다. + +드라이버 구현체에서 이를 구현해서 사용하게 된다. Jasync-sql을 사용하면 JasyncConnectionFactory 클래스가 구현체로 사용된다. + +### org.springframework.r2dbc.core.DatabaseClient + +ConnectionFactory 를 사용하는 non-blocking, reactive client이다. 아래와 같이 사용할 수 있다. + +```java +DatabaseClient client = DatabaseClient.create(factory); +Mono actor = client.sql("select first_name, last_name from t_actor") + .map(row -> new Actor(row.get("first_name", String.class), + row.get("last_name", String.class))) + .first(); +``` + +## Relational Mapping 지원 + +또한, R2dbc에서는 Annotation을 통한(JPA style의) Relational mapping을 지원하지 않는다 [(관련 이슈)](https://github.com/spring-projects/spring-data-r2dbc/issues/356) + +그러므로 Lazy loading, Method name을 통한 Join 등이 불가능하다. 아래는 Spring data r2dbc의 공식 페이지 설명과 이슈 내용이다. + +> Spring Data R2DBC aims at being conceptually easy. In order to achieve this, it does NOT offer caching, lazy loading, write-behind, or many other features of ORM frameworks. This makes Spring Data R2DBC a simple, limited, opinionated object mapper. + +> The reason we cannot provide the functionality yet is that object mapping is a synchronous process as we directly read from the response stream. Issuing sub-queries isn’t possible at that stage.

Other object mapping libraries work in a way, that they collect results and then issue queries for relation population. That correlates basically with collectList() and we’re back to all disadvantages of blocking database access including that auch an approach limits memory-wise consumption of large result sets.

Joins can help for the first level of nesting but cannot solve deeper nesting. We would require a graph-based approach to properly address relationships. + +그러므로 Join이 필요한 상황이라면 직접 Query를 작성해야만 한다고 한다. + +## Repository 정의하기 + +Spring Data 프로젝트는 리액티브 패러다임을 지원하는 `ReactiveCrudRepository`(R2dbcRepository)를 제공하므로 `ReactiveCrudRepository` 인터페이스를 상속하는 인터페이스를 만들어 주면 쉽게 Spring Data R2DBC를 사용할 수 있다. + +> `ReactiveSortingRepository` 또한 존재한다. 하지만 Paging은 지원하지 않는다. + +```kotlin +interface PostRepository : ReactiveCrudRepository +interface PostRepository : ReactiveSortingRepository +``` + +## Service + +이를 이용해 Service logic을 구현하면 다음과 같다. 코드 상으로만 본다면 JPA를 사용할 때와 거의 유사한 흐름대로 작성되며, Input/Ouput만 Reactive type으로 이루어진다. (내부 동작은 당연히 다르다.) + +```kotlin +@Service +class PostService( + private val postRepository: PostRepository +) { + + fun getAll(): Flux { + return postRepository.findAll() + .map(PostResponse::from) + } + + fun getOne(Long postId): Mono { + return postRepository.findById(postId) + .map(PostResponse::from) + } + + fun save(SavePostRequest request): Mono { + return postRepository.save(request.toEntity()) + .then() + } + +} +``` + +--- + +참고 + +- https://www.sipios.com/blog-tech/handle-the-new-r2dbc-specification-in-java +- https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/RouterFunctions.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/RouterFunctions.md" new file mode 100644 index 00000000..4aa73742 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/RouterFunctions.md" @@ -0,0 +1,137 @@ +--- +title: 'RouterFunctions' +lastUpdated: '2024-03-02' +--- + +**RouterFunctions**로 webFlux를 사용해보자. 기본적인 원리와 방식은 **@Controller**에서 모두 설명하였으며 같은 기능의 코드를 테스트할 것이기 때문에 해당 글을 먼저 보고 오는 것을 추천한다. + +우선 GET("/")으로 Flux의 “Hello World"를 반환하는 Routing을 정의한다. + +```java +@Component +public class HelloHandler { + + RouterFunction routes() { + return route(GET("/"), + req -> ok().body(Flux.just("Hello", "World!"), String.class)); + } + +} +``` + +스트림 반환은 아래와 같이 한다. +```java + public Mono stream(ServerRequest req) { + + Stream stream = Stream.iterate(0, i -> i + 1); + + Flux> flux = Flux.fromStream(stream) + .map(i -> Collections.singletonMap("value", i)); + + return ok() + .contentType(MediaType.APPLICATION_NDJSON) + .body(fromPublisher(flux, new ParameterizedTypeReference>(){})); + } +``` + +HelloWebFluxApplication 클래스를 다시 시작하고 /stream에 접속해 보면 무한 JSON Stream이 반환된다. + +```java +$ curl -i localhost:8080/stream + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0HTTP/1.1 200 OK +transfer-encoding: chunked +Content-Type: application/x-ndjson + +{"value":0} +{"value":1} +{"value":2} +{"value":3} +{"value":4} +{"value":5} +{"value":6} +{"value":7} +{"value":8} +{"value":9} +{"value":10} + +... + +``` + +--- + +POST의 경우는 RequestPredicates.POST를 사용하여 라우팅을 정의하는 것과 응답 본문을 ServerRequest.bodyToMono 또는 ServerRequest.bodyToFlux을 사용하면 된다. + +```java +@Component +public class HelloHandler { + + public RouterFunction routes() { + return route(GET("/"), this::hello) + .andRoute(GET("/stream"), this::stream) + .andRoute(POST("/echo"), this::echo); + } + + public Mono hello(ServerRequest req) { + return ok().body(Flux.just("Hello", "World!"), String.class); + } + + public Mono stream(ServerRequest req) { + Stream stream = Stream.iterate(0, i -> i + 1); + Flux> flux = Flux.fromStream(stream) + .map(i -> Collections.singletonMap("value", i)); + return ok().contentType(MediaType.APPLICATION_NDJSON) + .body(fromPublisher(flux, new ParameterizedTypeReference>(){})); + } + + public Mono echo(ServerRequest req) { + Mono body = req.bodyToMono(String.class).map(String::toUpperCase); + return ok().body(body, String.class); + } +``` + +POST /stream도 동일하지만 Request Body를 Generics 형태의 Publisher으로 받을 경우는 ServerRequest.bodyToFlux가 아닌 ServerRequest.body 메소드에 BodyInserters와 반대의 개념인 BodyExtractors을 전달한다. + +```java +@Component +public class HelloHandler { + + public RouterFunction routes() { + return route(GET("/"), this::hello) + .andRoute(GET("/stream"), this::stream) + .andRoute(POST("/echo"), this::echo) + .andRoute(POST("/stream"), this::postStream); + } + + public Mono hello(ServerRequest req) { + return ok().body(Flux.just("Hello", "World!"), String.class); + } + + public Mono stream(ServerRequest req) { + Stream stream = Stream.iterate(0, i -> i + 1); + Flux> flux = Flux.fromStream(stream) + .map(i -> Collections.singletonMap("value", i)); + return ok().contentType(MediaType.APPLICATION_NDJSON) + .body(fromPublisher(flux, new ParameterizedTypeReference>(){})); + } + + public Mono echo(ServerRequest req) { + Mono body = req.bodyToMono(String.class).map(String::toUpperCase); + return ok().body(body, String.class); + } + + public Mono postStream(ServerRequest req) { + Flux> body = req.body(toFlux( // BodyExtractors.toFlux을 static import해야 한다. + new ParameterizedTypeReference>(){} + )); + + return ok().contentType(MediaType.TEXT_EVENT_STREAM) + .body(fromPublisher(body.map(m -> Collections.singletonMap("double", m.get("value") * 2)), + new ParameterizedTypeReference>(){})); + } +} +``` + +> RequestPredicates.GET와 RequestPredicates.POST는 @GetMapping, @PostMapping에 대응되고, HTTP 메소드가 없는@RequestMapping에 대응하는 것은RequestPredicates.path이다.
그리고 이것들은 Functional Interface이므로, 람다 식을 통해 요청 매칭 규칙을 선택적으로 변형할 수 있다. diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebClient.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebClient.md" new file mode 100644 index 00000000..5e9f32e5 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebClient.md" @@ -0,0 +1,195 @@ +--- +title: 'WebClient' +lastUpdated: '2024-03-02' +--- + +외부 서비스와 HTTP로 통신해야 하는 경우 가장 흔한 방법은 RestTemplate을 사용하는 것이다. RestTemplate은 Spring 애플리케이션에서 가장 일반적인 웹 클라이언트지만 블로킹 API이므로 리액티브 기반의 애플리케이션에서 성능을 떨어트리는 원인이 될 수 있다. + +대신 Spring5에서 추가된 WebClient를 사용하면 reactive 기반의 비동기-논블로킹 통신을 구현할 수 있다. + +```kotlin + webClient + .get() + .uri("/users/" + userId) + .retrieve(); +``` + +## 기본 요청 방법 + +http method는 webClient의 get, post, put, head 등의 메서드로 지정해준다. + +image + +### query parameter + +query parameter는 uri builder로 지정해준다. + +```kotlin +webClient.post() + .uri { + it.path("https://example.com") + .queryParam("code", code) + .queryParam("email", email) + .build() + } + .retrieve() +``` + +### body + +```kotlin +webClient.mutate() + .baseUrl("https://some.com/api") + .build() + .post() + .uri("/login") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromFormData("id", idValue) + .with("pwd", pwdValue) + ) + .retrieve() + .bodyToMono(SomeData.class); +``` + +```kotlin +webClient.mutate() + .baseUrl("https://some.com/api") + .build() + .post() + .uri("/login") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .bodyValue(loginInfo) + .retrieve() + .bodyToMono(SomeData.class); +``` + +### 응답 값이 없는 경우 + +응답 값이 없는 요청은 `bodyToMono`에 `Void.class`를 넣어준다. + +```kotlin +webClient.mutate() + .baseUrl("https://some.com/api") + .build() + .delete() + .uri("/resource/{ID}", id) + .retrieve() + .bodyToMono(Void.class) +``` + +## mutate + +`WebClient`는 기존 설정값을 상속해서 사용할 수 있는 `mutate()` 함수를 제공하고 있다. `mutate()`를 통해 `builder()`를 다시 생성하여 추가적인 옵션을 설정하여 재사용이 가능하기 때문에 `@Bean`으로 등록한 `WebClient`는 각 Component에서 의존주입하여 mutate()를 통해 사용하는 것이 좋다. + +```kotlin +val a = WebClient + .builder() + .baseUrl("https://some.com") + .build(); + +val b = a.mutate() + .defaultHeader("user-agent", "WebClient") + .build(); + +val c = b.mutate() + .defaultHeader(HttpHeaders.AUTHORIZATION, token) + .build(); +``` + +WebClient c는 a와 b에 설정된 baseUrl, user-agent 헤더를 모두 가지고 있다. + +`@Bean`으로 등록된 WebClient 는 다음과 같이 사용할 수 있다. + +```kotlin +@Service +class SomeService( + private val webClient: WebClient +) : SomeInterface { + + public Mono getSomething() { + return webClient.mutate() + .build() + .get() + .uri("/resource") + .retrieve() + .bodyToMono(SomeData.class) + } +} +``` + +## retrieve() vs exchange() + +HTTP 호출 결과를 가져오는 두 가지 방법으로 `retrieve()`와 `exchange()`가 존재한다. retrieve를 이용하면 **ResponseBody를 바로 처리** 할 수 있고, `exchange`를 이용하면 **세세한 컨트롤**이 가능하다. 하지만 exchange를 이용하게 되면 Response 컨텐츠에 대한 모든 처리를 직접 하면서 발생할 수 있는 `memory leak` 가능성 때문에 Spring에서는 가급적 retrieve를 사용하기를 권고하고 있다. + +### retrieve + +```kotlin +webClient.get() + .uri("/persons/{id}", id) + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(Person.class); +``` +### exchange + +```kotlin +webClient.get() + .uri("/persons/{id}", id) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .flatMap(response -> response.bodyToMono(Person.class)) +``` + +### 4xx and 5xx 처리 + +HTTP 응답 코드가 4xx 또는 5xx로 내려올 경우 WebClient 에서는 WebClientResponseException이 발생하게 된다. 이 때 각 상태코드에 따라 임의의 처리를 하거나 Exception을 wrapping하고 싶을 때는 `onStatus()` 함수를 사용하여 해결할 수 있다. + +```kotlin +webClient.mutate() + .baseUrl("https://some.com") + .build() + .get() + .uri("/resource") + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .onStatus(HttpStatus::is4xxClientError) { Mono.error(ForbiddenException) } + .bodyToMono(SomeData.class) +``` + +## Synchronous Use + +WebClient 는 Reactive Stream 기반이므로 리턴값을 Mono 또는 Flux 로 전달받게 된다. Spring WebFlux를 이미 사용하고 있다면 문제가 없지만 Spring MVC를 사용하는 상황에서 WebClient를 활용하고자 한다면 Mono나 Flux를 객체로 변환하거나 Java Stream 으로 변환해야 할 필요가 있다. + +이럴 경우를 대비해서 `Mono.block()`이나 `Flux.blockFirst()`와 같은 blocking 함수가 존재하지만 `block()`을 이용해서 객체로 변환하면 Reactive Pipeline 을 사용하는 장점이 없어지고 모든 호출이 main 쓰레드에서 호출되기 때문에 Spring 측에서는 `block()`은 테스트 용도 외에는 가급적 사용하지 말라고 권고하고 있습니다. + +대신 완벽한 Reactive 호출은 아니지만 Lazy Subscribe 를 통한 Stream 또는 Iterable 로 변환 시킬 수 있는 `Flux.toStream()`, `Flux.toIterable()` 함수를 제공하고 있다. + +```kotlin +val res = webClient.mutate() + .baseUrl("https://some.com/api") + .build() + .get() + .uri("/resource") + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToFlux(SomeData.class) + .toStream() + .collect(Collectors.toList()) +``` + +```kotlin +val res = webClient.mutate() + .baseUrl("https://some.com/api") + .build() + .get() + .uri("/resource/{ID}", id) + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .bodyToMono(SomeData.class) + .flux() + .toStream() + .findFirst() + .orElse(defaultValue); +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebFilter.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebFilter.md" new file mode 100644 index 00000000..b58f2f9f --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebFilter.md" @@ -0,0 +1,238 @@ +--- +title: 'WebFilter' +lastUpdated: '2024-03-02' +--- + +WebFilter는 Spring WebMVC의 Filter에 대응되는 클래스이다. WebFilter는 Spring Web package에 있는 클래스이고, WebFlux를 사용할 때만 동작한다. + +## RequestContextWebFilter + +```kotlin +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Mono + +class RequestContextWebFilter( + private val requestContext: RequestContext, + private val requestContextModelFactory: RequestContextModelFactory, + private val instantGenerator: DateTimeGenerator, +) : WebFilter { + override fun filter( + exchange: ServerWebExchange, + chain: WebFilterChain + ): Mono { + val request = exchange.request + requestContext.setContext( + requestContextModelFactory.create( + requestId = request.id, + requestHeaders = request.headers.toMap(), + requestMethod = request.method?.name.toString(), + requestPath = request.path.value(), + userId = request.queryParams.getFirst("userId")?.toString() ?: "null", + requestQueryParams = request.queryParams.toSingleValueMap().toMap(), + requestInstant = instantGenerator.now(), + ) + ) + return chain.filter(exchange) + } +} +``` + +위와 같이 WebFilter를 작성할 수 있다. WebFilter는 `reactor-http-nio-$N`라는 id의 thread에서 실행된다. (N은 랜덤 숫자이다) + +**filter 함수를 호출한 thread**와 **filter 함수가 return한 Mono를 subscribe하는 thread**는 다를 수도 있다. + +## BasicAuthenticationWebFilter + +```kotlin +class BasicAuthenticationWebFilter( + private val authenticationService: AuthenticationService, +) : WebFilter { + + companion object { + const val allowedAuthScheme = "basic" + private val base64Decoder = Base64.getDecoder() + } + + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + val request = exchange.request + val (authenticationScheme, encodedCredential) = request + .headers.getFirst(RequestHeaderKeys.authorization) + ?.split(" ", limit = 2) + ?: throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "Authorization header not found.") + if (authenticationScheme.lowercase() != allowedAuthScheme) + throw ResponseStatusException( + HttpStatus.UNAUTHORIZED, + "$authenticationScheme is not allowed. Use $allowedAuthScheme." + ) + return chain.filter(exchange) + .doOnSubscribe { authenticationService.authenticate(decodeOrThrow(encodedCredential)) } + } + + private fun decodeOrThrow(s: String) = + base64Decoder + .runCatching { decode(s).decodeToString() } + .getOrElse { t -> + if (t is IllegalArgumentException) + throw ResponseStatusException( + HttpStatus.UNAUTHORIZED, + "Invalid credential. base64 decoding failed." + ).initCause(t) + else + throw ResponseStatusException( + HttpStatus.INTERNAL_SERVER_ERROR, + "Server error occurred." + ).initCause(t) + } +} +``` + +[RFC7235](https://datatracker.ietf.org/doc/html/rfc7235#section-4.2) (HTTP/1.1 Authentication), [RFC7617](https://datatracker.ietf.org/doc/html/rfc7617) (Basic HTTP Authentication Scheme) Spec에 따라 간단히 구현한 Authentication WebFilter이다. + +코드는 WebFilter 개념 중점으로 보면 좋을 것 같다. 특별히 설명할 부분은 없다. authenticate() 등이 blocking call일 경우에만 주의하자. + +## WebFilter Order + +이제 2개의 WebFilter가 생겼다. WebFilter를 2개 이상 사용할 경우 순서가 생길 것이다. 이 순서는 때때로 중요하다. (RequestContext → Logging과 같이 불가피하게 순서 의존적일 때) + +가장 좋은 방법은 순서 독립적으로 WebFilter를 구현하는 것이고, 대안은 WebFilter의 순서를 지정하는 것이다. WebFilter를 하나로 뭉쳐서 구현할 수도 있지만, 코드의 재사용성과 유연성이 떨어진다. + +순서는 Spring @Order 어노테이션으로 지정할 수 있다. 이 Order는 하나의 Config 파일에서 확인할 수 있도록 작성하는 것이 좋다. + +```kotlin +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.annotation.Order + +@Configuration +class WebFilterConfig { + @Order(100) + @Bean + fun requestContextWebFilter() = + RequestContextWebFilter( + requestContext(), + requestContextModelFactory(), + dateTimeGenerator(), + ) + @Order(200) + @Bean + fun authenticationWebFilter(authenticationService: AuthenticationService) = + AuthenticationWebFilter(requestContext(), authenticationService) + @Bean + fun requestContext(): ThreadLocalRequestContext = + ThreadLocalRequestContext() + @Bean + fun requestContextModelFactory() = + RequestContextModelFactory() + @Bean + fun dateTimeGenerator() = + DateTimeGenerator() +} +``` + +Order는 `lower is higher`이다. + +## WebFlux Decorator classes + +WebFlux에는 대표적으로 3개의 Decorator 클래스가 있다. + +- ServerWebExchangeDecorator +- ServerHttpRequestDecorator +- ServerHttpResponseDecorator + +세개의 클래스를 활용하여 로깅을 구현하는 예제를 살펴보자. + +### ServerWebExchangeDecorator + +```kotlin +class LoggingWebFilter( + private val requestContext: RequestContext, + private val dateTimeGenerator: DateTimeGenerator, +) : WebFilter { + + companion object : InsideLoggerProvider() + + override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono = + chain.filter(loggingDecoratedExchange(exchange, request, response)) + .doOnError { t -> + val context = requestContext.getContextOrThrow() + .apply { responseTime = dateTimeGenerator.now() } + log.error(t, LoggingType.errorResponseType, context) + } + } + + private fun loggingDecoratedExchange(exchange: ServerWebExchange): ServerWebExchange = + object : ServerWebExchangeDecorator(exchange) { + + override fun getRequest(): ServerHttpRequest = + LoggingDecoratedRequest(exchange.request, requestContext.getContext()) + + override fun getResponse(): ServerHttpResponse = + LoggingDecoratedResponse( + exchange.response, + requestContext.getContext(), + dateTimeGenerator, + ) + } +} +``` + +### ServerHttpRequestDecorator + +```kotlin +class LoggingDecoratedRequest( + delegate: ServerHttpRequest, + private val contextOrNull: RequestContextModel?, +) : ServerHttpRequestDecorator(delegate) { + + override fun getBody(): Flux = + super.getBody().doOnNext { dataBuffer -> + val body = DataBufferUtil.readDataBuffer(dataBuffer) + contextOrNull?.requestPayload = body + logRequest() + } +} +``` + +### ServerHttpResponseDecorator + +```kotlin +class LoggingDecoratedResponse( + delegate: ServerHttpResponse, + private val contextOrNull: RequestContextModel?, +) : ServerHttpResponseDecorator(delegate) { + + companion object : InsideLoggerProvider() + + override fun writeWith(body: Publisher): Mono = + super.writeWith( + Mono.from(body).doOnNext { dataBuffer -> + contextOrNull?.responsePayload = DataBufferUtil.readDataBuffer(dataBuffer) + statusCode?.name?.let { contextOrNull?.statusCode = it } + } + ) +} +``` + +### DataBufferUtil + +```kotlin +object DataBufferUtil { + + fun readDataBuffer(dataBuffer: DataBuffer): String { + + val baos = ByteArrayOutputStream() + return Channels.newChannel(baos) + .runCatching { write(dataBuffer.asByteBuffer().asReadOnlyBuffer()) } + .map { baos.toString() } + .onFailure { t -> if (t is IOException) t.printStackTrace() } + .getOrThrow() + // Closing a ByteArrayOutputStream has no effect. + } +} +``` + +### Conclusion + +WebFlux에서 사용하기 위한 WebFilter와 Decorator의 예제 코드들을 살펴보았다. use case로서 request context와 authentication을 살펴보았다. WebFlux Logging을 구현하기 위해 존재하는 challenge도 간략하게 살펴보았다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebFlux.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebFlux.md" new file mode 100644 index 00000000..d4fd5075 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/WebFlux/WebFlux.md" @@ -0,0 +1,12 @@ +--- +title: 'WebFlux' +lastUpdated: '2024-03-02' +--- + +Spring WebFlux는 **논블로킹(Non-Blocking) 런타임에서 리액티브 프로그래밍**을 할 수 있도록 하는 새로운 Web 프레임워크이다. Spring 5에서 추가되었다. + + + +지금까지 Spring MVC는 서블릿 컨테이너에 `Servlet API`를 기반으로 한 프레임워크이었지만, Spring WebFlux는 Servlet API를 사용하지 않고 **`Reactive Streams`와 그 구현체인 `Reactor`를 기반**으로 한 새로운 HTTP API로 구현되어 있다. 런타임으로서 Netty, Undertow(서블릿 컨테이너가 아닌 방향)와 같은 WAS로 NonBlocking을 사용할 수 있다. 또한 Servlet 3.1에서 도입된 NonBlocking API를 사용하여 Tomcat, Jetty 구현체를 사용할 수도 있다. + +WebFlux를 사용할 때에는, Web MVC와 같이 **@Controller** 어노테이션을 사용하여 기존과 같은 구현방식(하지만 다른 런타임)을 사용할 수도 있고, **Router Functions**라는 람다 기반의 새로운 Controller의 구현 방법을 사용해 비동기처리를 구현할 수도 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Web\342\200\205MVC/Request\342\200\205\354\262\230\353\246\254\352\263\274\354\240\225.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Web\342\200\205MVC/Request\342\200\205\354\262\230\353\246\254\352\263\274\354\240\225.md" new file mode 100644 index 00000000..b93034b7 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Web\342\200\205MVC/Request\342\200\205\354\262\230\353\246\254\352\263\274\354\240\225.md" @@ -0,0 +1,57 @@ +--- +title: 'Request 처리과정' +lastUpdated: '2024-03-02' +--- + +Spring Web MVC 에서 HTTP Request를 처리하는 핵심 클래스는 DispatcherServlet이란 클래스이다. 아 DispatcherServlet은 MVC 아키텍처로 구성된 프레젠테이션 계층을 만들 수 있도록 설계되어 있다. + +서버가 브라우저 등의 HTTP 클라이언트로부터 요청을 받아 처리 후 응답하기까지의 과정을 알아보자. + +![image](https://user-images.githubusercontent.com/81006587/204775740-0a612c04-c871-4710-8b99-44daa50fc176.png) + +## 1. DispatcherServlet의 요청 접수 + +- 자바 서버의 서블릿 컨테이너는 HTTP 프로토콜을 통해 들어오는 요청을 DispatcherServlet에 전달해준다. (이때, URL이나 확장자를 설정해주면 일부 요펑만 매핑되도록 설정할 수도 있다.) + +- DispatcherServlet은 요청을 제일 먼저 받아 원하는 전처리 동작을 수행한 후 다음 클래스에게 일부 동작을 위임하고, 원하는 처리를 완수하여 응답값을 보낼 때 까지의 대략적인 로직을 담고있다. 마치 **Facade 패턴에서의 Facade 클래스**와 비슷한 역할을 하는 것이다. + +## 2. DispatcherServlet에서 컨트롤러로 HTTP 요청 위임 + +- DispatcherServlet이 요청의 URL이나 파라미터 정보, Http 메서드 등을 참고하여 어떤 컨트롤러에게 작업을 위임할지 결정한다. 컨트롤러는 선정하는 것은 DispatcherServlet의 핸들러 매핑 전략을 이용한다. 즉, **사용자의 요청을 기준으로 작업을 어떤 핸들러에게 위임할지**를 정하는 것이다. + +- 이를 전략이라고 부르는 이유는 DI의 가장 대표적인 용도라고 할 수 있는 전략 패턴이 적용되어 있기 때문이다. DispatcherServlet의 핸들러 매핑 전략은 DispatcherServlet의 수정 없이도 DI를 통해 얼마든지 확장 가능하다ㅏ. 어떤 URL이 들어오면 어떤 컨트롤러 오브젝트가 이를 처리하게 만들지를 매핑해주는 전략을 만들어 DI로 제공해주기만 하면 된다. + + > 엄밀히 말하면 DispatcherServlet은 그 자체로 스프링 컨텍스트에 등록되는 빈이 아니므로, DI가 일어나는 것은 아니다. 하지만 마치 DI가 적용되는 것처럼 서블릿 애플리케이션 컨텍스트의 빈들 가져와 사용한다. (AutoWiring 기법 사용) + +- 어떤 컨트롤러/핸들러가 요청을 처리하게 할지를 결정했다면, 다음은 해당 컨트롤러 오브젝트의 메소드를 호출해서 실제로 웹 요청을 처리하는 작업을 위임할 차례다. 그런데 DispatcherServlet이 매핑으로 찾은 컨트롤러를 가져와 싱행하려면 컨트롤러 메소드를 어떻게 호출할지를 알고 있어야한다. + +- DispatcherServlet은 어댑터 인터페이스를 사용해 컨트롤러 오브젝트의 메소드를 호풀하는 방식을 사용한다. 이렇게 하면 항상 일정한 방식으로 컨트롤러를 호출하고 결과를 받을 수 있기 때문에 확장성이 높아진다. 결합도를 낮춰 확장성을 얻은 것이다. + + ```java + DispatcherServlet <--> AdapterA <--> ControllerA + ``` + +- DispatcherServlet이 핸들러 어댑터에 웹 요청을 전달할때는 모든 웹 요청 정보가 담긴 `HttpServletRequest` 타입의 오브젝트를 전달해준다. 이를 어댑터가 적절히 변환해서 컨트롤러의 메소드가 받을 수 있는 파라미터로 변환해서 전달해주는 것이다. + +- HttpServletResponse도 함께 전해준다. 원한다면 리턴하고 싶은 값은 `HttpServletResponse` 안에 결과를 직접 집어넣을 수도 있다. + +## 3. 컨트롤러의 모델 생성과 정보 등록 + +- 컨트롤러가 해야 할 마지막 중요한 두 가지 작업은 모델을 생성하고 모델에 정보를 넣어주는 것이다. (나머지 하나는 뷰에 정보를 넣는 것이다.) + +- 여기서 뷰란 맵 형태의 정보인데, 이름과 그에 대응되는 값의 쌍으로 정보를 만드는 것이다. 만약 jsp를 사용한다면 `.addAttribute(String, Object)`와 같은 함수를 호출하여 값을 집어넣고, `${String}`으로 특정 값을 화면에 보이게 띄울 수 있는데, 이때 값을 집어넣는 그 과정이 바로 모델의 정보를 추가하는 과정이다. + +- 단, Restful api 형태로 개발한다면(`@RestController`) ModelAndView 오브젝트를 사용하지 않는다. + +## 4. 뷰 호출과 모델 참조 + +- 뷰의 논리적인 이름을 리턴해주어 DispatcherServlet의 뷰 리졸버가 그를 이용해 뷰 오브젝트를 생성할 수 있도록 한다. jstlView가 컨트롤러가 돌려준 JSP뷰 템플릿을 바탕으로 적절한 모델의 내용을 삽입하는 등등의 과정을 거쳐 뷰 오브젝트를 만든다. + +## 5. HTTP 응답 돌려주기 + +- 뷰 생성까지의 모든 작업을 마쳤으면 DispatcherServlet은 등록된 후처리기가 있는지 확인하고, 있다면 후처리기에서 후속 작업을 진행한 뒤에 뷰가 만들어준 HttpServletResponse에 담인 최종 결과를 서블릿 컨테이너에게 돌려준다. +- 서블릿 컨테이너는 HttpServletResponse에 담긴 정보를 HTTP 응답으로 만들어 사용자의 브라우저나 클라리런트에게 전송하고 작업을 종료한다. + +--- + +참고: 토비의 스프링 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Web\342\200\205MVC/SpringServletContainerInitializer.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Web\342\200\205MVC/SpringServletContainerInitializer.md" new file mode 100644 index 00000000..d56e1932 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/Web\342\200\205MVC/SpringServletContainerInitializer.md" @@ -0,0 +1,196 @@ +--- +title: 'SpringServletContainerInitializer' +lastUpdated: '2024-03-02' +--- + +`ServletContainerInitializer`(in org.springframework.web) is the interface which allows a library/runtime to be notified of a web application's startup phase and perform any required programmatic registration of servlets, filters, and listeners in response to it. + +The spring also has an implementation for it. That is `ServletContainerInitializer`, When you started a spring apllication, it will be **loaded and instantiated** and have its `onStartup` method **invoked** by any Servlet-compliant container during container startup assuming the the spring-web moodule JAR is present on the classpath. + +Assuming that one or more WebApplicationInitializer types are detected, they will be instantiated Then the `WebApplicationInitializer.onStartup(ServletContext)` method will be invoked on each instance, delegating the ServletContext such that each instance may register and configure servlets(Spring's DispatcherServlet(), listeners(Spring's ContextLoaderListener), or any other Servlet API features(filters). + + + +Below is the code for `SpringServletContainerInitializer`. + +```java +@HandlesTypes(WebApplicationInitializer.class) +public class SpringServletContainerInitializer implements ServletContainerInitializer { + + @Override + public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) + throws ServletException { + + List initializers = Collections.emptyList(); + + if (webAppInitializerClasses != null) { + initializers = new ArrayList<>(webAppInitializerClasses.size()); + for (Class waiClass : webAppInitializerClasses) { //--------------------------------(1) + // Be defensive: Some servlet containers provide us with invalid classes, + // no matter what @HandlesTypes says... + if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && + WebApplicationInitializer.class.isAssignableFrom(waiClass)) { + try { + initializers.add((WebApplicationInitializer) + ReflectionUtils.accessibleConstructor(waiClass).newInstance()); + } + catch (Throwable ex) { + throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); + } + } + } + } + + if (initializers.isEmpty()) { + servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); + return; + } + + servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); + AnnotationAwareOrderComparator.sort(initializers); + for (WebApplicationInitializer initializer : initializers) { + initializer.onStartup(servletContext); //--------------------------------(2) + } + } + +} +``` + +A brief description of the action it performs is as follows : + +1. Repeat the Set received as a parameter to create a Web Application Initializer and put it in the `initializers`. +2. Sort the `initializers` and run the `WebApplicationInitializer#onStartup` method. + +If then, what is WebApplicationInitializer? + +# WebApplicationInitializer + +`WebApplicationInitializer` is the Interface to be implemented in Servlet environments in order to configure the ServletContext programmatically. + +As you can see from above, Implementations of this SPI will be **detected automatically by SpringServletContainerInitializer**, which itself is bootstrapped automatically by any Servlet container. + +```java +public interface WebApplicationInitializer { + + /** + * Configure the given {@link ServletContext} with any servlets, filters, listeners + */ + void onStartup(ServletContext servletContext) throws ServletException; + +} +``` + + + +--- + +```java + public class MyWebAppInitializer implements WebApplicationInitializer { + + @Override + public void onStartup(ServletContext container) { + // Create the 'root' Spring application context + AnnotationConfigWebApplicationContext rootContext = + new AnnotationConfigWebApplicationContext(); + rootContext.register(AppConfig.class); + + // Manage the lifecycle of the root application context + container.addListener(new ContextLoaderListener(rootContext)); + + // Create the dispatcher servlet's Spring application context + AnnotationConfigWebApplicationContext dispatcherContext = + new AnnotationConfigWebApplicationContext(); + dispatcherContext.register(DispatcherConfig.class); + + // Register and map the dispatcher servlet + ServletRegistration.Dynamic dispatcher = + container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext)); + dispatcher.setLoadOnStartup(1); + dispatcher.addMapping("/"); + } + + } +``` + +By implementing this WebApplicationInitializer, you can set the WebApplication in detail based on code. (like above) + +There are several implementations of the `WebApplicationInitializer` that are provided by default. **`AbstractAnnotationConfigDispatcherServletInitializer`** is the base class to initialize Spring application in Servlet container environment, so we will check that class and its parent class. + + + +## AbstractAnnotationConfigDispatcherServletInitializer + +It is constructed like "Decorator Pattern". + +If look at the order of operation from the bottom, + +- `AbstractAnnotationConfigDispatcherServletInitializer` registers the ContextLoaderListener, including the settings of the class with the specific Annotation(`@Configuration`), and creates the ServletApplicationContext, +- `AbstractContextLoaderInitializer` registers the ContextLoaderListener, +- `AbstractDispatcherServletInitializer` creates DispatcherServlet by register ServletFilters on servletContext, + +The order of execution is the order of calls, not the parent-child order. To explain it in detail by looking at the code, + +```java +//in AbstractAnnotationConfigDispatcherServletInitializer.java + @Override + @Nullable + protected WebApplicationContext createRootApplicationContext() { + Class[] configClasses = getRootConfigClasses(); + if (!ObjectUtils.isEmpty(configClasses)) { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.register(configClasses); + return context; + } + else { + return null; + } + } +``` + +1. `AbstractAnnotationConfigDispatcherServletInitializer` registers the ContextLoaderListener, including the settings of the class with the specific Annotation(`@Configuration`), and creates the ServletApplicationContext, + +--- + +```java +//in AbstractContextLoaderInitializer.java + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + registerContextLoaderListener(servletContext); + } + + protected void registerContextLoaderListener(ServletContext servletContext) { + WebApplicationContext rootAppContext = createRootApplicationContext(); //Result of AbstractAnnotationConfigDispatcherServletInitializer's createRootApplicationContext() + if (rootAppContext != null) { + ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); + listener.setContextInitializers(getRootApplicationContextInitializers()); + servletContext.addListener(listener); + } + else { + logger.debug("No ContextLoaderListener registered, as " + + "createRootApplicationContext() did not return an application context"); + } + } + +``` + +2. `AbstractContextLoaderInitializer` registers the ContextLoaderListener, + +--- + +```java +//in AbstractDispatcherServletInitializer + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); //It is equel to AbstractContextLoaderInitializer's onStartup method + registerDispatcherServlet(servletContext); + } + + protected void registerDispatcherServlet(ServletContext servletContext) { + String servletName = getServletName(); + Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty"); + + WebApplicationContext servletAppContext = createServletApplicationContext(); + Assert.state(servletAppContext != null, "createServletApplicationContext() must not return null"); + ... + } +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/@Autowired\342\200\205\353\271\210\342\200\205\354\243\274\354\236\205\342\200\205\354\212\244\354\272\224\342\200\205\354\233\220\353\246\254.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/@Autowired\342\200\205\353\271\210\342\200\205\354\243\274\354\236\205\342\200\205\354\212\244\354\272\224\342\200\205\354\233\220\353\246\254.md" new file mode 100644 index 00000000..3b65e31a --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/@Autowired\342\200\205\353\271\210\342\200\205\354\243\274\354\236\205\342\200\205\354\212\244\354\272\224\342\200\205\354\233\220\353\246\254.md" @@ -0,0 +1,72 @@ +--- +title: '@Autowired 빈 주입 스캔 원리' +lastUpdated: '2024-03-02' +--- + +```java + // AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata + // Autowiring을 해야하는지 여부를 확인하고, 주입 메타데이터를 반환하는 함수 + + private InjectionMetadata buildAutowiringMetadata(Class clazz) { + if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) { + return InjectionMetadata.EMPTY; // Autowired 어노테이션을 가지고 있지 않은 클래스는 Empty를 반환한다. + } + + List elements = new ArrayList<>(); + Class targetClass = clazz; + + do { + final List currElements = new ArrayList<>(); + + ReflectionUtils.doWithLocalFields(targetClass, field -> { + MergedAnnotation ann = findAutowiredAnnotation(field); // Autowired 어노테이션이 붙은 필드를 찾아온다. + if (ann != null) { // Autowired 어노테이션이 존재하는 필드라면 + if (Modifier.isStatic(field.getModifiers())) { // static이 아닌지 확인하고, static이면 빈 값을 반환한다. + if (logger.isInfoEnabled()) { + logger.info("Autowired annotation is not supported on static fields: " + field); + } + return; + } + boolean required = determineRequiredStatus(ann); // requeired인지 (꼭 여기서 주입해야하는지) 확인 + currElements.add(new AutowiredFieldElement(field, required)); // 주입할 필드로 저장해놓는다. + } + }); + + ReflectionUtils.doWithLocalMethods(targetClass, method -> { + Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); + if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { + return; + } + MergedAnnotation ann = findAutowiredAnnotation(bridgedMethod); // Autowired 어노테이션이 붙은 bridgedMethod를 찾아온다. + if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { // abstact가 아니면 + if (Modifier.isStatic(method.getModifiers())) { // static이 아닌지 확인하고, + if (logger.isInfoEnabled()) { + logger.info("Autowired annotation is not supported on static methods: " + method); + } + return; + } + if (method.getParameterCount() == 0) { // 파라미터가 존재하는지 확인하고, + if (logger.isInfoEnabled()) { + logger.info("Autowired annotation should only be used on methods with parameters: " + + method); + } + } + boolean required = determineRequiredStatus(ann); // requeired인지를 확인해서 + PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); // 빈이 존재하는지 조회하고 정보를 받아온다음 + currElements.add(new AutowiredMethodElement(method, required, pd)); // 주입할 메서드로 저장해놓는다. + } + }); + + elements.addAll(0, currElements); // elements에 옮긴다. + targetClass = targetClass.getSuperclass(); // 상위클래스로 올라가면서 탐색한다. (Object에서 멈춤) + } + while (targetClass != null && targetClass != Object.class); + + return InjectionMetadata.forElements(elements, clazz); // metadata로 변환하여 반환. + } + + protected boolean determineRequiredStatus(MergedAnnotation ann) { // requeired인지 (꼭 여기서 주입해야하는지) 확인하는 메서드 + return determineRequiredStatus(ann. asMap( + mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()))); + } +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/@ComponentScan.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/@ComponentScan.md" new file mode 100644 index 00000000..271789e2 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/@ComponentScan.md" @@ -0,0 +1,31 @@ +--- +title: '@ComponentScan' +lastUpdated: '2024-03-02' +--- + +`@ComponentScan`은 객체를 패키지에서 Scan하여 빈으로 등록해주는 어노테이션이다. + +보통은 `@Component`나 `@Service`, `@Application`등의 지정된 어노테이션을 가지고 있는 객체를 경로의 전체 패키지에서 탐색하여 등록하지만 Scan할 패키지나 어노테이션, 조건 등을 custom하여 마음대로 지정할 수도 있다. + + +기본적인 어노테이션들은 `@SpringBootApplication`에 달려있는 `@ComponentScan`에 의해 빈으로 등록된다. + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), + @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) +public @interface SpringBootApplication { + ... +} +``` + +`@ComponentScan`은 `BeanFactoryPostProcessor`를 구현한 `ComfigurationClassPostProcessor`에 의해 동작한다. + +`BeanFactoryPostProcessor`는 다른 모든 Bean들을 만들기 이전에 `BeanFactoryPostProcessor`의 구현체들을 모두 적용한다. 즉, 다른 Bean들을 등록하기 전에 컴포넌트 스캔을해서 Bean으로 등록해준다. + +`@Autowired`의 `BeanPostProcessor`와 비슷하지만, 실행 시점이 다르다. `@ComponentScan`은 Bean으로 등록해야하는 객체를 찾아 `BeanFactoryPostProcessor`의 구현체를 적용하여 Bean으로 등록해주는 과정이고 `@Autowired`는 등록된 다른 Bean을 찾아 `BeanPostProcessor`의 구현체를 적용하여 의존성 주입을 적용하는 역할을 하는 것이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Ioc\354\231\200\342\200\205DI.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Ioc\354\231\200\342\200\205DI.md" new file mode 100644 index 00000000..e23f29aa --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Ioc\354\231\200\342\200\205DI.md" @@ -0,0 +1,25 @@ +--- +title: 'Ioc와 DI' +lastUpdated: '2023-12-13' +--- +## 🍃 제어의 역전 + +### IoC (Inversion of Control) + - 기존에는 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행하는 프로그램. 즉, 구현 객체가 프로그램의 제어 흐름을 조정하는 프로그램이 많았다. + - 하지만 이런 방식은 코드의 재사용성과 유지보수성이 낮다. + - 하지만 그와 반대로 프로그램에 대한 제어 권한을 외부에서 가지고 있는 것을 제어의 역전(Ioc)이라고 한다. + - IoC는 객체지향성을 잃지 않고 유지보수성이 높은 코드를 만들 수 있게 해준다. + - (+ 프레임워크는 작성된 코드를 제어하고 대신 실행해주는 역할을 한다. Spring도 이와 같은 프레임워크의 일종이다.) + +### IoC의 장점 +- 애플리케이션 코드의 양을 줄일 수 있다. +- 클래스 간의 결합을 느슨하게 한다. +- 애플리케이션의 테스트와 유지 관리를 쉽게 해 준다. + +## 의존성 주입 + +### DI (Dependency Injection) +IoC를 구현하기 위한 방법 중 하나이다. 말 그대로 어떤 객체에 스프링 컨테이너가 또 다른 객체와 의존성을 맺어주는 행위를 말한다. 의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다. + +## Ioc 컨테이너, DI 컨테이너 +객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다. 어셈블러, 오브젝트 팩토리 등으로 불리기도 한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Programmatic\352\263\274\342\200\205Declarative.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Programmatic\352\263\274\342\200\205Declarative.md" new file mode 100644 index 00000000..3e231110 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Programmatic\352\263\274\342\200\205Declarative.md" @@ -0,0 +1,61 @@ +--- +title: 'Programmatic과 Declarative' +lastUpdated: '2024-03-02' +--- + +사실은 Spring에 관련된 개념은 아니지만, 스프링을 공부하면서 헷갈렸던 부분이라 정리해본다. 또한, 스프링을 공부할때 알면 더 많은 것을 이해하는데 도움이 될 것이다. + +어느날, Spring의 공식 Docs의 트랜잭션 부분을 보면서 이러한 부분을 발견하게 되었다. + +> **Declarative Transaction Management**
Most Spring Framework users choose declarative transaction management. This option has the least impact on application code and, hence, is most consistent with the ideals of a non-invasive lightweight container.
The Spring Framework offers declarative rollback rules, a feature with no EJB equivalent. Both programmatic and declarative support for rollback rules is provided. + + +선언적 트랜잭션 관리에 대한 설명이다. + +직역해보자면 아래와 같다. + +> **선언적 트랜잭션 관리**
대부분의 Spring Framework 사용자는 선언적 트랜잭션 관리를 선택합니다. 이 옵션은 애플리케이션 코드에 가장 적은 영향을 미치므로 비침습적 경량 컨테이너의 이상과 가장 일치합니다.
스프링 프레임워크는 EJB와 동등한 기능이 없는 선언적 롤백 규칙을 제공합니다. 롤백 규칙에 대한 명령적, 선언적 지원이 모두 제공됩니다. + +비침습적 경량 컨테이너라는 것은 Spring API에서 제공하는 사전 정의된 클래스 또는 인터페이스에서 클래스를 확장하거나 구현하도록 강요하지 않는다는 뜻..인데 이것에 대해 궁금하다면 다른 자료를 더 찾아보시면 좋을 것 같고, 여기선 `선언적`이라고 말하는 부분에 대해 집중해보자. + +선언적이라는게 대체 무슨 뜻일까? + +--- + +## 선언형(Declarative), 명령형(Programmatic)? + +간단하게 설명하자면 명령형 프로그래밍은 문제를 어떻게 해결해야하는지 컴퓨터에게 명시적으로 명령을 내리는 방법을 의미하고, 선언형 프로그래밍은 무엇을 해결할 것인지에 보다 집중하여 어떻게 문제를 해결하는지에 대해서는 컴퓨터에게 위임하는 방법이다. + +현실에서 예시를 들어보도록 하자. + +### Red Lobster + +당신은 회사에서 너무 오랜 시간 자바스크립트를 다루느라 피곤해졌다.
+그리고 이를 달래기 위해 퇴근 후에 아내와 함께 'Red Lobster' 식당에 근사한 데이트를 하러 갔다.
+당신은 Red Lobster에 도착했고, 프론트 데스크에 가서 다음과 같이 말했다. + +- **명령형 접근(HOW)** : "저기 Gone Fishin' 이라고 적힌 표지판 아래에 있는 테이블이 비어있네요. +우리는 저기로 걸어가서 저 테이블에 앉도록 하겠습니다." +- **선언형 접근(WHAT)** : "2명 자리 주세요." + +명령형 방식은 내가 실제로 자리에 어떻게 앉을지에 관심이 있다.
+이를 위해 나는 내가 어떻게 테이블을 잡아서 자리에 앉을지에 관해, 필요한 단계들을 하나하나 나열해야 한다.
+반면, 선언형 방식은 오로지 내가 무엇을 원하는지에 관심이 있다. `2명의 자리`를 원한다는 정보만 전달하면, 그것은 선언형 접근의 끝이다. + +### Wal-Mart + +친구가 당신의 집에 집들이를 오기 위해 Wal-Mart에서 선물을 구입했다.
+현재 친구는 Wal-Mart 바로 옆에 있으며, 당신의 집에 어떻게 도달해야 하는지를 전화로 질문했다. + +- **명령형 접근(HOW)** : "주차장 북쪽 출구로 나와서 좌회전을 해. 12번가 출구에 도착할 때까지 I-15 북쪽 도로를 타고 와야 해. 거기서 IKEA에 가는 것처럼 출구에서 우회전을 해. 그리고 거기서 직진하다가 첫 번째 신호등에서 우회전을 해. 그 다음에 나오는 신호등을 통과한 후에 좌회전을 하면 돼. 우리 집은 #298 이야."
+- **선언형 접근(WHAT)** : "우리 집 주소는 298 West Immutable Alley, Eden, Utah 84310 이야." + +위의 예시에서 볼 수 있듯, 명령형 접근은 목적을 **무엇을 어떻게** 달성할지의 방법을 아주 상세히 기술하는 방법이고 선언형 접근은 **무엇을** 달성할지의 내용만을 얘기하는 방법이다. + +명령형으로 코드를 작성하기 위해선 사람이 그 구현 내용을 직접 적어주면 된다. 하지만 선언형으로 코드를 작성하기 위해선 그 구현 내용이 미리 존재해야 한다. 즉, '어떻게 접근하는가'에 관한 내용이 먼저 추상화 되어있어야 한다. + +명령형 접근과 선언형 접근은 사용하는 언어 또는 프레임워크에 따라 무조건 하나만 사용해야할 수도 있고, 둘중 하나를 선택해야할 수도 있다. + +## 결론 + +결론적으로 위에서 얘기했던 `선언적 트랜잭션 관리`는 트랜잭션을 관리할 상태만 사람이 작성하면, 내부적인 내용을 스프링 프레임워크가 추상화를 통해 구현한다는 의미였다! diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Reflection\352\263\274\342\200\205\354\247\201\353\240\254\355\231\224.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Reflection\352\263\274\342\200\205\354\247\201\353\240\254\355\231\224.md" new file mode 100644 index 00000000..9c688ed8 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/Reflection\352\263\274\342\200\205\354\247\201\353\240\254\355\231\224.md" @@ -0,0 +1,37 @@ +--- +title: 'Reflection과 직렬화' +lastUpdated: '2024-03-02' +--- + +`Reflection`은 런타임에 동적으로 클래스들의 정보를 알아내고, 실행할 수 있는 것을 말한다. + +`Reflection`은 프로그래머가 데이터를 보여주고, 다른 포맷의 데이터를 처리하고, 통신을 위해 serialization(직렬화)을 수행하고, bundling을 하기 위해 일반 소프트웨어 라이브러리를 만들도록 도와준다. + +java와 같은 객체지향 프로그래밍언어에서 Reflection을 사용하면 컴파일 타임에 인터페이스, 필드, 메소드의 이름을 알지 못해도 실행중에 접글할 수 있다. 또, 멤버 접근 가능성 규칙을 무시하여 private 필드의 값을 변경할 수 있다. + +## 직렬화 + +jackson은 java.lang reflection 라이브러리를 사용한다. + +기본생성자가 있는 경우에는 _constructor.newInstance()를 사용하여, 객체를 생성한다. + +```java +@Override +public final Object call() throws Exception { + return _constructor.newInstance(); +} +``` + +기본 생성자가 없는 경우에는 _constructor.newInstance(Object[] args) 또는 _constructor.newInstance(Object arg) 등을 사용하여 생성한다. + +```java +@Override +public final Object call(Object[] args) throws Exception { + return _constructor.newInstance(args); +} + +@Override +public final Object call1(Object arg) throws Exception { + return _constructor.newInstance(arg); +} +``` diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\353\223\261\353\241\235\353\220\234\342\200\205\353\271\210\342\200\205\353\252\251\353\241\235\342\200\205\354\266\234\353\240\245\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\353\223\261\353\241\235\353\220\234\342\200\205\353\271\210\342\200\205\353\252\251\353\241\235\342\200\205\354\266\234\353\240\245\355\225\230\352\270\260.md" new file mode 100644 index 00000000..8d43e601 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\353\223\261\353\241\235\353\220\234\342\200\205\353\271\210\342\200\205\353\252\251\353\241\235\342\200\205\354\266\234\353\240\245\355\225\230\352\270\260.md" @@ -0,0 +1,59 @@ +--- +title: '등록된 빈 목록 출력하기' +lastUpdated: '2024-03-02' +--- + +Spring에서는 애플리케이션을 구성하는 몇몇 클래스들을 싱글톤으로 관리할 수 있도록 '빈'을 등록한다. 해당 객체는 애플리케이션 실행 과정에서 주요한 정보를 설정하거나, 로직을 수행하는 역할을 수행한다. + +빈은 자바 애플리케이션을 실행하여 Spring이 initializing 된 후에 스캔을 통해 컨텍스트에 등록되는데, 이렇게 등록된 빈의 목록을 출력하려면 아래와 같은 코드를 작성하여 클래스 경로 내에 넣어주면 된다. + +```java +//in java +@Component +public class BeanListPrinter implements CommandLineRunner { + + private ApplicationContext ac; + BeanListPrinter(ApplicationContext ac) { + this.ac = ac; + } + + @Override + public void run(String... args) { + String[] beanDefinitionNames = ac.getBeanDefinitionNames(); + Arrays.sort(beanDefinitionNames); + + for (String beanDefinitionName : beanDefinitionNames) { + System.out.println(beanDefinitionName); + } + } + +} +``` + +```kotlin +//in kotlin +@Component +class BeanListPrinter( + private val ac: ApplicationContext +): CommandLineRunner { + override fun run(args: Array) { + val beans = ac.beanDefinitionNames; + Arrays.sort(beans) + + for(i in beans.indices) { + println(beans[i]) + } + } +} +``` + +> 클래스 명은 아무거나 적어도 된다. + +CommandLineRunner(`org.springframework.boot.CommandLineRunner`)를 상속받은 클래스를 Component(Bean)으로 지정해주면 Spring이 올라간 후에 run 메소드를 자동으로 invoke 해준다. run 메소드를 원하는대로 재정의해주면 해당 코드가 초기단계에서 한번 실행되도록 할 수 있다. + +run 메소드 안에는, 빈 정보를 가지고 있는 ApplicationContext(`org.springframework.context.ApplicationContext`)에서 `getBeanDefinitionNames()`를 호출하여 출력하는 for문을 넣어주었다. 그리고 원하는 빈을 찾기 수월하도록, 출력 전 정렬을 해줬다. + +이렇게 하면 빈 목록이 잘 출력되는 것을 볼 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/209598379-3137fc7e-e802-42e1-947c-d9e034f1ac66.png) + diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\353\271\210.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\353\271\210.md" new file mode 100644 index 00000000..7eb79931 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\353\271\210.md" @@ -0,0 +1,39 @@ +--- +title: '빈' +lastUpdated: '2024-03-02' +--- + +## 스프링 컨테이너(Spring Container) +스프링에서 DI를 이용하여 애플리케이션을 구성하는 여러 빈(Bean)들의 생명주기(Lifecycle)와 애플리케이션의 서비스 실행 등을 관리하며 생성된 인스턴스들에게 기능을 제공하는 것을 부르는 말이다. +프로그래머의 개입 없이도 컨테이너에 설정된 내용에 따라 빈을 알아서 참조하고 관리한다. +대표적으론 BeanFactory와 ApplicationContext 가 있다. + +## BeanFactory +스프링 컨테이너의 최상위 인터페이스로서, 스프링 빈을 관리하고 조회하는 역할을 담당힌디. +getBean() 을 제공한다. +지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다. + +## ApplicationContext +BeanFactory 기능을 모두 상속받아서 제공한다. 빈을 관리하고 조회하는 기능 말고도 환경변수를 처리하는 기능, 메시지를 국제화해주는 기능, 리소스를 편리하게 조회할 수 있게 하는 기능 등등의 부가기능도 함께 구현되어있다. + + +## BeanDefinition +BeanDefinition은 빈 설정 메타정보인데, 스프링은 이 인터페이스를 통해 다양한 설정 형식을 지원한다. @Bean, 당 각각 하나씩 메타 정보가 생성되고 +스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다. BeanDefinition는 reader로 코드를 직접 스캔해서 메타 정보를 만들어준다. + +- AnnotationConfigApplicationContext 는 AnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition 을 생성한다. +- GenericXmlApplicationContext 는 XmlBeanDefinitionReader 를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition 을 생성한다. + +### BeanDefinition 정보 + - Scope: 싱글톤(기본값) + - lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 +생성을 지연처리 하는지 여부 + - InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명 + - DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명 + #### 팩토리 메서드 등록이 아닐때만 사용 + - BeanClassName: 생성할 빈의 클래스 명 + - Constructor arguments, Properties: 의존관계 주입에서 사용한다. + #### 팩토리 메서드 등록에서만 사용 + - factoryBeanName: 팩토리 역할을 수행하는 빈의 이름 + - factoryMethodName: 빈을 생성할 팩토리 메서드 지정 + diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\354\204\240\354\240\220\342\200\205\354\236\240\352\270\210\352\263\274\342\200\205\353\271\204\354\204\240\354\240\220\342\200\205\354\236\240\352\270\210.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\354\204\240\354\240\220\342\200\205\354\236\240\352\270\210\352\263\274\342\200\205\353\271\204\354\204\240\354\240\220\342\200\205\354\236\240\352\270\210.md" new file mode 100644 index 00000000..c7bbd3b7 --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\354\204\240\354\240\220\342\200\205\354\236\240\352\270\210\352\263\274\342\200\205\353\271\204\354\204\240\354\240\220\342\200\205\354\236\240\352\270\210.md" @@ -0,0 +1,118 @@ +--- +title: '선점 잠금과 비선점 잠금' +lastUpdated: '2024-03-02' +--- + +![image](https://user-images.githubusercontent.com/81006587/210163834-79471d37-754d-4d85-887d-c79ee02b3025.png) + +운영자 스레드와 고객 스레드는 같은 주문 애그리거트를 나타내는 다른 객체를 구하게 된다. 운영자 스레드와 고객 스레드는 개념적으로 동일한 애그리거트이지만 물리적으로 서로 다른 애그리거트 객체를 사용한다. + +위 그림과 같은 상황에서 두 스레드는 각각 트랜잭션을 커밋할 때 수정한 내용을 DB에 반영하는데, 상태가 서로 충돌되기 때문에 애그리거트의 일관성이 깨진다. 이 순서의 문제점은 운영자는 기존 배송지 정보를 이용해서 배송 상태로 변경했는데 그 사이 고객은 배송지 정보를 변경했다는 점이다. 즉 애그리거트의 일관성이 깨지는 것이다. 이런 문제가 발생하지 않도록 하려면 다음 두 가지 중 하나를 해야한다. + +- 운영자 배송지 정보를 조회하고 상태를 변경하는 동안 고객이 애그리거트를 수정하지 못하게 막는다. +- 운영자가 배송지 정보를 조회한 이후 고객이 배송지 정보를 변경하면 운영자가 애그리거트를 다시 조회한 뒤 수정하도록 한다. + +이 두 가지는 애그리거트 자체의 트랜잭션과 관련이 있는데, 이를 구현하기 위해선 DBMS가 지원하는 트랜잭션과 함께 애그리거트를 위한 추가적인 트랜잭션 처리 기법이 필요하다. 애그리거트에 대해 사용할 수 있는 대표적인 트랜잭션 처리 방식에는 **선점 잠금**과 **비선점 잠금**의 두 가지 방식이 있다. + +--- + +## 선점 잠금 + +선점 잠금은 먼저 애그리거트를 구한 스레드가 애그리거트 사용이 끝날 때까지 다른 스레드가 해당 애그리거트를 수정하는 것을 막는 방식이다. (Pessimistic Lock) + +![image](https://user-images.githubusercontent.com/81006587/210164216-64ba749b-a1e2-4bc3-bcea-b380aff8f197.png) + +선점 잠금을 사용하면 스레드1이 애그리거트를 구한 뒤 이에서 스레드2가 같은 애그리거트를 구하고 있는데, 이 경우 스레드2는 스레드1이 애그리거트에대한 잠금을 해제할 때 까지 블로킹된다. + +스레드1이 애그리거트를 수정하고 트랜잭션을 커밋하면 잠금을 해제한다. 이 순간 대기하고 있던 스레드2가 애그리거트에 접근하게 된다. 스레드1이 트랜잭션을 커밋 뒤에 스레드2가 애그리거트를 구하게 되므로 스레드2는 스레드1이 수정한 애그리거트의 내용을 보게된다. + +한 스레드가 애그리거트를 구하고 수정하는 동안 다른 스레드가 수정할 수 없음므로 동시에 애그리거트를 수정할 때 발생하는 데이터 충돌 문제를 해소할 수 있다. + +선점 잠금은 보통 DBMS가 제공하는 행 단위 잠금을 사용해서 구현한다. 오라클을 비롯한 다수 DBMS가 for update와 같은 쿼리를 사용해서 특정 레코드에 한 사용자만 접근할 수 있는 잠금 장치를 제공한다. + +JPA의 EntityManager는 LockModeType을 인자로 받는 find() 메서드를 제공하는데, `LockModeType.PESSIMISTIC_WRITE`를 값으로 전달하면 해당 엔티티와 매핑된 테이블을 이용해서 선점 잠금 방식을 적용할 수 있다. + +```java +Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE) +``` + +JPA 프로바이더와 DBMS에 따라 잠금 모드의 구현이 다른데, 하이버네티으의 경우 PESSIMISTIC_WRITE를 잠금 모드로 사용하면 for update 쿼리를 사용해서 선점 잠금을 구현한다. + +### 선점 잠금과 교착상태 + +선점 잠금 기능을 사용할 떄는 잠금 순서에 따른 교착 상태가 발생하지 않도록 주의해야 한다. 예를 들어, 다음과 같은 순서로 두 스레드가 선점 잠금을 시도를 한다고 해보자. + +```js +스레드 1 : A 애그리거트에 대한 선점 잠금 구함 +스레드 2 : B 애그리거트에 대한 선점 잠금 구함 +스레드 1 : B 애그리거트에 대한 선점 잠금 시도 +스레드 2 : A 애그리거트에 대한 선점 잠금 시도 +``` + +이 두 스레드는 상대방 스레드가 먼저 선점한 잠금을 구할수 없어 더 이상 다음 단계를 진행하지 못하게 된다. 즉 스레드 1과 스레드 2는 교착상태에 빠지게 된다. + +선점 잠금에 따른 교착 상태는 상대적으로 사용자 수가 많을 때 발생할 가능성이 높고, 사용자 수가 많아지면 교착 상태에 빠지는 스레드가 더 빠르게 증가하게 된다. 더 많은 스레드가 교착 상태에 빠질수록 시스템은 점점 아무것도 할 수 없는 상황에 이르게 된다. + +이런 문제가 발생하지 않도록 하려면 잠금을 구할 때 최대 대기 시간을 지정해야한다. JPA에서 선점 잠금을 시도할 때 최대 대기 시간을 지정하려면 다음과 같이 힌트를 사용하면 된다. + +```java +Map hints = new HashMap<>(); +hints.put("javax.persistence.lock.timeout", 2000); +Order order = entityManager.find(Order.class, orderNo, LockModeType.PESSIMISTIC_WRITE, hints); +``` + +JPA의 javax.persistence.lock.timeout 힌트는 잠금을 구하는 대기 시간을 밀리초 단위로 지정한다. 지정한 시간이내에 잠금을 구하지 못하면 익셉션을 발생 시킨다. + +DBMS에 따라 힌트가 적용되지 않을 수 있기 때문에 관련 기능을 지원하는지 확인해야 한다. + +스프링 데이터 JPA는 @QueryHints 어노테이션을 사용해 쿼리 힌트를 지정할 수 있다. + +--- + +## 비선점 잠금 + +비선점 잠금은 직접적으로 잠금하지 않고 버전을 통해 트랜잭션 결과의 정합성을 지키는 것이다. (Optimistic Lock) + + + +두 요청이 동시에 들어와서 정보를 수정했다면, 선점잠금은 마지막에 끝나는 트랜잭션의 결과가 남는 반면에 비선점 잠금은 먼저 끝난 트랜잭션의 결과가 유지된다. + +JPA는 @Version 어노테이션을 사용해 비선점 잠금 기능을 구현할 수 있다. + +```java +@Controller +public class OrderAdminController { + private StartShippingService startShippingService; + + @RequestMapping(value = "/startShipping", method = RequestMethod.POST) + public String startShipping(StartShippingRequest startReq) { + try { + startShippingService.startShipping(startReq); + return "shippingStarted"; + } catch(OptimisticLockingFailureException | VersionConflicException ex) { + // 트랜잭션 충돌 + return "startShippingTxConflict"; + } + } + ... +``` + +다음 코드는 스프링 프레임워크가 발생시키는 OptimisticLockingFailureException과 응용 서비스에서 발생시키는 VersionConflicException을 처리하고 있다. + +`VersionConflicException`은 이미 누군가가 애그리거트를 수정했다는 것을 의미하고, `OptimisticLockingFailureException`은 누군가가 거의 동시에 수정했다는 것을 의미한다. + +### 강제 버전 증가 + +JPA는 애그리거트 루트가 아닌 다른 엔티티가 변경되었을 때 루트 엔티티 자체의 값은 바뀌지 않으므로 버전 값을 갱신하지 않는다. + +하지만 애그리거트 관점에서 보았을 때 애그리거트의 구성요소가 바뀌면 논리적으로 애그리거트도 바뀐 것이다. + +이러한 경우에 버전을 올려주고 싶다면 `LockModeType.OPTIMISTIC_FORCE_INCREMENT` 옵션을 사용할 수 있다. + +--- + +참고 + +https://cheese10yun.github.io/transaction-lcok/ + +https://github.com/softpeanut/dul-dul-dul/blob/main/도메인%20주도%20개발%20시작하기/Chapter08.%20애그리거트%20트랜잭션%20관리/01.%20애그리거트와%20트랜잭션.md \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\354\213\261\352\270\200\355\206\244.md" "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\354\213\261\352\270\200\355\206\244.md" new file mode 100644 index 00000000..62db81ed --- /dev/null +++ "b/src/content/docs/TIL/\354\212\244\355\224\204\353\247\201\342\200\205Spring/\352\270\260\353\263\270\354\233\220\353\246\254/\354\213\261\352\270\200\355\206\244.md" @@ -0,0 +1,29 @@ +--- +title: '싱글톤' +lastUpdated: '2024-03-02' +--- + 웹 애플리케이션은 보통 여러 고객이 요청을 동시에 보내는데, 이럴 때 마다 새로운 객체를 생성해서 반환하는 것은 메모리 낭비가 너무 심하다. 그래서 고안된 것이 생성된 하나의 객체를 공유해서 사용하도록 설계한 '싱글톤 패턴'이다. +싱글톤 패턴은 이미 만들어진 객체를 재사용하기 때문에 객체를 생성하는 데 메모리와 시간을 쓰지 않아도 되니 아주 효율적이다! 하지만 싱글톤 패턴도 단점이 있다. + +### 싱글톤 패턴의 문제점 + - 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다. + - 의존관계상 클라이언트가 구체 클래스에 의존한다. DIP를 위반한다. + - 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다. + - 테스트하기 어렵다. + - 내부 속성을 변경하거나 초기화 하기 어렵다. + - private 생성자로 자식 클래스를 만들기 어렵다. + - 결론적으로 유연성이 떨어진다. + - 안티패턴으로 불리기도 한다 + +그래서 스프링은 이 싱글톤 패턴을 더 잘 사용할 수 있도록 싱글톤 컨테이너를 관리해주는 기능을 지원하고 있다. + +## 싱글톤 컨테이너 +스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다. 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다. +스프링 컨테이너의 이런 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 +있다. +스프링을 쓰면 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 되고, DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다. + +### 싱글톤 방식의 주의점 +객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 무상태(stateless)로 설계해야 한다. +특정 클라이언트에 의존적이거나, 값을 변경할 수 있는 필드가 있으면 절대 안된다. +변수가 필요하다면 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/GraphQL.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/GraphQL.md" new file mode 100644 index 00000000..42d38579 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/GraphQL.md" @@ -0,0 +1,67 @@ +--- +title: 'GraphQL' +lastUpdated: '2024-03-02' +--- + +GraphQL은 정확하게 데이터를 요청하는 방법을 설명하는 구문이다. 서로를 참조하는 복잡한 엔티티를 사용하는 애플리케이션의 데이터 모델을 사용하고, 그것을 다양한 방식으로 조회해야 할때 유용하게 사용된다. + +최근 GraphQL 생태계는 Apollo, GraphiQL, GraphQL Explorer와 같은 강력한 도구와 라이브러리로 확장되고 있다. + +## GraphQL 동작방식 + +GraphQL은 GraphQL API에서 만들 수 있는 모든 쿼리와 반환하는 모든 타입에 대해 설명하는 스키마를 정의하는 것으로 시작한다. 스키마 정의 언어(SDL)에서는 엄격한 타입 정의를 사용하기 때문에 스키마 정의가 쉽지 않다. + +쿼리 전에 스키마를 통해 쿼리에 대한 유효성 검사를 한 뒤 본 요청이 백엔드 애플리케이션에 도달하면, 전체 스키마를 통해 GraphQL 작업이 해석된 후, 프론트엔드 애플리케이션에 응답할 데이터가 생성된다. 하나의 대규모 쿼리를 서버에 요청하면 API는 요청한 데이터의 형태와 정확히 일치하는 JSON 데이터를 응답으로 반환한다. + +GraphQL에는 RESTful한 CRUD 작업 외에도 서버로부터 실시간 알림을 받는 구독이 있다. + +## GraphQL의 장점 + +### 1. 스키마에서 타입 사용 + +- GraphQL은 무엇을 할 수 있는지 미리 게시하여 검색 성능을 높인다. GraphQL API에서 클라이언트에 접근하면 사용 가능한 쿼리를 확인할 수 있다. + +### 2. 그래프 형태의 데이터에 유용함 + + - GraphQL은 연결된 관계의 데이터를 다루는데 효율적이다. (하지만 단순한 구조의 데이터를 다루는데에는 효율성이 떨어진다.) + +### 3. 비슷한 API를 계속 만들지 않아도 됨 + + - REST는 여러 버전의 API를 제공하지만, GraphQL은 발전하는 단일 버전을 사용하여 새로운 기능을 언제든 사용할 수 있고, 서버 코드를 더 깔끔하고 유지 관리가 용이하게 작성할 수 있다. + +### 4. 상세한 에러 메시지 + +- GraphQL은 SOAP과 유사한 방식으로 발생한 에러에 대한 세부 정보를 제공한다. 에러 메시지는 resolver와 에러가 발생한 쿼리 부분에 대한 정보를 명시한다. + +### 5. 유연한 권한 + +- GraphQL에서는 개인 정보를 보호하며 특정 기능을 선택적으로 노출할 수 있다. 반면 REST 아키텍처는 데이터를 부분적으로 공개할 수 없다. 데이터 전부가 노출되거나 전부 보호된다. + +## GraphQL의 단점 + +### 1. 성능 문제 + +- GraphQL은 하나의 요청에 너무 많은 중첩 필드가 있는 경우, 시스템에 과부하가 발생한다. + +### 2. 캐싱의 어려움 + +- GraphQL은 HTTP 캐싱 시멘틱을 재사용하지 않기 때문에 사용자 정의 캐싱을 구현하기 어렵다. + +### 3. 러닝커브 + +- 배우고 사용하는데 시간이 필요하다. + +## GraphQL 사용예시 + +### 모바일 API 개발 + +네트워크 성능과 단일 메시지의 페이로드 최적화가 중요하다. 따라서 GraphQL은 모바일 기기에서 보다 효율적인 데이터 로딩을 제공한다. + +### 복잡한 시스템과 마이크로서비스 + +GraphQL은 API 뒤에 있는 여러 시스템 통합의 복잡성을 숨길 수 있다. GraphQL은 여러 시스템으로부터 데이터를 집계하여 하나의 글로벌 스키마로 병합한다. 이는 시간에 지남에 따라 확장하는 레거시 인프라 또는 third-party API와 관련이 있다. + + + + + diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/REST.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/REST.md" new file mode 100644 index 00000000..cb107bc4 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/REST.md" @@ -0,0 +1,54 @@ +--- +title: 'REST' +lastUpdated: '2024-03-02' +--- + +REST는 설계적 제약 조건으로 API 스스로에 대한 설명이 가능하여 수 많은 API 소비자에게 채택될 수 있도록 정의된 API 아키텍처 스타일이다. + +오늘날 가장 대중적인 REST API 아키텍처 스타일은 2000년 Roy Fielding의 박사 학위 논문에 처음 소개되었다. REST API는 서버 측에서 JSON 및 XML과 같은 간단한 형식의 데이터로 표현 가능하게 한다. + +## REST 동작 방식 + +REST는 SOAP만큼 엄격하게 정의되어 있지 않다. RESTful한 아키텍처는 6개의 설계 제약 조건을 준수해야 한다. + +- **일관성 있는 인터페이스 :** 장비 또는 애플리케이션 타입과 무관하게 목표 서버와 통신하도록 일관성 있는 통신 방법을 허용한다. +- **무상태 :** 요청 자체가 요청을 처리하는 데 필요한 상태이고 서버와 세션에 대한 내용을 저장하지 않는다. +- **캐싱 :** 자주 필요한 정보를 캐싱하여 통신을 절약한다. +- **클라이언트 - 서버 아키텍처 :** 어느 측에서든 독립적으로 발전 가능하다. +- **애플리케이션의 계층화된 시스템** +- **서버가 클라이언트에서 실행 가능한 코드**를 제공 할 수 있다. + +사실 REST로 구현한 서비스의 핵심은 HATEOAS(Hypertext As The Engine of Application State)라 지칭하는 하이퍼미디어를 사용하는 데 있다. 기본적으로 REST API는 모든 응답마다 API 사용 방법에 대한 모든 정보를 묶어 놓은 메타데이터가 담겨 있다. 이 메타데이터를 통해 클라이언트와 서버를 분리 할 수 있다. 결과적으로 API 공급자와 API 소비자 모두 통신을 방해받지 않고 독립적으로 발전할 수 있다. + +## REST의 장점 + +### 1. 클라이언트-서버 분리 + +- 클라리런트와 서버가 완전히 분리되어있기 때문에 뛰어난 추상화를 제공한다. 그렇기 때문에 세부 속성을 캡슐화하여 내부 속성을 식별하고 유지하는데 용이하다. 이러한 특성은 안정화된 시스템을 유지하면서 REST API를 발전시킬 수 있는 유연성을 제공한다. + +### 2. 캐시 지원 + +- REST는 HTTP 레벨의 데이터 캐싱이 가능한 유일한 아키텍처이다. 추가적인 캐싱 모듈 없이도 캐싱을 구현할 수 있다. + +### 3. 다양한 형식 지원 + +- 데이터 저장과 교환을 위하 다양한 형식을 지원한다. (JSON, CML, MultiPart) + +## REST의 단점 + +### 1. 표준적인 형식이 없음 + +- REST API를 구축하는 방법의 정답은 없다. 리소스를 모델링을 어떻게 할지, 모델링에 어떤 리소스를 사용할지는 상황에 따라 달라진다. 이에 대한 결정을 온전히 객발자에게 맡기기 때문에, 자유도는 높지만 설계에 어려움을 줄 수도 있다. + +### 2. 페이로드가 많음 + +- REST는 클라이언트가 응답을 통해 애플리케이션 상태를 이해하는데 필요한 수많은 메타데이터를 응답에 포함한다. 이로 인해 REST API는 큰 대역폭을 요구한다. (하지만 대규모 네트워크 파이프에서 이것이 큰 문제가 되지는 않는다) + +## REST 사용 예시 + +### 관리를 위한 API 구현 + +시스템의 구성 요소 관리를 중심으로 다수의 소비자를 대상으로 하는 API가 가장 일반적인 REST API 사용 예시이다. REST는 API의 강력한 검색 성능, 체계화된 문서를 제공하는데 도움이 되며 객체 모델에도 부합한다. + +### 간단한 리소스 기반 앱 +REST는 쿼리의 유연성이 불필요한 리소스 기반의 앱을 연결하는데 유용하다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/RPC.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/RPC.md" new file mode 100644 index 00000000..406ed67d --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/RPC.md" @@ -0,0 +1,57 @@ +--- +title: 'RPC' +lastUpdated: '2024-03-02' +--- + +RPC란 원격 프로시저 호출이라는 뜻으로, 다른 컨텍스트에서 함수의 원격 실행을 허용하는 사양이다. RPC는 로컬 프로시저 호출 개념을 확장한 것이지만, **HTTP API 컨텍스트에 포함되는 개념**이다. + +초기 XML-RPC는 XML 페이로드 데이터의 타입을 보장하기 어렵다는 문제를 가지고 있었다. 그래서 차후의 RPC API는 보다 정형화된 JSON-RPC를 사용하여 SOAP보다 단순한 구조를 지닌 SOAP의 대안으로 인식되었다. + +그중 gRPC는 2015년 Google에서 개발한 최신 버전의 RPC이며, 로드밸런싱, 추적, 상태 확인, 그리고 인증을 위한 플러그인을 지원하기 때문에 마이크로 서비스 연결에 매우 적합하다. + +## RPC 동작방식 + +클라이언트는 원격 프로시저를 호출하고 매개 변수와 추가 정보를 직렬화하여 생성한 메시지를 서버로 보낸다. 서버에서는 받은 메시지를 역직렬화하여 요청받은 작업을 실행하고, 그 결과를 다시 클라이언트에게 보낸다. 서버 stub과 클라이언트 stub은 매개 변수의 직렬화와 역직렬화를 담당한다. + + + +## RPC의 장점 + +### 1. 단순한 상호작용 메커니즘 + +RPC는 직관적이고 단순한 상호작용 메커니즘을 가지고 있다. RPC는 GET을 통해 정보를 가져오고, 그 외 모든 것을 POST를 통해 처리한다. 서버와 클라이언트 간의 상호 작용 메커니즘은 endpoint를 호출하고 응답을 받는 것으로 완료된다. + + +### 2. 기능 추가 용이 + +API에 대한 새로운 요구사항 발생하는 경우에, 단순히 새로운 endpoint를 추가하여 사용하면 된다. (배포과정 X) + +### 3. 고성능 + +RPC가 사용하는 가벼운 페이로드는 공유 서버 및 워크스테이션의 네트워크에서 병렬 처리에 중요한 요소이기 때문에 네트워크가 성능을 유지하는데 기여한다. + +RPC는 네트워크 계층을 최적화하고 여러 서비스 간 오고 가는 수 많은 메시지들을 효율적으로 전송할 수 있게 한다. + +## RPC의 단점 + +### 1. 기반 시스템과의 결합도가 높음 + +API의 추상화 정도는 API의 재사용성과 비례한다. 즉, API와 기반 시스템의 결합도가 높을수록 API가 다른 시스템에서 재사용되기 어려워진다. + +RPC와 기반 시스템의 결합도가 높은 경우, 기반 시스템의 기능과 외부 API 사이의 추상화 계층을 허용하지 않는다. 이 경우 **기반 시스템 상세 구현 정보가 API를 통해 누출되기 쉽다**는 보안상의 문제가 발생한다. + +또한 높은 결합도의 RPC는 **API의 확장성과 느슨하게 결합된 아키텍처를 구현하기 어렵게 한다.** 따라서 클라이언트는 높은 결합도로 인해 특정 endpoint를 호출할 때 발생 가능한 부작용에 대해 걱정을 하거나 서버가 기능의 이름을 정하는 규칙을 이해하지 못한 관계로 어떤 endpoint를 호출해야 되는지 파악해야 한다. + +### 2. 코드 이해가 힘들다 + +RPC에서는 API를 introspect할 방법 또는 요청을 보내고 요청에 따라 호출해야되는 endpoint를 파악할 방법이 없다. + +## RPC 사용 예시 + +- RPC 패턴은 1980년대부터 사용되기 시작했지만 처음부터 널리 사용된 것은 아니다. 구글, 페이스북(Apache Thrift), 그리고 트위치(Twirp) 같은 대기업에서는 매우 고성능의 낮은 오버헤드 메시징이 가능한 RPC 고성능 변형을 내부적으로 사용하고 있다. 이러한 기업의 대규모의 마이크로 서비스 시스템은 짧은 메시지가 직렬화되는 동안 내부 통신간 문제가 없어야 한다. + +- 그외의 대표적인 사용 예시로는 **Command API**가 있다. RPC는 원격 시스템에 명령을 보내기에 매우 적절한 선택이다. 예를 들어, 채널 가입, 채널 탈퇴, 메시지 전송 기능을 제공하는 Slack API는 매우 명령 중심적이다. 그래서 Slack API 설계자들은 작고 단단하며 사용하기 쉬운 RPC 스타일로 Slack API를 모델링했다. + +- 또한, **내부 마이크로 서비스 제공을 위한 고객별 API 구현**에도 사용된다. RPC는 단일 공급자와 단일 소비자를 직접 연결하기 위해, REST API 처럼 유선으로 수 많은 메타데이터 주고 받는데 많은 시간을 할애하기를 원하지 않는다. 높은 메시징 속도와 메시징 성능을 가진 gRPC와 Twirp가 마이크로 서비스의 훌륭한 사례이다. + +- gRPC는 HTTP 2를 기반으로 네트워크 계층을 최적화하고, 서로 다른 서비스간 매일 오가는 수 많은 메시지의 송수신을 효율적으로 만든다. 하지만 뛰어난 네트워크 성능을 목표로 하는 것이 아니라, 상이한 마이크로 서비스를 배포하는 팀 사이의 안정적인 API 연동을 목표로 한다면 REST API가 더 나은 선택일 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/SOAP.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/SOAP.md" new file mode 100644 index 00000000..8b4c32e9 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/API\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230/SOAP.md" @@ -0,0 +1,57 @@ +--- +title: 'SOAP' +lastUpdated: '2024-03-02' +--- + +SOAP는 **XML 형식의 고도로 표준화된 웹 통신 프로토콜**이다. XML-RPC 출시 1년 후에 SOAP가 출시되었기 때문에 SOAP는 XML-RPC로부터 상당 부분을 물려받았다. 이후 REST가 출시되었을 때, SOAP와 REST는 같이 사용되었지만, 곧 REST만 사용하는 추세가 시작되었다. + +## SOAP의 동작 방식 + +SOAP 메시지는 다음과 같이 구성된다. + +- 메시지의 시작과 끝을 표시하는 envelop tag +- 요청 또는 응답 body +- 메시지가 특정 사항이나 추가 요구 사항을 결정해야되는지에 대한 정보를 나타내는 헤더 +- 요청을 처리하는 도중 발생할 수 있는 오류에 대한 안내 + + + +SOAP API 로직은 웹 서비스 기술 언어(WSDL)로 작성된다. 이 API 기술 언어는 endpoint를 정의하고 실행 가능한 모든 프로세스를 설명한다. 이를 통해 다양한 프로그래밍 언어와 IDE에서 통신 방법을 빠르게 설정할 수 있다. + +SOAP는 **stateful, stateless 메시지 모두 지원**한다. Stateful 메시지를 사용하는 경우, 매우 과중할 수 있는 응답 정보를 서버에서 저장하게 된다. 그러나 이는 다양하고 복잡한 트랜잭션을 처리하기에 적합하다. + +### SOAP의 장점 + +### 1. 언어 또는 플랫폼의 제약을 받지 않는다. + +- SOAP는 웹 기반 서비스를 생성하는 내장 기능을 통해 통신을 처리하기 때문에 응답 언어 및 플랫폼으로부터 자유롭다. + +### 2. 다양한 통신 프로토콜을 사용할 수 있다. + +- SOAP는 전통신 프로토콜 측면에서 다양한 시나리오에 사용될 수 있도록 설계되어있다. + +### 3. 에러 처리 기능을 내장한다. + +- SOAP API 사양은 에러 코드와 설명을 담은 XML 재시도 메시지를 반환하도록 명시한다. + +### 4. 보안 측면에서 확장 가능하다. + +- WS-Security 프로토콜과 통합된 SOAP는 엔터프라이즈급 트랜잭션 품질을 충족한다. SOAP는 메시지 수준에서 암호화를 허용하면서 트랜잭션 내부의 개인 정보 보호 및 무결성을 제공한다. + + + +## SOAP의 단점 + +1. XML만 사용한다. + - SOAP 메시지에는 많은 메타 데이터가 포함되며 요청 및 응답을 위해 장황한 XML의 특화 구조가 필요하다. + +2. 전송 용량이 무겁다. + - 큰 XML 파일 사이즈로 인해, SOAP 서비스는 큰 대역폭을 요구한다. + +3. SOAP는 협소하게 전문화된 지식을 사용한다. + - SOAP API 서버 설계는 모든 프로토콜과 매우 제한적인 규칙에 대한 깊은 이해도를 요구한다. + +4. SOAP 사용 예시 + - SOAP 아키텍처는 일반적으로 기업 내 또는 믿을 수 있는 협력사와 내부 통합이 필요한 경우 사용된다. + +SOAP는 주로 **고수준의 보안 데이터 전송**에 사용된다. SOAP의 엄격한 구조, 보안 및 권한 부여 기능은 API 공급자와 API 소비자 간의 법적 계약을 준수하면서 API와 클라이언트 간의 공식 소프트웨어 계약을 시행하는 데 가장 적합한 옵션이다. 이것이 금융 기관 및 기타 기업 고객이 SOAP를 사용하는 이유이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/CQRS.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/CQRS.md" new file mode 100644 index 00000000..e3e93e9f --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/CQRS.md" @@ -0,0 +1,71 @@ +--- +title: 'CQRS' +lastUpdated: '2024-03-02' +--- + + CQRS는 Command Query Responsibility Segregation 의 약자로, 단어 그대로 해석하면 "명령 조회 책임 분리"를 뜻한다. 즉, 우리가 보통 이야기하는 CRUD(Create, Read, Update, Delete)에서 CUD(Command)와 R(Query)의 책임을 분리하는 것이다. + +### CQRS가 필요한 이유 + + Application의 Business 정책이나 제약은 데이터 변경(CUD)에서 처리되고, 데이터 조회(R) 작업은 단순 데이터 조회를 처리하는데, 두가지 작업 모두를 동일 Domain Model로 처리하면 필요하지 않은 Domain 속성들이 생겨 복잡도가 증가한다. 도메인의 여러 속성들이 얽히면 유지보수의 비용이 증가하고 Domain model은 설계의 방향과 다르게 변질될 수 있다. + + 따라서 명령을 처리하는 책임과 조회를 처리하는 책임을 분리하여 각각의 작업에 맞는 방식을 사용하여 최적화하는 CQRS 패턴이 필요하다. + +### CQRS의 장점 + +#### 독립적인 크기 조정 + + - 읽기 및 쓰기의 워크로드를 독립적으로 확장할 수 있다. CQRS를 사용하면 CUD 작업보다 R작업이 더 많이 일어나는 경우에, 읽기 작업을 담당하는 DB만 성능을 높이는 식으로 유연하게 대응할 수 있다. + +#### DB 최적화 + + - 각 작업에 맞게 알맞는 DB를 선택하여 성능을 개선할 수 있다. polyglot 구조를 사용해 수정용 DB는 RDBMS로, 읽기 전용 DB를 NoSQL로 구분하여 사용하는 등, 각각의 Model에 맞게 저장소를 튜닝하여 사용할 수 있기 때문에 성능 튜닝에 용이하다. Event Sourcing과 함께, Queue(AWS SQS, RabbitMQ, Kafka와 같은 Message Queue 등등)를 이용하여 비동기적으로 데이터를 쓰고 읽어오도록 만들 수도 있다. + +#### 단순한 쿼리 + + - 읽기 데이터베이스에서 구체화된 뷰를 저장하여 쿼리 시 복잡한 조인을 방지할 수 있다. CUD 작업을 같은 DB에서 진행할떄에는 힘들었던 반정규화를 보다 적극적으로 도입할 수 있다. + +### 주의할 점 + +작은 애플리케이션의 경우에는 CQRS를 도입하는 것이 시스템을 불필요할 정도로 복잡하게 만들 수 있다. + +모델의 적절한 경계를 구분할 수 있는 DDD 방법론을 기반으로 한 도메인 모델링이 필요할 수 있다. + +--- + +### CQRS 구현하는 법 +CQRS 패턴은 다양한 방법론과 기술을 통해 구현할 수 있다. + + + +위는 CQRS 패턴을 점진적으로 적용하는 모습을 설명하는 그림이다. + +1. 간단한 CRUD 애플리케이션 + + - 엔티티를 가져와서 수정하고, 엔티티를 직접 조회하는 간단한 애플리케이션이다. 위에서 서술한 바와 같이, 이와 같은 애플리케이션은 도메인의 복잡성이 증가하고, 읽기, 또는 쓰기에만 적용되는 데이터들이 많아지는 경우에 유지보수가 어렵다. + +2. DTO 사용 + + - 엔티티를 통해 조회하는 것이 아닌, 데이터 응답용 DTO를 사용하여 사용자에게 정보를 전달한다. 도메인 모델에 반환용 데이터를 포함시킬 필요가 없기 때문에 R과 CUD 작업을 분리하고, 읽기에 필요한 데이터를 구분하여 저장할 수 있다. + +3. Polyglot + + - Command용 Database와 Query용 Database를 분리하고 별도의 Broker를 통해서 이 둘 간의 Data를 동기화 처리 하는 방식이다. 각각의 Model에 맞게 저장소(RDBMS, NoSql, Cache)를 튜닝하여 사용할 수 있기 떄문에 DB 성능 문제를 해결할 수 있다. 두 DB 사이를 이어주기 위해서 Kafka 등의 메세징 큐를 사용하기도 한다. (이벤트 소싱) + +### 정리 + +CQRS는 아래와 같은 아키텍처 패턴과 방법론을 파생하거나 필요로 할 수 있으며, 최종적으로는 명령과 조회에 대한 책임이 별도의 애플리케이션으로 완벽히 분리된 형태로 구현될 수 있다. + +- 이벤트 형태로 구현된 프로그래밍 모델  +- Event Sourcing +- Eventual Consistency +- Domain Driven Design + +CQRS는 애플리케이션의 복잡도가 증가하는 경우에 큰 변화를 줄 수 있는 방법중 하나인 것 같다. + +#### 참고하면 좋은 글/영상 +나만 모르고 있던 CQRS & EventSourcing + +[우아콘2020] 배달의민족 마이크로서비스 여행기 + +마틴파울러 블로그 diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/DDD.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/DDD.md" new file mode 100644 index 00000000..38ac3900 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/DDD.md" @@ -0,0 +1,51 @@ +--- +title: 'DDD' +lastUpdated: '2024-03-02' +--- + +DDD는 **Domain Driven Design**, 즉 도메인 주도 설계이다. + +도메인을 중심으로 애플리케이션을 설계, 구성하는 방법이다. + +## 도메인(Domain) + +도메인이란 '**소프트웨어로 해결하고자 하는 문제 영역**'을 의미한다. + +한 도메인은 다시 하위 도메인으로 나눌 수 있고, 도메인들은 서로 연동하여 완전한 기능을 제공한다. + +이 `도메인`은 소프트웨어의 요구사항을 이해하는데 중요한 요소이다. 서비스를 통해 어떤 문제를 해결할 것인지, 어떤 도메인을 다룰 것인지, 도메인끼리의 관계가 어떠한지 확실하게 알아야 요구사항을 제대로 이해하고 개발을 효율적으로 진행할 수 있다. + +도메인은 사용자가 누구인가, 어떻게 사용하냐에 따라 같은요소라고 할지라도 계속 바뀔 수 있고, 형태가 고정되어있지 않은 추상적인 요소이다. + +하지만 소프트웨어의 여러 관계자들은 모두 도메인을 잘 이해하고 있어야하기 때문에, 도메인에 적절한 이름을 붙이고 `도메인 모델`을 구체적으로 표현해보는 과정이 필요하다. + +## **도메인 모델** + +도메인 모델링을 하기 위해선 아래의 두가지 요소를 먼저 찾아야한다. + +- 도메인이 제공하는 기능 +- 도메인의 주요 데이터 구성 + +위 요소가 잘 드러나록 도메인 모델을 작성해야한다. 도메인 모델을 표현하는 방법에는 여러 가지가 있다. (상태 다이어그램, 클래스 다이어그램, UML 표기법 등) + +image + +주문 도메인을 잘 이해할 수 있도록 도메인 모델을 작성한 모습이다. + +도메인 모델을 사용하면 여러 관계자들이 동일한 모습으로 도메인을 이해하고 도메인 지식을 공유하는데 도움된다. + +다른 사람들이 빠르게 이해할 수 있도록 도메인을 **문서화**한다고 얘기할 수 있다. + +## **바운디드 컨텍스트** + +image + +위에서 말했듯, 도메인은 사용자가 누구인가, 어떻게 사용하냐에 따라 같은요소라고 할지라도 계속 바뀔 수 있다. 논리적으로는 같은 존재처럼 보이지만 하위 도메인에 따라 다른 용어를 사용하는 경우도 있다. + +하위 도메인에 따라 다른 용어를 사용할 수 있다는 것은, 문맥이나 환경에 따라 같은 물체도 다른 의미를 지닐 수 있다는 뜻이다. 즉, 모델은 특정한 컨텍스트(문맥) 하에서 완전한 의미를 가진다. + +DDD에서는 도메인의 문맥을 나누는 경계를 **바운디드 컨텍스트(Bounded Context)**라고 부른다. + +바운디드 컨텍스트의 경계를 지을떄는 전문가, 관계자, 개발자가 사용하는 도메인과 관련된 공통의 언어가 필요한데, 그것을 유비쿼터스 언어(Ubiquitous Language)라고 한다. + +유비쿼터스 언어는 도메인 전문가와 소프트웨어 엔지니어와의 원활한 소통을 위해 사용된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/DDD\354\235\230\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/DDD\354\235\230\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" new file mode 100644 index 00000000..709206bc --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/DDD\354\235\230\342\200\205\354\225\204\355\202\244\355\205\215\354\262\230.md" @@ -0,0 +1,47 @@ +--- +title: 'DDD의 아키텍처' +lastUpdated: '2024-03-02' +--- + +도메인 주도 설계에서는 도메인을 중심으로하여 애플리케이션을 설계, 구성한다. 애플리케이션은 도메인 외에도 표현, 응용, 인프라스트럭처 등의 영억으로 나뉘어있고 각각의 역할을 수행한다. +application전체를 구성하는 아키텍처 요소에 대해 알아보자. + +image + +대표적으로 DDD에서는 위와 같은 구조의 Layered Architecture를 가진다. + +### 계층별 설명 + +**1. Presentation Layer (표현 계층)** + +- 사용자 요청을 해석하고 응답하는 일을 책임지는 계층이다. +- 사용자에게 UI를 제공하거나 클라이언트에 응답을 보내는 모든 클래스가 포함된다. +- Client로부터 request를 받아 response를 보내는 API를 정의한다. + +**2. Application Layer (응용 계층)** + +- 사용자의 요청을 전달받아 시스템이 제공해야하는 기능과 로직을 구현하는 계층이다. +- 기능을 구현하기 위해 도메인 영역의 도메인 모델을 사용한다. +- 실질적인 데이터의 상태 변화 등의 처리는 도메인 계층에서 진행할 수 있도록 위임한다. + +**3. Domain Layer (도메인 계층)** + +- 비즈니스 규칙, 정보에 대한 실질적인 도메인에 대한 정보를 가지고있는 계층이다. +- 도메인의 핵심 로직을 구현한다. + +**4. Infrastructure Layer (인프라 계층)** + +- 외부와의 통신(DB, 메시징 시스템 등)을 담당하는 계층이다. +- 논리적인 개념을 표현하기보다 실제 구현 기술을 다룬다. + +### **Layered Architecture 구현시** 규칙 + +Layered Architecture를 올바르게 구현하기 위해선 아래 두가지의 규칙을 지켜야한다. + +**1. 상위 계층에서 하위 계층으로**의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다. + +- SOLID 원칙 중 DIP(고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다) 원칙과 동일한 규칙이다. 이에 위반되는 경우 저수준 모듈과 고수준 모듈 사이에 인터페이스를 두어, 추상 클래스와 의존 관계를 맺도록 해야한다. + +**2.** 한 계층의 관심사와 관련된 어떤 것도 **다른 계층에 배치되어서는** **안된다**. + +- 각 계층이 그에 맞는 역할만 수행해야한다는 의미이다. 클래스가 그에 맞는 계층에 배치되고, 계층의 경계가 구분되어야 계층형 아키텍처의 의미를 제대로 가질 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\353\217\204\353\251\224\354\235\270\354\230\201\354\227\255.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\353\217\204\353\251\224\354\235\270\354\230\201\354\227\255.md" new file mode 100644 index 00000000..5d25fb32 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\353\217\204\353\251\224\354\235\270\354\230\201\354\227\255.md" @@ -0,0 +1,70 @@ +--- +title: '도메인영역' +lastUpdated: '2024-03-02' +--- + +도메인 영역의 주요 구성요소는 아래 다섯가지가 있다. + +| 요소 | 설명 | +| --- | --- | +| 엔티티(Entity) | 고유의 식별자를 갖는 객체이다.
도메인 모델의 데이터를 포함하며, 해당 데이터와 관련된 기능을 함께 제공한다. | +| 밸류(Value) | 고유의 식별자를 갖지 않는 객체로, 주로 개념적으로 하나인 도메인 객체의 속성을 표현할 때 사용된다. (ex. 주소, 배송상태)
엔티티의 속성뿐만 아니라 다른 밸류 타입의 속성으로도 사용될 수 있다. | +| 애그리거트(Aggregate) | 애그리거트는 관련된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
도메인 관계 복잡도를 낮추기 위해 여러 하위 도메인을 독립된 객체군으로 나눈다. | +| 리포지터리(Repository) | 도메인 모델의 영속성을 처리한다.
애그리거트 단위로 도메인 객체를 저장하고 조회하는 기능을 정의한다. | +| 도메인 서비스(Domain Service) | 특정 엔티티에 속하지 않은 도메인 로직을 제공한다.
도메인 로직이 여러 엔티티와 벨류를 사용하는 경우에 이용한다. | + +## 1. Entity와 Value + +**Entity** + +- domain model의 엔티티에서는 데이터와 도메인 기능을 함께 제공한다. +- 두 개 이상의 데이터가 개념적으로 하나인 경우 Value ****type을 이용해서 표현할 수 있다. + +**Value** + +- 밸류 타입은 개념적으로 완전한 하나를 표현할 때 사용한다. +- 값만으로 식별되는 비즈니스 도메인의 개념으로, 명시적인 ID 필드가 필요없다. +- 무결성과 참조 투명성을 지키기 위해 밸류 타입은 무조건 불변으로 만들어야 한다. +- 데이터뿐만 아니라 행동도 모델링 할 수 있다. + +****엔티티 식별자와 Value 타입**** + +- 식별자는 도메인에서 특별한 의미를 지니는 경우가 많기 때문에 밸류 타입을 사용해 의미를 잘 드러낼 수 있다. +- 필드의 이름만으로 해당 필드를 알고 싶다면 식별자를 위한 밸류 타입을 만들어 사용할 수 있다. + +## 2. Aggregate + +도메인 객체 모델이 복잡해지면 개별 구성요소 위주로 모델을 이해하게 되어 전반적인 구조나 큰 수준에서 도메인 간의 관계를 파악하기 어려워지고, 코드 변경 및 확장이 힘들어진다. + +그러한 문제를 해결하기 위해 애그리거트로 복잡한 도메인을 이해, 관리하기 쉽게 묶어 관리한다. + +image + +애그리거트는 모델을 이해하는 데 도움을 주고, 일관성을 관리하는 기준이 될 수 있다. 관련된 모델을 하나로 모았기 때문에 한 애그리거트에 속한 객체는 유사하거나 동일한 라이프 사이클을 갖는다. + +애그리거트는 독립된 객체군이며 각 애그리거트는 자신 외에 다른 애그리거트를 관리하지 않는다. + +애그리거트의 경계는 도메인 규칙과 요구사항에 따라 결정된다. + +**애그리거트 루트** + +- 도메인 규칙을 지키려면 애그리거트에 속한 모든 객체의 상태가 정상적이어야 한다. 애그리거트에 속한 모든 객체가 일관된 상태를 유지하려면, 전체를 관리할 주체가 필요한데 이 책임을 지는 것이 바로 애그리거트의 루트 엔티티이다. + +- 애그리거트 루트는 애그리거트가 제공하는 도메인 기능을 구현한다. 해당 애그리거트에 속하는 객체는 애그리거트 루트만이 변경할 수 있다. (무분별한 setter X) + +- 애그리거트는 트랜잭션의 경계 역할을 하므로 모든 데이터는 원자적인 단일 트랜잭션으로 커밋되어야 한다. 그리고 애그리거트는 도메인 이벤트를 통해 외부 엔티티와 상호작용할 수 있다. + +## 3. Repository + + Repository 인터페이스는 애그리거트 루트를 기준으로 작성한다. 객체의 영속성을 처리하는 리포지터리는 애그리거트 단위로 존재하며, 완전한 애그리거트를 제공해야 한다. + +DB나 자료구조와 같은 인프라스트럭쳐 계층에 질의를 던지고 객체를 가져오는 책임은 Repository가 모두 가지고 있기 때문에, 보다 도메인에 중점적인 개발이 가능하다. + +## 4. Domain Service + +도메인 서비스는 특정 엔티티에 속하지 않은 도메인 로직을 제공한다. +즉, 도메인 로직이 여러 엔티티와 벨류를 사용하는 경우에 이용된다. + +애그리거트나 밸류와 다르게, 도메인 서비스는 **상태값 없이 로직만** 구현한다. + +도메인 서비스는 도메인 로직을 실행하므로 도메인 서비스의 위치는 다른 도메인 구성 요소와 동일한 패키지에 위치한다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\354\235\264\353\262\244\355\212\270\342\200\205\354\212\244\355\206\240\353\260\215.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\354\235\264\353\262\244\355\212\270\342\200\205\354\212\244\355\206\240\353\260\215.md" new file mode 100644 index 00000000..b3aa2372 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\354\235\264\353\262\244\355\212\270\342\200\205\354\212\244\355\206\240\353\260\215.md" @@ -0,0 +1,87 @@ +--- +title: '이벤트 스토밍' +lastUpdated: '2024-03-02' +--- + +이벤트 스토밍이란 도메인에 관련된 모든 이해 관계자가 모여서 화이트 보드와 포스트잇을 활용하여 이벤트를 중심으로 업무들 간의 상호 연관성을 찾기 위해 진행하는 워크숍 방법론이다. + +도메인 전문가와 개발자를 학습 과정에 참여시키기 위해 설계되었고, 모든 사람들이 시각적으로 도메인에 시각적으로 접근할 수 있게 해준다. + +### **이벤트 스토밍을 하는 이유?** + +이벤트 스토밍의 가장 큰 목적은 **도메인지식을 공유함으로써 전체 비스니스가 어떻게 돌아가는지 한눈에 볼 수 있는 지도(타임라인)를 만드는 것**이다. 각 도메인 전문가들의 도메인 지식은 모두 분산되어 있기 때문에 한번에 전체 지도를 그리기 힘든데, 이벤트 스토밍은 그 지식을 모아 정리할 수 있도록 한다. + +### **이벤트 스토밍 하는 법** + +이벤트 스토밍을 하기 위한 준비물은 “포스트잇과 펜”이다. + +어떤 이벤트와 정책, 애그리거트가 있을지 생각한 뒤, 순서에 따라 각 용도에 맞는 포스트잇으로 적어 붙이면 된다. 아래는 색깔별 포스트잇의 용도이다. + +![image](https://user-images.githubusercontent.com/81006587/205545021-1fde2b7b-a1b6-474f-9c15-2fb63f736f02.png) + +📌 **포스트잇의 용도** + + +|이름|설명| +|-|-| +|도메인 이벤트 Domain Event|비즈니스 프로세스에서 발생하는 이벤트이다. 과거형으로 작성한다.| +|액터 Actor| +- 뷰를 통해 명령을 실행하는 사람을 뜻한다. + +명령 Command + +- 사용자가 실행하는 명령이다. + +정책 Policy + +- 이벤트가 발생했을 때의 시스템의 반응을 규약한 것이다. + +외부 시스템 External System + +- Third Party 시스템을 뜻한다. (ex. 결제 시스템, 배송회사) + +뷰 Veiw + +- 사용자가 시스템에서 작업을 수행하기 위해 상호 작용하는 뷰를 의미한다. + +애그리거트 Aggregate + +- 단일 단위로 취급할 수 있는 도메인 개체의 모음이다. + +### **이벤트 스토밍 순서** + +이벤트 스토밍의 순서는 세 단계로 나뉜다. + +![image](https://user-images.githubusercontent.com/81006587/205545117-55753aef-2f89-41cc-87f1-fff6640e2e08.png) + + **1. Big Picture** + +전체적인 그림을 그리는 첫번째 단계이다. 이 때는 이벤트와 핫스팟, 외부 시스템, 액터를 적는다. + +도메인 객체의 상태를 변화시키는 이벤트를 전부 적어주고 각 컨텍스트에서 어떤 이벤트들이 생성되고 전달되는지 파악한다. + +이 단계가 끝나고 나면 이벤트의 타임라인을 통해 바운디드 컨텍스트를 대략적으로 찾을 수 있다. + + **2. Process Modelling** + +Big Picture 단계에서 더 나아가 정책, 명령을 뽑아낸다. 이 과정에서 기존에 적어놨던 이벤트나 외부 시스템, 액터를 수정할 수도 있고 이벤트를 추가로 뽑아내는 것도 가능하다. + +정책은 프로세스 매니저라고 부를 수도 있는데, 이벤트를 받았을때 처리되는 조건 혹은 이벤트가 생성되는 조건을 뜻한다. + +프로세스 모델링이 끝나면 이벤트의 처리주체 및 정책, 이벤트를 발생시키는 커맨드와 커맨드 주체(Person, System 등) 등의 세부적인 사항들이 드러난다. + + **3. Software Design** + +마지막 단계인 Software Design은 개발자들이 주도적으로 참여하는 단계이다. + +이제 프로그래밍 즉, 설계를 하기 위해 애그리거트를 뽑아낸다. + +소프트웨어 디자인이 끝나면 실제로 소프트웨어를 개발할 수 있다. 변경해야하는 객체들, 변경 조건 및 방법 등이 다 명확해졌기 때문이다. + +### 유의사항 + +이벤트 스토밍을 할 때에는, 이벤트를 배치하는 순서에도 신경써야한다. 이벤트 사이의 순차적 관계가 있는 경우에는 좌 →우으로 배치, 동시에 발생하는 이벤트일 경우 상 →하로 배치하여 사용 타임라인이 잘 드러나도록 해야한다. + +실제 적용에는 위의 내용을 기반으로 각 내용을 변경해서 쓰는 경우가 많다. 이벤트 스토밍은 상황과 환경에 따라 적절한 방식으로 응용해서 진행하는 것이 좋다. + +다만, 그것을 변경하는 이유와 효과를 확실히 인지하고 사용해야한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\354\273\250\355\212\270\353\236\231\355\212\270.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\354\273\250\355\212\270\353\236\231\355\212\270.md" new file mode 100644 index 00000000..1eec3794 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/DDD/\354\273\250\355\212\270\353\236\231\355\212\270.md" @@ -0,0 +1,56 @@ +--- +title: '컨트랙트' +lastUpdated: '2024-03-02' +--- + +바운디드 컨텍스트의 모델은 서로 독립적이지만 바운디드 컨텍스트 자체는 독립적이지 않다. + +컨텍스트는 각자 독립적으로 발전할 수 있지만 컨텍스트끼리는 서로 상호작용해야 하기 때문에, 그 사이에는 항상 접점이 생기는데 이를 **컨트랙트(Contract)** 라고 부른다. + +바운디드 컨텍스트 간의 연동, 즉 컨트랙트에 대한 고민은 솔루션 설계에서 평가되고 다뤄져야 한다. + +![image](https://user-images.githubusercontent.com/81006587/205544839-392eae40-d468-4825-95b5-a438477000b9.png) + +바운디드 컨텍스트간의 연관관계를 나타낸 컨텍스트 맵의 예시이다. + +바운디드 컨텍스트 간의 관계는 두 컨텍스트를 작업하는 팀 간의 협력적 특성이나, 설계에 따라 달라질 수 있다. 컨트랙트가 상호작용하는 패턴은 크게 **협력형 패턴 그룹**과 **사용자-제공자 패턴 그룹**으로 나뉜다. 또는, 아예 협력하지 않는 **분리형 노선**을 채택할 수도 있다. + +### 협력형 패턴 그룹(Cooperation) + +**파트너십 패턴(Partnership)** + +바운디드 컨텍스트 간의 연동을 ad-hoc 방식으로 조정하는 패턴으로, 한 팀이 다른 팀에게 API의 변경을 알리고 다른 팀은 그것을 충돌 없이 받아들이는 방식이다. 팀 간의 잦은 동기화와 협업이 필요하다. + +**공유 커널 패턴(Shared Kernel)** + +동일한 모델이 여러 바운디드 컨텍스트에서 사용되는 경우, 모델을 공유하여 사용할 수 있다. + +공유하는 코드베이스에 대한 변경을 조율하는 데 드는 비용보다 공유하는 모델에 대한 변경을 통합할 때 드는 비용이 더 높은 경우 사용된다. + +### 사용자-제공자 패턴 그룹(Customer-Supplier) + +사용자-제공자(Customer-Supplier) 패턴 그룹은 **서비스 제공자(업스트림, Upstream)와 서비스 사용자(다운스트림, Downstream)**으로 구분된다. + +**순응주의자 패턴(Conformist)** + +다운스트림이 업스트림의 모델을 받아들이는 바운디드 컨텍스트의 관계를 순응주의자(Conformist) 패턴이라고 부른다. 이 패턴은 힘의 균형이 서비스를 제공하는 업스트림에 치우쳐 있다. + +****충돌 방지 계층 패턴(Anti-Corruption Layer)**** + +힘의 균형이 업스트림 서비스로 치우쳐져있지만 다운스트림이 그것을 순응하지 않고, 충돌 방지층으로 자체 요구에 맞게 조정하는 패턴을 충돌 방지 계층 패턴이라 부른다. + +다운스트림 바운디드 컨텍스트가 핵심 하위 도메인을 포함하거나, 업스트림 모델이 비효율적인 경우, 컨트랙트가 자주 변경되는 경우에 사용될 수 있다. + +****오픈 호스트 서비스 패턴(Open-Host Service)**** + +오픈 호스트 서비스 패턴은 충돌 방지 계층 패턴과 반대로 사용자 대신 제공자가 내부 모델 번역을 구현한다. + +이 패턴에서 제공자는 사용자를 보호하는 데 관심이 있고 힘의 균형이 사용자 측에 치우쳐 있다. 그리고구현 모델의 변경으로부터 사용자를 보호하기 위해 퍼블릭 모델과 내부 구현 모델을 분리한다. + +퍼블릭 인터페이스는 자신의 유비쿼터스 언어가 아닌 연동 지향 언어(Integration-Oriented Language)를 통해 사용자의 편의를 고려한다. 다운스트림 바운디드 컨텍스트가 여러개인 경우에는 연동 모듈을 분리하여 각각 다른 버전을 동시에 노출하는 것도 가능하다. + +### **분리형 노선(Separated Ways)** + +협력과 연동보다 특정 기능을 중복으로 두는 것이 더 저렴한 경우에는, 특정 도메인을 공유하더라도 서로 분리하여 개발할 수도 있는데, 그러한 방식을 분리형 노선(Separated Ways)이라 부른다. + +커뮤니케이션이 힘들거나, 각 컨텍스트에서 쓰는 모델이 크게 다른경우에 분리형 노선을 선택한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/HexagonalArchitecture.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/HexagonalArchitecture.md" new file mode 100644 index 00000000..821a302f --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/HexagonalArchitecture.md" @@ -0,0 +1,57 @@ +--- +title: 'HexagonalArchitecture' +lastUpdated: '2024-03-02' +--- + +> The Hexagonal Architecture is a very powerful pattern. It helps you create more sustainable and better testable software by decoupling the business logic from the technical code. + +만약 비즈니스 로직(Domain)이 특정 라이브러리에 의존성을 가지고 있다면 기술 스택을 업그레이드하고 바꿔야 할떄, 모든 비즈니스 로직과 infrastructure를 갈아 엎어야 할 것이다! + +육각형 아키텍처(Hexagonal Architecture)는 그러한 문제를 해결하기 위해 **기술과 비지니스 로직 사이를 분리**하여 비지니스 로직이 기술에 구애받지 않고 동작할 수 있도록 하는 아키텍처이다. + +### Hexagonal Architecture의 특징 + + + +위의 그림은 Hexagonal Architecture의 구조를 간단히 표현한 것이다. + +특징은 다음과 같다. + +1. 안의 비즈니스 로직과 밖의 infrastructure 두 부분으로 나눌 수 있다. + +2. 종속성은 항상 infrastructure에서 Domain 내부로 이동한다. 이를 통해 외부 라이브러리, 기술과 비즈니스 도메인의 격리를 보장할 수 있게 된다. + +3. 비즈니스 로직은 자기 자신을 제외하고 어디에도 의존하지 않는다. + +### Hexagonal Architecture에서의 Interface 사용 + + + +육각형의 독립성을 보장하기 위해서는 의존성의 방향이 역전되어야 한다. + +따라서 특정 기술에 종속되어있는 코드는 interface를 매개체로 분리하고, Domain에서는 interface만을 사용하여 그 구현 방식이 어떻냐에 관계 없이 로직을 수행할 수 있다. + +이러한 interface는 육각형의 일부로 취급되며, 2가지 종류로 나눌 수 있다. + +- **API(application programming interface)**:
도메인에 질의하기 위해 필요한 인터페이스이다. 우리가 아는 그 API가 맞다. +- **SPI(Service Provider Interface)**:
Third party에서 정보를 구해올 때 도메인에 필요한 인터페이스들이다. 상황에 따라 Domain에서 이걸 구현할 수도 있다. + +### 정리 + +Hexagonal Architecture를 사용하면 다음과 같은 이점을 얻을 수 있다. + +1. 도메인이 기술과 무관하므로 비즈니스에 영향을 주지 않고 스택을 변경할 수 있다. +2. 그리고 기능에 대한 테스트를 보다 쉽게 만들 수 있다. +3. 도메인에서 시작하여 기능 개발에 집중하고, 기술 구현에 대한 선택을 연기할 수 있다. + +하지만 당연히 육각형 아키텍처기 모든 상황에 적합하지는 않다. trade off를 가지며, 다른 문제를 야기할수도 있고, 러닝 커브가 높다고 느낄수도 있다. 하지만 추후의 기술 변경시의 유연함을 지키기 위해서 육각형 아키텍처는 매력적인 선택지이다! + +### 느낀점 + +특정 기술에 대해 구현할때 해당 코드를 Interface, Facade 혹은 Util로 분리하는 구조를 많이 봤고, 사용도 꽤 해봤는데 이게 육각형 아키텍처와 관련이 있다는 것은 처음 알게 되었다. + +그리고 Hexagonal Architecture도 그렇고 아키텍처들은 SOLID, Ioc 등의 기본적인 원리를 응용한 경우가 많은 것 같다. 대부분 지향점이 비슷하다 보니 코드를 보고 어떤 아키텍처다 하고 확실히 구분하는게 조금 어려운 것 같다. (디자인 패턴을 공부할떄도 비슷한 기분이 들었다.) + +아키텍처는 "다 비슷비슷한 그런거"를 모두가 납득할 수 있도록 설명하고 명료하게 정리하여 정형화하여 프로젝트에 적용할 수 있도록 하는 것이 목표가 아닐까 하는 생각이 든다. 그러한 원리들을 코드에 담을 특정한 방법을 구체적으로 정해야, 모든 팀원들이 그에 맞춰 하나의 프로젝트를 만들어나갈 수 있으니까 말이다. + +더 좋은 코드를 만들고, 설명할 수 있는 사람이 되기 위해 더 노력해야겠다..! diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/MSA\354\235\230\342\200\205\354\236\245\353\213\250\354\240\220.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/MSA\354\235\230\342\200\205\354\236\245\353\213\250\354\240\220.md" new file mode 100644 index 00000000..dfe620a2 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/MSA\354\235\230\342\200\205\354\236\245\353\213\250\354\240\220.md" @@ -0,0 +1,61 @@ +--- +title: 'MSA의 장단점' +lastUpdated: '2023-12-20' +--- +## 마이크로서비스 아키텍처의 장점 + +### 크고 복잡한 애플리케이션을 지속적으로 전달/배포할 수 있다. (CI/CD) + +MSA를 구축하면 크고 복잡한 애플리케이션을 지속적 전달/배포할 수 있다. MSA는 다음과 같은 세가지 방법으로 지속적 전달/배포를 실현한다. + +- **테스트성:** 지속적 전달/배포를 하려면 자동화 테스트가 꼭 필요하다. MSA는 상대적으로 크기가 작아서 자동화 테스트를 작성하기 쉽고 더 빨리 실행되며, 애플리케이션 버그도 적은 편이다. +- **배포성:** MSA는 독립적으로 배포할 수 있기 때문에 개발자가 자신이 담당한 서비스 변경분을 배포할때 다른 개발자와 복잡한 협의 과정을 거칠 필요가 없다. 그래서 프로덕션에 변경분을 반영하기가 훨씬 수월하다. +- **자율성, 느슨한 결합:** 작은 팀이 여러개 결합되어있는 기술 조직을 꾸려나갈 수 있다. 각 팀이 다른 팀과 독립적으로 개발, 배포, 확장할 수 있으므로 개발 속도가 빨라진다. + +지속적 전달/배포를 하며 비즈니스 측면에서도 이점이 있다. + +- 제품을 시장에 내놓는 시기를 앞당길 수 있고 현장에서도 고객 피드백에 신속히 대응할 수 있다. +- 현재 고객들이 기대하는 수준으로 확실하게 서비스를 제공할 수 있다. +- 제품의 가치를 전단하는 데 더 많은 시간을 투자할 수 있다. + +### 서비스가 작아 관리하기 용이하다. + +- MSA는 비교적 크기가 작아서 개발자가 코드를 이해하기 쉽다. 코드베이스가 작으면 IDE도 느려지지 않으므로 개발 생산성이 올라간다. 각 서비스를 시동하는 시간이 빠르기 때문에 개발자가 작업 후 배포하는 과정 역시 빠르고 생산적이다. + +### 서비스를 독립적으로 배포/확장할 수 있다. + +- 마이크로서비스는 독립적으로 X축(복제)/Z축(파티셔닝) 확장을 할 수 있고, 서비스마다 상이한 리소스 요건에 맞추어 하드웨어에 배포할 수 있다. 리소스 요건이 전혀 다른 컴포넌트를 무조건 함께 배포할 수밖에 없었던 모놀리식과 큰 차이가 있다. + +### 결함 격리가 잘된다. + +- MSA는 결함 격리가 잘된다. 만약 어느 서비스에서 메모리 누수가 발생하더라도 해당 서비스만 영향을 받고 다른 서비스는 계속 정상 가동된다. + +### 신기술을 시험/도입하기 쉽다. + +- 특정 기술스택을 연구하느라 오랜 시간을 소비할 필요가 없다. 새로운 서비스를 개발할 떄 그 서비스에 가장 알맞은 언어와 프레임워크를 자유롭게 선택할 수 있다. + +- 서비스 규모가 작기 때문에 더 나은 언어와 기술로 얼마든지 재작성할 수 있다. 설령 새로운 기술을 시도했다가 실패하더라도 전체 프로젝트는 그대로 유지할 수 있다. 프로젝트 초기에 선택한 기술에 심하게 제약받을 필요가 없다. + +## 마이크로서비스 아키텍처의 단점 + +### 설계가 어렵다. + +- MSA에 맞게 시스템을 여러 서비스로 분해하는, 구체적으로 정립된 알고리즘은 따로 없다. 하지만 만약 시스템을 잘못 분해할 경우 모놀리식/MSA의 단점만 있는 **분산 모놀리스**(distributed monolith)를 구축하게 된다. + +### 복잡하다. + +- MSA를 사용하면 분산시스템이라는 복잡성을 감당해야한다. 서비스 간 통신에 필수적인 IPC 역시 단순 메서드 호출보다는 복잡하며, 사용 불능 또는 지연시간이 긴 원격 서비스, 부분 실패한 서비스를 처리할 수 있게 설계해야한다. + +- 여러 서비스를 상대로 유스케이스를 구현하려면 추가적으로 신경써줘야하는 부분도 있다. 특히 서비스마다 DB가 따로 있기 때문에 다중 DB에 접속하여 좆회하고 트랜잭션을 구현하기 어렵다. (이를 해결하기 위해 사가라는 기술을 사용한다. 여러 API를 조합하거나 CQRS뷰로 쿼리하는 방식을 사용하기도 한다.) + +### 여러 서비스에 걸침 공통 기능을 배포할 시 주의해야한다. + +- 여러 서비스에 걸친 기능을 배포할때는 여러 개발 팀 간에 세심한 조율이 필요하기도 한다. 그러므로 서비스 간 디펜던시에 따라 배포를 계획적으로 수행해야한다. + +### 도입 시기를 결정하기 어렵다. + +- 초기 버전을 개발할때는 굳이 MSA를 사용할 이유가 없다. 초기에는 오히려 개발속도가 더뎌지게 만들고, 재빠른 발전이 어려워진다. + +- 필요한 경우 애플리케이션의 도메인이 복잡해지는 시접에 기능분해하여 서서히 MSA로 리팩터링해야하는데, 이 적절한 시점을 딱 찾아내는 것도 어렵고 변환해가는 과정도 만만치 않다. + +- 마이크로서비스 도입에는 그에 따는 다양한 설계/아키테겇 이슈가 있으며, 그중 상당수에는 솔루션이 여러개이며 각자 나름의 trade-off가 있다. 완벽한 정답은 없기 때문에, 각 상황에 따라 적절하게 선택하여 구현하면 된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\353\251\224\354\213\234\354\247\200\342\200\205\353\270\214\353\241\234\354\273\244.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\353\251\224\354\213\234\354\247\200\342\200\205\353\270\214\353\241\234\354\273\244.md" new file mode 100644 index 00000000..468348e3 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\353\251\224\354\213\234\354\247\200\342\200\205\353\270\214\353\241\234\354\273\244.md" @@ -0,0 +1,43 @@ +--- +title: '메시지 브로커' +lastUpdated: '2024-03-02' +--- + + + +## 브로커리스 메시징 + +브로커리스 아키텍처의 서비스는 메세지를 서로 직접 교환한다. + +**장점** +- 송신자가 보낸 메시지가 브로커를 거쳐 수신자로 이동하는 것이 아닐, 송신자에서 수신자로 직접 전달되므로 네트워크 트래픽이 가볍고 지연 시간이 짧다. +- 메시지 브로커가 성능 병목점이나 SPOF(단일 정야좀)가 될 일이 없다. +- 메시지 브로커를 설정/관리할 필요가 없으므로 운영 복잡도가 낮다. + +**단점** +- 서비스가 서로의 위치를 알고 있어야 하므로 서비스 디스커버리 메커니즘을 사용해야한다. +- 메시지 교환시 송신자/수신자 모두 실행중이어야 하므로 강ㅇ성이 떨어진다. +- 전달 보장같은 메커니즘을 구현하기가 어렵다. + +## 브로커 기반 메시징 + +메시지 브로커는 모든 메시지가 지나가는 중간 지점이다. 송신자가 메시지 브로커에 메시지를 쓰면 메시지 브로커는 메시지를 수신자에게 전달한다. 메시지 브로커의 가장 큰 장점은 송신자가 컨슈머의 네트워크 위치를 몰라도 된다는 점이다. 또 컨슈머가 메시지를 처리할 수 있을때 까지 메시지 브로커에 메시지를 버퍼링할 수도 있다. + +**장점** +- **느슨한 결함:** 클라이언트는 채널에 메세지를 보니는 식으로만 요청하면 된다. 서비스 인스턴스를 몰라도 되므로 디스커버리 매커니즘이 필요없다. +- **메시지 버퍼링:** 메시지 브로커는 처리 가능한 시점까지 메시지를 버퍼링한다. HTTP같은 동기 요청/응답 프로토콜을 쓰면 교환이 일어나는 동안 클라이언트/서비스 모두 가동중이어야 하지만 메시징을 쓰면 컨슈머가 처리할 수 있을 때까지 그냥 큐에 메시지가 쌓이게 된다. 덕분에 수행이 지연되거나 서버가 잠시 불능 상태가 되어도, 다시 복구되면 요청을 다시 처리할 수 있다. + +**단점** +- **성능 병목 가능성:** 메시지 브로커가 성능 병목점이 될 위험이 있다. (하지만 다행이 요즘 메시지 브로커는, 대부분 확장이 잘 되도록 설계되어있어 문제가 줄어들었다.) +- **단일 장애점 가능성:** 메시지 브로커는 가용성이 높아야 한다. 모든 중요한 메세지들이 모이는 메시지 브로커에서 데이터가 유실되거나 장애가 생기면, 큰 SPOF가 될 수 있다. +- **운영 복잡도:** 메시징 시스템 역시 설치, 구성, 운영해야할 시스템 컴포넌트이기 때문에 운영 복잡도를 가중시킬 수 있다. + +메시지 브로커로 자주 쓰이는 제품으로는 다음과 같은 것들이 있다. 각 소프트웨어마다 메시지 패널이 구현되어있는 방식이 조금씩 다르다. + +|메시지 브로커|점대점 채널|발행-구독 채널| +|-|-|-| +|JMS|큐|토픽| +|아파치 카프카|토픽|토픽| +|AMQP 브로커(ex. RabbitMQ)|익스체인지+큐|팬아웃 익스체인지, 컨슈머 개별 큐| +|AWS 키네시스|스트림|스트림| +|AWS SQS|큐|-| \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\202\254\352\260\200\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\202\254\352\260\200\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..40dec559 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\202\254\352\260\200\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,42 @@ +--- +title: '사가 패턴' +lastUpdated: '2024-03-02' +--- + +사가는 MSA에서 **분산 트랜잭션 없이 데이터 일관성을 유지하는 메커니즘**이다. 사가는 일종의 로컬 트랜잭션인데, 각 로컬 트핸잭션은 ACID 트랜잭션 프레임워크/라이브러리를 이용하여 서비스별 데이터를 업데이트한다. + + + +주문 서비스의 createOrder() 작업은 6개의 로컬 트랜잭션을 가진 주문 생성 사가로 구현된다. + +- **Txn:1 주문 서비스:** 주문을 APPROVAL_PENDING 상태로 생성한다. +- **Txn:2 소비자 서비스:** 주문 가능한 소비자인지 확인한다. +- **Txn:3 주방 서비스:** 주문 내역을 확인하고 티켓을 CREATE_PENDING 상태로 생성한다. +- **Txn:4 회계 서비스:** 소비자 신용카드를 승인한다. +- **Txn:5 주방 서비스:** 티켓 상태를 AWATING_ACCEPTANCE 로 변경한다. +- **Txn:6 주문 서비스:** 주문 상태를 APPROVED로 변경한다. + +이 사가의 첫 번쨰 로컬 트랜잭션은 주문 생성이라는 외부 요청에 의해 시작되고 나머지 5개의 로컬 트랜잭션은 각자 자신의 선행 트랜잭션이 완료되면 트리거 된다. 이 트리거는 메시지 발행을 통해 전달되며, 이를 통해 사가 참여자를 느슨하게 결합함과 동시에 사가가 완료되도록 보장할 수 있다. + +이 사가에서 도중에 에러가 발생하는 경우, 보상 트랜잭션을 통해 변경분에 대한 롤백을 수행한다. + +## 보상 트랜잭션 + +사가는 단계마다 로컬 DB에 변경분을 커밋하기 때문에 ACID와 같은 자동 롤백은 불가능하다. 그렇기 때문에 적용된 변경분을 명시적으로 Undo할 수 있는 **보상 트랜잭션**(compensating transaction)을 미리 작성하여 롤백을 구현해야한다. + +(N + 1)번째 사가 트랜잭션이 실패하면 이전 N개의 트랜잭션을 언두해야 한다. 개념적으로 단계 Ti에는 Ti의 작용(effect)을 Undo하는 보상 트랜잭션 Ci가 대응되며, 처음 N개 단계의 작용을 언두하려면 사가는 각 Ci를 역순으로 실행하면 된다. 아래의 그림과 같이 T1 … Tn 순서로 트랜잭션이 실행되다가 Tn+1에서 실패할 경우 T1 … Tn을 언두하고 Cn … C1을 순서대로 실행하게 되는 것이다. + + + +이 보상 트랜잭션은, 로컬 트랜잭션이 실패하는 순간 가동된다. 하지만 모든 로컬 트랜잭션에 보상 트랜잭션이 필요한 것은 아니다. 소비자 검증같은 **읽기 전용 단계**(retriable transaction)나 **항상 성공하는 단계 이전에 있는 단계**(pivot transaction)는 보상 트랜잭션이 필요 없다. + +예를들어 소비자의 신용카드 승인이 실패하면 보상 트랜잭션은 다음 순서대로 작동할 것이다. + +1. **주문 서비스:** 주문을 APPROVAL_PENDING 상태로 생성한다. +2. **소비자 서비스:** 소비자가 주문을 할 수 있는지 확인한다. +3. **주방 서비스:** 주문 내역을 확인하고 티켓을 CREATE_PENDING 상태로 생성한다. +4. **회계 서비스:** 소비자의 신용카드 승인 요청이 거부된다. +5. **주방 서비스:** 티켓 상태를 CREATE_REJECTED로 변경한다. +6. **주문 서비스:** 주문 상태를 REJECTED로 변경한다. + +5, 6단계는 주방 서비스, 주문 서비스가 수행한 업데이트를 Undo하는 보상 트랜잭션이다. 일반 트랜잭션과 보상 트랜잭션의 순서화는 [사가 편성 로직](./%EC%82%AC%EA%B0%80%E2%80%85%ED%8E%B8%EC%84%B1.md)이 담당한다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\202\254\352\260\200\342\200\205\355\216\270\354\204\261.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\202\254\352\260\200\342\200\205\355\216\270\354\204\261.md" new file mode 100644 index 00000000..60da8256 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\202\254\352\260\200\342\200\205\355\216\270\354\204\261.md" @@ -0,0 +1,104 @@ +--- +title: '사가 편성' +lastUpdated: '2024-03-02' +--- + +사가는 단계를 편성하는 로직으로 구성된다. 시스템 커맨드가 사가를 시작할 때 해당 편성 로직은 첫 번째 사가 참여자를 정하여 로컬 트랜잭션 실행을 지시하고, 트랜잭션이 완료되면 모든 단계를 실행 될때까지 계속 그다음 사가 참여자를 호출한다. 로컬 트랜잭션이 도중에 하나라도 실패한다면 사가는 보상 트랜잭션을 역순으로 실행한다. 이와 같은 사가를 편성하는 로직은 두 가지 종류가 있다. + +|종류|설명| +|-|-| +|코레오그래피(choreography)|의사 결정과 순서화를 사가 참여자에게 맡긴다. 사가 참여자는 주로 이벤트 교환 방식으로 통신한다.| +|오케스트레이션(orchestration)|사가 편성 로직을 사가 오케스트레이터에 중앙화한다. 사가 오케스트레이터는 사가 참여자에게 커맨드 메시지를 보내 수행할 작업을 지시한다.| + +# 코레오그래피 사가 + +코레오그래피 방식은 중앙 편성자 없이, 서로 이벤트를 구독하여 그에 따라 반응하는 것이다. 아래 그림을 코레오그래피 스타일로 설계한 주문 생성 사가이다. 사가 참여자는 서로 이벤트를 주고 받으며 소통한다. 주문 서비스를 시작으로 각 참여자는 자신의 DB를 업데이트하고 다음 참여자를 트리거하는 이벤트를 발행한다. + + + +사용자가 주문 요청을 보낸 경우, 이벤트 순서는 다음과 같다. + +1. 주문 서비스: 주문을 APPROVAL_PENDING 상태로 생성 → 주문 생성 이벤트 발행 +2. 소비자 서비스: 주문 생성 이벤트 수신 → 소비자가 주문을 할 수 있는지 확인 → 소비자 확인 이벤트 발행 +3. 주방 서비스: 주문 생성 이벤트 수신 → 주문 내역 확인 → 티켓을 CREATE_PENDING 상태로 생성 → 티켓 생성됨 이벤트 발행 +4. 회계 서비스: 주문 생성 이벤트 수신 → 신용카드 승인 PENDING 상태로 생성 +5. 회계 서비스: 티켓 생성 및 소비자 확인 이벤트 수신 → 소비자 신용카드 과금 → 신용카드 승인됨 이벤트 발행 +6. 주방 서비스: 신용카드 승인 이벤트 수신 → 티켓 상태 AWAITING_ACCEPTANCE로 변경 +7. 주문 서비스: 신용카드 승인됨 이벤트 수신 → 주문 상태 APPROVED로 변경 → 주문 승인됨 이벤트 발행 + +회계 서비스에서 신용카드가 승인 거부된 경우, 이벤트 순서는 다음과 같다. + +1. 주문 서비스: 주문을 APPROVAL_PENDING 상태로 생성 → 주문 생성 이벤트 발행 +2. 소비자 서비스: 주문 생성 이벤트 수신 → 소비자가 주문을 할 수 있는지 확인 → 소비자 확인 이벤트 발행 +3. 주방 서비스: 주문 생성 이벤트 수신 → 주문 내역 확인 → 티켓 상태 CREATE_PENDING으로 생성 → 티켓 생성 이벤트 발행 +4. 회계 서비스: 주문 생성 이벤트 수신 → 신용카드 승인 PENDING 상태로 생성 +5. 회계 서비스: 티켓 생성 및 소비자 확인 이벤트 수신 → 소비자 신용카드 과금 → 신용카드 승인 실패 이벤트 발행 +6. 주방 서비스: 신용카드 승인 실패 이벤트 수신 → 티켓 상태 REJECTED로 변경 +7. 주문 서비스: 신용카드 승인 실패 이벤트 수신 → 주문 상태 REJECTED로 변경 + +--- + +코레오그래피 방식으로 사가를 구현하려면 두가지 통신 이슈를 고려해야한다. + +첫째, 사가 참여자가 자신의 DB를 업데이트한 뒤에, DB 트랜잭션의 일부로 이벤트를 발행하도록 해야한다. 다시말해, 로컬 트랜잭션의 실행이 성공하였을 시 이벤트를 발행하여 전달한다는 것을 무조건! 보장해야한다. 따라서 사가 참여자가 서로 확실하게 통신하려면 트랜잭셔널 메시징([트랜잭션 로그 테일링 패턴](./%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%E2%80%85%EB%A1%9C%EA%B7%B8%E2%80%85%ED%85%8C%EC%9D%BC%EB%A7%81%E2%80%85%ED%8C%A8%ED%84%B4.md)과 같은...)을 사용해야한다. + +둘째, 사가 참여자는 자신이 수신한 이벤트와 자신이 가진 데이터를 연관지을 수 있어야 한다. 데이터를 매핑할 수 있는 상관관계 ID를 포함하여 이벤트를 발행함으로써 해결할 수 있다. + +### 코레오그래피 사가의 장점 + +- **단순함:** 비즈니스 객체를 생성, 수정, 삭제할 때 서비스가 이벤트를 발행하기만 하면 된다. +- **느슨한 결합:** 참여자는 이벤트를 구독할 뿐, 서로를 직접 알지 못한다. + +### 코레오그래피 사가의 단점 + +- **복잡함:** 오케스트레이션 사가와 달리, 사가를 어느 한골에 정의한 것이 아니라서 여러 서비스에 구현 로직이 흩어져있다. 그렇기 때문에 어떤 로직이 어떻게 작동하는지 이해하기가 어렵다. +- **순환의존성:** 참여자가 서로 이벤트를 구동하는 특성상 순환 의존성(cyclic dependency)이 발생하기 쉽다. 꼭 문제라고 할 순 없지만, 잠재적인 설계 취약점이 될 수 있다. +- **단단히 결합될 가능성:** 사가 참여자는 각자 자신에게 영향을 미치는 이벤트를 모두 구독해야한다. 예를 들어 회계 서비스는 신용카드를 과금/환불 처리하게 만드는 모든 이벤트에 호출되어야 하기 때문에, 다른 서비스에 변동 사항이 생길때마다 항상 같이 수정해야만 하는 강결합이 생길 수 있다. + +# 오케스트레이션 사가 + +오케스트레이션 사가에서는 사가 참여자가 할 일을 알려주는 오케스트레이터 클래스를 정의한다. 사가 오케스트레이터는 커맨드/비동기 응답 상호작용을 하며 참여자와 통신한다. 사가 참여자가 작업을 마치고 응답 메시지를 오케스트레이터에 주면, 오케스트레이터는 응답 메시지를 처리한 후 다음 사가 단계를 어느 참여자가 수행할지 결정한다. + +아래 그림은 오케스트레이션 스타일로 설계한 주문 생성 사가이다. 주문 서비스에 구현된 사가 오케스트레이터는 비동기 요청/응답을 통해 사가 참여자를 호출하고 그 처리 과정에 따라 커맨드 메시지를 전송한다. 그리고 자신의 응답 채널에서 메시지를 읽어 다음 사가 단계를 결정한다. + + + +주문 생성 요청이 들어왔을때 처리 흐름은 다음과 같다. + +1. 사가 오케스트레이터가 소비자 서비스에 소비자 확인 커맨드 전송 +2. 소비자 서비스가 소비자 확인 메시지를 응답 +3. 사가 오케스트레이터는 주방 서비스에 티켓 생성 커맨드 전송 +4. 주방 서비스가 티켓 생성 메시지 응답 +5. 사가 오케스트레이터가 회계 서비스에 신용카드 승인 메시지 전송 +6. 회계 서비스가 신용카드 승인됨 메시지 응답 +7. 사가 오케스트레이터가 주방 서비스에 티켓 승인 커맨드 전송 +8. 사가 오케스트레이터가 주문 서비스에 주문 승인 커맨드 전송 + +제일 마지막 단계에서 사가 오케스트레이터는 커멘드 메시지를 주문 서비스에 전송한다. 물론 주문 생성 사가가 주문 서비스의 한 컴포넌트이기 때문에 주문을 직접 업데이트해서 승인 처리해도 되지만, 일관성 차원에서 마치 다른 참여자인 것 처럼 취급하는 것이다. + +오케스트레이션 사가에서 실패하는 케이스를 처리하기 위해서는 상태 기계를 모델링하여 유용하게 사용할 수 있다. 상태 기계(Finite-State Machine)는 상태와 이벤트에 의해 트리거되는 상태 전이(transition)로 구성된다. 전이가 발생할때마다 참여자를 호출하고, 상태간 전이는 참여자가 로컨 트랜잭션을 완료하는 시점에 트리거하면 된다. 상태 기계를 이용하여 사가를 모델링하면 설계, 구현, 테스트를 더 쉽게 진행할 수 있다. + +주문 생성 사가를 상태 기계로 표현하면 아래 그림과 같이 나타낼 수 있다. + + + +상태 기계는 사가 참여자의 여러가지 응답에 따라 다양한 상태 전이를 거치면서 결국 주문 승인, 또는 거부 두 상태중 한쪽으로 귀결된다. + +--- + +### 오케스트레이션 사가의 장점 + +- **의존관계 단순화:** 오케스트레이터는 참여자를 호출하지만 참여자는 로케스트레이터를 호출하지 않으므로 순환 의존성이 발생하지 않는다. +- **느슨한 결합:** 각 서비스는 오케스트레이터가 호출하는 API를 구현할 뿐, 사가 참여자가 발행하는 이벤트는 몰라도 된다. +- **관심사 분리, 로직 단순화:* 사가 편성 로직이 사가 오케스트레이터 한곳에만 있으므로 도메인 객체는 더 단순해지고 자신이 참여한 사가에 대해서는 알지 못한다. + +### 오케스트레이션 사가의 단점 + +- **병목현상:** 여러 서버에서 메시지를 받아 다음 참여자로 요청을 보내는 동작을 모두 한 곳에서 수행해야 하기 때문에, 아무리 비동기적으로 대기 없이 처리하더라도 트래픽이 한 곳으로 몰려 병목현상이 일어날 수 있다. (위 예시와 다르게, 비즈니스 로직을 가지지 않고 오직 순서화만 담당하는 오케스트레이터를 분리해내면 일부 해결되긴 한다.) + + +참고 + +[마이크로서비스패턴](http://www.yes24.com/Product/Goods/86542732) + +https://microservices.io/patterns/data/saga.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\213\234\353\247\250\355\213\261\342\200\205\353\262\204\354\240\200\353\213\235.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\213\234\353\247\250\355\213\261\342\200\205\353\262\204\354\240\200\353\213\235.md" new file mode 100644 index 00000000..d57a789e --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\354\213\234\353\247\250\355\213\261\342\200\205\353\262\204\354\240\200\353\213\235.md" @@ -0,0 +1,40 @@ +--- +title: '시맨틱 버저닝' +lastUpdated: '2024-03-02' +--- + +마이크로서비스 애플리케이션은 클라이언트를 다른 서비스 팀이 이비 개발한 경우가 대부분이기 때문에 서비스 API를 변경하기가 어렵다. 서비스를 클라리언트를 모두 찾아 강제로 업그레이드시키기도 쉽지 않다. 또 요즘은 유지보수시 서버를 내리지 않기 때문에 규칙적인 단계로 서비스를 업그레이드하여 신구버전을 동시에 실행해야한다. + +[시맨틱 버저닝](https://semver.org/) 명세는 API 버저닝에 관한 유용한 지침서이다. 여기에ㅔ는 버전 번호를 사용하고 증가시키는 규칙들이 면시되어있다. 시맨틱 버저닝은 원래 소프트웨어 패키지의 버저닝 용도로 쓰였지만, 분산 시스템의 API 버저닝에도 사용할 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/212671356-17763dcc-7716-432f-8555-a6b7ae35bde3.png) + +Major : 하위 호환되지 않는 변경분을 API에 적용 +Minor : 하위 호환되는 변경분을 API에 적용 +Patch : 하위 호환되는 오류 수정 + +## 하위 호환되는 소규모 변경 + +API에 뭔가를 추가하는 변경은 대부분 하위호환이 가능하다. + +- 옵션 속성을 요청에 추가 +- 속성을 응답에 추가 +- 새 작업을 추가 + +이런 종류의 변경은 새 서비스에 적용해도 기존 클라이언트 역시 별 문제없이 동작한다. 이것은 견고성 원칙을 지키기 때문이다. + +> 당신이 하는 일은 보수적으로, 다른 사람들이 하는 일은 관대하게 바라보라 + +요청 속성이 누락되어도 서비스는 기본값을 제공하고, 서비스가 필요한 것 보다 많은 속성을 응답하더라도 클라이언트는 무시할 수 있어야한다. 클라이언트/서비스가 견고성 원칙을 뒷받침하는 요청/응답 포맷을 사용하면 이런 과정이 매끄럽게 진행된다. + +## 중대한 대규모 변경 + +경우에 따라선 매우 중요한, 기존 버전과 호환이 안 되는 변경을 API에 적용해야 할 떄가 있다. 클라이언트를 강제로 업그레이드 하는 것은 불가능하므로 **일정 기간동안 서비스는 신구버전 API를 모두 지원**해야한다. HTTP 기반의 REST API라면 URL에 메이저 버전 번호를 잡입할 수 있다. (`**/v1/**`, `**/v2/**`) + +HTTP content negotiation을 이용해서 MIME 타입 내부에 버전 번호를 끼워넣는 방법도 있다. 버전 1.x의 주문은 클라이언트가 이렇게 요청할 수 있다. + +``` +GET /orders HTTP/1.1 +Accept: application/vnd.example.resource+json; version=1 +... +``` diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\206\265\354\213\240.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\206\265\354\213\240.md" new file mode 100644 index 00000000..0fe86bb3 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\206\265\354\213\240.md" @@ -0,0 +1,58 @@ +--- +title: '통신' +lastUpdated: '2024-03-02' +--- + +MSA에서는 각 서버가 서로 통신함으로써 여러 도메인이 엮인 요청을 처리할 수 있도록 한다. 이때 서버간 통신을 위해 일반적인 IPC인 REST를 사용할 수 있지만, REST는 동기 프로토콜이기 때문에 호출한 서비스가 응답할 때까지 클라이이언트가 대기해야한다는 단점이 있다. 따라서 서비스가 동기 프로토콜로 통신하면 그만큼 애플리케이션 가용성은 저하될 수밖에 없다. 서비스가 모두 HTTP 통신을 사용한다면, 어느 하나의 서버가 내려갔을때 제대로 동작할 수 없다. + +## 동기 상호 작용 제거 + +이를 해결하기 위해 비동기 만 있는 서비스를 정의해서 해결할 수도 있지만, 어쩔 수 없이 서비스에 동기 API를 포함시켜야 할 경우가 많다. 이런 경우에는 동작을 조금 우회한 비동기 처리 기법을 사용할 수 있다. + +### 비동기 상호 작용 + +클라이언트가 요청 메시지를 주문 서비스에 전송하여 주문을 생성한다. 그러면 주문 서비스는 다른 서비스와 메시지를 비동기 방식으로 교환하고 최종적으로 클라이언트에 응답 메시지를 전송한다. + + + +메시징 채널을 통해 메시지를 전송해서 서로 비동기 통신하기 때문에, 응답을 대기하며 블로킹될 필요가 없다. + +이런 아키텍처에서는 서버가 메시지를 소비할 수 있는 시점에 메시지 브로커(MQ)에 있는 메시지를 꺼내와 자유롭게 처리할 수 있기 때문에 매우 탄력적이다. + +### 데이터 복제 + +서비스에 동기 API가 있는 경우 사용할 수 있는 방법이다. + +데이터 복제는 서비스 요청 처리에 필요한 데이터를 레플리카, 즉 복제본을 만들어 가지는 구조이다. 실제 소비자, 음식점 DB를 가진 서비스가 발행하는 이벤트를 구독하여 복제본을 최신 데이터 상태로 유지한다면 동기식으로 서비스와 통신할 필요 없이 요청을 처리할 수 있다. + + + +데이터 복제는 때에 따라 유용하게 쓰일 수 있다. (예를 들어 주문 서비스가 음식점 서비스에서 수신한 데이터를 복제해서 메뉴 할목을 검증하고, 단가를 매기는 경우 등 간단한 유효성 검증을 위한 데이터가 필요할때 등이 있다.) 하지만 물론 대용량 데이터의 레플리카를 만드는 것은 엄청나게 비효율적이기 때문에 상황에 따라 적절히 사용해야한다. + +### 응답 반환 후 마무리 + +이 방법 또한 동기 API를 쓰는 경우 취할 수 있는 방법이다. 간단하게 표현하자면, 일단 동기 요청에 대해 반환한 후 비동기적으로 실제 동작을 수행하는 방법이다. + +Screenshot 2023-01-25 at 20 27 24 + +요청 처리 도중 동기 통신을 제거하기 위해서 사용자에게 들어온 요청은 다음과 같이 처리한다. + +1. 로컬(주문서비스)에서 사용 가능한 데이터로 요청을 검증한다. +2. 메시지를 OUTBOX 테이블에 삽입하는 식으로 DB를 업데이트한다. +3. 클라이언트에 응답을 반환한다. + +서비스는 요청 처리 중에 다른 서비스와 동기적 상호작용을 하지 않는 대신, 다른 서비스에 메시지를 비동기 전송한다. 이렇게 하면 서비스를 느슨하게 결합할 수 있다. + +주문 서비스를 위와 같이 구현한다면 먼저 주문을 PENDING 상태로 생성하고, 다른 서비스와 메시지를 교환하여 주문을 비동기 검증한 다음 이후 동작을 수행할 수 있다. createOrder를 호출하면 벌어지는 이벤트의 순서는 다음과 같다. + +1. 주문 서비스: 주문을 PENDING 상태로 생성한다. +2. 주문 서비스: 주문 ID가 포함된 응답을 클라이언트에 반환한다. +3. 주문 서비스: ValidateConsumerInfo 메시지를 소비자 서비스에 전송한다. +4. 주문 서비스: ValidateOrderDetails 메시지를 음식점 서비스에 전송한다. +5. 소비자 서비스: ValidateConsumerInfo 메시지를 받고 주문 가능한 소비자인지 확인 후, ConsumerValidated 메시지를 주문 서비스에 보낸다. +6. 음식점 서비스: ValidateOrderDetails 메시지를 받고 올바른 메뉴 항목인지, 그리고 음식점에서 주문 배달지로 배달 가능한지 여부를 확인 후 OrderDetailsValidated 메시지를 주문 서비스에 전송한다. +7. 주문 서비스: ConsumerValidated와 OrderDetailsValidated를 받고 주문 상태를 VALIDATED로 변경한다. + +주문 서비스가 ConsumerValidated와 OrderDetailsValidated 메시지를 받는 것이 어떤 순서인지는 크게 상관 없다. 이 서비스는 메시지를 수신하는대로 주문 상태를 변경할 수 있다. 주문 서비스는 주문 검증을 마친 후 주문 생성 프로세스를 최종적으로 마친다. 혹여 다른 서버가 내려가는 사고가 발생하더라도 주문 서비스는 계속 주문을 생성하고 클라이언트에 응답할 수 있다. 나중에 소비자 서비스가 재가동되면 큐에 쌓인 메시지를 처리해서 밀린 주문을 다시 검증하기만 하면 되기 때문이다. + +이 경우, 클라이언트가 주문 생성 성공 여부를 알아내려면 주기적으로 폴링하거나 주문 서비스가 알림 메시지를 보내주어야한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\352\262\251\353\246\254.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\352\262\251\353\246\254.md" new file mode 100644 index 00000000..e5772646 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\352\262\251\353\246\254.md" @@ -0,0 +1,36 @@ +--- +title: '트랜잭션 격리' +lastUpdated: '2024-03-02' +--- + +[사가](사가 패턴.md)를 사용하는 경우, 여러 서버와 도메인에 걸친 트랜잭션을 보장해줄 수 있지만 트랜잭션의 완전한 격리를 완벽히 보장할 순 없다. 따라서 [dirty read나 nonrepeatable read](../../데이터베이스 DataBase/DB설계/트랜잭션 ACID와 격리수준.md) 등의 문제들이 생길 수 있는데, 이런 문제들을 해결하기 위해선 사가의 격리를 위한 전략을 사용해줘야한다. + +이를 위한 전략으로는 아래와 같은 것들이 있다. + +## 시맨틱 락(Semantic Lock) + +- 보상 가능 트랜잭션이 생성/수정하는 레코드에 무조건 플래그를 세팅하는 대책이다. 레코드가 아직 커밋 전이라서 변경될지 모르니, 다른 트랜잭션이 레코드에 접근하지 못하도록 락을 걸어놓는 것이다. 그 플래그는 재시도 가능 트랜잭션(사가 완료) 또는 보상 트랜잭션(사가 롤백)으로 인해 해제될 수 있다. + +- 잠금된 레코드를 어떻게 처리할지는 각 사례별로 결정을 해줘야한다. 만약 클라이언트가 PENDING 상태의 Order를 cancel 해달라고 요청한다면 해당 요청을 실패 처리하거나 하는 식이다. 시맨틱 락을 사용하면 ACID 고유의 격리 기능을 되살릴 수 있다. 물론 애플리케이션에서 락 상태를 관리해야한다는 부담이 있고, Deadlock이 일어날 수 있는 경우 감지하여 해소할 수 있는 알고리즘이 필요할 수도 있다. + +## 교환적 업데이트 + +- 업데이트를 교환적(Commutative)으로, 즉 어떤 순서로도 실행 가능하게 설계하면 소실된 업데이트 문제를 방지할 수 있다. + +- 보상 가능 트랜잭션이 계좌에서 돈을 인출 후 사가를 롤백시켜야 하는 상황이라면 보상 트랜잭션은 다른 사가의 업데이트를 덮어쓸 일 없이 단순히 돈을 다시 입금해주기만 하면 된다. + +## 비관적 관점(Pessimistic View) + +- 비관적 관점은 dirty read로 인한 리스크를 최소화 하기 위해 사가 단계의 순서를 재조정하는 것이다. 다른 사가의 주요 작업의 조건이 되는 도메인을, 사가의 마지막 단계에서 수정하여 충돌 가능성을 낮춘다. + +## 값 다시 읽기(reread value) + +- 업데이트가 소실되는 것을 방지하기 위해 레코드 업데이트 전, 값을 다시 읽어 값이 변경되지 않았는지 확인하는 방법이다. 값을 다시 읽었을때 변경되었다면, 사가를 중단하고 나중에 재시작한다. 이 대책은 일종의 낙관적 오프라인 락(Optimistic Offline Lock)이라고 볼 수 있다. + +- 이 대책을 주문 생성사가에 적용하면 주문이 승인되는 도중 최소되는 불상사를 막을 수 있다. 주문이 처음 생성된 이후, 다시 조회 과정을 거쳐 변경 여부를 확인하면 데이터 충돌 여부에 따라 사가를 멈추고 롤백하거나, 사가를 계속 진행해나가는 둘 중 하나의 동작만을 수행할 수 있다. + +## 버전 파일 + +- 버전 파일(version file)은 레코드에 수행한 작업을 하나하나 기록하는 대책이다. 즉, 비교환적(noncommutative) 작업을 교환적(commutative) 작업으로 변환하는 것이다. 예를 들어 주문 생성 사가와 주문 취소 사가가 동시에 실행되었다고 해보자. 시맨틱 락 대책을 쓰지 않으면 주문 생성 사가가 소비자 신용카드를 승인하기 전에 주문 취소 사가가 해당 신용카드를 승인 취소하는 말도 안 되는 상황이 벌어질 수 있다. + +- 순서가 안 맞는 요청을 회계 서비스가 받아 처리하려면, 작업이 도착하면 기록해 두었다가 정확한 순서대로 실행하면 된다. 위와 같은 경우라면, 회계 서비스는 일단 승인 취소 요청을 기록하고, 나중에 신용카드 승인 요청이 도착하면 이미 승인 취소 요청이 접수된 상태이니 승인 작업은 생략해도 되겠구나라고 인지할 수 있도록 하여 충돌을 막는 것이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\353\241\234\352\267\270\342\200\205\355\205\214\354\235\274\353\247\201\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\353\241\234\352\267\270\342\200\205\355\205\214\354\235\274\353\247\201\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..927f0b48 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/MSA/\355\212\270\353\236\234\354\236\255\354\205\230\342\200\205\353\241\234\352\267\270\342\200\205\355\205\214\354\235\274\353\247\201\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,27 @@ +--- +title: '트랜잭션 로그 테일링 패턴' +lastUpdated: '2024-03-02' +--- + +- 메시지 브로커는 보통 메시지를 적어도 한 번 이상 전달한다. 클라이언트나 네트워크, 혹은 브로커 자신에 이상이 있는 경우 같은 메시지를 여러번 전달할 수도 있다. + +- 하지만 이때 메시지를 여러번 전달함으로 인해 중복 메세지가 발생하여 멱등하지 않은 시스템에 지장을 주거나, 메시지 순서가 꼬여 잘못 처리될 수도 있다. 이를 해결하기 위해 ID를 기록하여 검사하거나 DB 폴링 방식을 사용하기도 한다. + +- 이때 사용할 수 있는 정교하고 성능이 꽤 좋은 방식중 하나로는, 트랜잭션 로그 테일링 패턴이 있다. + +- 말 그대로 메시지 릴레이를 통해 DB 트랜잭션 로그(커밋 로그)를 tailing하는 방법이다. 애플리케이션에서 커밋된 업데이트를 각 DB의 트랜잭션 로그 항목(log entry)으로 남긴다. + +- 그리고 그 후에 트랜잭션 로그 마이너(transaction log miner)로 트랜잭션 로그를 읽어 변경분을 하나씩 메시지로 메시지 브로커에 발행하는 절차로 수행된다. + + + +### 사례 + +- **[Debezium:](http://debezium.io)** DB 변경분을 아파치 카프카 메시지 브로커에 발행하는 오픈 소스 프로젝트 + +- **[LinkedIn Databus:](https://github.com/linkedin/databus)** 오라클 트랜잭션 로그를 마이닝하여 변경분을 이벤트로 발행하는 오픈 소스 프로젝트. 링크드인에서는 데이터버스를 이용하여 다양한 파생 데이터 저장소를 레코드 체계와 동기화한다. + +- **[DynamoDB streams:](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/streams.html)** DynamoDB 스트림즈는 최근 24시간 동안 DynamoDB 테이블 아이템에 적용된 변경분(생성, 수정, 삭제)을 시간 순으로 정렬하여 가고 있으면, 애플리케이션은 스트림에서 변경분을 읽어 이벤트로 발행할 수 있다. + +- **[Eventuate Tram:](https://github.com/eventuate-tram/eventuate-tram-core)** 오픈 소스 트랜잭션 메시징 라이브러리. MYSQL 빈로그(binlog) 프로토콜, Postgres(포스트그레스) WAL(Write-Ahead Logging), 폴링을 응용해서 OUTBOX 테이블의 변경분을 읽어 아파치 카프카로 발행한다. + \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/SOLID.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/SOLID.md" new file mode 100644 index 00000000..6e1312b7 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/SOLID.md" @@ -0,0 +1,28 @@ +--- +title: 'SOLID' +lastUpdated: '2024-03-02' +--- + + +## 1. 단일 책임 원칙 +### (Single Responsibility Principle, SRP) + 하나의 모듈는 하나의 책임만 가져야 한다. SRP를 적용하면 메소드의 책임 영역이 확실해지기 때문에 한 기능이 수정되었을 때 다른 연쇄작용을 일으키지 않는다. 또, 책임을 적절히 분배함으로써 코드의 가독성이 향상되고 유지보수가 용이해진다. + +## 2. 개방 폐쇄 원칙 +### (Open-Closed Principle, OCP) + 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야 한다. 코드를 수정하거나 기능을 추가할 일이 생겨도 기존 구성 요소는 바뀌지 말아야한다. 추상화와 다형성으로 구현할 수 있다. + +## 3. 리스코프 치환의 원칙 +### (Liskov Substitutiong Principle, LSP) + 서브 타입은 기반 타입이 약속한 규약을 지켜야한다. 상속은 다형성을 통한 확장성 획득을 목표로 하기 때문에, 부모 객체를 자식 객체로 치환했을 때도 정상적으로 동작해야한다. LSP 원칙은 변화에 열려있는 프로그램을 만들 수 있도록 하는 바탕이다. OCP와도 연관된다. + +## 4. 인터페이스 분리 원칙 +### (Interface segregation Principle, ISP) + 한 클래스는 자신이 이용하지 않는 메서드에 의존하지 않아야힌다. 어떤 객체가 범위가 넓고 일반적인 인터페이스를 상속받으면 필요없는 메소드를 구현해야한다. 이것은 인터페이스에게 과하게 많은 책임을 지게 하는 일이다. ISP는 클라이언트로부터 발생하는 변경이 다른 클라이언트에 미치는 영향을 최소화하는 것을 목표로 한다. + +## 5. 의존관계 역전 원칙 +### (Dependency Icersion Principle, DIP) + 어떤 객체는 구체화에 의존하지 말고 추상화에 의존해야한다. 프로그램의 일부 기능이 수정될 때, 구체화에 의존하는 코드는 그 기능이 관여하는 부분을 모두 수정해야하지만 DIP를 지키면 같은 인터페이스를 상속받는 다른 클래스로 얼마든지 바꿔낄 수 있다. 의존성 주입을 적절히 사용하여 실현할 수 있다. + +--- + diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\353\271\214\353\215\224\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\353\271\214\353\215\224\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..c403260b --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\353\271\214\353\215\224\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,62 @@ +--- +title: '빌더 패턴' +lastUpdated: '2024-03-02' +--- +

빌더 패턴은 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다. 빌더 패턴을 사용하면 객체를 생성할 떄 필요한 값만 넣을 수 있다. 그리고 파라미터룰 사용한 생성자가 아니라 변수명을 명시하여 생성하는 것이기 때문에 변수 순서가 바뀌거나 새로 추가될때 유연하게 대처할 수 있고 매개변수가 많아져도 가독성을 높일 수 있다.

+

구현하는 것이 복잡하기 때문에 직접 구현하면 코드 구조가 읽기 어려워진다는 단점이 있다. Lombok에서 제공해주는 @Builder 어노테이션을 사용하면 Lombok이 자동으로 빌더를 만들어준다.

+ + + +--- + +## 예시 코드 + +```java +@Getter +@Setter +public class Pizza { + + String name; + String topping; + String sauce; + + public Pizza(String name, String topping, String sauce) { + this.name = name; + this.topping = topping; + this.sauce = sauce; + } + + public static PizzaBuilder builder() { + return new PizzaBuilder(); + } + + public static class PizzaBuilder { + + String name; + String topping; + String sauce; + + PizzaBuilder(){} + + public Pizza build() { + return new Pizza(this.name, this.topping, this. sauce); + } + + public PizzaBuilder name(String name){ + this.name = name; + return this; + } + + public PizzaBuilder topping(String topping){ + this.topping = topping; + return this; + } + + public PizzaBuilder sauce(String sauce){ + this.sauce = sauce; + return this; + } + } +} +``` +이
링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\354\213\261\352\270\200\355\206\244\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\354\213\261\352\270\200\355\206\244\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..a2da64f7 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\354\213\261\352\270\200\355\206\244\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,87 @@ +--- +title: '싱글톤 패턴' +lastUpdated: '2024-03-02' +--- +

싱글톤 패턴은 애플리케이션이 시작될 때, 어떤 클래스가 최초 한 번만 메모리를 할당(static)하고 인스턴스를 생성하여 사용하는 패턴이다. 똑같은 인스턴스를 여러개 생성하지 않고, 어디에서 접근하든 기존에 만들었던 인스턴스를 이용하도록 한다.

+ + + +--- + +## 예시 코드 + +```java +public class LazyInitSingleton { + + private static LazyInitSingleton instance; + + private LazyInitSingleton() {} + + public static LazyInitSingleton getInstance() { + if (instance == null) { + instance = new LazyInitSingleton(); + } + return instance; + } + +} +``` + +

가장 간단하게 싱글톤 패턴을 구현한 코드이다. getInstance() 메서드를 호출 할 떄 인스턴스를 생성하기 때문에 Lazy하다는 특징이 있다. 하지만 멀티 쓰레드 환경에서 동시 접속이 발생하면 인스턴스가 여러개 생성될 수 있다는 단점이 있다.

+ +--- + +```java +public class EagerInitSingleton { + + private static EagerInitSingleton instance = new EagerInitSingleton(); + + private EagerInitSingleton() {} + + public static EagerInitSingleton getInstance() { + return instance; + } + +} +``` + +

멀티 쓰레드 문제를 해결하기 위해 위처럼 클래스가 메모리에 할당되는 시점에 인스턴스를 생성하는 방법을 사용할 수도 있다. 하지만 이 방식을 사용하면 이 클래스가 사용되지 않더라도 인스턴스를 생성해야 하기 때문에 메모리가 낭비된다.

+ +--- + +```java +public class InnerHolderSingleton { + + private InnerHolderSingleton() { } + + private static class SingletonHolder { + private static final InnerHolderSingleton INSTANCE = new InnerHolderSingleton(); + } + + public static InnerHolderSingleton getInstance() { + return SingletonHolder.INSTANCE; + } + +} +``` + +

위처럼 innerHolder를 사용해서 싱글톤을 구현하는 방법도 있다. 이렇게 하면 멀티 쓰레드문제를 해결하면서 Lazy한 특성을 가질 수 있다. 중첩클래스 SingletonHolder는 getInstance() 메소드가 호출되기 전에는 참조 되지 않으며,최초로 getInstance() 메소드가 호출될 때 클래스로더에 의해 Singleton 인스턴스를 생성하여 리턴하기 때문에 Lazy하다. 그리고 SingletonHolder 내부 인스턴스는 static 이기 때문에 클래스 로딩 시점에 한 번만 호출되고, final을 사용해 다시 값이 할당되지 않는다.

+

하지만 이렇게 열심히 구현해도 리플렉션을 쓰면 생성자에 접근하여 싱글톤을 꺠뜨릴 수 있다. 특히 역직렬화할때 이 리플렉션으로 인해 문제가 생길 수 있기 때문에 아래와 같이 리플렉션에 안전한 Enum을 사용해서 싱글톤을 구현하기도 한다.

+ +--- + +```java +public enum EnumSingleton { + + INSTANCE; + +} +``` + +싱글톤 패턴은 프로그램의 성능과 동시성 문제를 고려해야 하기 떄문에 다양한 방식으로 구현될 수 있다. + +이 링크로 가면 코드를 볼 수 있다. + +출처:
+https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4 + diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\354\266\224\354\203\201\355\214\251\355\206\240\353\246\254\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\354\266\224\354\203\201\355\214\251\355\206\240\353\246\254\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..4e8ea028 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\354\266\224\354\203\201\355\214\251\355\206\240\353\246\254\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,139 @@ +--- +title: '추상팩토리 패턴' +lastUpdated: '2024-03-02' +--- +

서로 관련있는 여러 객체를 만들어주는 인터페이스를 만들어 구체적으로 어떤 클래스의 인스턴스를(concrete product)를 사용하는지 감추는 패턴이다.

+

구체적인 객체 생성 과정을 추상화한 인터페이스를 제공한다는 점에서 팩토리 메소드 패턴과 비슷하지만 팩토리 메소드 패턴은 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적이고, 추상 팩토리 패턴은 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적이라는 점에서 다르다.

+ + + +--- + +## 예시 코드 + +```java +@Getter +@Setter +@AllArgsConstructor +public abstract class Pizza { + + private String name; + private Sauce sauce; + private Topping topping; +} + +public class CheesePizza extends Pizza { + public CheesePizza(String name, Sauce sauce, Topping topping) { + super(name, sauce, topping); + } +} + +public class PineapplePizza extends Pizza { + public PineapplePizza(String name, Sauce sauce, Topping topping) { + super(name, sauce, topping); + } +} +``` + +추상 클래스 Pizza와 그 클래스를 상속받은 CheesePizza, PineapplePizza가 있다. Pizza는 String형의 name, 그리고 Sauce와 Topping을 가지고 있다. + +--- + +```java +public interface Topping {} + +@NoArgsConstructor +public class CheeseTopping implements Topping{} + +@NoArgsConstructor +public class PineappleTopping implements Topping{} +``` + +```java +public interface Sauce {} + +@NoArgsConstructor +public class CheeseSauce implements Topping{} + +@NoArgsConstructor +public class PineappleSauce implements Topping{} + +``` + +Sauce와 Topping 클래스는 인터페이스이고, Topping은 CheeseTopping과 PineappleTopping을, Sauce는 CheeseSauce와 PineappleSauce를 자식으로 가지고 있다. 추가적인 메서드나 필드는 구현하지 않았다. + +--- + +```java +public interface PizzaIngredientFactory { + public Sauce createSauce(); + public Topping createTopping(); +} + +public class CheesePizzaIngredientFactory implements PizzaIngredientFactory { + + @Override + public Sauce createSauce() { + return new CheeseSauce(); + } + + @Override + public Topping createTopping() { + return new CheeseTopping(); + } +} + +public class PineapplePizzaIngredientFactory implements PizzaIngredientFactory { + + @Override + public Sauce createSauce() { + return new PineappleSauce(); + } + + @Override + public Topping createTopping() { + return new PineappleTopping(); + } +} +``` + +그리고 피자의 재료를 준비해주는 PizzaIngredientFactory가 있다. 피자 종류별로 각각 소스와 토핑을 지정해준다. + +--- + +```java +public interface PizzaFactory { + + public Pizza createPizza(); +} + +public class CheesePizzaIngredientFactory implements PizzaIngredientFactory { + + @Override + public Sauce createSauce() { + return new CheeseSauce(); + } + + @Override + public Topping createTopping() { + return new CheeseTopping(); + } +} + +public class PineapplePizzaIngredientFactory implements PizzaIngredientFactory { + + @Override + public Sauce createSauce() { + return new PineappleSauce(); + } + + @Override + public Topping createTopping() { + return new PineappleTopping(); + } +} +``` + +PizzaIngredientFactory에서 넘어온 정보로 진짜 피자를 만드는 PizzaFactory가 있다. 이 PizzaFactory의 createPizza()메서드를 호출하면, 요청하는 클래스 입장에선 Pizza의 자식 클래스인 Sauce와 Topping에 대한 정보를 전혀 모르는 채로 Pizza를 생성할 수 있다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..ce322e6a --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,80 @@ +--- +title: '팩토리메소드 패턴' +lastUpdated: '2024-03-02' +--- +

팩토리 메소드 패턴은 부모(상위) 클래스 코드에 구체 클래스 이름을 감추고, 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이다.

+

구체적인 객체 생성 과정을 추상화한 인터페이스를 제공한다는 점에서 추상 팩토리 패턴과 비슷하지만 추상 팩토리 패턴은 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적이고, 팩토리 메소드 패턴은 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적이라는 점에서 다르다.

+

'확장에 대해 열려있고 수정에 대해 닫혀있어야 한다'는 개방-폐쇄 원칙(OCP)을 만족하는 객체 생성 방법이다.

+ + + +--- + +## 예시 코드 + +```java +@Getter +@Setter +@NoArgsConstructor +public abstract class Pizza { + + String name; + String topping; + String sauce; +} + +public class CheesePizza extends Pizza{ + + public CheesePizza() { + super(); + this.setName("Cheese pizza"); + this.setSauce("Cheese sauce"); + this.setTopping("Cheese"); + } +} + +public class PineapplePizza extends Pizza { + + public PineapplePizza() { + super(); + this.setName("Pineapple pizza"); + this.setSauce("Pineapple sauce"); + this.setTopping("Pineapple"); + } +} +``` + +추상 클래스 Pizza와 그 클래스를 상속받은 CheesePizza, PineapplePizza가 있다. + +--- + +```java +public enum PizzaType { + CHEESE, + PINEAPPLE +} +``` + +어떤 타입(클래스)의 피자를 생성할지 정확하게 입력받기 위해 enum 클래스를 만든다. (선택사항임) + +--- + +```java +@NoArgsConstructor +public class PizzaFactory { + + public Pizza createPizza(PizzaType pizzaType){ + switch (pizzaType) { + case CHEESE: + return new CheesePizza(); + case PINEAPPLE: + return new PineapplePizza(); + } + return null; + } +} +``` + +enum 값을 입력받아 각각 다른 클래스의 피자를 반환하는 팩토리 클래스를 만든다. 이렇게 하면 메인 클래스에서 자세한 피자 종류 클래스를 정해주지 않고 구체 클래스 객체를 생성할 수 있다. 클라이언트는 Pizza Interface를 사용해서 로직을 구현하면 된다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\355\224\204\353\241\234\355\206\240\355\203\200\354\236\205\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\355\224\204\353\241\234\355\206\240\355\203\200\354\236\205\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..71173b9f --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/1.\342\200\205\354\203\235\354\204\261\355\214\250\355\204\264/\355\224\204\353\241\234\355\206\240\355\203\200\354\236\205\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,61 @@ +--- +title: '프로토타입 패턴' +lastUpdated: '2024-03-02' +--- + +기존 인스턴스를 프로토타입으로 사용해 새 인스턴스를 만드는 패턴이다. 비슷한 객체를 여러개 만드는 경우 인스턴스를 생성하는 복잡한 과정을 거칠 필요 없이 새 인스턴스를 만들 수 있다는 장점이 있다. + + + +--- + +## 예시 코드 + +java의 Cloneable 인터페이스와 Object의 clone() 메서드를 사용하면 프로토타입 패턴을 간단하게 사용할 수 있다. + +```java +@Getter +@AllArgsConstructor +public class Pizza implements Cloneable{ + + String name; + String topping; + String sauce; + + @Override + public Pizza clone() { + try { + return (Pizza) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} +``` + +Cloneable을 상속받고, clone 메서드를 구현해줬다. + +```java + public static void main(String[] args) { + + Pizza pizza1 = new Pizza("Cheese Pizza", "Cheese Topping", "Tomato sauce"); + + Pizza pizza2 = pizza1.clone(); + + System.out.println("Pizza1"); + System.out.println("name = " + pizza1.getName()); + System.out.println("Sauce = "+ pizza1.getSauce()); + System.out.println("Topping = "+ pizza1.getTopping()); + System.out.println(); + + System.out.println("Pizza2"); + System.out.println("name = " + pizza2.getName()); + System.out.println("Sauce = "+ pizza2.getSauce()); + System.out.println("Topping = "+ pizza2.getTopping()); + System.out.println(); + } +``` + +pizza1의 정보가 pizza2에 똑같이 복사되는 것을 볼 수 있다. + +이 링크로 가면 코드를 볼 수 있다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..53df9827 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,155 @@ +--- +title: '데코레이터 패턴' +lastUpdated: '2024-03-02' +--- + +데코레이터 패턴은 주어진 상황 및 용도에 따라 객체에 책임을 덧붙이는 패턴이다. 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우에 각 추가 기능을 Decorator 클래스로 정의 한 후 필요한 Decorator 객체를 합함으로써 추가 기능의 조합을 설계 하는 방식이다. 데코레이터 패턴은 객체의 결합 을 통해 기능을 동적으로 유연하게 확장 할 수 있게 해준다. + +--- + +## 예시 코드 + +```java +public interface CommentService { + + void addComment(String comment); +} +public class DefaultCommentService implements CommentService{ + + @Override + public void addComment(String comment) { + System.out.println(comment); + } +} +``` + +댓글을 추가하는 서비스 인터페이스와 그걸 상속받아 구현한 클래스가 있다. 여기에 댓글을 필터링하는 로직을 데코레이터 패턴을 통해 추가해볼 것이다. + +--- + +```java +@RequiredArgsConstructor +public class CommentDecorator implements CommentService{ + + private final CommentService commentService; + + @Override + public void addComment(String comment) { + commentService.addComment(comment); + } + +} +``` + +CommentService를 상속받아 CommentDecorator클래스를 만든다. CommentService를 주입받아서 CommentService의 addComment() 메서드를 호출한다. 이 친구가 이제부터 추가할 Decorator들의 근간이 되어줄 것이다. + +--- + +```java +public class SpamFilteringCommentDecorator extends CommentDecorator{ + + public SpamFilteringCommentDecorator(CommentService commentService) { + super(commentService); + } + + @Override + public void addComment(String comment) { + if(isNotSpam(comment)) super.addComment(comment); + } + + private boolean isNotSpam(String comment) { + return !comment.contains("http"); + } + +} + +public class DateCommentDecorator extends CommentDecorator { + + public DateCommentDecorator(CommentService commentService) { + super(commentService); + } + + @Override + public void addComment(String comment) { + super.addComment(addDate(comment)); + } + + private String addDate(String comment) { + return comment + "["+ LocalDateTime.now() +"]"; + } + +} + +public class TrimmingCommentDecorator extends CommentDecorator{ + + public TrimmingCommentDecorator(CommentService commentService) { + super(commentService); + } + + @Override + public void addComment(String comment) { + super.addComment(trim(comment)); + } + + private String trim(String comment) { + return comment.replace("...", ""); + } + +} +``` + +addComment() 메서드에 필터링, 날짜 추가 등 기능을 추가(Decorate) 해주는 Decorator들이다. 이제 이걸 DefaultCommentService에 추가해주면 된다. + +--- + +```java +public class App { + + private static final boolean enabledSpamFilter = true; + private static final boolean enabledTrimming = true; + private static final boolean enabledDate = true; + + public static void main(String[] args) { + + CommentService commentService = new DefaultCommentService(); + + if (enabledSpamFilter) commentService = new SpamFilteringCommentDecorator(commentService); + if (enabledTrimming) commentService = new TrimmingCommentDecorator(commentService); + if (enabledDate) commentService = new DateCommentDecorator(commentService); + + commentService.addComment("댓글"); + commentService.addComment("ㅏㅓㅑㅐ..............."); + commentService.addComment("https"); + } +} +``` +바로 이렇게 추가해준다. 이렇게 하면 조건에 따라 동적으로 Decorator 로직을 추가해줄 수 있다. Decorator를 모두 추가한 commentService에서 addComment()를 호출하면 Decorator의 addComment()가 차례대로 실행된 후에 서비스가 실행된다. 근데 이게 왜 될까? 같은 인터페이스를 상속받았으니까 특정 Decorator를 commentService에 대입할 수 있다는건 알겠는데, 그게 어떻게 이렇게 차곡차곡 쌓여서 야무지게 실행될 수 있지? 라는 생각이 들었다. 각 클래스의 addComment() 메서드가 호출될 때 각 메서드명을 출력하여 메서드의 실행 시기를 추척해보니 다음과 같은 결과가 나왔다. +
+ +> DateCommentDecorator.addComment
+> CommentDecorator.addComment
+> +> TrimmingCommentDecorator.addComment
+> CommentDecorator.addComment
+> +> SpamFilteringCommentDecorator.addComment
+> CommentDecorator.addComment
+> +> DefaultCommentService.addComment
+> +> 댓글[2022-08-11T14:45:54.203837300] +> + +
+

Decorator의 addComment()가 main에서 추가했던 순서의 역순으로 실행되는 것을 볼 수 있다. Decorator를 추가할 때 commentService를 필드로 가지도록 생성하여 대입했던 것을 생각하면 쉽게 구조를 파악할 수있다.

+
+DateCommentDecorator(CommentService)의 addComment()가
+TrimmingCommentDecorator(CommentService)를 호출하고 TrimmingCommentDecorator가
+SpamFilteringCommentDecorator(CommentService)를 호출하고 SpamFilteringCommentDecorator가
+DefaultCommentService(CommentService)를 호출하는 구조였다.
+
+

마치 마트료시카처럼 관계가 중첩되어 있어서 가장 겉에 있는 Decorator가 안에 있는 Decorator를 호출하다가, 마지막에 진짜 DefaultCommentService가 호출되면서 출력되었다. 이름은 Decorator지만 Decorator 클래스가 Service클래스를 꾸며주는게 아니라 감싸는 구조였던 것이다...! 마치 네트워크에서 정보를 캡슐화하여 정보를 전송하는 것과 비슷한 느낌이다.

+ +

아무튼 이 데코레이터 패턴을 사용하면 동적으로 기능을 자유롭게 추가할 수 있다는 장점이 있다. 특정 기능을 유연하게 추가해야 할 때 이 패턴을 활용하면 좋을 것 같다.

+ +이 링크로 가면 코드를 볼 수 있다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\353\270\214\353\246\277\354\247\200\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\353\270\214\353\246\277\354\247\200\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..3c0e1c54 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\353\270\214\353\246\277\354\247\200\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,88 @@ +--- +title: '브릿지 패턴' +lastUpdated: '2024-03-02' +--- + +브릿지 패턴은 구현부에서 추상층을 분리하여 각자 독립적으로 변형할 수 있도록 하는 패턴이다. 즉, 기능과 구현에 대해 별도의 클래스로 구현하여 서로의 코드에 간섭하지 않고 변형, 확장할 수 있도록 하는 것이다. + + + +--- + +## 예시 코드 + +```java +@AllArgsConstructor +public abstract class Pizza { + + private String name; + private Recipe recipe; + + public String result(){ + return recipe.getName() + " " + name; + } +} + +public class CheesePizza extends Pizza { + public CheesePizza(Recipe recipe){ + super("치즈 피자", recipe); + } +} + +public class PineapplePizza extends Pizza { + public PineapplePizza (Recipe recipe){ + super("파인애플 피자", recipe); + } +} +``` + +Pizza가 있다. 그리고 Pizza를 상속받은 CheesePizza PineapplePizza 가 있다. 피자는 name과 recipe를 속성으로 가지고, result() 메서드를 호출하면 recipe.getName()이 어떤 값을 반환하는 지에 따라 결과가 달라진다. + +--- + +```java +public interface Recipe { + String getName(); +} + +public class DeliciousRecipe implements Recipe{ + + @Override + public String getName() { + return "맛있는"; + } +} + +public class NormalRecipe implements Recipe{ + + @Override + public String getName() { + return "그냥"; + } +} +``` + +Recipe는 인터페이스이고 피자에 수식어를 달아주는 역할을 한다. Recipe을 인터페이스로 구현했기 때문에 Pizza는 Recipe을 자유자재로 바꿔낄 수 있다. Pizza의 종류가 변해도, 새로운 Recipe가 추가돼도, 기존의 코드를 바꿀 필요가 없다. + +--- + +```java +public class App { + + public static void main(String[] args) { + + DeliciousRecipe deliciousRecipe = new DeliciousRecipe(); + NormalRecipe normalRecipe = new NormalRecipe(); + + Pizza pizza1 = new Pizza("파인애플 피자", deliciousRecipe); + Pizza pizza2 = new Pizza("치즈 피자", normalRecipe); + + System.out.println("pizza1.result() = " + pizza1.result()); + System.out.println("pizza2.result() = " + pizza2.result()); + } +} +``` + +브릿지 패턴을 사용하면 이런식으로 다양한 종류의 피자를 만들 수 있다. 피자에서 레시피를 추상화하여 독립적인 클래스를 만들었기 때문이다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\354\226\264\353\214\221\355\204\260\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\354\226\264\353\214\221\355\204\260\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..2b2e5903 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\354\226\264\353\214\221\355\204\260\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,133 @@ +--- +title: '어댑터 패턴' +lastUpdated: '2024-03-02' +--- +어댑터 패턴은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴이다. 기존 코드를 변경하지 않고 원하는 인터페이스 구현체를 만들어 사용하는 것이기 때문에 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다는 장점이 있다. + + + +--- + +## 예시 코드 + +```java +public interface UserDetails { + String getUsername(); + String getPassword(); +} + +public interface UserDetailsService { + UserDetails loadUser(String username); +} + +public class LoginHandler { + + private final UserDetailsService userDetailsService; + + public LoginHandler(UserDetailsService userDetailsService){ + this.userDetailsService = userDetailsService; + } + + public String login(String username, String password){ + UserDetails userDetails = userDetailsService.loadUser(username); + + if(userDetails.getPassword().equals(password)){ + return userDetails.getUsername(); + } else { + throw new RuntimeException(); + } + } +} +``` + +UserDetails, UserDetailsService, LoginHandler는 security 패키지에서 제공하는 코드로 라이브러리 코드라고 생각해도 된다. 실습을 위해 간단히 구현한 코드이다. + +--- + +```java +@Data +public class Account { + + private String name; + private String password; + private String email; +} + + +public class AccountService { + + public Account findAccountByUsername(String username) { + Account account = new Account(); + account.setName(username); + account.setPassword(username); + account.setEmail(username); + + return account; + } + + public Account createNewAccount(String username){ + //대충 새 유저를 생성하는 로직 + return new Account(); + } +} + +``` + +애플리케이션을 구현하는 데 필요한 Account와 AccountService 클래스이다. 나는 이 두 클래스와 security 패키지의 기능을 엮어서 LoginHandler의 login 기능을 사용하고 싶다. + +--- + +```java +@AllArgsConstructor +public class AccountUserDetails implements UserDetails { + + private Account account; + + @Override + public String getUsername() { + return account.getName(); + } + + @Override + public String getPassword() { + return account.getPassword(); + } +} + +@AllArgsConstructor +public class AccountUserDetailsService implements UserDetailsService { + + private AccountService accountService; + + @Override + public UserDetails loadUser(String username) { + + Account account = accountService.findAccountByUsername(username); + + return new AccountUserDetails(account); + } +} +``` + +Account, AccountService와 연결하고 싶은 security 패키지의 두 인터페이스를 상속받아 어댑터의 역할을 하는 새 클래스를 정의했다. + +--- + +```java +public class LoginHandler { + + private final UserDetailsService userDetailsService; + + public LoginHandler(UserDetailsService userDetailsService){ + this.userDetailsService = userDetailsService; + } + ... +} +``` + +LoginHandler의 UserDetailsService위치에 AccountUserDetailsService를 주입하면 우리가 구현한 Account를 통해 login메서드를 사용할 수 있다. + +이 링크로 가면 코드를 볼 수 있다. + +출처:
+https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\354\273\264\355\217\254\354\247\223\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\354\273\264\355\217\254\354\247\223\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..a256f5a9 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\354\273\264\355\217\254\354\247\223\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,77 @@ +--- +title: '컴포짓 패턴' +lastUpdated: '2024-03-02' +--- +

컴포짓 패턴이란 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한다. 복합 객체와 단일 객체의 처리 방법이 다르지 않을 경우, 그 관계를 전체-부분 관계로 정의할 수 있는데, 이 전체-부분 관계를 효율적으로 처리하기 위한 디자인 패턴이 컴포짓 패턴이다.

+ + + +--- + +## 예시 코드 + +

대표적인 컴포짓 패턴의 예시는 파일 디렉토리 구조이다. 내가 있는 폴더가 다른 폴더의 자식 폴더인지, root 폴더인지에 상관없이 똑같이 다룰 수 있다.

+

컴포짓 패턴을 사용하면 재귀적인 트리 구조를 구현할 수 있다.

+ +```java +public interface Composite { + String getName(); + String getTree(String tab); +} +``` + +```java +public class File implements Composite{ + + private String name; + + public File(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getTree(String tab) { + return "\n" + + tab + "File : " + this.getName() + "\n" + + tab.substring(2); + } +} +``` + +```java +public class Folder implements Composite { + + private String name; + private List composites; + + public Folder(String name, Composite... composites) { + this.name = name; + this.composites = Arrays.stream(composites) + .collect(Collectors.toList()); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getTree(String tab) { + return "\n" + + tab + "Folder(" + name + ") { \n" + + tab + " " + composites.stream().map(o -> o.getTree(tab + " ")) + .collect(Collectors.toList()) + "\n" + + tab + "} \n" + + tab.substring(2); + } +} +``` + +트리 구조를 확인하기 위해 재귀적인 getTree 메서드를 구현했다. 해당 Composite이 전체든, 일부든, 상관없이 동일하게 실행된다. + +이 링크로 가면 코드를 볼 수 있다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\215\274\354\202\254\353\223\234\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\215\274\354\202\254\353\223\234\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..6a1f5733 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\215\274\354\202\254\353\223\234\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,76 @@ +--- +title: '퍼사드 패턴' +lastUpdated: '2024-03-02' +--- + +퍼사드 패턴은 클라이언트가 사용해야 하는 복잡한 서브 시스템 의존성을 간단한 인터페이스로 추상화 하는 디자인 패턴이다. Client는 서브시스템의 존재를 모르는 상태로, 오직 facade클래스에만 접근할 수 있도록 하는것이 특징이다. + + + +--- + +## 예시 코드 + +```java +public class PizzaFacade { + + private final Chef chef = new Chef(); + private final Oven oven = new Oven(); + + public Pizza makingPizza() { + + Pizza pizza = chef.makingDough(); + + pizza = chef.addToppings(pizza); + + return oven.grillingPizza(pizza); + + } +} +``` + +```java +@NoArgsConstructor +public class Pizza { +} + +@NoArgsConstructor +public class Oven { + + public Pizza grillingPizza(Pizza pizza) { + return pizza; + } +} + +@NoArgsConstructor +public class Chef { + + public Pizza makingDough() { + return new Pizza(); + } + + public Pizza addToppings(Pizza pizza) { + return pizza; + } +} +``` + +여러 클래스에 접근해서 로직을 실행하는 책임을 PizzaFacade 클래스에 몰아넣었다. + +```java +public class App { + + public static void main(String[] args) { + + PizzaFacade pizzaFacade = new PizzaFacade(); + + Pizza pizza = pizzaFacade.makingPizza(); + + System.out.println(pizza); + } +} +``` + +클라이언트에선 pizzaFacade.makingPizza()를 호출하면 간단하게 피자를 만들 수 있다. + +이 링크로 가면 코드를 볼 수 있다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\224\204\353\241\235\354\213\234\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\224\204\353\241\235\354\213\234\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..b2c19833 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\224\204\353\241\235\354\213\234\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,51 @@ +--- +title: '프록시 패턴' +lastUpdated: '2024-03-02' +--- + +

프록시 패턴은 인터페이스를 사용하고 실행시킬 클래스에 대해 객체가 들어갈 자리에 대리자 객체를 대신 투입하여, 클라이언트는 실제 실행시킬 클래스에 대한 메소드를 호출하여 반환값을 받는지 대리 객체의 메소드를 호출해서 반환값을 받는지 모르도록 하는것을 말한다.

+

프록시 패턴을 사용하면 실제 메소드가 호출되기 이전에 필요한 기능(전처리등의)을 구현객체 변경없이 추가할 수 있고, 구현클래스에 직접 접근하지않고 Proxy를 통해 한번 우회하여 접근하도록 되어있기 때문에 흐름제어를 할 수 있다는 장점이 있다.

+ + + +--- + +## 예시 코드 + +```java +public interface HelloService { + String run(); +} + +public class HelloServiceImpl implements HelloService{ + + @Override + public String run() { + return "안녕하세요"; + } +} + +public class HelloServiceProxy implements HelloService{ + + private HelloService helloService; + + @Override + public String run() { + helloService = new HelloServiceImpl(); + return helloService.run(); + } +} + +public class App { + + public static void main(String[] args) { + + HelloServiceProxy helloServiceProxy = new HelloServiceProxy(); + System.out.println(helloServiceProxy.run()); + } +} +``` + +

프록시 패턴은 어떻게 활용할 것이냐에 따라 자유롭게 구현할 수 있다. 위에서는 main에서 HelloService를 상속받은 HelloserviceImpl에 접근할때 동일한 인터페이스를 상속받은 프록시 클래스로 접근하도록 해서 HelloserviceImpl에 접근하기 전, 후에 별도의 흐름제어나 검증을 할 수 있도록 구성했다.

+ +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..23fb715b --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/2.\342\200\205\352\265\254\354\241\260\355\214\250\355\204\264/\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,131 @@ +--- +title: '플라이웨이트 패턴' +lastUpdated: '2024-03-02' +--- +플라이 웨이트 패턴은 동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하여 사용하도록 하여 메모리 사용량을 최소화하는 패턴이다. 플라이웨이트 패턴에서는 일부 오브젝트의 상태 정보를 외부 자료 구조에 저장하여 플라이웨이트 오브젝트가 잠깐 동안 사용할 수 있도록 전달한다. + + + +--- + +## 예시 코드 + +```java +@Getter +@Setter +public class Pizza { + + private String name; + private int price; + private Dough dough; + private Sauce sauce; +} +``` + +피자가 있다.
+이제부터 피자를 아주 많이 만들어서 배송할건데, 만들 때 마다 새 인스턴스로 생성해서 배송하기엔 메모리가 너무 아깝다. 그래서 자주 변하지 않고, 범위가 작은 속성인 도우와 소스를 키값으로 Map에 저장해서 저장되어있는 도우와 소스를 사용하는 피자를 만들때는 똑같은 메모리에 저장해서 재사용해보려고 한다. + +--- + +```java +@Getter +@AllArgsConstructor +@EqualsAndHashCode +public class Ingredient{ + private Dough dough; + private Sauce sauce; +} + +public enum Dough { + NORMAL, + THIN +} + +public enum Sauce { + TOMATO, + CHEESE +} +``` + +도우와 소스를 enum으로 만들고 두 클래스를 엮어서 Key값으로 저장하기 위해 Ingredient라는 새 클래스를 만들었다. + +--- + +```java +@NoArgsConstructor +public class PizzaIngredientFactory { + private static final HashMap flyweightData = new HashMap<>(); + + public static Pizza getIngredient(Dough dough, Sauce sauce) { + + Ingredient ingredient = new Ingredient(dough, sauce); + + Pizza pizza = flyweightData.get(ingredient); + + if (pizza == null) { + pizza = new Pizza(ingredient); + flyweightData.put(ingredient, pizza); + } + return pizza; + } +} + +public class Pizza { + public Pizza(Ingredient ingredient){ + this.dough = ingredient.getDough(); + this.sauce = ingredient.getSauce(); + } + ... +} +``` + +Dough와 Sauce 값을 받아서 그 두개의 재료만 준비되어있는 피자를 반환하는 PizzaIngredientFactory를 만들어줬다. 그리고 Pizza에 Ingredient로 새 인스턴스를 생성하는 생성자를 만들었다. Ingredient를 키로 Map에서 Pizza를 찾아서 반환하기 때문에 똑같은 Ingredient를 요청하면 똑같은 메모리 주소를 반환한다. + +```java +public class App { + + public static void main(String[] args) { + + Pizza pizza1 = PizzaIngredientFactory.getIngredient(Dough.NORMAL, Sauce.TOMATO); + pizza1.setName("일반 피자"); + pizza1.setPrice(10000); + System.out.println("pizza1 = " + pizza1); + + Pizza pizza2 = PizzaIngredientFactory.getIngredient(Dough.NORMAL, Sauce.TOMATO); + pizza2.setName("특별한 피자"); + pizza2.setPrice(15000); + System.out.println("pizza2 = " + pizza2); + } +} +``` + +이렇게 출력해보면 pizza1과 pizza2가 같은 값이 나옴을 알 수 있다.
+근데 여기서 이런 생각이 들 수 있다. 같은 메모리를 쓰는데 서로 다른 두개의 변수가 나올 수 있나? + +```java +System.out.println(pizza1.getName() + " " + pizza2.getName()); +``` + +당연히 나올 수 없다. 두개의 이름을 가진 두개의 변수이지만, 메모리를 공유하기 때문에 값은 동일하다. pizza1과 pizza2 변수를 모두 선언한 뒤에 이렇게 출력해보면 `특별한 피자`가 두 번 출력된다. 근데 사실 이렇게 단순한 상황에서 플라이 웨이트 패턴을 사용하는건 오히려 메모리를 더 낭비하는 게 될 수도 있다. + +```java +public class App { + + public static void main(String[] args) { + + Pizza pizza = PizzaIngredientFactory.getIngredient(Dough.NORMAL, Sauce.TOMATO); + pizza.setName("일반 피자"); + pizza.setPrice(10000); + System.out.println("pizza = " + pizza); + + pizza = PizzaIngredientFactory.getIngredient(Dough.NORMAL, Sauce.TOMATO); + pizza.setName("특별한 피자"); + pizza.setPrice(15000); + System.out.println("pizza = " + pizza); + } +} +``` + +위처럼 사용할 바에는 그냥 이렇게 쓰는게 낫다. 실제로 코드를 짤 때는 저 Pizza 변수를 여러 클래스에서 호출해서 써야하거나, Key값이 되는 enum값에 따라서 결정되는 다른 복잡한 정보가 있을때 플라이웨이트 패턴을 고려할 수 있을 것 같다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\353\251\224\353\251\230\355\206\240\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\353\251\224\353\251\230\355\206\240\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..ca3eb0df --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\353\251\224\353\251\230\355\206\240\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,97 @@ +--- +title: '메멘토 패턴' +lastUpdated: '2024-03-02' +--- + +메멘토 패턴은 객체를 이전 상태로 되돌릴 수 있는 기능을 제공하는 소프트웨어 디자인 패턴이다. 현재(원본) 상태를 저장하는 객체인 Originator에서 특정 상태를 저장하는 Memento 객체로 변환하여 CareTaker에 저장하는 구조이며, 핵심 객체의 데이터를 계속해서 캡슐화된 상태로 유지하는 것이 특징이다. + + + +--- + +## 예시 코드 + + +```java +@Getter +@Setter +public class Originator { + private String state; + + public Memento saveStateToMemento() { + return new Memento(state); + } + + public void getStateFromMemento(Memento memento) { + state = memento.getState(); + } +} +``` + +현재 상태를 담고있는 객체인 Originator이다. 새 Memento를 생성하거나 Memento로부터 정보를 받아와 상태를 변경하는 메서드를 가지고있다. + +--- + +```java +@Getter +@AllArgsConstructor +public class Memento { + private String state; +} +``` + +특정 시점의 상태를 저장하는 Memento 객체이다. + + +--- + + +```java +public class CareTaker { + private List mementoList = new ArrayList<>(); + + public void add(Memento state) { + mementoList.add(state); + } + + public Memento get(int index) { + return mementoList.get(index); + } +} +``` + +Memento를 List형태로 저장하고있는 CareTaker이다. 새로운 Memento를 저장하거나 꺼내오는 메서드를 가지고 있다. + +--- + +```java +public class App { + + public static void main(String[] args) { + + Originator originator = new Originator(); + CareTaker careTaker = new CareTaker(); + + originator.setState("state1"); + originator.setState("state2"); + careTaker.add(originator.saveStateToMemento()); + + originator.setState("state3"); + careTaker.add(originator.saveStateToMemento()); + + originator.setState("state4"); + careTaker.add(originator.saveStateToMemento()); + + System.out.println("현재 상태 : " + originator.getState()); + System.out.println("첫 번째 저장 : " + careTaker.get(0).getState()); + System.out.println("두 번째 저장 : " + careTaker.get(1).getState()); + + originator.getStateFromMemento(careTaker.get(0)); // 두번째 저장으로 Rollback + System.out.println("롤백 후 상태 : " + originator.getState()); + } +} +``` + +이와 같이 상태를 careTaker에 저장하여 가져오는 동작을 수행할 수 있다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\353\260\251\353\254\270\354\236\220\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\353\260\251\353\254\270\354\236\220\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..d48afcdc --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\353\260\251\353\254\270\354\236\220\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,55 @@ +--- +title: '방문자 패턴' +lastUpdated: '2024-03-02' +--- + +방문자 패턴은 알고리즘을 객체 구조에서 분리시키는 디자인 패턴이다. 기존 클래스 필드 정보를 유지하면서 새로운 연산을 추가하는 방식 중 하나이다. 비슷한 동작을 수행하는 로직을 Visitor 객체에 모을 수 있기 때문에 코드를 유지보수하기 좋지만, Element와 Visitor 간의 결합도가 상승한다는 단점이 있다. + + + +--- + +## 예시 코드 + +```java +public interface Element { + void visit(Visitor visitor); +} + +public class ElementA implements Element{ + + @Override + public void visit(Visitor visitor) { + visitor.visit(this); + } +} + +public class ElementB implements Element{ + + @Override + public void visit(Visitor visitor) { + visitor.visit(this); + } +} +``` + +Visitor라는 클래스에 visit할 수 있는 두개의 Element를 만들었다. + +--- + +```java +public class Visitor { + + public void visit(ElementA element) { + System.out.println("ElementA가 방문함"); + } + + public void visit(ElementB element) { + System.out.println("ElementB가 방문함"); + } +} +``` + +어떤 Element가 방문했는지에 따라 다른 동작을 수행한다. (오버로딩) + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\203\201\355\203\234\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\203\201\355\203\234\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..47863d96 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\203\201\355\203\234\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,137 @@ +--- +title: '상태 패턴' +lastUpdated: '2024-03-02' +--- + +상태 패턴은 상태 패턴 인터페이스의 파생 클래스로서 각각의 상태를 구현함으로써, 상태를 객체화시키고 상태전이 메서드를 구현할 필요 없이 상태객체가 슈퍼클래스에 의해 정의한 메소드를 호출하여 사용하도록 하는 패턴이다. 상태 전이를 위한 조건 로직이 복잡한 경우 이를 해소하기 위해 사용될 수 있다. + + + +--- + +## 예시 코드 + +```java +public interface State { + State feelingBetter(); + State feelingBad(); + void printCurrentEmotion(); +} +``` + +```java +public class Happy implements State{ + + @Override + public State feelingBetter() { + return this; + } + + @Override + public State feelingBad() { + return new SoSo(); + } + + @Override + public void printCurrentEmotion() { + System.out.println("기분이 좋습니다"); + } +} + +public class SoSo implements State{ + + @Override + public State feelingBetter() { + return new Happy(); + } + + @Override + public State feelingBad() { + return new Bad(); + } + + @Override + public void printCurrentEmotion() { + System.out.println("기분이 그저그렇습니다"); + } +} + +public class Bad implements State{ + + @Override + public State feelingBetter() { + return new SoSo(); + } + + @Override + public State feelingBad() { + return this; + } + + @Override + public void printCurrentEmotion() { + System.out.println("기분이 좋지 않습니다"); + } +} +``` + +State 인터페이스를 상속받은 Happy, SoSo, Bad 세개의 상태 객체가 있다. + +--- + + +```java +@AllArgsConstructor +public class Human { + private State state; + + public void goodSituation() { + System.out.println("좋은 일이 생겼습니다"); + state = state.feelingBetter(); + state.printCurrentEmotion(); + System.out.println(); + } + + public void badSituation() { + System.out.println("나쁜 일이 생겼습니다"); + state = state.feelingBad(); + state.printCurrentEmotion(); + System.out.println(); + } +} +``` + +그리고 상태 객체를 가지고있는 Human이 있다. Human에게는 좋은일이 생길 수도 있고, 나쁜 일이 생길 수도 있는데 각 상황에서는 기분이 좋아지거나 나빠진다. + + +--- + + +```java +public class App { + + public static void main(String[] args) { + + Human human = new Human(new SoSo()); + human.goodSituation(); + human.badSituation(); + human.badSituation(); + } +} +``` + +```java +//실행 결과 +좋은 일이 생겼습니다 +기분이 좋습니다 + +나쁜 일이 생겼습니다 +기분이 그저그렇습니다 + +나쁜 일이 생겼습니다 +기분이 좋지 않습니다 +``` + +Human 인스턴스 생성 후 좋은일과 나쁜 일을 만들어주면 위와 같이 출력되는 것을 볼 수 있다. 어떤 상태인지 `if`또는 `switch`문으로 확인 후 로직을 실행해줄 필요 없이, 상태 객체가 어떤 클래스냐에 따라 자동으로 그 상태에 맞는 로직이 실행되는 것을 볼 수 있다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\230\265\354\240\200\353\262\204\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\230\265\354\240\200\353\262\204\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..1db6e7d0 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\230\265\354\240\200\353\262\204\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,112 @@ +--- +title: '옵저버 패턴' +lastUpdated: '2024-03-02' +--- + +옵서버 패턴은 객체의 상태 변화를 관찰하는 관찰자들을 생성하여, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 패턴이다. 옵저버 패턴을 활용하면 다른 객체의 상태 변화를 별도의 함수 호출 없이 즉각적으로 알 수 있기 때문에, 이벤트를 자주 처리해야하는 프로그램에서 적합하다. + + + +--- + +## 예시 코드 + +```java +public interface Publisher { + void add(Observer observer); + void delete(Observer observer); + void notifyObserver(V message); +} + +public class NewsPublisher implements Publisher{ + + private final ArrayList> observers = new ArrayList<>(); + + @Override + public void add(Observer observer) { + observers.add( observer); + } + + @Override + public void delete(Observer observer) { + observers.remove(observer); + } + + @Override + public void notifyObserver(String message) { + for(Observer observer : observers) { + observer.update(message); + } + } + +} +``` + +뉴스를 생성하는 NewsPublisher 클래스를 만들었다. 위의 사진에서의 Subject에 해당한다. + +Publisher는 구독중인 감시자(observer)들의 리스트를 저장해놓고, 메세지가 변경된 경우 notifyObserver()로 메세지를 observer들에게 알린다. + +Publisher 인터페이스가 String이 아닌 다른 클래스의 데이터를 전송하는 경우에도 사용될 수 있도록 하기 위해 제네릭을 사용했다. + +--- + +```java +public interface Observer { + void update(V content); +} + +public class NewsObserver implements Observer { + + private Publisher publisher; + + public NewsObserver(Publisher publisher) { + this.publisher = publisher; + publisher.add(this); + } + + @Override + public void update(String message) { + display(message); + } + + public void withdraw() { + publisher.delete(this); + } + + public void display(String message) { + System.out.println("뉴스"); + System.out.println("->" + message); + } +} +``` + +Observer를 상속받은 NewsObserver 클래스를 만들었다. publisher에서 이벤트가 생기면 update()가 호출되고, NewsObserver는 이벤트 메세지를 받아 콘솔에 출력한다. + +--- + +```java +public class App { + + public static void main(String[] args) { + + NewsPublisher newsPublisher = new NewsPublisher(); + NewsObserver newsObserver = new NewsObserver(newsPublisher); + + newsPublisher.notifyObserver("[속보] 큰일남"); + + newsObserver.withdraw(); + + newsPublisher.notifyObserver("[속보] 완전 큰일남"); + } +} +``` + +```java +//실행 결과 +뉴스 +->[속보] 큰일남 +``` + +위와 같은 코드를 실행하면 newsPublisher의 이벤트를 newsObserver가 받아서 잘 출력되는 것을 볼 수 있다. withdraw()를 호출하면 observers에서 newsObserver가 삭제되기 때문에 더이상 이벤트에 반응하지 않는다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\235\264\355\204\260\353\240\210\354\235\264\355\204\260\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\235\264\355\204\260\353\240\210\354\235\264\355\204\260\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..41c8044c --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\235\264\355\204\260\353\240\210\354\235\264\355\204\260\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,80 @@ +--- +title: '이터레이터 패턴' +lastUpdated: '2024-03-02' +--- + +이터레이터 패턴은 컬렉션 구현 방법을 노출시키지 않으면서 집합체 안에 들어있는 모든 항목에 접근할 수 있도록 하는 패턴이다. java의 stream이 대표적인 이터레이터 패턴의 예시이다. + +--- + +## 예시 코드 + +```java +@RequiredArgsConstructor +public class ArrIterator implements Iterator { + + private final Integer[] list; + private int position = 0; + + @Override + public boolean hasNext() { + return position < list.length; + } + + @Override + public Integer next() { + return list[position++]; + } +} + +@RequiredArgsConstructor +public class ListIterator implements Iterator { + + private final List list; + private int position = 0; + + @Override + public boolean hasNext() { + return position < list.size(); + } + + @Override + public Integer next() { + return list.get(position++); + } +} +``` + +java.util의 Iterator를 상속받았고 각각 일반 배열, List를 가지고 있는 두개의 클래스를 만들었다. + + +--- + + +```java +public class App { + + public static void main(String[] args) { + + Integer[] arr = {1, 2, 3}; + ArrIterator iterator1 = new ArrIterator(arr); + + System.out.println("ArrIterator"); + while(iterator1.hasNext()){ + System.out.println(iterator1.next()); + } + + List list = Arrays.asList(1, 2, 3); + ListIterator iterator2 = new ListIterator(list); + + System.out.println("ListIterator"); + while(iterator2.hasNext()){ + System.out.println(iterator2.next()); + } + } +} +``` + +ArrIterator와 ListIterator는 다른 타입의 Integer 목록을 가지고 있지만 인터페이스를 상속받아 구현한 hasNext()와 next()를 통해 동일한 방식으로 요소를 탐색할 수 있다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\235\270\355\204\260\355\224\204\353\246\254\355\204\260\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\235\270\355\204\260\355\224\204\353\246\254\355\204\260\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..cb7ab791 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\235\270\355\204\260\355\224\204\353\246\254\355\204\260\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,81 @@ +--- +title: '인터프리터 패턴' +lastUpdated: '2024-03-02' +--- + +

인터프리터 패턴은 자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴이다. 인터프리터 패턴을 사용하면 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있다. 쉽게 말하면, 일정한 형식을 갖춘 텍스트(String)를 해석해서 규칙에 맞는 로직을 실행할 수 있도록 하는 것이다.

+

어떤 용도로, 어떤 언어를 구현하는지에 따라 정말 다양한 코드가 나올 수 있지만, 보통 명령을 입력받아 해석하는 Parser와 그것을 바탕으로 로직을 실행하는 Expression으로 나뉜다.

+ + + +--- + +## 예시 코드 + +항이 2개인 간단한 덧셈, 뺄셈 식 계산을 인터프리터 패턴으로 구현했다. + +```java +public interface Expression { + Integer interpret(String context); +} +``` + +```java +@NoArgsConstructor +public class PlusExpression implements Expression{ + + @Override + public Integer interpret(String context) { + if (context.contains("+")) { + try { + String[] split = context.split("\\+", 2); + return Integer.parseInt(split[0]) + Integer.parseInt(split[1]); + } catch (Exception e) { + return null; + } + } else { + return null; + } + } +} + +@NoArgsConstructor +public class MinusExpression implements Expression { + + @Override + public Integer interpret(String context) { + if (context.contains("-")) { + try { + String[] split = context.split("-", 2); + return Integer.parseInt(split[0]) - Integer.parseInt(split[1]); + } catch (Exception e) { + return null; + } + } else { + return null; + } + } +} +``` + +```java +public class App { + + public static void main(String[] args) { + + PlusExpression plusExpression = new PlusExpression(); + MinusExpression minusExpression = new MinusExpression(); + + System.out.println("1 + 2 = " + plusExpression.interpret("1+2")); + System.out.println("3 - 2 = " + minusExpression.interpret("3-2")); + } +} +``` + +```java +//실행 결과 +1 + 2 = 3 +3 - 2 = 1 +``` + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\240\204\353\236\265\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\240\204\353\236\265\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..1ddf6b75 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\240\204\353\236\265\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,79 @@ +--- +title: '전략 패턴' +lastUpdated: '2024-03-02' +--- + +전략 패턴은 프로그램이 진행되면서 캡슐화된 로직을 선택할 수 있게 하는 디자인 패턴이다. 로직 실행은 인터페이스에 의존을 시키고 인터페이스를 구현한 로직들을 전달해줌으로써 분기처리 없이 유연성을 갖출 수 있다. 객체지향 원칙중 개방-폐쇄 원칙(Open-Closed Principle)을 지키기 위한 디자인 패턴이다. + + + +--- + +## 예시 코드 + +```java +@Setter +@AllArgsConstructor +public class Coupon { + + private DiscountStrategy discountStrategy; + + public int discount(int price) { + return discountStrategy.execute(price); + } +} + +public interface DiscountStrategy { + int execute(int price); +} +``` + +고정할인, 또는 비율할인을 할 수 있는 쿠폰 객체를 만들었다. 쿠폰이 DiscountStrategy 인터페이스를 구현한 클래스를 주입받아서 그 클래스에 구현되어있는 전략을 사용할 수 있도록 discount 메서드를 만들어줬다. + +--- + +```java +public class FixedDiscountStrategy implements DiscountStrategy { + + @Override + public int execute(int price) { + return price - 1000; + } +} + +public class RateDiscountStrategy implements DiscountStrategy { + + @Override + public int execute(int price) { + return price - (int)(price * 0.1); + } +} +``` + +고정할인과 비율할인 전략을 구현한 두개의 클래스이다. 할인할 금액, 비율값은 상수로 설정해두었다. + +--- + +```java +public class App { + + public static void main(String[] args) { + + Coupon coupon = new Coupon(new FixedDiscountStrategy()); + System.out.println("Coupon.discount(5000) = " + coupon.discount(5000)); + + coupon.setDiscountStrategy(new RateDiscountStrategy()); + System.out.println("Coupon.discount(5000) = " + coupon.discount(5000)); + } +} +``` + +```java +//실행 결과 +Coupon.discount(5000) = 4000 +Coupon.discount(5000) = 4500 +``` + +쿠폰 객체를 만들어 Strategy를 주입해주면 전략을 바꿈에 따라 다른 로직이 적용되는 것을 볼 수 있다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\244\221\354\236\254\354\236\220\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\244\221\354\236\254\354\236\220\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..5ef9ca32 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\244\221\354\236\254\354\236\220\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,110 @@ +--- +title: '중재자 패턴' +lastUpdated: '2024-03-02' +--- + +중재자 패턴은 객체간 커뮤니케이션을 하는 경우 중재자를 생성하여 객체끼리의 결합을 약화하는 디자인 패턴이다. 중재자 패턴을 사용하면 N:M개의 객체, 또는 N:1개의 객체간의 데이터 전달도 구현할 수 있다. + +객체의 통신을 다룬다는 점에서 옵저버 패턴과 비슷하지만, 옵저버패턴은 한 객체에서 일방적으로 데이터를 송신하는데 반해 중재자패턴은 양방향으로 데이터를 전달할 수 있다는 것이 차이점이다. + + + +--- + +## 예시 코드 + +```java +@Getter +@Setter +public abstract class Colleague { + + protected String name; + private Mediator mediator; + private String message; + + public Colleague(String name) { + this.name = name; + } + + public void send() { + System.out.println(this.name + " send()"); + mediator.mediate(this); + } + + public abstract void receive(Colleague colleague); + +} + +public class ConcreteColleague extends Colleague{ + + public ConcreteColleague(String name) { + super(name); + } + + @Override + public void receive(Colleague colleague) { + System.out.println(this.name + " received " + colleague.getName() + "'s Message : " + colleague.getMessage()); + } + +} +``` + +다른 객체와 통신을 하는 Colleague를 만들었다. 하지만, Colleague가 직접 동신하지는 않고 Mediator를 거쳐야 통신할 수 있다. + +```java +public interface Mediator { + void addColleague(Colleague colleague); + void mediate(Colleague colleague); +} + +public class ConcreteMediator implements Mediator { + private final List colleagues = new ArrayList<>(); + + @Override + public void addColleague(Colleague colleague) { + this.colleagues.add(colleague); + colleague.setMediator(this); + } + + @Override + public void mediate(Colleague colleague) { + for (Colleague receiverColleague : colleagues) { + System.out.println(" Mediating " + colleague.getName() + " to " + receiverColleague.getName()); + receiverColleague.receive(colleague); + } + } +} +``` + +Mediator 클래스는 여러 컴포넌트를 통제해준다. Colleague를 list로 가지고있고 mediate()를 호출하면 그 Colleague들에게 메세지를 뿌려준다. 마치 SocketIo에서 `socketIOServer.getRoomOperations().sendEvent();`로 소켓 메세지를 전송하는 것과 같다...! + +```java +public class App { + public static void main(String args[]) { + Mediator mediator = new ConcreteMediator(); + Colleague colleague1 = new ConcreteColleague("User1"); + Colleague colleague2 = new ConcreteColleague("User2"); + + mediator.addColleague(colleague1); + mediator.addColleague(colleague2); + + colleague1.setMessage("Hello, world!"); + colleague1.send(); + } +} +``` + +```java +//실행 결과 +User1 send() + Mediating User1 to User1 +User1 received Message : Hello, world! + Mediating User1 to User2 +User2 received Message : Hello, world! +``` + +colleague1에서 mediator로 보낸 메세지가 mediator를 구독하고 있는 객체들에게 무사히 전달되는 것을 볼 수 있다. + +이렇게 여러 객체간의 통신을 중재해주는 객체를 추가해줌으로써, 통신 객체 간 의존성을 줄여 결합도를 감소시키는 것이 중재자 패턴이었다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\261\205\354\236\204\354\227\260\354\207\204\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\261\205\354\236\204\354\227\260\354\207\204\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..eb6d2011 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\261\205\354\236\204\354\227\260\354\207\204\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,111 @@ +--- +title: '책임연쇄 패턴' +lastUpdated: '2024-03-02' +--- + +책임 연쇄 패턴은 객체를 연결리스트와 같은 사슬 방식으로 연결한 후에 요청을 수행하지 못하는 객체인 경우 책임을 다음 객체에 넘기며 책임을 넘기는 형태의 패턴을 말한다. 각각의 체인은 자신이 해야하는 일만 담당하기 때문에 집합 내의 처리 순서를 변경하거나 처리객체를 유연하게 추가 또는 삭제할 수 있다. + + + +--- + +## 예시 코드 + +```java +@AllArgsConstructor +public class RequestHandler { + private RequestHandler nextHandler; + + public void handle(Request request) { + + if (nextHandler != null){ + nextHandler.handle(request); + } + } +} +``` + +요청을 처리할 RequestHandler의 기본 클래스이다. 다른 RequeestHandler를 주입받아 생성되고, handle 메서드를 호출하면 (nextHandler가 null이 아닌경우) 주입받은 핸들러의 handle 메서드를 부른다. 이 클래스 자체가 어떤 의미를 담고있는건 아니고, 얘는 이 클래스를 상속받아서 handle 메서드에 로직을 담아 실행한 후에 다음 체인으로 넘어가게 하기 위한 응용 Handler들의 기본이 되는 클래스가 된다. + +--- + +```java +@NoArgsConstructor +public class Request { } +``` + +```java +public class AuthRequestHandler extends RequestHandler { + + public AuthRequestHandler(RequestHandler nextHandler) { + super(nextHandler); + } + + @Override + public void handle(Request request) { + //대충 유저 인증하는 코드 + System.out.println("AuthRequestHandler.handle"); + super.handle(request); + } +} + +public class LoggingRequestHandler extends RequestHandler { + + public LoggingRequestHandler(RequestHandler nextHandler) { + super(nextHandler); + } + + @Override + public void handle(Request request) { + //대충 요청을 로깅하는 코드 + System.out.println("LoggingRequestHandler.handle"); + super.handle(request); + } +} + +public class DoWorkRequestHandler extends RequestHandler{ + + public DoWorkRequestHandler(RequestHandler nextHandler) { + super(nextHandler); + } + + @Override + public void handle(Request request) { + //대충 request를 처리하는 코드 + System.out.println("DoWorkRequestHandler.handle"); + super.handle(request); + } +} +``` + +실제 체인을 실행하는 파트를 맡아줄 클래스들이다. RequestHandler를 상속받아 handle에 뭔가를 수행하는 부분을 추가했다. 실제로 돌아가는 코드를 적진 않았고, 주석으로 대체했다. 각 클래스들은 자신의 책임 내에서 처리할 수 있는 부분을 처리한 다음에 다음 Handler에 Request를 다시 넘긴다. Handler끼리는 서로의 코드에 전혀 간섭하지 않고, 앞에서 무슨 일이 있더라도 자신의 책임을 다하는 것에만 관심을 가지고있다. + +--- + +```java +public class App { + + public static void main(String[] args) { + RequestHandler chain = new AuthRequestHandler(new LoggingRequestHandler(new AuthRequestHandler(null))); + Client client = new Client(chain); + client.doWork(); + } +} + +@RequiredArgsConstructor +public class Client { + + private final RequestHandler requestHandler; + + public void doWork() { + Request request = new Request(); + requestHandler.handle(request); + } +} +``` + +chain을 생성하는 부분을 제외하면 나머지에선 requestHandler의 내용에 간섭하지 않는다. chain의 구조가 바뀌거나, handler가 추가된다고 해도 코드를 크게 변경할 필요가 없다. + +책임 연쇄 패턴을 사용하면 객체지향 원칙 중 단일 책임 원칙(Single Responsibility Principle)을 잘 지킬 수 있다. + +이 링크로 가면 코드를 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\273\244\353\247\250\353\223\234\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\273\244\353\247\250\353\223\234\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..9ec7592c --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\354\273\244\353\247\250\353\223\234\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,168 @@ +--- +title: '커맨드 패턴' +lastUpdated: '2024-03-02' +--- + +커맨드 패턴은 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 매서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수 있게 하는 패턴이다. 어떤 로직에 대한 요청을 객체화 시킴으로써, 코드를 수정하거나 교체하기 쉽게 하고, 유지보수성을 높인다. + + + +--- + +## 예시 코드 + +```java +@Setter +@AllArgsConstructor +public class Lamp { + private boolean power; + + public boolean isOn(){ + return power; + } +} + +@Setter +@AllArgsConstructor +public class AirConditioner { + private boolean power; + + public boolean isOn(){ + return power; + } +} +``` + +램프와 에어컨이 있다. + +--- + +```java +@Setter +@NoArgsConstructor +public class RemoteControl { + + private Command command; + + void pressButton() { + if (command != null){ + command.execute(); + } + } +} +``` + +그리고 리모컨이 있다.
+우리는 램프와 에어컨, 두개의 장치를 하나의 리모컨으로 켜고 끄는 기능을 구현해볼 것이다. + +--- + +```java +public interface Command { + void execute(); +} +``` + +실행할 Command들의 기반이 될 인터페이스를 만든다. + +```java +@RequiredArgsConstructor +public class LampOnCommand implements Command { + + private final Lamp lamp; + + @Override + public void execute() { + lamp.setPower(true); + } +} + +@RequiredArgsConstructor +public class LampOffCommand implements Command { + + private final Lamp lamp; + + @Override + public void execute() { + lamp.setPower(false); + } +} +``` +```java +@RequiredArgsConstructor +public class AirConditionerOnCommand implements Command { + + private final AirConditioner airConditioner; + + @Override + public void execute() { + airConditioner.setPower(true); + } +} +@RequiredArgsConstructor +public class AirConditionerOffCommand implements Command { + + private final AirConditioner airConditioner; + + @Override + public void execute() { + airConditioner.setPower(false); + } +} +``` + +각 장치를 주입받아 상태를 변경시키는 로직을 수행하는 객체를 만들었다. 마치 일반적인 함수를 사용하는 것 처럼 객체를 생성할때 수정할, 또는 사용할 대상을 주입받아서 로직을 실행하는 것이 특징이다. + +--- + +```java +public class App { + + public static void main(String[] args) { + + AirConditioner airConditioner = new AirConditioner(false); + Lamp lamp = new Lamp(false); + + RemoteControl remoteControl = new RemoteControl(); + + System.out.println("lamp"); + System.out.println(lamp.isOn()); + + remoteControl.setCommand(new LampOnCommand(lamp)); + remoteControl.pressButton(); + System.out.println(lamp.isOn()); + + remoteControl.setCommand(new LampOffCommand(lamp)); + remoteControl.pressButton(); + System.out.println(lamp.isOn()); + + System.out.println("airConditioner"); + System.out.println(airConditioner.isOn()); + + remoteControl.setCommand(new AirConditionerOnCommand(airConditioner)); + remoteControl.pressButton(); + System.out.println(airConditioner.isOn()); + + remoteControl.setCommand(new AirConditionerOffCommand(airConditioner)); + remoteControl.pressButton(); + System.out.println(airConditioner.isOn()); + + } +} +``` + +```java +//실행 결과 +lamp +false +true +false +airConditioner +false +true +false +``` + +위와 같이 커맨드 로직을 객체로 변환하여 remoteControl에 주입한 뒤 사용하면 된다. 요청을 객체의 형태로 캡슐화하였기 때문에 코드 실행중에 커맨드를 다른 객체로 변경하는 것도 가능하다. + +이 링크로 가면 코드를 볼 수 있다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\355\205\234\355\224\214\353\246\277\353\251\224\354\206\214\353\223\234\342\200\205\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\355\205\234\355\224\214\353\246\277\353\251\224\354\206\214\353\223\234\342\200\205\355\214\250\355\204\264.md" new file mode 100644 index 00000000..02ef2382 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/3.\342\200\205\355\226\211\354\234\204\355\214\250\355\204\264/\355\205\234\355\224\214\353\246\277\353\251\224\354\206\214\353\223\234\342\200\205\355\214\250\355\204\264.md" @@ -0,0 +1,62 @@ +--- +title: '템플릿메소드 패턴' +lastUpdated: '2024-03-02' +--- + +템플릿 메소드 패턴은 동작 상의 알고리즘의 프로그램 뼈대를 정의하는 행위 디자인 패턴이다. 템플릿 메소드 패턴은 여러 작업들이 동일한 구조를 갖지만, 일부 동작은 각각 다르게 구현해야할 때 사용된다. + + 템플릿 메소드 패턴은 전체 실행과정을 구현한 상위 클래스(추상 클래스)와 실행 과정의 일부 단계를 구현한 하위 클래스(구체클래스)로 나뉘며, 추상 메서드를 재정의함으로써 알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다 + + + + --- + +## 예시 코드 + +```java +public abstract class Human { + + abstract void Introducing(); + + void eating() { + System.out.println("밥먹기"); + } + + void sleeping() { + System.out.println("잠자기"); + } + + void coding() { + System.out.println("코딩하기"); + } + +} +``` + +사람(Human) 클래스가 있다. 사람은 밥먹기, 잠자기, 코딩하기 메서드를 공통으로 가지고있고 사람마다 다른 방법으로 자기소개를 할 수 있다. + +--- + +```java +@NoArgsConstructor +public class Kimeunbin extends Human { + + @Override + void Introducing() { + System.out.println("저는 김은빈입니다 ^^"); + } +} + +@NoArgsConstructor +public class Kimgeumbin extends Human { + + @Override + void Introducing() { + System.out.println("저는 김금빈입니다 ㅎㅎ"); + } +} +``` + +김은빈과 김금빈은 Human이라는 추상 클래스를 상속받아 3개의 공통 메소드를 가지고있고, 자기소개 메소드를 상세하게 구현하고있다. 이러한 템플릿 메소드 패턴은 중복코드를 줄여준다는 장점이 있다. + +이 링크로 가면 코드를 볼 수 있다. diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264.md" new file mode 100644 index 00000000..bb81705e --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264.md" @@ -0,0 +1,66 @@ +--- +title: '디자인패턴' +lastUpdated: '2024-03-02' +--- + 디자인 패턴이란 프로그래밍을 할 때에 공통적으로 생기는 문제를 해결하고자 설계한 일정한 코드의 패턴이다. 애플리케이션이나 시스템을 디자인하는 과정에서 자주 발생하는 문제를 해결하는데에 쓰이는 형식화 된 관행이자, 재사용 가능한 해결책이기도 하다. + +## GoF 디자인 패턴 종류 + 디자인 패턴에 대해 다루는 유명한 책 중 하나인 'GoF의 디자인 패턴'에서 다룬 디자인 패턴의 종류는 다음과 같다. + +### 1. 생성 패턴 (Credential Patterns) + 객체 생성과 관련된 패턴이다. 객체의 생성과 조합을 캠슐화하여 특정 객체가 생성되거나 변경되어도 프로그램 구조에 크게 영향을 받지 않도록 유연하게 설계하는 것이 목적이다. + +- ### 싱글톤(Singleton) 패턴 + - 클래스의 인스턴스를 오직 한개만 생성하여 제공하는 패턴 +- ### 팩토리 메소드(Factory Method) 패턴 + - 객체 생성 처리를 서브 클래스로 분리해 처리하도록 캡슐화하는 패턴 +- ### 추상 팩토리(Abstract Factory) 패턴 + - 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 생성할 수 있도록 하는 패턴 +- ### 빌더(Builder) 패턴 + - 객체를 만드는 프로세스의 직관성, 유연성을 높이고 독립적으로 분리하는 패턴 +- ### 프로토타입(Prototype) 패턴 + - 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 패턴 + +### 2. 구조 패턴 (Structural Patterns) + 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴이다. 객체들을 서로 묶어 단일 인터페이스나 새로운 기능을 제공한다. + +- ### 어댑터(Adapter) 패턴 + - 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴 +- ### 브릿지(Bridge) 패턴 + - 추상적인 것과 구체적인 것을 분리하여 연결하는 패턴 +- ### 컴포짓(Composite) 패턴 + - 그룹 전체와 개별 객체를 동일한 컴포넌트로 취급하여 처리하는 패턴 +- ### 데코레이터(Decorator) 패턴 + - 기존 코드를 변경하지 않고 부가 기능을 추가하는 패턴 +- ### 퍼사드(Façade) 패턴 + - 복잡한 서브 시스템 의존성을 간단한 인터페이스로 추상화하는 패턴 +- ### 플라이 웨이트(Flyweight) 패턴 + - 외적인 속성과 내적인 속성을 분리하고 재사용하여 메모리 사용을 줄이는 패턴 +- ### 프록시(Proxy) 패턴 + - 해당 객체를 대항하는 객체를 통해 대상 객체에 접근하는 방식을 제공하는 패턴 + +### 3. 행위 패턴 (Behavioral Pattern) + 객체나 클래스 사이의 알고리즘이나 책임을 분배하는 것에 관련된 패턴이다. 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 분배하고, 객체 사이의 결합도를 최소화하는 것에 중점을 둔다. + +- ### 책임 연쇄(Chain of Responsibility) 패턴 + - 요청을 보내는 쪽(sender)과 요청을 처리하는 쪽(receiver)을 분리하는 패턴 +- ### 커맨드(Command) 패턴 + - 요청을 캡슐화 하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴 +- ### 인터프리터(Interpreter) 패턴 + - 자주 등장하는 문제를 간단한 언어로 정의하고 재사용하는 패턴 +- ### 이터레이터(Iterator) 패턴 + - 집합 객체 내부 구조를 노출시키지 않고 순회 하는 방법을 제공하는 패턴 +- ### 중재자(Mediator) 패턴 + - 여러 객체들이 소통하는 방법을 캡슐화하는 패턴 +- ### 메멘토(Memento) 패턴 + - 캡슐화를 유지하면서 객체 내부 상태를 외부에 저장하는 패턴 +- ### 옵저버(Observer) 패턴 + - 다수의 객체가 특정 객체 상태 변화를 감지하고 알림을 받는 패턴 +- ### 상태(State) 패턴 + - 객체의 상태에 따라 객체의 행위 내용을 변경해주는 패턴 +- ### 전략(Strategy) 패턴 + - 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 해주는 패턴 +- ### 템플릿 메소드(Template Method) 패턴 + - 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴 +- ### 방문자(Visitor) 패턴 + - 기존 코드를 변경하지 않고 새로운 기능을 추가하는 패턴 diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/\354\234\204\354\236\204\342\200\205\355\214\250\355\204\264(Delegate\342\200\205Pattern).md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/\354\234\204\354\236\204\342\200\205\355\214\250\355\204\264(Delegate\342\200\205Pattern).md" new file mode 100644 index 00000000..c87c6ae1 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\353\224\224\354\236\220\354\235\270\355\214\250\355\204\264/\354\234\204\354\236\204\342\200\205\355\214\250\355\204\264(Delegate\342\200\205Pattern).md" @@ -0,0 +1,120 @@ +--- +title: '위임 패턴(Delegate Pattern)' +lastUpdated: '2024-03-02' +--- + +소프트웨어 엔지니어링에서 delegate pattern(위임 패턴)은 객체 합성이 상속과 동일하게 코드 재사용을 할 수 있도록 하는 객체 지향 디자인 패턴이다. + +## 상속(inheritance) vs 합성(composition) +객체지향 시스템에서 기능의 재사용을 위해 구사하는 가장 대표적인 기법은 클래스 상속, 그리고 객체 합성(object composition)이다. + +## 클래스 상속 + +- 서브클래싱, 즉 다른 부모 클래스에서 상속받아 한 클래스의 구현을 정의하는 것. +- 서브클래싱에 의한 재사용을 화이트박스 재사용(white-box reuse)이라고 한다. +- '화이트박스’는 내부를 볼 수 있다는 의미에서 나온 말로, 상속을 받으면 부모 클래스의 내부가 서브클래스에 공개되기 때문에 화이트박스인 셈이다. + +### 상속의 장점 + +- 컴파일 시점에 정적으로 정의되고 프로그래밍 언어가 직접 지원하므로 그대로 사용하면 된다. +- 서브클래스는 부모클래스의 일부만 재정의할 수도 있다. + +### 상속의 단점 + +- 런타임에 상속받은 부모 클래스의 구현을 변경할 수 없다. +- 상속은 컴파일 시점에 결정되는 사항이기 때문. +- 부모 클래스는 서브클래스의 물리적 표현의 최소 부분만을 정의하기 때문에 서브클래스는 부모 클래스가 정의한 물리적 표현들을 전부 또는 일부 상속받는다. +- 상속은 부모 클래스의 구현이 서브클래스에 다 드러나는 것이기 때문에 상속은 캡슐화를 파괴한다고 보는 시각도 있다. +- 서브클래스는 부모 클래스의 구현에 종속될 수밖에 없으므로, 부모 클래스 구현에 변경이 생기면 서브클래스도 변경해야 한다. + +이 구현의 종속성이 걸림돌로 작용하면서, 서브클래스를 재사용하려고 할 때 문제가 발생한다. 상속한 구현이 새로운 문제에 맞지 않을 때, 부모 클래스를 재작성해야 하거나 다른 것으로 대체하는 일이 생기게 된다. 이런 종속성은 유연성과 재사용성을 떨어뜨린다. + +이를 해결하는 방법 한 가지는 추상 클래스에서만 상속 받는 것이다. 추상 클래스에는 구현이 거의 없거나 아예 없기 때문이다. 이미 추상 클래스를 상속했다는 것은 구현이 아닌 인터페이스를 상속한 것이므로 구현 자체는 서브클래스가 정의한다. 구현이 변경되면 서브클래스만 변경하면 되고 상위 추상 클래스는 고려할 필요가 없다. + +## 객체 합성 +- 클래스 상속에 대한 대안으로 다른 객체를 여러 개 붙여서 새로운 기능 혹은 객체를 구성하는 것이다. +- 즉, 객체 또는 데이터 유형을 더 복잡한 유형으로 결합하는 방법 +- 객체를 합성하려면, 합성에 들어가는 객체들의 인터페이스를 명확하게 정의해 두어야 한다. +- 이런 스타일의 재사용을 블랙박스 재사용(black-box reuse)이라고 한다. +- 객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용되기 때문. + +객체 합성은 한 객체가 다른 객체에 대한 `참조자`를 얻는 방식으로 런타임에 동적으로 정의된다. 합성은 객체가 다른 객체의 인터페이스만을 바라보게 하기 때문에, 인터페이스 정의에 더 많은 주의를 기울여야 한다. 객체는 **인터페이스에서만 접근하므로 캡슐화**를 유지할 수 있다. 동일한 타입을 갖는다면 다른 객체로 런타임에 대체가 가능하다. 객체는 인터페이스에 맞춰 구현되므로 구현 사이의 종속성이 확실히 줄어든다. + +객체 합성은 시스템 설계에 또 다른 영향을 끼친다. 클래스 상속보다 객체 합성을 더 선호하는 이유는 각 클래스의 **캡슐화를 유지**할 수 있고, **각 클래스의 한 가지 작업에 집중**할 수 있기 때문이다. 객체 합성으로 설계되면 클래스의 수는 적어지고 객체의 수는 좀더 많아질 수 있지만, 시스템의 행동은 클래스에 정의된 정적인 내용보다는 런타임에 드러나는 객체 합성에 의한 상호 관련성에 따라 달라질 수 있다. + +결론적으로 객체 합성을 사용하면 **재사용을 위해서 새로운 구성요소를 생성할 필요 없이 필요한 기존의 구성요소를 조립해서 모든 새로운 기능을 얻어올 수 있다.** 하지만 기존 구성요소의 조합을 통한 재사용만으로 목적을 달성할 수 있는 경우는 드물다. 상속에 의한 재사용은 기존 클래스들을 조합해서 새로운 구성요소를 쉽게 만들 수 있도록 해 준다. 그러므로 상속과 객체 합성은 적절히 조합되어야만 완벽히 재사용이 가능하다. + +## 위임(delegation) + +위임은 합성을 상속만큼 강력하게 만드는 방법이다. + +위임에서는 두 객체가 하나의 요청을 처리한다. 수신 객체가 연산의 처리를 위임자(delegate)에게 보낸다. 이는 서브클래스가 부모 클래스에게 요청을 전달하는 것과 유사한 방식이다. + +위임과 동일한 효과를 얻으려면 수신 객체는 대리자에게 자신을 매개변수로 전달해서 위임된 연산이 수신자를 참조하게 한다. + +```java +class Rectangle(val width: Int, val height: Int) { + fun area() = width * height +} + +class Window(val bounds: Rectangle) { + // Delegation + fun area() = bounds.area() +} +``` + +`Window` 클래스는 `Rectangle` 클래스를 자신의 인스턴스 변수로 만들고 `Rectangle` 클래스에 정의된 행동이 필요할 때는 Rectangle 클래스에 위임함으로써 `Rectangle`의 행동을 재사용할 수 있다. + +다시 말해, 상속처럼 Window 인스턴스를 Rectangle 인스턴스로 간주하는 방식이 아닌 Window 인스턴스가 Rectangle 인스턴스를 포함하도록 하고, Window 인스턴스는 자신이 받은 요청을 Rectangle 인스턴스로 전달하는 것이다. + + +`Window` 클래스는 `area()` 연산을 `Rectangle` 인스턴스에 전달한다. + +실선 화살표는 한 클래스가 다른 클래스의 인스턴스에 대한 참조자를 갖고 있음을 보여준다. 참조는 이름을 선택적으로 정의할 수 있는데, 다이어그램에선 rectangle로 정의한다. + +위임의 가장 중요한 장점은 런타임에 행동의 조합을 가능하게 하고, 조합하는 방식도 변경해준다는 것이다. + +`Window` 객체가 런타임에 `Rectangle` 인스턴스를 `Circle` 인스턴스로 대체하면 원형의 윈도우가 될 것이다. (물론 이를 위해서는 Rectangle 클래스와 Circle 클래스가 동일한 타입이라는 가정이 필요하다.) + +위임이 갖는 단점은 객체 합성을 통해 소프트웨어 설계의 유연성을 보장하는 방법과 동일하게 동적인데다가 고도로 매개변수화된 소프트웨어는 정적인 소프트웨어 구조보다 이해하기가 더 어렵다. 왜냐하면 클래스의 상호작용이 다 정의되어 있는 것이 아니고, 런타임 객체에 따라서 항상 그 결과가 달라지기 때문이다. 위임은 그러한 복잡함보다 단순화의 효과를 더 크게 할 수 있는 경우에 사용하면 좋다. + +### 위임을 부분적으로 사용하는 디자인 패턴 + +- 상태(State) 패턴 +- 전략(Strategy) 패턴 +- 방문자(Visitor) 패턴 + +상태 패턴에서 객체는 현재 상태를 표현하는 상태 객체에 요청의 처리를 위임한다. 전략 패턴에서 객체는 요청을 수행하는 추상화한 전략 객체에게 특정 요청을 위임한다. + +이 두 패턴의 목적은 처리를 전달하는 객체를 변경하지 않고 객체의 행동을 변경할 수 있게 하자는 것이다. +방문자 패턴에서, 객체 구조의 각 요소에 수행하는 연산은 언제나 방문자 객체에게 위임된 연산이다. + +### 위임에 전적으로 의존하는 디자인 패턴 + +- 중재자(Mediator) 패턴 +- 책임 연쇄(Chain of Responsibility) 패턴 +- 가교(Bridge) 패턴 + +중재자 패턴은 객체 간의 교류를 중재하는 객체를 도입하여 중재자 객체가 다른 객체로 연산을 전달하도록 구현한다. 이때, 연산에 자신에 대한 참조자를 함께 보내고 위임받은 객체가 다시 자신에게 메시지를 보내서 자신이 정의한 데이터를 얻어가게 함으로써 진정한 위임을 구현한다. + +책임 연쇄 패턴은 한 객체에서 다른 객체로 고리를 따라서 요청의 처리를 계속 위임한다. 이 요청에는 요청을 처음 받은 원본 객체에 대한 참조자를 포함한다. + +위임은 객체 합성의 극단적인 예로서, 코드 재사용을 위한 매커니즘으로 상속을 객체 합성으로 대체할 수 있다. + +## 코틀린의 위임 + +코틀린은 기본적으로 보일러 플레이트가 필요 없이 위임 패턴을 지원한다. + +`Window`의 supertype 목록에 있는 `by` 절은 `bounds`가 `Window`의 객체 내부에 저장되고, `컴파일러가 bounds`로 전달하는 `ClosedShape`의 모든 메서드를 생성함을 의미한다. + +```kotlin +interface ClosedShape { + fun area(): Int +} + +class Rectangle(val width: Int, val height: Int) : ClosedShape { + override fun area() = width * height +} + +class Window(private val bounds: ClosedShape) : ClosedShape by bounds +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\354\235\221\354\247\221\353\217\204\354\231\200\342\200\205\352\262\260\355\225\251\353\217\204.md" "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\354\235\221\354\247\221\353\217\204\354\231\200\342\200\205\352\262\260\355\225\251\353\217\204.md" new file mode 100644 index 00000000..e7b94271 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\204\355\202\244\355\205\215\354\262\230\342\200\205\353\260\217\342\200\205\353\260\251\353\262\225\353\241\240/\352\260\235\354\262\264\354\247\200\355\226\245/\354\235\221\354\247\221\353\217\204\354\231\200\342\200\205\352\262\260\355\225\251\353\217\204.md" @@ -0,0 +1,16 @@ +--- +title: '응집도와 결합도' +lastUpdated: '2024-03-02' +--- + +응집도와 결합도는 코드의 관심사와 연결관계가 어느정도로 구분되어있고, 얽혀있는지를 나타내는 정도이며, 모듈의 독립성을 판단하는 두 가지 지표이다. 응집도는 모듈 내부의 기능적인 집중 정도, 결합도는 모듈과 모듈간의 상호 의존 정도라고 할 수 있다. + +**높은 응집도와 낮은 결합도**를 가진 코드가 객체지향적으로 좋은 코드로 여겨지며,
객체지향 원칙 중 개방 폐쇄 원칙(Open-Closed Principle, OCP)과 연관있는 개념이다. + +## 응집도(Cohesion) + 응집도는 모듈에 포함된 내부 요소들이 하나의 책임/ 목적을 위해 연결되어있는 연관된 정도이다. 응집도가 높다는 것은, 하나의 모듈또는 쿨래스가 하나의 책임 또는 관심사에만 집중되어있다는 것을 뜻한다. 응집도가 높으면 그 모듈이 처리할 수 있는 기능 중 하나가 변경된다고 하더라도 해당 모듈에 있는 코드만 수정하면 된다. + +즉, 응집도는 얼마나 적은 책임을 가지고있는지의 정도이며, 요구사항 변경시에 코드를 최소로 변경하기 위한 조건이다. + +## 결합도(Coupling) + 결합도는 한 객체가 책임과 관심사가 다른 오브젝트와 어느정도 결합되어있는 지의 정도이다. 느슨한 결합은 관계를 유지하는 데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공하고, 나머지는 서로 독립적인 상태를 의미한다. 그렇기 떄문에 결합도가 낮은 경우엔 변경이 일어나는 객체가 결합되어있는 다른 객체에게 주는 영향이 적어지게 된다. 결합도가 낮아지면 변화에 대응하는 속도가 높아지고, 구성이 깔끔해진다. 또한 확장하기에도 편리해진다. diff --git "a/src/content/docs/TIL/\354\225\214\352\263\240\353\246\254\354\246\230\342\200\205Algorithm/\354\204\270\352\267\270\353\250\274\355\212\270\355\212\270\353\246\254.md" "b/src/content/docs/TIL/\354\225\214\352\263\240\353\246\254\354\246\230\342\200\205Algorithm/\354\204\270\352\267\270\353\250\274\355\212\270\355\212\270\353\246\254.md" new file mode 100644 index 00000000..1fdbb2eb --- /dev/null +++ "b/src/content/docs/TIL/\354\225\214\352\263\240\353\246\254\354\246\230\342\200\205Algorithm/\354\204\270\352\267\270\353\250\274\355\212\270\355\212\270\353\246\254.md" @@ -0,0 +1,109 @@ +--- +title: '세그먼트트리' +lastUpdated: '2024-03-02' +--- + +세그먼트트리로 구간합을 구하는 알고리즘이다. + +https://www.acmicpc.net/problem/2042 + +```java +import java.util.*; + +public class boj2042{ + static Scanner sc = new Scanner(System.in); + static long[] Tree; + static long[] arr; + + static int log2(int x,int base){ + return (int) Math.ceil(Math.log10(x)/Math.log10(2)); + } + + /* + makeTree : + 길이가 8인 배열이 있을때 + + (1-8의 구간합) + / \ + (1-4의 구간합) (5-4의 구간합) + / \ / \ + (1-2의 구간합) (3-4의 구간합) (5-6의 구간합) (7-8의 구간합) + / \ / \ / \ / \ + (1) (2) (3) (4) (5) (6) (7) (8) + + 재귀함수를 써서 이런 형태로 트리를 만든다. (Tree배열에 1차원으로 저장) + (배열은 (1-8의 구간합), (1-4의 구간합), (5-4의 구간합), (1-2의 구간합), (3-4의 구간합), (5-6의 구간합)...의 순서로 구성) + */ + + static long makeTree(int l, int r, int x){ + if(l==r) Tree[x] = arr[l]; + else Tree[x] = makeTree(l,(l+r)/2,x*2)+makeTree((l+r)/2+1,r,x*2+1); + return Tree[x]; + } + + /* + updateTree : + + (1-8의 구간합) + // \ + (1-4의 구간합) (5-4의 구간합) + / \\ / \ + (1-2의 구간합) (3-4의 구간합) (5-6의 구간합) (7-8의 구간합) + / \ / \\ / \ / \ + (1) (2) (3) (4) (5) (6) (7) (8) + + 4를 업데이트 한다고 가정, + 구간을 반으로 쪼개서 4가 왼쪽에 속하는지 오른쪽에 속하는지 판단해서 4가 구간에 포함된 노드를 탐색함 + 그리고 4가 포함된 노드들의 바뀔 값-원래값을 더해줌 + */ + + static void updateTree(int l, int r, int x, int goal, long val){ + Tree[x]+=val; + if(l==r) return; + if(l<=goal&&goal<=(l+r)/2) updateTree(l,(l+r)/2,x*2,goal,val); + else if((l+r)/2+1<=goal&&goal<=r) updateTree((l+r)/2+1,r,x*2+1,goal,val); + } + + /* + sumTree : + - 만약에 지금 위치가(지금 범위가) 내가 찾아야 하는 구간에 포함된다 -> 더해줌 + - 만약에 내가 합을 찾아야 하는 구간이 왼쪽의 부분집합이다 -> 왼쪽 탐색 + - 만약에 내가 합을 찾아야 하는 구간이 오른쪽의 부분집합이다 -> 오른쪽 탐색 + - 만약에 왼쪽이랑 오른쪽에 애매하게 겹쳐져있다 -> 왼쪽 오른쪽 쪼개서 탐색 + */ + + static long sumTree(int l, int r, int x, int gl, long gr){ + if(gl<=l&&r<=gr)return Tree[x]; + else if(gr<=(l+r)/2) return sumTree(l,(l+r)/2,x*2,gl,gr); + else if((l+r)/2+1<=gl) return sumTree((l+r)/2+1,r,x*2+1,gl,gr); + else if(gl<=(l+r)/2&&(l+r)/2<=gr)return sumTree(l,(l+r)/2,x*2,gl,gr)+sumTree((l+r)/2+1,r,x*2+1,gl,gr); + return 0; + } + + public static void main(String[] args){ + int n = sc.nextInt(); + int m = sc.nextInt(); + int k = sc.nextInt(); + arr = new long[n]; + Tree = new long[(int)Math.pow(2,log2(n,2)+1)+1]; + + for(int i = 0; i < n; i++)arr[i] = sc.nextLong(); + makeTree(0,n-1,1); + + for(int i = 0; i < m + k; i++){ + int c = sc.nextInt(); + int a = sc.nextInt(); + long b = sc.nextLong(); + + if(c==1){ + long tmp = arr[a-1]; + arr[a-1] = b; + updateTree(0,n-1,1,a-1,b-tmp); + } else if(c==2) { + System.out.println(sumTree(0,n-1,1,a-1,b-1)); + } + } + sc.close(); + } +} +``` diff --git "a/src/content/docs/TIL/\354\225\214\352\263\240\353\246\254\354\246\230\342\200\205Algorithm/\354\231\270\355\214\220\354\233\220\354\210\234\355\232\214.md" "b/src/content/docs/TIL/\354\225\214\352\263\240\353\246\254\354\246\230\342\200\205Algorithm/\354\231\270\355\214\220\354\233\220\354\210\234\355\232\214.md" new file mode 100644 index 00000000..955797a5 --- /dev/null +++ "b/src/content/docs/TIL/\354\225\214\352\263\240\353\246\254\354\246\230\342\200\205Algorithm/\354\231\270\355\214\220\354\233\220\354\210\234\355\232\214.md" @@ -0,0 +1,54 @@ +--- +title: '외판원순회' +lastUpdated: '2024-03-02' +--- + +비트마스크와 dp를 활용한 외판원 순회 알고리즘이다. + +https://www.acmicpc.net/problem/2098 + +```java +import java.util.*; + +public class boj2098{ + static Scanner sc = new Scanner(System.in); + static int[][] map; + static int[][] dp; + static int n; + static int INF = 9999999; + static int min(int a, int b){ + return (a + +- CPU Core가 존재하고 있으며 OS Scheduler에 의해서 다수의 Thread가 Scheduling되어 동작하고 있는 모습을 나타내는 그림이다. +- Network Poller는 Network를 처리하는 별도의 독립된 Thread를 의미한다. +- Run Queue에는 GRQ (Global Run Queue)와 LRQ (Local Run Queue)가 2가지가 존재한다. + - GRQ는 의미 그대로 전역 Goroutine Queue 역할을, + - LRQ는 의미 그대로 지역 Goroutine Queue 역할을 수행한다. + +### GMP 구조체 + +- **G** (Goroutine) : 고루틴을 의미 + - 런타임이 고루틴을 관리하기 위해서 사용한다. + - 컨텍스트 스위칭을 위해 스택 포인터, 고루틴의 상태 등을 가지고 있다. + - G는 LRQ에서 대기하고 있다. +- **M** (Machine) : OS 스레드를 의미 + - M은 P의 LRQ로부터 G를 할당받아 실행한다. + - 고루틴과 OS 스레드를 연결하므로 스레드 핸들 정보, 실행중인 고루틴, P의 포인터를 가지고 있다. +- **P** (Processor) : 논리 프로세서를 의미 + - P는 컨텍스트 정보를 담고 있으며, LRQ를 가지고 있어서 G를 M에 할당한다. +- **LRQ**(Local run queue) : P마다 존재하는 Run Queue + - P는 LRQ로 부터 고루틴을 하나씩 POP하여 실행한다. + - P마다 하나의 LRQ가 존재하기 때문에 레이스 컨디션을 줄일 수 있다. + - LRQ가 M에 존재하지 않는 이유는 M이 증가하면 LRQ의 수도 증가하여 오버헤드가 커지기 때문입니다. +- **GRQ**(Global run queue) : LRQ에 할당되지 못한 고루틴을 관리하는 Run Queue + - 실행 상태의 고루틴은 한번에 10ms까지 실행되는데, 10ms 동안 실행된 고루틴은 대기 상태가되어 GRQ로 이동됩니다. + - 고루틴이 생성되는 시점에 모든 LRQ가 가득찬 경우 GRQ에 고루틴이 저장된다. + +- GMP 구조체는 [runtime.runtime2.go](https://github.com/golang/go/blob/master/src/runtime/runtime2.go)에 구현되어 있다. + + image + +- Goroutine은 반드시 Processor(P)와 Thread(M)과 같이 존재할 경우에만 실행 상태가 된다. 따라서 동시에 최대로 구동시킬수 있는 Goroutine의 개수는 Processor(P)의 개수에 따라서 정해진다. +- P는 LRQ에 존재하며, P의 컨텍스트 내에서 실행되도록 설정된 고루틴을 관리한다. +- LRQ의 고루틴들이 P에 할당된 M에서 교대로 실행되도록 스케줄링 된다. +- OS가 M에게 할당한 시간 동안 G 작업을 다 끝내면 M은 스피닝(Spinning, Busy waiting)하며 P의 LRQ의 맨앞에 있는 G를 가져온다. + +### 고루틴 선택 (Poll order) + +- P가 선택할 수 있는 고루틴은 여러 가지가 있다. + + - Local Run Queue + - Global Run Queue + - Invoke Network poller + - Work Stealing + +- 고루틴을 선택하는 다양한 방법이 존재하지만 크게는 아래와 같은 순서로 이루어진다. + + - P가 자신의 LRQ에서 고루틴을 POP + - LRQ, GRQ가 비어 있으면 다른 P의 LRQ에서 고루틴을 POP (Work Stealing) + - GRQ를 확인하여 고루틴을 POP + +### 작업 훔치기 (Work Stealing) + +- M, P가 모든 일을 마치면 GRQ 또는 다른 P의 LRQ에서 일을 가져온다. 이를 통해서 병목 현상을 없애고 OS 자원을 더 효율적으로 사용한다. +- 아래 예시를 살펴보자. + + image + +- P1, P2는 LRQ의 고루틴을 POP하여 작업을 수행한다. + + image + +- P1이 LRQ의 모든 작업을 완료한다. P2는 여전히 G1 작업을 처리 중이고, 이 시점에 작업 훔치기가 시도된다. + + image + +- P1은 P2의 LRQ를 확인하여 절반인 G3, G5를 가져와 작업을 계속 진행한다. + + image + +- 이번엔 P2가 작업을 완료했다. 그리고 작업 훔치기를 시도하기 위해서 P1의 LRQ를 확인하지만 실행 중인 고루틴을 제외하면 대기 중인 고루틴이 존재하지 않는다. 계속해서 P2는 GRQ를 확인하고 대기 중인 G9를 가져와서 작업을 진행한다. + + image + +- 이를 통해서 프로세서 간에 효과적으로 로드 밸런싱이 가능하며, M이 컨텍스트 스위칭 하는 것을 막을 수 있다. + +--- +참고 +- https://www.scs.stanford.edu/12au-cs140/notes/l2.pdf +- https://www.slideshare.net/KennethCeyer/grpc-goroutine-gdg-golang-korea-2019 +- https://itnext.io/load-balancing-goroutines-in-go-57e0896c7f86 +- https://ssup2.github.io/theory_analysis/Golang_Goroutine_Scheduling/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Go/\353\251\224\353\252\250\353\246\254\342\200\205\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Go/\353\251\224\353\252\250\353\246\254\342\200\205\352\264\200\353\246\254.md" new file mode 100644 index 00000000..c5c27a90 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Go/\353\251\224\353\252\250\353\246\254\342\200\205\352\264\200\353\246\254.md" @@ -0,0 +1,110 @@ +--- +title: '메모리 관리' +lastUpdated: '2024-03-02' +--- + +- Go는 힙에서 동적 할당이 필요한 메모리를 암묵적으로 할당한다. + - 할당이 암묵적으로 이뤄지기 때문에 코딩이 쉽지만, 메모리 할당과 해제가 명확하지 않으니 메모리 사용량이 높아질 수 있는 부분을 놓칠 가능성이 있다. + +- Go는 참조 지향이 아닌 값 지향적 언어이다. Go 변수는 절대 객체를 참조하지 않고, 항상 객체의 전체 값을 저장한다. (포인터가 없다는 의미는 아니다.) + - 모든 변수 선언(함수 인자, 반환 인자, 메서드 리시버 포함)은 해당 타입 전체를 할당하거나 그 포인터만 할당한다. + +- `new()`은 `&`과 동일하며, 힙 상의 포인터 박스와 별도의 메모리 블록에 타입을 할당한다. + +- Go 할당자는 특정 스팬에 하나 혹은 여러 8KB 페이지를 포함하는 메모리 블록을 할당하여 단편화를 해결한다. + - 각 스팬을 클래스 메모리 블록 크기에 맞게 생성된다. + - Go 1.21에서는 [67개의 크기 클래스](https://github.com/golang/go/blob/4f543b59c5618abccf0e78a17a2aeb173c085a91/src/runtime/sizeclasses.go)가 있으며 32KB가 최대 크기다. + +- Go 할당자는 가상 메모리 영역에서 메모리 블록을 [bin packing](https://en.wikipedia.org/wiki/Bin_packing_problem)한다. 또한 0으로 초기화된 개인 익명 페이지와 함꼐 `mmap`을 사용하여 운영체제로부터 더 많은 공간을 요청한다. + +- 하지만 메모리 공간을 `make`로 할당받는 즉시 사용할 수 있는 메모리 공간을 받는 것은 아니다. 600MB의 바이트 슬라이스를 사용하는 예제를 살펴보자. + + ```go + b := make([]byte, 600 * 1024 * 1024) + b[500] = 1 + b[100000] = 1 + b[104000] = 1 + for i := range b { + b[i] = 1 + } + ``` + +1. b 변수는 `[]byte` 슬라이스로 선언된다. 이후 등장하는 `make` 문은 600MB 크기의 데이터를 가진 바이트 배열을 만들어 힙을 할당하는 작업이다. + - 이를 디버깅해보면 아래와 같은 정보를 알 수 있다. + - 해당 슬라이스에 사용되는 세 가지 메모리 매핑에 대한 RSS는 548KB, 0KB, 120KB이다. (VSS 번호보다 훨씬 작음) + - 전체 프로세스의 총 RSS는 21MB이며, 프로파일링에 따르면 대부분 힙의 외부에서 들어오는 것으로 나타났다. + - 힙 크기는 600.16MB이다. (RSS가 훨씬 더 낮음에도 불구하고) + +2. 슬라이스 요소에 대한 액세스(쓰기 또는 읽기)를 시작하면 운영체제는 해당 요소를 둘러싼 실제 물리 메모리를 예약하기 시작한다. + - 3개의 메모리 매핑 RSS는 556KB, 0KB, 180KB이다. + - 총 RSS는 여전히 21MB이다. + - 힙 크기는 600.16MB이다. (실제로는 더 크지만 배경이나 루틴 때문일 수 있다.) + +3. 모든 요소에 반복해서 접근한 후, b 슬라이스의 모든 페이지가 요구에 따라 피지컬 메모리에 매핑되는 것을 볼 수 있다. + - 다음 통계가 이를 증명한다. + - 3개의 메모리 매핑에 대한 RSS는 1.5MB, (완전히 매핑된) 598MB, 그리고 1.2MB이다. + - 전체 프로세스의 RSS는 621.7MB를 나타낸다. (마침내 힙 사이즈와 같아졌다.) + - 힙 크기는 600.16MB이다. + +### 가비지 컬렉션 + +- Go는 주기적으로 힙에 있는 객체를 대상으로 가비지 컬렉션을 실행한다. + +- `GOGC` 옵션은 가비지 컬렉터 비율을 나타낸다. + - 기본값은 100이다. + - 가비지 컬렉터 주기가 끝난 후 힙 크기가 n%가 될 떄 수행될 것이라는 의미다. + - [debug.SetGCPercent](https://pkg.go.dev/runtime/debug#SetGCPercent) 함수를 사용해서 프로그래밍적으로 설정할 수도 있다. + +- `GOMEMLINIT` 옵션은 소프트 메모리 제한을 제어한다. + - 기본값은 비활성화되어 있으며 설정된 메모리 제한에 가까운 경우에 가비지 콜렉터를 더 자주 실행하도록 한다. + +- [`runtime.GC()`](https://pkg.go.dev/runtime#GC)를 호출하여 가비지 컬렉터의 수집을 트리거할 수도 있다. + - GC 구현은 여러 단계로 구정된다. + 1. STW(Stop the world) 이벤트를 실행하여 모든 고루틴에 Write barrier(데이터 쓰기에 대한 lock)을 주입한다. + 2. 프로세스에 제공된 CPU 용량의 25%를 사용해, 쓰고 있는 힙의 모든 객체를 표시한다. + 3. 고루틴에서 쓰기 장벽을 제거하여 표시를 종료한다. + +- Go 런타임은 가비지 컬렉터 실행시 `MADV_DONTNEED` 인수를 사용하여 [`madvise`](https://man7.org/linux/man-pages/man2/madvise.2.html) 시스템 콜을 사용한다. + - 그렇기 떄문에 호출 프로세스의 RSS는 즉시 감소해도, 페이지는 즉시 해제되지 않고 커널이 적절한 순간까지 지연시킬 수도 있다. + +- 자세한 동작을 이해하기 위해 600MB의 바이트 슬라이스를 GC하는 예제를 살펴보자. + + ```c + b := make([]byte, 600*1024*1024) + for i := range b { + b[i] = 1 + } + b[5000] = 1 + + // 스코프가 금방 끝나는 경우에는 변수를 다른 객체에 대한 포인터로 대체할 때 `b = nil`과 같은 초기화가 필요없지만, + // 수명이 긴 함수에서는 변수를 nil로 설정하여 GC 대상에 포함되도록 해주는 것이 좋다. + b = nil + runtime.GC() + + // Let's allocate another one, this time 300MB! + b = make([]byte, 300*1024*1024) + for i := range b { + b[i] = 2 + } + ``` + +1. 큰 슬라이스를 할당하고 모든 요소에 액세스한 후의 통계는 다음과 같다. + - 3개의 메모리 매핑에 대한 RSS는 1.5MB, 598MB, 그리고 1.2MB이다. + - 전체 프로세스의 RSS는 621.7MB를 나타낸다. + - 힙 크기는 600.16MB이다. + +2. `b = nil`로 초기화하고 GC를 수동 호출한 후의 동계는 다음과 같다. + - 세 가지 메모리 매핑에 대한 RSS는 1.5MB, 0, 60kb이다. (중간 값이 해제되었고, VSS 값은 동일하다.) + - 전체 프로세스의 총 RSS는 21MB이다. + - 힙 크기는 159KB이다. + +3. 더 작은 슬라이스를 하나 더 배정하면 이전 메모리 매핑을 다시 사용한다. + - 세 가지 메모리 매핑에 대한 RSS는 1.5MB, 300MB, 60KB다. + - 전체 프로세스의 총 RSS는 321MB다. + - 힙크기는 300.1KB이다. + +--- +참고 +- [Go 성능 최적화 가이드](https://product.kyobobook.co.kr/detail/S000208953343) +- https://www.baeldung.com/linux/resident-set-vs-virtual-memory-size +- https://man7.org/linux/man-pages/man2/madvise.2.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Inner\342\200\205static\342\200\205class.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Inner\342\200\205static\342\200\205class.md" new file mode 100644 index 00000000..2aaaa085 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Inner\342\200\205static\342\200\205class.md" @@ -0,0 +1,21 @@ +--- +title: 'Inner static class' +lastUpdated: '2024-03-02' +--- + +클래스를 사용하면서 외부 인스턴스에 대한 참조가 필요없는 클래스를 선언 시 `Inner class may be static`이라는 경고가 뜬다. Inner class가 static으로 정의되어야 한다는 뜻인데, 그래야하는 이유가 무엇일까? + +'static'키워드가 붙은 내부 클래스는 메모리에 하나만 올라가는 인스턴스가 아니다. + +```java +MyClass.InnerClass mic1 = new MyClass().new InnerClass(); +MyClass.InnerClass mic2 = new MyClass().new InnerClass(); + +if (mic1 == mic2) { + System.out.println("내부 클래스는 새로만들어도 같은 참조지"); +} else { + System.out.println("내부 클래스도 클래스니까 다른 참조지"); +} +``` + +InnerClass의 새로운 인스턴스를 만들게 될 때는 위와 같이 new 연산자를 두번 사용해야한다. 외부 클래스에 대한 인스턴스를 이용해서 내부 클래스의 인스턴스를 생성한다. static 키워드를 붙이지 않았을 때는 그냥 일반 클래스와 비슷하게 취급되기 떄문에 내부 클래스로 셍성한 두개의 객체는 서로 다른 인스턴스이다. diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JAR\352\263\274\342\200\205WAR.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JAR\352\263\274\342\200\205WAR.md" new file mode 100644 index 00000000..6638351d --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JAR\352\263\274\342\200\205WAR.md" @@ -0,0 +1,19 @@ +--- +title: 'JAR과 WAR' +lastUpdated: '2023-12-02' +--- +## JAR + +- Java Archive의 약자 +- 자바에서 사용되는 압축 파일의 한 형태로, 작동 방식은 흔히 자료를 압축하는 `.zip`과 유사. +- `.jar`는 압축을 따로 해제하지 않아도 JDK(Java Development Kit)에서 접근하여 사용이 가능 + + (JDK에 포함되는 JRE(Java Runtime Environment)만 가지고도 실행이 가능) + +- `.jar` 파일은 일반적으로 라이브러리, 자바 클래스 및 해당 리소스 파일(텍스트, 음성, 영상자료 ...), 속성 파일을 담는다. + +## WAR + +- WAR는 Web Application Archive의 약자로 웹 애플리케이션을 압축하고 배포하는데 사용되는 파일 형태. (`.war` 파일도 압축파일의 일종으로 `.jar`와 유사) +- WAR는 JSP, Servlet, Java Class, XML, 라이브러리, 정적 웹페이지(html ...) 및 웹 애플리케이션을 구성할 때 필요한 자원을 압축한 jar 파일이다. +- 배포 서술자라고 불리는 `web.xml`을 통해 경로를 반드시 지정해줘야 한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JAVA.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JAVA.md" new file mode 100644 index 00000000..f1ceb494 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JAVA.md" @@ -0,0 +1,51 @@ +--- +title: 'JAVA' +lastUpdated: '2023-12-02' +--- +## 객체 지향 프로그래밍(OOP, Object-Oriented Programming) +> #### 모든 데이터를 객체(object)로 취급하여 객체의 상태와 행동을 구체화 하는 형태의 프로그래밍 +### 클래스 (class) +#### 객체를 정의하는 틀 또는 설계도 + - 필드 = 클래스에 포함된 변수 + - 메소드 = (class에 종속된) 함수 + + +### static + - 클래스 변수 혹은 클래스 메소드 앞에 붙는거 + - 인스턴스 변수와 다르게 인스턴스하지 않아도 그냥 사용가능 (클래스가 메모리에 올라갈 때 메소드 영역에 바로 저장 되기 때문) + - A라는 클래스 안에 num이라는 static 변수가 있으면 그냥 A.num하고 쓰면 됨 + - 함수도 그냥 메소드 이름.하고 파라미터만 넣어서 씀 + - 반면, 인스턴스 변수는 new 해서 인스턴스로 만든담에 힙 영역에 저장돼야 사용 가능 + - 인스턴스 함수만 이 인스턴스 변수의 내용을 바꿀 수 있음 + +--- +## 자바의 특징 + +### 상속 + - extend (부모) + - 클래스는 딱 하나의 클래스만 상속받을 수 있음 + - super + - this는 내 안에서 나를 부르는거지만, super는 내 안에서 부모를 부르는거임 + - super.변수이름 하면 부모 클래스의 멤버를, super()하면 부모의 생성자를 뜻함. + - 자식 객체를 생성하면 부모의 기본 생성자가 자동으로 만들어짐 + + - method overriding + - 상속받은 메소드를 자식에서 재정의해서 쓰는거 + +### 다형성(polymorphism) + + - 다형성은 하나의 객체가 여러 가지 타입을 가질 수 있다는 뜻 + - 그래서 자바에서는 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 함 + - 단, 참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 같거나 적을때만 됨 + - 부모변수 = 자식변수, 자식변수 = (자식클래스)부모변수 + +### 추상화 + - 부모클래스에선 있다고 치고 이름만 만들어놈 + - 그리고 자식에서 쓸때 오버라이딩 해서 알아서 구현해서 씀 + - 이러한 추상 클래스는 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가지는 메소드의 집합을 정의할 수 있게 해줌 + +### 인터페이스(interface) + - implements (부모),(부모),(부모)... + - 오로지 추상 메소드와 상수만을 포함할 수 있지만, 여러 클래스를 상속받을 수 있음 + - 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스임 + - 모든 필드가 public static final이어야 하며, 모든 메소드는 public abstract이어야 함 diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JDKProxy\354\231\200\342\200\205CGLibProxy.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JDKProxy\354\231\200\342\200\205CGLibProxy.md" new file mode 100644 index 00000000..a7677158 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JDKProxy\354\231\200\342\200\205CGLibProxy.md" @@ -0,0 +1,76 @@ +--- +title: 'JDKProxy와 CGLibProxy' +lastUpdated: '2024-03-02' +--- + +Proxy를 구현한 것 중 가장 많이 쓰이는 종류로는 **JDK Proxy와 CGLib Proxy**가 있다. + +두 방식의 가장 큰 차이점은 Target의 어떤 부분을 상속 받아서 프록시를 구현하느냐에 있다. + +![image](https://user-images.githubusercontent.com/81006587/200806976-6528c443-8c57-4920-85e4-fc2131efcfbe.png) + +JDK Proxy는 Target의 상위 인터페이스를 상속 받아 프록시를 만든다. + +따라서 **인터페이스를 구현한 클래스가 아니면 의존할 수 없다**. Target에서 다른 구체 클래스에 의존하고 있다면, JDK 방식에서는 그 클래스(빈)를 찾을 수 없어 런타임 에러가 발생한다. + +우리가 의무적으로 서비스 계층에서 인터페이스 -> XXXXImpl 클래스를 작성하던 관례도 다 이러한 JDK Proxy의 특성 때문이기도 하다. + +또한 내부적으로 Reflection을 사용해서 추가적인 비용이 발생한다. + +```java +public class ExamDynamicHandler implements InvocationHandler { + private ExamInterface target; // 타깃 객체에 대한 클래스를 직접 참조하는것이 아닌 Interface를 이용 + + public ExamDynamicHandler(ExamInterface target) { + this.target = target; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + // TODO Auto-generated method stub + // 메소드에 대한 명세, 파라미터등을 가져오는 과정에서 Reflection 사용 + String ret = (String) method.invoke(target, args); //타입 Safe하지 않다는 단점이 있다. + return ret.toUpperCase(); //메소드 기능에 대한 확장 + } +} +``` + +CGLib Proxy는 Target 클래스를 상속 받아 프록시를 만든다. + +JDK Proxy와는 달리 리플렉션을 사용하지 않고 바이트코드 조작을 통해 프록시 객체 생성을 하고 있다. + +게다가 인터페이스를 구현하지않고도 해당 구현체를 상속받는 것으로 문제를 해결하기 때문에 성능상 이점을 가지고, 런타임 에러가 발생할 확률도 상대적으로 적다. + +CGLib는 Enhancer라는 클래스를 바탕으로 Proxy를 생성한다. + +```java +// 1. Enhancer 객체를 생성 +Enhancer enhancer = new Enhancer(); + +// 2. setSuperclass() 메소드에 프록시할 클래스 지정 +enhancer.setSuperclass(BoardServiceImpl.class); +enhancer.setCallback(NoOp.INSTANCE); + +// 3. enhancer.create()로 프록시 생성 +Object obj = enhancer.create(); + +// 4. 프록시를 통해서 간접 접근 +BoardServiceImpl boardService = (BoardServiceImpl)obj; +boardService.writePost(postDTO); +``` + +이처럼 상속을 통해 프록시 객체가 생성되는 모습을 볼 수 있다. + +BoardServiceProxy.writePost(postDTO) -> BoardServiceImpl.writePost(postDTO) +`enhancer.setCallback(NoOp.INSTANCE);`라는 코드는 `Enhancer` 프록시 객체가 직접 원본 객체에 접근하기 위한 옵션이다. + +기본적으로 프록시 객체들은 직접 원본 객체를 호출하기 보다는, 별도의 작업을 수행하는데 CGLib의 경우 Callback을 사용한다. + +CGLib에서 가장 많이 사용하는 콜백은 net.sf.cglib.proxy.MethodInterceptor인데, 프록시와 원본 객체 사이에 인터셉터를 두어 메소드 호출을 조작하는 것을 도와줄 수 있게 된다. + +``` +BoardServiceProxy -> BoardServiceInterceptor -> BoardServiceImpl +``` + +자바 리플렉션 방식보다 CGLib의 MethodProxy이 더 빠르고 예외를 발생시키지 않는다고 하여 Springboot에서는 CGLib를 기본 프록시 객체 생성 라이브러리로 채택하게 되었다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JLink\342\200\205&\342\200\205JDeps.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JLink\342\200\205&\342\200\205JDeps.md" new file mode 100644 index 00000000..c6f892de --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JLink\342\200\205&\342\200\205JDeps.md" @@ -0,0 +1,65 @@ +--- +title: 'JLink & JDeps' +lastUpdated: '2024-03-02' +--- + +## JLink + +Java is one of the most used programming languages for enterprise application development globally. However, developers often struggle with the size of the Docker images when deploying Java applications in Docker containers. One of the ways to solve this problem is to use JLink, a tool introduced in JDK 9. + +JLink (Java Linker) is a command-line tool that **assembles and optimizes a set of modules and their dependencies** into a custom runtime image. This essentially means it creates a minimal Java runtime environment with only the necessary modules required by your application. + +```bash +jlink --module-path $JAVA_HOME/jmods:mlib --add-modules test.module --output testRunTime +``` + +In the above command, `test.module` is your module, and `testRuntime` is the custom runtime image that JLink will create. + +When creating Docker images for Java applications, the size of the image is often a concern — particularly for Spring Boot applications, which come with many dependencies. + +JLink enables you **to create a minimal Java runtime** with only the necessary modules. By doing so, it significantly reduces the size of your Docker image. For example, a standard Java runtime environment might be over 200 MB, but with JLink, you can bring it down to less than 50 MB. + +## Jdeps + +Jdeps is a Java tool that shows the **package-level or class-level dependencies**. The tool, introduced in Java 8, can be used to understand an application’s dependencies, which can then be used to create a custom runtime image using JLink. + +When ensuring that all our dependencies are located in one directory, we can use jdeps to print a summary of the dependencies. + +```bash +jdeps -cp 'mydeps/lib/*' -recursive --multi-release 17 -s target/MyJar.jar +``` + +Similarly, we can use jdeps to print all module dependencies recursively for the Spring Boot application and the dependencies. The output generated by jdeps enables JLink to create a Java Runtime that only contains the modules needed for this application. + +## Building a Docker image with a custom Java Runtime + +So now, let’s combine jdeps and JLink to build a custom Java Runtime. With this runtime, we can create a perfect, minimal Docker image specifically for a Spring Boot application. + +```dockerfile +FROM maven:3-eclipse-temurin-17 as build + +COPY ./build/libs/*.jar app.jar + +RUN jdeps --ignore-missing-deps -q \ + --recursive \ + --multi-release 17 \ + --print-module-deps \ + --class-path 'BOOT-INF/lib/*' \ + app.jar > deps.info + +RUN jlink \ + --add-modules $(cat deps.info) \ + --strip-debug \ + --compress 2 \ + --no-header-files \ + --no-man-pages \ + --output /myjre + +ENTRYPOINT ["java","-jar","/app.jar"] +``` + +The example above lightens the image for the jar file docker build in a typical location in a spring application. + +--- +reference +- https://aws.amazon.com/ko/blogs/tech/amazon-corretto-base-container-diet/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Heap\342\200\205\354\230\201\354\227\255\342\200\205\352\265\254\354\241\260\354\231\200\342\200\205GC.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Heap\342\200\205\354\230\201\354\227\255\342\200\205\352\265\254\354\241\260\354\231\200\342\200\205GC.md" new file mode 100644 index 00000000..a1b2e5c2 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Heap\342\200\205\354\230\201\354\227\255\342\200\205\352\265\254\354\241\260\354\231\200\342\200\205GC.md" @@ -0,0 +1,54 @@ +--- +title: 'Heap 영역 구조와 GC' +lastUpdated: '2024-03-02' +--- + +여기에선, JVM이 사용하는 런타임 데이터 영역이 어떻게 나뉘어지는지에 대해 알아봤다. 런타임 데이터 영역은 크게 Method영역, Heap영역, Stack영역 등등으로 나뉘어있는데, 런타임중 가장 많은 메모리가 새로 할당되는 영역이 Heap영역이기 때문에, GC 또한 Heap영역을 위주로 실행된다. + +이때, Heap 영역은 GC와 메모리 관리를 위해 저장되어있는 데이터를 내부에서 여러 유형으로 분류하여 저장한다. + + + +Heap영역은 크게 3가지의 영역으로 나뉜다. + +### Young Generation 영역 + +자바 객체가 생성되자마자 저장되는 영역이다. 이름 그대로 생긴지 얼마 안되는 어린 객체들이 속하는 곳이다. + +Heap 영역에 객체가 생성되면 그 즉시 Young Generation 중에서도 Eden 영역에 할당되고, 이 영역에 데이터가 어느정도 쌓이게 되면 참조정도에 따라 Servivor의 빈 공간으로 이동되거나 회수된다. + +━━> 각 영역이 채워지면, 참조가 남아있는 객체들이 비워진 Survivor(s0, s1)로 이동한다.
+ - s0이 채워져있다면 s1으로, s1이 채워져있다면 s0로 이동하는 것이다. (따라서 s0과 s1중 한 곳은 항상 비어있다)
+ - 그리고 참조가 없는 객체들은 `Minor GC`를 통해 수집되어 삭제된다. + +### Old Generation(Tenured) 영역 + +Young Generation(Eden+Servivor) 영역이 차게 되면 또 참조정도에 따라 Old영역으로 이동하거나 회수된다. + +이 Old 영역에도 공간이 모자라면 모든 스레드를 멈추고(이걸 `Stop-The-World`라고 부른다.) 긴 시간동안 **Major GC**를 수행하는데, 그 시간동안은 애플리케이션을 구동시킬 수 없기 때문에 처리가 지연된다. 성능 향상을 위해선 이 Major GC가 자주 일어나지 않도록, 메모리 관리를 잘 해야한다. + +### Permanent 영역 + +Permanent 영역(PermGen)은, 보통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들을 저장하는 영역으로,(Method 영역을 포함한 개념), heap 영역에 포함되어 있기는 하나 heap 영역과는 다른 개념으로 간주되었다. + +![image](https://user-images.githubusercontent.com/81006587/209075255-602ec07e-906d-4576-8297-0fe4c3f89ab6.png) + +(Permanent는 Heap영역 안에 있긴 하지만, Non-Heap으로 취급되는 영역, Method 영역을 포함하고있음 참고) + +그래서 이 Permanent 영역을 Heap이라고 해야하는지, 아닌지 애매하여 많이 혼동되었다. + +하지만 JAVA 8부터는 Permanent라는 개념이 사라지고 완전히 Non-Heap인 Metaspace라는 영역이 생겼다! + +Permanent 영역은 JVM에 의해 크기가 제한되어있는 영역이었는데, 그로 인해 큰 애플리케이션을 돌리는 경우 OS에 여유공간이 있더라도 메모리가 초과되어 사용할 수 없는 문제가 있었다. 그를 해결하기 위해 JAVA에는 JVM에 의해 메모리가 제한되지 않는 `Native Memory 영역`에 **Metaspace**라는 영역을 만들어 대체하여 사용하도록 하였고, 이젠 OS에 의해 메모리 할당 공간이 자동으로 조절되므로 이론상 아키텍쳐가 지원하는 메모리 크기까지 확장할 수 있다. + +Metaspace가 Permanent의 어떤 역할을 대체하고있고, 그로 인해 어떤 변화가 생겼는지는 [여기](./Permanent%E2%80%85to%E2%80%85Metaspace.md)에서 더 알아보자. + +--- + +참고 + +https://stackoverflow.com/questions/9095748/method-area-and-permgen
+https://stackoverflow.com/questions/1262328/how-is-the-java-memory-pool-divided
+https://jaemunbro.medium.com/java-metaspace%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-ac363816d35e
+https://8iggy.tistory.com/229
+https://javapapers.com/core-java/java-jvm-memory-types/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/JVM\342\200\205\352\265\254\354\204\261\354\232\224\354\206\214.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/JVM\342\200\205\352\265\254\354\204\261\354\232\224\354\206\214.md" new file mode 100644 index 00000000..a22a157c --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/JVM\342\200\205\352\265\254\354\204\261\354\232\224\354\206\214.md" @@ -0,0 +1,45 @@ +--- +title: 'JVM 구성요소' +lastUpdated: '2024-03-02' +--- + + + +JVM 구성요소는 아래와 같은 것들이 있다. + +- 클래스 로더(Class Loader) +- 실행 엔진(Execution Engine) + - 인터프리터(Interpreter) + - JIT 컴파일러(Just-in-Time) + - 가비지 콜렉터(Garbage collector) +- 런타임 데이터 영역 (Runtime Data Area) + +각각에 대해서 자세히 알아보자. + +## 클래스 로더 + +JVM 내로 클래스 파일(`*.class`)을 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. 런타임시 동적으로 클래스를 로드하고 jar 파일 내에 저장된 클래스들을 JVM 위에 탑재한다. +즉, **클래스를 처음으로 참조할 때, 해당 클래스를 로드하고 링크**하는 역할을 한다. + +## 실행 엔진 + +클래스 로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시키면, 실행엔진은 **그것을 실행한다.** + +자바 바이트 코드(*.class)는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것인데, 실행 엔진은 이와 같은 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다. + +- 인터프리터 + 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.
+ 하지만 한 줄씩 수행하기 때문에 느리다는 단점이 있다. + +- JIT(Just-In-Time) + 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 기계어로 변경하고, 이후에는 해당 더 이상 인터프리팅 하지 않고 기계어로 직접 실행하는 방식이다 + +- 가비지 콜렉터(GC) + 더이상 사용되지 않는 인스턴스를 찾아 메모리에서 삭제한다. 자세한 내용은 여기에서 더 다룬다. + + +## 런타임 데이터 영역 + +JVM이 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간이다. 자세한 내용은 여기에서 더 다룬다. + + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Java\342\200\205Bytecode.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Java\342\200\205Bytecode.md" new file mode 100644 index 00000000..f73fa364 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Java\342\200\205Bytecode.md" @@ -0,0 +1,45 @@ +--- +title: 'Java Bytecode' +lastUpdated: '2024-03-02' +--- + +Java는 **JVM** 이라는 가상머신을 거쳐서 OS에 도달하기 때문에 OS가 인식할 수 있는 기계어로 바로 컴파일 되는게 아니라 JVM이 인식할 수 있는 Java bytecode(`*.class`)로 먼저 변환된다. + +`.java` 파일을 `.class` 라는 Java bytecode로 변환하는 것은 Java compiler의 역할이다. + +## (바이트코드로) 직접 컴파일 하는방법 + +아래는 Java Compiler에 의해 'java' 파일을 '.class' 라는 Java bytecode로 만드는 과정이다. + +Java Compiler는 JDK를 설치하면 `javac.exe`라는 실행 파일 형태로 설치된다. 정확히는 JDK의 bin 폴더에 'javac.exe'로 존재한다. + +Java Complier 의 `javac` 라는 명령어를 사용하면 .class 파일을 생성할 수 있다. + +```java +public class test { + public static void main(String[] args) { + System.out.println("Hello World"); + } +} +``` + +`"Hello World"`를 출력하는 .java 파일을 생성하고 이를 .class 파일로 변환시켜보자. + +```bash +C:\Users\owner>cd Desktop +``` + +Windows를 기준으로, cmd 창을 열고 해당 .java 파일이 있는 곳으로 이동한다. + +```bash +C:\Users\owner\Desktop>javac test.java +``` + +해당 위치에서 javac 명령어로 컴파일을 진행한다. 그렇기 하면 현재 위치(바탕화면)에 .class 파일이 생성된 걸 확인할 수 있다. + + +```bash +C:\Users\owner\Desktop>java test +``` + +위 명령어를 입력하면 java.exe를 통해 JVM이 구동되어 결과가 나오는 것을 볼 수 있다! \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Permanent\342\200\205to\342\200\205Metaspace.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Permanent\342\200\205to\342\200\205Metaspace.md" new file mode 100644 index 00000000..1d7f7a0f --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Permanent\342\200\205to\342\200\205Metaspace.md" @@ -0,0 +1,67 @@ +--- +title: 'Permanent to Metaspace' +lastUpdated: '2024-03-02' +--- + +JAVA8에서부터, Permanent가 사라지고 Metaspace가 생김으로써 그 역할을 일부 대체하게 되었다. + +Permanent는 JVM에 의해 크기가 강제되었지만, Metaspace는 OS가 자동으로 크기를 조절할 수 있는 Native memory 영역이기 때문에 기존과 비교해 큰 메모리 영역을 사용할 수 있게 되었다. + +Perm 영역에는 주로 클래스, 메소드 정보와 클래스 변수의 정보, static 변수와 상수 정보들이 저장되었는데, 이게 **Metaspace로 대체**되면서 Perm에 있었던 대부분의 정보가 Metaspace에 저장되도록 바뀌었다. + +다만, 기존 Perm 영역에 존재하던 **static Object**는 Heap 영역으로 옮겨져서 최대한 GC의 대상이 될 수 있도록 하였다고 한다. + +> The proposed implementation will allocate class meta-data in native memory and move interned Strings and class statics to the Java heap. Hotspot(JVM) will explicitly allocate and free the native memory for the class meta-data. + +또한, 메모리 옵션을 설정하는 명령어도 명칭이 바뀌었다. 표로 정리하자면 아래와 같다. + +|Java 7 (Permanent)|Java 8 (Metaspace)| +|-|-| +|Class 메타 데이터|저장|저장| +|Method 메타 데이터|저장|저장| +|Static Object 변수,상수|저장|Heap 영역으로 이동| +|메모리 옵션|-XX:PermSize
-XX:MaxPermSize|-XX:MetaspaceSize
-XX:MaxMetaspaceSize| + +그림으로 구조를 표현하자면 아래와 같다. + +- JAVA7의 JVM +```js +<----- Java Heap -----------------> <--- Native Memory ---> ++------+----+----+-----+-----------+--------+--------------+ +| Eden | S0 | S1 | Old | Permanent | C Heap | Thread Stack | ++------+----+----+-----+-----------+--------+--------------+ + <---------> + Permanent Heap +S0: Survivor 0 +S1: Survivor 1 +``` + +- JAVA8의 JVM +```js +<----- Java Heap -----> <--------- Native Memory ---------> ++------+----+----+-----+-----------+--------+--------------+ +| Eden | S0 | S1 | Old | Metaspace | C Heap | Thread Stack | ++------+----+----+-----+-----------+--------+--------------+ + +``` + +### Static Object가 GC된다고? + +static Object를 Heap 영역으로 옮겨서 최대한 GC의 대상이 될 수 있다는건 뭔가 좀 이상하다. + +static 변수는 클래스 변수로, 명시적 null 선언이 되지 않은 경우엔 GC되어서는 안되는 변수이기 때문이다. + +하지만 여기서 GC 한다는 것은 static 변수에 대한 참조를 삭제한다는 것이 아니라, 그 내용물을 지우겠다는 뜻이다. static Object를 GC하더라도, 그 객체나 변수에 대한 reference는 여전히 metaspace에 남아있도록 되어있기 때문에 상관이 없다. + +static object가 참조를 잃은 경우에 GC의 대상이 될 수 있으나, static object에 대한 참조가 살아있다면 GC의 대상이 되지 않음을 의미한다. + +다시말해, static Object가 Heap 영역으로 옮겨진다고 하더라도 metaspace는 여전히 static object에 대한 reference를 보관하고 있는 것이고, static 변수(primitive type, interned string)는 heap 영역으로 옮겨짐에 따라 GC의 대상이 될 수 있게끔 조치한 것이다. + +이는 클래스 변수 및 객체의 저장위치와 클래스 메타 정보의 위치가 **Method 영역이 속한 ParmGen으로부터 Heap과 메모리로 서로 분리되었다**는 점을 의미한다. + +--- + +참고 +https://openjdk.java.net/jeps/122 +http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-September/006679.html +https://blogs.oracle.com/poonam/about-g1-garbage-collector%2c-permanent-generation-and-metaspace \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Runtime\342\200\205Data\342\200\205Area.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Runtime\342\200\205Data\342\200\205Area.md" new file mode 100644 index 00000000..31ab2234 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/Runtime\342\200\205Data\342\200\205Area.md" @@ -0,0 +1,44 @@ +--- +title: 'Runtime Data Area' +lastUpdated: '2024-03-02' +--- + +런타임 데이터 영역(Runtime Data Area)은 JVM이 자바 프로그램 실행을 위한 데이터와 명령어를 저장하기 위해 OS로부터 할당받는 메모리 공간이다. + +![image](https://user-images.githubusercontent.com/81006587/208620894-79b2837e-8358-4a14-826c-825a288322a9.png) + +Runtime Data Area는 크게 Method영역, Heap영역, Stack영역, PC 레지스터 영역, Native Method Stack으로 나눌 수 있다. + +--- + +### Method 영역 + +- 클래스 로더에 의해 로드된 클래스, 메소드 정보와 클래스 변수의 정보가 저장되는 영역이다. 프로그램 시작부터 종료될때까지 메모리에 적재되지만 명시적 null 선언시 GC가 청소하도록 만들 수 있다. + +- 데이터가 가장 먼저 저장되는 영역이며, 모든 스레드가 공유한다. + +### Heap 영역 + +- **`런타임`시 결정되는 참조 자료형**이 저장되는 영역이다. new 연산자를 통해 생성된 객체(인스턴스)도 이곳에 저장된다. + +- 객체가 더 이상 쓰이지 않거나 명시적 null 선언 시 GC가 청소하도록 할 수 있다. 모든 스레드가 공유한다. + +### Stack 영역 + +- **`컴파일`시 결정되는 기본자료형과 참조변수**가 저장되는 영역이다. (기본자료형은 `int`, 'double'과 같은 자료형, 참조변수는 WapperClass인 `Integer`, `Double`과 같은 자료형을 말한다.) + +- Stack영역은 **각 스레드별로** 하나씩 생성되는데, 메소드가 호출될 때마다 이 영역에 각각의 `스택 프레임`이 만들어져 그 메소드에 대한 정보를 저장한다. 스택프레임은 스코프를 벗어나거나 메소드가 종료될 때 삭제된다. + +- 각 스레드별로 생성된다. + +### PC 레지스터 영역 + +- **JVM이 수행할 명령어의 주소**를 저장하는공간이다. OS의 PC 레지스터와 유사한 역할이나 CPU와는 별개로 JVM이 관리한다. + +- 각 스레드가 시작될 때마다, 스레드별로 생성된다. + +### Native Method Stack + +- 바이트코드가 아닌 기계어로 작성된 코드를 실행하는 공간이다. 다른 언어(c/c++)로 작성된 코드를 수행하기 위해 존재한다. + +- JNI(Java Native Interface) 호출 시 생성되며, Java Stack에서 Native Stack으로 동적 연결(Dynamic Linking)을 통해 확장된다. 각 스레드별로 생성된다. diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/TLAB\352\263\274\342\200\205PLAB.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/TLAB\352\263\274\342\200\205PLAB.md" new file mode 100644 index 00000000..57e6cbe0 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/TLAB\352\263\274\342\200\205PLAB.md" @@ -0,0 +1,37 @@ +--- +title: 'TLAB과 PLAB' +lastUpdated: '2024-03-02' +--- + +## 1. TLAB(Thread Local Allocation Buffers) + +JVM에서는 Eden 영역에 객체를 빠르게 할당(Allocation)하기 위해 bump the pointer와 TLABs(Thread-Local Allocation Buffers)라는 기술을 사용하고 있다. bump the pointer란 Eden 영역에 마지막으로 할당된 객체의 주소를 캐싱해두는 것이다. bump the pointer를 통해 새로운 객체를 위해 유효한 메모리를 탐색할 필요 없이 마지막 주소의 다음 주소를 사용하게 함으로써 속도를 높이고 있다. 이를 통해 새로운 객체를 할당할 때 객체의 크기가 Eden 영역에 적합한지만 판별하면 되므로 빠르게 메모리 할당을 할 수 있다. + +- Heap 메모리에 새로운 객체가 생성될 때, 만약 TLAB이 활성화되어 있다면 객체는 우선 TLAB에 위치하게 된다. +- TLAB은 Eden 영역에만 존재한다. 따라서 TLAB을 사용하면 에덴 영역을 좀 더 많이 사용하게 되지만 객체 생성시 성능 효과를 볼 수 있다. +- 각 스레드는 빠른 메모리 할당을 위해 자신만의 TLAB을 가지고 있다. +- TLAB은 각 스레드에 미리 할당되어 있다. 따라서 TLAB의 총 크기는 스레드 수에 비례한다. +- TLAB을 사용하려면 `-XX:+UseTLAB` 옵션을 사용해야한다. +- TLAB의 크기를 조절하려면 `-XX:+UseTLAB` 옵션을 사용해야한다. 디폴트는 `0`인데 이때는 시스템이 알아서 조절하게 된다. + +> As the runtime has TLAB per thread, it makes the process thread safe automatically (we have nothing to synchronize) and very cheap, as we take the pointer to the top of free space and move it on allocation size. Using pseudo-code, the algorithm looks like this: + +```c +start = currentThread.tlabTop; +end = start + sizeof(Object.class); + +if (end > currentThread.tlabEnd) { + goto slow_path; +} +``` + +## 2. PLAB(Promotion Local Allocation Buffers) + +- GC에서 Generation을 청소하는 동안 사용된다. +- 각 스레드에 존재한다. + +--- +참고 +- https://www.gmc-uk.org/registration-and-licensing/join-the-register/plab +- https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html +- https://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/\353\251\224\353\252\250\353\246\254\353\210\204\354\210\230.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/\353\251\224\353\252\250\353\246\254\353\210\204\354\210\230.md" new file mode 100644 index 00000000..dd09595a --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/JVM/\353\251\224\353\252\250\353\246\254\353\210\204\354\210\230.md" @@ -0,0 +1,201 @@ +--- +title: '메모리누수' +lastUpdated: '2024-03-02' +--- + +CS적으로 보면, 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상이다. + +할당된 메모리를 사용한 다음 반환하지 않는 것이 누적되면 메모리가 낭비된다. 즉, 더 이상 불핑요한 메모리가 해제되지 않으면서 메모리 할당을 잘못 관리할때 발생한다. + +## 자바에서 메모리 누수 + +더이상 사용되지 않는 객체들이 GC에 의해 회수되지 않고 계속 누적되는 현상을 말한다. 메모리 누수가 생기면 Old 영역에 계속 누적된 객체로 인해 Major GC가 빈번하게 발생하게 되면서, 프로그램 응답 속도가 늦어지고 성능이 저하된다. 이는 결국 OutOfMemory Error로 프로그램이 종료되게 한다. + +가비지 컬렉션을 소멸 대상이 되기 위해서는 어떠한 reference 변수에서 가르키지 않아야 한다. + +다 쓴 객체에 대한 참조를 해제하지 않으면 가비지 컬렉션의 대상이 되지 않아 계속 메모리가 할당되는 메모리 누수 현상이 발생된다. + +GC가 되지 않는 루트 참조 객체는 크게 3가지다. + +### 1. Static 변수에 의한 객체 참조 + +static는 GC의 대상이 되지 않는다. Static 변수는 클래스가 생성될 때 메모리를 할당 받고 프로그램 종료 시점에 반환되므로 사용하지 않고 있어도 메모리가 할당되어 있다. 잘 활용하면 성능을 향상시킬 수 있지만, 사용하지 않는데 계속 할당 되기만 한다면 GC가 되지 않아 메모리 릭으로 이어져 시스템 자체가 돌아갈 수 없는 상태에 이를 수 있다. + +### 2. 모든 현재 자바 스레드 스택내의 지역 변수, 매개 변수에 의한 객체 참조 + +자바에서 현재 실행중인 (각 스레드별로) 모든 메소드내에 선언된 지역 변수와 매개변수에 의해 참조되는 객체와 그 객체로부터 직간접적으로 참조되는 모든 객체는 참조되어 사용될 가능성이 있으며, 이 뿐만 아니라 caller 메소드로 return된 후에는 caller 메소드에서 참조하고 있는 지역변수, 매개변수에 의해 참조되는 객체와 객체로부터 직간접적으로 참조되는 모든 객체 또한, GC되지 않고 참조되어 사용될 가능성이 있다. + +따라서, 각 자바 스레드의 스택 프레임내에 있는 모든 지역변수와 매개 변수에 의해 참조되는 객체와 그 객체로부터 직간접적으로 참조되는 모든 객체들이 참조되어 사용될 가능성이 있다는 것이다. + +### 3. JNI 프로그램에 의해 동적으로 만들어지고 제거되는 JNI global 객체 참조 + +그 외 또 여러가지 방법으로 메모리 누수(memory leak)가 발생하는 패턴들이 있다. + +#### 1. Integer, Long 같은 래퍼 클래스(Wrapper)를 이용하여, 무의미한 객체를 생성하는 경우 + +```java +public class Adder { + publiclong addIncremental(long l) { + Long sum = 0L; + sum = sum + l; + return sum; + } + public static void main(String[] args) { + Adder adder = new Adder(); + for(long ; i < 1000; i++) { + adder.addIncremental(i); + } + } +} +``` + +long 대신 Long을 사용함으로써, 오토 박싱으로 인해 `sum = sum + l;`에서 매 반복마다 새 객체를 생성하므로 1000개의 불필요한 객체가 생성된다. + +#### 2. 맵에 캐쉬 데이터를 선언하고 해제하지 않는 경우 + + ```java +import java.util.HashMap; +import java.util.Map; +public class Cache { + + private Map map= new HashMap(); + + public void initCache() { + map.put("Anil", "Work as Engineer"); + map.put("Shamik", "Work as Java Engineer"); + map.put("Ram", "Work as Doctor"); + } + + public Map getCache() { + return map; + } + + public void forEachDisplay() { + for(String key : map.keySet()) { + String val = map.get(key); + System.out.println(key + " :: "+ val); + } + } + + public static void main(String[] args) { + Cache cache = new Cache(); + cache.initCache(); + cache.forEachDisplay(); + } +} +``` + +캐시에 직원과 직종을 넣었지만, 캐시를 지우지 않았다. 객체가 더 이상 사용되지 않을 때도 Map에 강력한 참조가 있기 때문에 GC가 되지 않는다. + +캐시의 항목이 더 이상 필요하지 않을 떄는 캐시를 지워주는 것이 바람직하다. + +또한 WeakHashMap으로 캐시를 초기화 할 수 있다. WeakHashMap의 장점은 키가 다른 객체에서 참조되지 않는 경우 해당 항목이 GC가 된다는 것이다. + +하지만, 캐시에 저장된 값을 재사용할때 항목이 GC되어 사라져있을 수 있기 때문에 주의하여야 한다. + +#### 3. 스트림 객체를 사용하고 닫지 않는 경우 + +```java +try +{ + Connection con = DriverManager.getConnection(); + ... + con.close(); +} Catch(exception ex) { + ... +} +``` + +실수로 발생할 수 있는 경우이다. + +try 블록에서 연결 리소스를 닫으므로 예외가 발생하는 경우 연결이 닫히지 않는다. 이 연결이 풀로 다시 돌아 오지 않기 때문에 메모리 누수가 발생한다. 또한 닫아지지 않아서 데드락이 발생할 가능 성이 크다. +항상 finally 블록엔 닫는 내용을 넣거나, TryWhitResource를 사용하자. + +#### 4. 맵의 키를 사용자 객체로 정의하면서 equals(), hashcode()를 재정의 하지 않아서 항상 다른 키로 착각하여 데이터가 계속 쌓이게 되는 경우 + +```java +public class CustomKey { + + public CustomKey(String name) { + this.name=name; + } + + private String name; + + public static void main(String[] args) { + + Map map = new HashMap(); + + map.put(new CustomKey("Shamik"), "Shamik Mitra"); + String val = map.get(new CustomKey("Shamik")); + + System.out.println("Missing equals and hascode so value is not accessible from Map " + val); + + } + +} +``` + +#### 5. 맵의 키를 사용자 객체로 정의하면서 equals(), hashcode()를 재정의 하였지만, 키값이 불변(Immutable) 데이터가 아니라서 데이터 비교시 계속 변하게 되는 경우 + +```java +import java.util.HashMap; +import java.util.Map; + +public class MutableCustomKey { + + public MutableCustomKey(String name) { + this.name = name; + } + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + final int prime = 31; int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + pblic boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + MutableCustomKey other = (MutableCustomKey) obj; + if (name == null) { + if (other.name != null) return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + + public static void main(String[] args) { + + MutableCustomKey key = new MutableCustomKey("Shamik"); + Map map = new HashMap(); + + map.put(key, "Shamik Mitra"); + + MutableCustomKey refKey = new MutableCustomKey("Shamik"); + String val = map.get(refKey); + + System.out.println("Value Found " + val); + key.setName("Bubun"); + + String val1 = map.get(refKey); + + System.out.println("Due to MutableKey value not found " + val1); + } +} +``` + +속성이 변경되면 프로그램에선 찾을 수 없지만, Map에서는 참조가 있으므로 메모리 누수가 발생한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/@Volatile.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/@Volatile.md" new file mode 100644 index 00000000..4db4779f --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/@Volatile.md" @@ -0,0 +1,74 @@ +--- +title: '@Volatile' +lastUpdated: '2024-03-02' +--- + +변수 선언시에 자바의 volatile 키워드 또는 코틀린의 `@Volatile` 애노테이션을 지정해줄 수 있다. 사전적으론 ‘휘발성의’라는 뜻을 가지며, 변수 선언시 volatile을 지정하면 값을 메인 메모리에만 적재하게 된다. + +> Annotate INSTANCE with @Volatile. The value of a volatile variable will never be cached, and all writes and reads will be done to and from the main memory. This helps make sure the value of INSTANCE is always up-to-date and the same to all execution threads. It means that changes made by one thread to INSTANCE are visible to all other threads immediately, and you don't get a situation where, say, two threads each update the same entity in a cache, which would create a problem. + +volatile 변수를 사용하지 않는 일반적인 경우는 내부적으로 성능 향상을 위해 메인 메모리로부터 읽어온 값을 CPU 캐시에 저장한다. 하지만 멀티쓰레드 애플리케이션에서는 각 쓰레드를 통해 CPU에 캐싱한 값이 서로 다를 수 있다 (CPU 캐시1 값 ≠ CPU 캐시2 값). 예제코드를 살펴보자. + +다음과 같은 Thread를 확장한 서브 클래스가 있다고 가정하자. + +```kotlin +class Worker : Thread() { + var stop = false + override fun run() { + super.run() + while(!stop){ } + } +} +```` + +이 Worker 클래스는 단순히 무한루프에 빠지는 쓰레드이다. 하지만 stop이 true가 되는 순간 루프에서 빠져나와 작업을 마칠 수 있게되고, 쓰레드는 종료된다. + +이제 Worker 클래스를 사용한 다음 예제코드를 살펴보자. + +```kotlin +@Test +fun `Worker test`(){ + repeat(3){ + val worker = Worker() // worker 쓰레드 생성 + worker.start() // worker 쓰레드 시작 + Thread.sleep(100) // 메인 쓰레드 잠시 수면 + println("stop을 true로 변경") + worker.stop = true // worker쓰레드의 stop 플래그 변경 + worker.join() // worker 쓰레드가 끝날 때까지 메인쓰레드에서 대기 + } + println("작업 종료") +} +``` + +이 코드를 실행했을 때 직관적으로 생각할 수 있는 출력 메시지는 아마 다음과 같을 것이다. + +``` +stop을 true로 변경 +stop을 true로 변경 +stop을 true로 변경 +작업 종료 +``` + +하지만 실제로는 그렇지 않다. “stop을 true로 변경” 메시지만 남긴 채 프로그램이 멈추게 된다. + +그 이유는 Worker 쓰레드가 ‘stop’이란 값을 계속 참조해야 하기 때문에 성능을 위해 CPU 캐시에 담아두게 되는데, 이때 메인쓰레드에서 접근하는 worker.stop의 값은 메인 메모리로부터 참조하는 값이므로 서로 다른 두개의 영역에 값이 존재한다. + +메인스레드에서 stop을 true로 바꿔도 Worker 쓰레드가 참조하는 stop은 CPU 캐시 영역에 저장된 값이므로 여전히 stop은 false이다. 그렇기 때문에 Worker 쓰레드는 루프를 빠져나오지 못하고 프로그램은 멈추게 된다. + +이제 코드를 약간 수정해보자. 수정은 매우 간단하다. @Volatile 만 붙이면 된다. + +```kotlin +class Worker : Thread() { + @Volatile + var stop = false + override fun run() { + super.run() + while(!stop){ } + } +} +``` +이제 아까 테스트 코드를 다시 수행하면 기대한 결과를 얻을 수 있다. + +### 결론 + +`@Volatile`을 붙이면 변수의 값이 메인 메모리에만 저장되며, 멀티 쓰레드 환경에서 메인 메모리의 값을 참조하므로 변수 값 불일치 문제를 해결할 수 있게된다. 다만 CPU캐시를 참조하는 것보다 메인메모리를 참조하는 것이 더 느리므로, 성능은 떨어질 수 밖에 없다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/Thread\342\200\205\354\203\201\355\203\234.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/Thread\342\200\205\354\203\201\355\203\234.md" new file mode 100644 index 00000000..fdad8534 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/Thread\342\200\205\354\203\201\355\203\234.md" @@ -0,0 +1,34 @@ +--- +title: 'Thread 상태' +lastUpdated: '2024-03-02' +--- + +JVM은 쓰레드를 New, Runnable, Running, Wating, Terminate의 다섯가지 상태로 관리한다. 쓰레드의 상태는 `getState()` 메서드 호출로 반환받을 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/216767536-7644d9f4-d6b9-417c-83de-d26c5a227c56.png) + +#### New + +객체를 생성한 뒤, 아직 start() 메서드 호출되기 전의 상태이다. + +### Runnable + +start() 메서드를 호출하여 동작시킨 쓰레드 객체는 JVM의 쓰레드 스케줄링 대상이 되며 Runnable 상태에 돌입하게 된다. 한 번 Runnable 상태에 돌입한 쓰레드는 다시 New 상태가 될 수 없다. + +#### Running + +Runnable 상태의 쓰레드는 큐에 대기하게 되는데, JVM은 각 쓰레드의 우선순위에 따라서 Running 상태로 만들어 쓰레드를 동작시킨다. + +쓰레드 스케줄러에 의해 Running 상태로 이동하게된 쓰레드는 재정의된 run() 메소드가 호출된다. + +run() 메소드가 호출되면 실제 동작이 수행되며, 그 결과에 따라 Waiting 또는 Terminate 상태로 바뀌게 된다. + +### Waiting + +쓰레드의 수행 중 I/O 블로킹이나 sleep(), join() 메서드에 의해 대기해야하는 경우 Waiting Pool 로 이동하게 된다. + +Waiting Pool 내의 쓰레드는 해당 I/O의 수행을 마치거나, sleep(), join() 등의 대기 조건이 끝나거나 혹은 인터럽트가 발생되게 되면 다시 Runnable 큐로 이동한다. + +### Terminate + +run()메소드의 수행이 끝나면 Terminate 상태가 되며 쓰레드가 종료된다. 한번 Terminate 된 쓰레드 객체는 start() 메서드를 호출해도 스레드 스케쥴링에 포함시킬 수 없다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/wait()\352\263\274\342\200\205notifyAll().md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/wait()\352\263\274\342\200\205notifyAll().md" new file mode 100644 index 00000000..9e42fab5 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/Thread/wait()\352\263\274\342\200\205notifyAll().md" @@ -0,0 +1,101 @@ +--- +title: 'wait()과 notifyAll()' +lastUpdated: '2024-03-02' +--- + +`wait()`과 `notifyAll()`을 사용하면 스레드의 순서를 제어할 수 있다. + +![image](https://user-images.githubusercontent.com/81006587/234431001-e64a2240-ffcd-4363-bee0-dab7e8c0d4a5.png) + + +간단하게 말하자면 wait은 스레드를 대기하게 만들고, notifyAll(또는 notify)는 대기중인 스레드를 꺠워주는 역할을 하는데, 자세한 것은 예시 코드를 보며 이해해보자. + +```java +package voting; + +public class VotingPlace { + private String voter1; + private String voter2; + private boolean isEmptyPlace1; + private boolean isEmptyPlace2; + + public VotingPlace() { + this.isEmptyPlace1 = true; + this.isEmptyPlace2 = true; + } + + public synchronized void vote (String voter) throws InterruptedException { + while ( (isEmptyPlace1 == false) && (isEmptyPlace2 == false)) wait(); // 두 방이 모두 꽉차있다면 기다린다. + // 두 방중 하나 이상이 비어있으면 탈출한다. + + if (isEmptyPlace1) { // 첫번쨰 방이 비어있으면 첫번쨰 방으로 들어가고 + voter1 = voter; + isEmptyPlace1 = false; + System.out.println("투표소1 : " + voter1 + "님이 투표중 입니다."); + } else if (isEmptyPlace2) { // 두번쨰 방이 비어있으면 두번쨰 방으로 들어간다. + voter2 = voter; + isEmptyPlace2 = false; + System.out.println("투표소2 : " + voter2 + "님이 투표중 입니다."); + } + } + + public synchronized void voteDone(String voter) { + // 방에서 나온다. + if (voter.equals(voter1)) { + voter1 = null; + isEmptyPlace1 = true; + System.out.println("투표소1 : " + voter + "투표 완료. 현재 비어있음"); + } else if (voter.equals(voter2)) { + voter2 = null; + isEmptyPlace2 = true; + System.out.println("투표소2 : " + voter + "투표 완료. 현재 비어있음"); + } + + // 대기중인 스레드를 깨운다. + notifyAll(); + } +} +``` + +여기서 가장 중요한건 `wait()`과 `notifyAll()` 메서드 모두 synchronized 처리되어 있는 메서드 내부에서 호출되어야한다는 것이다. + +그리고 `notify()`라는 메서드도 있는데, notifyAll() 은 잠자고 있는 모든 스레드를 깨우고 notify()는 하나의 스레드만 깨운다. 여기서는 둘중 어느것을 써도 결과는 똑같다. + +```java +public class VoteThread implements Runnable { + VotingPlace votingPlace; + String voter; + + public VoteThread(VotingPlace votingPlace, String voter) { + this.votingPlace = votingPlace; + this.voter = voter; + } + + @Override + public void run() { + try { + votingPlace.vote(voter); + Thread.sleep(3000); + votingPlace.voteDone(voter); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} +``` + +아래 코드를 보면 투표소가 두개 있고, 해당 투표소에서 투표를 하는 사람은 모두 10명이다. + +```java +public class Vote { + public static void main (String[] args) { + VotingPlace votingPlace = new VotingPlace(); + for(int i=1; i<= 10; i++) { + Thread thread = new Thread(new VoteThread(votingPlace, "투표자"+i)); + thread.start(); + } + } +} +``` + +여기서 투표소가 빈 곳이 하나도 없으면 기다리다가 하나라도 비었다고 알려주면 대기하던 사람 하나가 해당 투표소에서 투표를 할 수 있게 된다ㅏ. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/record.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/record.md" new file mode 100644 index 00000000..2a6290c0 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/record.md" @@ -0,0 +1,185 @@ +--- +title: 'record' +lastUpdated: '2024-03-02' +--- + +record란? +- 불변(immutable) 데이터 객체를 쉽게 생성할 수 있도록 하는 새로운 유형의 클래스이다. +- JDK14에서 preview로 등장하여 JDK16에서 정식 스펙으로 포함되었다. +- 묵시적으로 추상 클래스인 `Record`를 상속받는다. + +## 기존의 불변 데이터 객체 + +```java +public class Person { + private final String name; + private final int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } +} +``` + +상태(name, age)를 보유하는 불변 객체를 생성하기 위해 많은 코드를 작성해야했다. + +- 모든 필드에 final을 사용하여 명시적으로 정의 +- 필드 값을 모두 포함한 생성자 +- 모든 필드에 대한 접근자 메서드(getter) +- 상속을 방지하기 위해 클래스 자체를 final로 선언하기도함 +- 로깅 출력을 제공하기 위한 `toString()` 재정의 +- 두 개의 인스턴스를 비교하기 위한 `equals()`, `hashCode()` 재정의 + +하지만 레코드 클래스를 사용하면 훨씬 간결한 방식으로 동일한 불변 데이터 객체를 정의할 수 있다. + +```java +public record Book(String title, String author, String isbn) { } +``` + +```java +이름(Person), 헤더(String name, int age), 바디({}) +record 레코드명(컴포넌트1, 컴포넌트2, ...) { } +``` + +컴파일러는 헤더를 통해 내부 필드를 추론한다. + +record class에 대해서는 생성자를 작성하지 않아도 되고, `toString()`, `equals()`, `hashCode()` 메소드에 대한 구현을 자동으로 제공한다. + +## 일반 class와의 비교 + +일반 class와의 차이점은 아래와 같은 것들이 있다. + +- 다른 클래스를 상속받을 수 없다. +- 레코드에는 인스턴스 필드를 선언할 수 없다. 다르게 말하면 정적 필드는 가능하다. +- 레코드를 abstract로 선언할 수 없으며 암시적으로 final로 선언된다. +- 레코드의 컴포넌트는 암시적으로 final로 선언된다. + +클래스와 비슷한 점을 나열하면 다음과 같다. + +- 클래스 내에서 레코드를 선언할 수 있다. 중첩된 레코드는 암시적으로 static으로 선언된다. +- 제네릭 레코드를 만들 수 있다. +- 레코드는 클래스처럼 인터페이스를 구현할 수 있다. +- new 키워드를 사용하여 레코드를 인스턴스화할 수 있다. +- 레코드의 본문(body)에는 정적 필드, 정적 메서드, 정적 이니셜라이저, 생성자, 인스턴스 메서드, 중첩 타입(클래스, 인터페이스, 열거형 등)을 선언할 수 있다. +- 레코드나 레코드의 각 컴포넌트에 어노테이션을 달 수 있다. + +## 코드 비교 + +Book 레코드를 class와 바꾸면 아래와 같이 될 것 이다. + +```java +// 레코드 +public record Book(String title, String author, String isbn) { } +``` + +```java +// 클래스 +// 암시적으로 추상 클래스인 java.lang.Record를 상속받는다. +public final class Book extends java.lang.Record { + // 레코드의 각 컴포넌트는 내부에서 private final인 인스턴스 필드로 선언된다. + private final String title; + private final String author; + private final String isbn; + + // 레코드 내부에서 표준 생성자(canonical constructor)가 만들어진다. + // 암시적으로 선언된 표준 생성자의 접근 제어자는 레코드의 접근 제어자와 동일하다. + public Book(String title, String author, String isbn) { + super(); + this.title = title; + this.author = author; + this.isbn = isbn; + } + + // 기본 구현 toString(), hashCode(), equals()은 원하면 변경할 수 있다. + @Override + public final String toString() { + // 내부 구현의 정확한 문자열 포맷은 향후 변경될 수도 있다. + return "Book[" + this.title + ", " + this.author + ", " + this.isbn + "]"; + } + + // 암시적 구현은 동일한 컴포넌트로부터 생성된 두 레코드는 해시 코드가 동일해야 한다. + @Override + public final int hashCode() { + // 구현에 사용되는 정확한 알고리즘은 정해지지 않았으며 향후 변경될 수 있다. + int result = title == null ? 0 : title.hashCode(); + result = 31 * result + (author == null ? 0 : author.hashCode()); + result = 31 * result + (isbn == null ? 0 : isbn.hashCode()); + return result; + } + + // 암시적 구현은 두 레코드의 모든 컴포넌트가 서로 동일하면 true를 반환한다. + @Override + public final boolean equals(Object o) { + // 구현에 사용되는 정확한 알고리즘은 정해지지 않았으며 향후 변경될 수 있다. + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Book book = (Book) o; + return Objects.equals(title, book.title) && Objects.equals(author, book.author) && Objects.equals(isbn, book.isbn); + } + + // 컴포넌트명과 동일한 게터(getter)가 선언된다. + public String title() { + return this.title; + } + + public String author() { + return this.author; + } + + public String isbn() { + return this.isbn; + } +} +``` + +## 컴팩트 생성자(Compact Constructor) + +만약 별도의 초기화 로직이 필요하다면 레코드 안에 표준 생성자를 만들 수도 있다. + +```java +public record Book(String title, String author, String isbn) { + // 물론 이렇게 다른 생성자를 추가할 수도 있다. + public Book(String title, String isbn) { + this(title, "Unknown", isbn); + } + + public Book(String title, String author, String isbn) { + // 조금 더 복잡한 초기화 로직 ... + } +} +``` + +여기서 이러한 표준 생성자 말고도 컴팩트 생성자를 사용할 수도 있다. 아래와 같이 생성자 매개변수를 받는 부분이 사라진 형태이다. 개발자가 일일이 명시적으로 인스턴스 필드를 초기화하지 않아도 컴팩트 생성자의 마지막에 초기화 구문이 자동으로 삽입된다. 그리고 표준 생성자와는 달리 컴팩트 생성자 내부에서는 인스턴스 필드에 접근을 할 수가 없으며, 접근하려고 하면 "final 변수 'x'에 값을 할당할 수 없습니다."와 같은 에러 메시지를 볼 수 있다. + +```java +public record Book(String title, String author, String isbn) { + // public Book(String title, String author, String isbn) { ... }과 동일 + public Book { + Objects.requireNonNull(title); + Objects.requireNonNull(author); + Objects.requireNonNull(isbn); + // this.title = title; + // this.author = author; + // this.isbn = isbn; + } + + // 여전히 아래와 같이 표준 생성자와 컴팩트 생성자를 혼용해서 쓸 수 있다. + public Book(String title, String isbn) { + this(title, "Unknown", isbn); + } +} +``` + +--- +참고 +- https://openjdk.java.net/jeps/359 + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/\354\230\210\354\231\270\354\231\200\342\200\205\354\227\220\353\237\254.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/\354\230\210\354\231\270\354\231\200\342\200\205\354\227\220\353\237\254.md" new file mode 100644 index 00000000..28792ea7 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/\354\230\210\354\231\270\354\231\200\342\200\205\354\227\220\353\237\254.md" @@ -0,0 +1,99 @@ +--- +title: '예외와 에러' +lastUpdated: '2024-03-02' +--- + +프로그래밍에서 **예외(Exception)**란 입력 값에 대한 처리가 불가능하거나, 프로그램 실행 중에 참조된 값이 잘못된 경우 등 정상적인 프로그램의 흐름을 어긋나는 것을 말한다. 그리고 자바에서 예외는 개발자가 직접 처리할 수 있기 때문에 예외 상황을 미리 예측하여 핸들링할 수 있다. + +한편, **에러(Error)**는 시스템에 무엇인가 비정상적인 상황이 발생한 경우에 사용된다. 주로 자바 가상머신에서 발생시키는 것이며 예외와 반대로 이를 애플리케이션 코드에서 잡으려고 하면 안 된다. 에러의 예로는 `OutOfMemoryError`, `ThreadDeath`, `StackOverflowError` 등이 있다. + +## Checked Exception과 Unchecked Exception + +image + +Exception은 다시 Checked Exception과 Unchecked Exception으로 구분할 수 있는데, RuntimeException을 상속하지 않는 클래스는 Checked Exception, 반대로 상속한 클래스는 Unchecked Exception으로 분류할 수 있다. Checked Exception은 반드시 try ~ catch로 예외를 잡거나 throw로 호출한 메소드에게 예외를 던져야 한다. 반면 Unchecked Exception은 명시적인 예외 처리를 강제하지 않는다. + +image + +## 예외 처리 + +예외를 처리하는 방법에는 예외 복구, 예외 처리 회피, 예외 전환이 있다. + +### 예외 복구 + +- 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 방법 +- 예외를 잡아서 일정 시간, 조건만큼 대기하고 다시 재시도를 반복한다. +- 최대 재시도 횟수를 넘기게 되는 경우 예외를 발생시킨다. + +```java +final int MAX_RETRY = 100; +public Object someMethod() { + int maxRetry = MAX_RETRY; + while(maxRetry > 0) { + try { + ... + } catch(SomeException e) { + // 로그 출력. 정해진 시간만큼 대기한다. + } finally { + // 리소스 반납 및 정리 작업 + } + } + // 최대 재시도 횟수를 넘기면 직접 예외를 발생시킨다. + throw new RetryFailedException(); +} +``` + +### 예외처리 회피 + +- 예외 처리를 직접 담당하지 않고 호출한 쪽으로 던져 회피하는 방법 +- 그래도 예외 처리의 필요성이 있다면 어느 정도는 처리하고 던지는 것이 좋다. + +```java +// 예시 1 +public void add() throws SQLException { + // ...생략 +} + +// 예시 2 +public void add() throws SQLException { + try { + // ... 생략 + } catch(SQLException e) { + // 로그를 출력하고 다시 날린다! + throw e; + } +} +``` + +### 예외 전환 + +- 예외 회피와 비슷하게 메서드 밖으로 예외를 던지지만, 그냥 던지지 않고 적절한 예외로 전환해서 넘기는 방법 +- 조금 더 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경한다. +- 예외 처리를 단순하게 만들기 위해 포장(wrap) 할 수도 있다. + +```java +// 조금 더 명확한 예외로 던진다. +public void add(User user) throws DuplicateUserIdException, SQLException { + try { + // ...생략 + } catch(SQLException e) { + if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) { + throw DuplicateUserIdException(); + } + else throw e; + } +} + +// 예외를 단순하게 포장한다. +public void someMethod() { + try { + // ...생략 + } catch(NamingException ne) { + throw new EJBException(ne); + } catch(SQLException se) { + throw new EJBException(se); + } catch(RemoteException re) { + throw new EJBException(re); + } +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/\354\240\234\353\204\244\353\246\255\352\263\274\342\200\205variance.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/\354\240\234\353\204\244\353\246\255\352\263\274\342\200\205variance.md" new file mode 100644 index 00000000..05df4fd6 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Java/\354\240\234\353\204\244\353\246\255\352\263\274\342\200\205variance.md" @@ -0,0 +1,99 @@ +--- +title: '제네릭과 variance' +lastUpdated: '2024-03-02' +--- + +코틀린에서 제네릭과 variance를 다루는 방법에 대해 알아보기 전에, 제네릭과 variance의 정의가 무엇인지 먼저 살펴보자. + +# 제네릭 + +프로그래밍 언어들에서 제공해주는 기능 중 하나인 `제네릭`은 클래스나 인터페이스 혹은 함수 등에서 동일한 코드로 여러 타입을 지원해주기 위해 존재한다. 한가지 타입에 대한 템플릿이 아니라 **여러가지 타입**을 사용할 수 있는 클래스와 같은 코드를 간단하게 작성할 수 있다. + +## 예시 + +```kotlin +class Wrapper(var value: T) + +fun main(vararg args: String) { + val intWrapper = Wrapper(1) + val strWrapper = Wrapper("1") + val doubleWrapper: Wrapper = Wrapper(0.1) +} +``` + +`Wrapper`라는 클래스는 꺽쇠안에 `T`라는 형식 인자(Type paraameter)를 가진다. 그곳에는 `Int`, `String`, `Double`등 여러 형식이 저장될 수 있다. + +```kotlin +fun > greaterThan(lhs: T, rhs: T): Boolean { + return lhs > rhs +} +``` + +함수에 제네릭을 적용한 예시이다. `Comparable` 을 구현한 형식 인자만 `> `연산자를 사용할 수 있기 때문에 꺽쇠 안의 T의 선언에 `Comparable` 를 구현했다는 것을 표시해주었습니다. + +흔하게 사용되는 Kotlin의 컬렉션들인 `List`, `MutableList`, `Set`, `MutableSet`, `Map`등도 초기화될 때 제네릭으로 타입을 넣는다. 혹은 타입 추론이 될 수도 있다. (ex: listOf(1,2)가 자동으로 List로 추론됨) + +# Invariance + +제네릭을 사용할 때 가장 헷갈리는 부분은 variance이다. 자바에선 와일드 카드(Wild card)라고 불리는 기능과 비슷하다. + +자바에서 `String`은 `Object`의 subType이다. 그러나 `List`은 `List`의 subType이 아니다. + +```kotlin +val strs: MutableList = mutableListOf() + +//val objs: MutableList = strs 에러발생 +``` + +위 코드는 실제 코틀린으로 작성을 하면 2번째 줄에서 컴파일 에러가 발생한다. 만약 `MutableList` 이 `MutableList`의 subType이면 2번째 줄이 에러가 발생하지 않아야 한다. + +이렇게 형식 인자들끼리는 sub type 관계를 만족하더라도 제네릭을 사용하는 클래스와 인터페이스에서는 subType 관계가 유지되지 않는 것이 **Invariance(불공변)**이다. 기본적으로 코틀린의 모든 제네릭에서의 형식 인자는 Invariance이 된다. + +## Invariance의 한계 + +Invariance는 컴파일 타임 에러를 잡아주고 런타임에 에러를 내지않는 안전한 방법이다. 그러나, 안전하다고 보장된 상황에서도 컴파일 에러를 내 개발자를 불편하게 할 수도 있다. + +```java +interface Collection ... { + void addAll(Collection items); +} +void copyAll(Collection to, Collection from) { + to.addAll(from); + //addAll은 Invariance여서 to의 addAll에 from을 전달할 수 없다! +} +``` + +to는 `Collection`고 from은 `Collection`이다. String을 Object로 취급하여, `String`만 사용할 수 있는 메서드나 속성을 사용하지 않을 것이라면 이 코드는 문제될게 없다. 하지만 이 코드는 컴파일 에러가 발생한다. + +## Java Wildcard, Covariance, Contravariance + +이를 해결하기위해 자바에서는 Wildcard가 등장한다. 제네릭 형식 인자 선언에 `? extends E`와 같은 문법을 통해 `E`나 `E`의 subType의 제네릭 형식을 전달받아 사용할 수 있다. + +```java +interface Collection ... { + void addAll(Collection items); +} +``` + +만약 Collection의 코드가 이런 형식으로 되어있다고 가정해보자. + +items엔 E일수도 있고 E의 sub type일 수도 있는 아이템들이 들어있을 것이다. 여기서 어떤 아이템을 꺼내든(읽든) 그것은 E라는 형식안에 담길 수 있다. + +그러나 `items`에 어떤 값을 추가하려면 `items`의 형식 인자인 ?가 어떤 것인지를 알아야한다. 하지만 그 인자가 무엇인지 정확히 알 수 없기 떄문에 값을 추가할 수 없다. + +예를 들어 `items`가 `Collection`라면 `items`에서 우리는 어떤 아이템을 꺼내서 그것을 `Object` 타입 안에 담을 수 있다. 그러나 `Object`를 `items`에 넣을 수는 없다. 왜냐하면 **전달된 `items`가 `Collection`일 수도 있고 `Collection`일 수도 있기 때문**이다. + +읽기만 가능하고 쓰기는 불가능한 `? extends E 는` 코틀린에서의 out과 비슷한 의미로 사용되고 이런 것들을 **covariance(공변)**이라 부른다. + +반대로 읽기는 불가능하고 쓰기만 가능한 자바에선 `? super E` 로 사용되고 코틀린에선 `in` 으로 사용되는 **contravariance(반공변)**이 있다. + +**contravariance**에서는 `E`와 같거나 `E의 상위타입`만 `?` 자리에 들어올 수 있다. items이 `Collection` 라면, `items`에서 어떤 변수를 꺼내도 `E`에 담을 수 있을 지 보장할 수 없지만(읽기 불가) `E`의 상위 타입 아무거나 `items`에 넣을 수 있기 때문에 **covariance와 반대**된다고 생가하면 된다. + +## 정리 + +|정리|설명|java|kotlin| +|-|-|-|-| +|Invariance(불공변)|제네릭으로 선언한 타입과 일치하는 클래스만 삽입할 수 있다.|Collection|Collection| +|Covariance(공변)|**특정 클래스를 상속받은 하위클래스들**을 리스트로 선언할 수 있다. 하지만 해당 리스트의 타입을 특정할 수 없기 때문에 **Write가 불가능**하다.|`Collection`|`out`| +|Contravariance(반공변)|**특정 클래스의 상위 클래스들**을 리스트로 선언할 수 있다. 하지만 꺼낼 인스턴스가 어떤 타입인지 알 수 없기 때문에 Read가 불가능하다.|`Collection`|`in`| + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Iterator.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Iterator.md" new file mode 100644 index 00000000..c2ac45ab --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Iterator.md" @@ -0,0 +1,95 @@ +--- +title: 'Iterator' +lastUpdated: '2023-12-30' +--- +```js +const curry = f => + (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._); + +const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a); + +const go = (...args) => reduce((a, f) => f(a), args); + +const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs); +``` + +## 병렬 평가 + +```js +const L = {}; + +L.range = function* (l) { + let i = -1; + while (++i < l) yield i; +}; + +L.map = curry(function* (f, iter) { + for (const a of iter) { + yield go1(a, f); + } +}); + +const nop = Symbol('nop'); + +L.filter = curry(function* (f, iter) { + for (const a of iter) { + const b = go1(a, f); + if (b instanceof Promise) yield b.then(b => b ? a : Promise.reject(nop)); + else if (b) yield a; + } +}); + +L.entries = function* (obj) { + for (const k in obj) yield [k, obj[k]]; +}; + +L.flatten = function* (iter) { + for (const a of iter) { + if (isIterable(a)) yield* a; + else yield a; + } +}; + +L.deepFlat = function* f(iter) { + for (const a of iter) { + if (isIterable(a)) yield* f(a); + else yield a; + } +}; + +L.flatMap = curry(pipe(L.map, L.flatten)); + +const map = curry(pipe(L.map, takeAll)); +const filter = curry(pipe(L.filter, takeAll)); +const find = curry((f, iter) => go( + iter, + L.filter(f), + take(1), + ([a]) => a)); +const flatten = pipe(L.flatten, takeAll); +const flatMap = curry(pipe(L.map, flatten)); +``` + +## 엄격한 평가 + +```js +const C = {}; + +function noop() { +} + +const catchNoop = ([...arr]) => + (arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr); + +C.reduce = curry((f, acc, iter) => iter ? + reduce(f, acc, catchNoop(iter)) : + reduce(f, catchNoop(acc))); +C.take = curry((l, iter) => take(l, catchNoop(iter))); +C.takeAll = C.take(Infinity); +C.map = curry(pipe(L.map, C.takeAll)); +C.filter = curry(pipe(L.filter, C.takeAll)); +``` + +--- +참고 +- https://github.com/indongyoo/functional-javascript-01/blob/master/11.%20%EB%B9%84%EB%8F%99%EA%B8%B0%20%EB%8F%99%EC%8B%9C%EC%84%B1%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%203/1/lib/fx.js \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Lexical\342\200\205Scope\354\231\200\342\200\205Closure.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Lexical\342\200\205Scope\354\231\200\342\200\205Closure.md" new file mode 100644 index 00000000..f08a39c2 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Lexical\342\200\205Scope\354\231\200\342\200\205Closure.md" @@ -0,0 +1,70 @@ +--- +title: 'Lexical Scope와 Closure' +lastUpdated: '2024-03-02' +--- + +### Lexical scope + +프로그래밍에서 scope란 변수의 유효범위를 나타내는 용어이다. JavaScript는 **함수를 어디서 선언하였는지에 따라 상위스코프를 결정**하는 Lexical Scope를 따르고 있다. 다른 말로, 정적 스코프(Static scope)라 부르기도 한다. + +```js +var num = 1; + +function a() { + var num = 10; + b(); +} + +function b() { + console.log(num); +} + +a(); +``` + +위 예제를 살펴보자. a 함수 내에서 호출하는 b 함수에서 참조하는 num은, 전역의 num을 참조하는 것이기 때문에 1이 출력된다. + +### Closure + +클로저는 주변 상태(어휘적 환경)에 대한 참조와 함께 묶인 함수의 조합이다. 즉, 클로저는 내부 함수에서 외부 함수의 범위에 대한 접근을 제공한다. JavaScript에서 클로저는 함수 생성 시 함수가 생성될 때마다 생성된다. + + +```js +function makeFunc() { + const name = "Mozilla"; + function displayName() { + console.log(name); + } + return displayName; +} + +const myFunc = makeFunc(); +myFunc(); +``` + +이 코드는 JS에서 정상적으로 동작한다. 어찌보면 당연하게 생각될 수도 있지만, 몇몇 프로그래밍 언어에서, 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재하기도 한다. 그러나 JS는 클로저를 형성하기 때문에 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수에 접근할 수 있게 되는 것이다. + +클로저를 이용하면 아래와 같은 코드를 작성할 수도 있다. + +```js +function makeAdder(x) { + return function (y) { + return x + y; + }; +} + +const add5 = makeAdder(5); +const add10 = makeAdder(10); + +console.log(add5(2)); // 7 +console.log(add10(2)); // 12 +``` + +x를 받아서 새 함수를 반환하는 함수 `makeAdder(x)`를 정의하고, 그 함수를 사용해 각각 5, 10을 더하는 함수를 만들어 활용하는 코드이다. + +`add5`와 `add10`은 둘 다 클로저이다. 둘은 같은 함수 본문 정의를 공유하지만, 서로 다른 맥락(어휘)적 환경을 저장한다. 함수 실행 시 `add5`의 맥락에서, 클로저 내부의 x는 5 이지만, `add10`의 맥락에서 x는 10이 된다. + +--- +참고 +- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures +- https://poiemaweb.com/js-closure diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Promise.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Promise.md" new file mode 100644 index 00000000..94b2ec51 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/Promise.md" @@ -0,0 +1,50 @@ +--- +title: 'Promise' +lastUpdated: '2024-03-02' +--- + +- Kleisli composition은 함수형 프로그래밍에서 두 개의 Kleisli 함수를 조합하는 기술을 가리킨다. +- Kleisli 함수는 모나드를 사용하는 함수로, 입력을 받아 모나드 값을 반환한다. +- 이러한 함수를 조합하여 모나드 내의 값을 연속적으로 처리하거나 변환하는 작업을 가능하게 하는 것이 바로 Kleisli composition이다. + +```js +// 모나드 M의 Kleisli 함수 정의 +kleisliFuncA :: A -> M +kleisliFuncB :: B -> M + +// Kleisli 함수 조합 +composedKleisliFunc :: A -> M +composedKleisliFunc = kleisliFuncA >=> kleisliFuncB +``` + +- Promises는 **kleisli composition을 지원하는 도구**이다. + +- kleisli composition을 지원한다는 것은 함수 합성을 언제나 수학적으로 안전하게 할 수 있다는 의미이다. + +- 예를 들어 f,g 함수를 합성할 경우 `f(g(x)) = f(g(x))` 식을 수학적으로 바라볼 경우 항상 성립되어야 한다. + - 해당 식이 성립되지 않는 요인이 있다면 순수한 함수형 프로그래밍을 가능하게 (수학적으로 프로그래밍을 바라볼 수 있도록) 보장해주지 못하는 것이다. + +- kleisli composition을 지원해주기 위해서는 에러 처리를 해주어서 에러가 발생하더라도 `f(g(x)) = f(g(x))` 라는 식이 성립되도록 해야 한다. + +- Promise는 reject와 catch를 통해 언제나 `f(g(x)) = f(g(x))` 라는 식이 성립되도록 에러 처리를 해줄 수 있기 때문에 kleisli composition을 지원한다고 할 수 있다. + +```js +const user = [ + {id:1, name: 'aa'}, + {id:2, name: 'bb'}, +]; + +const getUserById = id => find(u=>u.id===id,user) || promise.reject('없어요!'); +const ({name}) => name; +const g = getUserById; + +const fg = id => f(g(id)); +const fg = id => promise.resolve(id).then(g).then(f).catch(a=>a); + +fg(3).then(log); +``` + +--- +참고 +- https://www.inflearn.com/course/functional-es6/dashboard +- https://github.com/indongyoo/functional-javascript-01/tree/master/09.%20%EB%B9%84%EB%8F%99%EA%B8%B0%20%EB%8F%99%EC%8B%9C%EC%84%B1%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/1 diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/useEffect\342\200\205\354\225\210\354\227\220\354\204\234\342\200\205setInterval\342\200\205\354\202\254\354\232\251\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/useEffect\342\200\205\354\225\210\354\227\220\354\204\234\342\200\205setInterval\342\200\205\354\202\254\354\232\251\355\225\230\352\270\260.md" new file mode 100644 index 00000000..cda6e162 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/useEffect\342\200\205\354\225\210\354\227\220\354\204\234\342\200\205setInterval\342\200\205\354\202\254\354\232\251\355\225\230\352\270\260.md" @@ -0,0 +1,108 @@ +--- +title: 'useEffect 안에서 setInterval 사용하기' +lastUpdated: '2024-03-02' +--- + +- setInterval은 일정 시간마다 작업을 수행하는 메서드다. +- 주기적으로 카운트를 증가시키기 위해 이런 코드를 작성할 수 있다. + +```js +function Counter() { + let [count, setCount] = useState(0); + + useEffect(() => { + let id = setInterval(() => { + setCount(count + 1); + }, 1000); + return () => clearInterval(id); + }, []); + + return

{count}

; +} + +const rootElement = document.getElementById("root"); +ReactDOM.render(, rootElement); +``` + +- 1초마다 +1이 되어야 할 것 같지만, 이 코드에서 count는 계속 1에서 머문다. +- 이렇게 되는 이유는 `useEffect`가 첫 render에 count를 capture하기 때문이다. + - `setInterval` 메소드도 브라우저에서 제공하는 Web API 중 하나이기 때문에 호출되면 바로 실행되지 않고 등록한 delay 시간을 기다렸다가 Callback Queue에 쌓인다. 그리고 Call Stack이 비면 그때 실행된다. 그리고 실행된 `setInterval`은 한 번 호출된 후에 바로 종료된다. 이제부터 `setInterval`이 주기적으로 실행하라고 남겨놓은 `setCount` 함수가 주기적으로 실행된다. + - Closure에서 외부 함수가 종료되면 내부 함수가 그 함수의 값을 기억하는 특성이 있어 문제가 발생한다. + - `setInterval`은 종료되었지만, `setInterval`의 내부 함수인 setCount가 실행될 때마다 그 초기값이었던 0을 기억하고 계속 +1을 하는 것이다. 다시 실행될 때에도 마찬가지다. setCount가 기억하는 count는 계속 0이기때문에, 값이 계속 1이 된다. + +### 해결 방법 + +- count가 우리가 원하는 대로 동작하려면, count가 0에서 1이 되고, 1이 된 것을 기억했다가 다시 +1을 해서 2가 되어야 한다. 즉, 바뀌기 전의 state를 기억해야한다. +- 이를 해결하기 위한 다양한 방법들이 있지만, 가장 쉬운 방법은 `setState`에 callback 함수를 넘겨주는 것이다. `setState`에서는 이전의 state를 보장할 수 있는 방법을 제공하고 있다. `setState`에 callback 함수를 넘겨주면 해결된다. 즉, `setCount((previousCount) => previousCount + 1)`을 하게 되면 문제가 해결된다. + +- 이런 방법을 통해 문제를 당장 해결하더라도 `setInterval`을 React에서 사용하는 것이 불편한 이유가 한가지 존재한다. react의 lifecycle과 다소 벗어난 행동을 한다는 것이다. state가 바뀌면 React는 리렌더링을 하게 되는데, `setInterval`은 렌더와 관계없이 계속 살아남아있는다. +- React는 리렌더링을 하면서 이전의 render된 내용들을 다 잊고 새로 그리게 되는데, `setInterval`은 그렇지 않다. Timer를 새로 설정하지 않는 이상 계속 이전의 내용(props나 state)들을 기억하고 있다. +- 이런 문제점을 해결하기 위해 아래와 같은 코드를 사용할 수 있다. + +```js +import { useState, useEffect, useRef } from 'react'; + +function useInterval(callback, delay) { + const savedCallback = useRef(); // 최근에 들어온 callback을 저장할 ref를 하나 만든다. + + useEffect(() => { + savedCallback.current = callback; // callback이 바뀔 때마다 ref를 업데이트 해준다. + }, [callback]); + + useEffect(() => { + function tick() { + savedCallback.current(); // tick이 실행되면 callback 함수를 실행시킨다. + } + if (delay !== null) { // 만약 delay가 null이 아니라면 + let id = setInterval(tick, delay); // delay에 맞추어 interval을 새로 실행시킨다. + return () => clearInterval(id); // unmount될 때 clearInterval을 해준다. + } + }, [delay]); // delay가 바뀔 때마다 새로 실행된다. +} +``` + +- 이 Hook은 interval을 set하고 unmount 되기 전에 clearInterval을 해준다. useRef를 사용해 setInterval이 React의 Lifecycle과 함께 동작하도록 만들어주었다. + +### savedCallback을 저장할 때에 state 대신 ref를 사용한 이유 + +- `useRef`와 `useState`의 가장 큰 차이점은 리렌더링의 여부이다. +- `setState`로 State의 값을 바꾸어주면 함수가 새로 실행되면서 리렌더링이 일어난다. 반면 `ref.current`에는 새로운 값을 넣어주더라도 리렌더링이 일어나지 않는다. +- 만약 위의 코드에서 아래의 예시 코드처럼 `useState`을 사용했다면, `useEffect` 안에서 `savedCallback`을 새로 set할 때 리렌더링이 일어나면서 아래에 주석으로 남겨놓은 문제점이 일어나게된다. + +```js +import { useEffect, useState } from 'react'; + +const useInterval = (callback, delay) => { + const [savedCallback, setSavedCallback] = useState(null) // useState사용 + + // callback이 바뀔 때마다 실행 + // 첫 실행에 callback이 한 번 들어옴 -> 리렌더링 -> 다시 들어옴 -> 리렌더링 -> .. 무한 반복 + // 원래의 의도는 callback이 새로 들어오면 그 callback을 저장해두고 아래의 setInterval을 다시 실행해주려는 의도 + useEffect(() => { + setSavedCallback(callback); + }, [callback]); + + // mount가 끝나고 1번 일어남 + // 맨 처음 mount가 끝나고 savedCallback은 null이기 때문에 setInterval의 executeCallback이 제대로 실행되지 않음 (null이기 때문에) + useEffect(() => { + console.log(savedCallback()); + const executeCallback = () => { + savedCallback(); + }; + + const timerId = setInterval(executeCallback, delay); + + return () => clearInterval(timerId); + }, []); +}; + +export default useInterval; +``` + +- 따라서 값이 바뀌어도 리렌더링이 일어나지 않는 Ref를 사용해야 제대로 동작한다. + +--- +참고 +- https://ko.javascript.info/settimeout-setinterval +- https://overreacted.io/making-setinterval-declarative-with-react-hooks/ +- https://youtu.be/wcxWlyps4Vg \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/\355\224\204\353\241\234\355\206\240\355\203\200\354\236\205.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/\355\224\204\353\241\234\355\206\240\355\203\200\354\236\205.md" new file mode 100644 index 00000000..83171779 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/\355\224\204\353\241\234\355\206\240\355\203\200\354\236\205.md" @@ -0,0 +1,40 @@ +--- +title: '프로토타입' +lastUpdated: '2024-03-02' +--- + +JavaScript에는 객체라는 구조가 존재하고, 각 객체에는 다른 객체에 대한 링크를 보유하는 프로토타입이라는 비공개 속성이 있다. 그 프로토 타입 객체도 자신만의 프로토타입을 가지고 있으며, 프로토타입으로 `null`을 가진 객체에 도달할 때까지 이 연결은 계속된다. + +image + +### 예시 코드 + +닭의 프로토타입을 참새로 지정하면, 닭의 `날개갯수`와 `날수있나` 값을 참조했을 때 참새가 가지고 있던 값과 동일한 값이 반환된다. 이떄 `날수있나` 값을 `false`로 재지정하면 값이 바뀌게 된다. + +```js +function 참새(){ + this.날개갯수 = 2; + this.날수있나 = true; +} +const 참새1 = new 참새(); +console.log("참새의 날개 갯수 : ", 참새1.날개갯수); // 2 + +function 닭() { + this.벼슬 = true; +} +닭.prototype = 참새1; // reference (인스턴스를 프로토타입으로 등록) +const 닭1 = new 닭(); +console.log("닭1 날개 : ", 닭1.날개갯수, ", 날수있나? ", 닭1.날수있나); // 2, true +닭1.날수있나 = false; +console.log("다시 물어본다. 닭1은 날 수 있나? :", 닭1.날수있나); // false +``` + +위 코드는 아래처럼 도식화할 수 있다. + + + + +--- +참고 +- https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42 +- https://developer.mozilla.org/ko/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#different_ways_of_creating_and_mutating_prototype_chains \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/\355\231\224\354\202\264\355\221\234\342\200\205\355\225\250\354\210\230.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/\355\231\224\354\202\264\355\221\234\342\200\205\355\225\250\354\210\230.md" new file mode 100644 index 00000000..6f9419b3 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/JavaScript/\355\231\224\354\202\264\355\221\234\342\200\205\355\225\250\354\210\230.md" @@ -0,0 +1,117 @@ +--- +title: '화살표 함수' +lastUpdated: '2024-03-02' +--- + +ES6가 도입되면서, js에 화살표 함수라는 문법이 등장했다. 화살표 함수는 말 그대로 화살표(`=>`) 표시를 사용해서 함수를 선언하는 방법이다. + +```js +// 기존 함수 +const foo = function () { + console.log('기존 함수'); +} + +// 화살표 함수 +const foo = () => console.log('화살표 함수'); +``` + +두 코드는 기능적으로 완전히 동일하게 작동한다. 그러나, 두 코드는 this 바인딩에서 차이가 있다. + +## this 바인딩 + +JS의 this는 상황에 따라 다르게 바인딩 된다. 대표적으로 this에 바인딩되는 값들은 이렇다. + +``` +전역 공간의 this : 전역 객체 +메소드 호출 시 메소드 내부의 this : 해당 메소드를 호출한 객체 +함수 호출 시 함수 내부의 this : 지정되지 않음 +``` + +여기서 자세히 살펴봐야하는 것은 함수에 대한 부분이다. 함수를 호출했을 때 그 함수 내부의 this는 지정되지 않는다. 그리고 this가 지정되지 않은 경우, this는 자동으로 전역 객체를 바라보기 때문에 함수를 호출하면 함수 내부에서의 this는 전역 객체가 된다고 정리할 수 있다. + +즉, 그냥 함수를 호출한다면 다음과 같은 상황이 연출된다. + +```js +const cat = { + name: 'meow', + foo1: function() { + const foo2 = function() { + console.log(this.name); + } + foo2(); + } +}; + +cat.foo1(); // undefined +``` + +- `cat.foo1()` 메소드 호출 시 내부 함수 `foo2`가 실행됨 +- 함수가 호출됐으므로 `foo2` 내부의 this는 지정되지 않아서 곧 전역 객체를 가리킴 +- 전역 객체에 `name`이란 속성은 존재하지 않으므로 undefined가 뜸 + +그러나 화살표 함수는 this로 지정된 값이 없기 때문에 선언될 시점에서의 상위 스코프가 this로 바인딩된다. 그렇기 때문에 화살표 함수를 사용하면 this가 cat 객체를 가리키도록 할 수 있다. + +```js +const cat = { + name: 'meow', + foo1: function() { + const foo2 = () => { + console.log(this.name); + } + foo2(); + } +}; + +cat.foo1(); // meow +``` + +## 화살표 함수를 사용하면 안되는 경우 + +화살표 함수도 사용해선 안되는 때가 있다. 상위 환경의 this를 참조한다는 점이 문제가 될 수도 있다. 바로 다음과 같은 경우이다. + +### 1. 메소드 + +```js +const cat = { + name: 'meow'; + callName: () => console.log(this.name); +} + +cat.callName(); // undefined +``` + +이 같은 경우, callName 메소드의 this는 자신을 호출한 객체 cat이 아니라 함수 선언 시점의 상위 스코프인 전역객체를 가리키게 된다. + +### 2. 생성자 함수 + +```js +const Foo = () => {}; +const foo = new Foo() // TypeError: Foo is not a constructor +``` + +화살표 함수는 생성자 함수로 사용할 수 없게 만들어졌기 때문에, 이와 같이 사용하면 에러가 발생한다. + +### 3. addEventListener()의 콜백함수 + +```js +const button = document.getElementById('myButton'); + +button.addEventListener('click', () => { + console.log(this); // Window + this.innerHTML = 'clicked'; +}); + +button.addEventListener('click', function() { + console.log(this); // button 엘리먼트 + this.innerHTML = 'clicked'; +}); +``` + +원래 addEventListener의 콜백함수에서는 this에 해당 이벤트 리스너가 호출된 엘리먼트가 바인딩되도록 정의되어 있다. 이처럼 이미 this의 값이 정해져있는 콜백함수의 경우, 화살표 함수를 사용하면 기존 바인딩 값이 사라지고 상위 스코프(이 경우엔 전역 객체)가 바인딩되기 때문에 의도했던대로 동작하지 않을 수 있다. + +물론 상위 스코프의 속성들을 쓰려고 의도한 경우라면 사용할 수 있다. + +--- +참고 +- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/Arrow_functions +- https://codingapple.com/unit/es6-3-arrow-function-why/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Collections.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Collections.md" new file mode 100644 index 00000000..c72f6225 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Collections.md" @@ -0,0 +1,65 @@ +--- +title: 'Collections' +lastUpdated: '2024-03-02' +--- + +코틀린에서 아래와 같은 방식으로 컬렉션을 사용할 수 있다. + +```kotlin +val set = hashSetOf(1, 7, 53) +val list = arrayListOf(1, 7, 53) +val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three") +``` + +자바의 컬렉션(`Set`,`List`,`Map`)과 동일한 구조이기 때문에 서로 호환된다. + +코틀린의 컬렉션은 자바보다 더 많은 기능을 지원한다. + +```kotlin +fun main(args: Array) { + val strings = listOf("first", "second", "fourteenth") + println(strings.last()) // 리스트의 마지막 원소를 가져온다. + val numbers = setOf(1, 14, 2) + println(numbers.max()) // 컬렉션에서 최댓값을 가져온다. +} +``` + +### 불변 리스트 + +코틀린과 자바의 컬렉션은 서로 호환되지만 코틀린의 `List`가 자바의 `List`와 같은것은 아니다. + +코틀린의 `List`는 한번 정의되면 그 이후로는 변경이 불가능한 불변(immutable) 리스트이며 `add`같은 메서드가 존재하지 않는다. + +```kotlin +fun main(args:Array){ + + //list는 데이터를 읽는 메서드만 가지고있다. + + val list = listOf(1,2,3,"String") + + println(list.size) + + if(list.contains(1)){ + println(true) + }else{ + println(false) + } + + println(list.indexOf(2)) + + println(list[2]) +} +``` + +자바의 `list`처럼 읽기, 쓰기를 모두 사용하려면 `mutableList`로 선언해야한다. + +참고로 kotlin에서도 ArrayList와 List의 개념은 동일하다. + +```kotlin +public fun listOf(vararg elements: T): List = if (elements.size > 0) elements.asList() else emptyList() + +public fun mutableListOf(vararg elements: T): MutableList = + if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true)) +``` + +`mutableListOf()`나 `listOf()`를 호출하면 ArrayList의 생성자를 호출해서 반환하는 것을 볼 수 있다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/InlineFuntion\352\263\274\342\200\205Reified.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/InlineFuntion\352\263\274\342\200\205Reified.md" new file mode 100644 index 00000000..01d9113a --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/InlineFuntion\352\263\274\342\200\205Reified.md" @@ -0,0 +1,167 @@ +--- +title: 'InlineFuntion과 Reified' +lastUpdated: '2024-03-02' +--- + +`inline` 키워드는 자바에서는 제공하지 않는 코틀린만의 키워드이다. + +코틀린 공식문서의 [inline function](https://kotlinlang.org/docs/inline-functions.html)을 보면, 코틀린에서 고차함수(High order functions, 함수를 인자로 전달하거나 함수를 리턴)를 사용하면 패널티가 발생한다고 나와있다. + +패널티란 추가적인 메모리 할당 및 함수호출로 Runtime overhead가 발생한다는 것으로, 람다를 사용하면 각 함수는 객체로 변환되어 메모리 할당과 가상 호출 단계를 거치는데 여기서 런타임 오버헤드가 발생한다. + +inline functions는 내부적으로 함수 내용을 호출되는 위치에 복사하며, Runtime overhead를 줄여준다. + +## 객체로 변환되는 오버헤드란? + +아래와 같은 고차함수(함수를 인자로 전달하거나 함수를 리턴하는 함수)를 컴파일 하면 자바 파일이 생성된다. + +```kotlin +fun doSomethingElse(lambda: () -> Unit) { + println("Doing something else") + lambda() +} +``` + +컴파일된 자바 코드는 Functional Interface인 Function 객체를 파라미터로 받고 invoke 메서드를 실행한다. + +그러면 이제 위에서 선언한 메소드를 이용해 또 다른 메서드를 만들어보자. + +doSomethingElse를 실행 하기전 출력문을 실행 후 함수를 호출하며 파라미터로 { println("Inside lambda") } 람다식을 넣었다. + +```kotlin +fun doSomething() { + println("Before lambda") + + doSomethingElse { + println("Inside lambda") + } + + println("After lambda") +} +``` + +위 코드를 자바로 컴파일 하면 아래와 같이 된다. + +```java +public static final void doSomething() { + System.out.println("Before lambda"); + + doSomethingElse(new Function() { + public final void invoke() { + System.out.println("Inside lambda"); + } + }); + + System.out.println("After lambda"); +} +``` + +이 코드의 문제점은 파라미터로 매번 새로운(`new Function()`)객체를 만들어 넣어준다는 것이다. 이렇게 의미없이 객체로 변환되는 코드가 바로 **객체로 변환되는 오버헤드이자 패널티**이다. + +## Inline-Funtions 으로 오버헤드 해결하기 + +메소드 앞에 `inline`를 붙이면 이렇게 된다. + +```java +inline fun doSomethingElse(lambda: () -> Unit) { + println("Doing something else") + lambda() +} + +public static final void doSomething() { + System.out.println("Before lambda"); + System.out.println("Doing something else"); + System.out.println("Inside lambda"); + System.out.println("After lambda"); +} +``` + +위 자바 컴파일 코드를 보면 새로운 객체를 생성하는 부분이 사라지고 +- `System.out.println("Doing something else");` +- `System.out.println("Inside lambda");` +두 코드로 변경된 것을 알 수있다. + +## Reified + +범용성 좋은 메소드를 만들기 위해 generics 를 사용할 때가 있다. + +이떄 inline과 함께 refied 키워드를 사용하면 Generics를 사용하는 메소드 까지 처리할 수 있다. + +```kotlin +fun doSomething(someValue: T) +``` + +이러한 class Type `T` 객체는 원래 타입에 대한 정보가 런타임에서 `Type Erase` 되어버려 알 수 없어져서, 실행하면 에러가 난다. + +따라서 Class를 함께 넘겨 type을 확인하고 casting 하는 과정을 거치곤한다. + +```kotlin + // runtime에서도 타입을 알 수 있게 Class 넘김 +fun doSomething(someValue: T, Class type) { + // T의 타입을 파라미터를 통해 알기에 OK + println("Doing something with value: $someValue") + // T::class 가 어떤 class인지 몰라서 Error + println("Doing something with type: ${T::class.simpleName}") +} +``` + +인라인(inline) 함수와 reified 키워드를 함께 사용하면 T type에 대해서 런타임에 접근할 수 있게 해준다. + +따라서 타입을 유지하기 위해서 Class와 같은 추가 파라미터를 넘길 필요가 없어진다. + +```kotlin +//reified로 런타임시 T의 타입을 유추 할 수있게됨 +inline fun doSomething(someValue: T) { + // OK + println("Doing something with value: $someValue") + // T::class 가 reified로 인해 타입을 알 수 있게되어 OK + println("Doing something with type: ${T::class.simpleName}") +} +``` + +inline keyword는 1~3줄 정도 길이의 함수에 사용하는 것이 효과적일 수 있다. + +## noinline + +인자 앞에 noinline 키워드를 붙이면 해당 인자는 inline에서 제외된다. 따라서 noinline 키워드가 붙은 인자는 다른 함수의 인자로 전달하는 것이 가능하다. + +```kotlin +inline fun doSomething(action1: () -> Unit, noinline action2: () -> Unit) { + action1() + anotherFunc(action2) +} + +fun anotherFunc(action: () -> Unit) { + action() +} + +fun main() { + doSomething({ + println("1") + }, { + println("2") + }) +} +``` + + +## cross inline + +crossinline은 lambda 가 non-local return 을 허용하지 않아야 한다는 것을 이야기한다. inline 되는 higher order function 에서 lambda 를 소비하는 것이 아니라 다른 곳으로 전달할 때 마킹을 해준다. + +```kotlin +inline fun foo(crossinline f: () -> Unit) { + /** + * 다른 고차함수에서 func를 호출시엔 crossinline 을 표시해주어야 함. + */ + bar { f() } +} + +fun bar(f: () -> Unit) { + f() +} +``` + +예제를 보면 f lambda 는 바로 소비되지 않고, boo 로 한번 더 전달이 된다. + +전달이 되지 않는다면 inline 함수이기 때문에 non-local return 이 허용되지만, 한번 더 전달되므로 non-local return을 할 수 없게 되고, 이것을 명시적으로 알리기 위해(함수 사용자 & compiler) crossinline 으로 마킹해준다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Label.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Label.md" new file mode 100644 index 00000000..1893be87 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Label.md" @@ -0,0 +1,84 @@ +--- +title: 'Label' +lastUpdated: '2024-03-02' +--- + +Kotlin은 loop에 label을 지정해 break, continue 스코프를 정할 수 있다. + +label은 @ 식별자를 통해 지정할 수 있고, @abc, fooBar@ 처럼 사용할 수 있다. + +아래는 내부 for문에서 break 한 경우 -> 바깥의 for문 까지 종료 시키는 예제와, continue의 next iteration을 바깥 for문으로 지정하는 예제이다. + +```java +fun labelReturnJump() { + loop@ for (i in 1..10) { // label 지정 + for (j in 1..10) { + if (i + j > 12) { + break@loop // label을 통해 break + } + } + } +} + +fun labelContinue() { + loop@ for (i in 1..10) { + for (j in 1..10) { + if (j > 2) { + continue@loop + } + } + } +} +``` + +## Return Labels + +Label을 쓰지 않고 Lambda를 사용하는 경우 아래와 같은 문제가 있다. 아래의 코드에서 return은 foo 메소드 자체를 끝내버린다. + +```java +fun foo() { + listOf(1, 2, 3, 4, 5).forEach { + if (it == 3) return // non-local return directly to the caller of foo() + print(it) + } + println("this point is unreachable") +} +``` + +따라서 코틀린에서 람다의 경우 레이블을 이용해서 아래처럼 람다식만 종료시켜야한다. + +```java +fun fooLabmdaReturn () { + var ints = listOf(1, 2, 3, 4, 5) + ints.forEach lambda@ { + if (it == 1) return@lambda + println(it) + } + println("this point is unreachable") +} +``` + +## 암시적 Label + +매번 이렇게 작성하는 것은 귀찮으니 연산자 이름을 레이블로 활용하는 암시적 레이블도 지원한다. + +```java +fun fooLambdaReturn() { + var ints = listOf(1, 2, 3, 4, 5) + ints.forEach{ + if (it == 1) return@forEach + println(it) + } + println("this point is unreachable") +} + +fun fooReturnLabelWithValue(): List { + var ints = listOf(1, 2, 3, 4, 5) + return ints.map { + if (it == 0) { + return@map "zero" + } + "number $it" + } +} +``` diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Sequences.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Sequences.md" new file mode 100644 index 00000000..bae6d6f6 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/Sequences.md" @@ -0,0 +1,75 @@ +--- +title: 'Sequences' +lastUpdated: '2024-03-02' +--- + +Sequences는 Collections와 비슷하게 Iterable한 자료구조이다. 하지만 `Eager evaluation`으로 동작하는 Collections와 다르게 Sequences는 `Lazy evaluation`으로 동작한다. + +### Lazy evaluation과 Eager evaluation의 차이 + +Lazy evaluation은 지금 하지 않아도 되는 연산은 최대한 뒤로 미루고, 어쩔 수 없이 연산이 필요한 순간에 연산을 수행하는 방식이다. + +```kotlin +val fruits = listOf("apple", "banana", "kiwi", "cherry") + +fruits + .filter { + println("checking the length of $it") + it.length > 5 + } + .map { + println("mapping to the length of $it") + "${it.length}" + } + .take(1) +``` + +``` +//실행결과 +checking the length of apple +checking the length of banana +checking the length of kiwi +checking the length of cherry +mapping to the length of banana +mapping to the length of cherry +``` + +Collections를 사용하면 `Eager evaluation`이기 떄문에 함수를 호출한대로 모든 과정을 수행한다. + +실행결과를 보면 1개의 원소만 반환하지만 모두 순회하며 필터링, 매핑하는 모습을 볼 수 있다. + +```kotlin +val fruits = listOf("apple", "banana", "kiwi", "cherry") + +fruits + .asSequence() + .filter { + println("checking the length of $it") + it.length > 5 + } + .map { + println("mapping to the length of $it") + "${it.length}" + } + .take(1) + .toList() +``` + +``` +//실행결과 +checking the length of apple +checking the length of banana +mapping to the length of banana +``` + +하지만 Sequence로 변환 후 사용하면 `Lazy evaluation`이기 떄문에 모든 과정을 수행하지 않고, 결과물 반환에 필요한 작업만 수행한다. + +`.take(1)`로 설정되어있기 때문에 `filter()` 조건의 맞는 한개의 원소를 찾자마자 `map()`으로 넘어가 반환하는 것을 볼 수 있다. + +심지어 마지막에 `.toList()`를 안 붙여주면 아무것도 실행되지 않는다. + +이처럼, `Lazy evaluation`은 꼭 필요할 떄에만 연산하며, kotlin의 Sequence가 이러한 특성을 가진다. + +### 결론 + +원소가 너무 많은 경우에는 Sequences로 연산하는 것이 성능에 큰 향상을 줄 수 있겠지만, 작은 배열에서는 그냥 Collections를 사용하는게 오히려 좋을 수도 있지 않을까 싶다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/field\342\200\205\354\203\201\354\206\215.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/field\342\200\205\354\203\201\354\206\215.md" new file mode 100644 index 00000000..da739842 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/field\342\200\205\354\203\201\354\206\215.md" @@ -0,0 +1,41 @@ +--- +title: 'field 상속' +lastUpdated: '2024-03-02' +--- + +JAVA에는 field 상속이라는 개념이 없다. 하지만 kotlin에서는 `override val`과 같이 쓰면 필드를 상속받은 것 처럼 사용할 수 있다. + +```kotlin +data class Teacher( + val id: UUID = UUID.randomUUID(), + val accountId: String, + val password: String +) : Domain +``` + +이렇게 `Teacher`라는 클래스를 만들고, 그걸 상속받은 `TeacherEntity`라는 클래스를 만들면, `TeacherEntity` 안에 private 필드가 따로 만들어지는 것을 볼 수 있다. + +image + +private이기 때문에 외부에선 함부로 참조할 수 없고, 마치 그냥 `override` 받은 것 같이 편하게 사용할 수 있다. + +그래서 이 특성을 사용해서 POJO인 `Domain class`를 `Entity class`가 상속 받게 구현하고, 라이브러리 의존성을 분리하는 Hexagonal Architecture를 구현하려고 했다. + +하지만 여기서 문제가 생겼다... + +## Reflection + +Spring framework data에서는 class 정보를 가져와서 private인지 여부에 상관 없이 필드를 가져오는데, 경우에 따라 필드 이름이 중복이라서 등록이 안되는 경우가 있었다. + +여기서 등록이 되는지 안되는지 여부는 DB 구현에 따라 조금 다른 것 같다. +- mongo data에서는 등록 자체가 불가능했고 +- R2DBC에서는 필드를 override로 정의하는 것 까진 가능했지만, data class거나 필드가 전부 var이어야 했다. (data class는 상속받을 수 없기 때문에, 선택지가 하나라고 봐도 무방하다.) + +코틀린 상으로는 `override`라고 쓰긴 했지만 내부적으로는 private 필드를 각각 가지고 있는 것이기 때문에 스캔 방식에 따라 등록되는 조건이 조금씩 다른 것 같다. + + +--- + +참고 + +- https://stackoverflow.com/questions/47757454/kotlin-override-abstract-val-behavior-object-vs-class \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\236\214\353\213\244\342\200\205\355\221\234\355\230\204\354\213\235.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\236\214\353\213\244\342\200\205\355\221\234\355\230\204\354\213\235.md" new file mode 100644 index 00000000..56a4f050 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\236\214\353\213\244\342\200\205\355\221\234\355\230\204\354\213\235.md" @@ -0,0 +1,81 @@ +--- +title: '람다 표현식' +lastUpdated: '2024-03-02' +--- + +람다는 익명 함수이며, 함수형 프로그래밍에서 자주 사용하는 패턴이다. +코틀린은 객체지향 프로그래밍 뿐만 아니라 함수형 프로그래밍 또한 지원하는 언어이기 때문에 당연히 람다 표현식을 사용할 수 있다. + +### 익명함수 생성 + +익명함수는 아래처럼 이름없이 정의되는 함수를 말한다. 변수 printHello에 할당되고 있는 내용이 바로 익명함수이다. + +```kotlin + // 익명함수를 생성하여 printHello에 할당 + val printHello = fun(){ println("Hello, world!") } + // 익명함수 호출 + printHello() +``` + +실행결과 +``` +Hello, world! +``` + +람다를 이용하면 더 간단히 익명함수를 정의할 수 있다. 아래는 위와 동일한 코드를 람다로 재작성 한 것이다. + +```kotlin + // 익명함수를 생성하여 printHello에 할당 + val printHello = () -> Unit = { println("Hello, world!") } + // 익명함수 호출 + printHello() +``` + +### 인자가 있는 익명함수 + +```kotlin +fun main(args: Array) { + // 익명함수를 생성하여 printHello에 할당 + val printHello = { name: String, age:String -> "Hello. My name is $name. I'm $age year old" } + + // 익명함수 호출 + val result = printHello("김은빈", "17") + println(result) +} +``` + +익명함수에서도 인자를 받아 값을 반환할 수 있다. + +`->`의 옆에 하나의 값이나 문장이 들어오면 `return`을 생략할 수 있다. 여러 줄의 코드가 들어가는 경우엔 중괄호를 넣어줘야한다. (자바와 같다) + +```kotlin +fun main(args: Array) { + + val printHello: (String, String) -> String = { name, age -> "Hello. My name is $name. i'm $age year old" } + + val result = printHello("김은빈", "17") + println(result) +} +``` + +함수의 인자, 반환 타입을 정의한 다음에 익명함수를 만드는 방법도 있다. + +### 인자 선언 생략 + +```kotlin +val printHello: (String) -> String = { name -> "Hello. My name is $name."} +val result = printHello("김은빈") +``` + +인자가 1개일때는 아래와 같이 인자의 이름을 선언하지 않아도 된다. + +```kotlin +val printHello: (String) -> String = { "Hello. My name is $it."} +val result = printHello("김은빈") +``` + +인자 `name`을 생략한 대신 `it`으로 인자에 접근하고 있다. 코틀린은 인자를 생략하는 경우 `it`으로 인자를 사용할 수 있다. + + + + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\262\224\354\234\204\342\200\205\354\247\200\354\240\225\342\200\205\355\225\250\354\210\230.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\262\224\354\234\204\342\200\205\354\247\200\354\240\225\342\200\205\355\225\250\354\210\230.md" new file mode 100644 index 00000000..b70aaff6 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\262\224\354\234\204\342\200\205\354\247\200\354\240\225\342\200\205\355\225\250\354\210\230.md" @@ -0,0 +1,124 @@ +--- +title: '범위 지정 함수' +lastUpdated: '2024-03-02' +--- + +범위 지정 함수는 특정 객체에 대한 작업을 블록 안에 넣어 실행할 수 있도록 하는 함수이다. + +특정 객체에 대한 작업을 블록으로 묶으면 수행할 작업의 범위를 지정함으로써, 코드의 가독성을 개선할 수 있다. + +코들린에서는 let, run, apply. also, with로 총 5가지의 기본 범위 지정함수를 지원한다. + +하나씩 알아보도록 하자. + +```kotlin +public inline fun T.apply(block: T.() -> Unit): T +public inline fun T.run(block: T.() -> R): R +public inline fun with(receiver: T, block: T() -> R):R +``` + +### apply + +```kotlin +public inline fun T.apply(block: T.() -> Unit): T +``` + +apply는 수신객체 내부 프로퍼티를 통해 작업한 다음 수신객체 자체를 반환하는 함수이다. + +apply에서의 block은 람다식의 수신객체로 apply의 수신객체(T)를 지정하기 때문에 람다식 내부에서 수신객체에 대해 명시하지 않고 함수를 호출할 수 있다. (this 사용도 가능하다.) + +apply를 사용하면 + +```kotlin +val user = User() +user.name = "rlaisqls" +user.age = 17 +``` + +위와 같은 코드를 + +```kotlin +val user = User().apply { + name = "rlaisqls" + age = 17 +} +``` + +이렇게 바꿀 수도 있다. `user.`과 같은 중복된 부분을 없앨 수 있다. + +### run + +```kotlin +public inline fun T.run(block: T.() -> R): R +``` + +run은 apply와 똑같이 동작하지만 수신 객체를 return하지 않고, run 블록의 마지막 라인을 return하는 범위 지정 함수이다. + +```kotlin +val user = User("rlaisqls", 17) +val isUserAdult = user.run { + age = 20 + isAdult() +} + +println("isUserAdult : $isUserAdult") //true +``` + +run은 수신객체 없이도 동작할 수 있다. 다만 수신객체 없이 run을 사용하면 내부에 수신객체를 명시해줘야 한다. + +### with + +```kotlin +public inline fun with(receiver: T, block: T() -> R):R +``` +with는 수신객체에 대한 작업 후 마지막 라인을 return한다. run과 완전히 똑같이 동작한다. + +run은 확장 함수로 사용되지만 with은 수신객체를 파라미터로 받아 사용한다는 점이 차이점이다. + +```kotlin +val user = User("rlaisqls", 17) +val isUserAdult = with(user) { + isAdult() +} + +println("isUserAdult : $isUserAdult") //false +``` + +### let + +```kotlin +public inline fun T.let(block: (T) -> R): R +``` + +let은 수신객체를 이용해 작업을 한 후 마지막 줄을 return 하는 함수이다. + +수신객체를 접근할 때 it을 사용해야 한다는 것 빼고 run, with과 동작이 같다. + +하지만 다른 범위 지정함수와 다르게 let은 `null check`를 한 후 실행하는 것이 가능하다.
+(run과 with은 객체가 존재한다는 것을 가정하여 null이든 아니든 바로 동작한다.) + +```kotlin +val user = null +val isUserAdult = user?.let { + isAdult() +} + +println("isUserAdult : $isUserAdult") //null +``` + +`null check`가 되지 않았다면 NPE가 throw 됐겠지만, `null check` 이후 코드가 실행되어 에러가 뜨지 않고 실행된 것을 확인할 수 있다. + +### also + +```kotlin +public inline fun T.also(block: (T) -> Unit): T +``` + +also는 apply와 마찬가지로 수신객체 자신을 반환한다. apply가 프로퍼티를 세팅 후 객체 자체를 반환 하는데만 사용된다면, also는 프로퍼티 세팅 뿐만아니라 객체에 대한 추가적인 작업(로깅, 유효성 검사 등)을 한 후 객체를 반환할 때 사용된다. + +also에서의 block은 람다식의 입력 파라미터로 also의 수신객체(T)를 지정하기 때문에 내부에서 수신객체를 사용하기 위해서는 it을 사용해야 한다. + + +--- + +공식문서 : https://kotlinlang.org/docs/scope-functions.html diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/List\354\231\200\342\200\205MutableList.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/List\354\231\200\342\200\205MutableList.md" new file mode 100644 index 00000000..fe93a781 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/List\354\231\200\342\200\205MutableList.md" @@ -0,0 +1,47 @@ +--- +title: 'List와 MutableList' +lastUpdated: '2024-03-02' +--- + +List는 수정이 불가능한 리스트이고, MutableList는 수정이 가능한 리스트이다. + +내부 코드는 어떤 차이가 있을까? + +```kotlin +public interface Collection : Iterable { + + public val size: Int + + public fun isEmpty(): Boolean + + public operator fun contains(element: @UnsafeVariance E): Boolean + + override fun iterator(): Iterator + + public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean + +} + +public interface MutableCollection : Collection, MutableIterable { + + override fun iterator(): MutableIterator + + public fun add(element: E): Boolean + + public fun remove(element: E): Boolean + + public fun addAll(elements: Collection): Boolean + + public fun removeAll(elements: Collection): Boolean + + public fun retainAll(elements: Collection): Boolean + + public fun clear(): Unit + +} +``` + +`Collection`은 공변이고 `MutableCollection`은 불공변이다. + +공변은 Write가 안되는 대신에, 하위 클래스를 포함할 수 있다. 이러한 특성을 고려하여 개발하면 좋을 것 같다. + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/Nullable.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/Nullable.md" new file mode 100644 index 00000000..c213bd49 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/Nullable.md" @@ -0,0 +1,88 @@ +--- +title: 'Nullable' +lastUpdated: '2024-03-02' +--- + +Kotlin에서는 코틀린은 원시 타입(primitive type)과 래퍼 타입(wrapper type)을 따로 구분하지 않고, Null일 수 있는 타입과, Null이 불가능한 타입으로 나누어 사용한다. + +널이 될 수 있는지 여부를 타입 시스템을 통해 확실히 구분함으로써 컴파일러가 여러 가지 오류를 컴파일 시 미리 감지해서 실행 시점에 발생할 수 있는 예외의 가능성을 줄일 수 있다. + +또, null 처리를 위한 다양한 연산자를 지원한다. + +
+ +--- + +### NULL이 될 수 있는 타입 `? !!` + +코틀린의 모든 타입은 기본적으로 널이 될 수 없는 타입이다. + +타입 옆에 물음표(`?`)를 붙이면 널이 될 수 있음을 뜻한다. + +느낌표 2개(`!!`)를 변수 뒤에 붙이면 NULL이 될 수 있는 타입의 변수이지만, 현재는 NULL이 아님을 나타낼 수 있다. + +```kotlin +var a:Int? = null +var b:Int? = 10 +var c:Int = b!! +``` +
+ +### 안전한 메서드 호출 `?.` + +`?.`은 null 검사와 메서드 호출을 한 번의 연산으로 수행한다. + +```kotlin +foo?.bar() +``` + +foo가 null이면 bar() 메서드 호출이 무시되고 null이 결과 값이 된다.
+foo가 null이 아니면 bar() 메서드를 정상 실행하고 결과값을 얻어온다. + +
+ +### 엘비스 연산자 `?:` + +null 대신 사용할 디폴트 값을 지정할때, 3항 연산자 대신 사용할 수 있는 연산자이다. + +```kotlin +fun foo(s: String?) { + val t: String = s ?: "" +} +``` + +우항에 return, throw 등의 연산을 넣을 수도 있다. + +
+ +### 안전한 캐스트 `as?` + +자바 타입 캐스트와 마찬가지로 대상 값을 as로 지정한 타입으로 바꿀 수 없다면 ClassCastException이 발생한다. +그래서 자바에서는 보통 is를 통해 미리 as로 변환 가능한 타입인지 검사해 본다. + +as? 연산자는 어떤 값을 지정한 타입으로 캐스트하고, 변환할 수 없으면 null을 반환한다. + +```kotlin +foo as? Type +``` + +foo is Type이면 foo는 Type으로 변환하고
+foo !is Type이면 null을 반환한다. + +
+ +### `let{}` + +let 함수를 안전한 호출 연산자와 함께 사용하면 원하는 식을 평가해서 결과가 널인지 검사한 다음에 그 결과를 변수에 넣는 작업을 간단한 식을 사용해 한꺼번에 처리할 수 있다. + +```kotlin +fun sendEmailTo(email: String) { + println("Sending email to $email") +} + +fun main(args: Array) { + var email: String? = "yole@example.com" +// sendEmailTo(email) -> error + email?.let { sendEmailTo(it) } +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/val\352\263\274\342\200\205var.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/val\352\263\274\342\200\205var.md" new file mode 100644 index 00000000..1d19522a --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\353\263\200\354\210\230/val\352\263\274\342\200\205var.md" @@ -0,0 +1,47 @@ +--- +title: 'val과 var' +lastUpdated: '2024-03-02' +--- + +코틀린을 사용할 때 변수를 선언하려면 아래와 같이 한다. + +```kotlin +val 변수명: 타입 +var 변수명: 타입 +``` + +String을 써서 문자열 데이터를 집어넣는다면 아래와 같이 할 수 있다. + +```kotlin +val name1: String = "kimeunbin" +val name2: String = "kimeunbin" +``` + +근데 val과 var의 차이는 무엇일까? + +### val + +`val`이 붙은 변수는 읽을 수만 있고 수정할 수는 없는 변수가 된다. 자바로 치면 final 키워드가 붙은 변수와 비슷하다. + +```kotlin +fun main(args: Array) +{ + val a: String = "aaa" + a = "bbb" //에러가 난다! Val cannot be reassigned +} +``` + +### var + +`val`가 붙은 변수는 읽기/쓰기가 모두 가능하다. + +```kotlin +fun main(args: Array) +{ + var a: String = "aaa" + a = "bbb" + println(a) +} +``` + +성공적으로 재할당 되는 것을 볼 수 있다! \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\354\240\234\353\204\244\353\246\255\352\263\274\342\200\205variance.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\354\240\234\353\204\244\353\246\255\352\263\274\342\200\205variance.md" new file mode 100644 index 00000000..93755562 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\354\240\234\353\204\244\353\246\255\352\263\274\342\200\205variance.md" @@ -0,0 +1,93 @@ +--- +title: '제네릭과 variance' +lastUpdated: '2024-03-02' +--- + +프로그래밍 언어들에서 제공해주는 기능 중 하나인 `제네릭`은 클래스나 인터페이스 혹은 함수 등에서 동일한 코드로 여러 타입을 지원해주기 위해 존재한다. 한가지 타입에 대한 템플릿이 아니라 **여러가지 타입**을 사용할 수 있는 클래스와 같은 코드를 간단하게 작성할 수 있다. + +## 예시 + +```kotlin +class Wrapper(var value: T) + +fun main(vararg args: String) { + val intWrapper = Wrapper(1) + val strWrapper = Wrapper("1") + val doubleWrapper = Wrapper(0.1) +} +``` + +`Wrapper`라는 클래스는 꺽쇠안에 `T`라는 형식 인자(Type paraameter)를 가진다. 그곳에는 `Int`, `String`, `Double`등 여러 형식이 저장될 수 있다. + +```kotlin +fun > greaterThan(lhs: T, rhs: T): Boolean { + return lhs > rhs +} +``` + +함수에 제네릭을 적용한 예시이다. `Comparable` 을 구현한 형식 인자만 `>` 연산자를 사용할 수 있기 때문에 꺽쇠 안의 T의 선언에 `Comparable` 를 구현했다는 것을 표시해주었다. + +흔하게 사용되는 Kotlin의 컬렉션들인 `List`, `MutableList`, `Set`, `MutableSet`, `Map`등도 초기화될 때 제네릭으로 타입을 넣는다. 혹은 타입 추론이 될 수도 있다. (ex: listOf(1,2)가 자동으로 List로 추론됨) + +# Invariance + +제네릭을 사용할 때 가장 헷갈리는 부분은 variance이다. 자바에선 와일드 카드(Wild card)라고 불리는 기능과 비슷하다. + +자바에서 `String`은 `Object`의 subType이다. 그러나 `List`은 `List`의 subType이 아니다. + +```kotlin +val strs: MutableList = mutableListOf() + +//val objs: MutableList = strs 에러발생 +``` + +두번째 줄과 같은 코드는 에러 발생으로 인해 실행될 수 없다. 만약 `MutableList` 이 `MutableList`의 subType이면 2번째 줄이 에러가 발생하지 않아야 한다. + +이렇게 형식 인자들끼리는 sub type 관계를 만족하더라도 제네릭을 사용하는 클래스와 인터페이스에서는 subType 관계가 유지되지 않는 것이 **Invariance**(불공변)이다. 기본적으로 코틀린의 모든 제네릭에서의 형식 인자는 Invariance이 된다. + +## Invariance의 한계 + +Invariance는 컴파일 타임 에러를 잡아주고 런타임에 에러를 내지않는 안전한 방법이다. 그러나, 안전하다고 보장된 상황에서도 컴파일 에러를 내 개발자를 불편하게 할 수도 있다. + +```java +interface Collection ... { + void addAll(Collection items); +} +void copyAll(Collection to, Collection from) { + to.addAll(from); + //addAll은 Invariance여서 to의 addAll에 from을 전달할 수 없다! +} +``` + +to는 `Collection`고 from은 `Collection`이다. String을 Object로 취급하여, `String`만 사용할 수 있는 메서드나 속성을 사용하지 않을 것이라면 이 코드는 문제될게 없다. 하지만 이 코드는 컴파일 에러가 발생한다. + +## Java Wildcard, Covariance, Contravariance + +이를 해결하기위해 자바에서는 Wildcard가 등장한다. 제네릭 형식 인자 선언에 `? extends E`와 같은 문법을 통해 `E`나 `E`의 subType의 제네릭 형식을 전달받아 사용할 수 있다. + +```java +interface Collection ... { + void addAll(Collection items); +} +``` + +만약 Collection의 코드가 이런 형식으로 되어있다고 가정해보자. + +items엔 E일수도 있고 E의 sub type일 수도 있는 아이템들이 들어있을 것이다. 여기서 어떤 아이템을 꺼내든(읽든) 그것은 E라는 형식안에 담길 수 있다. + +그러나 `items`에 어떤 값을 추가하려면 `items`의 형식 인자인 ?가 어떤 것인지를 알아야한다. 하지만 그 인자가 무엇인지 정확히 알 수 없기 떄문에 값을 추가할 수 없다. 예를 들어 `items`가 `Collection`라면 `items`에서 우리는 어떤 아이템을 꺼내서 그것을 `Object` 타입 안에 담을 수 있다. 그러나 `Object`를 `items`에 넣을 수는 없다. 왜냐하면 **전달된 `items`가 `Collection`일 수도 있고 `Collection`일 수도 있기 때문**이다. + +읽기만 가능하고 쓰기는 불가능한 `? extends E 는` 코틀린에서의 out과 비슷한 의미로 사용되고 이런 것들을 **covariance**(공변)이라 부른다. + +반대로 읽기는 불가능하고 쓰기만 가능한, 자바에선 `? super E` 로 사용되고 코틀린에선 `in` 으로 사용되는 **contravariance**(반공변)이 있다. + +**contravariance**에서는 `E`와 같거나 `E의 상위타입`만 `?` 자리에 들어올 수 있다. items이 `Collection` 라면, `items`에서 어떤 변수를 꺼내도 `E`에 담을 수 있을 지 보장할 수 없지만(읽기 불가) `E`의 상위 타입 아무거나 `items`에 넣을 수 있기 때문에 **covariance와 반대**된다고 생가하면 된다. + +## 정리 + +|정리|설명|java|kotlin| +|-|-|-|-| +|Invariance(불공변)|제네릭으로 선언한 타입과 일치하는 클래스만 삽입할 수 있다.|``|``| +|Covariance(공변)|**특정 클래스를 상속받은 하위클래스들**을 리스트로 선언할 수 있다.
하지만 해당 리스트의 타입을 특정할 수 없기 때문에 **Write가 불가능**하다.|``|``| +|Contravariance(반공변)|**특정 클래스의 상위 클래스들**을 리스트로 선언할 수 있다.
하지만 꺼낼 인스턴스가 어떤 타입인지 알 수 없기 때문에 Read가 불가능하다.|``|``| + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/@JvmField.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/@JvmField.md" new file mode 100644 index 00000000..7906112d --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/@JvmField.md" @@ -0,0 +1,46 @@ +--- +title: '@JvmField' +lastUpdated: '2024-03-02' +--- + +`@JvmField`는 get/set을 생성하지 말라는 의미이다. + +다음 코틀린 코드에서 프로퍼티 var barSize는 getter/setter를 생성한다. + +```kotlin +class Bar { + var barSize = 0 +} +``` + +자바로 변환해보면 getter/setter가 생성된 것을 볼 수 있다. + +```java +public final class Bar { + private int barSize; + public final int getBarSize() { + return this.barSize; + } + public final void setBarSize(int var1) { + this.barSize = var1; + } +} +``` + +이번엔 `@JvmField`를 붙여보자 + +```kotlin +class Bar { + @JvmField + var barSize = 0 +} +``` + +자바로 변환해보면 getter/setter가 생성되지 않은 것을 볼 수 있다. + +```java +public final class Bar { + @JvmField + public int barSize; +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/@JvmStatic.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/@JvmStatic.md" new file mode 100644 index 00000000..2d057734 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/@JvmStatic.md" @@ -0,0 +1,88 @@ +--- +title: '@JvmStatic' +lastUpdated: '2024-03-02' +--- + +함수와 프로퍼티에 static 하게 접근할 수 있도록 추가적인 메서드 또는 getter / setter 를 생성한다. + +다음 Bar 클래스는 barSize라는 변수를 companion object에 선언함으로써, 전역변수를 만들었다. + +```kotlin +class Bar { + companion object { + var barSize : Int = 0 + } +} +``` + +자바로 변환해보면 Bar클래스에 barSize는 선언되었지만 getter/setter는 Bar.Companion 클래스에 등록된 것을 볼 수 있다. + +```java +public final class Bar { + private static int barSize; + public static final class Companion { + public final int getBarSize() { + return Bar.barSize; + } + public final void setBarSize(int var1) { + Bar.barSize = var1; + } + } +} +``` + +자바에서 get/set 함수에 접근하려면 다음처럼 Companion을 꼭 써줘야 한다. + +```java +Bar.Companion.getBarSize(); +Bar.Companion.setBarSize(10); +``` + +static과 companion object가 다르다고 하는 이유가 바로 이것이다. kotlin만 사용할때는 다른 것이 없지만, java와 kotlin을 같이 사용하는 경우에는 이 부분에서 차이가 생긴다. + +companion object를 static처럼 사용하려면 `@JvmStatic`을 사용해야한다. + +```kotlin +class Bar { + companion object { + @JvmStatic var barSize : Int = 0 + } +} +``` + +자바로 변환해보면 Bar클래스에 barSize가 선언되었고, Bar클래스와 Bar.Companion 클래스에 get/set함수가 모두 생성된 것을 볼 수 있다. + +```java +public final class Bar { + private static int barSize; + public static final int getBarSize() { + return barSize; + } + + public static final void setBarSize(int var0) { + barSize = var0; + } + + public static final class Companion { + public final int getBarSize() { + return Bar.barSize; + } + public final void setBarSize(int var1) { + Bar.barSize = var1; + } + } +} +``` + +자바에서 위 코드를 접근하면 Bar.Companion 도 가능하지만 Bar.getBarSize 처럼 바로 접근도 된다. + +```java +Bar.getBarSize(); +Bar.setBarSize(10); +Bar.Companion.getBarSize(); +Bar.Companion.setBarSize(10); +``` + +@JvmStatic를 사용하면 클래스도 마찬가지로 `Companion` 키워드 없이 접근할 수 있다. + +정리하자면, @JvmStatic는 Companion에 등록된 변수를 자바의 static처럼 선언하기 위한 annotation이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/Object.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/Object.md" new file mode 100644 index 00000000..9bd07dc5 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/Object.md" @@ -0,0 +1,103 @@ +--- +title: 'Object' +lastUpdated: '2024-03-02' +--- + +코틀린에서 클래스를 정의하는 키워드는 class이다. 하지만 간혹 object 키워드로 클래스 정의하는 경우를 볼 수 있다. object로 클래스를 정의하면, 싱클턴(Singleton) 패턴이 적용되어 객체가 한번만 생성되도록 한다. 코틀린에서 object를 사용하면 싱글톰을 구현하기 위한 Boiler plate를 작성하지 않아도 된다. 또는, 익명 객체를 생성할 때 쓰이기도 한다. + +#### object의 용도 +- 싱글턴 클래스로 만들 때 +- 익명 클래스 객체를 생성할 때 + +## 싱글턴 클래스 정의를 위한 Object + +아까도 말했듯, object로 싱글턴 클래스를 정의할 수 있다. 원래 클래스를 정의할때 class가 있는 자리에 object를 입력해주면 이 클래스는 싱글턴으로 동작하게 된다. + +```kotlin +object CarFactory { + val cars = mutableListOf() + + fun makeCar(horsepowers: Int): Car { + val car = Car(horsepowers) + cars.add(car) + return car + } +} +``` + +object로 설정하면, 아래 코드처럼 CarFactory.makeCar 처럼 메소드에 접근하여 Car객체를 생성할 수 있다. 또한, CarFactory.cars 처럼 직접 변수에 접근할 수 있습니다. 마치 static처럼 사용하는 것 같지만, 인스턴스는 한개 만들어져있는 상태이다. 당연히 여러번 호출해도 CarFactory 객체는 한번만 생성된다. + +```kotlin +val car = CarFactory.makeCar(150) +println(CarFactory.cars.size) +``` + +위에서 봤던 CarFactory 클래스를 자바로 변환한 코드는 아래와 같다. + +```java +public final class CarFactory { + private static final List cars; + public static final CarFactory INSTANCE; + + public final List getCars() { + return cars; + } + + public final Car makeCar(int horsepowers) { + Car car = new Car(horsepowers); + cars.add(car); + return car; + } + + static { + CarFactory var0 = new CarFactory(); + INSTANCE = var0; + cars = (List)(new ArrayList()); + } +} +``` + +위의 자바로 변환된 코드를 보면 CarFactory 객체는 INSTANCE라는 static 객체를 생성한다. 그리고 이 객체에 접근할 때는 CarFactory.INSTANCE를 통해서 접근하게 된다. INSTANCE는 static으로 생성되기 때문에 프로그램이 로딩될 때 생성됩니다. 그래서 쓰레드 안전성(thread-safety)이 보장되지만, 내부적으로 공유자원을 사용하는 경우에는 쓰레드 안전성이 보장되지 않기 때문에 동기화(synchronization) 코드를 작성해야 한다. + +아무튼, object를 사용하면 객체를 간단하게 싱글톤으로 만들 수 있다. companion object로도 싱글톤으로 구현하는 방법도 있긴 하다. + +## 익명객체로 Object 사용 + +object는 익명객체를 정의할 때도 사용된다. 익명객체는 이름이 없는 객체로, 한번만 사용되고 재사용되지 않는 객체를 말한다. 한번만 쓰이기 때문에 이름조차 필요없다는 의미이다. + +예를들어, 아래와 같이 Vehicle 인터페이스, start() 메소드가 정의되어있다. start()는 Vehicle 객체를 인자로 전달받는다. + +```kotlin +interface Vehicle { + fun drive(): String +} + +fun start(vehicle: Vehicle) = println(vehicle.drive()) +``` + +아래 코드에서 start()의 인자로 전달되는 `object : Vehicle{...}`는 익명객체이다. 이 익명객체는 Vehicle 인터페이스를 상속받은 클래스를 객체로 생성된 것을 의미한다. 익명객체이기 때문에 클래스 이름은 없고, 구현부는 `{...}` 안에 정의해야한다. + +```kotlin +//start 함수 호출하기 (익명객체 파라미터로 넣기) +start(object : Vehicle { + override fun drive() = "Driving really fast" +}) +``` + +자바로 변환한 코드는 다음과 같다. + +```java +public final class VehicleKt { + public static final void start(@NotNull Vehicle vehicle) { + + Intrinsics.checkNotNullParameter(vehicle, "vehicle"); + + String var1 = vehicle.drive(); + System.out.println(var1); + } +} +``` + +object의 사용방법에 대해서 알아보았다. 클래스를 정의할 때 object를 사용하면 싱글턴 패턴이 적용되고, object를 사용하여 익명객체를 생성할 수도 있다는 사실을 알 수 있었다. + +java로 변환된 kotlin 코드를 보니, 원리가 더 잘 이해되는 것 같다. diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/Sealed\342\200\205Class,\342\200\205interface.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/Sealed\342\200\205Class,\342\200\205interface.md" new file mode 100644 index 00000000..a0143a0c --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/Sealed\342\200\205Class,\342\200\205interface.md" @@ -0,0 +1,148 @@ +--- +title: 'Sealed Class, interface' +lastUpdated: '2024-03-02' +--- + +### Sealed Classe 와 Enum Classe 의 공통점 + +Sealed Classe와 Enum Class는 모두 하나의 클래스로 여러 가지 상태를 열거할 때 사용한다. +예를 들어, API 통신 결과를 핸들링하고 싶을 때 사용하는데 sealed class 경우 아래와 같이 소스코드를 작성한다. + +예를 들어, sealed class를 사용하면 다음과 같이 API 통신 결과를 핸들링 하기 위해 사용할 수 있다. + +```kotlin +sealed class Resource(val data: T? = null, val message: String? = null) { + class Success(data: T) : Resource(data) + class Loading(data: T? = null) : Resource(data) + class Error(message: String, data: T? = null) : Resource(message, data) +} +``` + +### Sealed Class 와 Enum Class 의 차이점 + +Sealed Class 는 위의 소스코드에서 정의한 것 처럼 class 를 포함해 자식 클래스를 세 가지로 분류한다. +- Object + 상태를 특정하는 변수가 필요하지 않은 경우 메모리 관리를 위해 singleton 기법으로 하나의 객체만을 생성하기 위한 object class +- Class + Sealed class 에서 정의한 변수를 사용할 수 있는 일반적인 class +- Data class + Sealed class 에서 정의한 변수 이외에 특정한 상태를 표현하기 위한 변수를 새로 정의할 수 있는 data class + +Enum Class에서는 그냥 Object 타입의 요소만 추가할 수 있지만, Sealed Cless를 사용하면 더 다양하게 활용할 수 있다. + +```kotlin +sealed class HttpError(val code: Int) { + data class Unauthorized(val reason: String) : HttpError(401) + object NotFound : HttpError(404) +} +``` + +```kotlin +enum class HttpErrorEnum(val code: Int) { + Unauthorized(401), + NotFound(404) +} +``` + +```java +@Metadata("") +public abstract class HttpError { + private final int code; + + public final int getCode() { + return this.code; + } + + private HttpError(int code) { + this.code = code; + } + + // $FF: synthetic method + public HttpError(int code, DefaultConstructorMarker $constructor_marker) { + this(code); + } + + @Metadata("") + public static final class Unauthorized extends HttpError { + @NotNull + private final String reason; + + @NotNull + public final String getReason() { + return this.reason; + } + + public Unauthorized(@NotNull String reason) { + Intrinsics.checkNotNullParameter(reason, "reason"); + super(401, (DefaultConstructorMarker)null); + this.reason = reason; + } + + @NotNull + public final String component1() { + return this.reason; + } + + @NotNull + public final Unauthorized copy(@NotNull String reason) { + Intrinsics.checkNotNullParameter(reason, "reason"); + return new Unauthorized(reason); + } + + // $FF: synthetic method + public static Unauthorized copy$default(Unauthorized var0, String var1, int var2, Object var3) { + if ((var2 & 1) != 0) { + var1 = var0.reason; + } + + return var0.copy(var1); + } + + @NotNull + public String toString() { + return "Unauthorized(reason=" + this.reason + ")"; + } + + public int hashCode() { + String var10000 = this.reason; + return var10000 != null ? var10000.hashCode() : 0; + } + + public boolean equals(@Nullable Object var1) { + if (this != var1) { + if (var1 instanceof Unauthorized) { + Unauthorized var2 = (Unauthorized)var1; + if (Intrinsics.areEqual(this.reason, var2.reason)) { + return true; + } + } + + return false; + } else { + return true; + } + } + } + + @Metadata( + mv = {1, 6, 0}, + k = 1, + d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002¨\u0006\u0003"}, + d2 = {"Lcom/example/helloworld/domain/HttpError$NotFound;", "Lcom/example/helloworld/domain/HttpError;", "()V", "helloworld-application"} + ) + public static final class NotFound extends HttpError { + @NotNull + public static final NotFound INSTANCE; + + private NotFound() { + super(404, (DefaultConstructorMarker)null); + } + + static { + NotFound var0 = new NotFound(); + INSTANCE = var0; + } + } +} + +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\354\203\235\354\204\261\354\236\220.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\354\203\235\354\204\261\354\236\220.md" new file mode 100644 index 00000000..8c292db0 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\354\203\235\354\204\261\354\236\220.md" @@ -0,0 +1,57 @@ +--- +title: '생성자' +lastUpdated: '2022-11-25' +--- +## 생성자 + +### 주 생성자 (Primary Constructor) + +코틀린에선 클래스를 선언하는 동시에 주생성자를 만든다. 주 생성자는 생성자 파라미터를 지정하고 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의하는 두 가지 목적으로 쓰인다. + +(주 생성자에 넣는 파라미터는 자동으로 필드에 추가된다.) + +```kotlin +class User constructor(val username: String) +``` + +#### constructor 키워드 생략 + +```kotlin +class User(val username: String) +``` + +#### 접근 제어자 + +```kotlin +class User private constructor(val username: String) +``` + +접근 제어자를 설정하는 경우엔 `constructor` 키워드를 삭제할 수 없다. + +
+ +### 부 생성자 (Secondary Constructor) + +부 생성자는 클래스 블록 내에 존재하는 생성자이다. 주 생성자에서는 constructor 키워드를 생략할 수 있었지만, 부 생성자는 constructor 키워드를 생략할 수 없다. + +또, 주생성자가 존재한다면 부생성자는 무조건 주생성자에게 직간접적으로 생성을 위임해야 한다. + +따라서 name과 age를 파라미터로 가지는 생성자는 주생성자에게 this(name)을 통해 생성을 위임해야 한다. + +```kotlin +class User(val username: String) { + + var age: Int = 20 + var height: Int = 500 + + constructor(name: String, age: Int) : this(name) { + this.age = age + } + + constructor(name: String, age: Int, height: Int) : this(name, age) { + this.height = height + } +} +``` + +> 만약 주생성자나 부생성자를 구현하지 않을 경우에는 코틀린이 자동으로 인자가 없는 생성자를 자동으로 생성해준다. diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\354\275\224\355\213\200\353\246\260\354\227\220\354\204\234\354\235\230\342\200\205Static.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\354\275\224\355\213\200\353\246\260\354\227\220\354\204\234\354\235\230\342\200\205Static.md" new file mode 100644 index 00000000..ef3aa405 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\354\275\224\355\213\200\353\246\260\354\227\220\354\204\234\354\235\230\342\200\205Static.md" @@ -0,0 +1,76 @@ +--- +title: '코틀린에서의 Static' +lastUpdated: '2024-03-02' +--- + +코틀린에는 static 키워드가 없다. 자바와 같이 정적 변수 혹은 메서드를 사용하려면 다른 방법을 사용해야한다. + +### 클래스 외 필드 선언 + +```java +package foo.bar; + +class Foo { + + public static final String BAR = "bar"; + + public static void baz() { + // Do something + } +} +``` + +자바에서 static을 사용해서 위와 같이 작성해야할떄, 코틀린에선 이렇게 사용할 수 있다. + +```kotlin +package foo.bar + +const val BAR = "bar" + +fun baz() { + // Do something +} +``` + +위와 같이 Foo.kt 파일에 정의한 속성 및 함수는 **자바**에서 각각 FooKt.BAR 및 FooKt.baz()로 접근할 수 있다. Top level에 속성이나 함수 선언이 있으면 뒤에 kt라는 접미사가 자동으로 추가되기 때문이다. + +단, 이러한 규칙을 무시하고 자신이 원하는 이름으로 생성되도록 하려면 파일의 맨 첫 줄에 `@file:JvmName(name: String)`을 사용하면 된다. + +만약 원한다면, 파일 내에 함수/필드 + 클래스를 정의할 수도 있다. + +```kotlin +package foo.bar + +const val BAR = "bar" + +fun baz() { + // Do something +} + +// Foo 클래스 선언 +class Foo { + ... +} +``` + +하지만, 이렇게 할 경우에도 **자바**에서 Foo.kt 파일에 정의한 속성 및 함수에 접근하려면 각각 FooKt.BAR 및 FooKt.baz()로 접근해야한다. + +### companion object + +companion object를 사용하면 위에서 나열했던 문제 없이 자바에서 정적 변수/메서드를 사용했던 것과 동일하게 사용할 수 있다. + +```kotlin +class Foo { + + companion object { + + const val BAR = "bar" + + fun baz() { + // Do something + } + } +} +``` + +companion object 내에 선언된 속성과 함수는 {클래스 이름}.{필드/함수 이름} 형태로 바로 호출할 수 있다. 즉, 위의 Foo클래스 내 companion object에 선언된 baz() 함수는 다음과 같이 호출 가능하다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\355\201\264\353\236\230\354\212\244\342\200\205\354\203\201\354\206\215.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\355\201\264\353\236\230\354\212\244\342\200\205\354\203\201\354\206\215.md" new file mode 100644 index 00000000..995f33ba --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\355\201\264\353\236\230\354\212\244\342\200\205\354\203\201\354\206\215.md" @@ -0,0 +1,95 @@ +--- +title: '클래스 상속' +lastUpdated: '2024-03-02' +--- + +상속은 클래스의 기능을 확장하고자 할때 현재의 클래스의 기능을 모두 가지고 자신만의 기능이 추가된 새로운 클래스를 정의 하는 방법이다. 따라서 상속을 하게 되면 클래스가 상, 하의 계층구조를 가지게 된다. + +코틀린에서 모든 클래스는 공통의 상위클래스(superclass)로 Any 클래스를 가진다. 클래스에 상위 클래스를 선언하지 않아도 기본적으로 Any가 상위 클래스가 된다. + +마치 JAVA에서 Object가 클래스의 기본 상위 클래스가 되는 것괴 비슷하다. 하지만 클래스 내용이 같지는 않다. + +
+Any와 Object 비교 +
+ +Kotlin의 Any보다 Java의 Object가 가지는 메서드의 수가 더 많다. + +```kotlin +public open class Any { + + public open operator fun equals(other: Any?): Boolean + public open fun hashCode(): Int + public open fun toString(): String +} +``` + +```java +public class Object { + + private static native void registerNatives(); + static { + registerNatives(); + } + + public final native Class getClass(); + + public native int hashCode(); + + public boolean equals(Object obj) { + return (this == obj); + } + + protected native Object clone() throws CloneNotSupportedException; + + public String toString() { + return getClass().getName() + "@" + Integer.toHexString(hashCode()); + } + + public final native void notify(); + + public final native void notifyAll(); + + public final native void wait(long timeout) throws InterruptedException; + + public final void wait(long timeout, int nanos) throws InterruptedException { + if (timeout < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException( + "nanosecond timeout value out of range"); + } + + if (nanos >= 500000 || (nanos != 0 && timeout == 0)) { + timeout++; + } + + wait(timeout); + } + + public final void wait() throws InterruptedException { + wait(0); + } + + protected void finalize() throws Throwable { } +} +``` + +
+
+ +
+ +### 상속 + +특정 클래스를 상속받기 위해선 아래와 같이 코드를 작성해주면 된다. + +```kotlin +open class Base(p: Int) +class Derived(p: Int) : Base(p) +``` + +파생 클래스가 기본 생성자를 가진다면, 베이스 클래스(base class)는 기본 생성자의 인자를 사용해서 바로 초기화 될 수 있어야 한다. 따라서, 클래스를 상속받을때 부모 클래스의 주 생성자를 어떻게 호출할지를 같이 적어줘야한다. + diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\355\225\204\353\223\234\354\231\200\342\200\205\354\240\221\352\267\274\354\236\220\342\200\205\353\251\224\354\204\234\353\223\234.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\355\225\204\353\223\234\354\231\200\342\200\205\354\240\221\352\267\274\354\236\220\342\200\205\353\251\224\354\204\234\353\223\234.md" new file mode 100644 index 00000000..522d39dc --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\201\264\353\236\230\354\212\244/\355\225\204\353\223\234\354\231\200\342\200\205\354\240\221\352\267\274\354\236\220\342\200\205\353\251\224\354\204\234\353\223\234.md" @@ -0,0 +1,59 @@ +--- +title: '필드와 접근자 메서드' +lastUpdated: '2024-03-02' +--- + +주 생성자를 만들면 클래스의 필드가 알아서 생성된다. + +```kotlin +class User(var username: String) +``` + +정말 간단하다! + +접근자 메서드인 `getter`와 `setter`는 코틀린이 알아서 만들어준다. + +```kotlin +class User( + var name: String + val age: Int +) +``` + +읽기 전용 필드를 만들 수도 있다.
+`var` 대신 `val`을 앞에 붙여 선언하면, getter 함수만 생성된다. + +val은 수정할 수 없는 필드기 때문에, final과 비슷하다. + +필드에 접근할 때는 그냥 변수에 접근하듯이 사용하면, 코틀린에서 내부적으로 접근자 메서드를 사용하는 것 처럼 작동한다. + +```kotlin +class User( + var name: String + val age: Int +) + +fun main(args: Array) { + + val user = User("user", 17) + user.username = "useruser" + + println("username : ${user.name}") + println("age : ${user.age}") + +} +``` + +getter/setter를 원하는 대로 정의할 수도 있다. + +```kotlin +class Rectangle( + val height: int, + val width: int +) { + val isSquare: Boolean + get() { + return height == width + } +} +``` diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\225\250\354\210\230/\352\270\260\353\263\270\354\235\270\354\236\220.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\225\250\354\210\230/\352\270\260\353\263\270\354\235\270\354\236\220.md" new file mode 100644 index 00000000..cd0e2892 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Kotlin/\355\225\250\354\210\230/\352\270\260\353\263\270\354\235\270\354\236\220.md" @@ -0,0 +1,53 @@ +--- +title: '기본인자' +lastUpdated: '2024-03-02' +--- + +C++에는 기본인자라는 개념이 있다. + +```c++ + +#include + +int add(int a, int b=10) { + return a + b; +} + +int main() +{ + int res = add(5, 7); + std::cout << "res : " << res << std::endl; + + res = add(5); + std::cout << "res : " << res << std::endl; + + return 0; +} + +``` + +``` +res: 12 +res: 15 +``` + +`add`는 인자를 2개 받는 함수지만, b에 기본인자로 10을 지정해줬기 떄문에 인자를 하나만 입력해줘도 작동한다. + +코틀린에서도 이와 같이 기본인자를 지정할 수 있다. + +```kotlin + +fun add(a: Int, b: Int = 10): Int { + return a + b; +} + +fun main() { + var res = add(5, 7); + println("res : $res"); + + res = add(5); + println("res : $res"); + +} + +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/Rc\342\200\205\355\203\200\354\236\205\352\263\274\342\200\205Weak\342\200\205\355\203\200\354\236\205.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/Rc\342\200\205\355\203\200\354\236\205\352\263\274\342\200\205Weak\342\200\205\355\203\200\354\236\205.md" new file mode 100644 index 00000000..51fe58db --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/Rc\342\200\205\355\203\200\354\236\205\352\263\274\342\200\205Weak\342\200\205\355\203\200\354\236\205.md" @@ -0,0 +1,209 @@ +--- +title: 'Rc 타입과 Weak 타입' +lastUpdated: '2024-03-02' +--- + +- 소유권 규칙에 따라 Rust에서 어떤 값은 여러 소유자를 가질 수 없다. +- Reference Counted를 의미하는 Rc는 힙 메모리에 할당된 타입 T 값의 소유권을 공유할 수 있게 해주는 타입이다. 즉, 스마트 포인터 Rc를 사용하면 타입 T의 값에 대한 여러 개의 소유자를 만들 수 있다. +- 기본적으로, Rc 타입은 Clone Trait을 구현하고 있고 clone을 호출해서 T 값에 대한 새로운 포인터를 생성하며, 모든 Rc 포인터가 해제되면 메모리에 할당된 T 값이 drop되는 구조이다. + - Rust에서 공유된 참조자는 수정할 수 없는데, Rc 타입 또한 예외가 아니며 일반적인 방법으로는 mutable한 참조를 얻을 수 없다. 만약, mutable 한 참조가 필요하면 Cell 타입이나 RefCell 타입을 함께 사용해야 한다. + +- Rc 타입은 원자적이지 않은 참조 카운팅으로 오버헤드가 낮은 장점이 있지만 단일 스레드에서만 사용 가능하다. + - 따라서, Rc 타입은 스레드 간 전송을 위한 Trait인 Send 타입을 구현하지 않다. 한편, 원자적 참조 카운팅 타입인 Arc(Atomic Reference Counted)가 있다. Arc 타입은 다중 스레드 간 사용이 가능하지만 Rc 타입보다 오버헤드가 큰 단점이 있다. + +- `std::rc` 모듈에는 Rc 타입과 더불어 약한 참조인 Weak 타입이 존재한다. + - Rc 타입을 downgrade해서 `Weak` 타입을 얻을 수 있고, Weak 타입을 upgrade해서 `Option>` 타입을 얻을 수 있다. Weak 타입에서 Rc 타입을 얻고자 upgrade를 사용할 때, T 값이 이미 drop 되었다면 None을 리턴한다. 이 말은 Rc 타입과 다르게 Weak 타입은 메모리에 할당된 **타입 T의 값이 살아있는 것을 보장하지 않는다**는 의미이다. + - 즉, Rc 타입의 강한 참조 카운트가 0이 되면 T 값이 drop 되는데, 약한 참조 카운트를 의미하는 Weak 타입은 영향을 미치지 않는다. + +- 앞서 설명했듯, Rc 타입이 갖고 있는 강한 참조 카운트가 0이 되지 않으면 T 값은 drop 되지 않는다. 이는 T 값이 결코 drop 될 수 없는 순환 참조를 야기할 수도 있다. T 값의 drop에 영향을 미치지 않는 Weak 타입은 이런 문제의 상황에서 유용하게 사용될 수 있다. + +자세한 내용은 아래에서 코드와 함께 살펴보자. + +## Rc 타입 사용하기 + + +```rust +fn main() { + let rc = Rc::new(10); + println!("[ 1 ]"); + println!("value of rc => {}", rc); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); +} + +``` + +Rc 타입은 new 함수를 통해 생성할 수 있다. 위 코드 3번째 라인에서 볼 수 있듯이 Rc 타입은 Deref Trait을 구현하고 있어 자동으로 역참조 된다. Rc가 생성될 때 strong_count 값은 1로 초기화된다. + +아래 코드는 clone을 통해 타입 T 값에 대한 여러 개의 참조자를 만드는 것을 보여준다. + + +```rust +fn main() { + let rc = Rc::new(10); + println!("[ 1 ]"); + println!("value of rc => {}", rc); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); + + { + let rc2 = rc.clone(); + println!("[ 2 ]"); + println!("value of rc2 => {}", rc2); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); + + let rc3 = Rc::clone(&rc); + println!("[ 3 ]"); + println!("value of rc3 => {}", rc3); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); + } + + println!("[ 4 ]"); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); +} + +``` + +Rc 타입은 Clone Trait을 구현하고 있고 `rc.clone()`과 `Rc::clone()` 두 가지 방식으로 호출할 수 있다. clone을 호출하면 타입 T 값에 대한 새로운 참조자가 생성되며, strong_count는 1 증가한다. rc2와 rc3가 코드 블록을 벗어남에 따라 strong_count는 총 2가 감소해 마지막으로 출력되는 strong_count의 값은 1이 된다. 생성된 모든 Rc 타입이 drop 되면 strong_count가 0이 되면서 힙에 할당된 타입 T 값 역시 drop 된다. + + +## Weak 타입 사용하기 + +Weak 타입에도 생성 메서드 new가 존재하지만, 인자로 어떠한 타입 값도 받지 않는다. 즉, 타입 T에 대한 어떠한 값도 메모리에 할당되지 않다. 따라서, new로 새롭게 생성한 Weak 타입의 upgrade 메서드는 항상 None을 리턴한다. + +```rust +fn main() { + let weak: Weak = Weak::new(); + assert!(weak.upgrade().is_none()); +} +``` + + +아래 코드에서 Rc 타입을 Weak 타입으로 downgrade 하고, Weak 타입을 Rc 타입으로 upgrade 하는 과정을 볼 수 있다. 예제를 실행해 strong_count와 weak_count 값의 변화를 확인해보자. + +```rust +fn main() { + let rc = Rc::new(10); + println!("[ 1 ]"); + println!("value of rc => {}", rc); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); + + let weak = Rc::downgrade(&rc); + println!("[ 2 ]"); + println!("value of weak => {}", unsafe { &*weak.as_ptr() }); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); + + if let Some(rc2) = Weak::upgrade(&weak) { + println!("[ 3 ]"); + println!("value of rc2 => {}", rc2); + println!("strong count => {}", Rc::strong_count(&rc)); + println!("weak count => {}", Rc::weak_count(&rc)); + } else { + println!("강한 참조가 남아 있지 않다."); + } +} +``` + +9번째 줄에서 값을 출력하는 방식이 Rc와 다른 것을 확인할 수 있다. Weak 타입은 Rc 타입과 다르게 Deref Trait을 구현하고 있지 않기 때문에 자동으로 역참조가 일어나지 않다. 또한, as_ptr() 메서드를 통해 T 값에 접근할 수 있지만, 아직 타입 T 값이 메모리에서 drop 되지 않았다는 것을 알 수 없기 때문에 unsafe 키워드를 사용해야 한다. 그래서 개발자는 Weak 참조자가 가리키는 값이 아직 유효하다는 것을 보장할 수 있을 때 사용해야 한다. + + +Weak 타입은 Rc 타입으로 upgrade 할 수 있는데, downgrade와 달리 upgrade는 Option 을 반환한다. 이는, 앞서 설명했듯이 Weak 타입이 메모리에 할당된 타입 T 값의 유효성을 보장하지 않기 때문이다. 만약 strong_count가 0이 되어 타입 T 값이 drop 된 상태라면, upgrade 메서드는 None을 반환할 것이다. + + +## 순환 참조의 문제 + +Rc 타입 간에는 순환 참조 문제가 발생할 수 있는데, Weak 타입을 사용하여 이를 해결할 수 있다. 트리 데이터 구조 예제 코드를 통해 순환 참조가 일어날 수 있는 상황에서 Weak 타입을 활용하는 방법을 알아보자. + +```rust +#[derive(Debug)] +struct Node { + value: i32, + children: RefCell>>, +} +``` + +먼저, 트리의 노드 구조체를 만든다. 이 노드는 하나의 값과 자식 노드들의 참조자들을 가지고 있다. 여기서 자식 노드는 Rc 타입으로 소유권을 공유하고 직접 접근할 수 있다. 또한 자식 노드가 수정될 수 있도록 RefCell 타입으로 감쌌다. + + +이제, 이 노드 구조체를 이용해 leaf와 leaf를 자식 노드로 가지는 branch를 만들어 보자. + + +```rust +fn main() { + let leaf = Rc::new( + Node { + value: 3, + children: RefCell::new(vec![]), + } + ); + + let branch = Rc::new( + Node { + value: 5, + children: RefCell::new(vec![Rc::clone(&leaf)]), + } + ); +} +``` + +leaf는 자식이 없는 Node이고, branch는 leaf를 자식으로 갖는 Node이다. 우리는 이제 branch.children을 통해 branch에서 leaf로 접근할 수 있다. 하지만, leaf가 부모 노드에 대한 참조자를 알지 못하기 때문에 leaf에서 branch로는 접근이 불가능한 상황이다. 이를 위해 자식 노드가 부모 노드로 접근할 수 있도록 하는 parent 참조자를 추가해야 한다. + + + +parent 타입을 추가함으로써 leaf가 부모인 branch를 참조하고, branch가 자식인 leaf를 참조한다는 것을 쉽게 생각해볼 수 있다. 이때 parent의 타입을 children과 같이 Rc 타입으로 만든다면, strong_count 값이 0이 될 수 없는 순환 참조 문제를 야기할 수 있다. 따라서, 우리는 순환 참조 문제를 피하기 위해 parent 타입을 Weak 타입으로 만들 것이다. + + +```rust +#[derive(Debug)] +struct Node { + value: i32, + parent: RefCell>, + children: RefCell>>, +} + +``` + +이제, 노드는 부모 노드를 소유하지는 않지만 참조할 수 있게 되었다. + + +```rust +fn main() { + let leaf = Rc::new(Node { + value: 3, + parent: RefCell::new(Weak::new()), + children: RefCell::new(vec![]), + }); + + println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); + + let branch = Rc::new(Node { + value: 5, + parent: RefCell::new(Weak::new()), + children: RefCell::new(vec![Rc::clone(&leaf)]), + }); + + *leaf.parent.borrow_mut() = Rc::downgrade(&branch); + + println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); +} + +``` + +16번째 줄에서 leaf 노드의 parent에 branch 노드의 Weak 참조자를 넣어 주는 것을 볼 수 있다. + + +아래는 위 코드를 출력한 결과이다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/918510c0-96a6-4276-a326-e7cc9ea4d3db) + +결과를 보면 Weak 참조자가 (Weak)로 출력되는 것을 알 수 있고, 무한 출력이 없다는 것은 순환 참조를 야기하지 않는다는 것을 의미한다. + +--- + 참고 + - https://doc.rust-lang.org/stable/std/rc/struct.Rc.html + - https://doc.rust-lang.org/book/ch15-06-reference-cycles.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/String.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/String.md" new file mode 100644 index 00000000..1f78a24d --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/String.md" @@ -0,0 +1,103 @@ +--- +title: 'String' +lastUpdated: '2024-03-02' +--- + +### String과 str + +```rust +fn main() { + let s1: &str = "World"; + println!("s1: {s1}"); + + let mut s2: String = String::from("Hello "); + println!("s2: {s2}"); + s2.push_str(s1); + println!("s2: {s2}"); + + let s3: &str = &s2[6..]; + println!("s3: {s3}"); +} +``` + +- Rust에서 `&str`은 문자열 슬라이스에 대한 (불변) 참조이다. (C++의 `const char*`와 유사하지만 항상 유효한 문자열을 가리킨다) +- `String`은 문자열을 담을 수 있는 버퍼이다. (문자열을 이루는 바이트에 대한 백터(Vec)이며, 가리키고 있는 문자열은 String의 소유이다.) + +### String 생성 + +- new 함수를 이용하여 스트링을 생성할 수 있다. + +```rust +let mut s = String::new(); +``` + +- `to_string` 메소드 또는 `String::from()`을 사용하여 스트링 리터럴로부터 String을 생성할 수 있다. + +```rust +let data = "initial contents"; +let s = data.to_string(); +let s = "initial contents".to_string(); + +let s = String::from("initial contents"); +``` + +- `+` 연산자나 `format!` 매크로를 사용하여 편리하게 String 값들을 서로 접합(concatenation)할 수 있다. + +### 포맷팅 + +- 아래와 같이 변수를 포맷팅하여 출력하는 여러가지 방법이 있다. +- `format()` 매크로를 사용하여 포맷팅 결과를 String으로 반환받을 수 도 있다. + +```rust +fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + // a라는 배열을 출력하는 여러가지 방법 + + println!("a: {a}"); // 일반 출력 + // println!("a: {}", a);과 동일 + /* + 배열은 일반 출력이 불가능하다. + `[i8; 10]` cannot be formatted with the default formatter + */ + + println!("a: {a:?}"); // 디버깅 출력 + // println!("a: {:?}", a);과 동일 + /* + a: [42, 42, 42, 42, 42, 0, 42, 42, 42, 42] + */ + + println!("a: {a:#?}"); // 예쁜 디버깅 출력 + // println!("a: {:#?}", a);과 동일 + /* + a: [ + 42, + 42, + 42, + 42, + 42, + 0, + 42, + 42, + 42, + 42, + ] + */ +} +``` + +### escape + +- `r`을 붙이면 특수문자를 escape하기 위한 백슬래시(`\`)를 적지 않아도 된다. (`r"\n" == "\\n"`) +- string 양쪽에 `#`를 붙이면 붙인 갯수만큼 쌍따옴표를 문자열에 포함할 수 있다. + +### String 내부 인덱싱 + +- Rust String은 인덱싱을 지원하지 않는다. (`[0]`과 같이 참조할 수 없다.) +- String은 `Vec`을 감싼 것인데, 유니코드 스칼라 값이 저장소의 2바이트를 차지하거나 유효하지 않은 문자가 껴있는 경우에 대한 처리가 힘들기 때문에 단순 인덱싱을 불가능하게 만들었다. +- 원한다면 슬라이스를 사용하거나, `.chars()` 혹은 `.bytes()`로 interate 할 수 있다. + +--- +참고 +- https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/std/index.html +- https://rinthel.github.io/rust-lang-book-ko/ch08-02-strings.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/Trait.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/Trait.md" new file mode 100644 index 00000000..7e4676bf --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/Trait.md" @@ -0,0 +1,248 @@ +--- +title: 'Trait' +lastUpdated: '2024-03-02' +--- + +- 여러 타입(type)의 공통된 행위/특성을 표시한 것을 Trait이라고 한다. Rust에서의 Trait는 (약간의 차이는 있지만) 다른 프로그래밍 언어에서의 인터페이스(interface)와 비슷한 개념이다. +- Trait는 trait 라는 키워드를 사용하여 선언하며, trait 블럭 안에 공통 메서드의 원형(method signature)을 갖는다 + + ```rust + trait Draw { + fn draw(&self, x: i32, y: i32); + } + ``` + +- `Rectangle` 이라는 구조체에 `Draw` 라는 trait를 구현하기 위해서는 `impl 트레이트명 for 타입명`과 같이 정의하고 trait 안의 메서드들을 구현하면 된다. + + ```rust + struct Rectangle { + width: i32, + height: i32 + } + + impl Draw for Rectangle { + fn draw(&self, x: i32, y: i32) { + let x2 = x + self.width; + let y2 = y + self.height; + println!("Rect({},{}~{},{})", x, y, x2, y2); + } + } + + struct Circle { + radius: i32 + } + + impl Draw for Circle { + fn draw(&self, x: i32, y: i32) { + println!("Circle({},{},{})", x, y, self.radius); + } + } + ``` + +- `Draw`의 구현체는 `impl Draw`와 같이 함수의 인자 혹은 반환 타입으로 명시하여 사용할 수 있다. + + ```rust + fn draw_it(item: impl Draw, x: i32, y: i32) { + item.draw(x, y); + } + + fn main() { + let rect = Rectangle { width: 20, height: 20 }; + let circle = Circle { radius: 5 }; + + draw_it(rect, 1, 1); + draw_it(circle, 2, 2); + } + ``` + +## Trait Bound + +- 제네릭에 **Trait Bound**를 추가해서 사용할 수도 있다. + - 복수 개의 Trait을 갖는다면 `+`를 사용하여 여러 Trait을 지정할 수 있다. + +```rust +fn draw_it(item: impl Draw, x: i32, y: i32) { + item.draw(x, y); +} + +fn draw_it(item: T, x: i32, y: i32) { + item.draw(x, y); +} + +trait Print {} + +fn draw_it(item: (impl Draw + Print), x: i32, y: i32) { + item.draw(x, y); +} + +fn draw_it(item: T, x: i32, y: i32) { + item.draw(x, y); +} + +// 이렇게 쓰는 것도 가능 +fn draw_it(item: T, x: i32, y: i32) + where T: Draw + Print +{ + item.draw(x, y); +} +``` + +## dyn + +- Trait Bound, impl Trait을 사용하는 것은 결과적으로 정적 디스패치를 구현하는 것이다. 즉, 컴파일러가 컴파일 타임에 타입들을 검사하고 내부적으로 명시된 타입들에 대한 코드를 구현하는 것이다. +- Rust에서 **동적 디스패치**를 구현하기 위해서는 `dyn` 키워드를 사용해야한다. +- 이는 컴파일 비용을 줄이는 대신 런타임 비용을 증가시킨다. +- `dyn Trait`의 참조자는 인스턴스 객체를 위한 포인터와 `vtable`을 가리키는 포인터 총 두 개의 포인터를 갖는다. 그리고 런타임에 이 함수가 필요해지면 `vtable`을 참조해 포인터를 얻게 된다. + +```rust +fn get_car(is_sedan: bool) -> Box{ + if is_sedan { + Box::new(Sedan) + } else { + Box::new(Suv) + } +} +``` + +## 디폴트 구현 + +- Trait은 일반적으로 공통 행위(메서드)에 대해 어떠한 구현도 하지 않는다. +- 하지만, 필요한 경우 Trait의 메서드에 디폴트로 실행되는 구현을 추가할 수 있다. + +## 뉴타입 패턴 (newtype pattern) + +- 튜플 구조체 내에 새로운 타입을 만드는 **뉴타입 패턴**을 사용하면 외부 타입에 대해 외부 트레잇을 구현할 수 있다. + +- `Vec`에 대하여 `Display`을 구현하고 싶다고 가정해보자. + - `Display` 트레잇과 `Vec` 타입은 라이브러리에 정의되어 있기 때문에 바로 구현하는 것은 불가능하다. + - 이때 뉴타입 패턴을 적용하여 `Vec`의 인스턴스를 가지고 있는 `Wrapper` 구조체를 만들 수 있다. 그리고 `Wrapper` 상에 `Display`를 구현하고 `Vec` 값을 이용할 수 있여 구현할 수 있다. + +```rust +use std::fmt; + +struct Wrapper(Vec); + +impl fmt::Display for Wrapper { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{}]", self.0.join(", ")) + } +} + +fn main() { + let w = Wrapper(vec![String::from("hello"), String::from("world")]); + println!("w = {}", w); +} +``` + + +## Derive + +- Rust는 derive 키워드를 사용해서 구조체가 특정 기본 trait 구현 기능을 사용하도록 할 것인지를 정의할 수 있도록 한다. +- 구조체 위에 `#[derive()]`와 같이 적고, 괄호 안에 구현할 항목을 `,`로 구분하여 적으면 된다. + - 가능한 항목은 아래와 같은 것들이 있다. + - `Eq`, `PartialEq`, `Ord`, `PartialOrd`: 동등, 순서 비교 + - `Clone`: `&T`로부터 `T`를 복사하는 메서드를 사용하는지 여부 + - `Copy`: 소유권 이전(move) 대신 copy가 동작하도록 할 것인지 여부 + - `Hash`: `&T`로부터 hash를 계산할 것인지 여부 + - `Default`: data type 대신 빈 instance를 사용할 수 있게 할 것인지 여부 + - `Debug`: `{:?}` 포매터에 대한 출력을 사용하는지 여부 + + ```rust + // `Centimeters`, a tuple struct that can be compared + #[derive(PartialEq, PartialOrd)] + struct Centimeters(f64); + + // `Inches`, a tuple struct that can be printed + #[derive(Debug)] + struct Inches(i32); + + impl Inches { + fn to_centimeters(&self) -> Centimeters { + let &Inches(inches) = self; + Centimeters(inches as f64 * 2.54) + } + } + + // `Seconds`, a tuple struct with no additional attributes + struct Seconds(i32); + + fn main() { + let _one_second = Seconds(1); + + // Error: `Seconds` can't be printed; it doesn't implement the `Debug` trait + //println!("One second looks like: {:?}", _one_second); + // TODO ^ Try uncommenting this line + + // Error: `Seconds` can't be compared; it doesn't implement the `PartialEq` trait + //let _this_is_true = (_one_second == _one_second); + // TODO ^ Try uncommenting this line + + let foot = Inches(12); + + println!("One foot equals {:?}", foot); + + let meter = Centimeters(100.0); + + let cmp = + if foot.to_centimeters() < meter { + "smaller" + } else { + "bigger" + }; + + println!("One foot is {} than one meter.", cmp); + } + ``` + +## 연산자 오버로딩 + +- Rust는 Trait으로 연산자 오버로딩을 지원한다. + +```rust +use std::ops; + +struct Foo; +struct Bar; + +#[derive(Debug)] +struct FooBar; + +#[derive(Debug)] +struct BarFoo; + +// The `std::ops::Add` trait is used to specify the functionality of `+`. +// Here, we make `Add` - the trait for addition with a RHS of type `Bar`. +// The following block implements the operation: Foo + Bar = FooBar +impl ops::Add for Foo { + type Output = FooBar; + + fn add(self, _rhs: Bar) -> FooBar { + println!("> Foo.add(Bar) was called"); + + FooBar + } +} + +// By reversing the types, we end up implementing non-commutative addition. +// Here, we make `Add` - the trait for addition with a RHS of type `Foo`. +// This block implements the operation: Bar + Foo = BarFoo +impl ops::Add for Bar { + type Output = BarFoo; + + fn add(self, _rhs: Foo) -> BarFoo { + println!("> Bar.add(Foo) was called"); + + BarFoo + } +} + +fn main() { + println!("Foo + Bar = {:?}", Foo + Bar); + println!("Bar + Foo = {:?}", Bar + Foo); +} +``` + +--- +참고 +- http://rust-lang.xyz/rust/article/22-Trait +- https://doc.rust-lang.org/rust-by-example/trait.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\353\217\231\354\213\234\354\204\261.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\353\217\231\354\213\234\354\204\261.md" new file mode 100644 index 00000000..a176e4fb --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\353\217\231\354\213\234\354\204\261.md" @@ -0,0 +1,235 @@ +--- +title: '동시성' +lastUpdated: '2024-03-02' +--- + +- 대부분의 요즘 운영 체제에서, 실행되는 프로그램의 코드는 프로세스 내에서 실행되고, 프로그램 내에서도 동시에 실행되는 독립적인 스레드를 가진다. +- 계산 부분을 여러 개의 스레드로 쪼개는 것은 프로그램이 동시에 여러 개의 일을 할 수 있기 때문에 성능을 향상시킬 수 있지만, 프로그램을 복잡하게 만들기도 한다. +- 러스트 표준 라이브러리는 언어 런타임의 크기를 작게 유지하기 위해 1:1 스레드 구현만 제공한다. + +## 스레드 생성 + +- 새로운 스레드를 생성하기 위해서는 `thread::spawn` 함수를 호출하고, 새로운 스레드 내에서 실행하기를 원하는 코드가 담겨 있는 클로저를 넘기면 된다. + +```rust +use std::thread; +use std::time::Duration; + +fn main() { + thread::spawn(|| { + for i in 1..10 { + println!("hi number {} from the spawned thread!", i); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hi number {} from the main thread!", i); + thread::sleep(Duration::from_millis(1)); + } +} +``` + +## JoinHandle + +- `thread::spawn`의 반환 타입은 `JoinHandle`이며, `join` 메소드를 호출했을 때 그 스레드가 끝날때까지 기다리는 소유된 값이다. +- `JoinHandle`을 저장하면 스레드가 완전히 실행되는 것을 보장할 수 있다. +- 핸들에 대해 `join`을 호출하는 것은 핸들에 대한 스레드가 종료될 때까지 현재 실행중인 스레드를 블록하여 그 스레드의 작업을 수행하거나 종료되는 것을 방지한다. + +```rust +use std::thread; +use std::time::Duration; + +fn main() { + let handle = thread::spawn(|| { + for i in 1..10 { + println!("hi number {} from the spawned thread!", i); + thread::sleep(Duration::from_millis(1)); + } + }); + + for i in 1..5 { + println!("hi number {} from the main thread!", i); + thread::sleep(Duration::from_millis(1)); + } + + handle.join().unwrap(); +} +``` + +## 스레드에 move 클로저 사용하기 + +- 어떤 스레드의 데이터를 다른 스레드 내에서 사용하도록 하기 위해 `move` 클로저와 `thread::spawn`를 함께 사용할 수 있다. + +- `v`에 대한 소유권이 메인 스코프에 속하기 때문에 이 예제에서 `move` 클로저를 사용하지 않으면 컴파일러는 새로 생성한 Thread가 `v`를 안전하게 사용할 수 없다고 판단한다. 따라서 `move`를 함께 명시해줘야한다. + +```rust +use std::thread; + +fn main() { + let v = vec![1, 2, 3]; + + let handle = thread::spawn(move || { + println!("Here's a vector: {:?}", v); + }); + + handle.join().unwrap(); +} +``` + +## 메세지 패싱 + +- `mpsc::channel` 함수를 사용하여 새로운 채널을 생성할 수 있다. + - `mpsc`는 복수 생성자, 단수 소비자 (multiple producer, single consumer)를 나타낸다. + +- 하위 스레드에서 채널로 string을 send하고, 메인 스레드에서 받아 출력하는 예제이다. + + ```rust + use std::thread; + use std::sync::mpsc; + + fn main() { + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let val = String::from("hi"); + tx.send(val).unwrap(); + }); + + let received = rx.recv().unwrap(); + println!("Got: {}", received); + } + + /* + 출력 결과: + Got: hi + */ + ``` + +- 채널의 수신 단말은 `recv()`, `try_recv()` 두가지의 메서드를 가지고 있다. (receive 의 줄임말이다.) + - `recv()`는 메인 스레드의 실행을 블록시키고 채널로부터 값이 보내질 때까지 기다릴 것이다. + - 그리고 값이 전달되면 `recv()`는 `Result` 형태로 이를 반환하고, 채널의 송신 단말이 닫히면 더 이상 어떤 값도 오지 않을 것이라는 의미의 에러를 반환할 것이다. + +- 생성자가 여러개인 예시를 살펴보자. + ```rust + // --snip-- + + let (tx, rx) = mpsc::channel(); + + let tx1 = mpsc::Sender::clone(&tx); + thread::spawn(move || { + let vals = vec![ + String::from("hi"), + String::from("from"), + String::from("the"), + String::from("thread"), + ]; + + for val in vals { + tx1.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + thread::spawn(move || { + let vals = vec![ + String::from("more"), + String::from("messages"), + String::from("for"), + String::from("you"), + ]; + + for val in vals { + tx.send(val).unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + + for received in rx { + println!("Got: {}", received); + } + + // --snip-- + + ``` + - `mpsc::Sender::clone(&tx);`을 통해 생성자를 복제해서 각각의 생성자를 두 개의 하위 스레드에서 사용했다. + +## 공유 상태 동시성 + +- 뮤텍스는 상호 배제 (mutual exclusion)의 줄임말로서, 주어진 시간에 오직 하나의 스레드만 데이터 접근을 허용한다. +- `Mutex`는 연관함수 new를 사용하여 만들어지고, `lock` 메소드를 사용하여 락을 얻는다. +- `Mutex`를 사용하여 여러 스레드들 사이에서 값을 공유해보자. 10개의 스레드를 돌리고 이들이 카운터 값을 1만큼씩 증가 시켜서, 카운터를 0에서 10으로 증가시키는 예제이다. + + ```rust + use std::sync::Mutex; + use std::thread; + + fn main() { + let counter = Mutex::new(0); + let mut handles = vec![]; + + for _ in 0..10 { + let handle = thread::spawn(move || { + let mut num = counter.lock().unwrap(); + + *num += 1; + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Result: {}", *counter.lock().unwrap()); + } + ``` + + - 사실 이 코드에선 예외가 발생한다! counter의 소유권을 여러 스레드로 이동시킬 수 없기 때문이다. + - 이 문제를 해결하려면 복수 소유자 메소드인 `Rc`의 Thread-safe 버전인 `Arc`를 사용해야한다. + + ```rust + use std::sync::{Mutex, Arc}; + use std::thread; + + fn main() { + let counter = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let counter = Arc::clone(&counter); + let handle = thread::spawn(move || { + let mut num = counter.lock().unwrap(); + + *num += 1; + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + println!("Result: {}", *counter.lock().unwrap()); + } + + /* + 출력결과: + Result: 10 + */ + ``` + + +## Sync와 Send Trait + +- Rust에서는 언어상에서 지원하는 동시성 기능이 매우 적다. 대신 위에서 살펴봤던 기능들은 모두 표준 라이브러리에 구현되어있는 것이다. +- `Send` 마커 트레잇은 `Send`가 구현된 타입의 소유권이 스레드 사이에서 이전될 수 있음을 나타낸다. + - 대부분의 Rust 타입이 `Send`이지만 예외가 있다. 대표적으로 `Rc`는 클론하여 다른 스레드로 복제본의 소유권을 전송하는 경우 두 스레드 모두 동시에 참조 카운트 값을 갱신할 가능성이 있기 때문에 `Send`가 될 수 없다. + - `Send` 타입으로 구성된 어떤 타입은 또한 자동적으로 `Send`로 마킹된다. +- `Sync` 마커 트레잇은 `Sync`가 구현된 타입이 여러 스레드로부터 안전하게 참조 가능함을 나타낸다. + - 바꿔 말하면, 만일 `&T` (`T`의 참조자)가 Send인 경우 (참조자가 다른 스레드로 안전하게 보내질 수 있는 경우) `T`는 Sync를 수행한다. + +--- +참고 +- https://rinthel.github.io/rust-lang-book-ko/ch16-01-threads.html +- https://nnethercote.github.io/perf-book/parallelism.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\353\251\200\355\213\260\342\200\205\354\212\244\353\240\210\353\223\234\342\200\205\354\233\271\342\200\205\354\204\234\353\262\204\342\200\205\353\247\214\353\223\244\352\270\260.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\353\251\200\355\213\260\342\200\205\354\212\244\353\240\210\353\223\234\342\200\205\354\233\271\342\200\205\354\204\234\353\262\204\342\200\205\353\247\214\353\223\244\352\270\260.md" new file mode 100644 index 00000000..096b99b1 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\353\251\200\355\213\260\342\200\205\354\212\244\353\240\210\353\223\234\342\200\205\354\233\271\342\200\205\354\204\234\353\262\204\342\200\205\353\247\214\353\223\244\352\270\260.md" @@ -0,0 +1,383 @@ +--- +title: '멀티 스레드 웹 서버 만들기' +lastUpdated: '2024-03-02' +--- + +> https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html
+> Docs에 있는 예제를 따라 정리한 글임을 미리 밝힙니다. + +- 요청했을 때 아래와 같은 웹 화면을 반환하는 웹 서버를 만들어보자! + + + +- Rust 프로젝트는 아래와 같이 생성할 수 있다. + ```bash + $ cargo new hello --bin + Created binary (application) `hello` project + $ cd hello + ``` + +## TCP 연결 처리 + +- 우선 `std::net`를 사용해서 하나의 TCP 연결을 처리하는 코드를 짜볼 것이다. +- `TcpListener` 를 사용하여 `127.0.0.1:7878` 주소로 TCP연결을 수신할 수 있다. +- 위 코드에서 `bind` 함수는 `new` 함수처럼 동작하며 `TcpListner`의 새 인스턴스를 반환한다. + - bind 함수는 바인딩의 성공여부를 나타내는 `Result`를 반환한다. +- `TcpListener`의 incoming 메소드는 스트림의 차례에 대한 iterator를 반환한다. 각각의 stream 은 클라이언트와 서버간의 열려있는 커넥션을 의미한다. + +```rust +use std::net::TcpListener; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + + println!("Connection established!"); + } +} +``` + +## 요청 데이터 읽기 + +- 브라우저로부터의 요청을 읽는 기능을 구현하기 위해 `handle_connection` 이라는 함수를 새로 만들어보자. + +```rust +use std::io::prelude::*; +use std::net::TcpStream; +use std::net::TcpListener; + +fn main() { + let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + + handle_connection(stream); + } +} + +fn handle_connection(mut stream: TcpStream) { + let mut buffer = [0; 512]; + + stream.read(&mut buffer).unwrap(); + + println!("Request: {}", String::from_utf8_lossy(&buffer[..])); +} +``` + +- `std::io::prelude`를 가져와 스트림으로부터 읽고 쓰는것을 허용하는 특성에 접근할 수 있도록 한다. +- `handle_connection` 함수에선, stream 매개변수를 가변으로 만들어 줬습니다. 이유는 TcpStream 인스턴스가 내부에서 어떤 데이터가 우리에게 반환되는지 추적하기 때문이다. 요청하는것에 따라 더 많은 데이터를 읽거나, 다음 요청때까지 데이터를 저장하는 등 내부의 상태가 변경될 수 있기에 `mut`이 되어야 한다. + +- 이제 실제로 스트림으로부터 데이터를 읽어보자. +- 데이터를 읽기 위해선 buffer(버퍼)를 읽을 데이터를 저장할 스택에 선언해야 한다. 그리고 버퍼를 `stream.read` 로 전달하여 `TcpStream`으로부터 읽어들인 바이트를 버퍼로 집어넣는다. +- 이후 버퍼 안에있는 바이트들을 문자열로 변환하고 출력한다. `String::from_utf8_lossy` 함수는 `&[u8]` 을 전달받고 String 으로 바꿔서 제공해준다. + +- 프로그램을 실행하고 TCP로 접속하면 아래와 같이 출력될 것이다. + +```bash +$ cargo run + Compiling hello v0.1.0 (file:///projects/hello) + Finished dev [unoptimized + debuginfo] target(s) in 0.42 secs + Running `target/debug/hello` +Request: GET / HTTP/1.1 +Host: 127.0.0.1:7878 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 +Firefox/52.0 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 +Accept-Language: en-US,en;q=0.5 +Accept-Encoding: gzip, deflate +Connection: keep-alive +Upgrade-Insecure-Requests: 1 +������������������������������������ +``` + +## HTTP 응답 + +- `handle_connection` 함수의 `println!`을 지우고 원하는 HTTP 형식의 메시지를 반환해보자. + +- 우선 간단한 html을 작성한다. + + ```html + + + + + Hello! + + +

Hello!

+

Hi from Rust

+ + + ``` + +- HTML파일을 읽고, 응답의 body에 추가, 전송하도록 코드를 수정한다. + + ```rust + use std::fs::File; + // --생략-- + + fn handle_connection(mut stream: TcpStream) { + let mut buffer = [0; 512]; + stream.read(&mut buffer).unwrap(); + + let mut file = File::open("hello.html").unwrap(); + + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", + contents.len(), + contents + ); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); + } + ``` + +- `HTTP Method`가 `GET`인 경우에만 Hello 페이지를 반환하고, 그렇지 않은 경우 404를 반환하도록 하려면 html을 추가해주고 if문으로 분기처리해주면 된다. + + ```rust + // --생략-- + + fn handle_connection(mut stream: TcpStream) { + // --생략-- + + let (status_line, filename) = if buffer.starts_with(get) { + ("HTTP/1.1 200 OK", "hello.html") + } else { + ("HTTP/1.1 404 NOT FOUND", "404.html") + }; + + let mut file = File::open(filename).unwrap(); + let mut contents = String::new(); + + file.read_to_string(&mut contents).unwrap(); + + let response = format!( + "{}\r\nContent-Length: {}\r\n\r\n{}", + status_line, + contents.len(), + contents + ); + + stream.write(response.as_bytes()).unwrap(); + stream.flush().unwrap(); + } + ``` + +## 멀티스레드로 바꾸기 + +- 스레드풀은 대기중이거나 작업을 처리할 준비가 되어 있는 스레드들의 그룹이다. +- 요청이 들어온다면, 요청들은 처리를 위해 풀로 보내지고 풀에선 들어오는 요청들에 대한 큐(queue)를 유지하도록 할 것이다. +- 풀 내의 각 스레드들은 이 큐에서 요청을 꺼내서 처리하고 또 다른 요청이 있는지 큐에 물어보며 여러 요청을 동시에 처리한다. +- 구조는 다음과 같다. + +1. ThreadPool 은 채널을 생성하고 채널의 송신단을 유지한다. +2. 각 Worker 는 채널의 수신단을 유지한다. +3. 우린 채널로 전송하려는 클로저를 저장할 새로운 Job 구조체를 생성한다. +4. execute 메소드는 채널의 송신단으로 실행하려는 작업을 전송한다. +5. 스레드에선 Worker 가 채널의 수신단에서 반복되며 수신되는 모든 작업의 클로저를 실행한다. + +```rust +use std::thread; +type Job = Box; + +pub struct ThreadPool { + workers: Vec, +} + +impl ThreadPool { + // --snip-- + + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static + { + let job = Box::new(f); + self.sender.send(job).unwrap(); + } + + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + + ThreadPool { + workers, + sender, + } + } + // --snip-- +} + +struct Worker { + id: usize, + thread: thread::JoinHandle<()>, +} + +impl Worker { + fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || { + while let Ok(job) = receiver.lock().unwrap().recv() { + println!("Worker {} got a job; executing.", id); + job.call_box(); + } + }); + + Worker { + id, + thread, + } + } +} +``` + +## Graceful한 종료 + +- Graceful한 종료란, 서버 종료 신호를 받았을 때 현재 처리하고 있는 요청을 모두 처리할 때 까지 기다린 후 종료하는 것을 의미한다. +- 풀 안의 각 스레드 상에서 `join`을 호출하여 스레드가 종료되기 전에 그들이 처리하던 요청을 마저 처리할 수 있도록 하기 위하여 Drop 트레잇을 구현해보자. 아래와 같이 `Drop`에서 `worker`의 Thread들에 `join`하여 종료하길 기다리도록 하는 것이 목표이다. + + ```rust + ... + impl Drop for ThreadPool { + fn drop(&mut self) { + for worker in &mut self.workers { + println!("Shutting down worker {}", worker.id); + thread.join().unwrap(); + } + } + } + ... + ``` + +- 하지만 이 코드는 아직 작동하지 않는다. worker를 가변 형태로 빌려왔으니, 인수의 소유권을 필요로 하는 `join`을 호출할 수 없다. 이 이슈를 해결하기 위해, `join`이 스레드를 사용할 수 있도록 thread의 소유권을 `Worker` 인스턴스로부터 빼내야 한다. +- `Worker`가 `OptionJoinHandle<()>`를 대신 갖도록 하면, `Option`의 `take` 메소드를 사용하여 `Some` variant에서 값을 빼내고 `None`으로 대체할 수 있다. 즉, 실행중인 `Worker`는 thread에 `Some` variant 를 갖게 되고, 우린 `worker`를 종료하고자 할때 `Some`을 `None`으로 대체하여 `worker`가 실행할 스레드를 없앨 수 있다. + + ```rust + ... + struct Worker { + id: usize, + thread: Option>, + } + + impl Worker { + fn new(id: usize, receiver: Arc>>) -> Worker { + // --생략-- + + Worker { + id, + thread: Some(thread), + } + } + } + ``` +- `worker`로 부터 thread를 빼내기 위해선 `Option`에서 take를 호출해야하므로 이에 맞춰 `drop` 함수의 내용을 조금 수정해준다. `Some`을 파괴하고 스레드를 얻기 위해 `if let`를 사용한다. + + ```rust + impl Drop for ThreadPool { + fn drop(&mut self) { + for worker in &mut self.workers { + println!("Shutting down worker {}", worker.id); + + if let Some(thread) = worker.thread.take() { + thread.join().unwrap(); + } + } + } + } + ``` + +- `Job`이나 리스닝을 멈추고 무한 반복문을 탈출하라는 신호를 기다리도록 스레드를 수정한다. 두 `variant`를 가진 `enum`을 만들어 Job 대신 해당 `enum`을 가지도록 구현해보자. + + ```rust + ... + enum Message { + NewJob(Job), + Terminate, + } + ... + + pub struct ThreadPool { + workers: Vec, + sender: mpsc::Sender, + } + + // --생략-- + + impl ThreadPool { + // --생략-- + + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static + { + let job = Box::new(f); + + self.sender.send(Message::NewJob(job)).unwrap(); + } + } + + // --생략-- + + impl Worker { + fn new(id: usize, receiver: Arc>>) -> + Worker { + + let thread = thread::spawn(move ||{ + loop { + let message = receiver.lock().unwrap().recv().unwrap(); + + match message { + Message::NewJob(job) => { + println!("Worker {} got a job; executing.", id); + job.call_box(); + }, + Message::Terminate => { + println!("Worker {} was told to terminate.", id); + break; + }, + } + } + }); + + Worker { + id, + thread: Some(thread), + } + } + } + + ... + // 각 worker 스레드에 join 을 호출하기 전에 Message::Terminate를 전달한다. + impl Drop for ThreadPool { + fn drop(&mut self) { + println!("Sending terminate message to all workers."); + + for _ in &mut self.workers { + self.sender.send(Message::Terminate).unwrap(); + } + + println!("Shutting down all workers."); + + for worker in &mut self.workers { + println!("Shutting down worker {}", worker.id); + + if let Some(thread) = worker.thread.take() { + thread.join().unwrap(); + } + } + } + } + ``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\206\214\354\234\240\352\266\214\352\263\274\342\200\205Lifetime.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\206\214\354\234\240\352\266\214\352\263\274\342\200\205Lifetime.md" new file mode 100644 index 00000000..04151daf --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\206\214\354\234\240\352\266\214\352\263\274\342\200\205Lifetime.md" @@ -0,0 +1,215 @@ +--- +title: '소유권과 Lifetime' +lastUpdated: '2024-03-02' +--- + +- 기본적으로 모든 변수 바인딩은 유효한 “범위(스코프)“를 가지며, 범위 밖에서 변수 사용하면 에러가 발생한다. +- 스코프가 종료되면 변수는 “삭제(drop)“되었다고 하며 그 변수의 데이터는 메모리에서 해제된다. +- Rust에서는 스코프가 종료될 때 다른 리소스를 해제하기 위해 소멸자가 호출되도록 하는 것을 변수가 **값을 소유한다**고 정의한다. +- 러스트의 각각의 값은 해당값의 오너(owner)라고 불리우는 변수를 갖고 있으며 한번에 딱 하나의 오너만 존재할 수 있다. + +```rust +fn main() { + let s1: String = String::from("Rust"); + let s2: String = s1; +} +``` + +- 위 코드는 `"Rust"`라는 String 값에 대한 소유권을 `s1`에서 `s2`로 이전한다. +- `s2`에 `s1`을 대입하면 + - String 데이터(스택에 있는 포인터, 길이값, 용량값)이 복사된다. 포인터가 가리키고 있는 힙 메모리 상의 데이터는 복사되지 않는다. + - 그리고 `s1`는 더이상 유효하지 않은 상태가 된다. 두 변수가 같은 메모리를 가리킬 때 생기는 double free를 방지하기 위함이다. 따라서 참조시 에러가 발생한다. + + ```rust + error[E0382]: use of moved value: `s1` + --> src/main.rs:4:27 + | + 3 | let s2 = s1; + | -- value moved here + 4 | println!("{}, world!", s1); + | ^^ value used here after move + | + = note: move occurs because `s1` has type `std::string::String`, + which does not implement the `Copy` trait + ``` + +- **이동 전 메모리** + + image + +- **이동 후 메모리** + + image + +- 단, 정수형과 같이 컴파일 타임에 결정되어 있는 크기의 타입은 스택에 모두 저장되기 때문에, 실제 값의 복사본이 빠르게 만들어질 수 있다. +- 이 경우에는 변수 y가 생성된 후에 x가 더 이상 유효하지 않도록 해야할 이유가 없어서, 아래와 같은 단순 타입은 소유권 이전이 이뤄지지 않고 항상 값이 복사된다. + + - u32와 같은 모든 정수형 타입들 + - true와 false값을 갖는 부울린 타입 bool + - f64와 같은 모든 부동 소수점 타입들 + - Copy가 가능한 타입만으로 구성된 튜플들 + +## 함수 호출에서의 이동(Move) + +```rust +fn say_hello(name: String) { + println!("Hello {name}") +} + +fn main() { + let name = String::from("Alice"); + say_hello(name); + // say_hello(name); +} +``` +- 러스트는 함수 호출시 이동을 기본으로 하고, 복제하고 싶은 경우 명시적으로 선언하도록 한다. +- name에 할당되있는 힙 메모리는 `say_hello` 함수의 끝에서 해제된다. +- main 함수에서 name을 참조로 전달하고(&name), `say_hello`에서 매개변수를 참조형으로 수정한다면 main 함수는 name의 소유권을 유지할 수 있다. +- `say_hello` 함수를 호출할 때 main함수는 자신이 가진 name에 대한 소유권을 포기하므로, 이후 main함수에서는 name을 사용할 수 없다. +- 가변 참조자를 사용하면 참조하는 값에 대한 수정이 가능하다. + - 그러나, 특정한 스코프 내에 특정한 데이터 조각에 대한 가변 참조자는 딱 하나만 만들 수 있다. + - 불변 참조자를 가지고 있을 동안에도 역시 가변 참조자를 만들 수 없다. + - 필요한 경우 새로운 스코프를 정의하는 방법을 사용할 수 있다. + +- 이러한 제한은 Rust가 컴파일 타임에 아래와 같은 동작으로 데이터 레이스(data race)가 발생하지 않도록 해준다. + 1. 두 개 이상의 포인터가 동시에 같은 데이터에 접근한다. + 2. 그 중 적어도 하나의 포인터가 데이터를 쓴다. + 3. 데이터에 접근하는데 동기화를 하는 어떠한 메커니즘도 없다. + +## 댕글링 참조자(Dangling References) + +- 댕글링 포인터란 어떤 메모리를 가리키는 포인터를 보존하는 동안, 그 메모리를 해제함으로써 다른 개체에게 사용하도록 줘버렸을 지도 모를 메모리를 참조하고 있는 포인터를 말한다. + +- 러스트에서 컴파일러는 모든 참조자들이 댕글링 참조자가 되지 않도록 보장해준다. 만일 우리가 어떤 데이터의 참조자를 만들었다면, 컴파일러는 그 참조자가 스코프 밖으로 벗어나기 전에는 데이터가 스코프 밖으로 벗어나지 않을 것임을 확인해 줄 것이다. + +- 댕글링 참조자를 만드는 예시를 보자. + +```rust +fn main() { + let reference_to_nothing = dangle(); +} + +fn dangle() -> &String { + let s = String::from("hello"); + + &s +} +``` + +- 위 코드의 오류 메세지이다. + +```rust +error[E0106]: missing lifetime specifier + --> dangle.rs:5:16 + | +5 | fn dangle() -> &String { + | ^^^^^^^ + | + = help: this function's return type contains a borrowed value, but there is no + value for it to be borrowed from + = help: consider giving it a 'static lifetime + +error: aborting due to previous error +``` + +- 빌려온 값이 실제로 존재하지 않는다며 에러가 발생하는 것을 볼 수 있다 +- 참조자가 아니라 String 값을 직접 반환하면 문제를 해결할 수 있다. + +## Lifetime(수명) + +- 러스트에서 모든 참조자는 코드가 유효한 스코프인 라이프타임(lifetime) 을 갖는다. +- 대부분의 경우 라이프타임 또한 암묵적이며, 추론된다. + + +```rust +#[derive(Debug)] +struct Point(i32, i32); + +fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + if p1.0 < p2.0 { p1 } else { p2 } +} + +fn main() { + let p1: Point = Point(10, 10); + let p2: Point = Point(20, 20); + let p3: &Point = left_most(&p1, &p2); + println!("left-most point: {:?}", p3); +} +``` +- `'a`는 제네릭 매개변수로 컴파일러에 의해 추론된다. +- 수명의 이름은 ` 로 시작하며 보통 `'a`를 많이 사용한다. +- `&'a Point`는 `Point`의 수명이 최소한 `'a`라는 수명보다는 같거나 더 길다는 것을 의미한다. +- 매개변수들이 서로 다른 스코프에 있을 경우 “최소한“이라는 조건이 중요하다. + +- 아래 코드에서는 p3의 수명이 p2 보다 길기 때문에 컴파일되지 않는다. + +```rust +struct Point(i32, i32); + +fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { + if p1.0 < p2.0 { p1 } else { p2 } +} + +fn main() { + let p1: Point = Point(10, 10); + let p3: &Point; + { + let p2: Point = Point(20, 20); + p3 = left_most(&p1, &p2); + } + println!("left-most point: {:?}", p3); +} +``` + +### 빌림 검사기(Borrow Checker) + +- Borrow Checker 라고 불리는 컴파일러의 컴포넌트가 스코프를 비교하여 모든 빌림이 유효한지를 결정한다. + +```rust +{ + let r; // -------+-- 'a + // | + { // | + let x = 5; // -+-----+-- 'b + r = &x; // | | + } // -+ | + // | + println!("r: {}", r); // | + // | + // -------+ +} +``` + +- 위 코드는 각 변수의 라이프타임을 명시적으로 주석으로 표현한 것이다. +- `'b` 라이프타임이 `'a` 라이프타임에 비해 작기 때문에 오류가 발생한다. + +### 구조체에서의 수명 + +- 어떤 타입이 빌려온 데이터를 저장하고 있다면, 반드시 수명을 표시해야 한다. + +```rust +#[derive(Debug)] +struct Highlight<'doc>(&'doc str); + +fn erase(text: String) { + println!("Bye {text}!"); +} + +fn main() { + let text = String::from("The quick brown fox jumps over the lazy dog."); + let fox = Highlight(&text[4..19]); + let dog = Highlight(&text[35..43]); + // erase(text); + println!("{fox:?}"); + println!("{dog:?}"); +} +``` +- 위의 예제에서 Highlight의 어노테이션(`<'doc>`)은 적어도 `Highlight` 인스턴스가 살아있는 동안에는 그 내부의 `&str`가 가리키는 데이터 역시 살아있어야 한다는 것을 의미한다. +- 만약 text가 fox (혹은 dog)의 수명이 다하기 전에 `erase` 함수 호출 등으로 사라지게 된다면 빌림 검사기가 에러를 발생한다. + +--- +참고 +- https://doc.rust-lang.org/nomicon/ownership.html +- https://doc.rust-lang.org/nomicon/lifetimes.html +- https://google.github.io/comprehensive-rust/ko/ownership.html +- https://rinthel.github.io/rust-lang-book-ko/ch10-03-lifetime-syntax.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\212\244\353\247\210\355\212\270\342\200\205\355\217\254\354\235\270\355\204\260\342\200\205\355\231\234\354\232\251.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\212\244\353\247\210\355\212\270\342\200\205\355\217\254\354\235\270\355\204\260\342\200\205\355\231\234\354\232\251.md" new file mode 100644 index 00000000..17c3bdde --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\212\244\353\247\210\355\212\270\342\200\205\355\217\254\354\235\270\355\204\260\342\200\205\355\231\234\354\232\251.md" @@ -0,0 +1,278 @@ +--- +title: '스마트 포인터 활용' +lastUpdated: '2024-03-02' +--- + +- 스마트 포인터(Smart Pointer)는 포인터처럼 작동하지만 추가적인 메타데이터와 능력들도 가지고 있는 데이터 구조이다. +- `String`과 `Vec`는 스마트 포인터이다. +- 스마트 포인터는 보통 구조체를 이용하여 구현되어 있다. 스마트 포인터가 일반적인 구조체와 구분되는 특성은 스마트 포인터가 `Deref`와 `Drop` 트레잇을 구현한다는 것이다. + +- `Deref` 트레잇은 스마트 포인터 구조체의 인스턴스가 참조자처럼 동작하도록 하여, 참조자나 스마트 포인터 둘 중 하나와 함께 작동하는 코드를 작성하게 해준다. + - 스마트 포인터가 평범한 참조자처럼 취급될 수 있도록 구현한다. + - `Deref`를 구현한 구조체에 대해 `*`로 값을 참조하면, `deref` 함수를 호출한 후 `*`를 한번 호출하는 것으로 대치된다. (소유권 이전을 막기 위해 이런 식으로 구현되어있다.) + - Rust는 타입이 맞지 않는 경우 역참조를 강제하기 때문에 함수와 메소드 호출을 작성할 때 `&`와 `*`를 이용한 명시적 참조 및 역참조를 생략할 수 있다. `Deref::deref`를 구현하면 커스텀 구조체에서도 이것이 가능해진다. + + ```rust + fn hello(name: &str) { + println!("Hello, {}!", name); + } + + fn main() { + let m = MyBox::new(String::from("Rust")); + + // 역참조 강제가 있기 때문에 가능한 코드이다. + hello(&m); + + // 역참조 강제가 없었다면 이렇게 작성해야한다. + hello(&(*m)[..]); + } + ``` + - 가변 참조자에 대한 `*`를 오버라이딩하기 위해선 `DerefMut` 트레잇을 사용해야한다. + - 러스트는 다음의 세 가지 경우에 역참조 강제를 수행한다. + - `T: Deref`일때 `&T`에서 `&U`로 + - `T: DerefMut`일때 `&mut T`에서 `&mut U`로 + - `T: Deref`일때 `&mut T`에서 `&U`로 + - 불변 참조자는 가변 참조자로 강제될 수 없다. + +- `Drop` 트레잇은 스마트 포인터의 인스턴스가 스코프 밖으로 벗어났을 때 실행되는 코드를 커스터마이징 가능하도록 해준다. + - 파일이나 네트워크 연결 같은 자원을 해제하는 데에 사용될 수도 있다. + - 변수들은 만들어진 순서의 역순으로 Drop된다. + + ```rust + struct CustomSmartPointer { + data: String, + } + + impl Drop for CustomSmartPointer { + // Drop Trait을 구현하면 drop 메서드가 drop시 실행된다. + fn drop(&mut self) { + println!("Dropping CustomSmartPointer with data `{}`!", self.data); + } + } + + fn main() { + let c = CustomSmartPointer { data: String::from("my stuff") }; + let d = CustomSmartPointer { data: String::from("other stuff") }; + println!("CustomSmartPointers created."); + } + + /* + 출력 결과: + CustomSmartPointers created. + Dropping CustomSmartPointer with data `other stuff`! + Dropping CustomSmartPointer with data `my stuff`! + */ + ``` + - Double free 문제가 생길 수 있기 때문에 `drop()` 함수를 직접 호출하는 것은 허용되지 않는다. 대신 `std::mem::drop`를 사용하여 메모리에서 직접 지울 수 있다. + +- 스마트 포인터는 Rust에서 자주 활용되는 패턴이므로, 직접 비슷한 구조로 구현할 수 있다. + +## 대표적인 스마트 포인터 + +- 표준 라이브러리의 대표적인 스마트 포인터들에 대해 알아보자. + +### 값을 힙에 할당하기 위한 `Box` + +- `Box`는 데이터를 스택이 아니라 힙에 저장할 수 있도록 해준다. +- 아래와 같은 상황에서 사용할 수 있다. + - 컴파일 타임에 크기를 알 수 없는 타입을 갖지만, 사이즈를 아는 상태에서 해당 타입의 값을 이용하고 싶을 때 + - 커다란 데이터의 데이터를 복사하지 않고 소유권을 옮기기를 원할 때 + - 박스 안의 힙에 큰 데이터를 저장하면, 작은 양의 포인터 데이터만 스택 상에서 복사되고 데이터는 힙의 한 곳에 머물게 된다. + - 어떤 값의 소유와 타입에 관계없이 특정 트레잇을 구현한 타입이라는 점만 신경 쓰고 싶을 때 + +- new를 사용해서 생성할 수 있다. +```rust +fn main() { + let b = Box::new(5); + println!("b = {}", b); +} +``` + +- 박스는 재귀적 타입을 가능하게 한다. + - 현재 아이템의 값과 다음 아이템을 저장하는 Pair(Cons)가 있다고 해보자. + + ```rust + enum List { + Cons(i32, List), + Nil, + } + + use List::{Cons, Nil}; + + fn main() { + let list = Cons(1, Cons(2, Cons(3, Nil))); + } + ``` + + - `Nil`을 값으로 가지게 함으로써 Cons가 계속해서 이어지는 걸 막을 수 있지만, 컴파일러가 variants를 계산할 때는 무한대의 메모리가 필요한 타입인 것으로 해석한다. 따라서 위의 코드는 실행할 수 없다. + + image + + - `Box`를 사용하면 무한한 variants를 가졌던 위와 달리, 한정된 크기를 가지는 pointer 값만을 가지기 때문에 문제가 해결된다. + + ```rust + enum List { + Cons(i32, Box), + Nil, + } + + use List::{Cons, Nil}; + + fn main() { + let list = Cons(1, + Box::new(Cons(2, + Box::new(Cons(3, + Box::new(Nil)))))); + } + ``` + + image + +### 복수개의 소유권을 가능하게 하는 참조 카운팅 타입 `Rc` + +- 대부분의 경우에서, 소유권은 한 변수당 하나로 명확하다. +- 하지만 하나의 값이 여러 개의 소유자를 가질 수도 있는 경우가 있다. + - 예를 들면, 그래프 데이터 구조에서, 여러 에지가 동일한 노드를 가리킬 수 있다. 그 노드는 개념적으로 해당 노드를 가리키는 모든 에지들에 의해 소유된다. 노드는 어떠한 에지도 이를 가리키지 않을 때까지는 메모리 정리가 되어서는 안된다. + +- 복수 소유권을 가능하게 하기 위해서, 러스트는 `Rc`라는 타입을 가지고 있다. Reference Counting의 약자이고, 이 타입은 어떤 값이 계속 사용되는지 혹은 그렇지 않은지를 알기 위해 해당 값에 대한 참조자의 갯수를 계속 추적한다. +- 만일 값에 대한 참조자가 0개라면, 그 값은 어떠한 참조자도 무효화하지 않고 메모리에서 정리될 수 있다. +- 프로그램의 여러 부분에서 읽을 데이터를 힙에 할당하고 싶고, 어떤 부분이 그 데이터를 마지막에 이용하게 될지 컴파일 타임에 알 수 없는 경우 `Rc` 타입을 사용한다. +- `Rc`는 오직 단일 스레드 상에서만 사용 가능하다. + +- 예제를 살펴보자. + +image + +- 이러한 구조를 나타내는 코드가 있다. + + ```rust + enum List { + Cons(i32, Box), + Nil, + } + + use List::{Cons, Nil}; + + fn main() { + let a = Cons(5, + Box::new(Cons(10, + Box::new(Nil)))); + let b = Cons(3, Box::new(a)); + let c = Cons(4, Box::new(a)); + } + ``` + +- `a`에 대한 소유권을 두 군데에 지정하는 것이기 때문에 위 코드는 컴파일 되지 않는다. (참조자를 사용하는 경우 a가 사라지지 않는다는 것을 보장할 수 없다.) +- `Box`의 자리에 `Rc`를 이용하여 정의하면 이러한 문제를 해결할 수 있다. + +```rust +enum List { + Cons(i32, Rc), + Nil, +} + +use List::{Cons, Nil}; +use std::rc::Rc; + +fn main() { + let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); + let b = Cons(3, Rc::clone(&a)); + let c = Cons(4, Rc::clone(&a)); +} +``` +- `Rc::clone`의 호출은 오직 참조 카운트만 증가시킨다. +- `Rc::strong_count` 함수를 호출함으로써 카운트 값을 얻을 수 있다. + +```rust +fn main() { + let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); + println!("count after creating a = {}", Rc::strong_count(&a)); + let b = Cons(3, Rc::clone(&a)); + println!("count after creating b = {}", Rc::strong_count(&a)); + { + let c = Cons(4, Rc::clone(&a)); + println!("count after creating c = {}", Rc::strong_count(&a)); + } + println!("count after c goes out of scope = {}", Rc::strong_count(&a)); +} +/* +출력결과: +count after creating a = 1 +count after creating b = 2 +count after creating c = 3 +count after c goes out of scope = 2 +*/ +``` + +- `Rc`는 읽기 전용으로 우리 프로그램의 여러 부분 사이에서 데이터를 공유하도록 허용해준다. + +### 빌림 규칙을 컴파일 타임 대신 런타임에 강제하는 타입인, `RefCell`를 통해 접근 가능한 Ref와 RefMut + +- `Rc`와는 다르게, `RefCell` 타입은 가지고 있는 데이터 상에 단일 소유권을 나타낸다. +- `Box`는 하나의 가변 참조자 혹은 임의 개수의 불변 참조자를 가질 수 있고 항상 유효해야 한다는 것을 컴파일러가 강하게 제약한다. +- 하지만 `RefCell`는 런타임에 검사된 가변 빌림을 허용하기 때문에, `RefCell`이 불변일 때라도 `RefCell` 내부의 값을 변경할 수 있다. 즉, 코드가 빌림 규칙을 따르는 것을 확신하지만 컴파일러는 이를 이해하고 보장할 수 없을 경우 유용하게 쓰인다. + +- 이 코드는 Rust에 의해 컴파일될 수 없다. (cannot borrow immutable local variable `x` as mutable) + ```rust + fn main() { + let x = 5; + let y = &mut x; + } + ``` + +- `RefCell`를 사용하면 아래와 같이 수정할 수 있다. + + ```rust + use std::cell::RefCell; + + fn main() { + let x = RefCell::new(5); + let y = &mut *x.borrow_mut(); + } + ``` + +- 컴파일러 내의 빌림 검사기는 이러한 내부 가변성을 허용하는 대신 런타임에 검사를 수행한다. 만약 규칙이 위반된다면 컴파일러 에러 대신 `panic!`을 얻을 것이다. +- `RefCell` 또한 단일 스레드 상에서만 사용 가능하다. + +### `Rc`와 `RefCell`의 조합 + +- `Rc`는 어떤 데이터에 대해 복수의 소유자를 허용하지만, 그 데이터에 대한 불변 접근만 제공한다. + - 그러므로 만약 `RefCell`을 들고 있는 `Rc`를 선언한다면, 변경 가능하면서 복수의 소유자를 갖는 값을 가질 수 있다! + +- 어떤 리스트의 소유권을 공유하는 여러 개의 리스트를 가질 수 있도록 하기 위해 `Rc`를 사용했던 예제를 떠올려보자. + - 이전에는 `Rc`가 불변의 값만을 가질 수 있었다. + - 이 리스트 안의 값을 변경할 수 있게 하기 위해서 `RefCell`를 사용할 수 있다. + + ```rust + #[derive(Debug)] + enum List { + Cons(Rc>, Rc), + Nil, + } + + use List::{Cons, Nil}; + use std::rc::Rc; + use std::cell::RefCell; + + fn main() { + let value = Rc::new(RefCell::new(5)); + + let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); + + let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a)); + let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a)); + + *value.borrow_mut() += 10; + + println!("a after = {:?}", a); + println!("b after = {:?}", b); + println!("c after = {:?}", c); + } + ``` + +--- +참고 +- https://rinthel.github.io/rust-lang-book-ko/ch15-00-smart-pointers.html +- https://doc.rust-lang.org/book/ch15-00-smart-pointers.html +- https://doc.rust-lang.org/book/ch15-01-box.html +- https://doc.rust-lang.org/std/ops/trait.Drop.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\241\260\352\261\264\353\254\270\352\263\274\342\200\205\353\260\230\353\263\265\353\254\270.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\241\260\352\261\264\353\254\270\352\263\274\342\200\205\353\260\230\353\263\265\353\254\270.md" new file mode 100644 index 00000000..99735a2b --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\354\241\260\352\261\264\353\254\270\352\263\274\342\200\205\353\260\230\353\263\265\353\254\270.md" @@ -0,0 +1,190 @@ +--- +title: '조건문과 반복문' +lastUpdated: '2024-03-02' +--- + +## 조건문 + +- 일반적인 if 조건문은 다른 언어와 비슷하게 사용할 수 있다. + +```rust +fn main() { + let number = 6; + + if number % 4 == 0 { + println!("number is divisible by 4"); + } else if number % 3 == 0 { + println!("number is divisible by 3"); + } else if number % 2 == 0 { + println!("number is divisible by 2"); + } else { + println!("number is not divisible by 4, 3, or 2"); + } +} +``` + +### match + +- match라는 흐름 제어 연산자를 사용할 수 있다. (Kotlin의 when, Java 또는 C의 switch와 같은 역할이다.) + +```rust +enum Coin { + Penny, + Nickel, + Dime, + Quarter, +} + +fn value_in_cents(coin: Coin) -> u32 { + match coin { + Coin::Penny => 1, + Coin::Nickel => 5, + Coin::Dime => 10, + Coin::Quarter => 25, + } +} +``` + +### if let + +- if와 let을 조합하여 하나의 패턴만 매칭 시키는 구문을 작성할 수 있다. + +```rust +let some_u8_value = Some(0u8); +match some_u8_value { + Some(3) => println!("three"), + _ => (), +} + +// 위 코드와 동일 +if let Some(3) = some_u8_value { + println!("three"); +} +``` + +### 패턴 매칭 + +- 구조체에 대한 match 조건을 아래와 같이 작성할 수 있다. +- 조건문에서 새로운 변수명을 사용하여 값을 캡처할 수 있다. + +```rust +struct Foo { + x: (u32, u32), + y: u32, +} + +#[rustfmt::skip] +fn main() { + let foo = Foo { x: (1, 2), y: 3 }; + match foo { + Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"), + Foo { y: 2, x: i } => println!("y = 2, x = {i:?}"), + Foo { y, .. } => println!("y = {y}, other fields were ignored"), + } +} +``` + +- 배열이나 튜플, 슬라이스도 그 요소들에 대해 패턴 매칭으로 분해할 수 있다. +- `..`는 요소 개수에 상관없이 매치될 수 있다. +- `[.., b]`나 `[a@.., b]`와 같은 패턴으로 꼬리 부분을 매칭할 수 있다. + +```rust +#[rustfmt::skip] +fn main() { + let triple = [0, -2, 3]; + println!("Tell me about {triple:?}"); + match triple { + [0, y, z] => println!("First is 0, y = {y}, and z = {z}"), + [1, ..] => println!("First is 1 and the rest were ignored"), + _ => println!("All elements were ignored"), + } +} +``` + +- 패턴 뒤에 추가 불리언 표현식인 가드(guard, 조건식)를 덧붙일 수 있다. +- 패턴에 정의된 변수를 가드의 표현식에서 사용할 수 있다. + +```rust +#[rustfmt::skip] +fn main() { + let pair = (2, -2); + println!("Tell me about {pair:?}"); + match pair { + (x, y) if x == y => println!("These are twins"), + (x, y) if x + y == 0 => println!("Antimatter, kaboom!"), + (x, _) if x % 2 == 1 => println!("The first one is odd"), + _ => println!("No correlation..."), + } +} +``` + +--- + +## 반복문 + +- for, while, loop 등의 반복문을 제공한다. + +```rust +fn main() { + + // for 반복문 + // for 반복문은 자동으로 into_iter()를 호출한 다음 이를 반복한다. + let v = vec![10, 20, 30]; + + for x in v { + println!("x: {x}"); + } + + for i in (0..10).step_by(2) { + println!("i: {i}"); + } + + // while 반복문 + let mut x = 10; + while x != 1 { + x = if x % 2 == 0 { + x / 2 + } else { + 3 * x + 1 + }; + } + println!("Final x: {x}"); + + // 무한 루프를 만드는 loop 키워드 + // while, for와 달리 최소한 한 번은 루프문을 수행하는 것이 보장된다. + let mut x = 10; + loop { + x = if x % 2 == 0 { + x / 2 + } else { + 3 * x + 1 + }; + if x == 1 { + break; + } + } + println!("Final x: {x}"); +} +``` + +### while let + +- while와 let을 조합하여 패턴을 매칭 시키는 구문을 작성할 수 있다. + +```rust +fn main() { + let v = vec![10, 20, 30]; + let mut iter = v.into_iter(); + + while let Some(x) = iter.next() { + println!("x: {x}"); + } +} +``` + +--- +참고 +- https://rinthel.github.io/rust-lang-book-ko/ch06-03-if-let.html +- https://google.github.io/comprehensive-rust/ko/control-flow/if-let-expressions.html +- https://google.github.io/comprehensive-rust/ko/control-flow/novel.html +- https://google.github.io/comprehensive-rust/ko/pattern-matching/destructuring-enums.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\355\203\200\354\236\205\352\263\274\342\200\205\353\263\200\354\210\230.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\355\203\200\354\236\205\352\263\274\342\200\205\353\263\200\354\210\230.md" new file mode 100644 index 00000000..020d10f5 --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\355\203\200\354\236\205\352\263\274\342\200\205\353\263\200\354\210\230.md" @@ -0,0 +1,164 @@ +--- +title: '타입과 변수' +lastUpdated: '2024-03-02' +--- + +### 변수 정의 + +- 변수를 정의할 때는 let 키워드를 사용한다. +- 타입 추론을 지원하기에 타입 정의 부분을 생략해도 된다. + +```rust +let thing1: i32 = 100; +let thing2 = 200 + thing1; + +// mut은 mutable을 의미하는 것으로, 수정 가능한 변수인지를 표현한다. +let mut changing_thing = true; +changing_thing = false; + +let (part1, part2) = ("first", "second"); + +struct Example { + a: bool, + b: u64, +} + +let Example { a, b: _ } = Example { + a: true, + b: 10004, +}; +assert!(a); + +// 변수 이름을 shadowing 해서 정의할 수도 있다. +// shadowing을 하면 새로운 변수가 생기며, 이전 변수와 새 변수는 메모리의 서로 다른 위치에 존재한다. +let shadowing_example = true; +let shadowing_example = 123.4; +let shadowing_example = shadowing_example as u32; +let mut shadowing_example = format!("cool! {shadowing_example}"); +shadowing_example += " something else!"; // not shadowing +println!("{shadowing_example}") +``` + +- `const`로 선언하면 컴파일 할 때 그 값이 정해지며, 상수가 사용되는 모든 부분에서 인라인된다. +- `static` 변수는 프로그램이 수행되는 동안 유지되며, 다른 변수로 이동(move)되지 않는다. + +### 타입 + +|이름|타입|비트|리터럴 값| +|-|-|-|-| +|부호있는 정수|`i8`, `i16`, `i32`, `i64`, `i128` (`isize`는 포인터 크기)|N 비트 (알파벳 뒤의 숫자와 동일)|`-10`, `0`, `1_000`, `123_i64`| +|부호없는 정수|`u8`, `u16`, `u32`, `u64`, `u128` (`isize`는 포인터 크기)|N 비트|`0`, `123`, `10_u16`| +|부동소수|`f32`, `f64`|N 비트|`3.14`, `-10.0e20`, `2_f32`| +|문자열|`&str`|포인터 크기|`"foo"`, `"two\nlines"`| +|유니코드 문자|`char`|32 비트|`'a'`, `'α'`, `'∞'`| +|불린|`bool`|8 비트|`true`, `false`| + +### 복합 타입 + +|이름|타입|리터럴 값| +|-|-|-| +|배열|`[T; N]`|`[20, 30, 40], [0; 3]`| +|튜플|`(), (T,), (T1, T2), …`|`(), ('x',), ('x', 1.2), …`| + +- **배열** + - 배열은 같은 타입 T의 값이 N개 있는 것이고 N은 컴파일 타임에 결정된 값이어야 한다. + - 길이도 배열 타입의 일부이기에, `[u8; 3]`와 `[u8; 4]`은 서로 다른 타입이다. + + ```rust + fn main() { + let mut a: [i8; 10] = [42; 10]; + a[5] = 0; + println!("a: {:?}", a); + } + ``` + - 배열의 일부를 슬라이스 해서 가져올 수 있다. + - 슬라이스는 큰 컬랙션의 일부(혹은 전체)를 보여주는 뷰(view)이며, 다른(슬라이스 된) 타입으로부터 데이터를 빌려온다. 다시말해 소유권을 갖지 않는다. + - 메모리 안전을 위해 슬라이스가 선언, 사용될 때는 원본 배열과 슬라이스 배열을 수정할 수 없다. 슬라이스가 사용되지 않을 때, 즉 슬라이스를 만들기 전이나, 혹은 println이후에는 `a[3]`을 바꿀 수 있다. + + ```rust + fn main() { + let mut a: [i32; 6] = [10, 20, 30, 40, 50, 60]; + println!("a: {a:?}"); + + // 인덱스 0부터 시작한다면 시작 인덱스는 생략 가능하다. + let a1: &[i32] = &a[..a.len()] // = &a[0..a.len()] + + // 마지막 인덱스도 생략 가능하다. + let a1: &[i32] = &a[2..] // = &a[2..a.len()] + + // 전체 배열에 대한 슬라이스는 &a[..] + let s: &[i32] = &a[2..4]; + + println!("s: {s:?}"); + } + ``` + +- **튜플** + - 튜플은 서로 다른 타입의 값들을 하나의 복합 타입으로 묶는다. + - 튜플에 속한 값은 `t.0`, `t.1`과 같이 인덱스로 접근할 수 있다. + - 비어있는 튜플`()`은 단위 타입(unit type)이라고 하고, 함수나 식에서 반환 값이 없음을 나타낼 때 사용한다. (다른 언어의 `void` 개념과 비슷하다.) + + ```rust + fn main() { + let t: (i8, bool) = (7, true); + println!("1st index: {}", t.0); + println!("2nd index: {}", t.1); + } + ``` + +### 포인터 + +`&`를 통해서 포인터를 선언하고, `*`를 통해 역참조할 수 있다. + +> `let mut ref_x: &i32`와 `let ref_x: &mut i32`는 다른 의미이다. 첫번째 값은 다른 값에 바인딩 될 수 있는 가변 참조이고, 두번째 값은 가변 값에 대한 참조이다. + +```rust +fn main() { + let mut x: i32 = 10; + let ref_x: &mut i32 = &mut x; + *ref_x = 20; + println!("x: {x}"); +} +``` + +### enum + +- 러스트는 열거형 variant를 구분하기 위해 내부적으로 식별자(discriminant) 필드를 사용한다. + +```rust +// 일반 enum +enum CoinFlip { + Heads, + Tails, +} + +// 데이터를 포함하는 enum (Variant Payloads) +enum WebEvent { + PageLoad, // Variant without payload + KeyPress(char), // Tuple struct variant + Click { x: i64, y: i64 }, // Full struct variant +} + +#[rustfmt::skip] +fn inspect(event: WebEvent) { + match event { + WebEvent::PageLoad => println!("page loaded"), + WebEvent::KeyPress(c) => println!("pressed '{c}'"), + WebEvent::Click { x, y } => println!("clicked at x={x}, y={y}"), + } +} + +fn main() { + let load = WebEvent::PageLoad; + let press = WebEvent::KeyPress('x'); + let click = WebEvent::Click { x: 20, y: 80 }; + + inspect(load); + inspect(press); + inspect(click); +} +``` + +--- +참고 +- https://google.github.io/comprehensive-rust/ko/basic-syntax/scalar-types.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\355\225\250\354\210\230\354\231\200\342\200\205\353\251\224\354\204\234\353\223\234.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\355\225\250\354\210\230\354\231\200\342\200\205\353\251\224\354\204\234\353\223\234.md" new file mode 100644 index 00000000..fe21321c --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/Rust/\355\225\250\354\210\230\354\231\200\342\200\205\353\251\224\354\204\234\353\223\234.md" @@ -0,0 +1,79 @@ +--- +title: '함수와 메서드' +lastUpdated: '2024-03-02' +--- + +## 함수 + +```rust +fn main() { + print_fizzbuzz_to(20); +} + +fn is_divisible(n: u32, divisor: u32) -> bool { + if divisor == 0 { + return false; + } + n % divisor == 0 +} + +fn fizzbuzz(n: u32) -> String { + let fizz = if is_divisible(n, 3) { "fizz" } else { "" }; + let buzz = if is_divisible(n, 5) { "buzz" } else { "" }; + if fizz.is_empty() && buzz.is_empty() { + return format!("{n}"); + } + format!("{fizz}{buzz}") +} + +fn print_fizzbuzz_to(n: u32) { + for i in 1..=n { + println!("{}", fizzbuzz(i)); + } +} +``` + +- 반환값이 없는 함수의 경우, 유닛 타입 `()`을 반환한다. `-> ()`가 생략된 경우 컴파일러는 이를 추론한다. +- `fizzbuzz_to()`함수 내 for 반목문의 범위 표현식 중 `=n`은 n까지 포함한다는 의미이다. + +### 메서드 + +```rust +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + fn area(&self) -> u32 { + self.width * self.height + } + + fn inc_width(&mut self, delta: u32) { + self.width += delta; + } +} +``` + +- 메서드는 특정 타입과 연결된 함수이다. +- 메서드의 self 인자가 그 메서드가 연결된 인스턴스의 타입이다. +- Rust에서는 기술적으로 커스텀 생성자를 직접 지원하지는 않는다. [(참고)](https://doc.rust-lang.org/nomicon/constructors.html) +- 오버로딩은 지원되지 않는다. +- 파라미터의 기본 값이 지원되지 않는다. 이런 사항들이 제약이 될 경우, 대안으로 매크로를 사용하기도 한다. +- 하지만, 함수의 매개변수에 제네릭을 적용할 수 있다. + + ```rust + fn pick_one(a: T, b: T) -> T { + if std::process::id() % 2 == 0 { a } else { b } + } + + fn main() { + println!("coin toss: {}", pick_one("heads", "tails")); + println!("cash prize: {}", pick_one(500, 1000)); + } + ``` + +--- +참고 +- https://google.github.io/comprehensive-rust/ko/basic-syntax/functions.html +- https://doc.rust-lang.org/nomicon/constructors.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/\354\236\220\353\260\224<\357\274\237>\354\231\200\342\200\205\354\275\224\355\213\200\353\246\260<*>.md" "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/\354\236\220\353\260\224<\357\274\237>\354\231\200\342\200\205\354\275\224\355\213\200\353\246\260<*>.md" new file mode 100644 index 00000000..28b9675a --- /dev/null +++ "b/src/content/docs/TIL/\354\226\270\354\226\264\342\200\205Language/\354\236\220\353\260\224<\357\274\237>\354\231\200\342\200\205\354\275\224\355\213\200\353\246\260<*>.md" @@ -0,0 +1,126 @@ +--- +title: '자바<?>와 코틀린<*>' +lastUpdated: '2024-03-02' +--- + +자바 코드를 코틀린으로 변환하던 중 이슈가 발생했다. + +### 자바 코드 + +```java + private final ConcurrentMap> eventListeners = ConcurrentHashMap(); + ... + public void onEvent(NamespaceClient client, String eventName, List args, AckRequest ackRequest) { + + EventEntry entry = eventListeners.get(eventName); + + Queue listeners = entry.getListeners(); + for (DataListener dataListener : listeners) { + Object data = getEventData(args, dataListener); + dataListener.onData(client, data, ackRequest); + } + } +``` + +```java +public interface DataListener { + + void onData(SocketIOClient client, T data, AckRequest ackSender) throws Exception; + +} +``` + +중요한 부분만 보자면 이런 코드였다. + +- `eventListeners`에 `string`(이벤트 이름)과 `EventEntry`(해당 이벤트의 listener List를 가지고 있는 객체)가 들어있다. +- `listeners`를 하나씩 순회하면서 `client`, `data`, `ackSender` 파라미터를 넣어서 호출한다. + +## 코틀린 코드 + +문제가 되는 코틀린 코드는 아래와 같았다. + +```kotlin + private val eventListeners = ConcurrentHashMap>() + ... + fun onEvent(client: NamespaceClient, eventName: String, args: List, ackRequest: AckRequest) { + val entry = eventListeners[eventName] ?: return + + val listeners = entry.getListeners() + for (dataListener: DataListener in listeners) { + val data: Any? = getEventData(args, dataListener) + dataListener.onData( + client = client, + data = data, // error -> Type mismatch. Required: Nothing? Found: Any? + ackSender = ackRequest + ) + } + } +``` + +```kotlin +interface DataListener { + fun onData(client: SocketIOClient, data: T?, ackSender: AckRequest) +} +``` + +자바에서 사용했던 ``를 `<*>`로 바꾸고, eventListeners에서 get할때 null check를 해준 걸 뺴면 완전히 동일한 코드이다. + +dataListenr의 제네릭이 `out Any?` 타입이니까 `Any?` 타입인 data도 정상적으로 들어갈 것이라 생각했지만? 뜬금없이 `Type mismatch` 컴파일 에러가 뜬다. + +일단 Nothing이라는 클래스 자체도 처음 봐서, 제네릭과 Nothing, star-projection에 대해서 알아보기로 했다. + +## Nothing + +```kotlin +/** + * Nothing has no instances. You can use Nothing to represent "a value that never exists": for example, + * if a function has the return type of Nothing, it means that it never returns (always throws an exception). + */ +public class Nothing private constructor() +``` + +Nothing은 어떠한 값도 포함하지 않는 타입이다. 생성자가 private으로 정의되어 있어 인스턴스를 생성할 수 없다. + +이름 그대로 **없는 타입**이라고 생각하면 된다. + +```kotlin +fun throwException(): Nothing { + throw IllegalStateException() +} +``` + +kotlin에서 함수의 반환값을 정의하지 않으면 `Unit` 이라는 것을 반환한다. 하지만 반환값이 없는 수준을 넘어서, **아예 반환할 일이 절대로 없는 함수**가 있다면 (`return` 조차 쓰지 않음) `Nothing`을 지정해주면 된다. 위와 같이 무조건 Exception을 던지는 함수라면 반환할 일이 없기 떄문에 Nothing을 반환하는 것과 동일하다고 볼 수 있는 것이다. + +```kotlin +val value: String = nullableString ?: throw IllegalStateException() +``` + +이런 코드가 가능한 이유도, `throw`를 하면 `Nothing`이기 때문이다. + +(사실 내부적으로는 `Nothing` 모든 타입의 서브 클래스이기 때문이다.) + +## `<*>`과 `` + +그렇다면 코틀린의 `<*>`이 자바의 ``와 다른 것인가??하는 의문이 들 수 있는데, 사실 다른게 맞다. 문서를 천천히 읽어보면 알 수 있다. + +Java의 ?는 wildcard를 뜻하는 것으로, 제네릭에서 알 수 없는 유형을 나타낼떄 사용한다. wildcard이므로 어떤 클래스든 들어갈 수 있다. ([문서](https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html)) + +그런데 kotlin의 *은 그 클래스에 대해 아는 정보가 없지만 그것을 **안전하게** 사용하고 싶은 경우 사용할 수 있는 문법이다. `out Any?`와 비슷하여, List로 사용했을때 write가 불가능하다. (Sometimes you want to say that you know nothing about the type argument, but you still want to use it in a safe way. Star-projections are very much like Java's raw types, but safe.) ([문서](https://kotlinlang.org/docs/generics.html#star-projections)) + +kotlin은 java와 다르게 더 안전한 공변성을 구현하기 위해서 [Mixed-Site Variance](https://rosstate.org/publications/mixedsite/)라는 것을 사용하며, JAVA와 같은 Wildcard의 개념이 존재하지 않는다. + +실제로 JAVA의 ``로 구현되어있던 기존 코드에는, Listener의 제네릭 타입을 무시하고 인자를 넣을 수 있는 위험한 부분이 존재했다. + +Screenshot 2023-02-11 at 12 17 45 + +`entry.getListener()`를 호출했을때, 큐에 담겨있는 각 DataListener의 제네릭 타입을 알 수 없음에도 불구하고 `Object` 타입의 data 값을 인자로 넣을 수 있었지만 코틀린에선 이러한 경우를 Nothing으로 표시해서 사전에 방지했던 것이다. + +try catch문을 사용해서 Exception이 난 경우 catching 처리는 하고 있지만 이에 대한 이해 없이 ``를 남용했다면 분명히 문제가 발생했을 것이다. 기존 자바 코드를 코틀린으로 변환하기 위해선, 필드에 eventClass 데이터를 직접 받아서 저장후 비교해서 타입 캐스팅이 안되는 경우에는 Exception을 throw하는 로직을 직접 명시하는 등의 다른 처리방식을 사용해야한다. + +--- + +참고 + +- https://kotlinlang.org/docs/generics.html#star-projections +- https://rosstate.org/publications/mixedsite/ +- https://stackoverflow.com/questions/45520368/java-wildcard-types-vs-kotlin-star-projection \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/c\354\226\270\354\226\264\342\200\205\354\273\264\355\214\214\354\235\274\352\263\274\354\240\225.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/c\354\226\270\354\226\264\342\200\205\354\273\264\355\214\214\354\235\274\352\263\274\354\240\225.md" new file mode 100644 index 00000000..92e63cc2 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/c\354\226\270\354\226\264\342\200\205\354\273\264\355\214\214\354\235\274\352\263\274\354\240\225.md" @@ -0,0 +1,39 @@ +--- +title: 'c언어 컴파일과정' +lastUpdated: '2024-03-02' +--- + +### 1) 전처리 - code.c -> code.i +: 전처리기(Preprocessor)로 컴파일 전에 코드를 적정한 상태로 준비하거나 처리하는 일 + +```c +#include +#define STD 10 +``` + +- 전처리기는 필요한 헤더파일을 불러오거나, 기호 상수를 정의해서 코드 상으로 필요한 내용을 먼저 채워주는 역할을 한다. + +- 전처리기는 `#include` 구문을 만나면 해당하는 헤더 파일을 찾아 그 내용을 순차적으로 삽입한다. + +- 그리고 `#define` 부분을 심볼 테이블에 저장하고, 심볼 테이블에 들어 있는 문자열과 같은 내용을 만나면 해당 내용으로 치환한다. (매크로 치환 작업. `#ifdef` 와 같은 전처리기 구문도 처리됨) + +### 2) 컴파일 - code.i -> code.s +: 컴파일러(Compiler)가 고수준 언어를 저수준 언어로 나타내는 일 + +- 소스 프로그램을 목적(object) 프로그램으로 변환하는 작업이다. + +- 즉, 우리가 c언어로 열심히 코딩한 내용을 어셈블리어로 바꿔준다. + +### 3) 어셈블 - hello.s -> hello.o +: 어셈블러(Assembler)가 어셈블리어를 기계어로 바꿔주는 일 + +- 오브젝트 파일을 생성한다. + +### 4) 링크 - hello.o -> hello.exe +: 링커 (Linker) 가 여러 오브젝트 파일을 하나로 합치거나 라이브러리와 합치는 일 + +- 즉, 목적 프로그램을 라이브러리와 연결하여 실행 프로그램(.exe)을 작성한다. + +- **라이브러리(library)** : 프로그래머들이 많이 사용하는 기능을 미리 작성해 놓은 것(e.x 입출력, 파일 처리, 수학 함수 등등) + +![image](https://user-images.githubusercontent.com/81006587/234717678-13eec145-a56c-43c1-b046-cd3b8ed032ae.png) \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/chattr\352\263\274\342\200\205chown.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/chattr\352\263\274\342\200\205chown.md" new file mode 100644 index 00000000..2aa6eeae --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/chattr\352\263\274\342\200\205chown.md" @@ -0,0 +1,69 @@ +--- +title: 'chattr과 chown' +lastUpdated: '2023-12-19' +--- +# chattr + +파일의 속성을 지정하는 명령어이다. + +> change file attributes on a Linux file system + +```bash +chattr [옵션] [+.-.=속성] [파일명] +``` + +**옵션** +- `-R` : 하위 디렉토리까지 재귀적으로 바꿈 +- `-V` : 파일 속성을 바꾼 다음에 보여줌 +- `-v` version : 지정된 파일에 버전을 설정할 수 있습니다. + +**설정모드** +- `+` : 속성을 추가한다. +- `-` : 속성을 제거한다. +- `=` : 원래 파일이 가지고 있던 그 속성만을 유지하게 합니다. + +**속성** +- `a` : 파일을 추가모드로만 열 수 있다. +- `c` : 압축하여 저장 +- `d` : dump 명령을 통하여 백업받을 경우 백업 대상에 포함하지 않는다. +- `i` : 파일을 read-only로만 열 수 있게 설정한다. 루트만이 이 속성을 제거 할 수 있다. +- `s` : 파일이 삭제 될 경우에 디스크 동기화가 일어난다. +- `S` : 파일이 변경 될 경우에 디스크 동기화가 일어난다. +- `u` : 파일이 삭제 되어도 그 내용이 이전 버전으로 저장 되며, 삭제되기 전의 데이터로 복구 가능해진다. + +--- +# chown + +리눅스에서 파일은 어떤 Onwer, Group에 속해있다. + +chown 명령어는 파일의 Owner 또는 Group을 변경하는 명령어이다. + +아래와 같이 사용할 수 있다. + +```bash +$ chown [OPTIONS] USER[:GROUP] FILE... +``` + +## 소유자 변경 +ls -l 명령어는 파일의 소유자가 누구인지 보여준다. + +명령어를 입력하면 아래와 같은 결과가 출력된다. `js`라고 되어있는 부분이 현재 유저와 그룹을 나타낸다. + +```bash +$ ls -l +-rwxr-xr-x 1 js js 6 3월 10 16:02 file1.txt +``` + +chown 명령어를 통해 소유자, 소유 그룹을 root로 바꾸는 모습이다. + +```bash +$ sudo chown root file1.txt +$ ls -l +-rwxr-xr-x 1 root root 6 3월 10 16:02 file1.txt +``` + +--- +참고 +- https://man7.org/linux/man-pages/man1/chattr.1.html +- https://codechacha.com/ko/linux-chown/ +- https://www.ibm.com/docs/ko/i/7.3?topic=directories-chown \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/chmod.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/chmod.md" new file mode 100644 index 00000000..575e628d --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/chmod.md" @@ -0,0 +1,165 @@ +--- +title: 'chmod' +lastUpdated: '2024-03-02' +--- + +`ls -l` 명령을 사용하여 파일, 디렉토리 리스트를 출력하면 파일에 지정된 permission을 확인할 수 있다. + +```bash +$ ls -l +-rwxr-xr-x 1 pi pi 5720 Jul 3 20:06 a.out +-rw-r--r-- 1 pi pi 722 Jul 2 21:12 crontab.bak +-rw-r--r-- 1 pi pi 52 Jul 2 21:10 test.c +``` + +출력 결과는 각각 파일종류 및 권한(Permission), 링크수, 사용자(소유자), 그룹, 파일크기, 수정시간, 파일이름을 나타낸다. + +권한을 변경하거나 할 때도 `chmod` 명령어 뒤에 옵션으로 부여할 권한과 대상을 적어주기만 하면 된다. + +```bash +chmod [options] mode[,mode] file1 [file2 ...] +``` + +앞에 있는 부분은 세글자씩 각각 소유자, 그룹, 그 외의 접근 가능 권한을 뜻한다. + +## option 표현법 + +```bash +# 3글자씩 각 권한을 의미 +# R(ead), W(rite), (e)X(ecute) +# owner | group | other + -rwx r-x r-x +``` + +2진수로 표현하는 방법도 있다. + +r은 4, w는 2, x는 1을 뜻해서 각각을 더해서 숫자로 표현해도 같은 의미이다. + +```bash +# 3글자씩 각 권한을 의미 +# R(ead), W(rite), (e)X(ecute) +# owner | group | other + -7 5 5 +``` + +표로 정리하면 아래와 같다. + +|#|Sum|rwx|Permission| +|-|-|-|-| +|7| 4(r) + 2(w) + 1(x) | rwx | read, write and execute +|6| 4(r) + 2(w) | rw- | read and write| +|5| 4(r) + 1(x) | r-x | read and execute| +|4| 4(r) | r-- | read only| +|3| 2(w) + 1(x) | -wx | write and execute| +|2| 2(w) | -w- | write only| +|1| 1(x) | --x | execute only| +|0| 0 | --- | none| + +## 수정 + +명령어를 통해 기존에 부여된 권한에서 권한을 수정할 수도 있다. + +```bash +$ chmod [references][operator][modes] file ... +``` + +|Reference |Class |Description| +|-|-|-| +|u |user |소유자| +|g |group |파일 그룹의 유저| +|o |others |그룹에 속하지 않는 유저| +|a |all |전부 포함| +|(empty) |default|all과 동일| + +|Operator|Description| +|-|-| +|+|추가| +|-|삭제| +|=|대입| + +|Mode|Name|Description| +|-|-|-| +|r |read |조회| +|w |write |작성| +|x |execute|실행(dir에서는 내부 파일 접근)| +|X |special execute|디렉토리 또는 실행(x) 권한이 있는 파일에 실행(x) 권한 적용| +|s |setuid/gid |(특별 권한)실행 순간에 super 권한을 빌려오듯이 실행| +|t |sticky |(특별 권한)공유모드| + +#### 예시 +```bash +$ ls -ld shared_dir # show access modes before chmod +drwxr-xr-x 2 jsmitt northregion 96 Apr 8 12:53 shared_dir +$ chmod g+w shared_dir # 그룹에 읽기 권한 추가 +$ ls -ld shared_dir # show access modes after chmod +drwxrwxr-x 2 jsmitt northregion 96 Apr 8 12:53 shared_dir + +$ ls -l ourBestReferenceFile +-rw-rw-r-- 2 tmiller northregion 96 Apr 8 12:53 ourBestReferenceFile +$ chmod a-w ourBestReferenceFile # 전체에 읽기 권한 삭제 +$ ls -l ourBestReferenceFile +-r--r--r-- 2 tmiller northregion 96 Apr 8 12:53 ourBestReferenceFile + +$ ls -l sample +drw-rw---- 2 oschultz warehousing 96 Dec 8 12:53 NY_DBs +$ chmod ug=rx sample # 유저와 그룹을 rx로 초기화 +$ ls -l sample +dr-xr-x--- 2 oschultz warehousing 96 Dec 8 12:53 NJ_DBs +``` + +## 특별 권한 + +아까 위에 있었던 s(setuid/gid), t(sticky)라는 권한에 대해 더 알아보자. + +1. s(setuid/gid) + - setuid가 붙은 프로그램은 실행시 소유자의 권한으로 전환되고, setgid가 붙은 프로그램은 실행시 소유 그룹의 권한으로 전환된다. + - setuid와 setgid가 필요한 이유는 일반 사용자가 변경할 수 없는 파일이지만 변경이 필요한 경우가 있기 때문이다. + - 예를 들어 사용자의 암호를 담고있는 `/etc/shadow` 파일은 root만 읽을수 있고 수정이 불가능하다. 하지만 사용자가 암호를 변경할 경우엔 해당 파일이 변경되어야 한다. + - 그래서 암호를 변경하는 `/usr/bin/passwd`에 setuid를 붙이면 실행시 파일의 소유자 권한으로 전환되므로 root 권한을 갖게 되어 `/etc/shadow` 파일에 변경된 암호를 기록할 수 있게 된다. + - 파일에 setuid 비트를 붙이려면 root 권한으로 다음과 같이 맨 앞에 4를 붙여서 지정하면 된다. + - `4755` + - `-rwsr-xr-x` + - setgid 비트는 root 권한으로 다음과 같이 맨 앞에 2를 붙여서 지정한다. + - `2755` + - `-rwr-xrs-x` + +2. t(sticky) + - 스티키 비트(1000)가 설정된 디렉터리는 누구나 파일을 만들수 있지만 자신의 소유가 아닌 파일은 삭제할 수 없다. 즉 일종의 공유 디렉터리라고 볼수 있는데 sticky bit가 붙은 가장 대표적인건 유닉스의 임시 파일 디렉터리인 `/tmp` 이다. + - 디렉터리에 스티키 비트를 붙일 땐 누구나 읽고, 쓰고, 실행할 수 있도록 777 권한을 줘야 한다. + - `1777` + - `rwxt` + +3. X + - 디렉토리 또는 실행(x) 권한이 있는 파일에 실행(x) 권한 적용 + - 대상이 실행(x) 권한을 가져도 괜찮은 경우에만 실행 권한을 지정하고 싶을 때 사용한다 + ```bash + $ chmod u+X FILE # FILE이 실행 권한을 가진 경우에만 파일 소유 사용자에게 실행 권한 추가. + $ chmod -R a-x,a+X * # 현재 디렉토리 아래 모든 파일의 실행 권한 제거, 디렉토리 실행 권한 추가. + $ chmod -R a-x+X * # 위(chmod -R a-x,a+X *)와 동일. + ``` + +## umask + +```bash +$ umask --help +umask: umask [-p] [-S] [mode] + Display or set file mode mask. + + Sets the user file-creation mask to MODE. If MODE is omitted, prints + the current value of the mask. + + If MODE begins with a digit, it is interpreted as an octal number; + otherwise it is a symbolic mode string like that accepted by chmod(1). + + Options: + -p if MODE is omitted, output in a form that may be reused as input + -S makes the output symbolic; otherwise an octal number is output + + Exit Status: + Returns success unless MODE is invalid or an invalid option is given. +``` +--- +참고 +- https://en.wikipedia.org/wiki/Chmod +- https://eunguru.tistory.com/115 +- https://www.lesstif.com/lpt/linux-setuid-setgid-sticky-bit-93127311.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/\354\202\254\354\232\251\354\236\220\342\200\205\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/\354\202\254\354\232\251\354\236\220\342\200\205\352\264\200\353\246\254.md" new file mode 100644 index 00000000..1402db84 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Authority/\354\202\254\354\232\251\354\236\220\342\200\205\352\264\200\353\246\254.md" @@ -0,0 +1,306 @@ +--- +title: '사용자 관리' +lastUpdated: '2024-03-02' +--- + +- root 계정의 UID 값은 0이다. +- root 이외에 UID가 0인 사용자가 없도록 해야 한다. +- `TMOUT` 환경 변수를 사용해 일정시간 미사용시 자동으로 로그아웃 되도록 설정하여 보안을 강화할 수 있다. +- 사용자 인증 모듈인 `PAM`을 이용해 root 계정으로의 직접 로그인을 차단할 수 있다. +- 일반 사용자에게 특정 명령어에 대한 root 권한이 필요할 때는 `su` 명령어보다는 `sudo` 명령어를 이용하도록 한다. + +--- + +## 계정 관리 + +계정 정보가 저장되어있는 디렉토리에 대해 알아보자. + +- `/etc/passwd` +- 비밀번호를 파일 내 계정 정보와 함께 저장하는 일반 정책에서 사용된다. +- passwd 파일의 로그인 쉘을 점검하여 로그인이 불필요한 계정에 대한 접근권한을 설정해야 한다. +- ```c + [user_account]: [user_password] : [UID] : [GID] : [comment] : [home_directory] : [login_shell] + ``` + 1. user_account: 사용자 계정 + 2. user_password: /etc/shadow 파일에 암호화되어 저장되어 있다. + 3. UID: User ID. 보통 100번 이하는 시스템이 사용, 0번은 시스템 관리자를 나타낸다. 4) GID: Group ID + 4. comment: 부가 설명 + 5. home_directory: 로그인 성공 후에 사용자가 위치할 홈 디렉터리의 절대경로 + 6. login_shell: 로그인 셸의 절대경로 + +- `/etc/shadow` +- 파일에 비밀번호를 별도로 저장하는 shadow 패스워드 정책에서 사용된다. +- shadow 파일은 암호화된 패스워드를 가지고 있어 보안 상 shadow 정책을 사용하는 것이 안전하다. +- ```c + [user_id] : [encryption_pw] : [last_change] : [minlife] : [maxlife] : [warn] : [inactive] : [expires] + ``` +`1. user_id: 사용자 계정 +2. encryption_pw: 일방향 해시 알고리즘을 이용해 암호화한 패스워드 + - 형식: `$ id $ salt $ encrypted_password` + - id: 적용된 일방향 해시 알고리즘 (`1: MD5` / `5: SHA-256` / `6: SHA-512 등`) +3. last_change: 마지막으로 패스워드를 변경한 날 (1970.01.01.부터 지난 일수로 표시) +4. minlife: 최소 패스워드 변경 일수(패스워드를 변경할 수 없는 기간) +5. maxlife: 최대 패스워드 변경 일수(패스워드 변경 없이 사용할 수 있는 일수) +6. warn: 경고 일수(maxlife 필드에 지정한 일수가 얼마 남지 않았음을 알림) +7. inactive: 최대 비활성 일수 +8. expires: 계정이 만료되는 날 (1970.01.01.부터 지난 일수로 표시)` + +**encryption_pw 필드의 기호 뜻** +|기호|설명| +|-|-| +|*|패스워드 잠긴 상태, 별도의 인증방식을 사용하여 로그인| +|!!|패스워드 잠긴 상태, 모든 로그인이 불가능| +|(빈값)|패스워드가 설정되지 않은 상태| + +- `/etc/login.def` + - 사용자 계정의 설정과 관련된 기본 값을 정의한 파일 + - 기본 메일 디렉터리, 패스워드 에이징, 사용자 계정의 UID/GID 값 범위 등의 기본값을 설정할 수 있다. +- `/etc/skel` + - 사용자 계정 생성 시 공통으로 배포할 파일이나 디렉터리를 저장하는 디렉터리 +- `/etc/default/useradd` + - useradd 명령어로 계정 생성 시 기본 값을 지정한 파일 + +--- + +## 그룹 관리 + +- `/etc/group` + - ```c + 그룹 명 : 그룹 패스워드 : GID : 그룹 멤버 + ``` + - /etc/passwd 파일에는 기본 그룹의 GID가 저장되고, /etc/group 파일에는 2차 그룹의 정보가 저장 + +- `/etc/gshadow` + - ```c + 그룹 명 : 암호화 된 그룹 패스워드 : 관리자 : 그룹 멤버 + ``` +--- + +## 명령어 + +- `useradd` + - 사용자 계정을 생성하는 명령어 + - 옵션 없이 계정 생성할 경우 패스워드를 설정하지 않았기 때문에 /etc/shadow 파일에 패스워드 항목이 !!로 지정되어 있다. (패스워드가 잠겨 있음) + - ```c + useradd --help + Usage: useradd [options] LOGIN + useradd -D + useradd -D [options] + + Options: + -g, --gid GROUP name or ID of the primary group of the new account + -G, --groups GROUPS list of supplementary groups of the new account + -o, --non-unique allow to create users with duplicate (non-unique) UID + + -c, --comment COMMENT GECOS field of the new account + -d, --home-dir HOME_DIR home directory of the new account + -e, --expiredate EXPIRE_DATE expiration date of the new account + -f, --inactive INACTIVE password inactivity period of the new account + + -s, --shell SHELL login shell of the new account + -u, --uid UID user ID of the new account + -U, --user-group create a group with the same name as the user + ... + ``` + +- `usermod` + - 사용자 계정의 정보를 변경하는 명령어 + - useradd 명령어와 옵션 동일 + - `-l`: 계정 이름 변경 + - ```c + -L, --lock lock the user account + -U, --unlock unlock the user account + ``` + +- `userdel` + - 사용자 계정을 삭제하는 명령어 + - `-r`: 홈 디렉터리 제거 + +- `chage` + - 패스워드 에이징에 대한 설정 명령어 + - ```c + $ chage --help + Usage: chage [options] LOGIN + + Options: + -l, --list show account aging information + + -E, --expiredate EXPIRE_DATE set account expiration date to EXPIRE_DATE + -I, --inactive INACTIVE set password inactive after expiration to INACTIVE + + -m, --mindays MIN_DAYS set minimum number of days before password change to MIN_DAYS + -M, --maxdays MAX_DAYS set maximum number of days before password change to MAX_DAYS + -W, --warndays WARN_DAYS set expiration warning days to WARN_DAYS + ... + + $ chage -l ubuntu + Last password change : Nov 06, 2022 + Password expires : never + Password inactive : never + Account expires : never + Minimum number of days between password change : 0 + Maximum number of days between password change : 99999 + Number of days of warning before password expires : 7 + ``` + +- `passwd` + - 사용자 계정의 패스워드를 변경/관리하는 명령어 + - ```c + $ passwd --help + Usage: passwd [options] [LOGIN] + + Options: + -a, --all report password status on all accounts + -d, --delete delete the password for the named account + -e, --expire force expire the password for the named account + + -l, --lock lock the password of the named account + -u, --unlock unlock the password of the named account + + -n, --mindays MIN_DAYS set minimum number of days before password change to MIN_DAYS + -x, --maxdays MAX_DAYS set maximum number of days before password change to MAX_DAYS + -w, --warndays WARN_DAYS set expiration warning days to WARN_DAYS + ... + ``` + +- `groupadd` + - 그룹을 생성하는 명령어 + - ```c + $ groupadd --help + Usage: groupadd [options] GROUP + + Options: + -g, --gid GID use GID for the new group + -o, --non-unique allow to create groups with duplicate + ``` + +- `groupmod` + - 그룹의 정보를 변경하는 명령어 + - groupadd 명령어와 옵션이 동일 + - `-n`: 그룹 이름 변경 + +- `groupdel` + - 그룹 삭제 + +- `gpasswd` + - 그룹의 패스워드를 변경하거나 그룹에 계정을 추가/삭제하는 명령어 + - ```c + $ gpasswd --help + Usage: gpasswd [option] GROUP + + Options: + -a, --add USER add USER to GROUP + -d, --delete USER remove USER from GROUP + + -r, --remove-password remove the GROUP's password + -A, --administrators ADMIN,... + set the list of administrators for GROUP + ... + ``` + +- `newgrp` + - 계정의 소속 그룹을 변경하는 명령어 + - `newgrp grp01` → grp01로 그룹을 변경 + +--- + +## 사용자 정보 확인 + +- `who` + - 현재 시스템을 사용하는 사용자의 정보를 출력하는 명령어 + - ```c + $ who --help + Usage: who [OPTION]... [ FILE | ARG1 ARG2 ] + Print information about users who are currently logged in. + -a, --all same as -b -d --login -p -r -t -T -u + -b, --boot time of last system boot + -H, --heading print line of column headings + --ips print ips instead of hostnames. with --lookup, + canonicalizes based on stored IP, if available, + rather than stored hostname + -q, --count all login names and number of users logged on + -r, --runlevel print current runlevel + -s, --short print only name, line, and time (default) + ... + + $ who + ubuntu pts/0 2023-06-26 15:39 (14.50.190.128) + ubuntu pts/1 2023-06-26 19:48 (14.50.190.128) + ``` + +- `w` + - 현재 시스템을 사용하는 사용자의 정보와 작업 정보를 출력하는 명령어 + - ```c + $ w + 19:59:10 up 232 days, 59 min, 2 users, load average: 0.00, 0.00, 0.00 + USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT + ubuntu pts/0 14.50.190.128 15:39 1:35m 0.15s 0.04s -c + ubuntu pts/1 14.50.190.128 19:48 1.00s 0.07s 0.00s w + ``` + +- `whoami` + - 현재 작업하고 있는 자신의 계정을 출력 + - ```c + $ whoami + ubuntu + ``` + +- `id` + - 현재 작업하고 있는 자신의 계정명, 그룹명, UID, GID를 출력하는 명령어 + - ```c + $ id + uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),118(netdev),119(lxd) + ``` + +- `users` + - 현재 로그인 되어 있는 사용자의 계정을 출력 + - ```c + $ users + ubuntu ubuntu ubuntu + ``` + +- `groups` + - 사용자 계정이 속한 그룹을 출력 + - ```c + $ groups + ubuntu adm dialout cdrom floppy sudo audio dip video plugdev netdev lxd + ``` + +- `lslogins` + - 시스템 내에 있는 사용자 계정에 대한 정보를 출력 + - ```c + $ lslogins + UID USER PROC PWD-LOCK PWD-DENY LAST-LOGIN GECOS + 0 root 115 root + 1 daemon 0 daemon + 2 bin 0 bin + 3 sys 0 sys + 4 sync 0 sync + ``` + +--- + +## 무결성 검사 명령어 + +- `pwck` + - `/etc/passwd` 파일과 `/etc/shadow` 파일 내용의 무결성을 검사하는 명령어 + - ```c + $ sudo pwck + user 'lp': directory '/var/spool/lpd' does not exist + user 'news': directory '/var/spool/news' does not exist + user 'uucp': directory '/var/spool/uucp' does not exist + user 'list': directory '/var/list' does not exist + user 'irc': directory '/run/ircd' does not exist + user 'gnats': directory '/var/lib/gnats' does not exist + user 'nobody': directory '/nonexistent' does not exist + pwck: no changes + ``` + +- `grpck` + - `/etc/group` 파일과 `/etc/gshadow` 파일 내용의 무결성을 검사하는 명령어 + +--- +참고 +- `--help` command +- https://www.javatpoint.com/linux-user-management +- https://unix.stackexchange.com/questions/461022/what-is-the-difference-between-etc-shadow-and-etc-passwd \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BCC.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BCC.md" new file mode 100644 index 00000000..6ae4d701 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BCC.md" @@ -0,0 +1,169 @@ +--- +title: 'BCC' +lastUpdated: '2024-03-02' +--- + +BPF는 c언어로 작성할 수 있고, LLVM의 clang과 같은 컴파일러를 활용해 C언어로 된 BPF 프로그램 코드를 eBPF 바이트코드로 컴파일할 수 있다. + +[linux man 페이지](https://man7.org/linux/man-pages/man8/BPF.8.html)에서 BPF 프로그램 예제를 볼 수 있다. + +```c +#include + +#ifndef __section +# define __section(x) __attribute__((section(x), used)) +#endif + +__section("classifier") int cls_main(struct __sk_buff *skb) { + return -1; +} + +char __license[] __section("license") = "GPL"; +``` + +이 코드는 아무 동작도 하지 않는 빈 껍데기 프로그램이다. clang을 사용해 다음과 같은 식으로 직접 코드를 빌드할 수 있다. + +```bash +$ clan -02 -emit-llvm -c bpf.c -0 - | llc -march=bpf -filetype=obj -o bpf.o +$ file bpf.o +bpf.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped +``` + +빌드된 BPF 바이너리는 사용자 영역에서 `bpf()` 시스템 콜을 통해 로드해서 사용할 수도 있고, `tc`나 `ip` 같은 도구를 통해 `IC`나 `XDP` 등의 시스템에 사용되기도 한다. 사용자 영역에서 BPF로 통신하거나, 결과를 처리하기 위해선 별도의 복잡한 로직이 필요하다. + +이러한 것들을 고려하지 않고 **eBPF를 더 쉽게 사용할 수 있도록** 하기 위해 BCC(BPF COmpiler Collection)가 만들어졌다. + +### 활용 + +DCC는 C++, Python, lua 로 작성된 사용자 공간의 프로그램과 C로 작성도니 커널 공간의 BPF 프로그램을 작성할 수 있게 도와준다. BCC를 활용하면 BPF 프로그램을 빌드, 로드, 실행하는 데 많은 편의를 얻을 수 있다. + +[BCC tools](https://github.com/iovisor/bcc/tree/master/tools)에는 BPF를 응용한 80여개 이상의 넘는 도구가 포함되어있다. 아래는 BCC tools에 포함된 대표적인 도구와 각 도구가 바라보는 타깃 영역을 나타낸 것이다. + +image + +### Example + +**1. 시스템 콜을 사용한 유저의 id 출력** + +아래 코드는 BCC를 활용하여 eBPF 상에서 시스템 콜이 일어났을 때 `hello_world`라는 함수에서 콜 호출자의 user id를 출력하도록 하는 예제이다. + +program에 해당하는 부분은 c언어 코드이고, 나머지는 python 코드로 되어있다. + +[bcc를 먼저 설치](https://github.com/iovisor/bcc/blob/master/INSTALL.md#amazon-linux-1---binary)한 후에 `ebpf.py`라는 이름의 파일을 만들어 실행했다. + +```python +#!/usr/bin/python3 +from bcc import BPF +from time import sleep + +program = """ +int hello_world(void *ctx) { + u64 uid; + uid = bpf_get_current_uid_gid() & 0xFFFFFFFF; + bpf_trace_printk("id %d\\n", uid); + return 0; +} +""" + +b = BPF(text=program) +clone = b.get_syscall_fnname("clone") +b.attach_kprobe(event=clone, fn_name="hello_world") +b.trace_print(); +``` + +결과는 아래와 같다. root 유저인 0과 첫번째 유저인 1000이 시스템 콜을 사용했음을 알 수 있다. + +쉘을 하나 더 열고 명령어를 사용하면 로그가 계속해서 찍힌다. + +```bash +$ sudo ./ebpf.py +b' systemd-udevd-1205 [001] d..31 89.861992: bpf_trace_printk: id 0' +b'' +b' systemd-udevd-1205 [001] d..31 89.863882: bpf_trace_printk: id 0' +b'' +b' (udev-worker)-3191 [001] d..31 89.972630: bpf_trace_printk: id 0' +b'' +b' <...>-3190 [000] d..31 89.972772: bpf_trace_printk: id 0' +b'' +b' <...>-4623 [001] d..31 215.181875: bpf_trace_printk: id 1000' +``` + +**2. 시스템 콜을 사용한 유저의 id 출력** + +발생하는 sync 시스템 콜 호출 간의 시간 간격을 측정하고, 이 간격이 1초 미만인 경우 해당 간격을 출력한다. + +> sync 시스템 콜은 주로 시스템이 자동으로 수행하는데, `sync` 명령어를 사용하여 수동으로 호출할 수도 있다. sync는 파일 시스템의 버퍼를 디스크에 즉시 쓰고 변경된 내용을 디스크에 동기화한다. 주로 시스템 관리 목적으로 사용되며, 변경 내용을 안정하게 디스크에 저장하고 파일 시스템의 무결성을 보장하는 데 도움된다. + +```python +#!/usr/bin/python3 +from __future__ import print_function +from bcc import BPF +from bcc.utils import printb + +# load BPF program +b = BPF(text=""" +#include + +BPF_HASH(last); + +// "last"라는 hash (associative array) 유형의 BPF map object를 생성한다. 타입을 지정하지 않았기에 키 값 모두 기본적으로 u64 타입이 된다. +// 이 코드에선 hash에서 key가 0인 공간만을 사용할 것이다. + +int do_trace(struct pt_regs *ctx) { + u64 ts, *tsp, delta, key = 0; + + // 저장된 timestamp를 가져온다. + // lookup 함수는 hash에서 key를 찾고 값 존재 여부에 따라 값의 포인터 또는 NULL을 반환한다. + tsp = last.lookup(&key); + if (tsp != NULL) { + delta = bpf_ktime_get_ns() - *tsp; + if (delta < 1000000000) { + // output if time is less than 1 second + bpf_trace_printk("%d\\n", delta / 1000000); + } + last.delete(&key); + } + + // 저장된 timestamp를 갱신한다. + ts = bpf_ktime_get_ns(); + last.update(&key, &ts); + return 0; +} +""") + +b.attach_kprobe(event=b.get_syscall_fnname("sync"), fn_name="do_trace") +print("Tracing for quick sync's... Ctrl-C to end") + +# format output +start = 0 +while 1: + try: + (task, pid, cpu, flags, ts, ms) = b.trace_fields() + if start == 0: + start = ts + ts = ts - start + printb(b"At time %.2f s: multiple syncs detected, last %s ms ago" % (ts, ms)) + except KeyboardInterrupt: + exit() +``` + +간단하게 설명하면 bpf hash에 이전 sync 호출 시간을 저장해놓고, sync 호출 시 이전 sync 시간과 비교하여 출력하는 코드이다. + +출력 예시는 다음과 같다. + +```bash +$ sudo ./ebpf.py +Tracing for quick sync's... Ctrl-C to end +At time 0.00 s: multiple syncs detected, last 616 ms ago +At time 4.92 s: multiple syncs detected, last 795 ms ago +At time 7.06 s: multiple syncs detected, last 530 ms ago +At time 7.61 s: multiple syncs detected, last 544 ms ago +At time 8.18 s: multiple syncs detected, last 575 ms ago +At time 8.88 s: multiple syncs detected, last 693 ms ago +At time 36.94 s: multiple syncs detected, last 767 ms ago +At time 121.95 s: multiple syncs detected, last 615 ms ago +``` + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000001766462 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BPF.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BPF.md" new file mode 100644 index 00000000..ebb89b08 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BPF.md" @@ -0,0 +1,92 @@ +--- +title: 'BPF' +lastUpdated: '2024-03-02' +--- + +> For connected systems evolving these days, the amount of data transfer is huge, and the support infrastructure for the network analysis needed a way to filter out things pretty fast. + +- BPF는 1992년 패킷 분석 및 필터링을 위해 개발된 in-kernel virtual machine이다. +- BSD라는 OS에서 처음 도입했으며 리눅스에서도 이 개념을 빌려와서 서브시스템을 만들었다. +- in-kernel virtual machine이라고 함은 정말로 가상의 레지스터와 스택 등을 갖고 있으며 이를 바탕으로 코드를 실행한다는 뜻이다. +- 커널 레벨의 프로그램을 개발하기 위한 일종의 프레임워크 같은 형태라고 볼 수 있다. 다양한 커널 및 애플리케이션 이벤트에서 작은 프로그램을 실행할 수 있는 방법을 제공한다. + +> 자바스크립트는 브라우저 내에 존재하는 가상 머신에서 안정하게 실행되면서 이벤트에 따라 정적인 HTML 웹페이지를 동적으로 바꾸고, eBPF는 커널에서 여러 이벤트에 따라 동작하는 작은 프로그램을 가상머신위에서 안전하게 동작시킨다.
+> -Brendan Gregg- + +### 구조 + +image + +- BPF 프로그램은 위의 코드처럼 커널 코드 내에 미리 정의된 훅이나 kprobe, uprobe, tracepoint를 사용해서 프로그램을 실행할 수 있다. +- 위의 그림은 간단한 예시로, execve 시스템 호출이 실행될 때마다 BPF 프로그램을 실행해서 새로운 프로세스가 어떻게 만들어지는지를 나타낸다. + +### BPF 코드 컴파일 과정 + + + +- 자바나 파이썬 등의 VM 언어처럼 eBPF 또한 JIT(Just In Time) 컴파일을 지원한다. 컴파일 시점에서 물리 장비의 기계어로 즉시 변환 후 실행되어 성능을 더욱 개선할 수 있다. +- BPF는 사용자측에서 가져온 코드를 커널에서 실행하기 때문에 안전성이 매우 중요하다.
시스템의 안정성을 해칠만한 코드인지 아닌지 검증하는 과정이 필요하다. + - 무한 루프가 발생할 수 있기 때문에 반복문도 매우 제한적으로 지원한다. +- 모든 BPF 프로그램은 Verifier를 통과해야만 실행된다. + +- 위 사진은 BPF 코드가 검증되고 컴파일되는 과정을 나타낸다. + +1. C 코드에서 LLVM 중간 표현으로 번역 +2. BPF 바이트코드로 다시 번역 +3. 바이트코드를 Verifier로 검증 +4. JIT 컴파일러로 컴파일 + +이 4가지 과정을 거치면 실행할 수 있는 상태가 된다. + +### BPF의 장점 + +- 커널을 새로 빌드할 필요 없이 바로 코드를 실행해볼 수 있다. + - 물론 애초에 커널의 기능을 바꿀 일이 있다면 소스를 고치는게 맞지만, **트레이싱을 하는 경우에는 필요할 때만 트레이싱 코드를 실행하고 작업이 끝나고 나면 다시 그 코드를 비활성화**해야 하는데 그럴때마다 매번 커널을 새로 빌드할 필요가 없어진다. + +### BPF의 단점 + +- 자주 실행되는 함수를 트레이싱할 경우 오버헤드가 크다. +- 인라인 함수를 트레이싱 하려면 매우 번거롭다. +- 사용자 공간 함수를 트레이싱 하는 경우에는 커널 공간을 들렀다가 가야 하므로 비효율적이다. +- 지원되는 구문이 제한적이다. (위에서 말한 반복문 처럼) +- 고정된 스택을 갖는다 (512바이트) + +### eBPF: extended BPF + +- eBPF는 확장 BPF라는 뜻이다. 기존의 BPF에서 사용하던 머신에서 더 나아가서 레지스터의 크기를 늘려주고 스택과 맵을 도입하는 등의 변화가 있었다. +- 그래서 기존의 BPF를 cBPF (classic BPF)라고 부르고 새로운 BPF를 eBPF로 부르게 되었다. +- 현재의 리눅스 커널은 둘을 모두 지원하고, eBPF를 처리하는 가상머신에서 기존의 cBPF도 같이 처리하는 형태로 작동한다. +- cBPF와 eBPF 스펙의 상세 차이는 다음과 같다. + +|항목|cBPF|eBPF| +|-|-|-| +|레지스터|32bit, 2개의 레지스터와 스택|64bit, 11개의 레지스터| +|저장소|16 메모리 블록|크기 제한이 없는 맵, 512바이트의 스택| +|시스템 콜|N/A|bpf()| +|이벤트|네트워크 패킷
시스템 콜|네트워크 패킷
시스템 콜
kprobe
uprobe
트레이스포인트
USDT
소프트웨어 이벤트
하드웨어 이벤트| + +image + +### eBPF를 활용한 프로그램 + +- **Cilium** + - Cilium은 eBPF 기반 네트워킹, 보안 및 observability를 제공하는 오픈 소스 프로젝트이다. 컨테이너 워크로드의 새로운 확장성, 보안 및 가시성 요구사항을 해결하기 위해 설계되었다. Service Mesh, Hubble, CNI 3가지 타입이 있다. + + image + + + +- **Calico** + - K8s cni로 사용할 수 있는 Pluggable eBPF 기반 네트워킹 및 보안 오픈소스이다. Calico Open Source는 컨테이너 및 Kubernetes 네트워크를 단순화, 확장 및 보안하기 위해 설계되었디. + - Calico의 eBPF 데이터 플레인은 eBPF 프로그램의 성능, 속도 및 효율성을 활용하여 환경에 대한 네트워킹, 로드 밸런싱 및 커널 내 보안을 강화한다. + + image + +--- +참고 +- https://www.tcpdump.org/papers/bpf-usenix93.pdf +- https://netflixtechblog.com/how-netflix-uses-ebpf-flow-logs-at-scale-for-network-insight-e3ea997dca96 +- https://www.brendangregg.com/bpf-performance-tools-book.html +- https://www.amazon.com/gp/reader/0136554822?asin=B081ZDXNL3&revisionId=c47b7fdb&format=1&depth=1 +- https://en.wikipedia.org/wiki/Berkeley_Packet_Filter +- https://www.youtube.com/watch?v=lrSExTfS-iQ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BPF\342\200\205\355\224\204\353\241\234\352\267\270\353\236\250\342\200\205\355\203\200\354\236\205.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BPF\342\200\205\355\224\204\353\241\234\352\267\270\353\236\250\342\200\205\355\203\200\354\236\205.md" new file mode 100644 index 00000000..7c6f0b7e --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BPF\342\200\205\355\224\204\353\241\234\352\267\270\353\236\250\342\200\205\355\203\200\354\236\205.md" @@ -0,0 +1,236 @@ +--- +title: 'BPF 프로그램 타입' +lastUpdated: '2024-03-02' +--- + +BPF 프로그램의 종류는 다양하다. BPF 프로그램은 이벤트를 중심으로 작성되는데, 프로그램 타입에 따라 사용할 수 있는 이벤트가 제한적이므로 작성하고자 하는 프로그램이 어떤 범주에 속하는지 잘 알고 있어야 한다. + +작성한 프로그램은 해당하는 이벤트가 발생할 때 실행되고, 실행 시점에 프로그램에서 필요로 하는 정보가 컨텍스트로 제공될 것이다. + +### 프로그램 타입 + +[커널 내 소스](https://github.com/torvalds/linux/blob/v5.8/include/uapi/linux/bpf.h#L161)에는 커널에서 지원하는 BPF 프로그램의 타입 전체가 나열되어 있다. + +```c +/* Note that tracing related programs such as + * BPF_PROG_TYPE_{KPROBE,TRACEPOINT,PERF_EVENT,RAW_TRACEPOINT} + * are not subject to a stable API since kernel internal data + * structures can change from release to release and may + * therefore break existing tracing BPF programs. Tracing BPF + * programs correspond to /a/ specific kernel which is to be + * analyzed, and not /a/ specific kernel /and/ all future ones. + */ +enum bpf_prog_type { + BPF_PROG_TYPE_UNSPEC, + BPF_PROG_TYPE_SOCKET_FILTER, + BPF_PROG_TYPE_KPROBE, + BPF_PROG_TYPE_SCHED_CLS, + BPF_PROG_TYPE_SCHED_ACT, + BPF_PROG_TYPE_TRACEPOINT, + BPF_PROG_TYPE_XDP, + BPF_PROG_TYPE_PERF_EVENT, + BPF_PROG_TYPE_CGROUP_SKB, + BPF_PROG_TYPE_CGROUP_SOCK, + BPF_PROG_TYPE_LWT_IN, + BPF_PROG_TYPE_LWT_OUT, + BPF_PROG_TYPE_LWT_XMIT, + BPF_PROG_TYPE_SOCK_OPS, + BPF_PROG_TYPE_SK_SKB, + BPF_PROG_TYPE_CGROUP_DEVICE, + BPF_PROG_TYPE_SK_MSG, + BPF_PROG_TYPE_RAW_TRACEPOINT, + BPF_PROG_TYPE_CGROUP_SOCK_ADDR, + BPF_PROG_TYPE_LWT_SEG6LOCAL, + BPF_PROG_TYPE_LIRC_MODE2, + BPF_PROG_TYPE_SK_REUSEPORT, + BPF_PROG_TYPE_FLOW_DISSECTOR, + BPF_PROG_TYPE_CGROUP_SYSCTL, + BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE, + BPF_PROG_TYPE_CGROUP_SOCKOPT, + BPF_PROG_TYPE_TRACING, + BPF_PROG_TYPE_STRUCT_OPS, + BPF_PROG_TYPE_EXT, + BPF_PROG_TYPE_LSM, +}; +``` + +아래 표와 같이 이 프로그램 타입들을 범주별로 묶을 수 있다. + +|범주|프로그램 타입| +|-|-| +|소켓 관련|`SOCKET_FILTER`
`SK_SKB`
`SOCK_OPS`| +|TC 관련|`BPF_PROG_SCHED_CLS`
`BPF_PROG_SCHED_ACT`| +|XDP 관련|`BPF_PROG_TYPE_XDP`| +|트레이싱 관련|`BPF_PROG_TYPE_KPROBE`
`BPF_PROG_TYPE_TRACEPOINT`
`BPF_PROG_TYPE_RERF_EVENT`| +|CGROUP 관련|`BPF_PROG_TYPE_CGROUP_SKB`
`BPF_PROG_TYPE_CGROUP_SOCK`
`BPF_PROG_TYPE_CGROUP_DEVICE`| +|터널링 관련|`BPF_PROG_TYPE_LWT_IN`
`BPF_PROG_TYPE_LWT_OUT`
`BPF_PROG_TYPE_LWT_XMIT`
`BPF_PROG_TYPE_LWT_SEGGLOCAL`| + +### 타입별 특징 + +각 타입별로 신경써야 하는 부분은 다음 3가지이다. + +1. 어떤 역할이며, 언제 프로그램이 실행되는가? +2. 어떻게 커널에 로딩하는가? +3. 어떤 컨텍스트가 제공되는가? + +BPF_PROG_TYPE_KPROBE 타입의 프로그램을 예로 들어 살펴보자. + +1. 어떤 역할이며, 언제 프로그램이 실행되는가? + - `BPF_PROG_TYPE_KPROBE`는 이름 그대로 [`kprobe`](https://www.kernel.org/doc/Documentation/trace/kprobetrace.txt)를 활용하는 프로그램 타입이다. + - `kprobe`는 커널의 함수 진입점에 바인딩되는 이벤트로서 커널 내 특정 함수 호출 정보를 제공할 수 있다. + +2. 어떻게 커널에 로딩하는가? + + - `kprobe`에 관련된 인터페이스는 sysfs 밑의 tracef애 있다. + - tracefs는 트레이싱을 위한 특별한 파일 시스템으로, 바인딩을 위한 ID를 tracefs를 통해 발급받을 수 있다. 보통은 debugfs 아래에 마운트되어 있다. + + ```bash + // 마운트 정보를 확인한다. + $ mount | grep tracing + tracefs on /sys/kernel/debug/tracing type tracefs (rw, nosuid,nodev,noexec,relatime) + + // 바인딩을 위한 ID를 발급받는다. + $ echo 'p:myprobe tcp_retransmit_skb' > /sys/kernel/debug/tracing/kprobe_events + $ cat /sys/kernel/debug/tracing/events/kprobes/myprobe/id + 1965 + ``` + + - 이렇게 발급받은 ID는 BPF 프로그램을 로드할 때 `sys_perf_event_open()`에 해당 id를 찾아서 전달된다. + + ```c + static int load_and_attach(const char *event, struct bpf_insn *prog, int size) { + ... + if (is_kprobe || is_kretprobe) { + ... + strcpy(buf, DEBUGFS); + strcat(buf, "events/kprobes/"); + strcat(buf, event_prefix); + strcat(buf, event); + strcat(buf, "/id"); + } + ... + buf[err] = 0; + id = atoi(buf); + attr.config = id; + + efd = sys_perf_event_open(&attr, -1/*pid*/, 0/*cpu*/, -1/*group_fd*/, 0); + ... + } + ``` + + - BPF 바이너리는 이벤트가 바인딩되기 전에 커널로 먼저 로딩되어야 한다. `do_load_bpf_file()` 함수에서 주어진 경로의 ELF 바이너리를 읽어서 프로그램과 맵, 그리고 그외 ELF 섹션에 기술된 라이선스 및 버전 등을 추출한다. + + ```c + static int do_load_bpf_file(const char *path, fixup_map_cb fixup_map) { + /* scan over all elf sections to get license and map info */ + for (i = 1; i < ehdr.e_shnum; i++) { + + if (get_sec(elf, i, &ehdr, &shname, &shdr, &data)) + continue; + + if (0) /* helpful for llvm debugging */ + printf("section %d:%s data %p size %zd link %d flags %d\n", + i, shname, data->d_buf, data->d_size, + shdr.sh_link, (int) shdr.sh_flags); + + if (strcmp(shname, "license") == 0) { + processed_sec[i] = true; + memcpy(license, data->d_buf, data->d_size); + } else if (strcmp(shname, "version") == 0) { + processed_sec[i] = true; + if (data->d_size != sizeof(int)) { + printf("invalid size of version section %zd\n", + data->d_size); + return 1; + } + memcpy(&kern_version, data->d_buf, sizeof(int)); + } else if (strcmp(shname, "maps") == 0) { + int j; + + maps_shndx = i; + data_maps = data; + for (j = 0; j < MAX_MAPS; j++) + map_data[j].fd = -1; + } else if (shdr.sh_type == SHT_SYMTAB) { + strtabidx = shdr.sh_link; + symbols = data; + } + } + ... + /* load programs */ + for (i = 1; i < ehdr.e_shnum; i++) { + + if (processed_sec[i]) + continue; + + if (get_sec(elf, i, &ehdr, &shname, &shdr, &data)) + continue; + + if (...) { + ret = load_and_attach(shname, data->d_buf, + data->d_size); + if (ret != 0) + goto done; + } + } + } + ``` + + - 필요한 정보를 처리하고 나면 `load_and_attach()`가 호출되며, 다음 코드 블럭에서 프로그램을 커널로 로딩한다. + - `bpf_load_program()`은 bpf() 시스템 콜을 추상화해주는 `libbpf` 라이브러리에서 제공하는 함수이다. + + ```c + static int do_load_bpf_file(const char *path, fixup_map_cb fixup_map) { + ... + fd = bpf_load_program(prog_type, prog, insns_cnt, license, kern_version, + bpf_log_buf, BPF_LOG_BUF_SIZE); + ... + } + ``` + +3. 어떤 컨텍스트가 제공되는가? + + - 전달되는 컨텍스트는 `struct pt_regs *ctx`이다. + - 이 구조체는 레지스터를 추상화한 구조체로서 이를 통해 함수 호출 시 전달되는 매개변수, 레지스터 상태, 함수 실행 전후의 컨텍스트 등을 확인할 수 있다. + + ```c + struct pt_regs { + unsigned long pc; /* 4 */ + unsigned long ps; /* 8 */ + unsigned long depc; /* 12 */ + unsigned long exccause; /* 16 */ + unsigned long excvaddr; /* 20 */ + unsigned long debugcause; /* 24 */ + unsigned long wmask; /* 28 */ + unsigned long lbeg; /* 32 */ + unsigned long lend; /* 36 */ + unsigned long lcount; /* 40 */ + unsigned long sar; /* 44 */ + unsigned long windowbase; /* 48 */ + unsigned long windowstart; /* 52 */ + unsigned long syscall; /* 56 */ + unsigned long icountlevel; /* 60 */ + unsigned long scompare1; /* 64 */ + unsigned long threadptr; /* 68 */ + + /* Additional configurable registers that are used by the compiler. */ + xtregs_opt_t xtregs_opt; + + /* Make sure the areg field is 16 bytes aligned. */ + int align[0] __attribute__ ((aligned(16))); + + /* current register frame. + * Note: The ESF for kernel exceptions ends after 16 registers! + */ + unsigned long areg[XCHAL_NUM_AREGS]; + }; + ``` + + + - (BCC를 사용하면 구조체에서 정보를 가져오는 부분도 추상화되어 작동한다.) + +--- +참고 +- https://github.com/torvalds/linux/blob/v5.8/samples/bpf/bpf_load.c +- https://github.com/torvalds/linux/blob/v5.8/include/uapi/linux/bpf.h#L161 +- https://www.kernel.org/doc/Documentation/trace/kprobetrace.txt \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BTF.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BTF.md" new file mode 100644 index 00000000..5a1e08e1 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/BPF/BTF.md" @@ -0,0 +1,164 @@ +--- +title: 'BTF' +lastUpdated: '2024-03-02' +--- + +BTF is the metadata format which encodes the debug info related to BPF program/map. The name BTF was used initially to describe data types. The BTF was later extended to include function info for defined subroutines, and line info for source/line information. + +The debug info is used for map pretty print, function signature, etc. The function signature enables better bpf program/function kernel symbol. The line info helps generate source annotated translated byte code, jited code and verifier log. + +The BTF specification contains two parts, + +- BTF kernel API +- BTF ELF file format + +The kernel API is the contract between user space and kernel. The kernel verifies the BTF info before using it. The ELF file format is a user space contract between ELF file and libbpf loader. + +The type and string sections are part of the BTF kernel API, describing the debug info (mostly types related) referenced by the bpf program. These two sections are discussed in details in 2. BTF Type and String Encoding. + +## BTF Type and String Encoding + +The file `include/uapi/linux/btf.h` provides high-level definition of how types/strings are encoded. + +The beginning of data blob must be: + +```c +struct btf_header { + __u16 magic; + __u8 version; + __u8 flags; + __u32 hdr_len; + + /* All offsets are in bytes relative to the end of this header */ + __u32 type_off; /* offset of type section */ + __u32 type_len; /* length of type section */ + __u32 str_off; /* offset of string section */ + __u32 str_len; /* length of string section */ +}; +``` + +The magic is `0xeB9F`, which has different encoding for big and little endian systems, and can be used to test whether BTF is generated for big- or little-endian target. The `btf_header` is designed to be extensible with `hdr_len` equal to `sizeof(struct btf_header)` when a data blob is generated. + +### String Encoding + +The first string in the string section must be a null string. The rest of string table is a concatenation of other null-terminated strings. + +### Type Encoding + +BTF represents each type with one of a few possible type descriptors identified by kind: `BTF_KIND_INT`, `BTF_KIND_ENUM`, `BTF_KIND_STRUCT`, `BTF_KIND_UNION`, `BTF_KIND_ARRAY`, ect. + +That type has a index, and the type id `0` is reserved for void type. The type section is parsed sequentially and type id is assigned to each recognized type starting from id `1`. Currently, the following types are all supported list: + +```c +#define BTF_KIND_INT 1 /* Integer */ +#define BTF_KIND_PTR 2 /* Pointer */ +#define BTF_KIND_ARRAY 3 /* Array */ +#define BTF_KIND_STRUCT 4 /* Struct */ +#define BTF_KIND_UNION 5 /* Union */ +#define BTF_KIND_ENUM 6 /* Enumeration up to 32-bit values */ +#define BTF_KIND_FWD 7 /* Forward */ +#define BTF_KIND_TYPEDEF 8 /* Typedef */ +#define BTF_KIND_VOLATILE 9 /* Volatile */ +#define BTF_KIND_CONST 10 /* Const */ +#define BTF_KIND_RESTRICT 11 /* Restrict */ +#define BTF_KIND_FUNC 12 /* Function */ +#define BTF_KIND_FUNC_PROTO 13 /* Function Proto */ +#define BTF_KIND_VAR 14 /* Variable */ +#define BTF_KIND_DATASEC 15 /* Section */ +#define BTF_KIND_FLOAT 16 /* Floating point */ +#define BTF_KIND_DECL_TAG 17 /* Decl Tag */ +#define BTF_KIND_TYPE_TAG 18 /* Type Tag */ +#define BTF_KIND_ENUM64 19 /* Enumeration up to 64-bit values */ +``` + +Note that the type section encodes debug info, not just pure types. BTF_KIND_FUNC is not a type, and it represents a defined subprogram. + +Each type contains the following common data: + +```c +struct btf_type { + __u32 name_off; + /* "info" bits arrangement + * bits 0-15: vlen (e.g. # of struct's members) + * bits 16-23: unused + * bits 24-28: kind (e.g. int, ptr, array...etc) + * bits 29-30: unused + * bit 31: kind_flag, currently used by + * struct, union, fwd, enum and enum64. + */ + __u32 info; + /* "size" is used by INT, ENUM, STRUCT, UNION and ENUM64. + * "size" tells the size of the type it is describing. + * + * "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT, + * FUNC, FUNC_PROTO, DECL_TAG and TYPE_TAG. + * "type" is a type_id referring to another type. + */ + union { + __u32 size; + __u32 type; + }; +}; +``` + +For certain kinds, the common data are followed by kind-specific data. The name_off in struct btf_type specifies the offset in the string table. The following sections detail encoding of each kind. + +## BTF type graph + +image + +The diagram above should give a pretty good idea of the BTF type graph. + +It shows some C source code on the left and its corresponding BTF type graph on the right. The type with ID 0 is special, it represents void and is implicit: the first type descriptor in the .BTF section has type ID 1. In all the diagrams in this blog post, type IDs are written out in the top-left corner of each type node. Type references are marked with arrows. For structs/unions it should be obvious from the diagrams which field references which type. + +A well-known tool, [`pahole`](https://linux.die.net/man/1/pahole), was recently updated with the ability to convert DWARF into corresponding BTF type information in a straightforward, one-to-one fashion. It iterates over each DWARF type descriptor, converts it trivially into a BTF type descriptor and embeds all of them into the `.BTF` section in the ELF binary. + +### Deduplication + +In BTF type graph, the same type hierarchy (e.g., struct and all the types that struct references) can be represented in DWARF/BTF to various degrees of completeness (or rather, incompleteness) due to struct/union forward declarations. + +Let's take a look at an example. Suppose we have two compilation units, each using same `struct S`, but each of them having incomplete type information about struct's fields: + +image + +This compilation unit isolation means that it's possible there is no single CU with complete type information describing `structs S`, `A`, and `B`. Also, we might get tons of duplicated and redundant type information. Unfortunately, for BPF use cases that are going to rely on BTF data, it's important to have a single and complete type information per each unique type, without any duplicates and incompleteness. + +That means we need to have an algorithm, that would take this duplicated and potentially incomplete BTF information and emit nicely deduplicated type information, while also "reconstructing" complete type information by merging pieces of it from different CUs. + +So, in summary, we need an algorithm that will: + +- deduplicate redundant BTF information and leave single instance of each unique type; +- merge and reconstruct complete type information across multiple CUs; +- handle type cycles correctly and efficiently; + +do all of the above fast and reliably to become a part of Linux kernel build process. + +The algorithm completes its work in five separate passes, which we'll describe briefly here and in detail in subsequent sections: + +- **Strings deduplication**: Deduplicate string data, re-write all string offsets in BTF type descriptors to simplify string comparisons later. +- **Non-reference types deduplication**: Establish equivalence between and deduplicate integer, enum, struct, union and forward types. +- **Reference types deduplication**: Deduplicate pointers, const/volatile/restrict modifiers, typedefs, and array. +- **Type compaction**: Compact type descriptors leaving only unique ones. +- **Type IDs fix up**: Update all referenced type IDs with new ones established during compaction. + +There are also a few important ideas and data structures the algorithm relies on, which are critical to understanding it and would be useful to keep in mind as you follow along. + +1. **String deduplication** as a very first step. + - BTF doesn't embed strings into type descriptors. Instead, all the strings are concatenated into a byte array of string data with `\0` as a separator. + - Strings themselves are referenced from type descriptors using offsets into this array (typically through `name_off` fields). By performing string deduplication early, we can avoid comparing string contents later: after string deduplication it's enough to just compare corresponding string offsets to determine string equality, which both saves time and simplifies code. + +2. Using a side array to store **type equivalence mapping** for duplicated and resolved BTF types, instead of modifying IDs in-place. + - As the algorithm performs deduplication and type info merging, it needs to transform the type graph to record type equivalence and resolve forward declarations to struct/union type descriptors. + - By utilizing a separate array to store this mapping (instead of updating referenced type IDs in-place inside each affected BTF type descriptor), we are performing graph transformations that would potentially need O(N) type ID rewrites (if done in-place) with just a single value update in this array. + - This is a crucial idea to allow simple and efficient `BTF_KIND_FWD` → `BTF_KIND_{STRUCT|UNION}` remapping. The small price to pay for this is the need to consult this array for every type ID resolution when trying to get BTF type descriptor by type ID. + +3. **Canonical types**. + - The algorithm determines the canonical type descriptor ("one and only representative") for each unique type. This canonical type is the one that will go into the final deduplicated BTF type information. + - For struct/unions, it is also the type that the algorithm will merge additional type information into, as it discovers it from data in other CUs. + - To facilitate fast discovery of canonical types, we also maintain a canonical index, which maps the type descriptor's signature (i.e., kind, name, size, etc.) into a list of canonical types that match that signature. + - With sufficiently good choice of type signature function, we can limit the number of canonical types for each unique type signature to a very small number, allowing the discovery of canonical type for any duplicated type very quickly. + +--- +**reference** +- https://www.kernel.org/doc/html/next/bpf/btf.html +- https://facebookmicrosites.github.io/bpf/blog/2018/11/14/btf-enhancement.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/LVM.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/LVM.md" new file mode 100644 index 00000000..56398cf2 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/LVM.md" @@ -0,0 +1,90 @@ +--- +title: 'LVM' +lastUpdated: '2024-03-02' +--- + +LVM이란 Logical Volume을 효율적이고 유연하게 관리하기 위한 커널의 한 부분이자 프로그램이다. 기존방식이 파일시스템을 블록 장치에 직접 접근해서 R/W를 했다면, LVM은 파일시스템이 LVM이 만든 가상의 블록 장치에 R/W를 하게 된다. + +이처럼 LVM은 물리적 스토리지 이상의 추상적 레이어를 생성해서 논리적 스토리지(가상의 블록 장치)를 생성할 수 있게 한다. 직접 물리 스토리지를 사용하는 것보다 다양한 측면에서 뮤연성을 제공하기 위해, 유연한 용량 조절, 크기 조정이 가능한 스토리지 풀(Pool), 편의에 따른 장치 이름 지정, 디스크 스트라이핑, 미러 볼륨 등 기능을 가지고 있다. + +LVM의 주요 용어를 알아보자. + +## PV(Physical Volume) + +LVM에서 블록 장치(블록 단위로 접근하는 스토리지. 하드 디스크 등)를 사용하려면 우선 PV로 초기화를 해야한다. 즉, 블록 장치 전체 또는 그 블록 장치를 이루고 있는 파티션들을 **LVM에서 사용할 수 있게 변환**하는 것이다. + +예를 들어, `/dev/sda1`, `/dev/sda2` 등의 블록 스토리지를 LVM으로 쓰기위해서 PV로 초기화하게 된다. PV는 일정한 크기의 PE(Physical Extent)들로 구성된다. + +## PE(Physical Extent) + +PV를 구성하는 일정한 크기의 블록으로, LVM2에서의 기본크기는 `4MB`이다. LVM은 LVM1과 LVM2이 있는데, 여러 차이가 있지만 간단히 보면 LVM2가 기능이 개선된 버전이라고 이해할 수 있다. + +PE는 LV(Logical Volume)의 LE(Logical Extent)들과 1:1로 대응된다. 그렇기에 항상 PE와 LE의 크기는 동일하다. + +아래의 사진은 블록 장치(물리적 디스크)의 파티션들을 PV들로 초기화 시킨 모습이다. 각각의 PV들은 동일한 크기의 PE들로 구성된다. + +image + +## VG(Volume Group) + +PV들의 집합으로 LV를 할당할 수 있는 공간이다. 즉, PV들로 초기화된 장치들은 VG로 통합되게 된다. + +사용자는 VG안에서 원하는대로 공간을 쪼개서 LV로 만들 수 있다. 아래 사진은 위에서 만든 PV들을 하나의 VG1로 그룹지은 모습이다. + +## LV(Logical Volume) + +사용자가 최종적으로 다루게 되는 논리적인 스토리지이다. 생성된 LV는 파일 시스템 및 애플리케이션(Database 등)으로 사용된다. 위에서도 언급했듯이, LV를 구성하는 LE들은 PV의 PE들과 mapping된다. + +LE와 PE가 mapping되면서 총 3가지 유형의 LV가 생성된다. + +- **Linear LV** + 하나의 LV로 PV를 모으는 방법이다. 예를 들어 2개의 60GB 디스크(PV)를 가지고 120GB의 LV를 만드는 방식이다. LV만을 사용하는 사용자 입장에서는 120GB의 단일 장치만 있는 것 처럼 사용하게 된다. + image + +- **Striped LV** + LV에 데이터를 기록하게 되면 파일 시스템은 PE와 LE의 매핑대로 PV에 데이터를 기록하게 되는데, 스트라이프된 LV를 생성해서 데이터가 PV에 기록되는 방식을 바꿀 수 있다. 대량의 순차적 R/W 작업에서 효율을 올릴 수 있는 방법이다. + Striped LV는 Round-Robin 방식으로 미리 지정된 PV에 데이터를 분산기록하여 성능을 높이고, R/W를 병렬로 실행할 수 있도록 한다. 번갈아가는 기준은 데이터의 크기인데 이를 **스트라이프 크기**라고 하며 Extent의 크기(PE/LE 크기)를 초과할 수 없다. + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/58b4a62c-7204-4ecb-a040-4f27f50225bb) + +- Mirrored LV + 이름 그대로 블록 장치에 저장된 데이터의 복사본을 다른 블록 장치에 저장하는 방식이다. 데이터가 하나의 PV에 저장될때, 이를 미러하고있는 PV에 동일한 데이터가 저장된다. + Mirrored LV를 사용하면 장치에 장애가 발생했을 때 데이터를 보호할 수 있다. 하나의 장치에 장애가 발생하게 되면, 선형(Linear)으로 저장되어있기에 다른 장치에서 쉽게 접근이 가능해지고, 어떤 부분이 미러를 써서 동기화되었는지에 대한 로그를 디스크에 저장한다. + ![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/90dd07aa-43d5-4849-8168-21434b121085) + +## LE(Logical Extent) + +LV를 구성하는 일정한 크기의 블록으로 기본크기는 PE와 마찬가지로 4MB이다. 아래 그림은 위에서 만든 VG1에서 사용자가 원하는 크기대로 분할해서 LV1과 LV2를 만든 모습이다. 꼭 VG의 모든 공간을 다 써야하는 것은 아니다. 각각의 LV들은 동일한 크기의 LE로 구성되며 PE와 1:1로 매핑된다. + +image + +## 동작 + +Logical volume management는 디스크나 대용량 스토리지 장치를 유연하고 확장이 가능하게 다룰 수 있는 기술이며, 이를 커널에 구현한 기능을 바로 LVM(Logical Volume Manager) 라고 부른다. + +전통적으로 저장 장치를 사용했던 방식은 물리 디스크를 파티션이라는 단위로 나누어서 이를 OS에 마운트하여 사용했는데, 마운트를 하려면 파티션을 특정 디렉토리와 일치시켜 줘야 한다. + +만약 특정 파티션(/home 이라 가정하자)에 마운트된 파티션이 용량이 일정 수준 이상 찼을 경우 다음과 같이 번거로운 작업을 수행해야 했다. +- 추가 디스크 장착 +- 추가된 디스크에 파티션 생성 및 포맷 +- 새로운 마운트 포인트(/home2) 를 만들고 추가한 파티션을 마운트 +- 기존 home data를 home2 에 복사 또는 이동 +- 기존 home 파티션을 언마운트(umount) +- home2 를 home 으로 마운트 + +LVM 은 이름처럼 파티션대신 볼륨이라는 단위로 저장 장치를 다룰 수 있으며, 물리 디스크를 볼륨 그룹으로 묶고 이것을 논리 볼륨으로 분할하여 관리한다. 스토리지의 확장이나 변경시 서비스의 변경을 할 수 있으며 특정 영역의 사용량이 많아져서 저장 공간이 부족할 경우에 유연하게 대응할 수 있다. 아래는 LVM의 구성 예시이다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/963abfbb-ae4c-4796-8385-13ebda7eebe6) + +이제 /home 영역이 거의 찼을 경우 LVM이 적용되어 있으면 다음과 같이 처리할 수 있다. + +- 추가 디스크 장착 +- 추가된 디스크에 파티션을 만들어서 물리 볼륨(PV) 생성 +- 물리 볼륨을 볼륨 그룹(VG)에 추가. (여기서는 vg_data 볼륨 그룹으로 추가한다.) +- /home 이 사용하는 논리 볼륨인 lv_home의 볼륨 사이즈를 증가 + +위와 같이 변경 작업을 기존 데이터의 삭제나 이동 없이 서비스가 구동중인 상태에서 유연하게 볼륨을 늘리고 줄일 수 있다. + +--- +참고 +- https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux) +- https://tech.cloud.nongshim.co.kr/2018/11/23/lvmlogical-volume-manager-1-%EA%B0%9C%EB%85%90/ diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/Linux\342\200\205\353\224\224\353\240\211\355\206\240\353\246\254\342\200\205\352\265\254\354\241\260.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/Linux\342\200\205\353\224\224\353\240\211\355\206\240\353\246\254\342\200\205\352\265\254\354\241\260.md" new file mode 100644 index 00000000..c3104c8c --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/Linux\342\200\205\353\224\224\353\240\211\355\206\240\353\246\254\342\200\205\352\265\254\354\241\260.md" @@ -0,0 +1,50 @@ +--- +title: 'Linux 디렉토리 구조' +lastUpdated: '2024-03-02' +--- + +- `/bin`: 기본 명령어 저장 + +--- + +- `/sbin`: 시스템 관리를 위한 명령어들이 저장 + +--- + +- `/etc`: 환경설정에 연관된 파일들과 디렉터리들이 저장 2016(1) + - `/etc/rc.d`: 시스템의 부팅과 런 레벨 관련 스크립트들이 저장 + - `/etc/inittab`: init을 설정하는 파일 + - `/etc/issue`: 로그인을 위한 프롬프트가 뜨기 전에 출력되는 메시지를 설정하는 파일 + - `/etc/issue.net`: issue 파일과 기능은 같으나, 원격지 상에서 접속(telnet 등)할 경우에 출력되는 메시지 설정 - /etc/motd: 로그인 성공 후 쉘이 뜨기 전에 출력되는 메시지를 설정하는 파일 + - `/etc/nologin.txt`: 사용자의 쉘이 /sbin/nologin으로 지정되어 있을 때, 로그인 거부 메시지를 설정하는 파일 •/boot: 리눅스 부트에 필요한 부팅 지원 파일들이 저장 + +--- + +- `/mnt`: 외부장치를 마운트하기 위해 제공되는 디렉터리 + +--- + +- `/usr`: 각종 응용프로그램들이 설치되는 디렉터리 + - `/usr/bin`: /bin 디렉터리에 없는 다양한 실행파일이 저장 + - `/usr/src`: 시스템에서 사용하는 각종 프로그램들의 컴파일되지 않은 소스파일들이 저장 + +--- + +- `/lib`: 각종 라이브러리들이 저장 + +--- + +- `/dev`: 장치 드라이버들이 저장되는 가상 디렉터리 + - `/dev/console`: 시스템의 콘솔 + - `/dev/null`: 폐기를 위한 디렉터리. 이 디렉터리로 파일이나 데이터를 보내면 폐기된다. + - `find / -perm -4000 2>/dev/null`: 명령어 실행결과에서 오류메시지를 출력하지 않고 폐기 + +--- + +- `/proc`: 시스템의 각종 프로세서, 프로그램 정보, 하드웨어적인 정보들이 저장되는 가상 디렉터리 + - `/proc/partitions`: 파티션 정보 파일 + +--- + +- `/var`: 시스템에서 사용되는 동적인 파일들이 저장 + - `/var/log`: 프로그램들의 로그 파일들이 저장 diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/RAID.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/RAID.md" new file mode 100644 index 00000000..eed16570 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/RAID.md" @@ -0,0 +1,65 @@ +--- +title: 'RAID' +lastUpdated: '2023-12-15' +--- +## RAID(Redundant Array of Independent Disks) + +직역하면 **복수 배열 저가/독립 디스크**이다. 저장장치 여러 개를 묶어 고용량, 고성능인 저장 장치 한 개와 같은 효과를 얻기 위해 개발된 기법이다. + +RAID의 주 사용 목적은 크게 가용성과 고성능 구현으로 구분된다. + +동작 방식에 따라 Level 0~6으로 분류한다. + +주로 사용되는 것은 0, 1, 5, 6이며 무정지 구현을 극도로 추구하면 RAID 1, 고성능 구현을 극도로 추구하면 RAID 0, RAID 5, 6은 둘 사이에서 적당히 타협한 형태이다.. RAID 10이나 RAID 01과 같이 두 가지 방식을 혼용하는 경우도 있다. + +- RAID 0: 스트라이핑 기능(분배 기록) 사용 + - 여러 개의 멤버 하드디스크를 병렬로 배치하여 거대한 하나의 디스크처럼 사용한다. 데이터 입출력이 각 멤버 디스크에 공평하게 분배되며, 디스크의 수가 N개라면 입출력 속도 및 저장 공간은 이론상 N배가 된다. 다만 멤버 디스크 중 하나만 손상 또는 분실되어도 전체 데이터가 파손되며, 오류검출 기능이 없어 멤버 디스크를 늘릴수록 안정성이 떨어지는 문제가 있다. + +- RAID 1: 미러링을 통해 두 개 이상의 디스크를 하나의 디스크처럼 사용 + - 각 멤버 디스크에 같은 데이터를 중복 기록한다. 멤버 디스크 중 하나만 살아남으면 데이터는 보존되며 복원도 1:1 복사로 매우 간단하기 때문에, 서버에서 끊김 없이 지속적으로 서비스를 제공하기 위해 사용한다. + - 멤버 디스크를 늘리더라도 저장 공간은 증가하지 않으며, 대신 가용성이 크게 증가하게 된다. 상용 환경에서는 디스크를 2개를 초과해서 쓰는 경우가 드물지만, 극한 환경에서는 3개 이상의 멤버 디스크를 사용하기도 한다. + +- RAID 2: ECC(에러 검출 기능) 탑재 + - 오류정정부호(ECC)를 기록하는 전용의 하드디스크를 이용해서 안정성을 확보한다. RAID 2는 비트 단위에 Hamming code를 적용한다. + - 예를 들어, 디스크 1에 3, 디스크 2에 6을 저장하면 디스크 3에는 1+2의 값인 9를 저장한다. 이렇게 저장하면 디스크 1이 사라지더라도 디스크 2의 6의 값을 읽고, 디스크 3의 9의 값에서부터 디스크 1의 값 3을 읽을 수 있기 때문에 저장소 하나가 파손되더라도 데이터를 읽을 수 있는 것이다. 용량을 약간 희생하지만 하드가 고장났을때 데이터가 날아가는 일부 레이드 시스템에 비해 매우 높은 가용성과 저장용량 효율을 보인다. + - 모든 I/O에서 ECC 계산이 필요하므로 입출력 병목 현상이 발생하며, ECC 기록용으로 쓰이는 디스크의 수명이 다른 디스크들에 비해 짧아지는 문제가 있어 현재는 사용하지 않는다. (RAID 3, RAID 4도 마찬가지이다.) + +- RAID 3: 하나의 디스크를 에러검출을 위한 패리티 정보 저장용으로 사용하고 나머지 디스크에 데이터를 균등하게 분산 저장 + - 바이트 단위에 Hamming code를 적용한다. + +- RAID 4: RAID 3 방식과 같지만 블록 단위로 분산 저장 + - 블록 단위에 Hamming code를 적용한다. + +- RAID 5: 하나의 디스크에 패리티 정보를 저장하지 않고 분산 저장 (회전식 패리티 어레이) + - 패리티를 한 디스크에 밀어 넣지 않고 **각 멤버 디스크에 돌아가면서 순환적으로 저장**하여 입출력 병목 현상을 해결한다. N개의 디스크를 사용하면 (N-1)개의 저장 공간을 사용할 수 있다. RAID 4처럼 하나의 멤버 디스크 고장에는 견딜 수 있지만 디스크가 두 개 이상 고장 나면 데이터가 모두 손실된다. 데이터베이스 서버 등 큰 용량과 무정지 복구 기능을 동시에 필요로 하는 환경에서 주로 쓰인다. + - 매번 쓰기 작업 때마다 패리티 연산 과정이 추가되어, 성능을 보장하려면 고가의 패리티 연산 전용 프로세서와 메모리를 사용해야 한다. 멤버 디스크도 최소 3개 이상 사용해야 하므로 초기 구축 비용이 비싸다는 단점이 있다. + +- RAID 6: 하나의 패리티 정보를 두개의 디스크에 분산 저장. 쓰기 능력은 저하될 수 있지만 고장 대비 능력이 매우 높음. 두 개의 오류까지 검출 가능. + - RAID 5와 원리는 같으며, 서로 다른 방식의 패리티 2개를 동시에 사용한다. 성능과 용량을 희생해서 가용성을 높인 셈. N개의 디스크를 사용하면 (N-2)개의 저장 공간을 사용할 수 있다. RAID 5가 1개까지의 고장을 허용하는 것에 반해 이쪽은 2개까지 허용한다. + - 스토리지 서버와 같이 디스크를 빼곡히 꽂는(기본 10개 단위) 환경에서 RAID 5는 유지보수가 어려우며, 어레이 안정성을 높이기 위한 목적으로 주로 사용된다. + - Disk fail이 2개 동시에 나지 않는다면 정보를 보호할 수 있다. + - 컨트롤러가 RAID 5보다 더 비싸고, 멤버 디스크도 기본 4개 이상 확보해야 하므로 초기 구축 비용이 비싸다. + - 하드디스크 가용성이 RAID 5보다 높아야 하는 상황에서 쓰인다. + +## Nested RAID + +레이드 볼륨의 멤버로 다른 레이드 볼륨을 사용하는 형태이다. 볼륨 확장 과정에서 구성 편의성 문제로 형성되는 경우가 많다. 이 때 멤버 디스크를 묶는 배열을 하위 배열, 하위 배열을 묶는 배열을 상위 배열이라고 한다. 표기 방법은 (하위 배열 숫자)(상위 배열 숫자) 형식이다. 만약 하위 배열 숫자가 0이면 혼동을 피하기 위해 +상위배열 숫자를 적는다. 대표적인 예시로 10, 0+1, 15, 50, 0+5, 51 등이 존재한다. + +- RAID 10 + - 하위 배열은 RAID 1, 상위 배열은 RAID 0이다. RAID 1로 미러링 된 볼륨을 RAID 0으로 스트라이핑 한다. + - 퍼포먼스 및 용량 위주로 구성한다면, 디스크 2개를 RAID 1로 묶은 미러 볼륨 3개를 RAID 0으로 스트라이핑 한다. 이 경우 사용 가능한 총 용량은 3TB가 된다. + - 가용성 위주로 구성한다면, 디스크 3개를 RAID 1로 묶은 미러 볼륨 2개를 RAID 0으로 스트라이핑 한다. 이 경우 사용 가능한 총 용량은 2TB가 된다. + - RAID 0+1에 비해 디스크 장애 발생 시 복구가 수월하다. + +- RAID 0+1: 최소 4개 이상의 디스크를 2개씩 스트라이핑으로 묶고(RAID 0) 미러링으로 결합(RAID 1)한 방식 - RAID 10: 두 개의 디스크를 미러링으로 묶고(RAID 1) 스트라이핑(RAID 0)으로 결합한 방식 + - 하위 배열은 RAID 0, 상위 배열은 RAID 1이다. RAID 0으로 스트라이핑 된 볼륨을 RAID 1로 미러링 한다. + - 퍼포먼스 및 용량 위주로 구성한다면 디스크 3개를 RAID 0으로 묶은 스트라이프 볼륨 2개를 RAID 1로 미러링 한다. 이 경우 사용 가능한 총 용량은 3TB가 된다. + - 가용성 위주로 구성한다면, 디스크 2개를 RAID 0으로 묶은 스트라이프 볼륨 3개를 RAID 1로 미러링 한다. 이 경우 사용 가능한 총 용량은 2TB가 된다. + - RAID 10에 비해 RAID 볼륨이 깨졌을 경우 복구가 번거롭다. RAID 10과 비교하자면, 미러 볼륨으로 구성된 어레이에서 디스크 하나가 고장이 났다면, 미러 볼륨 자체는 깨지지 않는다. 즉, 디스크만 바꿔 넣어주면 알아서 리빌딩하여 원래 상태로 돌아간다. 하지만 RAID 0+1의 경우 디스크 하나가 고장이 났다면, 해당 RAID 0 어레이 전체가 깨져 버린다. 디스크를 교체한 뒤 RAID 0 어레이를 다시 구성한 다음, 미러링해야 한다. + +생성된 RAID 장치에 대한 레벨 정보를 확인할 때 사용하는 파일 -> `/proc/mdstat` + +--- +참고 +- https://en.wikipedia.org/wiki/RAID +- http://bitsavers.trailing-edge.com/pdf/ibm/7030/22-6530-2_7030RefMan.pdf#page=160 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/Symbolic\342\200\205Link.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/Symbolic\342\200\205Link.md" new file mode 100644 index 00000000..36f58ece --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/Symbolic\342\200\205Link.md" @@ -0,0 +1,40 @@ +--- +title: 'Symbolic Link' +lastUpdated: '2024-03-02' +--- + +Linux/UNIX 시스템에는 두 가지 유형의 링크가 있다. + +- **Hard link** : Hard link는 둘 이상의 파일 이름을 동일한 i-node와 연결한다. + +- **Soft link** : Soft link는 Windows(윈도우)의 바로 가기 같은 것으로, 파일 또는 디렉터리에 대한 간접 포인터이다. Hard link와 달리 심볼릭 링크는 다른 파일 시스템 또는 파티션의 파일이나 디렉터리를 가리킬 수 있다. + +image + + +### ln + +ln은 파일 간의 링크를 만드는 명령어이다. + +기본적으로 ln 명령은 Hard link를 생성한다. 심볼릭 링크를 만들려면 `-s` 옵션을 사용해야한다. + +심볼릭 링크를 생성하기 위한 ln 명령 구문은 다음과 같다. + +```bash +ln -s [OPTIONS] FILE LINK +``` + +FILE과 LINK가 모두 주어지면 ln은 첫 번째 인수(FILE)로 지정된 파일에서 두 번째 인수(LINK)로 지정된 파일에 대한 링크를 생성한다. + +### Symlinks 제거 + +심볼릭 링크를 삭제/제거하려면 unlink나 rm 명령을 사용할 수 있다. + +```bash +unlink symlink_to_remove +rm symlink_to_remove +``` + +--- +참고 +- https://www.freecodecamp.org/korean/news/rinugseu-symlink-tyutorieol-simbolrig-ringkeu-symbolic-link-reul-saengseonghago-sagjehaneun-bangbeob/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/od.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/od.md" new file mode 100644 index 00000000..1c5c1a5a --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/od.md" @@ -0,0 +1,87 @@ +--- +title: 'od' +lastUpdated: '2024-03-02' +--- + +`od` is a command that dumps binary files to eight digits. + +```bash +od [-aBbcDdeFfHhIiLlOosvXx] [-A base] [-j skip] [-N length] [-t type] + [[+]offset[.][Bb]] [file ...] +``` + +|Option|Description| +|-|-| +|-A base|Specify the input address base. The argument base may be one of d, o, x or n, which specify decimal, octal, hexadecimal addresses or no address, respectively.| +|-a|Output named characters. Equivalent to `-t a`.| +|-B, -o|Output octal shorts. Equivalent to `-t o2`.| +|-b|Output octal bytes. Equivalent to `-t o1`. +|-c|Output C-style escaped characters. Equivalent to `-t c`. +|-D|Output unsigned decimal ints. Equivalent to `-t u4`. +|-d|Output unsigned decimal shorts. Equivalent to `-t u2`. +|-e, -F|Output double-precision floating point numbers. Equivalent to `-t fD`.| +|-f|Output single-precision floating point numbers. Equivalent to `-t fF`.| +|-H, -X|Output hexadecimal ints. Equivalent to `-t x4`.| +| -h, -x|Output hexadecimal shorts. Equivalent to `-t x2`.| +|-I, -L, -l|Output signed decimal longs. Equivalent to `-t dL`.| +|-i|Output signed decimal ints. Equivalent to `-t dI`.| +|-j skip|Skip skip bytes of the combined input before dumping. The number may be followed by one of b, k, m or g which specify the units of the number as blocks (512 bytes), kilobytes, megabytes and gigabytes, respectively.| +|-N length|Dump at most length bytes of input.| +|-O|Output octal ints. Equivalent to `-t o4`.| +|-s|Output signed decimal shorts. Equivalent to `-t d2`.| +|-t type|Specify the output format. The type argument is a string containing one or more of the following kinds of type specifiers.| +|a|Named characters (ASCII). Control characters are displayed using the following names:image| +|c|Characters in the default character set. Non-printing characters are represented as 3-digit octal character codes, except the following characters, which are represented as C escapes like `\0`, `\n`, `\t`, so on. Multi-byte characters are displayed in the area corresponding to the first byte of the character. The remaining bytes are shown as ‘**’.| +|[d|o|u|x][C|S|I|L|n]|Signed decimal (d), octal (o), unsigned decimal (u) or hexadecimal (x). Followed by an optional size specifier, which may be either C (char), S (short), I (int), L (long), or a byte count as a decimal integer.| +|f[F|D|L|n]|Floating-point number. Followed by an optional size specifier, which may be either F (float), D (double) or L (long double).| +|-v|Write all input data, instead of replacing lines of duplicate values with a ‘*’.| + +### Example + +File `test`'s content is: + +``` +abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 +``` + +`od -t x1 test` -> Express the test file by 1 byte in hexadecimal. + +```bash +$ od -t x1 test +0000000 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 +0000020 71 72 73 74 75 76 77 78 79 7a 41 42 43 44 45 46 +0000040 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 +0000060 57 58 59 5a 31 32 33 34 35 36 37 38 39 30 0a +0000077 +``` + +`od -t x1z test` -> Express 1 byte by hexadecimal and display ASCII characters on the below. + +```bash +$ od -t x1c test +0000000 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 + a b c d e f g h i j k l m n o p +0000020 71 72 73 74 75 76 77 78 79 7a 41 42 43 44 45 46 + q r s t u v w x y z A B C D E F +0000040 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 + G H I J K L M N O P Q R S T U V +0000060 57 58 59 5a 31 32 33 34 35 36 37 38 39 30 0a + W X Y Z 1 2 3 4 5 6 7 8 9 0 \n +0000077 +``` + +`od -Ad test` -> Print as demical. + +```bash +$ od -Ad test +0000000 061141 062143 063145 064147 065151 066153 067155 070157 +0000016 071161 072163 073165 074167 075171 041101 042103 043105 +0000032 044107 045111 046113 047115 050117 051121 052123 053125 +0000048 054127 055131 031061 032063 033065 034067 030071 000012 +0000063 +``` + +--- +reference +- https://man7.org/linux/man-pages/man1/od.1.html +- https://m.blog.naver.com/syung1104/191927932 diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\353\260\261\354\227\205.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\353\260\261\354\227\205.md" new file mode 100644 index 00000000..ab626e00 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\353\260\261\354\227\205.md" @@ -0,0 +1,63 @@ +--- +title: '백업' +lastUpdated: '2024-03-02' +--- + +- **Day-zero Backup**: 시스템을 설치한 후 사용자들이 시스템을 사용하기 전에 시스템을 백업하는 것 +- **Full Backup**: 주기적으로 시스템을 백업하는 것 +- **Incremental Backup**: 특정한 이벤트 후 또는 주기적으로 이전의 백업 후 변경된 파일들만 백업 하는 것 +- **단순 백업**: 첫 백업 때 풀 백업을 진행한 후, 그 다음부터 변경분 백업을 수행하는 것 +- **다단계 백업**(**Multilevel Backup**): 큰 규모나 중요한 시스템의 백업을 수행하는 것 + +### 디렉터리 단위 백업 명령어 + +- `tar` + - 마운트된 파일시스템 내에서 백업 + - [-g]: 증분 백업을 위한 옵션 + +- `cpio` + - 많은 양의 데이터를 테이프 드라이브에 백업하기 위한 명령어 + - 네트워크를 통한 백업 / 증분 백업을 지원하지 않는다. + - 주요 옵션 + - [-c]: ASCII 형태로 헤더 정보를 읽고 쓴다. + - [-i]: 아카이브에서 파일 추출 + - [-o]: 아카이브 생성 + - [-v]: 진행 과정을 자세하게 출력 + - `ls *.conf | cpio -ocv > config.bak` → 모든 .conf 파일을 백업 + +### 파일시스템 단위 백업 명령어 + +- `dump` + - 파일 시스템 전체를 백업하는 명령어 + - 점진적인 백업 기능: 이전 백업 이후 변경된 파일들에 대해 백업 수행 가능 + - 장점 + - 여러 개의 테이프에 백업 가능 + - 어떤 타입의 파일도 백업 가능 + - 증분 백업 가능 + - 결함을 가진 파일들도 다룰 수 있음 + - 단점 + - 모든 파일 시스템이 개별적으로 dump 되어야 함 (각 파티션도 개별적으로 dump) + - NFS 파일 시스템을 dump할 수 없다. (로컬 파일 시스템만 dump 가능) + - 주요 옵션 + - [-0~9]:0– 전체 백업 /1,2,...- 부분 백업 - [-f]: 지정한 파일명으로 백업 파일 생성 + +- `restore` + - dump 복구를 위한 명령어 + - 주요 옵션 + - [-i]: 대화형으로 복원 수행 -[-f]: 장치 이름 지정 + - [-r]: 백업 대상 전체를 복원 + + +### 디스크 단위의 백업 +- `dd` + - 잘 사용되지 않는다. + +### 파일 동기화 + +- `rsync` + - 파일을 동기화하는 명령어. 원격지에 있는 파일들도 동기화할 수 있다. + - cp, ftp, rcp 명령어보다 동기화 기능이 뛰어남 + - `rsync [옵션] [동기화할 원본] [동기화될 위치]` + - [-a]: 아카이브 모드 (여러 옵션을 묶어 놓은 옵션) + - [-v]: 진행 과정을 자세하게 출력 + - [-z]: 동기화 파일을 압축 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\352\264\200\353\246\254\342\200\205\353\252\205\353\240\271\354\226\264.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\352\264\200\353\246\254\342\200\205\353\252\205\353\240\271\354\226\264.md" new file mode 100644 index 00000000..586e6484 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\352\264\200\353\246\254\342\200\205\353\252\205\353\240\271\354\226\264.md" @@ -0,0 +1,179 @@ +--- +title: '파일 관리 명령어' +lastUpdated: '2023-12-15' +--- +## 디스크 사용량 + +- `df` + - 파일 시스템별 디스크 사용량 + + ```bash + $ df --help + Usage: df [OPTION]... [FILE]... + Show information about the file system on which each FILE resides, + or all file systems by default. + + Mandatory arguments to long options are mandatory for short options too. + -a, --all include pseudo, duplicate, inaccessible file systems + -h, --human-readable print sizes in powers of 1024 (e.g., 1023M) + -t, --type=TYPE limit listing to file systems of type TYPE + -T, --print-type print file system type + + $ df -h + Filesystem Size Used Avail Use% Mounted on + /dev/root 20G 13G 6.6G 66% / + tmpfs 1.9G 0 1.9G 0% /dev/shm + tmpfs 743M 1.3M 742M 1% /run + tmpfs 5.0M 0 5.0M 0% /run/lock + /dev/nvme0n1p15 105M 5.3M 100M 5% /boot/efi + tmpfs 372M 4.0K 372M 1% /run/user/1000 + ``` + +- `df` + - 디렉터리, 사용자 별 디스크 사용량 + - 옵션 없이 사용 시 현재 디렉터리의 디스크 사용량을 출력 + + ```c + $ du --help + Usage: du [OPTION]... [FILE]... + or: du [OPTION]... --files0-from=F + Summarize disk usage of the set of FILEs, recursively for directories. + + -d, --max-depth=N print the total for a directory (or file, with --all) + only if it is N or fewer levels below the command + line argument; --max-depth=0 is the same as + --summarize + -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G) + --inodes list inode usage information instead of block usage + -s, --summarize display only a total for each argument + $ du -h + 8.0K ./.ssh + 4.0K ./.cache + 8.0K ./.docker + 4.0K ./snap/certbot/common + 4.0K ./snap/certbot/3024 + 4.0K ./snap/certbot/2913 + ``` + +## quota + +- `quotacheck` + - 쿼터 파일 생성/확인/수정을 위해 파일 시스템을 검사하는 명령어 + - [-a]: 전체 파일 시스템 검사 + - [-u]: 사용자 쿼터 확인 + - [-g]: 그룹 쿼터 확인 + - [-m] : 파일 시스템을 다시 마운트하지 않는다. + - [-v] : 자세하게 출력 + - ```c + $ quotacheck --help + Utility for checking and repairing quota files. + quotacheck [-gucbfinvdmMR] [-F ] filesystem|-a + -a, --all check all filesystems + -u, --user check user files + -g, --group check group files + + -m, --no-remount do not remount filesystem read-only + -M, --try-remount try remounting filesystem read-only, + continue even if it fails + ``` + - ex. `quotacheck -avugm` + +- `quotaon` + - 파일 시스템의 쿼터 기능을 활성화하는 명령어 + - ```c + $ quotaon --help + quotaon: Usage: + quotaon [-guPvp] [-F quotaformat] [-x state] -a + quotaon [-guPvp] [-F quotaformat] [-x state] filesys ... + + -a, --all turn quotas on for all filesystems + + -u, --user operate on user quotas + -g, --group operate on group quotas + -v, --verbose print more messages + ``` + - ex. `quotaon -augv` + +- `edquota` + - 쿼터를 설정하는 명령어 (vi 형식) + - ```c + $ edquota --help + edquota: Usage: + edquota [-u|g|-P] [-F formatname] [-f filesystem] -t + edquota [-u|g|-P] [-F formatname] [-f filesystem] -T username|groupname|projectname ... + + -u, --user edit user data + -g, --group edit group data + -P, --project edit project data + ``` + +- `setquota` + - 쿼터를 설정하는 명령어 (커맨드 라인에서 옵션으로 설정하는 형식) + - 형식 : `setquota [옵션] [이름] [block soft limit] [block hard limit] [inode soft limit] [inode hard limit] [장치명]` + - ex. `setquota -u fedora 1000 1100 0 0 /` + +- `quota` + - 쿼터 정보를 출력하는 명령어 + - [-u], [-g] 옵션 포함 + - ex. `quota -u fedora` → fedora 유저의 쿼터 설정 내용 출력 + +- `repquota` + - 쿼터 정보를 요약하여 출력하는 명령어 + - [-a], [-u], [-g] 옵션 포함 + - [-v]: 사용량이 없는 쿼터의 정보 출력 + +## 파일 시스템 ACL +- 접근 권한을 확인하는 명령어 +- `getfacl` + - 접근 권한을 확인하는 명령어 +- `setfacl` + - 접근 권한을 설정하는 명령어 + +## LVM + +1. 파티션의 종류 변경 + - `fdisk` → `t` → 기존 83(linux) 를 8e(linux LVM)로 변경 +2. PV 생성 + - `pvcreate` + - `pvscan` 명령어: PV 상태를 확인 +3. VG 생성: vgcreate 명령어 +4. VG 활성화 + - `vgchange -a y`: 활성화 + - `vgchange -a n`: 비활성화 + - `vgdisplay -v`: VG 상태 확인 +5. LV 생성 + - `lvcreate` + - `lvscan` 명령어: LV 상태 확인 +6. LV에 파일 시스템 생성: `mkfs`/`mke2fs` 명령어 사용 +7. LV 마운트: mount 명령어 사용 + +## 파일 시스템 점검/복구 + +- `fsck` + - 파일 시스템을 검사/복구하는 명령어 + - ```c + $ fsck --help + + Usage: + fsck [options] -- [fs-options] [ ...] + + Check and repair a Linux filesystem. + + -A check all filesystems + -V explain what is being done + ``` + - `fsck.ext2` / `fsck.ext3` / `fsck.ext4` 명령어도 제공 + +- `e2fsck` + - 파일 시스템을 점검하기 전에 해당 파일 시스템을 umount 후 진행 + - [-j ext3/ext4]: ext3나 ext4 파일 시스템을 검사할 때 지정 + +- `badblocks` + - 지정한 장치의 배드 블록을 검사하는 명령어 + - `-v`: 검사 결과 자세히 출력 + - `-o`: 검사 결과를 지정한 출력 파일에 저장 + +- `tune2fs` + - ext2 파일 시스템을 설정(튜닝)하는 명령어 + - `-i`: ext2 파일 시스템을 ext3 파일 시스템으로 바꿈 + - `-l`: 파일 시스템 슈퍼 블록 내용을 출력 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\354\213\234\354\212\244\355\205\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\354\213\234\354\212\244\355\205\234.md" new file mode 100644 index 00000000..f7203e72 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\354\213\234\354\212\244\355\205\234.md" @@ -0,0 +1,55 @@ +--- +title: '파일 시스템' +lastUpdated: '2024-03-02' +--- + +- ext + - 리눅스 초기에 사용되던 파일 시스템 + +- ext2 + - ext3가 개발되기 이전까지 가장 많이 사용하는 파일 시스템으로 리눅스 파일 시스템 대부분의 기능을 제공하는 파일 시스템이다. + - ext2는 뛰어난 안정성과 속도로 가장 유명한 파일 시스템으로 자리잡았고 ext3 또한 ext2에 기반해서 개발되었다. + - 또한 쉽게 호환되며 업그레이드도 어렵지 않게 설계되어 있다. + +- ext3 + - ext2에서 fsck의 단점을 보완하기 위해 저널링 기술을 도입한 파일시스템 + - 저널링(Journaling) 기술: 데이터를 디스크에 쓰기 전에 로그에 데이터를 남겨 fsck보다 빠르고 안정적인 복구 기능을 제공 + - 최대 볼륨크기 2TB~16TB + - 최대 파일크기 16GB~2TB 지원 + - 하위 디렉터리 수 :32000개 + +- ext4 + - ext3의 기능을 향상시킨 파일시스템 + - ext2/ext3 파일시스템과 호환 가능 + - 지연된 할당: 데이터가 디스크에 쓰여지기 전까지 블록 할당을 지연시켜 향상된 블록 할당이 가능 + - 최대 볼륨크기 1EB / 최대 파일크기 16TB 지원 / 하위 디렉터리 수: 64000개 + +- XFS + - 고성능 64비트 저널링 파일 시스템 + - 리눅스 커널 2.4.20 버전에서 커널로 포팅되었다. + +- minix + - 과거 Minix에서 사용되었던 파일 시스템으로 가장 오래되고 기본이 되는 파일 시스템이다. + - 몇몇 Time Stamp가 유실되는 경우가 있고, 파일 이름은 30문자로 제한된다. 또한 파일 시스템마다 최대 64MB 성능제한이 있다. + - 대부분의 배포본의 부팅 디스크는 보통 minix 파일 시스템으로 구성되어 있다. + +- msdos + - MS-DOS의 FAT파일 시스템과 호환을 지원하는 파일 시스템이다. 또한 msdos는 OS/2와 윈도즈 NT의 FAT파일 시스템과도 호환된다. + +- hpfs OS/2 + - OS/2의 파일 시스템이다. 하지만 현재는 읽기 전용인 파일 시스템으로 파일 시스템에 대한 읽기만이 가능하다. + +- isofs CD-ROM + - ISO 기준을 따르는 표준 CD-ROM의 파일 시스템이다. + - isofs CD-ROM은 CD-ROM에 좀더 긴 파일 이름을 사용할 수 있도록 확장한 Rock Bridge가 기본으로 지원된다. + +- umsdos + - MS-DOS파일 시스템을 리눅스상에서도 긴 파일 이름과 소유자, 접근 허가, 링크와 장치 파일등을 사용할 수 있도록 확장한 파일 시스템이다. + - umsdos는 일반적으로 DOS파일 시스템이 마치 리눅스 파일 시스템인 것처럼 보이도록 하는 기능을 제공하므로 따로 리눅스를 위한 파티션은 필요하지 않다. + +- nfs + - Network File System 이다. 네트워크상의 많은 컴퓨터들이 각각의 시스템에 가진 파일들을 서로 쉽게 공유하기 위해 제공되는 + - 공유 파일 시스템이다. + +- sysv + - System V/386, Xenix, Coherent 파일 시스템이다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\354\242\205\353\245\230.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\354\242\205\353\245\230.md" new file mode 100644 index 00000000..dedc3d4e --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Disk\342\200\205&\342\200\205Dir/\355\214\214\354\235\274\342\200\205\354\242\205\353\245\230.md" @@ -0,0 +1,108 @@ +--- +title: '파일 종류' +lastUpdated: '2024-03-02' +--- + +파일 형태로 표현된 커널 내 객체, 즉 OS의 자원이다. 유닉스 시스템은 OS 자원을 파일 형태로 표현한다. + +OS 자원이라고 함은 디스크, CPU, 네트워크, RAM 등을 말한다. 리눅스에서 이런 자원에 데이터 전송, 장치 접근 시 사용하는 파일을 특수 파일이라고 한다 + +``` +device files, pipes, socket, ... +``` + +특수 파일은 장치와 데이터를 주고 받는 통로이다 데이터 블록이 없으며(디스크에 저장 X) 장치 번호를 inode에 저장한다. 모니터나 프린터 역시 특수 파일을 통해 데이터를 주고 받는다. 디바이스는 반드시 HW를 말하는것이 아니고 SW적인 디바이스도 있다. + +### 파일 종류 문자 + +``` +- : 일반 파일 +d : 디렉토리 +b : 블록 장치 특수 파일 +c : 문자 장치 특수 파일 +l : 심볼릭 링크 +``` + +### device numer + +- major device number : 장치의 종류(monitor, printer, ...) +- minor device number : 장치의 수 + +```bash +$ ls -l /dev/ +total 0 +crw------- 1 root root 5, 1 <-- major, minor ... +crw-r--r-- 1 root root 10, 235 Mar 11 10:09 autofs +drwxr-xr-x 2 root root 40 Mar 11 10:09 block +drwxr-xr-x 2 root root 100 Mar 11 10:09 bsg +crw------- 1 root root 10, 234 Mar 11 10:09 btrfs-control +drwxr-xr-x 3 root root 60 Mar 11 10:09 bus +``` + +### 예시 + +`/dev` 디렉토리에 있는 시리얼 포트의 목록을 보면 아래와 같은 내용이 출력된다. 출력 결과를 분석해보자. + +```bash +(1) (2) (3) (4) (5) (6) (7)(8) (9) (10) +crwxrwxrwx 1 root tty 4 64 Jan 1 2006 ttyS00 +crwxrwxrwx 1 root tty 4 65 Jan 1 2006 ttyS01 +crwxrwxrwx 1 root tty 4 66 Jan 1 2006 ttyS02 +crwxrwxrwx 1 root tty 4 67 Jan 1 2006 ttyS03 +``` + +- (1) 접근 권한을 보면 `crwxrwxrwx` 로 c로 시작하는 것은 장치가 "문자 장치"임을 알려준다. c 가 아닌 b로 시작한다면 예로 하드디스크와 같이 블럭 단위로 읽거나 쓰기를 하는 블록 장치라는 의미이다. + +- (5)의 4는 메이저 장치 번호, (6)의 64, 65, 66 등은 마이너 장치 번호이다. 우리가 작성하는 프로그램은 하드웨어 장치를 직접 제어하는 것이 아니라 커널을 통해 제어하게 된다. 하드웨어를 파일 개념으로 처리할 수 있는 것도 중간에 커널이 가상 파일을 만들어서 제공하기 때문에 가능한 것이다. + +- 프로그램에서 하드웨어 장치에 대해 어떤 작업을 커널에게 요청하면, 커널은 메이저 번호를 가지고 어떤 디바이스 드라이버 사용할 지를 결정하게 된다. 디바이스 드라이버는 커널로부터 받은 정보 중 마이너 장치 번호를 가지고 자기에게 할당된 장치 중 어떤 장치를 제어할 지를 결정한다. + 위의 장치 목록을 보면 메이저 번호가 모두 4로 똑같고 마이너 번호만 다르다. 커널은 메이저 번호로 따라 디바이스 드라이버를 선택하고 다음 처리를 넘기면 디바이스 드라이버는 마이너 번호를 가지고 어느 장치를 사용할 지를 결정한다는 뜻이다. + +### 디바이스 드라이버 + +디바이스 드라이버를 만들어 커널을 올릴 때, 다른 디바이스 드라이버와 구별할 수 있도록 앞에 번호가 붙여진다. 이 번호가 주 장치 번호, 주 번호이다. + +```bash +insmod user_device_name +``` + +이렇게 `insmod` 명령을 실행하면 `user_device_name` 디바이스 드라이버 안에 주 번호를 몇 번으로 등록할 지 커널에 요청하는 코드가 들어 있습니다. 즉, `user_device_name` 디바이스 드라이버를 만들 때부터 프로그래머에 의해 주 장치 번호가 경정됩니다. 커널은 디바이스 드라이버에서 요청하는 장치 번호로 `user_device_name` 디바이스 드라이버를 커널 영역으로 로드합니다. + +`user_device_name`가 주 번호를 250을 사용한다고 가정해보자. + + +`insmod` 명령을 실행했으니 이제 커널에서는 디바이스 드라이버를 사용할 준비가 되었다. 그러나 아직 커널만 알고 밖에서는 아무도 모른다. 커널 밖에서도 이 디바이스 드라이버를 사용할 수 있도록 공개해 주어야 하는데, 이때 응용 프로그램이 접근하기 쉽도록, 또한 같은 장치이면서 내부에 처리하는 번호가 다르다면 하나의 디바이스 드라이버에서 모두 처리할 수 있도록, 결국 응용프로그램에서 쉽게 사용할 수 있도록 가상 파일 시스템으로 제공할 수 있도록 한다. + +`/dev`안에 있는 아이템들, 다시 말해 `node`들이 가상 인터페이스의 역할을 하는 것이다. + +```bash +]# mknod /dev/user_device_S0 c 250 0 +]# mknod /dev/user_device_S1 c 250 1 +]# mknod /dev/user_device_S2 c 250 2 +]# mknod /dev/user_device_S3 c 250 3 +]# mknod /dev/user_device_S4 c 250 4 +``` + +만약 아래와 같은 통신 포트를 open 하는, 즉 통신 포트를 사용하기 위한 프로그램 코드를 실행한다고 해보자. + +```c +fd = open( "/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK ); +``` + +통신 포트라는 장치를 사용하기 위해서 `/dev/ttyS0`를 open했다. 하지만 그것은 실제 디바이스 드라이브 파일 명이 아니다. 이 프로그램 코드는 당연히 커널로 전송되어진다. 커널은 `/dev/ttyS0`에서 주 장치 번호를 확인한다. 또한 주 장치를 보고 커널에 등록된 디바이스 드라이버중에 장치 번호에 해당하는 디바이스 드라이버를 찾아서 응용 프로그램의 `open()`에 대한 명령을 전송해 줍니다. 이때, 함께 전송되는 것이 `/dev/ttyS0`에 해당하는 부 장치 번호이다. + +디바이스 드라이버는 커널로부터 받은 부 장치 번호를 가지고 자신이 처리하는 여러 장치 중 어느 장치를 처리할 지를 파악하게 된다. + +즉, `/dev/ttyS0` 장치를 사용한다면, + +1. 커널은 `/dev/ttyS0` 의 주 장치 번호 4번에 해당하는 디바이스 드라이버를 찾고 +2. 디바이스 드라이버에 부 장치 번호 64를 전송해 주면, +3. 디바이스 드라이버는 자기가 처리하는 여러 통신 포트 중에 부 장치 번호인 64로 처리할 포트를 알게 된다. + +--- +참고 +- http://ssmsig.tistory.com/109 + + + + diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/Block\342\200\205I\357\274\217O.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/Block\342\200\205I\357\274\217O.md" new file mode 100644 index 00000000..ee4323dc --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/Block\342\200\205I\357\274\217O.md" @@ -0,0 +1,284 @@ +--- +title: 'Block I/O' +lastUpdated: '2024-03-02' +--- + +## 1. 버퍼 헤드 + +- 블록 장치는 고정된 크기의 데이터에 임의 접근하는 플래시 메모리 같은 HW 장치를 의미한다. +- 블록 장치가 물리적으로 접근하는 최소 단위는 섹터(sector, 약 512-byte)고, 논리적으로 접근하는 최소 단위는 블록(Block)이며 섹터 크기의 배수다. +- 디스크 상의 ‘블록’이 메모리 상에 나타나려면 객체 역할을 하는 ‘버퍼’가 필요하다. +- 커널은 버퍼가 어느 블록 장치의 어떤 블록에 해당하는지 등의 관련 제어 정보를 ‘버퍼 헤드’(buffer_head) 구조체를 사용해서 저장하고 표현하며 ``에 정의돼있다. + + ```c + // https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/buffer_head.h#L59 + /* + * Historically, a buffer_head was used to map a single block + * within a page, and of course as the unit of I/O through the + * filesystem and block layers. Nowadays the basic I/O unit + * is the bio, and buffer_heads are used for extracting block + * mappings (via a get_block_t call), for tracking state within + * a page (via a page_mapping) and for wrapping bio submission + * for backward compatibility reasons (e.g. submit_bh). + */ + struct buffer_head { + unsigned long b_state; /* 해당 버퍼의 현재 상태를 의미하며 여러 플래그 중 하나의 값을 가진다. (see above) */ + struct buffer_head *b_this_page;/* circular list of page's buffers */ + union { + struct page *b_page; /* the page this bh is mapped to */ + struct folio *b_folio; /* the folio this bh is mapped to */ + }; + + sector_t b_blocknr; /* start block number */ + size_t b_size; /* 블록의 길이(크기) */ + char *b_data; /* 버퍼가 가리키는 블록 */ + + struct block_device *b_bdev; + bh_end_io_t *b_end_io; /* I/O completion */ + void *b_private; /* reserved for b_end_io */ + struct list_head b_assoc_buffers; /* associated with another mapping */ + struct address_space *b_assoc_map; /* mapping this buffer is + associated with */ + atomic_t b_count; /* 버퍼의 사용 횟수를 의미한다. get_bh(), put_bh() 함수로 증감한다. */ + spinlock_t b_uptodate_lock; /* Used by the first bh in a page, to + * serialise IO completion of other + * buffers in the page */ + }; + ``` + +- 커널 2.6 버전 이전의 버퍼 헤드는 훨씬 크고 모든 블록 I/O 동작까지 책임지는 더 중요한 역할을 맡았다. 하지만, 버퍼 헤드로 블록 I/O를 하는 것은 크고 어려운 작업이었고, 페이지 관점에서 I/O를 하는 것이 보다 간단하고 더 좋은 성능을 보여줬으며, 페이지보다 작은 버퍼를 기술하기 위해 커다란 버퍼 헤드 자료구조를 사용하는 것은 비효율적이었다. +- 따라서 커널 2.6 버전 이후로 버퍼 헤드는 크게 간소화 됐으며, 상당수 커널 작업이 버퍼 대신 페이지와 주소 공간을 직접 다루는 방식으로 바뀌었다. +- 지금의 버퍼 헤드는 디스크 블록과 메모리의 페이지를 연결시켜주는 서술자 역할을 한다. + +## 2. bio 구조체 + +- 거대한 버퍼 헤드의 기능이 간소화되며 블록 I/O 동작은 ``의 bio 구조체가 담당한다. + +```c +// https://litux.nl/mirror/kerneldevelopment/0672327201/ch13lev1sec3.html +struct bio { + sector_t bi_sector; /* associated sector on disk */ + struct bio *bi_next; /* list of requests */ + struct block_device *bi_bdev; /* associated block device */ + unsigned long bi_flags; /* status and command flags */ + unsigned long bi_rw; /* read or write? */ + unsigned short bi_vcnt; /* number of bio_vecs off */ + unsigned short bi_idx; /* current index in bi_io_vec */ + unsigned short bi_phys_segments; /* number of segments after coalescing */ + unsigned short bi_hw_segments; /* number of segments after remapping */ + unsigned int bi_size; /* I/O count */ + unsigned int bi_hw_front_size; /* size of the first mergeable segment */ + unsigned int bi_hw_back_size; /* size of the last mergeable segment */ + unsigned int bi_max_vecs; /* maximum bio_vecs possible */ + struct bio_vec *bi_io_vec; /* bio_vec list */ + bio_end_io_t *bi_end_io; /* I/O completion method */ + atomic_t bi_cnt; /* usage counter */ + void *bi_private; /* owner-private method */ + bio_destructor_t *bi_destructor; /* destructor method */ +}; +``` + +- bio 구조체는 scatter-gather I/O 방식을 사용하므로 개별 버퍼가 메모리 상에 연속되지 않더라도 bio_io_vec 이라는 세그먼트 리스트로 연결해서 표현한다. + + image + +## 3. 요청 큐 (Request Queue) + +- 블록 장치는 대기 중인 블록 I/O 요청을 요청 큐에 저장한다. +- 요청 큐는 ``의 request_queue 구조체를 사용해 표현한다. + +```c +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/blkdev.h#L378 +struct request_queue { + struct request *last_merge; + struct elevator_queue *elevator; + + struct percpu_ref q_usage_counter; + + struct blk_queue_stats *stats; + struct rq_qos *rq_qos; + struct mutex rq_qos_mutex; + + const struct blk_mq_ops *mq_ops; + + /* sw queues */ + struct blk_mq_ctx __percpu *queue_ctx; + + unsigned int queue_depth; + + /* hw dispatch queues */ + struct xarray hctx_table; + unsigned int nr_hw_queues; + + /* + * The queue owner gets to use this for whatever they like. + * ll_rw_blk doesn't touch it. + */ + void *queuedata; + + /* + * various queue flags, see QUEUE_* below + */ + unsigned long queue_flags; + /* + * Number of contexts that have called blk_set_pm_only(). If this + * counter is above zero then only RQF_PM requests are processed. + */ + atomic_t pm_only; + + /* + * ida allocated id for this queue. Used to index queues from + * ioctx. + */ + int id; + + spinlock_t queue_lock; + + struct gendisk *disk; + + refcount_t refs; + + /* + * mq queue kobject + */ + struct kobject *mq_kobj; + +#ifdef CONFIG_BLK_DEV_INTEGRITY + struct blk_integrity integrity; +#endif /* CONFIG_BLK_DEV_INTEGRITY */ + +#ifdef CONFIG_PM + struct device *dev; + enum rpm_status rpm_status; +#endif + + /* + * queue settings + */ + unsigned long nr_requests; /* Max # of requests */ + + unsigned int dma_pad_mask; + +#ifdef CONFIG_BLK_INLINE_ENCRYPTION + struct blk_crypto_profile *crypto_profile; + struct kobject *crypto_kobject; +#endif + + unsigned int rq_timeout; + + struct timer_list timeout; + struct work_struct timeout_work; + + atomic_t nr_active_requests_shared_tags; + + struct blk_mq_tags *sched_shared_tags; + + struct list_head icq_list; +#ifdef CONFIG_BLK_CGROUP + DECLARE_BITMAP (blkcg_pols, BLKCG_MAX_POLS); + struct blkcg_gq *root_blkg; + struct list_head blkg_list; + struct mutex blkcg_mutex; +#endif + + struct queue_limits limits; + + unsigned int required_elevator_features; + + int node; +#ifdef CONFIG_BLK_DEV_IO_TRACE + struct blk_trace __rcu *blk_trace; +#endif + /* + * for flush operations + */ + struct blk_flush_queue *fq; + struct list_head flush_list; + + struct list_head requeue_list; + spinlock_t requeue_lock; + struct delayed_work requeue_work; + + struct mutex sysfs_lock; + struct mutex sysfs_dir_lock; + + /* + * for reusing dead hctx instance in case of updating + * nr_hw_queues + */ + struct list_head unused_hctx_list; + spinlock_t unused_hctx_lock; + + int mq_freeze_depth; + +#ifdef CONFIG_BLK_DEV_THROTTLING + /* Throttle data */ + struct throtl_data *td; +#endif + struct rcu_head rcu_head; + wait_queue_head_t mq_freeze_wq; + /* + * Protect concurrent access to q_usage_counter by + * percpu_ref_kill() and percpu_ref_reinit(). + */ + struct mutex mq_freeze_lock; + + int quiesce_depth; + + struct blk_mq_tag_set *tag_set; + struct list_head tag_set_list; + + struct dentry *debugfs_dir; + struct dentry *sched_debugfs_dir; + struct dentry *rqos_debugfs_dir; + /* + * Serializes all debugfs metadata operations using the above dentries. + */ + struct mutex debugfs_mutex; + + bool mq_sysfs_init_done; +}; +``` + +- request_queue와 request 그리고 bio와 bio_vec 사이의 복잡한 구조를 도식화한 그림이다. + + image + + - 요청 큐 request_queue 구조체에는 여러 블록 I/O 요청인 request 구조체가 들어있고, + - 각 request는 (블록 I/O의 동작을 의미하는) 하나 이상의 bio 구조체를 멤버로 가지고 있고, + - 각 bio 구조체는 bio_vec 배열을 가리키며 이 배열에는 여러 세그먼트가 들어 있을 수 있다. + +## 4. 입출력 스케줄러 + +- 커널은 블록 I/O 요청을 받자마자 바로 요청 큐로 보내지 않고, ​입출력 스케줄러로 대기 중인 블록 I/O 요청들 중 병합할 수 있는 건 합치고 정렬해서 디스크 탐색시간을 최소화 해 시스템 성능을 크게 개선한다. +- 요쳥 A가 접근하려는 섹터와 요청 B가 접근하려는 섹터가 인접하다면, 합쳐서 하나의 I/O 요청으로 만드는 것이 효율적이다. 한 번의 명령으로 추가 탐색 없이 여러 개의 요청을 처리할 수 있다. +- 병합할 수 있는 요청이 없을 때, 요청 큐 맨 끝에 넣는 것보다, 물리적으로 가까운 섹터에 접근하는 다른 요청 근처에 정렬해 추가한다면 효율적이다. +- 입출력 스케줄러 알고리즘은 우리의 일상생활 속의 ‘엘리베이터 알고리즘’과 상당히 흡사해 실제로 커널 2.4 버전까지 ‘리누스 엘리베이터’라는 이름으로 불렸다. +- 리눅스 커널 2.6 버전은 4가지 입출력 스케줄러 알고리즘​을 제공한다. + +- **데드라인 (Deadline)** + - 대부분의 사용자는 쓰기보다 읽기 성능에 민감하다. 어떤 읽기 요청 하나가 미처리 상태에 머물면 정체 시스템 지연 시간이 어마어마하게 커질 것이다. + - 이름 그대로 각 요청에 ‘만료 시간’을 설정한다. 읽기 요청은 0.5초, 쓰기 요청은 5초다. + - 데드라인 방식은 요청 큐로 FIFO 큐를 사용하는데, 쓰기 FIFO 큐나 읽기 FIFO 큐의 맨 앞 요청이 만료되면 해당 요청을 가장 우선으로 처리한다. +- **예측 (Anticipatory)** + - 데드라인 방식은 우수한 읽기 성능을 보장하지만 이는 전체 성능 저하의 대가를 감수한 것이다. + - 예측 방식은 데드라인을 기반으로 휴리스틱하게 동작한다. + - 읽기 요청이 발생하면 스케줄러는 요청을 처리한 뒤 바로 다른 요청을 처리하러 가지만, 예측 방식에선 수 ms 동안 아무 일도 하지 않는다. + - 이 시간 동안 사용자로부터 다른 읽기 요청이 계속해서 들어오고, 병합되고, 정렬된다. + - 대기 시간이 지난 뒤에 스케줄러는 다시 돌아가서 이전 요청 처리를 계속한다. + - 한 번에 많은 읽기 요청을 처리하기 위해 요청이 추가로 더 들어올 것을 예측하면서 수 ms를 아무것도 안 하고 가만히 있는 이 전략은 의외로 성능을 크게 증가시켰다. + - 특히, 스케줄러는 여러 입출력 양상 통계값과 휴리스틱을 이용해 애플리케이션과 파일시스템의 동작을 예측해 읽기 요청을 처리하기 때문에 필요한 탐색 시간을 허비하는 일을 크게 줄일 수 있었다. + - 이 스케줄러는 서버에 이상적인 스케줄러다. +- **완전 공정 큐 (Completely Fair Queueing)** + - 현재 리눅스의 기본 입출력 스케줄러로, 여러 부하 조건에서 가장 좋은 성능을 보여준다. + - 입출력 요청을 각 프로세스 별로 할당한 큐에 저장하고, 병합하고, 정렬한다. + - 각 요청 큐들 사이에서는 round-robin 방식으로 순서대로 미리 설정된 개수(기본값 4개)의 요청을 꺼내 처리한다. +- **무동작 (Noop)** + - 플래시 메모리와 같이 완벽하게 random-access가 가능한 블록 장치를 위한 스케줄러다. + - 병합만 하고 정렬이나 기타 탐색 시간 절약을 위한 어떤 동작도 수행하지 않는다. + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 +- https://litux.nl/mirror/kerneldevelopment/0672327201/ch13lev1sec3.html +- https://github.com/torvalds/linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\217\231\352\270\260\355\231\224\354\231\200\342\200\205lock.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\217\231\352\270\260\355\231\224\354\231\200\342\200\205lock.md" new file mode 100644 index 00000000..5d75e684 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\217\231\352\270\260\355\231\224\354\231\200\342\200\205lock.md" @@ -0,0 +1,134 @@ +--- +title: '동기화와 lock' +lastUpdated: '2024-03-02' +--- + +## 1. Critical section과 Race condition + +- 공유 메모리를 사용하는 애플리케이션을 개발할 때는 서로 다른 두 객체가 공유 자원에 동시에 접근하는 race condition을 반드시 막아야 한다. +- Critical section에 동시에 접근하는 것을 막기 위해서는 커널에서 제공하는 다양한 수단(원자적 연산, 스핀락, 세마포어) 원자적인(atomically) 접근을 보장해 race condition을 해소하는 동기화(synchronization)가 필요하다. +- 동기화의 기본적인 동작과정은 다음과 같다. + +1. 스레드 A와 B가 critical section에 접근하기 위해 락을 요청한다. +2. 스레드 A가 락을 휙득한다. 스레드 B는 무한루프(busy-waiting) 돌거나 sleep에 들어간다. +3. 스레드 A가 critical section을 처리한다. +4. 스레드 A가 락을 반환한다. +5. 스레드 B가 락을 휙득한다. 스레드 B가 critical section을 처리한다. + +- 커널 동기화에는 두 가지를 반드시 염두해야 한다. + - 커널 동시성 문제가 발생하는 것을 막는 것보다 막아야 한다는 사실을 깨닫는 것이 훨씬 더 어려우므로, 코드의 시작 단계부터 락을 설계해야 한다. + - 락을 설정하는 대상은 ‘코드 블록’이 아니라 ‘데이터’다. + +## 2. 데드락 (Deadlock) + +- 데드락은 실행 중인 2개 이상의 스레드와 2개 이상의 자원에 대해 발생하는 심각한 동기화 오류로, 각 스레드가 서로가 갖고 있는 자원을 기다리고 있지만, 모든 자원이 이미 점유된 상태라 옴싹달싹 못하는 상태를 말한다. +- 데드락을 예방하기 위해서는 3가지 규칙을 준수하자. + - 락이 중첩되는 경우 항상 같은 순서로 락을 얻고, 반대 순서로 락을 해제한다. + - 같은 락을 두 번 얻지 않는다. + - 락의 갯수나 복잡도 면에서 단순하게 설계한다. + +## 3. 동기화 수단 1: 원자적 연산 + +- 리눅스 커널은 지원하는 모든 아키텍처에 대해 원자적 정수 연산과 비트 연산을 제공한다. +- 원자적 연산은 이름 그대로 연산을 하는 동안 다른 프로세서, 프로세스, 스레드가 접근하지 못함을 보장한다. +- 원자적 연산은 int 대신 특별한 자료구조인 atomic_t를 사용한다. (``에 정의) + 1. 다른 자료형에 원자적 연산을 잘못 사용하는 것을 막을 수 있기 때문이다. + 2. 컴파일러가 개발자의 의도와 다르게 최적화하는 것을 막을 수 있기 때문이다. + +- 대표적인 몇 가지 원자적 정수 연산 함수는 다음과 같다. (``에 정의) + - `atomic_set(&var, num)`: atomic_t형 변수 var을 num으로 초기화한다. + - `atomic_add(num, &var)`: var을 num을 더한다. + - `atomic_inc(&var)`: var을 1 증가한다. +- 대표적인 몇 가지 원자적 비트 연산 함수는 다음과 같다. (에 정의) + - `test_and_set(int n, void *addr)`: 원자적으로 addr에서부터 n번째 bit를 set하고 이전 값을 반환한다. + - `test_and_clear(int n, void *addr)`: 원자적으로 addr에서부터 n번째 bit를 clear하고 이전 값을 반환한다. +- 가능하면 복잡한 락 대신 간단한 원자적 연산을 사용하는 것이 성능 면에서 훨씬 좋다. + +## 4. 동기화 수단 2: 스핀락(Spin-lock) + +- 간단한 원자적 연산만으로는 복잡한 상황에서는 충분한 보호를 제공할 수 없기 때문에 더 일반적인 동기화 방법인 ‘락’이 필요하다. +- ‘스핀락’이라는 이름대로 이미 사용 중인 락을 얻으려고 할 때 루프를 돌면서 (busy-wait) 기다린다. +- 스핀락은 프로세서 자원을 꽤 소모하므로 단기간만 사용해야 한다. + + ```c + DEFINE_SPINLOCK(lock); + + // 1. process context + spin_lock(&lock); + /***** critical section *****/ + spin_unlock(&lock); + + // 2. interrupt handler + unsigned long flags; + spin_lock_irqsave(&lock, flags); + /***** critical section *****/ + spin_unlock_irqrestore(&lock, flags); + ``` +- 스핀락은 ``과 ``에 정의되어있다. +- 스핀락은 위와 같은 함수들을 사용해서 lock과 unlock을 하며 인터럽트 핸들러에서도 사용할 수 있다. +- 인터럽트 핸들러 버전은 데드락을 방지하기 위해 로컬 인터럽트를 비활성화하고 복원하는 과정을 포함한다. + +## 5. 동기화 수단 3: 세마포어(Semaphore) + +- 이미 사용 중인 락을 얻으려고 시도할 때 busy-wait 하는 게 스핀락이라면, 세마포어는 sleep으로 진입한다. +- 무의미한 루프로 낭비하는 시간이 사라지니 프로세서 활용도가 높아지지만, 스핀락보다 부가 작업이 많다. + - Sleep 상태 전환, 대기큐 관리, wake-up 등 부가 작업을 처리하는 시간이 락 사용 시간보다 길 수 있기 때문에 오랫동안 락을 사용하는 경우에 적합하다. + - Sleep 상태로 전환 되므로 인터럽트 컨텍스트에선 사용할 수 없다. + - 세마포어를 사용할 때는 스핀락이 걸려있으면 안 된다. + +- 세마포어는 동시에 여러 스레드가 같은 락을 얻을 수 있도록 사용 카운트를 설정할 수 있다. +- 0과 1로 이루어져 있다면 바이너리 세마포어 또는 뮤텍스(mutex), 그 외는 카운팅 세마포어라 부른다. + + + ```c + struct semaphore sema; + sema_init(&sema, count); // 동적으로 세마포어 생성 + init_MUTEX(&sema); // 동적으로 뮤텍스 생성 + + // 세마포어(뮤텍스) 휙득 시도 + if (down_interruptible(&sema)) { + ... + } + /***** Critical Section *****/ + up(&sema); // 세마포어(뮤텍스) 반환 + ``` +- 주로 사용하는 세마포어 관련 함수는 위와 같다. +- 특히 `down()` 함수 보다는 `down_interruptible()` 함수를 많이 사용하는 것에 주목하자. +- 세마포어(뮤텍스)를 얻을 수 없을 때 sleep에 진입할 때 프로세스 상태는 `TASK_INTERRUPTIBLE` 또는 `TASK_UNINTERRUPTIBLE`로 들어갈탠데, 당연히 나중에 세마포어를 휙득할 수 있을 때 깨어나야 하므로 후자를 더 많이 사용한다. + +## 6. 동기화 수단 4: 뮤텍스(Mutex) + +- 2.6 커널부터 ‘뮤텍스’ 방식의 락이 구현되었다. + + ```c + DEFINE_MUTEX(mu); + mutex_init(&mu); + mutex_lock(&mu); + /* Critical section */ + mutex_unlock(&mu); + ``` +- 이 뮤텍스는 바이너리 세마포어와 유사하게 동작하지만, 인터페이스가 더 간단하고, 성능도 더 좋다. +- 뮤텍스를 사용할 수 없는 어쩔 수 없는 경우가 아니라면 세마포어보다는 새로운 뮤텍스를 사용하는 것이 좋다. + +## 7. 동기화 수단 비교 + +|요구사항|권장사항| +|-|-| +|락 사용시간이 짧은 경우|스핀락 추천| +|락 사용시간이 긴 경우|뮤텍스 추천| +|인터럽트 컨텍스트에서 락을 사용하는 경우|반드시 스핀락 사용| +|락을 얻은 상태에서 sleep 할 필요가 있는 경우|반드시 뮤텍스 사용| + +## 8. 선점 비활성화 & 배리어 + +- 리눅스 커널은 선점형 커널이므로 프로세스는 언제라도 선점될 수 있고 동시성 문제의 원인이 되기도 한다. +- 또한, SMP 환경에서는 프로세서별 변수가 아닌 이상 다른 프로세서가 동시적으로 접근할 수 있다. +- 따라서, 커널은 `preempt_disable()`, `preempt_enable()` 함수로 선점 카운터를 제어한다. +- 더 깔끔하고 자주 사용하는 방법으로 `get_cpu()` 함수를 사용하기도 한다. 프로세서 번호를 반환하면서 커널 선점을 비활성화 한다. 대응하는 함수는 `put_cpu()` 함수를 사용하면 커널 선점이 활성화된다. +- 동시성 문제는 굉장히 예민한 문제이므로 반드시 개발자의 의도대로 동작하게끔 컴파일러에게 알려야 한다. **성능을 위해 임의로 순서를 바꾸지 말고 코드 순서대로 메모리 I/O가 진행하게끔 컴파일러에게 알리는 명령을 ‘배리어(Barrier)’**라고 한다. +- 커널은 `rmb()`(메모리 읽기 배리어), `wmb()`(메모리 쓰기 배리어), `barrier()`(읽기 쓰기 배리어)를 제공한다. +- 배리어 명령, 특히 마지막 `barrier()` 명령은 다른 메모리 배리어에 비해 거의 코스트가 없고 상당히 가볍다. + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\224\224\353\262\204\352\271\205.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\224\224\353\262\204\352\271\205.md" new file mode 100644 index 00000000..b24608e3 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\224\224\353\262\204\352\271\205.md" @@ -0,0 +1,57 @@ +--- +title: '디버깅' +lastUpdated: '2024-03-02' +--- + +- 커널 디버깅에는 세 가지 요소가 필요하다. + - 버그가 처음 등장한 커널 버전을 파악할 수 있는가? + - 버그를 재현할 수 있는가? + - 커널 코드에 관한 지식을 갖추고 있는가? +- 버그를 명확하게 정의하고 안정적으로 재현할 수 있다면 성공적인 디버깅에 절반 이상 달성한 것이다. +​ +## 출력을 이용한 디버깅 + +### `printk()`와 웁스(oops) + +- 커널 출력 함수인 `printk()`는 C 라이브러리의 printf() 함수와 거의 동일하다. + - 주요 차이점은 로그수준(Loglevel)을 지정할 수 있다는 점이다. + - 가장 낮은 수준인 `KERN_DEBUG` 부터 가장 높은 수준인 `KERN_EMERG` 까지 7단계로 설정할 수 있다. + +- `printk()`의 장점은 커널의 어느 곳에서도 언제든지 호출할 수 있다는 점이다. + - 인터럽트 컨텍스트, 프로세스 컨텍스트 모두 호출 가능하다. + - 락을 소유하든 소유하지 않든 호출 가능하다. + - 어느 프로세서에서도 사용할 수 있다. + +- `printk()`의 단점은 커널 부팅 과정에서 콘솔이 초기화되기 전에는 사용할 수 없다는 점이다. + - 이식성을 포기하고 `printk()` 함수 변종인 `early_printk()` 함수를 사용하는 해결책이 있다. + +- `printk()`를 사용할 때 주의할 점은, 너무 빈번하게 호출되는 커널 함수에 사용하면 시스템이 뻗어버린다는 것이다. + - 출력 폭주를 막기 위해 두 가지 방법을 사용할 수 있다. + - 첫 번째 방법: jiffies를 이용해서 수초마다 한 번씩만 출력한다. + - 두 번째 방법: `printk_ratelimit()` 함수를 이용해서 n초에 한 번씩만 출력한다. + +- 커널 메시지는 크기가 `LOG_BUF_LEN`인 원형 큐(버퍼)에 저장된다. + - 크기는 `CONFIG_LOG_BUF_SHIFT` 옵션을 통해 설정할 수 있으며 기본값은 16KB다. + - 원형 큐이므로 가득찼을 때 가장 오래된 메시지를 덮어쓴다. + - 표준 리눅스 시스템은 사용자 공간의 ‘klogd’ 데몬이 로그 버퍼에서 커널 메시지를 꺼내고 ‘syslogd’ 데몬을 거쳐서 시스템 로그 파일(기본: `/var/log/messages`)에 기록한다. + +- 웁스(oops)는 커널이 사용자에게 무언가 나쁜 일이 일어났다는 것을 알려주는 방법이다. + - 커널은 심각한 오류가 났을 때 사용자 프로세스처럼 책임감 없이 프로세스를 종료할 수 없다. + - 커널은 웁스가 발생했을 때 최대한 실행을 계속 하려고 시도한다. + - 더 이상 커널 실행이 불가능하다고 판단할 경우 ‘패닉 상태’가 된다. + - 커널은 콘솔 오류 메시지 + 레지스터 내용물 + 콜스택 역추적 정보 등을 출력한다. + +### 버그 확인과 정보 추출 + +- `BUG()`, `BUG_ON()`: 웁스를 발생한다. + - 의도적으로 웁스를 발생시키는 시스템콜이다. + - 이 함수는 발생해서는 안 되는 상황에 대한 조건문을 확인할 때 사용한다. + - `if (...) BUG()` 랑 `BUG_ON(...)` 는 동일한 구문이다. 그래서 대부분 커널 개발자는 `BUG_ON()`을 사용하는 것을 더 좋아하며 조건문에 `unlikely()`를 함께 사용(`BUG_ON(unlikely());`)한다. +- `panic()` : 패닉을 발생한다. + - 좀 더 치명적인 오류의 경우에는 웁스가 아닌 패닉을 발생시킨다. + - 이 함수를 호출하면 오류 메시지를 출력하고 커널을 중지시킨다. +- `dump_stack()` : 디버깅을 위한 스택 역추적 정보를 출력한다. + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\251\224\353\252\250\353\246\254\342\200\205\352\264\200\353\246\254\354\231\200\342\200\205\354\272\220\354\213\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\251\224\353\252\250\353\246\254\342\200\205\352\264\200\353\246\254\354\231\200\342\200\205\354\272\220\354\213\234.md" new file mode 100644 index 00000000..209707fc --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\251\224\353\252\250\353\246\254\342\200\205\352\264\200\353\246\254\354\231\200\342\200\205\354\272\220\354\213\234.md" @@ -0,0 +1,138 @@ +--- +title: '메모리 관리와 캐시' +lastUpdated: '2024-03-02' +--- + +## 1. 페이지 (Page) & 구역 (Zone) + +- 프로세서가 메모리에 접근할 때 가장 작은 단위는 byte 또는 word지만, MMU와 커널은 메모리 관리를 페이지 단위로 처리한다. +- 페이지 크기는 아키텍처 별로 다르며 보통 32-bit 시스템에선 4KB, 64-bit 시스템에선 8KB다. +- 커널은 하나의 페이지를 여러 구역(zone)으로 나눠 관리한다. (``에 정의) + - **ZONE_DMA**: DMA를 수행할 수 있는 메모리 구역 + - **ZONE_DMA32**: 32-bit 장치들만 DMA를 수행할 수 있는 메모리 구역 + - **ZONE_NORMAL**: 통상적인 페이지가 할당되는 메모리 구역 + - **ZONE_HIGHMEM**: 커널 주소 공간에 포함되지 않는 ‘상위 메모리’ 구역 +- 메모리 구역의 실제 사용 방식과 배치는 아키텍처에 따라 다르며 없는 구역도 있다. + +## 2. 페이지 할당 & 반환 + +- 커널은 메모리 할당을 위한 저수준 방법 1개 + 할당받은 메모리에 접근하는 몇 가지 인터페이스를 제공한다. +- 모두 `` 파일에 정의돼있으며 기본 단위는 ‘페이지’다. + + ```c + // 방법 1 - alloc_pages + struct page* alloc_pages(gfp_t gfp_mask, unsigned int order); + // 방법 2 - __get_free_pages + unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order); + // 방법 3 - get_zeroed_page + unsigned long get_zeroed_page(unsigned int gfp_mask, unsigned int order); + ``` + +- 위 세 방법 중 하나를 선택해서 원하는 크기(2 * order 페이지)만큼 메모리를 할당받을 수 있다. +- 차이점은 반환값이다. 첫 번째 함수는 page 구조체를 얻을 수 있고, 두 번째 함수는 할당받은 첫 번째 페이지의 논리적 주소를 반환하며 마지막 함수는 0으로 초기화된 페이지를 얻을 수 있다. +- 특히, 커널 영역에서 메모리를 할당하는 것은 실패할 가능성이 있으며 반드시 오류를 검사하는 과정이 필요하다. 또한, 오류가 발생했다면 원위치 시켜야 하는 과정도 필요하다. +- 할당받은 페이지는 반드시 반환해야 하며, 할당받은 페이지만 반환해야 한다. +- 페이지 반환에는 `void __free_pages (struct page *page, unsigned int order);`를 사용한다. +- C의 `malloc()`과 `free()` 함수의 관계와 주의점을 생각하면 된다. +​ +## 3. 메모리 할당 (`kmalloc()`, `vmalloc()`) + +- `kmalloc()`은 `gfp_mask` 플래그라는 추가 인자가 있다는 점을 제외하면 C의 `malloc()`과 비슷하게 동작한다. +- 즉, 바이트 단위로 메모리를 할당할 때 사용하며 커널에서도 메모리를 할당할 때 대부분 이 함수를 사용한다. +- `gfp_mask` 플래그는 동작 지정자, 구역 지정자로 나뉘며 둘을 조합한 형식 플래그도 제공된다. +- 커널에서 자주 사용되는 대표적인 플래그는 아래와 같다. + - **GFP_KERNEL**: 중단 가능한 프로세스 컨텍스트에서 사용하는 일반적인 메모리 할당 플래그다. + - **GFP_ATOMIC**: 중단 불가능한 softirq, 태스크릿, 인터럽트 컨텍스트에서 사용하는 메모리 할당 플래그다. + - **GFP_DMA**: ZONE_DMA 구역에서 할당 작업을 처리해야 할 경우 사용한다. +- 메모리를 해제할 때는 ``에 정의된 `kfree()` 함수를 사용한다. +- `vmalloc()` 함수는 할당된 메모리가 물리적으로 연속됨을 보장하지 않는다는 것을 제외하면 `kmalloc()`과 동일하게 동작한다. + - 물리적으로 연속되지 않은 메모리를 연속된 가상 주소 공간으로 만들기 위해 상당량의 페이지 테이블을 조정하는 부가 작업이 필요하므로 `kmalloc()`의 성능이 훨씬 좋다. + - 큰 영역의 메모리를 할당하는 경우에만 `vmalloc()`을 사용하자. + - 메모리를 해제할 때는 `vfree()`를 사용한다. + +## 4. 슬랩 계층 (Slab layer) + +- 사용이 빈번한 자료구조는 사용할 때마다 메모리를 할당하고 초기화하고 사용한 뒤 메모리를 반환하는 것보다 풀(pool) 방식을 사용하는 것이 성능면에서 효율적이다. +- 슬랩 계층은 자료구조를 위한 캐시 계층이며 사용 종료 시 메모리를 반환하지 않고 해제 리스트에 넣어두고 다음 번에 재활용한다. +- 슬랩의 크기는 페이지 1개 크기와 같고, 1개 슬랩에는 캐시할 자료구조 객체가 여러개 들어간다. +- 빈번하게 할당 및 해제되는 자료구조일수록 슬랩을 이용해서 관리하는 것이 합리적이다. + +## 5. 스택 할당 + +- 사용자 공간은 동적으로 확장되는 커다란 스택 공간을 사용할 수 있지만, 커널은 고정된 작은 크기의 스택을 사용한다. +- 커널 스택은 컴파일 시점의 옵션에 따라 하나 또는 두 개의 페이지(4KB ~ 16KB)로 구성된다. +- 인터럽트 핸들러는 커널 스택을 사용하지 않고 프로세서별로 존재하는 1-page 짜리 스택을 사용한다. +- 특정 함수의 지역변수 크기는 1KB 이하로 유지하는 것이 좋다. 스택 오버플로우는 조용히 발생하며 확실하게 문제를 일으키며 가장 먼저 thread_info 구조체가 먹혀버린 뒤 모든 종류의 커널 데이터가 오염될 여지가 있다. +- 따라서 대량의 메모리를 사용할 때는 앞서 살펴본 동적 할당을 사용해야 한다. + +## 6. 상위 메모리 연결 + +- 1번 항목에서 설명했듯, 상위 메모리에 있는 페이지는 커널 주소 공간에 포함되지 않을 수도 있다. (대부분의 64-bit 시스템은 포함된다.) +- `alloc_pages()` 함수를 호출해서 얻은 페이지에 가상주소가 없을 수 있으므로, 페이지를 할당한 다음에 커널 주소 공간에 수동으로 연결하는 작업이 필요하다. +- 이를 위해 `` 파일에 `kmap(struct page* page);` 함수를 사용한다. + - 페이지가 하위 메모리에 속해 있다면, 그냥 페이지의 가상주소를 반환한다. + - 페이지가 상위 메모리에 속해 있다면, 메모리를 맵핑한 뒤 그 주소를 반환한다. +- 프로세스 컨텍스트에서만 동작한다. + +## 7. CPU별 할당 - percpu 인터페이스 +- SMP를 지원하는 2.6 커널에는 특정 프로세서 고유 데이터인 CPU별 데이터를 생성하고 관리하는 percpu라는 새로운 인터페이스를 도입했다. +- `` 헤더파일과 ``, `` 파일에 정의돼있다. + + ```c + // 컴파일 타임의 CPU별 데이터 + DEFINE_PER_CPU(var, name); // type형 변수 var 생성 + get_cpu_var(var)++; // 현재 프로세서의 var 증감 + // 여기부터 선점이 비활성화 된다. + put_cpu_var(var); // 다시 선점 활성화 + + // 런타임의 CPU별 데이터 + void *alloc_percpu(size_t size, size_t align); + void free_percpu(const void *); + ``` + +- CPU별 데이터를 사용하면 세 가지 장점이 있다. + - 락(스핀락, 세마포어)을 사용할 필요가 줄어든다. + - 캐시 무효화(invalidation) 위험을 줄여준다. + - 선점 자동 비활성화-활성화로 인터럽트 컨텍스트 & 프로세스 컨텍스트에서 안전하게 사용할 수 있다. + +--- + +# 캐시 (Page Cache & Page Writeback) + +## 1. 캐시 정의와 사용 방식 + +- 리눅스는 캐시를 ‘페이지 캐시(Page cache)’라고 부르며 디스크 접근 횟수를 최소화 하기 위해 사용한다. + - 프로세스가 `read()` 시스템콜 등으로 읽기 요청을 할 때, 커널은 가장 먼저 페이지 캐시를 확인한다. + - 만약 있다면, 메모리 또는 디스크 접근을 하지 않고 캐시에서 데이터를 바로 읽는다. + - 만약 없다면, 메모리 또는 디스크에 접근해 읽은 뒤 데이터를 캐시에 채워 넣는다. + +- 리눅스는 write policy로 지연 기록(write-back)을 채택하고 있다. + - 프로세스의 쓰기 동작은 캐시에 바로 적용된다. (메모리 or 디스크에 적용 X) + - 해당 캐시 라인에 dirty 표시를 한다. + - 적당한 때에 주기적으로 캐시의 dirty 표시된 내용이 메모리 or 디스크에 갱신되고 지워진다. + - 통합해서 한꺼번에 처리하므로 성능이 우수하지만 복잡도가 높다. + +- 캐시의 갱신된 페이지 내용을 메모리 or 디스크로 반영하는 작업을 ‘플러시(Flush)’라고 한다. + - 리눅스는 이 작업을 ‘플러시 스레드(Flush thread)’라는 커널 스레드가 담당한다. + - 페이지 캐시 가용 메모리가 특정 임계치 이하로 내려갈 때 dirty 캐시 라인을 플러시 한다. + - 페이지 케시 dirty 상태가 특정 한계 시간을 지나면 플러시 한다. + - 사용자가 `sync()`, `fsync()` 시스템콜을 호출하면 즉시 플러시 한다. + +- 리눅스는 replacement policy로 ‘이중 리스트 전략’(Two-list) 라는 개량 LRU(Least Recently Used) 알고리즘을 사용한다. + - 페이지 캐시가 가득찼을 때 어떤 데이터를 제거할 것인지 선택하는 과정이다. + - 언제 각 페이지에 접근했는지 타임스탬프를 기록해둔 뒤 가장 오래된 페이지를 교체하는 방법이다. + - 이중 리스트 전략은 ‘활성 리스트’와 ‘비활성 리스트’ 두 가지 리스트를 활용한다. + - 최근에 접근한 캐시 라인은 활성 리스트에 들어가서 교체 대상에서 제외한다. + - 두 리스트는 큐처럼 앞부분에서 제거하고 끝부분에 추가한다. + - 두 리스트는 균형 상태를 유지한다. 활성 리스트가 커지면 앞쪽 항목들을 비활성 리스트로 넘긴다. + +## 2. 리눅스 페이지 캐시 구조체 - address_space + +- 다양한 형태의 파일과 객체를 올바르게 캐시하는 것을 목표로 ``에 `address_space` 객체가 만들어졌다. +- 하나의 `address_space` 객체는 하나의 파일(inode)을 나타내고 1개 이상의 `vm_area_struct`가 포함된다. 단일 파일이 메모리상에서 여러 개의 가상 주소를 가질 수 있다는 걸 생각하면 된다. +- 특히, 페이지 캐시는 원하는 페이지를 빨리 찾을 수 있어야 하기 때문에 `address_space`에는 `page_tree`라는 이름의 기수 트리(radix tree)가 들어 있다. + +--- +참고 +- [Linux 커널 심층 분석 3판](https://product.kyobobook.co.kr/detail/S000000935348) +- https://github.com/torvalds/linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\252\250\353\223\210\352\263\274\342\200\205\354\236\245\354\271\230\342\200\205\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\252\250\353\223\210\352\263\274\342\200\205\354\236\245\354\271\230\342\200\205\352\264\200\353\246\254.md" new file mode 100644 index 00000000..8f2a7bd5 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\353\252\250\353\223\210\352\263\274\342\200\205\354\236\245\354\271\230\342\200\205\352\264\200\353\246\254.md" @@ -0,0 +1,152 @@ +--- +title: '모듈과 장치 관리' +lastUpdated: '2024-03-02' +--- + +## 1. 정의 +- 모듈: 커널 관련 하위 함수, 데이터, 바이너리 이미지를 포함해 동적으로 불러 올 수 있는 커널 객체를 의미한다. +- 장치: 리눅스 커널은 장치를 **블록 장치, 캐릭터 장치, 네트워크 장치** 3가지로 분류한다. 모든 장치 드라이버가 물리장치를 표현하는 것은 아니며 커널 난수 생성기, 메모리 장치처럼 가상 장치도 표현한다. + +## 2. 모듈 사용하기 + +### 모듈 만들기 + +- 모듈 개발은 새로운 프로그램을 짜는 것과 비슷하다. +- 각 모듈은 소스파일 내에 자신의 시작위치(`module_init()`)와 종료위치(`module_exit()`)가 있다. +- 아래는 `‘hello, world’`를 출력하는 간단한 모듈의 코드이다. + + ```c + #include + #include + #include + + static int hello_init(void) { + printk(KERN_ALERT, "hello\n"); + return 0; + } + + static void hello_exit(void) { + printk(KERN_ALERT, "world\n"); + } + + module_init(hello_init); // 모듈 진입점 + module_exit(hello_exit); // 모듈 종료점 + + MODULE_LICENSE("GPL"); // 저작권 정보 + MODULE_AUTHOR("Embeddedjune"); // 모듈 제작자 정보 + MODULE_DESCRIPTION("Test Hello,world"); // 모듈에 대한 정보 + ``` + +### 모듈 설치 준비하기 + +- 모듈 작성을 완료했다면, 모듈 소스를 패치의 형태나 커널 소스 트리에 병합한다. +- 모듈은 `/drivers`의 적당한 장치 하위 디렉토리에 디렉토리를 만들고 넣는다. +- `/drivers`의 Makefile과 방금 만든 하위 디렉토리 안의 Makefile을 수정한다. +- make 명령어로 모듈을 컴파일한다. + +### 모듈 설치하기 +- `make modules_install` 명령을 이용해서 모듈을 설치한다. + +### 모듈 의존성 생성하기 +- `depmod` 명령어를 이용해서 의존성 정보를 반드시 생성한다. + +### 메모리에 모듈 로드하기 +- `insmod`로 모듈을 메모리에 추가하고 rmmod로 모듈을 제거한다. +- `modprobe` 도구는 의존성 해소, 오류 검사 및 보고 등의 고급 기능들을 제공하므로 사용을 적극 권장한다. + +## 3. 장치 모델 + +- 리눅스 커널 2.6 버전의 중요한 새 기능으로 ‘장치 모델(Device model)’이 추가됐다. +- 장치 모델이 추가된 이유는 ‘전원 관리(Power management) 기능 운용’을 위한 정확한 장치 트리(Device tree, 시스템의 장치 구조를 표현하는 트리)를 제공하기 위해서다. +- 플래시 드라이브가 어느 컨트롤러에 연결됐는지, 어느 장치가 어느 버스에 연결됐는지 정보를 알려주고, 커널이 전원을 차단할 때 트리의 하위 노드 장치부터 전원을 차단할 수 있도록 도와준다. +- 이러한 일련의 서비스를 정확하고 효율적으로 제공하기 위해 장치 트리 및 장치 모델이 필요하다. +- 장치 모델은 `kobjects`, `ksets`, `ktypes` 세 가지 구조체로 표현한다. (모든 구조체는 ``에 정의되어있다.) + +```c +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/kobject.h#L64 +// 커널 자료구조의 기본적인 객체 속성 제공, sysfs 상의 디렉토리와 같음 +struct kobject { + const char *name; + struct list_head entry; + struct kobject *parent; + struct kset *kset; + const struct kobj_type *ktype; + struct kernfs_node *sd; /* sysfs directory entry */ + struct kref kref; + + unsigned int state_initialized:1; + unsigned int state_in_sysfs:1; + unsigned int state_add_uevent_sent:1; + unsigned int state_remove_uevent_sent:1; + unsigned int uevent_suppress:1; + +#ifdef CONFIG_DEBUG_KOBJECT_RELEASE + struct delayed_work release; +#endif +}; + +... + +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/kobject.h#L168C1-L173C22 +// 기능상 관련된 kobject의 집합(연결리스트) +struct kset { + struct list_head list; + spinlock_t list_lock; + struct kobject kobj; + const struct kset_uevent_ops *uevent_ops; +} __randomize_layout; + +... + +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/kobject.h#L116C1-L123C3 +// 공동 동작을 공유하는 kobject의 집합(연결리스트) +struct kobj_type { + void (*release)(struct kobject *kobj); // `kobjects`의 참조횟수가 0이 될 때 호출되서 C++의 소멸자 역할을 한다. + const struct sysfs_ops *sysfs_ops; + const struct attribute_group **default_groups; + const struct kobj_ns_type_operations *(*child_ns_type)(const struct kobject *kobj); + const void *(*namespace)(const struct kobject *kobj); + void (*get_ownership)(const struct kobject *kobj, kuid_t *uid, kgid_t *gid); +}; +... +``` +- `kobjects` 구조체는 부모 객체를 멤버 포인터 객체로 가지므로 계층 구조를 가지고 있다. +- `kobjects`를 사용하기 위해서는 `kobject_create()` 함수를 사용한다. + +--- + +# sysfs + +### sysfs 정의 + +- sysfs은 kobject 계층 구조를 보여주는 가상 파일시스템​이다. +- sysfs는 ​가상 파일을 통해 다양한 커널 하위 시스템의 장치 드라이버에 대한 정보를 제공한다. +- 리눅스 2.6 커널 이상을 이용하는 모든 시스템은 sysfs를 포함하며 `/sys` 디렉토리에 마운트되어있다. +- sysfs에는 block, bus, class, dev, devices, firmware, fs, kernel, module, power 등 최소 10개 디렉토리가 포함되어있다. +- 이 디렉토리들 중 가장 중요한 두 디렉토리는 class와 devices 디렉토리다. + - class는 시스템 장치의 상위 개념을 정리된 형태로 보여주고, + - devices는 시스템 장치의 하위 물리적 장치 연결 정보 관계를 보여준다. + - 나머지 디렉토리는 devices의 데이터를 단순히 재구성한 것에 불과하다. + +### sysfs에 kobject에 추가하고 제거하기 + +```c +struct kobject *kobject_create_and_add(const char *name, struct kobject *parent); +void kobject_del(struct kobject *kobj); +``` +- `kobject_create_and_add()`는 `kobject_create()` 함수와 `kobject_add()` 함수를 하나로 합친 함수다. +- kobject 객체를 생성하고 sysfs에 추가한다. +- kobject 객체를 제거할 때는 `kobject_del()` 함수를 사용한다. + +### sysfs에 파일 추가하기 + +- kobject를 sysfs 계층구조에 추가해도 kobject가 가리키는 ‘파일’이 없다면 아무 의미가 없다. +- kobjects 구조체 속 ktypes 구조체는 아무런 인자가 없어도 기본적인 파일 속성을 제공한다. + - `default_attrs` : 이 변수를 설정해서 파일의 이름, 소유자, 속성(쓰기, 읽기, 실행)을 부여한다. + - `sysfs_ops` : 파일의 기본적인 동작(읽기(show), 쓰기(store))을 정의한다. +- 속성을 제거하기 위해서는 `sysfs_remove_file()` 함수를 이용한다. + +--- +참고 +- [Linux 커널 심층 분석 3판](https://product.kyobobook.co.kr/detail/S000000935348) +- https://github.com/torvalds/linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\213\234\354\212\244\355\205\234\342\200\205\354\275\234\352\263\274\342\200\205\354\235\270\355\204\260\353\237\275\355\212\270.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\213\234\354\212\244\355\205\234\342\200\205\354\275\234\352\263\274\342\200\205\354\235\270\355\204\260\353\237\275\355\212\270.md" new file mode 100644 index 00000000..97f29dca --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\213\234\354\212\244\355\205\234\342\200\205\354\275\234\352\263\274\342\200\205\354\235\270\355\204\260\353\237\275\355\212\270.md" @@ -0,0 +1,442 @@ +--- +title: '시스템 콜과 인터럽트' +lastUpdated: '2024-03-02' +--- + +- 커널은 시스템콜의 일관성, 유연성, 이식성​ 3가지를 확보하는 것을 최우선 사항으로 생각하고 있다. + +- 따라서 커널은 POSIX 표준에 따라 표준 C 라이브러리 형태로 시스템콜을 제공한다. + +- 리눅스 커널에서 시스템콜은 네 가지 역할을 수행한다. + 1. 사용자 공간에 HW 인터페이스를 추상화된 형태로 제공한다. + 2. 시스템에 보안성 및 안정성을 제공한다. + 3. 인터럽트(및 트랩)와 함께 커널에 접근할 수 있는 유일한 수단이다. + 4. 사용자 공간과 기타 공간을 분리해 프로세스별 가상환경(가상메모리, 멀티태스킹 등)을 제공한다. + +- 리눅스의 시스템콜은 다른 OS보다 상대적으로 갯수가 적고 수행속도가 빠르다. + - 리눅스는 시스템콜 핸들러 호출 흐름이 간단하기 때문이다. + - 리눅스는 context switching 속도가 빠르기 때문이다. + +- 리눅스의 시스템콜은 아래 파일에 정의되어있다. + + - `include/linux/syscalls.h`: 시스템콜 선언부 + + - `kernel/sys.c`: 시스템콜 구현부 + +- 간단한 시스템콜 예시인 `getpid()`의 구현을 통해 조금 더 내용을 알아보자. + + ```c + #define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void) + SYSCALL_DEFINE0(getpid) // asmlinkage long sys_getpid(void); + { + return task_tgid_vnr(current); // return current->tgid + } + ``` + + - `getpid()` 함수는 인자를 갖지 않기 때문에 맨 끝 숫자로 0이 붙은 SYSCALL_DEFINE매크로를 사용한다. + + - asmlinkage 지시자는 함수의 인자를 프로세스 스택 메모리에서 찾을 것을 컴파일러에게 명령한다. + + - 반환형이 long인 이유는 32-bit와 64-bit 시스템 사이의 호환성을 유지하기 위함이다. + + - 모든 리눅스 시스템콜은 sys_ 라는 접두사를 명명규칙으로 따른다. + +- 커널은 시스템콜을 ‘시스템콜 번호’라는 고유번호로 식별한다. + - 시스템콜 번호는 한 번 할당 시 변경할 수 없다. + - 한 번 할당된 시스템콜 번호는 대응하는 시스템콜이 제거된 후라도 재사용하지 않는다. + - 모든 시스템콜 번호와 그 핸들러는 sys_call_table 이라는 상수 함수포인터 배열에 아키텍처별로 정의되어있다. (i.e. `x86은 arch/x86/kernel/syscall64.c`) + +- 커널이 시스템콜을 처리할 때는 ‘프로세스 컨텍스트’라는 특수한 컨텍스트에 진입한다. + + - 프로세스 컨텍스트에서 커널과 특정 프로세스는 같은 컨텍스트로 묶여 있는 상황이다. + - 실행은 커널이 하고 있지만, current 매크로는 여전히 특정 프로세스를 가리키고 있다. + - 실행은 커널이 하고 있지만, 여전히 sleep 가능하고 선점도 가능한 상태​다. + +- 사용자 애플리케이션이 시스템콜을 호출 했을 때, + 1. 대응하는 시스템콜을 호출 + 특정 레지스터에 시스템콜 번호 기록 → SW 인터럽트를 발생시킴. + 2. 커널이 시스템을 커널 모드로 전환함. + 3. `system_call()` 함수 호출 → 레지스터값이 유효한 시스템콜 번호인지 확인함. + 4. sys_call_table의 시스템콜 번호 인덱스의 함수를 호출해 적절한 핸들러를 호출함. + +- 이때 시스템콜의 매개변수는 레지스터를 통해 전달한다. (i.e. ARM의 R0~R3, x86의 ebx~edx, esi, edi) +- 사용자는 직접 시스템콜을 구현할 수도 있다. (하지만, 절대 권장하지 않는다.) + +- **시스템콜을 설계할 때 반드시 고려해야 하는 점** + - 해당 시스템콜이 수행할 정확한 하나의 목적을 정의할 것 + - 해당 시스템콜의 인자, 반환값, 오류코드를 정의할 것 + - 해당 시스템콜이 추후 새로운 기능을 추가할 여지가 있는지 생각할 것 (유연성) + - 해당 시스템콜이 하위 호환성을 깨지 않고 버그를 쉽게 수정할 수 있는지 생각할 것 (호환성) + - 해당 시스템콜이 특정 아키텍처의 워드나 엔디안을 가정하고 만들지는 않았는지 점검할 것 (이식성) +- **시스템콜을 구현할 때 반드시 고려해야 하는 점** + - 인자로 전달되는 포인터의 유효성을 확인할 것 + - 포인터는 사용자 공간의 메모리 영역을 가리켜야 한다. + - 포인터는 프로세스 주소 공간의 메모리 영역을 가리켜야 한다. + - 해당 메모리 영역의 권한에 맞는 접근을 해야 한다. + + - 수행결과를 반드시 알맞는 방법으로 제공할 것 + - 커널 영역의 수행 결과, 특히 포인터는 절대 사용자 공간으로 내보내선 안 된다. + - `copy_to_user()` 또는 `copy_from_user()` 두 함수를 이용해서 안전하게 정보를 전달 및 제공받아야 한다. + +- 사용자 시스템콜의 설계 및 구현이 완료됐다면 정식 시스템콜로 등록한다. + - entry.S 파일의 시스템콜 테이블 마지막에 새로운 시스템콜을 추가한다. + - `asm/unistd.h` 헤더파일에 새로운 시스템콜 번호를 추가한다. + - 새로운 시스템콜의 핸들러를 구현한다. + - 커널을 재컴파일 한다. + +--- + +# 인터럽트 + +## 1. 인터럽트 개요 + +- OS는 인터럽트를 구별하고 인터럽트가 발생한 HW를 식별 뒤 적절한 핸들러를 이용해 인터럽트를 처리한다. +- 요점은 장치별로 특정 인터럽트가 지정되어있으며, 커널이 이 정보를 가지고 있다는 것이다. +- 인터럽트 처리를 위해 커널이 호출하는 함수를 인터럽트 핸들러 또는 인터럽트 서비스 루틴(ISR)라고 부른다. +- 인터럽트는 ‘인터럽트 컨텍스트’에서 실행되며 중간에 실행을 중단할 수 없다. + + +- 인터럽트는 언제라도 발생할 수 있고 그동안 원래 실행흐름은 중단되므로 최대한 빨리 인터럽트 핸들러를 처리하고 복귀하는 것이 중요하다. 따라서 인터럽트 핸들러 실행시간은 가능한 짧은 것이 중요하다. + - 하지만, 때로는 인터럽트 핸들러에서 처리해야 하는 작업이 많거나 복잡할 수 있다. + - 이를 해결하기 위한 전략이 **전반부 처리(top-half) + 후반부 처리(bottom-half)**다. + - 당장 실시간으로 빠르게 처리해야 하는 부분은 인터럽트 핸들러 내에서 처리를 하고, + - 나중에 처리해도 되는 부분은 다른 프로세스로 따로 만들어 처리한다. + +## 2. 인터럽트 핸들러 등록 + +```c +// linux/interrupt.h +typedef irq_handler_t (*irq_handler_t)(int, void *); +int request_irq(unsigned int irq, + irq_handler_t handler, + unsigned long flags, + const char* name, + void* dev) +``` + +- 인터럽트 핸들러는 ``의 `request_irq()` 함수를 통해 등록할 수 있다. + 1. **irq** : IRQ 번호를 의미하며 보통 기본 페리페럴은 이 값이 하드코딩 되어있다. 다른 디바이스들은 탐색을 통해 동적으로 정해진다. + 2. **handler** : 인터럽트를 처리할 인터럽트 핸들러의 함수 포인터다. + 3. **flags** : + - `IRQF_DISABLED` : 인터럽트 핸들러를 실행하는 동안 모든 인터럽트를 비활성화한다. + - `IRQF_SAMPLE_RANDOM` : 커널 내에 난수 발생기의 엔트로피에 이번 인터럽트 이벤트를 포함시킬지 여부를 결정한다. 포함한다면 조금 더 무작위인 난수가 생성될 것이다. + - `IRQF_TIMER` : 이 핸들러가 시스템 타이머를 위한 인터럽트를 처리하는 핸들러임을 명시한다. + - `IRQF_SHARED` : 이 핸들러가 여러 인터럽트가 공유하는 핸들러임을 명시한다. + + 4. **name** : 개발자가 식별하기 위한 인터럽트 핸들러의 이름이다. + 5. **dev** : 같은 인터럽트를 사용하는 여러 핸들러 사이에서 특정 핸들러를 구별하기 위해 고유 쿠키값을 정의한다. 핸들러가 하나 뿐이라면 NULL을 사용해도 괜찮다. + +- 등록에 성공하면 `request_irq()` 함수는 0을 반환한다. +- (참고) `requeset_irq()` 함수는 sleep 상태를 허용하는 함수다. 따라서 인터럽트 컨텍스트를 포함한 코드 실행이 중단돼서는 안 되는 상황에서는 호출할 수 없다. 내부적으로 `proc_mkdir()` → `proc_create()` → `kmalloc()` 함수를 호출하는데, 이 함수가 sleep이 가능하기 때문이다. +​ +## 3. 인터럽트 핸들러 구현 및 동작순서 + +image + +1. 디바이스는 bus를 통해 인터럽트 컨트롤러로 전기신호를 전송한다. +2. 인터럽트 컨트롤러는 프로세서의 특정 핀에 인터럽트를 건다. +3. 프로세서는 하던 동작을 중단하고 작업 내용(및 context)을 스택에 저장한다. +4. 미리 정해진 메모리 주소의 코드로 branch한다. +5. 현재 발동된 인터럽트 라인을 비활성화 중복 인터럽트 발생을 예방하고, 유효한 핸들러가 등록돼있는지, 사용가능한지, 현재 미실행 상태인지 확인한다. +6. ``의 `handle_IRQ_event()`를 호출해 해당 인터럽트의 핸들러를 실행한다. + 핸들러 실행 후 복귀했다면 정리작업을 수행하고 `ret_from_intr()`로 이동한다. 이 함수는 3, 4번처럼 아키텍처 특화 어셈블리로 작성되어있으며, 대기 중인 스케줄링 작업 존재 여부 확인 후 `schedule()` 함수를 호출해 원래 실행흐름으로 복귀한다. + +## 4. 인터럽트 활성화/ 비활성화 + +- 인터럽트 제어 기능은 아키텍처 의존적이며 동기화를 위해 인터럽트를 비활성화해 선점을 막기 위해 사용한다. +- 보통 아래와 같은 구조를 가진다. + +```c +local_irq_disable(); +/* 인터럽트가 비활성화 된 상태! +여기서 할일을 한다 ~~~ */ +local_irq_enable(); +``` + +- 또는 `void disable_irq(unsigned int irq)`, `void enable_irq(unsigned int irq)`로 특정 인터럽트를 마스킹하는 방법도 제공한다. + +- 하지만, 이것만으로 완전한 동기화를 보장하지는 못한다. 리눅스는 SMP 환경을 지원하므로 여기에 몇 가지 잠금 장치를 더 추가해주어야 한다. + +## 5. 인터럽트 후반부 처리 (Bottom-half) + +- 어떤 일을 후반부로 지연시킬지 명확한 기준은 없지만, 실행시간에 민감하거나, 절대 선점되서는 안 되는 작업이 아니라면 후반부 처리로 넘길 것을 권장한다. + +- 후반부 처리의 가장 큰 요점은 모든 인터럽트가 활성화 된 상태에서, 시스템이 덜 바쁜 미래의 어떤 시점에 인터럽트의 나머지 부분을 처리해 시스템의 throughput을 극대화할 수 있다는 점이다. + +- 아랫부분에서 BH(Bottom-half), Softirq, Tasklet, WorkQueue 4가지 후반부 처리 기법에 대해서 알아볼 것이다. 결론부터 말하자면, 현재 2020년대에서는 주로 Threaded IRQ 또는 WorkQueue 2가지만 사용하고 나머지는 거의 사용하지 않는다. 리눅스 커널의 인터럽트 후반부 처리의 역사가 어떻게 발전했는지에 대해 알아보는 느낌으로 가볍게 살펴보도록 하자. + +- **① BH** + - 가장 먼저 만들어진 후반부 처리 기법으로, 정적으로 정의된 32개의 후반부 처리기가 존재했다. + - 인터럽트 핸들러에서 32-bit 전역 변수의 bit를 조작해 나중에 실행할 후반부 처리기를 지정했다. + - 서로 다른 프로세서가 두 개의 BH를 동시에 실행할 수 없었고, 유연성이 떨어졌고, 병목현상이 발생했다. + - 성능이슈, 확장성 이슈, 이식성 이슈로 인해 더 이상 BH를 사용하기 어려워져 2.5버전 이후로 사라졌다. + +- **② Softirq** + - 모든 프로세서에서 동시에 사용할 수 있는 정적으로 정의된 후반부 처리기의 모음집이다. + - 실행시간에 매우 민감하고 중요한 후반부 처리(i.e. 네트워크, Block I/O)를 해야 할 때 사용한다. + - 같은 유형의 softirq가 다른 프로세서에서 동시에 실행될 수 있으므로 ​각별한 주의가 필요하다. + - softirq 핸들러에서 만일 공유변수를 사용한다면, 적절한 락이 필요하다는 뜻이다. + - 하지만, 동시 실행을 막는다면, softirq를 사용하는 의미가 상당 부분 사라지기 때문에 tasklet을 사용하는 것이 낫다. + - 꼭 softirq를 써야만 하는 이유가 반드시 있는 것이 아니라면, 거의 모든 경우 tasklet을 사용하는 것을 권장한다. + + ```c + // https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/include/linux/interrupt.h#L588 + struct softirq_action + { + void (*action)(struct softirq_action *); + }; + ... + // https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/kernel/softirq.c#L59static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; + DEFINE_PER_CPU(struct task_struct *, ksoftirqd); + + const char * const softirq_to_name[NR_SOFTIRQS] = { + "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL", + "TASKLET", "SCHED", "HRTIMER", "RCU" + }; + ``` + + - Softirq는 ``의 sortirq_action 구조체로 표현하며 관련 함수는 에 구현되어있다. + - Softirq는 ``에 열거형으로 우선순위 순으로 커널에 정적으로 등록되어있다. + - Softirq의 핸들러는 `open_softirq()` 함수를 이용해서 런타임에 동적으로 등록할 수 있다. (`open_softirq(softirq 이름, my_softirq)`) + - 등록한 핸들러는 `my_softirq->action(my_softirq)`와 같이 실행할 수 있는데, `softirq_action` 구조체에 데이터를 추가하더라도 softirq 핸들러 원형을 바꿀 필요가 없기 때문에 확장성이 좋아진다는 장점이 있다. + - 등록된 softirq는 인터럽트 핸들러가 종료 전에 raise 해줘야 실행 가능하다. `raise_softirq`(softirq 이름) 함수를 사용해서 raise 할 수 있다. + - 커널은 다음 번 softirq를 처리할 때 raise 되어있는 softirq를 먼저 확인하고, 대응하는 softirq 핸들러를 실행한다 + + - Softirq를 도입할 때 한 가지 딜레마가 있었다. + - 만일 softirq의 발생 빈도가 높아질 경우 유저 공간 애플리케이션이 프로세서 시간을 얻지 못하는 starvation 문제가 발생할 가능성이 있다. + - 커널 개발자들은 softirq를 도입하기 위해서는 적절한 ‘타협’이 필요함을 깨달았다. + - 각 프로세서마다 nice 값 +19 (가장 낮은 우선순위)를 갖는 특수한 커널 스레드 `ksoftirqd`를 하나씩 만들어둔다. + - ksoftirqd 커널 스레드는 계속 루프를 돌면서 pending 중인 softirq가 발생할 때마다, 프로세서가 여유롭다면 바로바로 `do_softirq()` 함수를 호출해서 softirq를 처리한다. (그리고 사용자 애플리케이션을 방해하지도 않으며 꽤 괜찮은 성능도 보여줬다.) + - ksoftirqd 커널 스레드는 루프 한 바퀴를 돌 때마다 `schedule()` 함수를 호출해서 더 중요한 프로세스를 먼저 실행한다. + - ksoftirqd 커널 스레드가 실행할 softirq가 없다면 자신을 TASK_INTERRUPTIBLE 상태로 전환해 softirq가 발생할 때 깨어난다. +​ +- **③ Tasklet** + - Softirq 기반으로 만들어진 동적 후반부 처리 방식이다. (Task와는 아무런 관련 없다) + - 네트워크처럼 성능이 아주 중요한 경우에만 softirq를 사용하고 대부분의 후반부 처리는 tasklet을 사용하면 충분하다. + - 같은 유형의 tasklet은 서로 다른 프로세서에서 동시 실행 불가능하다. + - Softirq 보다 사용법이 간단하고 lock 사용 제한이 유연하다. + + ```c + // https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/include/linux/wait.h#L40 + struct tasklet_struct { + struct tasklet_struct *next; // 다음 tasklet + unsigned long state; // 현재 tasklet의 상태 + atomic_t count; // 참조 횟수 + bool use_callback; // 콜백 사용 여부 + union { + void (*func)(unsigned long data); // 핸들러 함수 + void (*callback)(struct tasklet_struct *t); // 핸들러 함수 콜백 + }; + unsigned long data; // 핸들러 함수의 인자 + }; + ``` + + - `` 헤더파일의 `tasklet_struct` 구조체로 표현한다. + - state는 0, `TASKLET_STATE_SCHED`(실행 대기 중), `TASKLET_STATE_RUN`(실행 중) 세 가지 중 한 가지를 가진다. + - count는 현재 태스크릿의 참조 횟수를 뜻하며, 0이 아니면 태스크릿은 비활성화, 0이면 태스크릿은 활성화 상태다. + - Softirq와 마찬가지로, 활성화 되기 위해서는 raising 돼야 하는데, 이를 ‘태스크릿 스케줄링’ 이라고 표현한다. + + ```c + static void __tasklet_schedule_common(struct tasklet_struct *t, + struct tasklet_head __percpu *headp, + unsigned int softirq_nr) + { + struct tasklet_head *head; + unsigned long flags; + + local_irq_save(flags); + head = this_cpu_ptr(headp); + t->next = NULL; + *head->tail = t; + head->tail = &(t->next); + raise_softirq_irqoff(softirq_nr); + local_irq_restore(flags); + } + ``` + + - 태스크릿 스케줄링은 파일에 구현되어있고 `tasklet_schedule()` 함수에서 처리한다. + - 태스크릿의 상태가 `TASKLET_STATE_SCHED` 라면, `__tasklet_schedule()` 함수는 호출한다. + - 현재 IRQ(인터럽트) 상태를 저장하고, 태스크릿을 현재 프로세서의 `tasklet_vec` 또는 `tasklet_hi_vec` 배열의 가장 뒤에 추가한다. + - `raise_softirq_irqoff()` 함수로 softirq를 raise해서 `do_softirq()` 함수가 태스크릿을 처리하도록 만든다. (바로 이 부분에서 태스크릿이 softirq 기반으로 만들어졌음을 알 수 있다.) + - 태스크릿이 스케줄링(활성화) 됐으니 이제 태스크릿이 핸들러 함수를 호출하고 처리되는 과정을 알아보자. + + ```c + static __latent_entropy void tasklet_action(struct softirq_action *a) + { + tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ); + } + ``` + + ```c + static void tasklet_action_common(struct softirq_action *a, + struct tasklet_head *tl_head, + unsigned int softirq_nr) + { + struct tasklet_struct *list; + + local_irq_disable(); + list = tl_head->head; + tl_head->head = NULL; + tl_head->tail = &tl_head->head; + local_irq_enable(); + + while (list) { + struct tasklet_struct *t = list; + + list = list->next; + + if (tasklet_trylock(t)) { + if (!atomic_read(&t->count)) { + if (tasklet_clear_sched(t)) { + if (t->use_callback) { + trace_tasklet_entry(t, t->callback); + t->callback(t); + trace_tasklet_exit(t, t->callback); + } else { + trace_tasklet_entry(t, t->func); + t->func(t->data); + trace_tasklet_exit(t, t->func); + } + } + tasklet_unlock(t); + continue; + } + tasklet_unlock(t); + } + + local_irq_disable(); + t->next = NULL; + *tl_head->tail = t; + tl_head->tail = &t->next; + __raise_softirq_irqoff(softirq_nr); + local_irq_enable(); + } + } + ``` + + - 태스크릿 핸들러 함수는 `` 파일의 `tasklet_action()` 함수에서 처리한다. + + - 인터럽트 비활성화 후, 현재 프로세서의 `tasklet_vec` 또는 `tasklet_hi_vec` 배열을 copy 해온 뒤 NULL로 초기화하고, 인터럽트를 활성화 한다. + + - 다시 한 번 태스크릿의 상태가 `TASKLET_STATE_SCHED` 임을 확인한 뒤 핸들러를 호출해 실행한다. + + - 배열에 더 이상 대기 중인 태스크릿이 없을 때까지 반복문을 돌면서 핸들러를 호출한다. + +- **④ WorkQueue** + + - 워크큐는 softirq, 태스크릿과 달리, 후반부 처리를 커널 스레드 형태로 프로세스 컨텍스트 내에서 처리한다. + - 워크큐는 스케줄링이 가능하고, 인터럽트가 활성화 된 상태이고, 선점될 수 있고, sleep 상태로 전환될 수 있다. + - 워크큐는 엄연히 커널 스레드이므로 사용자 공간 프로세스 메모리 영역을 접근할 수 없다. + - 따라서 워크큐는 대용량 메모리 할당/ 세마포어 관련 작업/ 블록 I/O에 적합하다. + - 사용 편의성 측면에서 워크큐가 가장 좋다. + - 워크큐의 전체적인 구조는 아래와 같다. (`` 파일, `` 파일 참고) + + image + + - 리눅스 커널은 프로세서별로 ‘작업 스레드’라는 events/n 이란 이름의 특별한 커널 스레드를 하나씩 가지고 있다. + - 작업 스레드는 여러 작업 유형으로 나뉘며, 각 작업 유형마다 하나의 `workqueue_struct` 구조체로 표현한다. + - 사용자가 원한다면 특정 작업 유형에 작업 스레드를 추가할 수 있으며, 작업 스레드는 `cpu_workqueue_struct` 구조체로 표현한다. + - 후반부 처리 할 작업은 `work_struct` 구조체로 표현한다. + - 모든 작업 스레드는 `worker_thread()` 라는 함수를 실행한다. + + ```c + // https://github.com/torvalds/linux/blob/9ace34a8e446c1a566f3b0a3e0c4c483987e39a6/kernel/workqueue.c#L2726 + /** + * worker_thread - the worker thread function + * @__worker: self + * + * The worker thread function. All workers belong to a worker_pool - + * either a per-cpu one or dynamic unbound one. These workers process all + * work items regardless of their specific target workqueue. The only + * exception is work items which belong to workqueues with a rescuer which + * will be explained in rescuer_thread(). + * + * Return: 0 + */ + static int worker_thread(void *__worker) + { + struct worker *worker = __worker; + struct worker_pool *pool = worker->pool; + + /* tell the scheduler that this is a workqueue worker */ + set_pf_worker(true); + woke_up: + raw_spin_lock_irq(&pool->lock); + + /* am I supposed to die? */ + if (unlikely(worker->flags & WORKER_DIE)) { + raw_spin_unlock_irq(&pool->lock); + set_pf_worker(false); + + set_task_comm(worker->task, "kworker/dying"); + ida_free(&pool->worker_ida, worker->id); + worker_detach_from_pool(worker); + WARN_ON_ONCE(!list_empty(&worker->entry)); + kfree(worker); + return 0; + } + + worker_leave_idle(worker); + recheck: + /* no more worker necessary? */ + if (!need_more_worker(pool)) + goto sleep; + + /* do we need to manage? */ + if (unlikely(!may_start_working(pool)) && manage_workers(worker)) + goto recheck; + + /* + * ->scheduled list can only be filled while a worker is + * preparing to process a work or actually processing it. + * Make sure nobody diddled with it while I was sleeping. + */ + WARN_ON_ONCE(!list_empty(&worker->scheduled)); + + /* + * Finish PREP stage. We're guaranteed to have at least one idle + * worker or that someone else has already assumed the manager + * role. This is where @worker starts participating in concurrency + * management if applicable and concurrency management is restored + * after being rebound. See rebind_workers() for details. + */ + worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND); + + do { + // pool에서 worker를 찾아서, 실행할 work가 있는 동안 동작을 계속한다. + struct work_struct *work = + list_first_entry(&pool->worklist, + struct work_struct, entry); + + if (assign_work(work, worker, NULL)) + process_scheduled_works(worker); + } while (keep_working(pool)); + + worker_set_flags(worker, WORKER_PREP); + sleep: + /* + * pool->lock is held and there's no work to process and no need to + * manage, sleep. Workers are woken up only while holding + * pool->lock or from local cpu, so setting the current state + * before releasing pool->lock is enough to prevent losing any + * event. + */ + worker_enter_idle(worker); + __set_current_state(TASK_IDLE); + raw_spin_unlock_irq(&pool->lock); + schedule(); // 작업이 없는 worker는 스케줄러에 의해 다시 깨어나 작업을 기다리게 된다. + goto woke_up; + } + ``` + - `worker_thread()`에서는 `raw_spin_lock_irq()`와 `raw_spin_unlock_irq()` 함수를 사용하여 동기화를 유지하고, 여러 worker가 동시에 작업 목록에 접근하지 못하도록 한다. + +- **워크큐 사용하기** + - 새로운 작업 유형 작업 스레드 생성: `struct workqueue_struct *create_workqueue(const char* name)` 함수를 이용한다. + - 정적 작업 생성: `DECLARE_WORK(name, void (*func)(void *), void *data);` 매크로를 사용한다. + - 동적 작업 생성: 포인터를 이용해서 `work_struct` 구조체를 동적 생성한 뒤 `INIT_WORK(struct work_struct *work, void (*func) (void *), void *data);` 매크로를 사용해서 초기화한다. + - 스케줄링: `schedule_work(&work);` 함수를 이용해서 작업 스레드를 깨우고 워크큐 핸들러를 실행한다. + - 만일 당장 실행하고 싶지 않다면, `schedule_delayed_word(&work, delay);` 함수로 원하는 시간 이후에 활성화 할 수도 있다. + +--- +참고 +- [Linux 커널 심층 분석 3판](https://product.kyobobook.co.kr/detail/S000000935348) +- https://github.com/torvalds/linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\234\240\354\240\200\353\252\250\353\223\234\354\231\200\342\200\205\354\273\244\353\204\220\353\252\250\353\223\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\234\240\354\240\200\353\252\250\353\223\234\354\231\200\342\200\205\354\273\244\353\204\220\353\252\250\353\223\234.md" new file mode 100644 index 00000000..4211b791 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\234\240\354\240\200\353\252\250\353\223\234\354\231\200\342\200\205\354\273\244\353\204\220\353\252\250\353\223\234.md" @@ -0,0 +1,49 @@ +--- +title: '유저모드와 커널모드' +lastUpdated: '2024-03-02' +--- + +커널은 중요한 자원을 관리하기 때문에, 사용자가 그 자원에 쉽게 접근하지 못하도록 모드를 2가지로 나눈다. + +- **커널모드** + - 운영체제 내부에서 실제로 하드웨어를 제어할 수 있다. + - 모든 자원(드라이버, 메모리, CPU 등)에 접근, 명령을 할 수 있다. + - 커널 모드에서 실행되는 모든 코드는 단일 가상 주소 공간을 공유한다. 따라서 커널 모드 드라이버는 다른 드라이버 및 운영 체제 자체와 격리되지 않는다. + +- **유저모드** + - 접근할 수 있는 영역이 제한적이어서 프로그램의 자원에 함부로 침범하지 못하는 모드이다. + - 여기서 코드를 작성하고, 프로세스를 실행하는 등의 행동을 할 수 있다. + - 사용자 모드에서 모든 프로세스는 별도의 가상 주소 공간을 할당받는다. + +## Mode bit + +- CPU 내부에 Mode bit 을 두어 kernel-mode, user-mode 를 구분한다. +- 즉, CPU 가 Mode bit 를 보고 커널모드인지, 유저모드인지 파악한다. +- 0이면 커널 모드, 1이면 유저모드이다. + +## 유저모드와 커널모드의 전환 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/a97692f1-43fc-40c6-916b-608febd970e9) + +- 프로세스가 실행되는 동안에 프로세스는 유저모드와 커널모드를 수없이 오가면서 실행된다. +- **유저모드 -> 커널모드 요청**: 프로세스가 유저모드에서 실행되다가 특별한 요청이 필요할때 `system call`을 이용해서 커널에 요청을 한다. +- **커널모드 -> 유저모드로 반환**: system call의 요청을 받은 커널이 그 요청에 대한 일을 하고 결과값을 system call의 리턴 값으로 전해준다. + +> 유저모드에서 커널모드로의 전환과 되돌림은 스레드 스케줄링에 영향을 미치지 않는다. 모드 전환은 컨텍스트 변경(context switch)이 아니다. + +## Context + +현재 system이 누구를 위해서 동작하는가를 context라고 한다. + +context를 바꾸는 것을 context switching이라고 한다. + +- process context : 현재 system이 process를 위해서 동작한다. +- system context : 현재 system이 특정 process를 위해서가 아닌 공동의 목적을 위해서 동작한다. + +image + +--- +참고 +- https://learn.microsoft.com/ko-kr/windows-hardware/drivers/gettingstarted/user-mode-and-kernel-mode +- https://www.geeksforgeeks.org/difference-between-user-mode-and-kernel-mode/ +- https://www.geeksforgeeks.org/user-mode-and-kernel-mode-switching/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\235\264\354\213\235\354\204\261.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\235\264\354\213\235\354\204\261.md" new file mode 100644 index 00000000..74475f00 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\235\264\354\213\235\354\204\261.md" @@ -0,0 +1,43 @@ +--- +title: '이식성' +lastUpdated: '2024-03-02' +--- + +- 이식성이란, 특정 시스템 아키텍처의 코드가 (가능하다면) 얼마나 쉽게 다른 아키텍처로 이동할 수 있는지를 의미한다. +- 이 장에서는 핵심 커널 코드나 디바이스 드라이버를 개발할 때 이식성 있는 코드를 작성하는 방법에 대해서 알아본다. +- 리눅스는 인터페이스와 핵심 코드는 아키텍처 독립적인 C로 작성됐고, 성능이 중요한 커널 기능은 각 아키텍처에 특화된 어셈블리로 작성해 최적화시켰다. + - 좋은 예로 스케줄러가 있다. 스케줄러 기능의 대부분은 `` 파일에 아키텍처 독립적으로 구현돼있다. + - 하지만, 스케줄링의 세부 과정인 context switching과 memory management를 책임지는 `switch_to()`, `switch_mm()` 함수는 아키텍처별로 따로따로 구현돼있다.​ + +## 1. 불확실한 자료형 크기 + +- 1-WORD는 시스템이 한 번에 처리할 수 있는 데이터의 길이를 의미하며 보통 범용 레지스터의 크기와 같다. +- 리눅스 커널은 long 데이터의 크기가 1-WORD 크기와 같다. +- 리눅스 커널은 아키텍처마다 `` 의 `BITS_PER_LONG` 에 long 데이터형 크기로 1-WORD 크기를 지정해 놓았다. +- 옛날에는 같은 아키텍처도 32-bit 버전과 64-bit 버전이 따로 구현돼있었지만, 2.6버전 이후로 통합됐다. +- 아키텍처에 따라 C 자료형의 크기가 불명확한 것에 따라 장단점이 있다. + - 장점: long 크기가 1-WORD임이 보장된다, 아키텍처별로 명시적으로 자료형의 크기를 지정하지 않아도 된다 등 + - 단점: 코드 상에서 자료형의 크기를 알 수가 없다. +- 따라서 자료형의 크기를 함부로 추정하지 않는 것이 좋다. +- 자료형이 실제 필요로 하는 공간과 형태가 바뀌어도 상관 없도록 코드를 작성해야 이식성 높은 코드를 작성할 수 있다. +​ +## 2. 더욱 구체적인 자료형 + +- 때로는 개발자가 코드에서 자료형을 더욱 구체적으로 명시화 해줄 필요가 있다. +- 예를 들어, 레지스터나 패킷 같이 HW, NW 관련 코드를 작성해야 하는 경우 +- 음수를 저장해야 하는 경우: 명시적으로 signed 키워드를 써주는 것을 권장한다. +- 커널은 `` 파일에 명시적으로 크기가 정해진 자료형(i.e. u8, u16, u32, u64 등)을 typedef로 정의해놨다. +- 이 자료형은 namespace 문제 때문에 커널 내부 코드에서만 사용해야 한다. +- 만일, 사용자 공간에 노출해야 한다면, 언더스코어 2개를 덧붙여서 `__u8`, `__u16` 처럼 사용하면 된다. 의미는 같다. + +## 3. 기타 권장사항 + +- **바이트 순서**: 절대로 바이트 순서를 예측하지 마라. 범용 코드는 빅엔디안, 리틀엔디안 모두에서 동작해야 한다. +- **시간**: 절대로 jiffies 값을 양수값과 비교해서는 안 된다. Hz값으로 곱하거나 나눠야 한다. +- **페이지 크기**: 페이지 크기는 아키텍처마다 다르다. 당연히 4KB라고 생각해선 안 된다. +- **처리 순서**: 아키텍처마다 다양한 방식으로 프로세서 처리 순서를 따르므로 적절한 배리어를 사용해야 한다. + +--- +참고 +- [Linux 커널 심층 분석 3판](https://product.kyobobook.co.kr/detail/S000000935348) +- https://github.com/torvalds/linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\273\244\353\204\220\342\200\205\352\260\234\353\205\220.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\273\244\353\204\220\342\200\205\352\260\234\353\205\220.md" new file mode 100644 index 00000000..4f6c16cf --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\273\244\353\204\220\342\200\205\352\260\234\353\205\220.md" @@ -0,0 +1,64 @@ +--- +title: '커널 개념' +lastUpdated: '2024-03-02' +--- + +## 1. OS와 커널 + +- 커널은 시스템의 기본적인 서비스를 제공하고, HW를 관리하며, 리소스를 분배하는 핵심 SW를 의미한다. + +- 커널의 주 구성요소 + - 인터럽트 핸들러 (ISR, Interrupt Service handler Routain) + - 프로세스 스케줄러 (Scheduler) + - 메모리 관리 시스템 (MM, Memory Management) + - 네트워크 및 IPC 서비스 + +- 일반적으로 사용자는 시스템의 ‘유저 공간’에서 사용자 애플리케이션을 수행하며, 커널 기능이 필요할 때 시스템콜 또는 인터럽트를 호출해 ‘커널 공간’에 있는 커널 애플리케이션을 요청한다. + +## 2. UNIX vs LINUX + +UNIX와 다르게 LINUX는.. + +- 커널 모듈 동적 로드 기능을 제공한다. (Monolothic kernel이지만 동시에 micro kernel 성격도 가짐) +- SMP(symmetric multiprocessing)를 지원한다. (최신 상용 UNIX도 지원한다) +- 커널도 선점형 스케줄러로 동작한다. +- 프로세스와 스레드를 구분하지 않는다. +- 디바이스 파일시스템(sysfs) 등을 지원해 객체지향적 장치 모델을 지원한다. + +## 3. 커널 소스 트리 + +커널 소스 트리는 여러 개의 디렉토리로 구성되는데, 최상단의 주요 디렉토리에 대한 설명은 다음과 같다. + +- `arch`: 특정 아키텍처(i.e. ARM, PowerPC, x86 등)에 대한 소스코드 +- `block`: 블록 I/O 관련 기능에 대한 소스코드 +- `crypto`: 암호화 관련 기능에 대한 소스코드 +- `Documentation`: 커널 소스와 관련된 문서 모음 +- `drivers`: 장치 드라이버 관련 소스코드 +- `firmwares`: 특정 장치 드라이버를 사용할 때 필요한 펌웨어의 모음 +- `fs`: 파일시스템 관련 소스코드 +- `include`: 커널의 헤더 파일 모음 +- `init`: 커널 초기화 관련 소스코드 +- `ipc`: 프로세스 간 통신(IPC) 관련 소스코드 +- `kernel`: 스케줄러와 같은 핵심 커널 시스템 관련 소스코드 +- `lib`: 유틸리티 모음 +- `mm`: 메모리 관리 시스템 및 가상 메모리 관련 소스코드 +- `net`: 네트워크 관련 소스코드 +- `samples`: 예제 및 데모 코드 모음 +- `scripts`: 커널 빌드를 위한 스크립트 모음 +- `security`: 보안 기능 관련 소스코드 +- `sound`: 사운드 시스템 기능 관련 소스코드 +- `usr`: 초기 사용자 공간 소스코드 +- `tools`: 리눅스 커널 개발에 유용한 도구 모음 +- `virt`: 가상과 기반 구조 관련 소스코드 + +## 4. 커널의 특징 + +- 커널은 속도 및 크기를 이유로 표준 C 라이브러리(libc) 대신 GNU C(glibc)를 이용한다. +- 커널 공간에는 유저 공간과 같은 메모리 보호 기능이 없다. +- 커널은 부동소수점 연산을 쉽게 수행할 수 없다. +- 커널은 프로세스당 고정된 작은 크기의 스택을 사용한다. +- 커널은 비동기식 선점형 인터럽트를 지원하며, SMP를 지원하므로 동기화 및 동시성(concurrency) 문제가 매우 중요하다. + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\273\244\353\204\220\342\200\205\353\252\250\353\223\210.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\273\244\353\204\220\342\200\205\353\252\250\353\223\210.md" new file mode 100644 index 00000000..358acd26 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\354\273\244\353\204\220\342\200\205\353\252\250\353\223\210.md" @@ -0,0 +1,193 @@ +--- +title: '커널 모듈' +lastUpdated: '2024-03-02' +--- + +- 모듈은 요청 시 커널에 로드 및 언로드할 수 있는 코드 조각이다. + +- 시스템을 재부팅할 필요 없이 커널의 기능을 확장한다. + - 예를 들어, 한 가지 유형의 모듈은 커널이 시스템에 연결된 하드웨어에 액세스할 수 있도록 하는 디바이스 드라이버이다. + - 모듈이 없으면 모놀리식 커널을 빌드하고 커널 이미지에 직접 새로운 기능을 추가해야 하고, 새로운 기능을 원할 때마다 커널을 다시 빌드 및 재부팅해야 한다는 단점이 있다. + - 따라서 모듈을 이용하면 커널 컴파일 시간을 단축할 수 있다. + +- **로드 가능한 커널 모듈**(LKM, Loadable Kernel Module)은 런타임에 Linux 커널에 코드를 추가하거나 제거하는 메커니즘이다. + - 모듈 없이 Linux 커널에 코드를 추가하려는 경우 가장 기본적인 방법은 커널 소스 트리에 일부 소스 코드 또는 파일을 추가하고 커널을 다시 컴파일하는 것이다. + - 모듈을 사용하면 Linux 커널이 실행되는 동안 코드를 추가할 수 있다. + - 이러한 방식으로 추가하는 코드 덩어리를 로드 가능한 커널 모듈이라고 한다. + - 모듈은 커널이 하드웨어와 통신할 수 있도록 하는 디바이스 드라이버에 이상적이다. + - 모듈은 `/lib/modules/` 디렉터리에서 `.ko`(kernel object, 커널 개체) 확장자를 가진 파일로 나타낼 수 있다. + - 디바이스 드라이버는 모듈 형태로 구성되어 있다. + - 파일 시스템, 메모리 매니지먼트 또한 커널 형태로 구성할 수 있다. + +- 요약: KCM은 모듈별 빌드가 가능하도록 하여 컴파일 시간을 최소화한다. + +### 명령어 + +- `lsmod` + - 현재 설치된 사용중인 모듈을 보여준다. + - lsmod 명령은 현재 커널에 설치된 모듈을 나열하는 데 사용할 수 있다. + - 결과는 모듈의 이름, Size 및 usage(Used by) 목록이다. + - "Used by"(usage)는 이 모듈을 사용하는 다른 모듈의 수를 의미한다. + + ```bash + $ lsmod + Module Size Used by + udp_diag 16384 0 + raw_diag 16384 0 + unix_diag 16384 0 + ... + ``` + +- `insmod` + - 모듈을 삽입하기 위해 사용하는 명령이다. + - insmod(또는 insert module, 모듈 삽입) 명령을 사용하여 커널에 모듈을 삽입할 수 있다. + - 사용자가 런타임에 커널 모듈을 로드하여 커널 기능을 확장할 수 있다. + - `insmod my_module.ko` -> 커널 개체 파일(.ko)을 커널에 삽입한다. + +- `rmmod` + - 모듈을 제거할 때 사용하는 명령이다. + - rmmod(또는 remove module, 모듈 제거) 명령은 실행 중인 커널에서 로드 가능한 모듈을 언로드한다. + - 모듈을 삭제하기 위해선 사용 중이 아니어야 하고, 다른 모듈에서 참조하지 않아야 한다. + - 명령줄에서 둘 이상의 모듈 이름이 지정되면 해당 모듈은 지정된 순서대로 제거된다. + - 이것은 스택된 모듈의 언로드를 지원한다. + +- `modinfo` + - 모듈 정보를 얻으려고 할 때 사용하는 명령이다. + - Linux 시스템에서 modinfo 명령은 Linux 커널 모듈에 대한 정보를 표시하는 데 사용된다. + - 이 명령은 명령줄에 제공된 Linux 커널 모듈에서 정보를 추출한다. + + ```bash + $ modinfo udp_diag + filename: /lib/modules/5.15.0-1019-aws/kernel/net/ipv4/udp_diag.ko + alias: net-pf-16-proto-4-type-2-136 + alias: net-pf-16-proto-4-type-2-17 + license: GPL + srcversion: A6913F04E5CF94B0DEC8CAB + depends: inet_diag + retpoline: Y + intree: Y + name: udp_diag + vermagic: 5.15.0-1019-aws SMP mod_unload modversions + sig_id: PKCS#7 + ``` + +- `modprobe` + - 모듈을 설치/삭제하는 명령어 + - insmod/rmmod 명령어와 다르게 `modules.dep` 파일을 참조해 의존성 문제를 해결 + + ```bash + $ modprobe --help + Usage: + modprobe [options] [-i] [-b] modulename + modprobe [options] -a [-i] [-b] modulename [modulename...] + modprobe [options] -r [-i] modulename + modprobe [options] -r -a [-i] modulename [modulename...] + modprobe [options] -c + modprobe [options] --dump-modversions filename + Management Options: + -a, --all Consider every non-argument to + be a module name to be inserted + or removed (-r) + -r, --remove Remove modules instead of inserting + --remove-dependencies Also remove modules depending on it + ``` + +- `depmod` + - 의존성을 검사하여 `modules.dep` 파일을 갱신하는 명령어 + + ```bash + $ depmod --help + Usage: + depmod -[aA] [options] [forced_version] + + If no arguments (except options) are given, "depmod -a" is assumed + + depmod will output a dependency list suitable for the modprobe utility. + ``` + +## 커널 모듈 만들기 + +- 모듈의 소스 코드는 커널 소스 트리 밖에 있을 수 있다. +- 모듈 소스 디렉토리에 메이크파일을 넣는다. +- 컴파일 후 컴파일된 모듈은 확장자가 .ko인 파일이다. + +### Makefile 생성 + +- `$ vi Makefile` + + ```bash + #------Makefile------# + obj-m := hello_module.o + KERNEL_DIR := /lib/modules/$(shell uname -r)/build + PWD := $(shell pwd) + default: + $(MAKE) -C $(KERNAL_DIR) SUBDIR=${PWD} modules + clean: + $(MAKE) -C $(KERNAL_DIR) SUBDIR=${PWD} clean + ``` + + - `오브젝트 모듈:= hello_module.o` + - 컴파일할 모듈의 이름을 넣으면 된다. + - `KERNEL_DIR := /lib/modules/$(shell uname -r)/build` + - 커널 디렉토리 + - shell uname : 커널 버전이 나온다. + - 커널 버전에 맞는 빌드 디렉토리를 넣어야 한다. + - `PWD := $(shell pwd)` + - 소스코드가 있는 디렉토리 + - Print Working Directory + - `default : $(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules` + - default는 뒤에 아무 옵션을 주지 않았을 때 시행되는 명령어이다. + - 커널 디렉토리와 서브 디렉토리를 넣어준다. + - 모듈 컴파일이니 modules를 붙인다. + - `clean : $(MAKE) -C $(KERNEL_DIR) SUBDIRS=$(PWD) clean` + - clean: make clean 썼을 때 시행되는 명령어이다. + +### 커널 모듈 작성 +```c +#--------- hello_module.c ---------# +#include //Needed by all modules +#include //Needed for KERN_ALERT +#include //Needed for the macros + +int __init hello_module_init(void) +{ + printk("Hello Module!\n"); + return 0; +} + +void __exit hello_module_cleanup(void) +{ + printk("Bye Module!\n"); +} + +module_init(hello_module_init); +module_exit(hello_module_cleanup); +MODULE_LICENSE("GPL"); +``` + +- `.c` 파일을 만들어 세 가지 커널 헤더를 include한다. +- `insmod`는 해당 모듈을 로드한다. 파일의 module_init 함수를 부른다. +- `rmmod`는 해당 모듈을 언로드한다. 파일의 module_exit 함수를 부른다. + +- 모듈 초기화 및 종료 + - **module_init(hello_module_init)** + - 초기화 진입점 + - 모듈 삽입 시 실행할 함수 (hello_module_init) + - `hello_module_init()`는 모듈을 로드할 때 호출된다. + - **module_exit(hello_module_cleanup)** + - 출구 진입점 + - 모듈 제거 시 실행할 함수(hello_module_cleanup) + - `hello_module_cleanup()`은 모듈을 언로드할 때 호출된다. +- 커널 모듈에는 최소한 두 가지 함수가 있어야 한다. + - 모듈이 커널에 insmod될 때 호출되는 "시작"(initialization) 함수(예: `hello_module_init`()) + - 모듈이 커널에 rmmod될 때 호출되는 "종료"(cleanup) 함수(예: `hello_module_cleanup`()) +- 커널 모듈 시작 + - 커널 코드를 실행하기 때문에 루트 권한이 필요하다. + - `insmod`로 커널 모듈을 로드해야 한다. + - `insmod hello_module.ko` (반드시 .ko를 붙여야 한다.) + - 모듈이 로드되고 초기화 함수가 실행된다. + - 모듈은 특정 커널 버전에 대해 컴파일되며 다른 커널에는 로드되지 않는다. + - `rmmod`로 모듈 제거 + - `rmmod hello_module` 또는 `rmmod hello_module.ko` + - 언로드 전에 모듈 종료 함수가 호출된다. + diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\203\200\354\235\264\353\250\270.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\203\200\354\235\264\353\250\270.md" new file mode 100644 index 00000000..d428be88 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\203\200\354\235\264\353\250\270.md" @@ -0,0 +1,138 @@ +--- +title: '타이머' +lastUpdated: '2024-03-02' +--- + +- 커널은 `` 헤더파일에 시스템 타이머의 진동수를 HZ라는 값에 저장한다. +- 일반적으로 HZ 값은 100 또는 1000으로 설정돼있고, 커널 2.5 버전부터 ​기본값이 1000으로 상향됐다. + - 장점: 타이머 인터럽트의 해상도와 정확도가 향상돼 더 정확한 프로세스 선점이 가능해졌다. + - 단점: 타이머 인터럽트 처리에 더 많은 시간을 소모하고, 전력 소모가 늘어난다. + - 실험결과 시스템 타이머를 1,000Hz로 변경해도 성능을 크게 해치지 않는다는 결론이 났다. + +- ``에 jiffies 라는 전역변수에는 시스템 시작 이후 발생한 틱 횟수가 저장된다. +- 타이머 인터럽트가 초당 HZ회 발생하므로 jiffies는 1초에 HZ만큼 증가한다. +- 따라서 시스템 가동 시간은 **jiffies / HZ** 로 계산할 수 있다. +- 32-bit 시스템에선 unsigned long 형인 jiffies는 HZ 100에선 497일, 1,000에선 50일이면 오버플로우가 발생한다. +- 반면에, 64-bit 시스템에선 평생 발생하지 않는다. + - 이 문제를 해결하기 위해 32-bit 시스템에서는 `extern u64 jiffies_64` 라는 변수를 만들고 + - 주 커널 이미지 링커 스크립트에 jiffies = jiffies_64라고 써서 두 변수를 겹쳐버린다. + - 이러면 jiffies 변수는 32-bit 시스템에서도 오버플로우가 발생하지 않는다. +- jiffies는 오버플로우일 때 다시 0으로 돌아간다. + +- 서로 다른 두 jiffies값을 올바르게 비교할 수 있도록 매크로 함수를 제공하고 있다. + - `#define time_after(a, b) ((long)(b) - (long)(a) < 0)` + - `#define time_before(a, b) ((long)(a) - (long)(b) < 0)` + - 보통 a는 현재 jiffies값이, b는 비교하려는 값이 들어간다. + +## ​1. 타이머 인터럽트 + +- 타이머 인터럽트는 다음 과정을 처리한다. + +1. xtime_lock 락을 얻어 xtime, jiffies 변수에 안전하게 접근한다. +2. 아키텍처 종속적인 `tick_periodic()` 함수를 호출한다. +3. jiffies 값을 1 증가, xtime에 현재 시간을 갱신한다. +4. 설정 시간이 만료된 동적 타이머의 핸들러를 실행한다. + +```c +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/kernel/time/tick-common.c#L82C1-L102C2 +/* + * Periodic tick + */ +static void tick_periodic(int cpu) +{ + if (tick_do_timer_cpu == cpu) { + raw_spin_lock(&jiffies_lock); + write_seqcount_begin(&jiffies_seq); + + /* Keep track of the next tick event */ + tick_next_period = ktime_add_ns(tick_next_period, TICK_NSEC); + + do_timer(1); + write_seqcount_end(&jiffies_seq); + raw_spin_unlock(&jiffies_lock); + update_wall_time(); + } + + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); +} + +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/kernel/time/timekeeping.c#L2289 +void do_timer(unsigned long ticks) +{ + jiffies_64 += ticks; + calc_global_load(); +} + +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/kernel/time/timer.c#L2064 +/* + * Called from the timer interrupt handler to charge one tick to the current + * process. user_tick is 1 if the tick is user time, 0 for system. + */ +void update_process_times(int user_tick) +{ + struct task_struct *p = current; + + /* Note: this timer irq context must be accounted for as well. */ + account_process_tick(p, user_tick); + run_local_timers(); + rcu_sched_clock_irq(user_tick); +#ifdef CONFIG_IRQ_WORK + if (in_irq()) + irq_work_tick(); +#endif + scheduler_tick(); + if (IS_ENABLED(CONFIG_POSIX_TIMERS)) + run_posix_cpu_timers(); +} +``` + +- 1/HZ 초마다 한 번씩 타이머 인터럽트가 발생해 `tick_periodic()` 핸들러가 호출된다. +- `do_timer()` 에서는 jiffies를 증가하고 시스템 내 여러 통계 변수를 갱신한다. +- `update_process_time()` 에서는 + - `account_process_tick()` 함수에서 프로세서의 시간을 갱신한다. + - `run_local_timers()` 함수에서 제한시간이 만료된 타이머들의 핸들러를 실행한다. + - `schedule_tick()` 함수는 현재 프로세스의 타임슬라이스 값을 줄이고, 필요한 경우 need_sched 플래그를 설정해 스케줄링 여부를 결정한다. + +## ​2. 타이머 + +```c +// +struct timer_list { + struct list_head entry; + unsigned long expires; + void (*function)(unsigned long); + unsigned long data; + struct tvec_base *base; +}; + +struct timer_list my_timer; +// 1. 타이머를 생성하고 초기화한다. +init_timer(&my_timer); +my_timer.expires = jiffies + delay; +my_timer.data = 0; +my_timer.function = my_function; + +// 2. 타이머를 활성화한다. +add_timer(&my_timer); +// 3. 타이머의 만료 시간을 갱신한다. +mod_timer(&my_timer, jiffies + new_delay); +// 4. 타이머를 제거한다. +del_timer(&my_timer); +``` + +- 커널 타이머는 초기화 작업 → 핸들러 설정 → 타이머 활성화로 사용하고 만료된 이후 자동으로 소멸된다. +- 타이머는 비동기적으로 실행되므로 race condition이 발생할 잠재적인 위험이 있다. 따라서 `del_timer()` 함수 보다는 조금 더 안전한 버전인 `del_timer_sync()` 함수를 사용하자. + +## 3. 작은 지연 + +- 간혹 커널 코드에서는 아주 짧지만 정확한 지연 시간이 필요한 경우가 있다. +- 커널의 ``에는 jiffies 값을 사용하지 않고도 지연처리를 하는 `mdelay()`, `udelay()`, `ndelay()` 3가지 함수를 제공한다. + - 물론, 지연 하는 동안 시스템이 동작을 정지하므로 꼭 필요한 경우가 아니면 절대 쓰면 안 된다. +- 더 적당한 해결책은 `schedule_timeout()` 함수를 사용하는 것이다. + - 최소한 인자로 넘긴 지정한 시간만큼 해당 작업이 휴면 상태로 전환됨을 보장한다. + - 당연하지만, 이 함수는 프로세스 컨텍스트에서만 사용할 수 있다. + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\214\214\354\235\274\354\213\234\354\212\244\355\205\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\214\214\354\235\274\354\213\234\354\212\244\355\205\234.md" new file mode 100644 index 00000000..8c8e4408 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\214\214\354\235\274\354\213\234\354\212\244\355\205\234.md" @@ -0,0 +1,30 @@ +--- +title: '파일시스템' +lastUpdated: '2024-03-02' +--- + +- VFS(Virtual FileSystem)는 시스템콜이 파일시스템이나 물리적 매체 종류에 상관없이 공통적으로 동작할 수 있도록 해주는 인터페이스다. +- 파일시스템 추상화 계층은 모든 파일시스템이 지원하는 기본 인터페이스와 자료구조를 선언한 것이다. +- VFS는 슈퍼블록(superblock), 아이노드(inode), 덴트리(dentry), 파일(file) 4가지 객체로 구성돼있다. + - **슈퍼블록**: 파일시스템을 기술하는 정보(+ file_system_type, vfsmount 구조체)를 저장한다. + - **아이노드**: 파일이나 디렉토리를 관리하는 데 필요한 모든 정보를 저장한다. + - **덴트리**: 디렉토리 경로명 속 각 항목의 유효성 정보 등을 저장한다. + - **파일**: 메모리 상에 로드 된 열려있는 파일에 대한 정보를 저장한다. 한 파일은 여러 프로세스에서 동시에 열고 사용할 수 있기 때문에, 같은 파일에 대해 여러 개의 파일 객체가 있을 수 있다. + + +- 각 객체들에는 여러 함수들이 들어있는 동작(operation) 객체가 멤버변수로 들어있다. + - **super_operation** 객체에는 `write_inode()`, `sync_fs()` 같이 특정 파일시스템에 대한 함수가 들어있다. + - **inode_operation** 객체에는 `create()`, `link()` 같이 특정 파일에 대한 함수가 들어있다. + - **dentry_operation** 객체에는 `d_compare()`, `d_delete()` 같이 디렉토리에 대한 함수가 들어있다. + - **file_operation** 객체에는 `read()`, `write()` 같이 열린 파일(프로세스)에 대한 함수가 들어있다. 표준 유닉스 시스템콜의 기초가 되는 익숙한 함수들이 들어있다. + + +- **프로세스 관련 자료구조 ** + - 프로세스는 `task_struct` 구조체로 관리한다. + - `task_struct`는 `files_struct` 구조체를 멤버변수로 가지고 있는데, 여기에 열려 있는 파일(프로세스)에 대한 세부 정보가 저장된다. + - `files_struct`는 ``에 정의되어있다. + + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\352\264\200\353\246\254.md" new file mode 100644 index 00000000..607ab295 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\352\264\200\353\246\254.md" @@ -0,0 +1,183 @@ +--- +title: '프로세스 관리' +lastUpdated: '2024-03-02' +--- + +## 1. 프로세스와 구조체 + +- 프로세스는 프로그램 코드를 실행하면서 생기는 모든 결과물이다. + - 일반적인 의미: 실행 중인 프로그램 + - 포괄적인 의미: 사용 중인 파일, 대기 중인 시그널, 커널 내부 데이터, 프로세서 상태, 메모리 주소 공간, 실행 중인 하나 이상의 스레드 정보 등 +- 프로세스는 `fork()` 호출 시 생성되고, 기능을 수행한 뒤, `exit()`를 호출해 종료된다. 부모 프로세스는 `wait()` 호출로 자식 프로세스 종료 상태를 확인할 수 있다. +- 스레드는 프로세스 내부에서 동작하는 객체이고, 개별적인 PC, Stack, Register(context)를 가지고 있다. +- 리눅스 커널은 프로세스와 스레드를 구분하지 않는다. +- 리눅스 커널에 대한 접근은 오직 시스템 콜과 ISR로만 가능하다. + +- 커널은 프로세스를 ‘task list’라는 **circular bidirctional linked list **자료구조로 저장한다. +- ‘Task list’는 `` 내 `task_struct` 구조체로 정의되어있다. + - `task_struct`는 프로세스를 관리하는 데 필요한 모든 정보를 가지고 있어서 정의만 약 300줄 가량이며 32-bit 시스템 기준 약 1.7KB에 달하는 상당히 큰 구조체다. + - `task_struct`는 ‘slab allocator’(객체 재사용 및 캐시 기능 지원)를 이용해 동적할당 한다. + - 커널 2.6 버전 이전에는 각 프로세스 커널의 최하단(또는 최상단)에 task_struct를 뒀다. 그래야 x86처럼 레지스터 개수가 적은 아키텍처에서 레지스터에 따로 task_struct의 주솟값을 저장하지 않고 바로 접근할 수 있었기 때문이다. + + ```c + struct task_struct { + volatile long state; + void *stack; + atomic_t usage; + unsigned int flags; + unsigned int ptrace; + + int lock_depth; + #ifdef CONFIG_SMP + #ifdef __ARCH_WANT_UNLOCKED_CTXSW + ... + } + ``` + +## 2. 프로세스 상태 + +- 프로세스는 다음과 같은 5가지 상태를 가진다. + + - `TASK_RUNNING`: Ready queue에서 대기중이거나, 현재 동작 중인 프로세스다. + - `TASK_INTERRUPTIBLE`/`TASK_UNINTERRUPTIBLE`: 특정 조건이 발생하기를 기다리며 중단된 상태에 있는 프로세스다. 조건 발생 시 `TASK_RUNNING`으로 바뀐다. Signal 수신 여부로 두 상태를 구분한다. + - `TASK_TRACED`: 디버거 같은 장비를 사용하는 외부 프로세스가 ptrace를 사용해 해당 프로세스를 추적하고 있는 상태다. + - `TASK_STOPPED`: 프로세스가 SIGSTOP 같은 signal을 받아 실행이 정지된 상태다. + +- 프로세스의 상태는 ``의 `set_task_state()` 함수로 설정 가능하다. + +## 3. 프로세스 계층 트리 + +- 모든 프로세스는 PID 1인 init 프로세스의 자식 프로세스다. +- `task_struct`는 부모-형제-자식 프로세스의 관계를 표현하고 있다. +- 또한, `task_struct`는 (Bidirectional circular linked list의 요소를 가리키는) `*next`, `*prev` 포인터를 갖고 있다. + + +## 4. 프로세스 생성 + +- UNIX에서는 `fork()`로 프로세스를 생성할 때 부모 프로세스의 모든 리소스를 그대로 자식 프로세스에 복사하는 식으로 구현했다. 일반적으로 자식 프로세스는 생성된 후 `exec()`을 이용해 다른 프로그램을 실행하는 경우가 많으므로 이러한 방법은 굉장히 비효율적이었다. + +- 리눅스는 ‘Copy-and-write’를 이용해서 이 문제를 해결했다. + - 자식 프로세스가 공유자원에 write을 시도할 때 부모 프로세스 → 자식 프로세스 리소스 복사한다. + - 자식 프로세스가 공유자원에 write을 하지 않는 경우 (대부분 생성 후 바로 `exec()`하는 경우), 큰 최적화 효과를 얻을 수 있다. + +- 프로세스가 생성되는 세부적인 과정은 다음과 같다. + +1. 리눅스의 glibc 속 `fork()`는 `clone()` 이라는 시스템콜을 다양한 플래그를 적용해 부모-자식 프로세스간 공유자원을 지정한 뒤 호출한다. + - linux - Which file in kernel specifies `fork()`, `vfork()`... to use `sys_clone()` system call - Unix & Linux Stack Exchange + +2. `clone()`은 `do_fork()`함수를 호출하고 `do_fork()`는 `copy_process()`를 호출해 내부적으로 아래 과정을 수행한다. + + 1. `dup_task_struct()` 함수 호출 + - 새로운 프로세스 스택 공간 할당, 새로운 thread_info, task_struct 구조체를 생성한다. + - 생성할 때 부모의 process descriptor를 그대로 가져와서 생성한다. + 2. 프로세스 개수 제한을 넘었는지 검사한다. + 자식 프로세스 구조체의 일부 멤버변수를 초기화. (상태= `TASK_UNINTERRUPTED`) + 4. `copy_flag()` 함수 호출 + - `task_struct`의 `flags` 내용을 정리한다. + - `PF_SUPERPRIV` 플래그 초기화: 현재 수행하는 작업이 관리자 권한임을 의미. + - `PF_FORKNOEXEC` 플래그 초기화: 프로세스가 exec() 함수를 호출하지 않았음을 의미. + 5. `alloc_pid()` 함수 호출 + - 자식 프로세스에게 새로운 PID값을 할당한다. + 6. `clone()`의 매개변수로 전달된 플래그에 따라 파일시스템 정보, signal handler, 주소공간, namespace 등을 share하거나 copy한다. (보통 스레드는 share를, 프로세스는 copy 한다.) + 생성한 자식 프로세스의 포인터를 반환한다. + +- `vfork()` 시스템콜은 부모 프로세스의 page table을 copy하지 않는다는 점을 제외하면 `fork()`와 동일. 그러나 copy-and-write을 사용하는 리눅스 특성상 `fork()` 대비 이득이 적어서 거의 사용하지 않는다. + +## 5. 스레드 구현 및 취급 + +- 대표적인 modern-programming 기법인 스레드는 공유 메모리를 가진 여러 프로그램을 ‘동시에’(concurrent) 수행해 multi-processor 환경에서는 진정한 병렬처리를 구현할 수 있다. +- 스레드는 개별 `task_struct`를 갖고 메모리를 부모 프로세스와 공유하고 있는 정상 프로세스다. (리눅스는 프로세스와 스레드를 구분하지 않는다.) +- 따라서 스레드도 내부적으로는 프로세스 생성 때와 똑같이 `clone()` 시스템콜을 이용한다. 여러 플래그를 parameter로 넘겨서 스레드의 특성을 부여할 뿐이다. (i.e. `clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)`;) +- ``의 최상단에 스레드 생성 관련 clone flags가 정의되어있다. + +### 커널 스레드 + - 커널도 일부 동작은 백그라운드에서 실행하는 것이 좋은데, 이때 커널 공간에서만 존재하는 특별한 스레드인 ‘커널 스레드’를 이용한다. + - 가장 큰 차이점은 주소 공간이 없다는 점이다. (프로세스의 주소 공간을 가리키는 mm 포인터가 NULL이다.) + - 커널 스레드는 ``에 정의돼있고, `kthreadd`라는 최상위 부모 스레드가 모든 하위 커널 스레드를 만드는 방식으로 동작한다. + + - 커널 스레드는 `kthread_run` 매크로로 `kthread_create()`를 호출해 `clone()` 시스템콜을 호출해 만든다. + +## 6. 프로세스 종료 + +- 프로세스는 `main()`이 끝날 때 묵시적으로 또는 명시적으로 `exit()`를 호출하여 종료된다. +- `exit()` 함수는 내부적으로 ``에 정의된 `do_exit()` 함수를 호출한다. +- 프로세스 종료 과정은 아래와 같다. + +```c +void __noreturn do_exit(long code) +{ + struct task_struct *tsk = current; + int group_dead; + + ... + exit_signals(tsk); /* 1 - sets PF_EXITING */ + + acct_update_integrals(tsk); /* 2 */ + group_dead = atomic_dec_and_test(&tsk->signal->live); + if (group_dead) { + /* + * If the last thread of global init has exited, panic + * immediately to get a useable coredump. + */ + if (unlikely(is_global_init(tsk))) + panic("Attempted to kill init! exitcode=0x%08x\n", + tsk->signal->group_exit_code ?: (int)code); + + ... + tsk->exit_code = code; /* 3 */ + taskstats_exit(tsk, group_dead); + + exit_mm(); /* 4 */ + + if (group_dead) + acct_process(); + trace_sched_process_exit(tsk); + + exit_sem(tsk); /* 5 */ + exit_shm(tsk); + exit_files(tsk); /* 6 */ + exit_fs(tsk); + + ... + exit_notify(tsk, group_dead); /* 7 */ + proc_exit_connector(tsk); + mpol_put_task_policy(tsk); + + ... + lockdep_free_task(tsk); + do_task_dead(); /* 8 */ +} +``` + +1. current의 flags의 `PF_EXITING` 플래그를 설정한다. +2. `acct_update_integrals()` 함수를 호출해 종료될 프로세스 정보를 기록한다. + current의 exit_code에 `exit()` 함수에서 지정한 값에 따른 종료코드가 저장된다. +4. `exit_mm()` 함수를 호출해 프로세스의 mm_struct를 반환해 자원 해제한다. +5. `exit_sem()` 함수를 호출해 프로세스의 세마포어를 반환해 대기 상태를 해제한다. +6. `exit_files()`, `exit_fs()` 함수를 호출해 file descriptor 및 file system의 참조 횟수를 하나 감소한다. 참조 횟수가 0이면 해당 객체를 사용하는 프로세스가 없다는 의미이므로 자원 해제한다. +7. `exit_notify()` 함수를 호출해 부모 프로세스에 signal을 보낸다. 이때 해당 프로세스가 자식 프로세스를 가지고 있었다면, 자신의 부모 프로세스 or 자신이 속한 스레드 group의 다른 스레드 or init 프로세스 중 하나를 부모로 설정한다. +8. current의 state을 `TASK_DEAD`로 설정해 좀비 프로세스로 만든다. + +- 부모 프로세스의 동작은 다음과 같다. +1. `release_task()` 함수를 호출해 더는 자식 좀비 프로세스가 필요없다고 커널에게 signal을 보낸다. `release_task()` -> `__exit_signal()` -> `__unhashed_process()` -> `detach_pid()` +2. `__exit_signal()`에서 좀비 프로세스의 남은 정보도 완전히 메모리 반환한다. + `release_task()`는 `put_task_struct()` 함수를 호출해 좀비 프로세스의 `stack`, `thread_info` 구조체, `task_struct` 구조체가 들어있던 페이지 및 slab cache를 반환한다. 이제 프로세스와 연관된 모든 자원이 해제돼 완전히 종료됐다. + +- 부모 프로세스가 좀비가 된 자식 프로세스를 책임지고 종료하지 못할 때 리눅스의 유명한 문제인 ‘좀비 프로세스 문제’가 발생한다. + - 시스템 메모리를 낭비하는 문제가 발생하는 것이다. + - 따라서, 위 과정 중 8번에서 다뤘듯, 부모 프로세스가 없을 때 다른 부모 프로세스 후보들 중 하나를 선택해 부모로 설정해주는 과정이 반드시 필요하다. + + 1. `do_exit()` 함수에서 `exit_notify()` 함수를 호출한다. + 2. `exit_notify()` 함수에서 `forget_original_parent()` 함수를 호출한다. + `forget_original_parent()` 함수는 종료할 프로세스의 부모 프로세스를 반환하는 함수다. + - 이때, 부모 프로세스가 먼저 종료된 ‘문제 좀비 프로세스’인 경우, 적당한 부모 프로세스를 선택해주는 역할도 함께 한다.종료 프로세스가 속한 스레드 group 내에서 다른 스레드를 찾는다. 찾았다면, 해당 스레드를 부모로 만들고 반환한다. + - 만일 다른 스레드가 없다면, init 프로세스를 찾고 init 프로세스를 부모로 만들어서 반환한다. + + 4. 부모 프로세스를 찾았으니 종료할 프로세스의 모든 자식 프로세스의 부모로 설정한다. + +- 이로써, 좀비 프로세스를 적절히 종료하지 못해 발생하는 문제를 미연에 방지할 수 있다. + + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\354\212\244\354\274\200\354\244\204\353\237\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\354\212\244\354\274\200\354\244\204\353\237\254.md" new file mode 100644 index 00000000..f30b622d --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\354\212\244\354\274\200\354\244\204\353\237\254.md" @@ -0,0 +1,494 @@ +--- +title: '프로세스 스케줄러' +lastUpdated: '2024-03-02' +--- + +## 1. 정의 및 역사 + +- 스케줄러는 어떤 프로세스를 어떤 순서로 얼마나 오랫동안 실행할 것인지 정책에 따라 결정한다. +- 스케줄러는 시스템의 최대 사용률을 끌어내 사용자에게 여러 프로세스가 동시에 실행되고 있는 듯한 느낌을 제공해야 한다. +- 스케줄러는 비선점형 스케줄러와 선점형 스케줄러로 나뉜다. + - 선점형 스케줄러는 일정한 timeslice 동안 전적으로 프로세서 자원을 사용할 수 있고, 시간이 지나면 다음으로 우선순위가 높은 프로세스에 선점된다. +- 1991년 리눅스 첫 버전부터 2.4 버전까지는 단순한 스케줄러를 제공했다. + - 2.5 버전부터 대대적인 스케줄러 개선작업을 통해 O(1) 스케줄러라는 이름의 새로운 스케줄러를 구현했다. Timeslice 동적 계산이 O(1)에 수행되며 프로세서마다 별도의 wait queue를 구현해 성능향상을 이뤄냈다. + - 그러나 O(1) 스케줄러는 서버 시스템에는 이상적이었지만, 대화형 서비스를 제공하는 데스크톱 시스템에서는 성능이 안 좋았다. + 그래서 2.6.23 버전부터 **CFS**(Completely Fair Scheduler)라는 새로운 스케줄러를 도입했다. + +## 2. 스케줄러 구성요소 + +### I/O 중심 프로세스 vs 프로세서 중심 프로세스 + - I/O 중심 프로세스: I/O 요청 후 기다리는 데 대부분의 시간을 사용하는 프로세스 (i.e. 대부분의 GUI 애플리케이션은 사용자의 키보드, 마우스 입력을 기다림) + - 프로세서 중심 프로세스: 선점될 때까지 대부분의 시간을 코드를 실행하는 데 사용하는 프로세스. 더 긴 시간동안 덜 자주 실행하도록 스케줄링 해야 한다. (i.e. ssh-keygen 등) + +### 스케줄링 정책 +- 정책(Policy)은 스케줄러가 무엇을, 언제 실행할 것인지를 정하는 것을 의미한다. +- 정책은 두 가지 목적을 갖고 있다. + - 프로세스 응답시간(latency)을 빠르게 하기 + - 시스템 사용률(Throughput)을 최대화 하기 +- 정책은 낮은 우선순위 프로세스는 최대한 공정하게, 높은 우선순위 프로세스는 최대한 빠르게 선택해서 실행할 책임이 있다. + +### 우선순위 +- 일반적으로 선점형 우선순위 스케줄링은 + - 우선순위가 높은 프로세스가 낮은 프로세스를 선점해 먼저 실행하고 + - 우선순위가 같은 프로세스 끼리는 round-robin으로 돌아가며 실행한다. +- 리눅스 커널 프로세스 스케줄링은 두 가지 별개의 우선순위 단위를 갖고 있다. + - **Nice** + - 20~+19 사이의 값을 가지며 값이 클수록 우선순위가 낮다. + - Timeslice의 비율을 조절할 때도 사용된다. + - **실시간 우선순위** + - 0~99 사이의 값을 가지며 값이 클수록 우선순위가 크다. + - 모든 real-time(실시간) 프로세스는 일반 프로세스보다 우선순위가 높다. + +### Timeslice +- Timeslice는 선점 당하기 전까지 프로세스가 작업을 얼마나 오랫동안 실행할 수 있는지를 의미한다. + - 너무 길면 대화형 프로세스의 성능이 떨어진다. + - 너무 짧으면 빈번한 context-switching으로 인해 전체 시스템의 성능이 떨어진다. +- CFS는 프로세스별로 timeslice를 설정하지 않고 프로세스별로 프로세서 할당 ‘비율’을 지정한다. + - 그래서 프로세스에 할당되는 CPU time은 시스템의 부하와 nice값에 대한 함수로 O(1)로 결정된다. +- CFS는 모든 프로세스가 공정하게 골고루 실행됨을 보장하기 위해 새 프로세스가 현재 실행 중인 프로세스보다 낮은 비율의 CPU time을 사용했다면, 현재 프로세스를 선점하고 즉시 실행한다. + +### 예시 + +간단한 예시를 통해 일반적인 스케줄러 정책의 동작과 CFS의 동작을 알아보자. + +문서 편집기(A, I/O 중심)와 동영상 인코더(B, 프로세서 중심) 두 가지 프로세스가 있다. + +- 일반적으로 A가 더 우선순위가 높고 더 많은 CPU time을 할당한다. + - 작업량이 많아서가 아니라, 필요한 순간에 항상 CPU time을 얻기 위해서 + - 사용자가 키보드를 눌러 A가 깨어날 때 B를 선점해야 더 좋은 대화형 성능을 보장할 수 있다. + - B가 실행시간 제약이 없기 때문이다. (지금 실행되든 0.5초 뒤에 실행되는 critical 하지 않음) + +- 리눅스의 CFS는 위와 조금 다르게 동작한다. + - A는 일정 비율(B와 같은 nice 값을 가진다면 50%)의 CPU time을 보장받는다. + - A는 사용자 입력을 기다리느라 할당받은 50%의 CPU time을 대부분 사용하지 못한다. 하지만, B는 할당받은 50%의 CPU time을 전부 활용한다. + - 사용자가 키보드를 눌러 A가 깨어날 때, CFS는 A가 아주 적은 CPU time만 사용했다고 알아차린다. + - A가 B보다 CPU time 비율이 적으므로 B를 선점하도록 한 뒤 사용자 입력을 빠르게 처리하고 다시 대기모드로 들어간다. 나머지 시간은 B가 온전히 사용할 수 있다. +​ +## 3. 리눅스 스케줄링 알고리즘 + +### 스케줄러 클래스 + - 리눅스 스케줄러는 모듈화 돼있어 각 프로세스를 각기 다른 알고리즘으로 스케줄링 할 수 있다. + - 이러한 형태를 ‘스케줄러 클래스’라고 말하며 각 클래스에는 우선순위가 있다. + - ``에는 기본 스케줄러가 구현되어있다. + - CFS는 리눅스의 일반 프로세스용 스케줄러 클래스이며 ``에서 구현되어있다. + +### UNIX의 프로세스 스케줄링 + - 전통적인 유닉스의 프로세스 스케줄링 방법에 대해서 알아보자. + - 앞서 말했듯, 유닉스는 nice 값을 기반으로 우선순위를 결정하고 정해진 timeslice 동안 프로세스를 실행한다. 높은 우선순위 프로세스스는 더 자주 실행되니 더 긴 timeslice를 할당받을 것이다. + - 하지만, 이 방법에는 몇 가지 문제가 있다. + +1. **Context-switching 최적화가 어렵다.** + - Nice에 timeslice를 대응하기 위해 각 nice 값에 할당할 timeslice의 절대값을 정해야 한다. (i.e. nice값 0 = timeslice 100ms, +19 = timeslice 5ms) + - 어떤 두 프로세스가 있다. + - Nice 0인 프로세스 + Nice 19인 프로세스: 전자가 100ms 수행된 뒤 후자가 선점해 5ms를 수행하므로 105ms에 context swtiching이 2회 발생한다. + - Nice 19인 프로세스 2개: 10ms에 context switching이 2회 발생한다. + - Nice 0인 프로세스 2개: 200ms에 context switching이 2회 발생한다. + - 잦은 context switching이 발생하고 우선순위가 낮은 프로세스는 잘 실행되지 않는다. + +2. **상대적인 nice값 차이로 문제가 발생한다.** + - Nice 0인 프로세스는 100ms, Nice 1인 프로세스는 95ms를 할당받는다고 가정하자. + - 두 프로세스의 timeslice 차이는 겨우 5%로, 큰 차이가 없다. + - Nice 18인 프로세스는 10ms, Nice 19인 프로세스는 5ms를 할당받는다고 가정하자. + - 두 프로세스의 timeslice 차이는 무려 50%로 굉장한 차이가 발생한다. + - 즉, ‘nice 값을 1 증가하거나 낮추는 행위’는 기존 nice값에 따라 의미가 달라지게 된다! + +3. **아키텍처에 의존적이다.** + - Nice값에 따라 timeslice의 절대값을 할당하기 위해 시스템 클럭의 일정 배수로 timeslice를 설정해야 한다. + - 시스템의 프로세서 아키텍처에 따라 1-tick은 가변적이므로 timeslice 또한 영향을 받는다. + - 즉, nice값 1 차이가 1ms 차이일 수도, 10ms 차이일 수도 있다는 문제가 있다. + + **여러 복잡한 문제를 해결할 수 없다.** + - 대화형 프로세스의 반응성을 개선하기 위해서는 사용자의 키 입력에 대한 인터럽트 발생 시 바로바로 반응할 수 있도록 우선순위를 높여 sleep-wake 과정 속도를 증가해야 한다. + - 하지만, 한 프로세스만 불공정하게 CPU time을 할당할 수 밖에 없는 방법론적인 허점이 존재한다. + - 이러한 문제는 UNIX의 스케줄링 방법이 선형적이고 단순하기 때문에 발생한다. +​ +### 공정(Fair) 스케줄링 (CFS) + +- CFS는 wait 중인 n개의 프로세스 각각에 1/n 비율의 CPU time을 할당해 모두 동일한 시간 동안 실행된다. + - CFS는 실행 가능한 전체 프로세스 n개와 nice값에 대한 함수를 이용해 개별 프로세스가 얼마 동안 실행할 수 있는지 동적으로 계산한다. 이때, nice값은 CPU time 비율의 가중치로 사용된다. + - Nice값이 높을수록(우선순위가 낮을수록) 프로세스는 낮은 가중치를 받아 낮은 비율을 할당받는다. + +- **목표 응답시간(Targeted Latency)**: CFS가 설정한 응답시간의 목표치이며, 실행이 완료된 프로세스가 다음 번에 자신의 순서가 돌아오기까지 기다려야 하는 최대 시간을 의미한다. + - 낮을수록 반응성이 좋아져 완전 멀티태스킹에 가까워진다. + - 낮을수록 context switching 비용은 높아져 전체 시스템 성능은 낮아진다. + - 목표 응답시간 20ms인 시스템에 + - 우선순위 동일 프로세스 2개 있다면? 각각 20 ÷ 2 = 10ms씩 실행된다. + - 우선순위 동일 프로세스 5개 있다면? 각각 20 ÷ 5 = 4ms씩 실행된다. + - 우선순위 동일 프로세스 20개 있다면? 각각 20 ÷ 20 = 1ms씩 실행된다. + +- **최소 세밀도(Minimum Granularity)**: 각 프로세스에 할당되는 CPU time의 최소값을 의미한다. + - 프로세스 개수가 늘어나면, 각 프로세스에 할당되는 CPU time은 점점 0에 수렴한다. + - Context switching이 전체 수행시간에서 큰 비율을 차지하게 되므로 최소치를 정해놓아야 한다 + - 기본값은 1ms다. + +- CFS에선, 오로지 nice값의 상대적인 차이만이 각 프로세스의 timeslice에 영향을 준다. + - 앞서 UNIX 스케줄링의 문제점 1번에서 언급했지만, nice값과 timeslice가 선형관계일 때, context switching이 언제(10ms? 105ms? 200ms?) 발생할지 예측할 수 없다는 문제가 있었다. + - 목표 응답시간 20ms인 시스템에 두 프로세서 A, B가 있을 때, + - A(nice: 0), B(nice: 5) 이라면, A는 15ms, B는 5ms를 할당받는다. + - A(nice: 10), B(nice: 15) 이라면, 똑같이 A는 15ms, B는 5ms를 할당받는다 + - Nice값의 절대값이 CFS의 결정에 영향을 미치지 않는것에 집중하자. + +- CFS는 프로세스 개수가 많이 늘어나서 최소 세밀도 이하로 내려갈 경우에는 공정성을 보장하지 못한다. + - 완전히 공정하진 않지만, 각 프로세스에 공정한 CPU time 비율을 나눠준다는 점에서 ‘Fair’하다. + - 최소한 목표 응답시간 n에 대해 n개 프로세스 까지는 공정성을 보장할 수 있다. + +## 4. CFS 구현 + +### 시간 기록 (Time Accounting) + + - 모든 프로세스 스케줄러는 각 프로세스의 실행시간(the time that a process runs)을 기록해야 한다. + - 시스템 클럭 1-tick이 지날 때마다 이 값은 1씩 감소하며, 0이 될 때 다른 프로세스에 의해 선점된다. + - 각 프로세스(task)의 스케줄러 관련 정보는 `task_struct` 내에 `sched_entity` 구조체 타입 se 멤버변수에 저장된다. `sched_entity` 구조체 내부는 아래와 같이 구성되어있다. + + ```c + struct sched_entity { + /* For load-balancing: */ + struct load_weight load; + struct rb_node run_node; + u64 deadline; + u64 min_deadline; + + struct list_head group_node; + unsigned int on_rq; + + u64 exec_start; + u64 sum_exec_runtime; + u64 prev_sum_exec_runtime; + // 가상실행시간, 프로세스가 실행한 시간을 정규화한 값이며 CFS는 실행 대기 프로세스 중 가상실행시간이 가장 낮은 프로세스를 다음 실행 프로세스로 선택한다. + u64 vruntime; + ... + } + ``` + +- 프로세스의 실행시간은 `vruntime` 멤버변수에 저장된다. +- 이 변수의 갱신은 `kernel/sched_fair.c` 소스코드 내 `uptate_curr()` 함수에서 담당한다. + - 이 함수는 시스템 타이머에 의해 주기적으로 호출된다. + - now - `curr->exec_start`로 이전에 기록된 시간으로부터 현재 얼마나 지났는지 차이를 계산해 delta_exec에 저장한다. + - vruntime을 갱신하기 위해 `__update_curr()` 함수를 호출한다. +- `__update_curr()` 함수에서는 `calc_delta_fair()` 함수를 호출해 현재 실행 중인 프로세스 개수를 고려해 가중치를 계산한 뒤 vruntime을 갱신한다. +- 위와 같은 방식으로 vruntime 값은 특정 프로세스의 실행시간을 정확하게 반영한다. + + ```c + static void update_curr(struct cfs_rq *cfs_rq) { // 현재 함수의 실행시간을 계산에 delta_exec에 저장 + struct sched_entity *curr = cfs_rq->curr; + u64 now = rq_of(cfs_rq)->clock; + unsigned long delta_exec; + + if (unlikely(!curr)) + return; + + /* + * Get the amount of time the current task was running + * since the last time we changed load (this cannot + * overflow on 32 bits): + * 지난번 갱신 시점 이후 현재 작업이 실행된 시간을 구한다. (32bit를 넘을 수 없음) + * + */ + delta_exec = (unsigned long)(now - curr->exec_start); + if (!delta_exec) + return; + + /* + * 계산된 실행값을 __update_curr함수에 전달한다, + * __update_curr함수는 전체 실행중인 프로세스 갯수를 고려해 가중치를 계산한다. + * 이 가중치 값을 추가하여 현재 프로세스의 vruntime에 저장한다. + */ + __update_curr(cfs_rq, curr, delta_exec); + curr->exec_start = now; + + if (entity_is_task(curr)) { + struct task_struct *curtask = task_of(curr); + + trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime); + cpuacct_charge(curtask, delta_exec); + account_group_exec_runtime(curtask, delta_exec); + } + } + ``` + + ```c + /* 현재 작업의 실행시간 통계를 갱신한다. 해당 스케줄링 클래스에 속하지 않는 작업은 무시한다. + * 시스템 타이머를 통해 주기적으로 실행되며, 프로세스가 실행 가능 상태로 바뀌거나 대기상태가 되어 실행이 중단되어도 + * 호출된다. + */ + static inline void __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr, + unsigned long delta_exec) + { + unsigned long delta_exec_weighted; + + schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max)); + + curr->sum_exec_runtime += delta_exec; + schedstat_add(cfs_rq, exec_clock, delta_exec); + delta_exec_weighted = calc_delta_fair(delta_exec, curr); + + curr->vruntime += delta_exec_weighted; + update_min_vruntime(cfs_rq); + } + ``` + +### 프로세스 선택 + +- CFS는 다음번에 실행될 프로세스를 결정할 때 ‘가장 낮은 비율로 CPU time을 실행한’ 프로세스로 결정한다, 즉 가장 낮은 vruntime을 가진 프로세스를 선택한다. +- CFS의 핵심은 매 context switching 시 실행 가능한 프로세스 중 가장 낮은 vruntime을 가진 프로세스를 찾아 선택하는 것이다. +- 빠른 탐색을 위해 self-balancing BST로 유명한 ​‘Red-Black Tree’ 자료구조를 사용​한다. + +image + +- 따라서 다음 작업을 선택할 때는 가장 왼쪽에 있는 node를 선택하면 된다. +```c +/** +* CFS가 다음에 실행해야 할 프로세스를 반환하는 함수 +*/ +static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq) + { + struct rb_node *left = cfs_rq->rb_leftmost; + # rb_leftmost 캐시된 가장 왼쪽 노드 포인터 + + if (!left) + return NULL; + + return rb_entry(left, struct sched_entity, run_node); + } +``` + +### 스케줄러 진입 위치 (Scheduler Entry Point) + +- 스케줄러의 main 함수는 `kernel/sched.c` 에 정의된 void `__sched schedule(void)` 함수다. +- 가장 우선순위가 높은 스케줄러 클래스의 가장 우선순위가 높은 프로세스를 찾아 다음 프로세스로 선택한다. +- `schedule()` 함수 내부에서 `pick_next_task()` 함수를 호출한다. + +```c +// https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/kernel/sched/core.c#L5983 +/* + * Pick up the highest-prio task: + */ +static inline struct task_struct * +__pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) +{ + const struct sched_class *class; + struct task_struct *p; + + /* + * Optimization: we know that if all tasks are in the fair class we can + * call that function directly, but only if the @prev task wasn't of a + * higher scheduling class, because otherwise those lose the + * opportunity to pull in more work from other CPUs. + */ + if (likely(!sched_class_above(prev->sched_class, &fair_sched_class) && + rq->nr_running == rq->cfs.h_nr_running)) { + + p = pick_next_task_fair(rq, prev, rf); + if (unlikely(p == RETRY_TASK)) + goto restart; + + /* Assume the next prioritized class is idle_sched_class */ + if (!p) { + put_prev_task(rq, prev); + p = pick_next_task_idle(rq); + } + + return p; + } + +restart: + put_prev_task_balance(rq, prev, rf); + + for_each_class(class) { + p = class->pick_next_task(rq); + if (p) + return p; + } + + BUG(); /* The idle class should always have a runnable task. */ +} +``` + +- if 문은 최적화를 위한 구문이다. + - 일반적으로 프로세스는 CFS를 스케줄러 클래스로 사용하는 경우가 많다. + - 즉, 현재 실행 중인 프로세스 개수와 CFS 스케줄러 클래스 사용 프로세스 개수가 동일할 가능성이 높다. + - 이런 경우에는 CFS 스케줄러 클래스 내부에 정의된 `pick_next_task()` 함수를 실행하도록 한다. + - 이 함수는 `kernel/sched_fair.c`에 `pick_next_task_fair()`에 정의되어있다. + - CFS의 `pick_next_task()`는 `pick_next_entity()`를 호출하고 이어서 `__pick_next_entity()`를 호출한다. + - for 문은 가장 우선순위가 높은 스케줄러 클래스의 가장 우선순위가 높은 프로세스를 찾는다. + - 가장 높은 우선순위부터 돌아가며 스케줄러 클래스 내 `pick_next_task()` 함수를 호출한다. + +### Sleeping and Waking Up + +- 프로세스가 sleep 또는 block 상태로 들어간 데에는 여러 가지 이유가 있지만, 커널 동작은 같다. + 1. 프로세스는 자신의 state가 ‘대기 상태’(`TASK_(UN)INTERRUPTIBLE`)임을 표시한다. + 2. CFS 스케줄러 클래스의 RBTree에서 자기 자신을 제거한다. + 3. `schedule()` 함수를 호출해 새 프로세스를 선택해 실행한다. + +- 이러한 프로세스들은 ‘Wait Queue(대기열)’에 들어가서 특정 조건이 발생하기를 기다린다. + +```c +// https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/include/linux/wait.h#L40 +struct wait_queue_head { + spinlock_t lock; + struct list_head head; +}; +typedef struct wait_queue_head wait_queue_head_t; +``` + +- 대기열은 `` 헤더파일에 `wait_queue_head_t` 구조체로 표현한다. + - 대기열은 `DECLARE_WAITQUEUE()` 매크로를 이용해 정적으로 만들 수 있다. + +```c +// https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/include/linux/wait.h#L48 +#define __WAITQUEUE_INITIALIZER(name, tsk) { \ + .private = tsk, \ + .func = default_wake_function, \ + .entry = { NULL, NULL } } + +#define DECLARE_WAITQUEUE(name, tsk) \ + struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk) + +``` + +- 대기열은 `init_waitqueue_head()` 함수를 이용해 동적으로 만들 수도 있다. + +```c +// https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/kernel/sched/wait.c#L8 +#define init_waitqueue_head(wq_head) \ + do { \ + static struct lock_class_key __key; \ + \ + __init_waitqueue_head((wq_head), #wq_head, &__key); \ + } while (0) +``` + +- [주의] Sleep과 wake는 동일 프로세스에 대한 일종의 경쟁상태(race condition)를 유발할 위험이 있다. + - 특정 단발성 wake 조건을 기다리는 프로세스가 sleep에 들어가기 전에 해당 조건이 발생했다면? 해당 sleep 프로세스는 영원히 wake 할 일이 없을 것이다. + - 따라서 아래와 같은 과정으로 sleep-wake을 처리할 것을 권장한다. + + ```c + add_wait_queue(q, &wait); + while (!condition) { + prepare_to_wait(&q, &wait, TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + /* handle signal */ + } + schedule(); + } + finish_wait(&q, &wait); + ``` + + 1. `add_wait_queue()` 를 이용해 프로세스를 대기열에 추가한다. + + 2. `prepare_to_wait()` 함수는 프로세스의 state를 `TASK_INTERRUPTIBLE`로 변경한다. + + 3. `schedule()` 함수는 다른 프로세스가 먼저 실행되도록 해준다. + + 원하는 wake-up 조건이 발생했다면, while-loop를 빠져나와 state를 `TASK_RUNNING`으로 변경하고 `fininsh_wait()` 함수를 호출해 대기열에서 프로세스를 제거한다. + + - 위와 같은 구조에서는 sleep 전 wake-up condition이 먼저 발생하더라도 기능에만 문제가 생기게 되고, 해당 프로세스가 영원히 sleep 상태에 빠질 위험은 예방할 수 있다. + +### 5. Context switching + +- `schedule()` 함수는 다음에 실행될 프로세스를 결정한 뒤 ``에 정의된 `context_switch()` 함수를 호출한다. + - `context_switch()` 함수는 ``에 정의된 `switch_mm_irqs_off()` 함수를 호출한다. 이 함수는 CPU의 메모리 관련 레지스터를 masking 해서 가상메모리 매핑을 새로운 프로세스로 변경한다. + - `context_switch()` 함수는 ``에 정의된 `switch_to()` 함수를 호출한다. 이 함수는 인라인 어셈블리를 이용해서 현재 프로세스의 TCB(Task Control Block)를 저장하고, 다음 프로세스의 TCB를 복원한다. + +```c +// https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/kernel/sched/core.c#L5320 +/* + * context_switch - switch to the new MM and the new thread's register state. + */ +static __always_inline struct rq * +context_switch(struct rq *rq, struct task_struct *prev, + struct task_struct *next, struct rq_flags *rf) { + prepare_task_switch(rq, prev, next); + + /* + * For paravirt, this is coupled with an exit in switch_to to + * combine the page table reload and the switch backend into + * one hypercall. + */ + arch_start_context_switch(prev); + + /* + * kernel -> kernel lazy + transfer active + * user -> kernel lazy + mmgrab_lazy_tlb() active + * + * kernel -> user switch + mmdrop_lazy_tlb() active + * user -> user switch + * + * switch_mm_cid() needs to be updated if the barriers provided + * by context_switch() are modified. + */ + if (!next->mm) { // to kernel + enter_lazy_tlb(prev->active_mm, next); + + next->active_mm = prev->active_mm; + if (prev->mm) // from user + mmgrab_lazy_tlb(prev->active_mm); + else + prev->active_mm = NULL; + } else { // to user + membarrier_switch_mm(rq, prev->active_mm, next->mm); + /* + * sys_membarrier() requires an smp_mb() between setting + * rq->curr / membarrier_switch_mm() and returning to userspace. + * + * The below provides this either through switch_mm(), or in + * case 'prev->active_mm == next->mm' through + * finish_task_switch()'s mmdrop(). + */ + switch_mm_irqs_off(prev->active_mm, next->mm, next); + lru_gen_use_mm(next->mm); + + if (!prev->mm) { // from kernel + /* will mmdrop_lazy_tlb() in finish_task_switch(). */ + rq->prev_mm = prev->active_mm; + prev->active_mm = NULL; + } + } + + /* switch_mm_cid() requires the memory barriers above. */ + switch_mm_cid(rq, prev, next); + + prepare_lock_switch(rq, next, rf); + + /* Here we just switch the register state and the stack. */ + switch_to(prev, next, prev); + barrier(); + + return finish_task_switch(prev); +} +``` + +- 커널은 `need_resched` 플래그를 이용해서 언제 스케줄링이 필요한지 판단한다. +- 사용자 공간으로 돌아가거나, 인터럽트 처리를 마칠 때마다 커널은 `need_resched` 플래그를 확인한다. 설정되어있다면 `schedule()` 함수를 호출해 최대한 빨리 새 프로세스로 전환한다. +- `need_resched` 플래그는 각 프로세스의 task_struct 속 thread_info 속 flags 멤버변수에 포함되며 `TIF_NEED_RESCHED` 라는 이름으로 선언되어있다. + +```c +// https://github.com/torvalds/linux/blob/bee0e7762ad2c6025b9f5245c040fcc36ef2bde8/include/linux/sched.h#L2261 +static __always_inline bool need_resched(void) { + return unlikely(tif_need_resched()); +} +``` + +- need_resched 플래그는 각 프로세스의 task_struct 속 thread_info 속 flags 멤버변수에 포함되며 `TIF_NEED_RESCHED` 라는 이름으로 선언되어있다. + +### ​6. 선점 + +- 리눅스 커널은 2.6 버전 이상부터 사용자 공간 뿐만 아니라, 커널도 선점될 수 있는 ‘완전 선점형’이다. +- 실행 중인 작업이 lock 돼있지 않다면 커널은 언제나 선점될 수 있다. +- `thread_info` 구조체에는 `preemp_count` 라는 멤버변수가 있다. 이 변수는 프로세스가 lock을 설정 할 때마다 1 증가하고 lock을 해제할 때마다 1 감소한다. +- 즉, 해당 프로세스의 `preemp_count`가 0이면, 해당 프로세스는 선점 가능한 상태라고 판단한다. +- 정리하자면, 커널은 need_resched 플래그 활성화 + 현재 프로세스의 preemp_count == 0일 경우 더 우선순위가 높은 프로세스를 찾은 뒤 선점을 허용하고 `schedule() -> context_switch()` 함수를 호출한다. + +​### 7. 실시간 스케줄링 정책 + +- 리눅스 커널은 FIFO와 Round-robin 두 가지 실시간 스캐줄링 정책으로 soft 실시간성을 제공한다. +- 실시간 프로세스는 일반 프로세스보다 우선순위가 높아 항상 먼저 실행​된다. +- 그러므로, 더 높은 우선순위 실시간 프로세스가 없다면, 양보 없이 무한히 계속 실행될 수도 있다. +- 반면, Round-robin 정책(SCHED_RR)은 정해진 `timeslice` 만큼만 실행된 뒤, 우선순위가 같은 다른 실시간 프로세스를 돌아가며 실행한다. +- 리눅스의 실시간 우선순위는 `task_struct`의 `rt_priority` 멤버변수에 저장하며, `0 ~ MAX_RT_PRIO-1` 사이의 값을 가지며, 기본값은 0 ~ 99다. + + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 +- https://jaykos96.tistory.com/27 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\354\243\274\354\206\214\342\200\205\352\263\265\352\260\204.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\354\243\274\354\206\214\342\200\205\352\263\265\352\260\204.md" new file mode 100644 index 00000000..230d18b2 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Kernel/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\354\243\274\354\206\214\342\200\205\352\263\265\352\260\204.md" @@ -0,0 +1,152 @@ +--- +title: '프로세스 주소 공간' +lastUpdated: '2023-12-17' +--- + +# 15. 프로세스 주소 공간 + +- 커널은 사용자 공간 프로세스의 메모리도 관리해야 하며 이를 ‘프로세스 주소 공간’이라고 부른다. +- 프로세스는 유효한 메모리 영역에만 접근해야 하며, 이를 어길시 segment fault를 만날 것이다. + +## 15.1 메모리 서술자 구조체 mm_struct + +```c +struct mm_struct { + struct { + atomic_t mm_users; + /* + * Fields which are often written to are placed in a separate + * cache line. + */ + struct { + /** + * @mm_count: The number of references to &struct + * mm_struct (@mm_users count as 1). + * + * Use mmgrab()/mmdrop() to modify. When this drops to + * 0, the &struct mm_struct is freed. + */ + atomic_t mm_count; + } ____cacheline_aligned_in_smp; + + struct maple_tree mm_mt; + ... + } +}; +``` + +- ``에는 `mm_struct` 라는 메모리 서술자 구조체가 정의돼있다. + - `mm_users`: 이 주소 공간을 사용하는 프로세스의 개수를 의미한다. + - `mm_count`: 이 구조체의 주 참조 횟수다. + - 9개 스레드가 주소 공간을 공유한다면? `mm_users == 9, mm_count == 1` + - `mm_users == 0` -> mm_count를 하나 감소시킨다. + - `mm_count == 0` -> 이 주소 공간을 참조하는 놈이 하나도 없으니 메모리를 해제한다. + - mmap, mm_rb: 동일한 메모리 영역을 전자는 연결리스트로, 후자는 레드-블랙 트리로 나타낸 것이다. + - 왜 같은 대상을 중복 표현해서 메모리를 낭비하는 걸까? + - 메모리 낭비는 있겠지만, 얻을 수 있는 이점이 있기 때문이다. + - 전후 관계를 파악하거나 모든 항목을 탐색할 때는 연결리스트가 효율적이다. + - 특정 항목을 탐색할 때는 레드-블랙 트리가 효율적이다. + - 이런 방식으로 같은 데이터를 두 가지 다른 접근 방식으로 사용하는 것을 ‘스레드 트리’라고 부른다. + +- 이미 3장에서 `task_struct`를 배울 때 mm 멤버변수로 이 구조체를 봤었다. + - 복습하자면, `current->mm`은 현재 프로세스의 메모리 서술자를 뜻하며, + - `fork()` → `copy_mm()` 함수가 부모 프로세스의 메모리 서술자를 자식 프로세스로 복사하며, + - 복사할 때 12.4절에서 배운 ‘슬랩 캐시’를 이용해 `mm_cachep`에서 `mm_struct` 구조체를 할당한다. + - 만일 만드는게 스레드라면, 생성된 스레드의 메모리 서술자는 부모의 mm을 가리킬 것이다. + - 그리고 커널 스레드라면, 당연히 프로세스 주소 공간이 없으므로 mm == NULL이다. + - (+ 추가내용: 커널 스레드가 종종 프로세스 주소 공간의 페이지 테이블 일부 데이터가 필요한 경우가 있다. 메모리 서술자의 mm == NULL일 때, active_mm 항목은 이전 프로세스의 메모리 서술자가 가리키던 곳으로 갱신된다. 따라서 커널 스레드는 이전 프로세스의 페이지 테이블을 필요할 때 사용할 수 있다.) + +## 15.2 가상 메모리 영역 구조체 vm_area_struct + +- 리눅스 커널에서 ‘가상 메모리 영역’은 VMA라고 줄여 부르며 ``의 `vm_area_struct` 구조체로 메모리 영역을 표현한다. + +```c +// https://github.com/torvalds/linux/blob/f2e8a57ee9036c7d5443382b6c3c09b51a92ec7e/include/linux/mm_types.h#L616 +struct vm_area_struct { + /* The first cache line has the info for VMA tree walking. */ + + union { + struct { + /* VMA covers [vm_start; vm_end) addresses within mm */ + unsigned long vm_start; + unsigned long vm_end; + }; +#ifdef CONFIG_PER_VMA_LOCK + struct rcu_head vm_rcu; /* Used for deferred freeing. */ +#endif + }; + + struct mm_struct *vm_mm; /* The address space we belong to. */ + pgprot_t vm_page_prot; /* Access permissions of this VMA. */ + + /* + * Flags, see mm.h. + * To modify use vm_flags_{init|reset|set|clear|mod} functions. + */ + union { + const vm_flags_t vm_flags; + vm_flags_t __private __vm_flags; + }; + + const struct vm_operations_struct *vm_ops; + ... +} __randomize_layout; +``` + +- 주요 멤버 변수를 살펴보면 아래와 같다. + - `vm_start`, `vm_end`: 가상 메모리 영역의 시작주소와 마지막 주소를 의미하므로 이 둘의 차이가 메모리 영역의 바이트 길이가 된다. 다른 메모리 영역끼리는 중첩될 수 없다. + - `vm_mm`: VMA 별로 고유한 mm_struct를 보유한다. 동일 파일을 별도의 프로세스들이 각자의 주소 공간에 할당할 경우 각자의 vm_area_struct를 통해 메모리 공간을 식별하게 된다. + - `vm_flags`: 메모리 영역 내 페이지에 대한 정보(읽기, 쓰기, 실행 권한 정보 등)를 제공한다. + - `vm_ops`: 메모리 영역을 조작하기 위해 커널이 호출할 수 있는 동작 구조체 vm_operations_struct를 가리킨다. (13절 VFS를 설명할 때 언급했던 ‘동작 객체’ 구조체와 비슷한 개념이다.) + +## 15.3. 실제 메모리 영역 살펴보기 +- 간단한 프로그램을 만들고, ‘/proc’ 파일시스템과 pmap 유틸리티를 통해 특정 프로세스의 주소 공간과 메모리 영역을 살펴보자. + +```bash +[ec2-user@ip-x-x-x-x ~]$ echo -e "int main(int argc, char *argv[]) { while(1); }" > test.c +[ec2-user@ip-x-x-x-x ~]$ gcc -o test test.c && ./test & +[1] 1024914 +[ec2-user@ip-x-x-x-x ~]$ cat /proc/1024914/maps +55ef85f2b000-55ef85f5a000 r--p 00000000 103:01 2253 /usr/bin/bash +55ef86c8c000-55ef86dad000 rw-p 00000000 00:00 0 [heap] +7ffb87000000-7ffb94530000 r--p 00000000 103:01 8524092 /usr/lib/locale/locale-archive +7ffb94600000-7ffb94ed4000 r--s 00000000 103:01 9692403 /var/lib/sss/mc/passwd +7ffb94fab000-7ffb95000000 r--p 00000000 103:01 443 /usr/lib/locale/C.utf8/LC_CTYPE +7ffb95000000-7ffb95028000 r--p 00000000 103:01 8524744 /usr/lib64/libc.so.6 +7ffb95230000-7ffb95231000 r--p 00000000 103:01 1600 /usr/lib/locale/C.utf8/LC_NUMERIC +7ffb95231000-7ffb95232000 r--p 00000000 103:01 1603 /usr/lib/locale/C.utf8/LC_TIME +7ffb95232000-7ffb95233000 r--p 00000000 103:01 442 /usr/lib/locale/C.utf8/LC_COLLATE +7ffb95233000-7ffb95234000 r--p 00000000 103:01 446 /usr/lib/locale/C.utf8/LC_MONETARY +7ffb95234000-7ffb95235000 r--p 00000000 103:01 8524051 /usr/lib/locale/C.utf8/LC_MESSAGES/SYS_LC_MESSAGES +7ffb95235000-7ffb95236000 r--p 00000000 103:01 1601 /usr/lib/locale/C.utf8/LC_PAPER +7ffb95236000-7ffb95237000 r--p 00000000 103:01 447 /usr/lib/locale/C.utf8/LC_NAME +7ffb95237000-7ffb9523e000 r--s 00000000 103:01 2799 /usr/lib64/gconv/gconv-modules.cache +7ffb9523e000-7ffb95240000 r--p 00000000 103:01 9455124 /usr/lib64/libnss_sss.so.2 +7ffb9524e000-7ffb9525c000 r--p 00000000 103:01 8522848 /usr/lib64/libtinfo.so.6.2 +7ffb95283000-7ffb95285000 r--p 00000000 103:01 8524740 /usr/lib64/ld-linux-x86-64.so.2 +7ffed54c8000-7ffed54e9000 rw-p 00000000 00:00 0 [stack] +7ffed55e0000-7ffed55e4000 r--p 00000000 00:00 0 [vvar] +7ffed55e4000-7ffed55e6000 r-xp 00000000 00:00 0 [vdso] +ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] +``` +- `/proc//maps` 파일은 프로세스 주소 공간의 메모리 영역을 출력해준다. +- pmap 유틸리티를 사용하면 위 정보를 조금 더 가독성 있게 표현해준다. +- 지금까지 다룬 구조체의 구조를 깔끔하게 도식화한 그림이다. + + image + +- `task_struct`의 mm은 각 프로세스의 메모리 서술자인 `mm_struct`이다. +- `mm_struct`의 mmap은 가상 메모리 영역 `vm_area_struct`을 표현하는 연결리스트다. +- `vm_area_struct`는 프로세스의 실제 메모리 영역(.txt, .data 등)을 나타낸다. + +image + +- 알다시피, 커널과 애플리케이션은 가상 주소를 사용하지만, 프로세서는 물리 주소를 사용한다. + - 따라서 프로세서와 애플리케이션이 서로 상호작용하기 위해서는 페이지 테이블을 통해 변환작업이 필요하다. +- 리눅스 커널은 PGD(Global), PMD(Middle), PTE 세 단계의 페이지 테이블을 사용한다. +- 페이지 테이블 구조는 아키텍처에 따라 상당히 다르며 ``에 정의돼있다. + +--- +참고 +- https://product.kyobobook.co.kr/detail/S000000935348 +- https://showx123.tistory.com/92 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205Package.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205Package.md" new file mode 100644 index 00000000..889e32f2 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205Package.md" @@ -0,0 +1,86 @@ +--- +title: 'Linux Package' +lastUpdated: '2024-03-02' +--- + +- 리눅스 패키지(Linux Package)란 리눅스 시스템에서 소프트웨어를 실행하는데 필요한 파일들(실행 파일, 설정 파일, 라이브러리 등)이 담겨 있는 설치 파일 묶음이다. + +- 패키지는 종류는 소스 패키지(Source Package)와 바이너리 패키지(Binary Package)가 있다. + +- 소스 패키지(Source Package)는 말 그대로 소스 코드(C언어..등)가 들어 있는 패키지로 컴파일 과정(configure,make,make install 명령어)을 통해 바이너리 파일로 만들어야 실행할 수 있다. 즉, 소스 패키지는 설치할 때 컴파일 작업도 진행되므로 설치 시간이 길고 컴파일 작업 과정에서 오류가 발생할 수 있다. + +- 바이너리 패키지(Binary Package)는 성공적으로 컴파일된 바이너리 파일이 들어있는 패키지이다. 이미 컴파일이 되어 있으니 소스 패키지에 비해 설치 시간도 짧고 오류가 발생할 가능성도 적다. 따라서 리눅스의 기본 설치 패키지들은 대부분 바이너리 패키지이다. + +--- + +리눅스 배포판에 따라서 서로 다른 패키지 형식을 지원하는데, 대부분 다음의 3가지 중 하나를 지원한다. + +- Debian 계열 (Debian, Ubuntu 등): `.deb` 파일 +- RedHat 계열 (RedHat, Fedora, CentOS): `.rpm` 파일 +- openSUSE 계열: openSUSE를 위해 빌드된 `.rpm` 파일 + +패키지 관리 도구는 저수준 툴과 고수준 툴이 있다. + +- 저수준 툴(low-level tools): 실제 패키지의 설치, 업데이트, 삭제 등을 수행 +- 고수준 툴(high-level toos): 의존성의 해결, 패키지 검색 등의 기능을 제공 + +`dpkg`, `rpm`과 같은 저수준 툴은 어떤 프로그램을 깔 때 그 프로그램을 돌리기 위해서 필요한 다른 프로그램 (종속된 프로그램)을 자동으로 깔아주지 않는다. 반면 `apt-get`, `yum`과 같은 고수준 툴은 종속된 프로그램을 알아서 깔아준다. + +아래 표는 리눅스 배포판 별 저수준/고수준 패키지 관리 도구이다. + +|구분|저수준 툴|고수준 툴| +|-|-|-| +|Debian 계열(ubuntu, debian)|dpkg|apt-get / apt| +|RedHat 계열(centos, redhat, fedora 등)|rpm|yum| +|openSUSE|rpm|zypper| + +--- + +`/etc/apt/sources.list`에 저장소가 기록되어 있다. + +```bash +## Note, this file is written by cloud-init on first boot of an instance +## modifications made here will not survive a re-bundle. +## if you wish to make changes you can: +## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg +## or do the same in user-data +## b.) add sources in /etc/apt/sources.list.d +## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl + +# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to +# newer versions of the distribution. +deb http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu/ jammy main restricted +# deb-src http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu/ jammy main restricted + +## Major bug fix updates produced after the final release of the +## distribution. +deb http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu/ jammy-updates main restricted +# deb-src http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu/ jammy-updates main restricted + +## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu +## team. Also, please note that software in universe WILL NOT receive any +## review or updates from the Ubuntu security team. +deb http://ap-northeast-2.ec2.archive.ubuntu.com/ubuntu/ jammy universe +... +``` + +(누가 봐도 ec2) + +담겨있는 정보 종류와 각 의미는 아래와 같다. + +```bash +[deb or deb-src] [repository url] [distribution] [component] +``` + +- deb or deb-src: 바이너리 패키지 저장소(Binary Package Repositories)와 소스 패키지 저장소(Source Package Repositories) 중 어떤 저장소를 사용하는지를 의미 +- repository url: 해당 저장소의 주소를 의미 +- distribution: 릴리즈하는 리눅스 버전 이름을 의미 +- component: + - main(표준으로 제공되는 무료 오픈소스 소프트웨어), + - restricted(공식적으로 지원하는 사유(유료) 소프트웨어), + - universe(커뮤니티에 의해 유지되고 지원되는 오픈소스(무료) 소프트웨어), + - multiverse(공식적으로 지원되지 않는 사유(유료) 소프트웨어)를 의미 + +--- +참고 +- https://gamsungcoding.tistory.com/entry/Linux-%EB%A6%AC%EB%88%85%EC%8A%A4Linux-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\353\260\260\355\217\254\355\214\220.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\353\260\260\355\217\254\355\214\220.md" new file mode 100644 index 00000000..2ec5317f --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\353\260\260\355\217\254\355\214\220.md" @@ -0,0 +1,57 @@ +--- +title: 'Linux 배포판' +lastUpdated: '2024-03-02' +--- + +• **RedHat** + - RPM 기반으로 제작된 리눅스 배포판 + - 현재는 기업용 배포판으로 상업적으로 배포하고 있으며, 무료 버전으로는 페도라가 있다. + +• **Fedora** + - RPM 기반. 레드햇 계열 + - 레드햇의 지원을 받아 개발 및 유지보수가 이루어진다. + - 페도라의 업데이트 후에 문제점을 파악하여 레드햇 리눅스(RHEL)에 업데이트를 반영하는 방식으로 운영되고 있다. + +• **Debian** + - GNU의 후원을 받는 리눅스 배포판 + - 패키지 설치 및 업그레이드가 단순하다. (패키지 관리 – dpkg, apt) + +• **Ubuntu** + - 데비안 계열 + - 영국 회사인 캐노니컬의 지원을 받음 + - 유니티(Unity)라는 고유한 데스크톱 환경을 사용 + +• **Slackware** + - 초창기에 나온 배포판으로 현재까지 살아있는 가장 오래된 배포판이다. + - 패트릭 볼커딩에 의해 만들어짐. + - 구조가 간결하지만 설치과정이 어렵고 패키지 관리가 어려워 많이 사용되지는 않는다. + +• **openSUSE** + - 대표 기능으로 YaST 유틸리티가 있다. + - 여러가지 데스크탑 환경(KDE, GNOME 등)의 버전이 있으며, KDE 판이 가장 유명하다. + +• **Mandrake** + - RPM 기반. + - 단순함을 추구하며 다양한 데스크탑 환경을 제공한다. + +• **우리나라의 리눅스 배포판** + - SULinux, 안녕 리눅스, 아시아눅스 등 + +• **Chrome OS** + - 구글에서 개발한 리눅스 기반 OS + - 인터페이스는 웹 브라우저인 크롬과 비슷하다. + +--- +**리눅스 커널이 사용된 모바일 OS** + +• 안드로이드(Android) +- C/C++ 라이브러리들을 포함하는 오픈소스 플랫폼 +- 가상머신은 Java 가상머신이 아니라 구글에서 자체 개발한 달빅(Dalvik) 가상머신을 사용 + +• 바다(Bada) OS +- 삼성에서 개발한 리눅스 커널 기반의 OS +- 미고(MeeGo)와 리모(LiMo)가 통합된 타이젠(Tizen)과 통합되었다. + +• 타이젠(Tizen) OS +- 삼성과 인텔의 주축으로 개발된 오픈소스 OS이다. +- HTML5 기반으로 만들어졌으며, 자바스크립트, CSS와 같은 웹 표준을 지원 diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\353\266\200\355\214\205\342\200\205\352\263\274\354\240\225.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\353\266\200\355\214\205\342\200\205\352\263\274\354\240\225.md" new file mode 100644 index 00000000..42d1579d --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\353\266\200\355\214\205\342\200\205\352\263\274\354\240\225.md" @@ -0,0 +1,167 @@ +--- +title: 'Linux 부팅 과정' +lastUpdated: '2024-03-02' +--- + +## 1단계: 하드웨어 단계 + +### Power On + +- 부팅 직후 CPU는 리셋 벡터를 통해 펌웨어(BIOS, EFI)로 접근하고, 이곳에서 펌웨어 코드(BIOS Code)를 실행한다. + +- BIOS 프로그램은 전원공급과 함께 메모리의 특정번지(예:FFFF0H)에 자동 로드된다. + +- CPU는 전원공급과 함께 특정번지(예:FFFF0H)의 BIOS프로그램(명령들)을 자동실행 한다. + +- 초기부팅 직후 CPU가 처음으로 하게 될 일은 EIP(Extend Instruction Pointer-CPU가 할 일들, 즉 메모리에 등록되어 있는 Code의 주소를 가리키는 포인터)에 숨겨진 0xFFFFFFF0 주소로 점프하는 것이다. 이 주소는 펌웨어(BIOS, EFI)의 엔트리 포인트로 매핑되는 영역으로 리셋 벡터(Reset Vector)라고 한다. 이 영역은 전원을 켰을 때 항상 같은 자리에 있지만 real mode(초기 부팅 때 전체 메모리의 1MB 영역까지만 접근 가능하지 못함)에서는 접근할 수 없도록 숨겨져 있다. + +- 펌웨어는 시스템의 하드웨어 구성을 저장한다. + +### POST, BOOT SECTOR(MBR/VBR/EBR) + +- Power-on-self-test (POST): CPU, RAM, 제어장치, BIOS 코드 자체, 주변장치 등에 대한 검사 진행 + +- OS외 기존 가상화 확장, 보안 등에 대한 구성 확인 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/9fc938e1-f801-410a-9995-dd076d3afcd9) + +- MBR(Master Boot Record) + POST 과정이 완료되면, BIOS는 부팅 디바이스(하드디스크 등)를 검색하고, 해당 디바이스의 파티션 테이블을 검색한다. 파티션 되지 않은 장치의 시동 섹터는 VBR이 된다. 파티션 테이블을 찾은 경우, 해당 파티션의 첫 번째 블록(섹터 0) 512 bytes의 MBR(Master Boot Record-시동섹터)에서 부트로더 코드(Boot Loader Code=OS Loader)를 검색한다. 부트로더 코드를 찾으면 메모리에 로드시킨다. + + - 파티션 테이블(Partition Table): 4개 Primary partition 정보(시작~끝 블록, 크기 등) (64bytes) + - 부트 시그니처(Boot Signature): 부트로더 코드의 고유값(0x55AA) (2 bytes) + +- VBR(Volume Boot Record) + - 각 Primary partition의 첫 번째 블록(부트로더 코드와 부트시그니처를 포함할 수 있다) + - 파티션되지 않은 장치의 시동 섹터는 VBR이다. + +- EBR(Extended Boot Record) + - 각 Logical partition(하나의 파티션을 sub-divide 한 파티션 단위)의 첫 번째 블록(파티션 테이블, VBR 부트시그니처 포함) + - EBR의 파티션 테이블에는 모든 Logical partition이 링크되어 있다. + +## 2단계: 부트로더 단계 + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/9dc79b1d-bf10-4832-a60b-276461f55e13) + +BIOS가 디스크의 MBR에서 부트 로더를 찾고, 이 부트 로더가 GRUB 설정 파일을 찾으면, 부트로더는 약 3~5초 동안의 시간을 카운트다운한다. 이 시간 동안(기본 운영체제가 시작하기 전까지) 사용자는 아무 키나 눌러 부트 로더에 개입할 수 있게 된다. + +일반적으로 유저가 부트 메뉴에서 부트 로더에 개입해야 하는 이유는 다음과 같다. + +- 다른 런레벨로 시작해야 할 때(런레벨 오버라이드) +- 다른 커널을 선택해야 할 때 + (ex. RHEL의 새 커널이 yum을 통해 설치되면 이전 커널이 하나씩 유지된다. 새 커널이 시작하지 않을 때를 대비하는 것이다) +- 다른 운영체제를 선택해야 할 때 + (ex. 페도라와 RHEL가 함께 설치된 컴퓨터에서 RHEL이 올바로 동작하지 않을 때 페도라로 시동한 뒤 RHEL 파일 시스템을 마운트하여 문제 해결을 시도할 수 있다) +- 부트 옵션을 변경해야 할 때 + (ex. 커널 옵션 추가, 특정 구성 요소 하드웨어 지원 비활성화-일시적으로 USB 포트 비활성화 등-) + +### GRUB(GRand Unified Bootloader): Linux Loader + +- GRUB은 커널(kernel)이미지를 불러들이고 시스템 제어권을 커널에게 넘겨준다. + +- GRUB은 GRUB Stage1 > 1.5 > 2 의 세 단계를 거치는 부트로더로 현재는 GRUB2가 가장 보편적으로 사용된다. + - **GRUB Stage 1** + MBR 또는 VBR에 저장되어 있는 부트 이미지가 메모리에 로드되고 실행됨(core.img의 첫 번쩨 섹터 로드) + - **GRUB Stage 1.5** + MBR과 첫번째 파티션 사이에 있는 블록(a.k.a MBR gap)에 저장된 core.img가 메모리에 로드되고 실행됨. core.img의 configuration 파일과 파일시스템을 위한 드라이버를 로드한다. + - **GRUB Stage 2** + `/boot/grub` 파일 시스템에 직접 접근하여 커널(vmlinuz)의 압축을 풀어 메모리에 로드하고, 커널이 필요로 하는 모든 드라이버와 모듈, 파일시스템(ext2, ext3, ext4...)등이 담긴 RAM 디스크 파일(initrd.img)를 메모리에 로드한다. + +`*` 커널은 로드되기 이전 `/boot` 아래 압축된 파일 형태인 `vmlinux` 로 존재 > GRUB Stage 2에서 압축 풀고 로드 + +`*` 커널 컴파일(Kernel Compile)을 통해 GRUB을 통해 로드할 커널 버전을 고를 수 있음 + +`*` `grub>` 이라는 고유의 작은 셸을 사용할 수 있음(이 셸의 프롬프트를 통해 부팅 파라미터 및 부팅OS 등을 정의할 수 있음) + +## 3단계: 커널 단계(Loading the Kernel) + +부팅 2단계까지 지나며 현재 부트로더는 커널파일과 RAM디스크 파일을 메모리에 로드해놓은 상태이다. + +커널은 컴퓨터의 각종 하드웨어를 사용하는 데 필요한 드라이버와 모듈을 로드한다. 이 시점에서는 주로 하드웨어 실패를 찾아야 한다. 관련된 기능이 올바로 동작하지 않는 문제를 차단해야 하기 때문이다. + +**로드된 커널파일 실행** +- 로드된 커널파일 실행, 콘솔에 관련 정보 띄워줌 +- PCI bus 점검 및 감지된 주변장치(Peripheral) 확인 후 `/var/log/dmesg` 파일에 기록 +- 커널은 swapper 프로세스(PID 0)를 호출, swapper(PID 0)는 커널이 사용할 각 장치드라이브들을 초기화 +- Root file system (`"/"`)을 읽기 전용으로 마운트, 이 과정에서 마운트 실패시 "커널 패닉" 메시지 출력 +- 문제없이 커널이 실행되고 나면 언마운트 후 Root File System을 읽기+쓰기 모드로 리마운트 +- 이후 Init 프로세스(PID 1)를 호출 + +커널이 시작하면서 생성한 메시지들이 복사되는 곳은 커널 링 버퍼(Kernel Ring Buffer)라고 한다. 이곳에 커널 메시지가 저장되며, 버퍼가 모두 채워지면 오래된 순서부터 메시지가 삭제된다. 부팅 후 시스템 로그인하여 커널 메시지를 파일로 캡처하는 커맨드를 통해 커널 메시지 기록을 파일 형태로 남길 수 있다. + +커널 메시지는 구성 요소 즉, CPU나 메모리, 네트워크 카드, 하드 드라이브 등이 감지될 때 나타난다. + +```bash +dmesg > /tmp/kernel_msg.txt +less /tmp/kernel_msg.txt + +# systemd는 systemd 저널에 저장된다 +# 따라서 journalctl을 실행해야 부트 시부터 지금까지 쌓인 메시지 확인 가능 +``` + +## 4단계: INIT + +init 시스템은 SysV 및 Systemd로 구분된다 + +### sysV + +#### SysVinit 1. Configuration. the file /etc/inittab +- `/etc/inittab`의 초기 시스템 구성 파일을 읽어옴(Operation mode, 런레벨, 콘솔 등) + +#### SysVinit 2. Initialization. the file /etc/init.d/rc +- `/etc/init.d/rc.S`(debian) 명령 실행 +- (시스템 초기화-스왑영역 로드, 필요없는 파일 제거, 파일시스템 점검 및 마운트, 네트워크 활성화 등) + +#### SysVinit 3. Services. /etc/init.d 및 /etc/rcN.d 디렉토리들 +- 지정된 런레벨에 해당하는 스크립트 및 서비스 실행 +- `/etc/init.d` 의 실행 가능한 서비스들 모두 실행(cron, ssh, lpd 등등) +- 각 런레벨별로 실행할 서비스들은 `/etc/rcN.d` 에서 정의할 수 있음 (S01: 런레벨 1에서 활성화, K01:런레벨 1에서 비활성화) + +### systemd: BSD init + +- systemd는 대표적인 Ubuntu Linux의 Init System이다. +- Sysvinit에 비해 시작속도가 빠르고, 리눅스 시스템을 보조하는 풀타임 프로세스로 상주한다 +- Target 유닛을 사용하여 부팅, 서비스관리, 동기화 프로세스를 진행한다 +- System Unit : Any resource that system can operate/manage (ex. .service, .target, .device, .mount, .swap...) + +**Systemd Boot process** + +- systemd용 GRUB2 구성(GRUB_CMDLINE_LINUX="init=/lib/systemd/systemd" (이후 update-grub 실행) +- 첫 번째 `.target` 유닛 실행 (보통 `graphical.target`의 심볼릭 링크임) + +```bash +#첫 번째 .target 유닛 +[Unit] + +Description=yon boot target +Requires=multi-user.target +Wants=yonbar.service +After=multi-user.target rescue.service rescue.target +Requires = hard dependencies +Wants = soft dependencies (시작이 필요하지 않은) +After = 여기서 정의된 서비스들 실행 이후에 부팅할 것 +``` +- Requires = hard dependencies +- Wants = soft dependencies (시작이 필요하지 않은) +- After = 여기서 정의된 서비스들 실행 이후에 부팅할 것 + +## 정리 + +리눅스 시스템이 올바로 시동하려면 일련의 과정이 올바르게 수행되어야 한다. + +PC 아키텍처에 직접 설치된 리눅스 시스템은 다음 과정을 거쳐 시동한다. + +- 전원을 켬 +- 하드웨어를 시작함(BIOS 또는 UEFI 펌웨어에서) +- 부트 로더 위치 찾기 + 시작하기 +- 부트 로더에서 운영체제 고르기 +- 선택된 운영체제에 맞는 커널과 초기 RAM 디스크(initrd) 시작하기 +- 초기화 프로세스(init 또는 systemd) 시작 +- 선택된 런레벨 또는 타깃에 따라 서비스 시작 + +--- +참고 +- https://itragdoll.tistory.com/3 +- https://ocw.unican.es/ +- https://manybutfinite.com/post/how-computers-boot-up/ + diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\355\212\271\354\247\225\342\200\205\353\260\217\342\200\205\352\264\200\353\240\250\354\232\251\354\226\264.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\355\212\271\354\247\225\342\200\205\353\260\217\342\200\205\352\264\200\353\240\250\354\232\251\354\226\264.md" new file mode 100644 index 00000000..d101a86c --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Linux\342\200\205\355\212\271\354\247\225\342\200\205\353\260\217\342\200\205\352\264\200\353\240\250\354\232\251\354\226\264.md" @@ -0,0 +1,119 @@ +--- +title: 'Linux 특징 및 관련용어' +lastUpdated: '2024-03-02' +--- + +- 리눅스 토발즈가 커널 개발 / 커널: 소스 공개, 누구나 수정 및 패키징하여 자유롭게 배포 가능 + +1. GNU(GNU’s Not UNIX) + - 리처드 스톨만 + - 유닉스와의 호환 + 더 놓은 기능의 운영체제를 만들고자 했던 프로젝트 + - 자유로운 유닉스를 만들고자 함 / 자유로운 소프트웨어 사용 / 상업화 반대 + - GCC, BASH, EMACS + +2. FSF(Free Software Foundation) + * 자유 소프트웨어: 상업적 목적으로 사용 가능 / 소스 코드 임의로 개작 가능 / 소스 코드 수정 시 반드시 소스 코드 + 공개 + - 리처드 스톨만 + - 자유 소프트웨어의 생산, 보급, 발전을 목표로 만든 비영리 조직 + - 자유 소프트웨어 재단 / 무료나 공짜의 뜻X, ‘구속되지 않는다’는 관점에서의 자유 + +## 라이선스 + +1. GPL(General Public License) + - 소스코드 무료 수정 가능, 수정한 코드도 GPL 라이선스 적용 + - 카피 레프트 / 독점 소프트웨어와 결합 불가 + +2. LGPL + - 리처드 스톨만 + - GPL의 강한 카피레프트 조건, 수정한 코드 비공개 가능 + - 자유 소프트웨어 + 독점 소프트웨어에서 사용 가능 + +3. BSD(Berkeley Software Distribution) + - 공개 소프트웨어 라이선스 + - 아무나 맘대로 사용 가능, 수정 코드 비공개 가능 + - 소스코드 공개하지 않는 상용 소프트웨어에서도 사용 가능 + +4. 아파치(Apache) + - 누구나 자유롭게 소프트웨어 다운로드 가능 + - 부분 혹은 전체를 개인적 혹은 상업적 목적으로 이용 가능 + - 재배포 시에도 소스코드 혹은 수정한 소스코드를 포함하여 반드시 공개 의무X + - 재배포할 경우, 아파치 라이선스 2.0 포함 의무 / 아파치 소프트웨어 재단에서 개발된 소프트웨어임을 명확히 + 밝혀야 함 + +5. MPL(Mozilla Public License) + - 해당 라이선스 적용된 소스코드 수정 시에는 소스코드 공개 필수 + - 다른 코드와 소스코드 결합하여 만든 프로그램: MPL코드를 제외한 다른 소스코드 공개 의무X + +* 정리 + - 무료 이용, 배포 허용, 소스코드 취득 및 수정 가능: GPL, LGPL, BSD, Apache, MPL, MIT + - 2차 저작물 소스코드 공개 의무: GPL, LGPL, MPL / L로 끝나는 라이선스 + (BSD, Apache, MIT는 2차 저작물 소스코드 비공개 가능) + - 독점 소프트웨어와 결합 불가능: GPL (나머지는 전부 가능) + +## 특징 + +1. 다중 사용자, 다중 처리 시스템: 다수의 사용자들이 동시 접속하여 각각 다수의 응용프로그램 실행 가능 +2. 공개된 시스템: 커널 + 같이 내장되어 배포되는 대부분의 응용프로그램의 소스 공개 +3. 뛰어난 네트워크 환경 + - 이더넷, SLIP, PPP, ATM 등 다양한 네트워크 환경과 TCP/IP, IPX, AppleTalk 등 대부분의 네트워크 프로토콜 + 지원 + - TCP/IP, IPX, AppleTalk 등 대부분의 네트워크 프로토콜 지원 +4. 다양한 파일 시스템 지원 + - 리눅스 고유의 파일 시스템(ext2~4) + - DOS의 FAT, Windows의 fat32 및 NFTS 등의 상용 유닉스 파일 시스템 + - 저널링(Journaling) 파일 시스템: ReiserFS, XFS, JFS / 시스템 다운 시 즉시 복구 가능 +5. 뛰어난 이식성: 약간의 어셈블리 + 대부분이 C언어 / 쉽게 다른 시스템에 이식 가능 +6. 유연성, 확장성: 쉬운 이식성, 자유로운 배포 가능, 커널 소스 공개 +7. 다양한 응용프로그램 제공 +8. 다양한 배포판: 서버, 개발용, PC용 등 다양한 목적으로 사용 가능 / 유료, 무료 모두 존재 +9. 유닉스 표준인 POSTIX 준수 + +## 단점 + +1. 특정 하드웨어에 대한 지원 부족: 특정한 하드웨어에서는 설치 어려움, 모든 플랫폼에서 작동하지는X +2. 사용자의 숙련된 기술 요구: 아직도 중요한 설정은 직접 명령어 입력 혹은 편집기를 통해 환경 설정 파일 편집 필요 + +## 기술적 특징 + +1. **계층적 파일 구조**: /(root) 기준으로 하위 디렉터리에 usr, var, home 등이 존재, ... / 트리 구조 + +2. **가상메모리**(SWAP): 하드디스크의 일부를 메모리처럼 사용 + - 하드디스크: 데이터 저장 공간 / RAM(메모리): 작업 공간 + - 프로그램 실행 -> 메모리로 공간 이동 / 메모리의 공간이 작으면 프로그램 실행 불가 + - 위 문제점 극복을 위해 가상메모리 사용 + * **스와핑**(Swapping): 메모리와 하드디스크 사이의 데이터 교환 + - 공간이 꽉 찼다고 하더라도 가상메모리 설정 시 추가로 새로운 프로그램 실행 가능 + - 메모리에 올라와 있지만 사용하지는 않고 있는 프로그램을 하드디스크에 설정된 가상메모리 공간으로 보내고 그 + 빈 공간에 새 프로그램 로딩 + +3. **라이브러리**(Library): 프로그램에서 특정한 기능을 하는 루틴들의 모임 / 공간 효율성 매우 높음 + - 공유 라이브러리를 통한 효율적 메모리 사용 + - 하드디스크 공간의 효율성을 높여준 기술 + +4. **가상 콘솔**: 하나의 모니터를 장착한 시스템에 여러 가상 화면을 제공해서 사용하게 하는 기능 + - 현재 로컬 시스템 상에서 특정 사용자로 로그인 중 -> 다른 계정에게 메시지 보내는 실습을 위해 추가 + 로그인하는 경우 + - 이미 지나간 작업 보기 위한 키 조합: Shift + PageUP + +5. **파이프**(|): 프로세스의 통신을 위해 도입 / 어떤 프로세스의 표준 출력이 다른 프로세스의 표준 입력으로 쓰이게 하는 것 / 여러 명령 조합 시 사용 + +6. **리다이렉션**(Redirection): 어떤 프로세스의 입출력을 표춘 입출력이 아닌 다른 입출력으로 변경 / 출력 결과를 파일로 +저장하거나 파일 내용을 프로세스의 입력으로 사용 + +7. 모든 장치 파일화하여 관리 + +## 클러스터링(Clustering) + +1. **고계산용 클러스터(HPC)**: 고성능의 계산 능력 제공 + - 여러 대의 컴퓨터를 병렬로 묶어 구성 + - 슈퍼컴퓨터 제작에 사용 + +2. **부하분산 클러스터(LVS)**: 대규모 서비스 제공 + - 여러 대의 리얼 서버 + 부하 분산해주는 로드 밸런서(Load Balancer) + - 이용자가 많은 웹서비스 등 활용가치가 높음 + +3. **고가용성 클러스터(HA)**: 지속적 서비스 제공 + - 부하분산 클러스터와 연동하여 많이 사용 + - 하나의 Primary Node: 부하분산 처리 수행 + - 다른 하나의 Backup Node: Primary Node의 상태 체크하고 있다가 이상 발생 시 서비스 이어 받는 구조 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Memory/Swap\353\251\224\353\252\250\353\246\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Memory/Swap\353\251\224\353\252\250\353\246\254.md" new file mode 100644 index 00000000..e8fe42f9 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Memory/Swap\353\251\224\353\252\250\353\246\254.md" @@ -0,0 +1,57 @@ +--- +title: 'Swap메모리' +lastUpdated: '2024-03-02' +--- + +Swap 메모리란, 실제 메모리 Ram이 가득 찼지만 더 많은 메모리가 필요할때 디스크 공간을 이용하여 부족한 메모리를 대체할 수 있는 공간을 의미한다. + +실제로 메모리는 아니지만, 디스크 공간을 마치 가상의 메모리처럼 사용하기 때문에 가상 메모리라고 부른다. + +실제 메모리가 아닌 하드디스크를 이용하는 것이기 때문에 속도면에서는 부족할 수 있으나 성능의 제약으로 메모리가 부족한 경우엔 유용하게 사용할 수 있다. + +## Swap 메모리 확인 + +`swapon -s` 또는 `free -h` 명령어를 통해 Swap 메모리를 확인할 수 있다. + + + +아직 Swap 메모리를 설정하지 않은 상태이기 때문에, Swap의 total 메모리가 0으로 뜬다. + +## Swap 메모리 설정 + +Swap 메모리를 설정하기 위해선, 우선 Swap 메모리를 저장할 파일에 공간을 할당해준 후에 그 파일을 메모리로 쓸 것이라고 선언해줘야한다. + +아래 명령어는 파일에 공간을 할당하여 파일 포맷을 바꿔준 후, swap으로 지정해주는 명령어이다. + +```js +sudo fallocate -l 2G /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile +``` + +## 시스템이 재시작 되더라도 Swap 메모리 활성화 + +시스템 설정 파일을 열어서 그 파일의 맨 밑부분에 swap 메모리를 default로 하도록 명령어를 추가해주면, linux 시스템을 껐다가 켜도 계속 활성화되도록 할 수 있다. + +```js +sudo vi /etc/fstab +``` + +```js +/swapfile swap swap defaults 0 0 +``` + + +## Swap 메모리 해제 + +swapoff 명령어를 사용하여 Swap 메모리를 비활성화할 수 있다. + +```java +sudo swapoff swapfile +``` + +그 후, 스왑파일로 사용했던 파일을 제거하면 Swap 메모리를 완전히 삭제할 수 있다. + +```java +sudo rm -r swapfile +``` diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Memory/VSS,\342\200\205RSS,\342\200\205PSS,\342\200\205USS.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Memory/VSS,\342\200\205RSS,\342\200\205PSS,\342\200\205USS.md" new file mode 100644 index 00000000..5dfd4d33 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Memory/VSS,\342\200\205RSS,\342\200\205PSS,\342\200\205USS.md" @@ -0,0 +1,39 @@ +--- +title: 'VSS, RSS, PSS, USS' +lastUpdated: '2023-12-15' +--- +image + +## VSS (Virtual set size) + +- VSS는 프로세스의 액세스 가능한 전체 주소 공간이다. +- 이 크기에는 (malloc과 같은 방법으로) 할당되었지만 기록되지 않은 RAM에 상주하지 않을 수 있는 메모리도 포함된다. +- 따라서 VSS는 프로세스의 실제 메모리 사용량을 결정하는 데 큰 관련이 없다. + +## RSS (Resident set size) + +- RSS는 프로세스를 위해 실제로 RAM에 보관된 총 메모리이다. +- RSS는 공유 라이브러리를 사용하는 경우에 중복해서 카운팅하기 때문에 정확하지 않다. +- 즉, 단일 프로세스의 메모리 사용량을 정확하게 나타내지 않는다. + +## PSS (Proportional set size) + +- PSS는 RSS와 달리 공유 라이브러리를 고려하여 비례한 크기를 나타낸다. +- 세 프로세스가 모두 30 페이지 크기의 공유 라이브러리를 사용하는 경우 해당 라이브러리는 세 프로세스 각각에 대해 보고되는 PSS에는 10 페이지가 더해진다. +- 시스템의 모든 프로세스에 대한 PSS를 합하면 시스템의 총 메모리 사용량과 동일하다. +- 프로세스가 종료되면 해당 PSS에 기여한 공유 라이브러리는 해당 라이브러리를 사용하는 나머지 프로세스의 PSS 총계에 비례하여 다시 분배된다. +- 따라서 프로세스가 종료될 때 전체 시스템에 반환된 메모리를 나타내지는 못한다. + +## USS (Unique set size) + +- USS는 프로세스의 총 개인 메모리, 즉 해당 프로세스에 완전히 고유한 메모리이다. +- USS는 특정 프로세스를 실행하는 실제 증분 비용을 나타내기 때문에 유용하다. +- 프로세스가 종료되면 실제로 시스템에 반환되는 총 메모리와 같다. +- 처음에 프로세스에서 메모리 누수가 의심될 때 관찰할 수 있는 가장 좋은 숫자이다. + +--- +참고 +- https://www.baeldung.com/linux/resident-set-vs-virtual-memory-size +- https://en.wikipedia.org/wiki/Resident_set_size +- https://en.wikipedia.org/wiki/Proportional_set_size +- https://en.wikipedia.org/wiki/Unique_set_size \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/DNS\342\200\205\354\204\234\353\262\204.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/DNS\342\200\205\354\204\234\353\262\204.md" new file mode 100644 index 00000000..f9c7d84f --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/DNS\342\200\205\354\204\234\353\262\204.md" @@ -0,0 +1,90 @@ +--- +title: 'DNS 서버' +lastUpdated: '2024-03-02' +--- + +**DNS(Domain Name System)** : 사람이 식별하기 쉬운 도메인 이름을 컴퓨터가 식별하기 위한 네트워크 주소(IP)간의 변환을 수행하기 위한 시스템 (TCP,UDP/53) +- **데몬 이름** : named +- 기본 DNS 서버 주소에 대한 정보는 `/etc/resolv.conf` 파일에 저장되어 있다. + +- **Recursive 네임 서버** (Cache DNS 서버) + - 서버에 질의가 들어오면 자신의 캐시에 저장된 정보 또는 반복적 질의를 통해 그 결과를 호스트에게 응답해주는 네임서버 + - **반복적 질의(Iterative Query):** Recursive 네임 서버가 각 네임서버(Authoritative 네임서버)로 질의하는 방식 + - **재귀적 질의(Recursive Query)** : 호스트가 Recursive 네임서버로 질의할 때 사용되는 방식 + +- **Authoritative 네임서버** (DNS 서버) + - 특정 도메인에 대한 정보를 관리하면서 해당 도메인에 대한 질의에만 응답해주는 네임서버 + - Zone(Zone) : 네임서버가 관리하는 도메인 영역 + - Zone 파일(ZoneFile): 관리 도메인에 대한 정보를 담고 있는 파일. 이 파일을 읽어 질의에 응답한다. + - Zone 전송(Zone Transfer) : 마스터에 있는 원본 Zone 데이터를 슬레이브가 동기화 하는 작업 + +- DNS 서버는 마스터(master) 네임서버와 슬레이브(Slave) 네임서버로 구분된다. +`*` master - slave OR Primary - Secondary + +### 네임서버 환경 설정 (`/etc/named.conf`) + +```json +$ vi /etc/named.conf + +acl “allow_user” {192.168.10.50; 192.168.10.51; 192.168.10.52; }; + +options { + directory “/var/named”; // zone 파일들의 위치 지정 + allow-query {any}; // 네임서버에 질의할 수 있는 대상 지정 + recursive no; // 재귀적 질의 허용 여부 + forward only; // 자신에게 들어온 질의 요청을 다른 서버로 넘기는 옵션 + forwarders {192.168.11.10 }; +}; + +zone "shionista.com" IN { + type master; // master 네임서버임을 의미 + file "shionista.com.db"; // 관리하는 도메인의 리소스 레코드 정보를 담고 있는 Zone 파일명을 의미 +}; + +zone "10.168.192.in-addr.arpa" IN { // 리버스 도메인. in-addr.arpa 도메인의 하위 도메인으로 구성한다. + type master; + file "shionista.com.db.rev"; +}; + +zone “.” IN { + type hint; // 루트 도메인 type은 hint로 지정한다. + file “named.ca”; +}; +``` + +### Zone 파일 + +내부 네트워크에서 작동하는 DNS 서버를 만들기 위해서는, 네트워크 내의 영역에 대한 "IP: 사이트 주소"가 매칭된 정보를 DNS 서버에 저장해놓아야 한다. 이 정보를 저장하는 파일을 Zone 파일이라고 한다. + +```bash +$ORIGIN {영역명}. +$TTL {TTL 시간} + +{영역명} IN SOA {DNS 서버 주소} {DNS 관리자 메일주소}. ( + {Zone 파일 개정번호 입력} + {보조 DNS 서버 사용 시, Refresh 타임 입력} + {보조 DNS 서버 사용 시, Retry 타임 입력} + {보조 DNS 서버 사용 시, Zone 파일 파기 유예기간 설정} + {네거티브 캐시 유효기간 설정} + ) + +{영역명} IN NS {DNS 서버 도메인 주소} +{영역명} IN MX {우선 순위 번호 입력} {메일 서버 도메인 주소 입력} +{서버1 Host} IN A {서버1 IP 주소 입력} +{서버2 Host} IN A {서버2 IP 주소 입력} +``` + +### DNS 관련 명령어 + +- `named-checkconf` : 네임서버 환경 설정 파일(`/etc/named.conf`)을 검사 +- `named-checkzone` : 존 파일(`/var/named/`)을 검사 +- `rndc` + - 네임서버 제어 명령어 (구 ndc 명령어) + - [stop] : 네임서버 중지 + - [status] : 네임서버 상태 정보 출력 + - [reload] : 존 파일을 다시 로드 + - [flush] : 네임서버의 캐시 제거 +- `nslookup` + - DNS 관련된 각종 정보를 확인할 수 있는 명령어 + - [server IP_ADDRESS] : 질의할 DNS 서버 지정 + - [set type = RECORD] : 질의할 레코드 유형 지정 diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/IP\342\200\205masquerading.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/IP\342\200\205masquerading.md" new file mode 100644 index 00000000..48b3ccd0 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/IP\342\200\205masquerading.md" @@ -0,0 +1,37 @@ +--- +title: 'IP masquerading' +lastUpdated: '2024-03-02' +--- + +`IP masquerading` **is a process where one computer acts as an IP gateway for a network.** All computers on the network send their IP packets through the gateway, which replaces the source IP address with its own address and then forwards it to the internet. Perhaps the **source IP port number is also replaced** with another port number, although that is less interesting. All hosts on the internet see the packet as originating from the gateway. + +Any host on the Internet which wishes to send a packet back, ie in reply, must necessarily address that packet to the gateway. Remember that the gateway is the only host seen on the internet. **The gateway rewrites the destination address, replacing its own address with the IP address of the machine which is being masqueraded, and forwards that packet on to the local network for delivery.** + +--- + +This procedure sounds simple, and it is. It provides an effective means by which you can provide second class internet connections for a complete LAN using only one (internet) IP address. Note the essential phrase, “second class internet connections”. + +IP masquerading cannot provide full internet connections to the hosts which hide behind it. The reason for this is that any connection can be established outwards, that is a hidden host can connect to any service which is “advertised” on the internet, but no connection can be established inwards. No host which is hidden behind the gateway will ever receive a connection for a port which it listens to. This precludes hidden hosts from offering services such as Telnet, file transfer, www, mail, news and so on. + +The reason why no inward connection will ever be established is **that the process of listening on a port produces no packet.** When a program listens it does not annouce that it is listening, it just listens. When a host wishes to connect to a service it has no way of knowing if that connection can possibly succeed; it simply sends a connection packet to the destination IP address. If there no host at that destination address, the host trying to connect eventually times out and reports the connection failed. If there is a host at that destination address, but it is not listening at that port, the destination host returns a connection refused message and the host trying to connect immediately reports the connection failed. + +Remember that the only IP address visible on the internet, with respect to a masqueraded LAN, is the gateway’s address. Any inbound connection must be addressed to the gateway’s address. With no prior communication between the hidden host and the gateway, there is nothing to indicate (to the gateway) how to rewrite the destination address for local delivery. + +The conclusion of all of this is that if your program works by listening at an address (I suspect ICQ does this) so that other hosts on the internet can connect to you, that program will be of no use to you if your connection is through a masquerading gateway. + +## with NAT + +IP masquerading and Network Address Translation (NAT) are closely related concepts, and sometimes the terms are used interchangeably. However, there is a subtle difference between the two: + +- Network Address Translation (NAT): + - NAT is a technique used to modify the IP addresses and/or port numbers in IP packet headers while they traverse a network device, typically a router or a firewall. The primary purpose of NAT is to allow multiple devices on a private network to share a single public IP address, which conserves the limited pool of available public IP addresses. It works by translating private IP addresses of devices on the local network to the public IP address of the router before sending packets out to the internet and vice versa. + + - NAT can be of different types: + - Static NAT: One-to-one mapping of private IP addresses to public IP addresses. + - Dynamic NAT: Maps private IP addresses to an available pool of public IP addresses on-demand. + - PAT (Port Address Translation or NAT Overload): Maps multiple private IP addresses to a single public IP address using different port numbers to distinguish between connections. + +- IP Masquerading: + - IP masquerading is a **specific implementation of NAT**, often used in the context of Linux-based routers or firewalls. It provides a form of dynamic NAT (similar to PAT) that allows multiple devices on a local network to share a single public IP address. IP masquerading involves translating the private IP addresses of local devices to the public IP address of the router and keeping track of the state of outgoing connections, so that when responses come back, the router can forward the traffic to the correct internal device based on the state information. + +In summary, the main difference between IP masquerading and NAT is **that IP masquerading is a specific type of NAT used in Linux-based systems for dynamic NAT** (usually implemented with PAT). NAT, on the other hand, is a more **general term** that encompasses various techniques used to modify IP addresses and/or port numbers in packet headers to allow communication between private and public networks. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/Netfilter.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/Netfilter.md" new file mode 100644 index 00000000..8038a1bf --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/Netfilter.md" @@ -0,0 +1,155 @@ +--- +title: 'Netfilter' +lastUpdated: '2024-03-02' +--- + +Netfilter, included in Linux since 2.3, is a critival component of packet handlin. Netfilter is a framework of kernel hooks, which allow userspace programs to handle packets on behalf of the kernel. In short, a program registers to a specific Nwtfilter hook, and the kernel calls that program on applicable packes. That program could tell the kernel to do something with the packet (like drop it), or it could send back a modified packet to the kernel. With this, developers can build normal programs that run in userspach and handle packets. Netfilter was created jointly with `iptables`, to separate kernel and userspace code. + +--- + +Netfilter has five hooks, shown in below table. + +Netfilter triggers each hook under specific stages in a packet's journey through the kernel. + +|Netfilter hook|Iptables chain name|Description| +|-|-|-| +|`NF_IP_PRE_ROUTING`|PREROUTING|Triggers when a packet arrives from an external system.| +|`NF_IP_LOCAL_IN`|INPUT|Triggers when a packet’s destination IP address matches this machine.| +|`NF_IP_FORWARD`|NAT|Triggers for packets where neither source nor destination matches the machine’s IP addresses (in other words, packets that this machine is routing on behalf of other machines).| +|`NF_IP_LOCAL_OUT`|OUTPUT|Triggers when a packet, originating from the machine, is leaving the machine.| +|`NF_IP_POST_ROUTING`|POSTROUTING|Triggers when any packet (regardless of origin) is leaving the machine.| + +Netfilter triggers each hook during a specific phase of packet handling, and under specific conditions, we can visualize Netfilter hooks with a flow diagram, as shown in below picture. + +image + +We can infer from our flow diagram that only certain permutations of Netfilter hook calls are possible for any given packet. For example, a packet originating from a local process will always trigger `NF_IP_LOCAL_OUT` hooks and then `NF_IP_POST_ROUTING` hooks. In particular, the flow of Netfilter hooks for a packet depends on two things: if the packet source is the host and is the packet destination is the host. Note that if a process sends a packet destined for the same host, it triggers the `NF_IP_LOCAL_OUT` and then the `NF_IP_POST_ROUTING` hook before "reentering" the system and triggering the `NF_IP_PRE_ROUTING` and `NF_IP_LOCAL_IN` hooks. + +In some systems, it is possible to spoof such a packet by writing a fake source address (i.e., spoofing that a packet has a source and destination address of `127.0.0.1`). + +Linux will normally filters packets when a packet arrives at an external interface. More broadly, Linux filters packets when a packet arrives at an interface and the packet's source address does not exist on that network. + +A packet with an "impossible" source IP address is called a _Martian packet_. It is possible to disable filtering of Martian packets in Linux. However, doing so poses substantial risk if any services on the host assume that traffic from localhost is "more trustworthy" that external traffic. This can be a common assumption, such as when exposing an API or database to the host without strong authentication. + +--- + +Below table shows the Netfilter hook order for various packet sources and destinations. + +|Packet source|Packet destination|Hooks (in order)| +|-|-|-| +|Local machine|Local machine|`NF_IP_LOCAL_OUT`, `NF_IP_LOCAL_IN`| +|Local machine|External machine|`NF_IP_LOCAL_OUT`, `NF_IP_POST_ROUTING`| +|External machine|Local machine|`NF_IP_PRE_ROUTING`, `NF_IP_LOCAL_IN`| +|External machine|External machine|`NF_IP_PRE_ROUTING`, `NF_IP_FORWARD`, `NF_IP_POST_ROUTING`| + +Note that packets from the machine to itself will trigger `NF_IP_LOCAL_OUT` and `NF_IP_POST_ROUTING` and then "leave" the network interface. They will "reenter" and be treated like packets from any other source. + +Network address translation (NAT) only impacts local routing decisions in the `NF_IP_PRE_ROUTING` and `NF_IP_LOCAL_OUT` hooks (e.g. the kernel makes no routing decisions after a packet reaches the `NF_IP_LOCAL_IN` hook). We see this reflected in the design of `iptables`, where source and destination NAT can be performed only in specific hooks/chains. + +Programs can register a hook by calling `NF_REGISTER_NET_HOOK` (`NF_REGISTER_HOOK` prior to Linux 4.13) with a handling function. The hook will be called every time a packet matches. This is how programs like iptables integrate with Netfilter, though you will likely never need to do this yourself. + +There are several actions that a Netfilter hook can trigger, based on the return value: + +- Accept: Continue packet handling. +- Drop: Drop the packet, without further processing. +- Queue: Pass the packet to a userspace program. +- Stolen: Doesn’t execute further hooks, and allows the userspace program to take ownership of the packet. +- Repeat: Make the packet “reenter” the hook and be reprocessed. + +Hooks can also return mutated packets. This allows programs to do things such as reroute or masquerade packets, adjust packet TTLs, etc. + +## Conntrack + +Conntrack is a component of Netfilter used to **track the state of connections to (and from) the machine.** Connection tracking directly associates packets with a particular connection. Without connection tracking, the flow of packets is much more opaque. Conntrack can be a liability or a valuable tool, or both, depending on how it is used. In general, Conntrack is important on systems that handle firewalling or NAT. + +Connection tracking allows firewalls to distinguish between responses and arbitrary packets. A firewall can be configured to allow inbound packets that are part of an existing connection but disallow inbound packets that are not part of a connection. To give an example, a program could be allowed to make an outbound connection and perform an HTTP request, without the remote server being otherwise able to send data or initiate connections inbound. + +NAT relies on Conntrack to function. `iptables` exposes NAT as two types: SNAT (source NAT, where `iptables` rewrites the source address) and DNAT (destination NAT, where `iptables` rewrites the destination address). NAT is extremely common; the odds are overwhelming that your home router uses SNAT and DNAT to fan traffic between your public IPv4 address and the local address of each device on the network. + +With connection tracking, packets are automatically associated with their connection and easily modified with the same SNAT/DNAT change. This enables** consistent routing decisions**, such as “pinning” a connection in a load balancer to a specific backend or machine. The latter example is highly relevant in Kubernetes, due to `kube-proxy`’s implementation of service load balancing via `iptables`. Without connection tracking, every packet would need to be deterministically remapped to the same destination, which isn’t doable (suppose the list of possible destinations could change…). + +Conntrack identifies connections by: +- tuple +- composed of source address +- source port +- destination address +- destination port and L4 protocol + +These five pieces of information are the minimal identifiers needed to identify any given L4 connection. All L4 connections have an address and port on each side of the connection; after all, the internet uses addresses for routing, and computers use port numbers for application mapping. The final piece, the L4 protocol, is present because a program will bind to a port in TCP or UDP mode (and binding to one does not preclude binding to the other). Conntrack refers to these connections as flows. A flow contains metadata about the connection and its state. + +--- + +![image](https://github.com/rlaisqls/TIL/assets/81006587/4083810b-eb7d-4778-84d3-d73754e0da59) +The structure of Conntrack flows + +Conntrack stores flows in a hash table, using the connection tuple as a key. The size of the keyspace is configurable. A larger keyspace requires more memory to hold the underlying array but will result in fewer flows hashing to the same key and being chained in a linked list, leading to faster flow lookup times. The maximum number of flows is also configurable. A severe issue that can happen is when Conntrack runs out of space for connection tracking, and new connections cannot be made. + +There are other configuration options too, such as the timeout for a connection. On a typical system, default settings will suffice. However, a system that experiences a huge number of connections will run out of space. If your host runs directly exposed to the internet, overwhelming Conntrack with short-lived or incomplete connections is an easy way to cause a denial of service (DOS). + +Conntrack’s max size is normally set in `/proc/sys/net/nf_conntrack_max`, and the hash table size is normally set in `/sys/module/nf_conntrack/parameters/hashsize`. + +--- + +Conntrack entries contain a connection state, which is one of four states. It is important to note that, as a layer 3 (Network layer) tool, Conntrack states are distinct from layer 4 (Protocol layer) states. + +|State|Description|Example| +|-|-|-| +|`NEW`|A valid packet is sent or received, with no response seen.|TCP SYN received.| +|`ESTABLISHED`|Packets observed in both directions.|TCP SYN received, and TCP SYN/ACK sent.| +|`RELATED`|An additional connection is opened, where metadata indicates that it is “related” to an original connection. Related connection handling is complex.|An FTP program, with an ESTABLISHED connection, opens additional data connections.| +|`INVALID`|The packet itself is invalid, or does not properly match another Conntrack connection state.|TCP RST received, with no prior connection.| + +Although Conntrack is built into the kernel, it may not be active on your system. Certain kernel modules must be loaded, and you must have relevant `iptables` rules (essentially, Conntrack is normally not active if nothing needs it to be). Conntrack requires the kernel module `nf_conntrack_ipv4` to be active. `lsmod | grep nf_conntrack` will show if the module is loaded, and `sudo modprobe nf_conntrack` will load it. You may also need to install the conntrack command-line interface (CLI) in order to view Conntrack’s state. + +When Conntrack is active, `conntrack -L` shows all current flows. Additional Conntrack flags will filter which flows are shown. + +Let’s look at the anatomy of a Conntrack flow, as displayed here: + +```bash +tcp 6 431999 ESTABLISHED src=10.0.0.2 dst=10.0.0.1 +sport=22 dport=49431 src=10.0.0.1 dst=10.0.0.2 sport=49431 dport=22 [ASSURED] +mark=0 use=1 + + + [flow state>] + [] +``` + +The expected return packet is of the form ` `. This is the identifier that we expect to see when the remote system sends a packet. Note that in our example, the source and destination values are in reverse for address and ports. This is often, but not always, the case. + +For example, if a machine is behind a router, packets destined to that machine will be addressed to the router, whereas packets from the machine will have the machine address, not the router address, as the source. + +In the previous example from machine `10.0.0.2`, `10.0.0.1` has established a TCP connection from port 49431 to port 22 on `10.0.0.2`. You may recognize this as being an SSH connection, although Conntrack is unable to show application-level behavior. + +Tools like `grep` can be useful for examining Conntrack state and ad hoc statistics: + +```bash +grep ESTABLISHED /proc/net/ip_conntrack | wc -l +``` + +## Routing + +When handling any packet, the kernel must decide where to send that packet. In most cases, the destination machine will not be within the same network. For example, suppose you are attempting to connect to `1.2.3.4` from your personal computer. + +`1.2.3.4` is not on your network; the best your computer can do is pass it to another host that is closer to being able to reach `1.2.3.4`. The route table serves this purpose by mapping known subnets to a gateway IP address and interface. You can list known routes with route (or route -n to show raw IP addresses instead of hostnames). A typical machine will have a route for the local network and a route for `0.0.0.0/0`. Recall that subnets can be expressed as a CIDR (e.g., `10.0.0.0/24`) or an IP address and a mask (e.g., `10.0.0.0` and `255.255.255.0`). + +This is a typical routing table for a machine on a local network with access to the internet: + +```bash +# route +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +0.0.0.0 10.0.0.1 0.0.0.0 UG 303 0 0 eth0 +10.0.0.0 0.0.0.0 255.255.255.0 U 303 0 0 eth0 +``` + +In the previous example, a request to `1.2.3.4` would be sent to `10.0.0.1`, on the eth0 interface, because `1.2.3.4` is in the subnet described by the first rule (`0.0.0.0/0`) and not in the subnet described by the second rule (`10.0.0.0/24`). Subnets are specified by the destination and genmask values. + +Linux prefers to route packets by specificity (how “small” a matching subnet is) and then by weight (“metric” in route output). Given our example, a packet addressed to `10.0.0.1` will always be sent to gateway `0.0.0.0` because that route matches a smaller set of addresses. If we had two routes with the same specificity, then the route with a lower metric wiould be preferred. + +Some CNI plugins make heavy use of the route table. + +--- +reference +- https://netfilter.org/ + \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/Virtual\342\200\205Networking\342\200\205Interface.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/Virtual\342\200\205Networking\342\200\205Interface.md" new file mode 100644 index 00000000..039403f5 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/Virtual\342\200\205Networking\342\200\205Interface.md" @@ -0,0 +1,93 @@ +--- +title: 'Virtual Networking Interface' +lastUpdated: '2024-03-02' +--- + +리눅스는 컨테이너 기술의 기반이 되는 virtual networking 관련 기능들을 제공한다. virtual networking에 자주 사용되는 대표적인 네트워크 인터페이스를 알아보자. + +## Bridge + +Linux Bridge는 일반적인 네트워크 스위치와 유사하게 동작한다. Bridge는 주로 라우터, 게이트웨이, VM 등에서 패킷을 목적지로 전달(forwarding)하는 역할을 수행한다. Bridge는 STP, VLAN filter, multicast snooping등의 기능도 추가적으로 지원한다. + + + +아래의 소스코드는 리눅스에서 Bridge를 생성하고, 서로 다른 네트워크 인터페이스와 연결하는 과정을 코드로 작성한 예시이다. 이 과정을 거치면, 브릿지를 통해 VM 1, VM 2과 network naemspace 1은 서로 통신이 가능해진다. + +```bash +$ ip link add br0 type bridge # bridge 생성 +$ ip link set eth0 master br0 # bridge와 host의 eth0 네트워크 인터페이스 연결 +$ ip link set tap1 master br0 # bridge와 VM1의 tap 디바이스 연결 +$ ip link set tap2 master br0 # bridge와 VM2의 tap 디바이스 연결 +$ ip link set veth1 master br0 # bridge와 network namespace 1를 veth로 연결 +``` + +## Bonded Interface + +Linux Bonding Driver(네트워크 본딩)는 서로 다른 네트워크 인터페이스를 하나의 논리적 인터페이스로 묶는 기능을 수행한다. Bonding Driver는 크게 `hot-standby`와 `load balacing` 모드로 나뉜다. + +`Hot-standby` 모드는 네트워크 인터페이스를 Active와 Standby로 나누고 Active에서 네트워크 장애 발생 시, 트래픽을 Standby로 전송하여 장애에 대응하는 방식으로 작동한다. + +`Load balancing` 모드는 패킷을 여러 네트워크 인터페이스에 분배하여 네트워크 처리량과 대역폭을 증가시키는 등 성능 향상을 위해 사용된다. Load balancing 모드에서는 Round-Robin, XOR, Broadcast 등의 세부적인 트래픽 분배 정책을 선택할 수 있다. + +![image](https://github.com/team-aliens/.github/assets/81006587/21dbf9d3-e17f-4ca9-a9ae-28b299d2ada0) + +아래는 두 네트워크 인터페이스(eth0, eth1)을 hot-standy 모드로 묶는 예시이다. + +```bash +$ ip link add bond0 type bond miimon 100 mode active-backup # bond0 인터페이스 추가, 모니터링 주기 100ms, active-backup 모드 +$ ip link set eth0 master bond0 # eth0을 bond0 인터페이스에 추가 +$ ip link set eth1 master bond0 # eth1을 bond0 인터페이스에 추가 +``` + +## Team Device + +Team Device는 bonded interface와 유사하지만, L2 레벨에서 여러 개의 NIC를 하나의 논리적 그룹으로 묶는 기능을 수행한다. + +![image](https://github.com/team-aliens/.github/assets/81006587/d3d5a481-0711-414d-acb0-1313d2bfeb78) + +아래는 팀 디바이스를 생성하고 2개의 네트워크 인터페이스(eth0, eth1)를 묶는 과정을 수행하는 예시이다. + +```bash +$ teamd -o -n -U -d -t team0 -c '{"runner": {"name": "activebackup"},"link_watch": {"name": "ethtool"}}' +$ ip link set eth0 down +$ ip link set eth1 down +$ teamdctl team0 port add eth0 +$ teamdctl team0 port add eth1 +``` + +## VLAN + +VLAN은 virtual LAN의 줄임말로 하나의 스위치에서 브로드캐스트 도메인을 논리적으로 분할하기 위해 활용된다. 브로드캐스트에서는 일반적으로 동일한 LAN에 포함된 모든 네트워크 단말로 패킷을 송출한다. VLAN은 하나의 스위치에서 태그를 붙이는 방식으로 LAN을 논리적으로 분할하여 브로트캐스트 과정에서 발생하는 불필요한 대역폭 낭비 등을 개선한다. + +VLAN을 위한 프로토콜은 IEEE 802.1Q에서 정의하고 있다. IEEE 802.1Q는 일반적으로 VLAN tagging이라고도 불린다. VLAN 헤더에는 VLAN ID 필드가 존재한다. VLAN ID는 VLAN을 구분하기 위해 활용되며 12bit로 4,096개의 값을 가질 수 있다. + + + + +아래는 메인 네트워크 인터페이스(eth0)를 2개의 VLAN(eth0.2, eth0.3)으로 분리하는 예시이다. + +```bash +ip link add link eth0 name eth0.2 type vlan id 2 +ip link add link eth0 name eth0.3 type vlan id 3 +``` + +## veth + +veth는 virtual Ethernet의 줄임말로, 로컬 이더넷 터널에 해당한다. veth는 한 쌍으로 생성되며, 한 쪽에서 다른 쪽으로 패킷을 전송할 수 있으며, 한 쪽에 다운된 경우 나머지 한 쪽도 정상적으로 기능하지 않는 것이 특징이다. + +veth 타입의 장비는 기본적으로 리눅스의 디폴트 네임스페이스에 속하게 되며, 활성화되지 않은 상태로 생성된다. veth0과 veth1은 직접 연결되어있기 때문에 활성화 상태는 항상 동시에 변경된다. + + + +아래의 코드는 호스트 머신에서 두 개의 네트워크 네임스페이스(netns1, netns2)를 생성하고, VETH를 활용하여 호스트 네트워크와 새롭게 생성된 네트워크 네임스페이스를 연결하는 과정을 구현한 것이다. + +```bash +$ ip netns add netns1 +$ ip netns add netns2 +$ ip link add veth1 netns netns1 type veth peer name veth2 netns netns2 +``` + +--- +참고 +- https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking/ +- https://www.44bits.io/ko/keyword/veth \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/iptables.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/iptables.md" new file mode 100644 index 00000000..53bf3cf9 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/iptables.md" @@ -0,0 +1,91 @@ +--- +title: 'iptables' +lastUpdated: '2024-03-02' +--- + +Iptables 방화벽은 규칙을 관리하기 위해 테이블을 사용한다. 이 테이블은 사용자가 익숙한 형태에 따라 규칙을 구분한다. 예를 들어 만약 하나의 규칙이 network 주소 변환을 다룬다면 그것은 nat 테이블로 놓여질 것이다. 만약 그 규칙이 패킷을 목적지로 허용하는데 사용된다면 그것은 filter 테이블에 추가 될 것이다. 이러한 각각의 iptables 테이블 내에서 규칙들은 분리된 체인안에서 더 조직화(organize)된다. 테이블들이 일반적인 규칙의 목적에 의해 정의되는 동안 빌트인 체인들은 그것들을 트리거 하는 netfilter hook들을 표현한다. 체인들은 기본적으로 언제 규칙이 평가될지를 결정한다. + +아래는 빌트인 체인들의 이름인데 이것들은 [netfilter](Netfilter.md) 이름을 그대로 사용한다. + +- PREROUTING : `NF_IP_PRE_ROUTING` Hook에 의해 트리거 된다. +- INPUT : `NF_IP_LOCAL_IN` Hook에 의해 트리거 된다. +- FORWARD : `NF_IP_FORWARD` Hook에 의해 트리거 된다. +- OUTPUT : `NF_IP_LOCAL_OUT` Hook에 의해 트리거 된다. +- POSTROUTING : `NF_IP_POST_ROUTING` Hook에 의해 트리거 된다. + +체인들은 관리자가 패킷의 delivery path에서 규칙이 어디로 평가되어질지를 허용하는데, 각각의 테이블이 여러개의 체인을 가지게 된 이후에, 하나의 테이블에 미치는 영향은 패킷이 처리되는 동안 여러개의 지점에서 영향을 받을 수 있다. 어떤 특정 타입의 결정은 오직 네트워크 스택안에서 특정 포인트에 인지되기 때문에 모든 테이블은 각각의 커널 Hook에 등록된 체인을 여러개 가질 수도, 가지지 못할 수도 있다. + +오직 5개의 netfilter 커널 Hook만이 존재하기 때문에, 여러개의 테이블로 부터 생성된 체인들은 각각의 hook에 등록된다. 예를 들어 3개 테이블이 `PREROUTING` 체인을 가지고 있고 이 체인들이 연관된 `NF_IP_PRE_ROUTING` hook에 연관되어 등록되어 있을 때, 그것들은 우선순위를 명시한다. 그 우선순위는 어떤 순서가 각 테이블의 PREROUTING 체인이 호출되는지를 지시한다. 각각의 규칙은 내부에 가장 높은 우선순위인 PREROUTING 체인이 순차적으로 평가되는데 그것은 다음 PREROUTING 체인으로 움직이기 전에 평가 된다. + +## 테이블 + +- **Filter Table** + - Iptables에서 가장 널리 사용되는 테이블. 이 Filter 테이블은 패킷이 계속해서 원하는 목적지로 보내거나 요청을 거부하는데에 대한 결정을 하는데 사용된다. 방화벽 용어로 이것은 “filtering” 패킷들로 알려진다. + +- **NAT Table** + - 네트워크 주소 변환 규칙을 구현하는데 사용된다. 패킷들이 네트워크 스택에 들어오면, 이 테이블에 있는 규칙들은 패킷의 source 혹은 destination 주소를 변경하거나 변경하는 방법을 결정한다. 보통 direct access가 불가능할 때 네트워크에 패킷들을 라우팅하는데 종종 사용된다. + +- **Mangle Table** + - 다양한 방법으로 패킷의 IP 헤더를 변경하는데 사용된다. 예를 들어 패킷의 TTL (Time to Live) Value를 조정할수 있다. 이 테이블은 다른 테이블과 다른 네트워킹 툴에 의해 더 처리되기 위해 패킷위에 내부 커널 “mark”를 위치 시킨다. 이 “marks”는 실제 패킷을 건들이지는 않지만 패킷의 커널의 표현에 mark를 추가하는 것이다. + +- **Raw Table** + - Iptables 방화벽은 stateful 방식이다. 이것은 패킷들은 이전 패킷들과 연관되어 평가된다는 것이다. Connection tracking 기능들은 netfilter 프레임워크의 최상단에서 빌드 되고, 이것은 iptables가 ongoing connection 혹은 관계없는 패킷의 스트림으로의 세션 일부분으로 패킷을 살펴 볼 수 있도록 허용해준다. 이 Raw 테이블은 배우 좁게 정의된 기능이다. 이것의 유일한 목적은 connection tracking을 위한 패킷을 표시하기 위한 메커니즘을 제공하는 것이다. + +- **Security Table** + - 내부의 internal SELinux 보안 context marks를 패킷에 설정하는데 사용된다. 이것은 SELinux나 다른 시스템들이 어떻게 패킷을 핸들링하는데 영향을 미치는지에 대해 관여한다. + +image + +하나의 패킷이 netfilter Hook을 트리거 할 때, 연관된 체인들은 아래 표에 있는 것 처럼 위에서 아래로 리스트 되어 있는 순서대로 처리가 된다. Columns에 있는 hook들은 패킷이 트리거 할 때 수행되고, 이것들은 incoming / outgoing 패킷 인지, 라우팅 decision인지, 패킷이 필터링 영역을 지나가는지에 의존한다. 특정 이벤트가 테이블의 체인이 프로세싱 하는 동안 스킵할 수 있도록 해주기도 한다. 예를 들어 오직 첫번째 패킷만이 하나의 connection안에서 NAT 규칙으로 수행될 때, 첫번째 패킷을 위해 만들어진 nat 결정은 추가적인 evaluation없이 모든 차후의 패킷으로 적용된다. NAT된 connection으로 response는 자동적으로 reverse NAT 규칙이 정확하게 라우팅 되게 된다. + +## Chain Traversal Order + +서버는 패킷이 라우팅 될 곳을 알고 있고, 방화벽 규칙이 전파되는 것을 허용한다고 가정하면, 다음의 flow가 다른 상황에서 traverse 되는지의 path를 보여준다. + +- 들어오는 패킷이 local system을 목적지로 한다 : PREROUTING -> INPUT +- 들어오는 패킷이 다른 호스트로 목적지로 한다 : PREROUTING -> FORWARD -> POSTROUTING +- 내부에(local) 생성된 패킷들 : OUTPUT -> POSTROUTING + +만약 이전 테이블에서 ordering layout에 대한 위 3가지 정보를 통합한다면, 우리는 local 시스템으로 향하는 incoming 패킷이 처음으로 raw, mangle, nat 테이블의 PREROUTING 체인에 대해 평가가 되는 것을 볼 수 있을 것이다. 그것은 그때 mangle, filter, security, nat테이블의 INPUT 체인을 traverse할 것이다. + +## IPTables Rules + +규칙들은 특정한 테이블의 특정한 체인안에 놓여진다. 각각의 체인이 호출될 때, in question 패킷은 순서대로 체인안에서 각각의 규칙에 대해 체크를 하게 된다. 각각의 규칙은 매칭된 component와 action component를 가진다. + +### Matching +하나의 규칙의 matching 비율은 영역을 명시하는데 그 영역은 하나의 패킷이 순서대로 만나야 하는 것이다. 그것은 연관된 액션 혹은 타켓을 위한 것인데, 이 Matching 시스템은 매우 flexible 해서 iptables을 확장할 수 있게 한다. 규칙들은 프로토콜 타입, destination 혹은 source 주소, 포트, 네트워크, input 혹은 output interface, 헤더, connection state에 의해 매치하기 위해 구성된다. 이것들은 다른 트래픽사이들에서 구별하기 위해 공평하게 복잡한 규칙 집합(set)을 생성해서 통합될 수 있다. + +### Targets + +타겟은 하나의 패킷이 하나의 규칙의 matching 영역을 만났을 때 트리거 되는 액션이다. 타겟들은 점진적으로 아래 두개의 영역으로 나뉘어 진다. + +- **Terminating targets**: 체인내에서 evaulation을 끝내고 netfilter Hook으로 컨트롤을 리턴하는 것이다. 제공된 return value에 의존하고 그 Hook은 패킷을 drop하거나 패킷이 다음 스테이지로 계속해서 수행할 수 있도록 해준다. +- **Non-terminating targets**: 체인 내에서 evaluation을 계속 수행하고 행위를 수행한다. 비록 각각의 체인이 결과적으로 최종 terminating decision으로 전달해야만 한다면, non-terminating targets의 숫자는 사전에 수행되어질 수 있다. + +규칙내에서 각각의 타겟의 가용성은 context에 의존적이다. 예를 들어 테이블과 체인 타입은 가용한 타겟을 가르키고, 규칙내에서 활성화된 extension들과 matching을 구분하는 것은 타겟의 가용성에 영향을 미칠 수 있다. + +## IPTables and Connection Tracking + +Connection tracking은 iptables이 ongoing connection의 context안에 보여지는 패킷에 대해 결정할 수 있게 한다. Connection tracking 시스템은 iptables 에게 “stateful” operation을 수행하는데 필요한 기능들을 제공한다. + +Connection tracking은 패킷이 네트워크 스택에 들어온 후 바로 적용된다. raw table 체인과 어떤 기본적인 온전한 체크들은 하나의 connection과 패킷을 연관시키는데 이전의 패킷위에 수행되는 유일한 로직이다. 시스팀은 각각의 패킷들을 현재 존재하는 connection들의 집합과 체크하고, 그것은 connection의 state를 업데이트 한다. 필요할 때 시스템에 새로운 connection을 추가한다. 패킷들은 하나의 raw 체인에서 NOTRACK tarket으로 표시된다. 그리고 connection tracking 경로를 bypass 한다. + +### Available States +- `NEW` : 하나의 패킷이 현재 Connection과 관계 없이 도착했을 때, 그리고 첫번째 패킷으로 맞지 않을 때 새로운 connection은 시스템이 이 라벨(NEW)로 추가된다. 이것은 둘다 TCP, UDP로 적용된다. +- `ESTABLISHED` : 하나의 Connection이 NEW에서 ESTABLISHED로 변하는 것은 connection이 valid response를 받을 때이다. TCP Connection을 위해 이것은 SYN/ACK를 의미하고 UDP와 ICMP 트래픽을 위해서는 오리지날 패킷의 source와 destination이 바뀌는 곳에 response를 의미한다. +- `RELATED` : 존재하는 connection의 일부가 아니고 시스템에서 이미 connection에 연계되고 있는 패킷들은 RELATED라고 label이 붙는다. 이것은 helper connection을 의미한다. FTP 데이터 전송 connection의 경우나 ICMP 응답일 수 있다. +- `INVALID` : 만약 패킷들이 존재하는 connection과 연관이 없고 새로운 connection을 열기 위해 적절하지 않고, identify될수 없거나 다른 이유로써 라우팅 되지 않는 다면 INVALID로 표시된다. +- `UNTRACKED` : 패킷들은 tracking을 bypass하기 위해 raw 테이블 체인안에서 target 되어지면 UNTRACKED 라고 표시된다. +- `SNAT` : source address가 NAT operation에 의해 변경되었을 때 가상의 상태가 셋팅된다. 이것은 connection tracking system에 의해 사용되어 져서 source address가 reply 패킷에서 변경되는지를 알수 있다. +- `DNAT` : destination address가 NAT operation에 의해 변경되었을 때 가상의 상태가 셋팅된다. 이것은 reply 패킷들을 라우팅할 때 connection tracking system에 의해 사용되어 져서 destination address를 변경되는지 알 수 있다. + +위 state들은 connection tracking system에서 추적되고 관리자는 connection lifetime에서 특정 포인트로 타겟팅 하는 규칙을 만들수 있다. 이것은 보다 빈틈없고 안전한 규칙이 필요로 하는 기능들을 제공한다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/be9ddedb-9bee-4af5-9dfe-292819909bff) + +--- +reference +- [Kubernetes and networking](https://learning.oreilly.com/library/view/networking-and-kubernetes/9781492081647/) +- https://kevinalmansa.github.io/network%20security/IPTables/ +- https://kubernetes.io/docs/concepts/cluster-administration/networking/ +- https://www.digitalocean.com/community/tutorials/a-deep-dive-into-iptables-and-netfilter-architecture \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/iptables\342\200\205\353\260\251\355\231\224\353\262\275\342\200\205\354\204\244\354\240\225.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/iptables\342\200\205\353\260\251\355\231\224\353\262\275\342\200\205\354\204\244\354\240\225.md" new file mode 100644 index 00000000..1f3eefed --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/iptables\342\200\205\353\260\251\355\231\224\353\262\275\342\200\205\354\204\244\354\240\225.md" @@ -0,0 +1,119 @@ +--- +title: 'iptables 방화벽 설정' +lastUpdated: '2024-03-02' +--- + +iptables란 넷필터 프로젝트에서 개발했으며 광범위한 프로토콜 상태 추적, 패킷 애플리케이션 계층검사, 속도 제한, 필터링 정책을 명시하기 위한 강력한 매커니즘을 제공한다. + +## 서비스 등록과 시작 + +CentOS 6.4 Minimal에는 iptables가 설치되어 있다. ip6tables도 함께 설치되어 있는데 이는 IPv6 체계에서 사용한다. + +```bash +rpm -qa | grep iptables + + iptables-1.4.7-9.el6.x86_64 + iptables-ipv6-1.4.7-9.el6.x86_64 +``` + +설치되어 있지 않다면 설치 + +```bash +yum -y install iptables +``` + +```bash +chkconfig --list + + ip6tables 0:해제 1:해제 2:해제 3:해제 4:해제 5:해제 6:해제 + iptables 0:해제 1:해제 2:해제 3:해제 4:해제 5:해제 6:해제 +``` + +서비스를 시작프로그램에 등록한다. + +```bash +chkconfig iptables on +``` + +서비스를 시작한다. + +```bash +service iptables start +``` + +## 용어 + +### 1) 테이블(tables) + +우선 iptables에는 테이블이라는 광범위한 범주가 있는데, 이 테이블은 filter, nat, mangle, raw 같은 4개의 테이블로 구성되며, 이중에서 우리에게 필요한 것은 필터링 규칙을 세우는 filter 테이블이다. + +### 2) 체인(chain) + +iptables에는 filter 테이블에 미리 정의된 세가지의 체인이 존재하는데 이는 INPUT, OUTPUT, FORWARD 이다. 이 체인들은 어떠한 네트워크 트래픽(IP 패킷)에 대하여 정해진 규칙들을 수행한다. + +가령 들어오는 패킷(INPUT)에 대하여 허용(ACCEPT)할 것인지, 거부(REJECT)할 것인지, 버릴(DROP)것인지를 결정한다. + +- INPUT : 호스트 컴퓨터를 향한 모든 패킷 +- OUTPUT : 호스트 컴퓨터에서 발생하는 모든 패킷 +- FORWARD : 호스트 컴퓨터가 목적지가 아닌 모든 패킷, 즉 라우터로 사용되는 호스트 컴퓨터를 통과하는 패킷 + +### 3) 매치(match) + +iptables에서 패킷을 처리할때 만족해야 하는 조건을 가리킨다. 즉, 이 조건을 만족시키는 패킷들만 규칙을 적용한다. + +- `--source` (-s) : 출발지 IP주소나 네트워크와의 매칭 +- `--destination` (-d) : 목적지 ip주소나 네트워크와의 매칭 +- `--protocol` (-p) : 특정 프로토콜과의 매칭 +- `--in-interface` (i) : 입력 인테페이스 +- `--out-interface` (-o) : 출력 인터페이스 +- `--state` : 연결 상태와의 매칭 +- `--string` : 애플리케이션 계층 데이터 바이트 순서와의 매칭 +- `--comment` : 커널 메모리 내의 규칙과 연계되는 최대 256바이트 주석 +- `--syn (-y)` : SYN 패킷을 허용하지 않는다. +- `--fragment` (-f) : 두 번째 이후의 조각에 대해서 규칙을 명시한다. +- `--table` (-t) : 처리될 테이블 +- `--jump` (-j) : 규칙에 맞는 패킷을 어떻게 처리할 것인가를 명시한다. +- `--match` (-m) : 특정 모듈과의 매치 + +### 4) 타겟(target) +iptables는 패킷이 규칙과 일치할 때 동작을 취하는 타겟을 지원한다. + +- ACCEPT : 패킷을 받아들인다. +- DROP : 패킷을 버린다(패킷이 전송된 적이 없던 것처럼). +- REJECT : 패킷을 버리고 이와 동시에 적절한 응답 패킷을 전송한다. +- LOG : 패킷을 syslog에 기록한다. +- RETURN : 호출 체인 내에서 패킷 처리를 계속한다. +- REJECT: 서비스에 접속하려는 사용자의 액세스를 거부하고 connection refused라는 오류 메시지를 보여주는 반면, DROP은 말 그대로 telnet 사용자에게 어떠한 경고 메시지도 보여주지 않은 채 패킷을 드롭한다. + 관리자의 재량껏 이러한 규칙을 사용할 수 있지만 사용자가 혼란스러워하며 계속해서 접속을 시도하는 것을 방지하려면 REJECT를 사용하는 것이 좋다. + +### 5) 연결 추적(Connection Tracking) +iptables는 연결 추적(connection tracking)이라는 방법을 사용하여 내부 네트워크 상 서비스 연결 상태에 따라서 그 연결을 감시하고 제한할 수 있게 해준다. 연결 추적 방식은 연결 상태를 표에 저장하기 때문에, 다음과 같은 연결 상태에 따라서 시스템 관리자가 연결을 허용하거나 거부할 수 있다. + +- NEW : 새로운 연결을 요청하는 패킷, 예, HTTP 요청 +- ESTABLISHED : 기존 연결의 일부인 패킷 +- RELATED : 기존 연결에 속하지만 새로운 연결을 요청하는 패킷, 예를 들면 접속 포트가 20인 수동 FTP의 경우 전송 포트는 사용되지 않은 1024 이상의 어느 포트라도 사용 가능하다. +- INVALID : 연결 추적표에서 어디 연결에도 속하지 않은 패킷 + 상태에 기반(stateful)한 iptables 연결 추적 기능은 어느 네트워크 프로토콜에서나 사용 가능하다. UDP와 같이 상태를 저장하지 않는 (stateless) 프로토콜에서도 사용할 수 있다. + +### 6) 명령어(commond) + +- `-A` (`--append`) : 새로운 규칙을 추가한다. +- `-D` (`--delete`) : 규칙을 삭제한다. +- `-C` (`--check`) : 패킷을 테스트한다. +- `-R` (`--replace`) : 새로운 규칙으로 교체한다. +- `-I` (`--insert`) : 새로운 규칙을 삽입한다. +- `-L` (`--list`) : 규칙을 출력한다. +- `-F` (`--flush`) : chain으로부터 규칙을 모두 삭제한다. +- `-Z` (`--zero`) : 모든 chain의 패킷과 바이트 카운터 값을 0으로 만든다. +- `-N` (`--new`) : 새로운 chain을 만든다. +- `-X` (`--delete-chain`) : chain을 삭제한다. +- `-P` (`--policy`) : 기본정책을 변경한다. + +### 7) 기본 동작 + +- 패킷에 대한 동작은 위에서 부터 차례로 각 규칙에 대해 검사하고, 그 규칙과 일치하는 패킷에 대하여 타겟에 지정한 ACCEPT, DROP등을 수행한다. +- 규칙이 일치하고 작업이 수행되면, 그 패킷은 해당 규칙의 결과에 따리 처리하고 체인에서 추가 규칙을 무시한다. +- 패킷이 체인의 모든 규칙과 매치하지 않아 규칙의 바닥에 도달하면 정해진 기본정책(policy)이 수행된다. +- 기본 정책은 policy ACCEPT, policy DROP 으로 설정할 수 있다. +- 일반적으로 기본정책은 모든 패킷에 대해 DROP을 설정하고 특별히 지정된 포트와 IP주소등에 대해 ACCEPT를 수행하게 만든다. + diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/namespace\354\231\200\342\200\205cgroup.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/namespace\354\231\200\342\200\205cgroup.md" new file mode 100644 index 00000000..f9ce6aed --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/namespace\354\231\200\342\200\205cgroup.md" @@ -0,0 +1,49 @@ +--- +title: 'namespace와 cgroup' +lastUpdated: '2023-12-15' +--- +## namespace + +VM에서는 각 게스트 머신별로 독립적인 공간을 제공하고 서로가 충돌하지 않도록 하는 기능을 갖고 있다. 리눅스에서는 이와 동일한 역할을 하는 namespaces 기능을 커널에 내장하고 있다. 리눅스 커널에서는 다음 6가지 namespace를 지원하고 있다. + +|namespace|description| +|-|-| +|mnt
(파일시스템 마운트)|호스트 파일시스템에 구애받지 않고 독립적으로 파일시스템을 마운트, 언마운트 가능| +|pid
(프로세스)|독립적인 프로세스 공간을 할당| +|net
(네트워크)|namespace간에 network 충돌 방지 (중복 포트 바인딩 등)| +|ipc
(SystemV IPC)|프로세스간의 독립적인 통신통로 할당| +|uts
(hostname)|독립적인 hostname 할당| +|user
(UID)|독립적인 사용자 할당| + +namespaces를 지원하는 리눅스 커널을 사용하고 있다면 다음 명령어를 통해 바로 namespace를 만들어 실행할 수 있다. 예시로 PID namespace를 띄워보자. + +```js +$ sudo unshare --fork --pid --mount-proc bash +``` + +```js +root@ec2-user:~# ps aux +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +root 1 4.0 0.0 17656 6924 pts/9 S 22:06 0:00 bash +root 2 0.0 0.0 30408 1504 pts/9 R+ 22:06 0:00 ps aux +``` + +namespace를 생성함으로써 독립적인 공간이 할당되었다. PID namespace에 실행한 bash가 PID 1로 할당되어 있고(일반적으로 init(커널)이 PID 1) 바로 다음으로 실행한 "ps aux" 명령어가 PID 2를 배정받았다. + +PID namespace 안에서 실행한 프로세스는 밖에서도 확인할 수 있다. namespaces 기능은 같은 공간을 공유하되 조금 더 제한된 공간을 할당해주는 것이라 볼 수 있다. + +namespace를 통해 독립적인 공간을 할당한 후에는 nsenter(namespace enter)라는 명령어를 통해 이미 돌아가고 있는 namespace 공간에 접근할 수 있다. + +Docker에서는 docker exec가 이와 비슷한 역할을 하고 있다. (단 nsenter의 경우 docker exec와는 다르게 cgroups에 들어가지 않기 때문에 리소스 제한의 영향을 받지 않는다) + +## cgroups (Control Groups) + +cgroups(Control Groups)는 자원(resources)에 대한 제어를 가능하게 해주는 리눅스 커널의 기능이다. `메모리`, `CPU`, `I/O`, `네트워크`, `device 노드`등의 리소스에 제한을 걸 수 있다. + +실행중인 프로그램의 메모리를 제한하고 싶다면 `/sys/fs/cgroup/*/groupname`의 아래에 있는 파일의 내용을 수정하면 된다. + +```bash +$ echo 2000000 > /sys/fs/cgroup/memory/testgrp/memory.kmem.limit_in_bytes +``` + +최대 메모리 사용량을 2MB로 제한하는 명령어이다. diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/network\342\200\205namespaces.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/network\342\200\205namespaces.md" new file mode 100644 index 00000000..1987ae8f --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/network\342\200\205namespaces.md" @@ -0,0 +1,118 @@ +--- +title: 'network namespaces' +lastUpdated: '2024-03-02' +--- + +컨테이너는 [namespace](namespace와 cgroup.md)를 사용하여 같은 host 안에서 자원을 격리한다. + +network namespace는 도커와 같은 컨테이너에서 네트워크 격리를 구현하기 위해 쓰인다. 기본적으로 호스트는 외부 네트워크와 연결하기 위한 인터페이스와 Routing Table, ARP Table을 가지고 있는데, 컨테이너를 만들면 그 컨테이너에 대한 network namespace가 생성되어서 호스트의 네트워크 인터페이스와 완전히 분리된다. + +대신에, 추가적으로 **컨테이너 각각에 가상의 인터페이스와 Routing Table, ARP Table을 설정**하면 설정대로 통신을 할 수 있게 된다. 호스트에서는 각 네트워크 인터페이스의 네트워크 요소를 확인할 수 없으며, 각 네임스페이스도 호스트가 외부와 통신하기 위해 쓰는 네트워크 요소를 확인할 수 없다. + +## 명령어 + +네임 스페이스 목록 보기 +```js +ip netns +``` + +인터페이스 목록 보기 +``` +ip link +``` + +네임스페이스 내부의 인터페이스 목록 보기 +```js +ip netns exec {namespace name} ip link +ip -n {namespace name} link +``` + +```js +arp +route +``` + +## 두 network namespace를 직접 연결하기 + +`red`와 `blue`라는 네트워크 인터페이스를 만들어놓았다고 해보자 + +두 인터페이스를 [veth](veth.md)로 연결되도록 한다. (아직 이 인터페이스가 네임스페이스와 등록되진 않았다.) +```js +ip link add veth-red type veth peer name veth-blue +``` + +각 인터페이스를 네임스페이스에 등록한다. +```js +ip link set veth-red netns red +ip link set veth-blue netns blue +``` + +네임스페이스(인터페이스)에 가상 ip를 지정한다. +```js +ip -n red addr add 192.168.15.1 veth-red +ip -n blue addr add 192.168.15.1 veth-blue +``` + +인터페이스가 실제로 작동할 수 있도록 활성화한다. +```js +ip -n red link set veth-red up +ip -n blue link set veth-blue up +``` + +red에서 blue의 ip로 ping을 보내면 arp 테이블에서 해당 호스트를 등록해놓은 것을 볼 수 있다. +```js +ip netns exec red ping 192.168.15.2 +ip netns exec +``` + +output +```js +Address HWtype HWaddress Flags Mak Iface +192.168.15.2 ether ba:b0:6d:68:09:e9 C veth-red +``` + +## 가상 switch 만들기 + +위와같이 인터페이스를 직접 연결하지 않고, [네트워크 스위치](https://ko.wikipedia.org/wiki/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC_%EC%8A%A4%EC%9C%84%EC%B9%98)를 가상으로 구현하여 연결하는 방법도 있다. + +linux bridge를 통해 가상 switch를 구현하는 방법을 알아보자. + +우선은 호스트에 브릿지 타입의 네트워크 인터페이스를 하나 정의해준다. +```js +ip link add v-net-0 type bridge +ip link set deb v-net-0 up +``` + +이 인터페이스는 브릿지 타입이기 떄문에, 각 네트워크 네임스페이스에서도 보고 접근할 수 있다. + +인터페이스를 통해 스위치와 네임스페이스를 이어주자. + +```js +ip link add veth-red type veth peer name veth-red-br +ip link set veth-red netns red +ip link set veth-red-br master v-net-0 + +ip link add veth-blue type veth peer name veth-blue-br +``` + +그리고 네임스페이스(인터페이스)에 가상 ip를 지정하고 활성화한다. +```js +ip -n red addr add 192.168.15.1 veth-red +ip -n blue addr add 192.168.15.1 veth-blue +ip -n red link set veth-red up +ip -n blue link set veth-blue up +``` + +이렇게 여러 네임스페이스를 연결해주면, 서로 통신할 수 있는 상태가 된다. 하지만 여기까지는 각 네임스페이스끼리만 연결되어있는 상태기 때문에, 외부에서는 이 네임스페이스에 접근할 방법이 아직 없다. + +외부에서 네트워크 네임스페이스에 접근할 수 있도록 하기 위해선, 호스트를 거쳐야한다. 이를 설정해줄 수 있도록 하는 것이 바로 iptable이다. + +호스트로 들어온 트래픽을 어떤 네트워크로 보낼지 구분하기 위해서 들어온 포트에 따라 매핑하는 방식을 사용하는 것이 대표적이다. 여기서 dport와 destination port를 다르게 설정하면, 포트포워딩이 된다. + +```js +iptables -t nat -A PRESOUTING --dport 80 --to-destination 192.168.15.2:80 -j DNAT +``` + +최종적으로 아래와 같은 모양이 된다. + + \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/arp.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/arp.md" new file mode 100644 index 00000000..231b6580 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/arp.md" @@ -0,0 +1,61 @@ +--- +title: 'arp' +lastUpdated: '2024-03-02' +--- + +```bash +/sbin/arp +``` + +시스템 사이의 통신에는 상대방의 MAC 주소가 필요하다. 이때 arp는 ARP를 이용하여 상대 시스템 IP에 신호를 보내 MAC 주소를 받아온다. + +서브넷의 ARP 정보는 연결 효율을 높이기 위해 `/proc/net/arp`에 저장된다. + +이와 같이 저장된 ARP 캐시의 내용(연결하려는 시스템의 MAC 주소)을 자세히 보고 싶다면 다음과 같이 실행한다. + +```bash +$ arp -v +Address HWtype HWaddress Flags Mask Iface +ip-172-18-0-3.ap-northe ether 02:42:ac:12:00:03 C br-58c1503932a1 +ip-172-18-0-2.ap-northe ether 02:42:ac:12:00:02 C br-58c1503932a1 +ip-172-31-32-1.ap-north ether 0a:7e:87:6d:6c:80 C ens5 +ip-172-17-0-4.ap-northe ether 02:42:ac:11:00:04 C docker0 +ip-172-17-0-3.ap-northe ether 02:42:ac:11:00:03 C docker0 +ip-172-17-0-2.ap-northe ether 02:42:ac:11:00:02 C docker0 +Entries: 6 Skipped: 0 Found: 6 +``` + +```bash +$ arp --help +Usage: + arp [-vn] [] [-i ] [-a] [] <-Display ARP cache + arp [-v] [-i ] -d [pub] <-Delete ARP entry + arp [-vnD] [] [-i ] -f [] <-Add entry from file + arp [-v] [] [-i ] -s [temp] <-Add entry + arp [-v] [] [-i ] -Ds [netmask ] pub <-''- + + -a display (all) hosts in alternative (BSD) style + -e display (all) hosts in default (Linux) style + -s, --set set a new ARP entry + -d, --delete delete a specified entry + -v, --verbose be verbose + -n, --numeric don't resolve names + -i, --device specify network interface (e.g. eth0) + -D, --use-device read from given device + -A, -p, --protocol specify protocol family + -f, --file read new entries from file or from /etc/ethers + + =Use '-H ' to specify hardware address type. Default: ether + List of possible hardware types (which support ARP): + ash (Ash) ether (Ethernet) ax25 (AMPR AX.25) + netrom (AMPR NET/ROM) rose (AMPR ROSE) arcnet (ARCnet) + dlci (Frame Relay DLCI) fddi (Fiber Distributed Data Interface) hippi (HIPPI) + irda (IrLAP) x25 (generic X.25) eui64 (Generic EUI-64) +``` + +## 관련 명령어 + +- **arping:** 대상 주소에 ARP 패킷을 보낸다. +- **arpwatch:** Ethernet/IP 주소의 진로를 추적한다. +- **arpsnmp:** Ethernet/IP 주소의 진로를 추적한다. +- **tcpdump:** 네트워크 인터페이스에서의 패킷 헤더를 출력한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/ifconfig.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/ifconfig.md" new file mode 100644 index 00000000..abb8e1e2 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/ifconfig.md" @@ -0,0 +1,74 @@ +--- +title: 'ifconfig' +lastUpdated: '2024-03-02' +--- + +ifconfig는 시스템에 설치된 네트워크 인터페이스 정보를 확인하거나 수정하는 명령어이다. + +`ifconfig [인터페이스][옵션]` 형식으로 입력하며, 아무 옵션 없이 `ifconfig`를 입력하면 현재 설정된 네트워크 인터페이스 상태를 보여준다. `lo`는 루프백 인터페이스로 자기 자신과 통신하는 데 사용하는 가상 장치이며, 흔히 랜카드라고 불리는 유선 네트워크 인터페이스는 `eth0`, 무선 네트워크 인터페이스는 `wlan0`라고 명명한다. + +IP 주소는 호스트에 하나씩 부여되는 것이 아니라 네트워크 인터페이스에 할당되기 때문에 각 네트워크 인터페이스마다 다른 IP 주소를 할당할 수 있다. + +내가 테스트해본 서버에서는 embedded NIC인 `eno1`와 가상 이더넷 인터페이스인 `veth`, 그리고 `lo` 등의 여러 네트워크 인터페이스를 확인해볼 수 있었다. eth는 선으로 연결된 onboard Ethernet, eno는 기기에 장착된 NIC라는 점이 차이이다. + +```bash +$ ifconfig +eno1: flags=4163 mtu 1500 + inet 114.108.176.85 netmask 255.255.255.224 broadcast 114.108.176.95 + inet6 fe80::1602:ecff:fe3d:cf14 prefixlen 64 scopeid 0x20 + ether 14:02:ec:3d:cf:14 txqueuelen 1000 (Ethernet) + RX packets 212179261 bytes 63191358374 (63.1 GB) + RX errors 0 dropped 65 overruns 0 frame 0 + TX packets 59341323 bytes 21948854635 (21.9 GB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 16 + +veth107fd5b: flags=4163 mtu 1500 + inet6 fe80::38ae:d4ff:fe91:238a prefixlen 64 scopeid 0x20 + ether 3a:ae:d4:91:23:8a txqueuelen 0 (Ethernet) + RX packets 6388 bytes 522059 (522.0 KB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 46505 bytes 4299681 (4.2 MB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + +lo: flags=73 mtu 65536 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 1000 (Local Loopback) + RX packets 35482223 bytes 91469141584 (91.4 GB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 35482223 bytes 91469141584 (91.4 GB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +... +``` + +각 네트워크 인터페이스의 필드는 다음을 의미한다. + +- **HWaddr:** 네트워크 인터페이스의 하드웨어 주소(MAC address) +- **inet addr:** 네트워크 인터페이스에 할당된 IP 주소 +- **Bcast:** 브로드캐스트 주소 +- **Mask:** 넷마스트 +- **MTU:** 네트워크 최대 전송 단위(Maximum Transfer Unit) +- **RX packets:** 받은 패킷 정보 +- **TX packets:** 보낸 패킷 정보 +- **collisions:** 충돌된 패킷 수 +- **Interrupt:** 네트워크 인터페이스가 사용하는 인터럽트 번호 + +필요하다면 `ifconfig` 명령으로 네트워크 인터페이스를 작동시키거나 중지시킬 수 있다. 하위 명령 up, down을 추가하여 네트워크 인터페이스를 중지할 수 있다. root 권한이 필요하기 때문에 sudo를 사용해야한다. 예를 들어 eth0를 중지하려면 다음과 같이 입력한다. + +```bash +$ sudo ifconfig eth0 down +``` + +다시 네트워크 인터페이스 eth0를 작동시키려면 하위 명령 up을 사용하는데, 옵션으로 IP 주소를 직접 입력해서 IP주소를 변경할 수 있다. + +```bash +$ sudo ifconfig eth0 192.168.0.254 up +``` + +이렇게 변경된 내용은 다시 ifconfig로 설정을 수정하거나 시스템이 다시 부팅될 때까지 임시로 유지된다. ifconfig나 [route](route.md)와 같은 명령어는 관리 목적으로 네트워크 주소 정보를 잠시 변경할 때 사용하고, 네트워크 설정을 변경하려면 네트워크 인터페이스 설정 파일을 수정해야한다. + +--- +참고 +- https://superuser.com/questions/1053003/what-is-the-difference-between-eth1-and-eno1 +- 책, 리눅스 서버를 다루는 기술, 길벗, 신재훈 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/ping\352\263\274\342\200\205netstat.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/ping\352\263\274\342\200\205netstat.md" new file mode 100644 index 00000000..f8a74fd0 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/ping\352\263\274\342\200\205netstat.md" @@ -0,0 +1,144 @@ +--- +title: 'ping과 netstat' +lastUpdated: '2024-03-02' +--- + +## ping + +ping은 네트워크 연결 상태를 점검하는 명령이다. 목적지에 ICMP(Internet Control Message Protocol) 패킷을 보내고 되돌아오는지 확인하여 연결 상태를 진단한다. + +`ping [옵션] 목적지주소` 형식으로 입력하며 목적지 주소에는 연결 상태를 확인하려는 대상의 주소를 입력한다. IP 주소와 도메인 모두 사용할 수 있다. + +도메인 주소로 ping 명령을 실행하면 DNS 서버에 질의해 대상 컴퓨터의 IP주소를 알아내고 ping을 실시한다. + +```bash +$ ping google.com +PING google.com (172.217.25.174): 56 data bytes +64 bytes from 172.217.25.174: icmp_seq=0 ttl=109 time=102.128 ms +64 bytes from 172.217.25.174: icmp_seq=1 ttl=109 time=34.757 ms +64 bytes from 172.217.25.174: icmp_seq=2 ttl=109 time=34.030 ms +64 bytes from 172.217.25.174: icmp_seq=3 ttl=109 time=34.423 ms +^C +--- google.com ping statistics --- +4 packets transmitted, 4 packets received, 0.0% packet loss +round-trip min/avg/max/stddev = 34.030/51.334/102.128/29.327 ms +``` + +명령어를 치면 일정한 시간 간격으로 응답을 받고 있다는 메시지가 출력된다. 목적지로부터 받는 응답 패킷의 크기는 64byte이며 TTL 값은 109로 나온다. + +결과를 보니 요청 패킷을 4개 보내 응답 패킷을 받았으며 패킷 손실이 없었다는 것을 알 수 있다. + +`-i` 옵션은 요청 패킷을 전송하는 대기 시간을 설정한다. 다음 명령은 5초 간격으로 패킷을 전송할 것이다. + +```bash +$ ping -i 5 google.com +``` + +`-t`로 ttl 값을 0에서 255까지 변경할 수 있다. ICMP 패킷의 ttl을 너무 작게 설정하면 패킷이 목적지에 닿기 전에 자동으로 소멸한다. + +```bash +$ ping -m 1 google.com +PING google.com (172.217.25.174): 56 data bytes +92 bytes from 192.168.1.1: Time to live exceeded +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 d7ae 0 0000 01 01 5907 192.168.1.196 172.217.25.174 +``` + +`-R` 옵션은 요청 패킷이 목적지까지 도달하는 데 거치는 호스트의 IP 주소를 차례로 보여준다. 목적지까지 경로에 문제가 있는지 확인할 때 유용한 옵션이다. + +```bash +$ ping -R google.com +PING google.com (172.217.161.78) 56(124) bytes of data. + 192.168.1.1 + 10.156.144.2 + 172.20.0.229 + 172.16.1.137 + ... +``` + +사용하는 서버의 연결상태를 진단할 때, ifconfig 결과 인터페이스가 정산적으로 올라와있고 route 결과 기본 게이트웨이로 가기 위한 라우팅 테이블도 설정되어 있다면 ping으로 연결 상태를 진단해야 한다. pin을 전략적으로 사용하면 문제의 원인을 알아낼 수 있다. + +아래는 문제를 파악하기 위해 시도해볼 수 있는 명령어들이다. +```bash +$ ping [기본 게이트웨이] # 우선 기본적인 게이트웨이와의 연결을 확인한다. +$ ping [내부 호스트] # 내부 호스트와의 네트워크 연결 상태를 확인할 수 있다. +$ ping 8.8.8.8 # DNS 서버의 IP 주소로 ping하여서 외부와의 연결 상태를 확인한다. +``` + +## netstat + +netstat은 리눅스 네트워크 상태를 종합적으로 보여주는 명령이다. 아무 옵션 없는 `netstat`은 현재 리눅스 서버의 열려있는 모든 소켓에 대한 데이터를 확인할 수 있다. + +```bash +$ netstat +Active Internet connections (w/o servers) +Proto Recv-Q Send-Q Local Address Foreign Address State +... +Active UNIX domain sockets (w/o servers) +Proto RefCnt Flags Type State I-Node Path +unix 2 [ ] DGRAM 18318 /run/systemd/journal/syslog +unix 11 [ ] DGRAM CONNECTED 18327 /run/systemd/journal/dev-log +... +``` + +`-i` 옵션을 사용하면 네트워크 인터페이스를 통해 주고 받은 패킷에 대한 정보를 확인할 수 있다. + +```bash +$ netstat -i +Kernel Interface table +Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg +docker0 1500 4516881 0 0 0 4453333 0 0 0 BMRU +docker_g 1500 31766422 0 0 0 30192881 0 0 0 BMRU +eno1 1500 212252892 0 65 0 59361573 0 0 0 BMRU +eno2 1500 0 0 0 0 0 0 0 0 BMU +eno3 1500 0 0 0 0 0 0 0 0 BMU +``` + +`-nr` 옵션을 사용하면 라우팅 테이블 정보를 확인할 수 있다. + +```bash +$ netstat -nr +Kernel IP routing table +Destination Gateway Genmask Flags MSS Window irtt Iface +0.0.0.0 114.108.176.65 0.0.0.0 UG 0 0 0 eno1 +114.108.176.64 0.0.0.0 255.255.255.224 U 0 0 0 eno1 +``` + +프로토콜에 따른 패킷 통계를 확인하기 위해서는 `-s` 옵션을 사용한다. 어떤 프로토콜이 제대로 동작하지 않는지, 쓸모없는 프로토콜이 동작하고 있는건 아닌지 확인할 수 있다. + +```bash +$ netstat -s +Ip: + Forwarding: 1 + 137414135 total packets received + 35886764 forwarded + 385 with unknown protocol + 0 incoming packets discarded + 100479457 incoming packets delivered + 122011477 requests sent out + 20 outgoing packets dropped + 52 reassemblies required + 26 packets reassembled ok +Icmp: + 3646098 ICMP messages received + 107 input ICMP message failed + ICMP input histogram: + destination unreachable: 5301 + timeout in transit: 264 +... +``` + +`-atp`를 사용하면 열려있는 포트와 데몬, 그리고 그 포트를 사용하는 프로그램에 대한 정ㅇ보를 상세히 확인할 수 있다. +```bash +$ netstat -atp +(Not all processes could be identified, non-owned process info + will not be shown, you would have to be root to see it all.) +Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +tcp 0 0 0.0.0.0:2001 0.0.0.0:* LISTEN - +tcp 0 0 0.0.0.0:cisco-sccp 0.0.0.0:* LISTEN - +tcp 0 0 0.0.0.0:2003 0.0.0.0:* LISTEN - +tcp 0 0 0.0.0.0:2002 0.0.0.0:* LISTEN - +tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN - +... +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/route\353\241\234\342\200\205\353\235\274\354\232\260\355\214\205\342\200\205\355\205\214\354\235\264\353\270\224\342\200\205\355\231\225\354\235\270\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/route\353\241\234\342\200\205\353\235\274\354\232\260\355\214\205\342\200\205\355\205\214\354\235\264\353\270\224\342\200\205\355\231\225\354\235\270\355\225\230\352\270\260.md" new file mode 100644 index 00000000..e041d090 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Network/\354\243\274\354\232\224\353\252\205\353\240\271\354\226\264/route\353\241\234\342\200\205\353\235\274\354\232\260\355\214\205\342\200\205\355\205\214\354\235\264\353\270\224\342\200\205\355\231\225\354\235\270\355\225\230\352\270\260.md" @@ -0,0 +1,48 @@ +--- +title: 'route로 라우팅 테이블 확인하기' +lastUpdated: '2024-03-02' +--- + +네트워크를 통해 목적지로 패킷이 전송될 경로를 지정해주는 것을 라우팅이라고 한다. 리눅스 시스템은 미리 설정되어 있는 라우팅 테이블이라는 지도를 보고 패킷을 어떤 네트워크를 거치게 할지 결정한다. + +라우팅 테이블을 확인하거나 설정하는 명령은 `route`이다. 옵션 없이 route 명령을 입력하면 현재 설정되어 있는 라우팅 테이블을 확인한다. + +```bash +$ route +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +default _gateway 0.0.0.0 UG 0 0 0 eno1 +114.108.176.64 0.0.0.0 255.255.255.224 U 0 0 0 eno1 +172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 +172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker_gwbridge +``` + +라우팅 테이블의 각 필드는 다음을 의미한다. + +- **Destination:** 목적지 +- **Gateway:** 외부 네트워크와 연결하기 위한 게이트웨이 주소 +- **Genmask:** 목적지 네트워크의 넷마스크 주소. 255.255.255.255로 지정되어 있으면 목적지 호스트의 주소, 0.0.0.0으로 지정되어 있으면 기본 게이트웨이 주소를 의미한다. +- **Flags:** 해당 경로에 대한 정보를 알려주는 기호. U(up)는 이 경로가 살아있는 상태임을, H(host)는 목적지가 호스트 주소라는 사실을, G(gateway)는 게이트웨이를 향하는 경로를 의미한다. +- **Metric:** 목적지 네트워크까지의 거리 +- **Ref:** 경로를 참조한 횟수 +- **Use:** 경로를 탐색한 횟수 +- **Iface:** 패킷이 오가는 데 사용할 네트워크 인터페이스 + +라우팅 테이블을 통해 패킷을 전달하는 과정은 다음과 같다. + +1. 패킷의 목적지 IP를 읽는다. +1. 각 행을 순회하면서 Genmask 값을 읽어서 패킷의 목적지 IP와 AND 연산한다. +2. 해당 값이 테이블에 저장된 Destination과 같은 값이면 그 행의 Iface가 가리키는 네트워크 인터페이스로 보낸다. 일치하지 않으면 다음 행의 Genmask 값을 읽어서 같은 작업을 되풀이하고 어느 행과도 일치하지 않는다면 패킷을 보내지 않는다. + +라우팅 테이블에 기본 게이트웨이를 추가하는 방법은 다음과 같다. + +```bash +$ sudo route detault gw 192.168.0.1 +``` + + + +--- +참고 +- +- 책, 리눅스 서버를 다루는 기술, 길벗, 신재훈 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/Deamon\342\200\205process.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/Deamon\342\200\205process.md" new file mode 100644 index 00000000..ac06f9e8 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/Deamon\342\200\205process.md" @@ -0,0 +1,62 @@ +--- +title: 'Deamon process' +lastUpdated: '2024-03-02' +--- + +Deamon Process란 메모리에 상주하면서 요청이 들어올 때마다 명령을 수행하는 프로세스이다. (백그라운드에서 작동) + +## 작동 방식 + +Deamon process의 작동 방식은 두가지가 있다. + +### standalone 방식 + +- 각 데몬 프로세스들이 독립적으로 수행되며, 항상 메모리에 상주하는 방식 +- 자주 실행되는 데몬 프로세스에 적용되는 방식이다. +- `/etc/rc.d/init.d` 디렉터리에 위치 +- 웹 서비스 데몬(apached, httpd, mysqld 등), 메일 서비스 데몬(sendmail 등), NFS 등 서비스 요청이 많은 프로세스들이 standalone 방식으로 작동한다. + +### (x)inetd 방식 (Super deamon) +- (x)inted 이라는 Super deamon이 서비스 요청을 받아 해당 데몬을 실행시켜 요청을 처리하는 방식 + - (x)inted는 기존의 inetd를 대체하는 보안이 강화된 오픈 소스 슈퍼 데몬이다. +- 서비스 속도는 standalone 방식보다 느리지만, (x)inted 데몬만 메모리에 상주해 있기 때문에 메모리를 많이 필요로 하지 않는다. +- `/etc/xinetd.d` 디렉터리에 위치 +- telnetd, ftpd, pop3d, rsyncd 등의 서비스들이 Super deamon 방식으로 작동한다. + +**(x)inted의 특징** +- TCP Wrapper 와 유사한 접근 제어 기능을 갖는다. +- RPC 요청에 대한 지원이 미비하지만, 포트맵(Portmap)으로 해결 가능 • standalone 과의 비교 : 12 페이지 참고 2014(1) 2015(1) +- 주요 기능 + - DoS 공격에 대한 효과적인 억제 + - 로그 파일 크기 제한 + - IP 주소 당 동시 접속 수 제한 + - TCP/UDP 및 RPC 서비스들에 대한 접근제어 + +**설정 파일 구조** (`/etc/xinetd.d/*`) +```c +$ vi /etc/xinetd.d/telnet +service telnet { + disable = no // 서비스 사용 설정 + socket_type = stream // tcp = stream , udp = dgram + wait=no // 요청을 받은 후 즉시 다음 요청 처리(no) + user = root // 실행할 사용자 권한 설정 + server = /usr/sbin/in.telnetd // 서비스 실행 파일 경로 + log_type = FILE /var/log/xinetd.log // 로그를 기록할 파일 경로 (FILE 선택자 사용 시) + log_on_failure += USERID // 로그인 실패 시 로그에 기록할 내용 + no_access = 10.0.0.0/8 // 접속 거부할 IP 대역 + only_from = 192.168.10.0/24 // 접속 허용할 IP 대역 + cps = 10 30 // 최대 접속 수 제한. 지정한 접속 수 초과할 시 지정 시간동안 서비스가 비활성화됨 + instances = 5 // 동시에 작동할 수 있는 최대 갯수 + access_times = 08:00-17:00 // 접속 허용 시간대 +} +``` + +**TCP Wrapper** +- Super deamon에 의해 수행되는 호스트 기반의 네트워킹 ACL(Access Control List) 시스템 +- `/etc/hosts.allow` 파일과 `/etc/hosts.deny` 파일에 정의된 호스트 정보를 기준으로 접근 통제 +- 접근 통제 파일 참조 순서 :`/etc/hosts.allow` → `/etc/hosts.deny` → 두 파일에 없으면 모든 접근 허용 + +--- +참고 +- http://litux.nl/Reference/Books/7213/ddu0139.html +- https://man7.org/linux/man-pages/man7/daemon.7.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/httpd.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/httpd.md" new file mode 100644 index 00000000..1ddfdf23 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/httpd.md" @@ -0,0 +1,54 @@ +--- +title: 'httpd' +lastUpdated: '2024-03-02' +--- + +- HTTPD(HyperText Transfer Protocol Daemon) is a software program or service that runs on a web server and handles the processing of HTTP requests and responses. + +- HTTPD is often used as a synonym for a web server, specifically the Apache HTTP Server, which is one of the most widely used web server software applications. + +### Feature + +- The HTTPD or web server software listens for incoming requests from clients (such as web browsers) and delivers the requested web pages or resources over the Internet. When a client makes a request for a particular URL (Uniform Resource Locator), the HTTPD processes the request, retrieves the requested resource from the server's file system, and sends it back to the client. + +- HTTPD supports various HTTP methods, including GET, POST, PUT, DELETE, and more, which enable different types of interactions between clients and servers. + +- It also supports features like virtual hosting, which allows hosting multiple websites on a single server, and provides configuration options for customizing server behavior, security settings, and performance optimizations. + +- The Apache HTTP Server (httpd) is an open-source HTTPD software widely used due to its flexibility, scalability, and extensive feature set. However, it's important to note that other web servers, such as Nginx and Microsoft IIS, also exist and serve similar purposes, though they may have different configuration and functionality. + +### Deamon process + +- HTTPD is an application that runs as a [daemon process](Deamon%E2%80%85process.md) in Linux. + - In a Linux system, a daemon is a background process that runs independently of user interaction and typically provides services or functions for the operating system or other applications. + +- When you install and configure Apache HTTP Server on a Linux system, it runs as a daemon process called "httpd" or "apache2" (depending on the distribution). This daemon process listens for incoming HTTP requests and serves web pages and resources to clients. + +- The daemon process is started during system boot or manually by the system administrator. It runs continuously in the background, waiting for incoming connections, handling requests, and responding to clients. The process is designed to be long-running and provides the necessary functionality to serve web content efficiently and securely. + +- By running as a daemon process, Apache HTTP Server can operate independently of user sessions, allowing it to serve web pages and handle requests even when no users are actively logged into the system. This enables the server to provide continuous web services to clients without requiring direct user intervention or control. + +### Advantage + +1. **Extensive module ecosystem:** Apache HTTP Server has a vast library of modules that extend its functionality. These modules enable features such as SSL/TLS encryption, server-side scripting languages (e.g., PHP, Python), authentication mechanisms, caching, proxying, and more. The modular architecture allows users to tailor the server to their specific needs. + +2. **High performance and scalability:** Apache HTTP Server is known for its efficiency and scalability. It can handle a large number of simultaneous connections and requests, making it suitable for high-traffic websites and applications. It also supports advanced features like load balancing and caching, which contribute to improved performance. + +3. **Robust security features:** Apache HTTP Server includes various security features, such as SSL/TLS encryption, access control mechanisms, and support for secure protocols. It also integrates with other security tools and modules to enhance server security. + + +### Disadvantage + +1. **Resource consumption:** While Apache HTTP Server is highly scalable, it can consume significant system resources, especially when serving large numbers of concurrent connections. This may impact server performance and require efficient resource management. + +2. **Performance in certain scenarios:** In certain scenarios, such as serving static files or handling high volumes of concurrent long-lived connections, other web servers like Nginx or specialized servers may outperform Apache HTTP Server. These servers are optimized for specific use cases and may offer better performance in those scenarios. + +3. **Lack of built-in event-driven architecture:** Apache HTTP Server primarily uses a process-based, multi-threaded architecture. While it offers good performance, event-driven architectures like those used by Nginx can provide even better scalability and efficiency in handling large numbers of concurrent connections. + +4. **Maintenance and updates:** As with any software, Apache HTTP Server requires regular maintenance and updates to address security vulnerabilities, bug fixes, and feature enhancements. Keeping the server up to date may require some effort and attention to ensure stability and security. + +--- +reference +- https://httpd.apache.org/docs/2.4/programs/httpd.html +- https://en.wikipedia.org/wiki/Httpd +- https://www.techtarget.com/whatis/definition/Hypertext-Transfer-Protocol-daemon-HTTPD \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/pipe.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/pipe.md" new file mode 100644 index 00000000..45a8811d --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/pipe.md" @@ -0,0 +1,101 @@ +--- +title: 'pipe' +lastUpdated: '2024-03-02' +--- + +Linux에서 pipe는 한 프로세스에서 다른 프로세스로 정보를 넘겨주는 방법 중 하나이다. + +아래와 같이 사용하면 왼쪽 명령어의 결과(output)을 오른쪽에 있는 명령어에 입력(input)으로 전달한다. 즉, 좌측의 stdout을 우측의 stdin으로 넘긴다고 생각하면 된다. + +```bash +$ ps -ef | grep bash +``` + +> Redirect(`>`)는 프로그램의 결과 혹은 출력(output)을 파일이나 다른 스트림으로 넘길 때 사용되는 반면, pipe(`|`)는 프로세스로 넘겨준다는 점이 차이이다. + +## c언어에서의 pipe + +`unistd.h` 헤더의 pipe 함수를 사용하면 프로그램 내에서 pipe를 사용할 수 있다. + +```c +#include +int pipe(int filedes[2]); +``` + +인자로 받는 파일기술자 배열에서 `[0]`은 읽기용이고 `[1]`은 쓰기용이다. 호출이 성공하면 0을 반환하고, 실패하면 -1을 반환한다. + +파일기술자는 파이프 용 특수 파일을 가리키게 되는데, 일반 파일을 다루는 것과 동일하게 사용하면 된다. + +### 예제 + +pipe를 활용한 프로그래밍 예제를 살펴보자. + +코드는 아래와 같은 동작을 수행한다. + +- **부모 프로세스:** + - for 루프를 통해 msg 배열의 각 문자열을 파이프를 통해 자식 프로세스로 보낸다. 각각의 문자열을 buffer에 복사하고, `write()` 함수를 사용하여 파이프에 쓰기 작업을 수행한다. + - 첫 버퍼를 출력해보고 버퍼에 msg의 첫 요소를 다시 삽입한다. + - 마지막으로 "bye!"라는 메시지를 출력한다. + +- **자식 프로세스:** + - 파이프를 통해 받은 데이터를 (`read()` 함수를 사용하여) 읽고, 해당 값을 출력한다. 이 과정을 for 루프 안에서 세 번 반복한다. + - 마지막으로 "bye!"라는 메시지를 출력한다. + +```c +#include +#include +#include +#include +#include + +#define SIZE 512 + +int main() { + char *msg[] = {"apple is red", "banana is yellow", "cherry is red"}; + char buffer[SIZE]; + int filedes[2], nread, i; + pid_t pid; + + if (pipe(filedes) == -1) { + printf("fail to call pipe()\n"); + exit(1); + } if ((pid = fork()) == -1) { + printf("fail to call fork()\n"); + exit(1); + } else if (pid > 0) { + for(i = 0 ; i < 3 ; i++){ + strcpy(buffer, msg[i]); + write(filedes[1], buffer, SIZE); + } + nread = read(filedes[0], buffer, SIZE); + printf("[parent] %s\n", buffer); + + write(filedes[1], buffer, SIZE); + printf("[parent] bye!\n"); + } else { + for (i = 0 ; i < 3 ; i++){ + nread = read(filedes[0], buffer, SIZE); + printf("[child] %s\n", buffer); + } + printf("[child] bye!\n"); + } +} +``` + +출력 결과는 아래와 같다. + +```c +[parent] apple is red +[parent] bye! +[child] banana is yellow +[child] cherry is red +[child] apple is red +[child] bye! +``` + +--- +참고 +- 리눅스 시스템 프로그래밍 수업 +- https://tldp.org/LDP/lpg/node11.html +- https://man7.org/linux/man-pages/man2/pipe.2.html +- https://twpower.github.io/133-difference-between-redirect-and-pipe \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/signal.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/signal.md" new file mode 100644 index 00000000..c925d879 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/signal.md" @@ -0,0 +1,99 @@ +--- +title: 'signal' +lastUpdated: '2024-03-02' +--- + +리눅스에서는 프로세스끼리 서로 통신할 때 정해진 signal을 사용한다. + +리눅스에서 사용하는 signal에는 사용자의 key interrupt로 인한 signal, 프로세스나 하드웨어가 발생시키는 signal 등 다양한 종류가 있다. + +signal 목록을 확인하고 싶다면 `kill -l` 명령어를 치면 된다. (`man 7 signal`으로 더 자세하게 확인할 수도 있다.) + +```bash +$ kill -l + 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP + 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 +11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM +16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP +21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ +26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR +31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 +38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 +43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 +48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 +53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 +58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 +``` + +`kill`은 특정 프로세스에 signal을 보내기 위한 명령어이기도 하다. + +프로세스를 종료할 때 사용하는 `kill -9 [pid]` 명령어 또한, 9번인 `SIGKILL` signal을 프로세스에 보내 종료시키는 것 이었다...! + +## signal handler + +프로세스가 signal을 받았을 때는 기본적으로 정의되어있는 동작에 따르지만, 프로세스가 특정 signal을 포착했을 때 수행해야한 별도의 함수는 signal handler로 따로 정의할 수 있다. + +프로세스는 signal을 포착했을 떄 작업을 일시중단하고, handler를 수행한 다음 중단된 작업을 재개한다. + +## 주로 쓰이는 sinal + +자주 사용되는 signal을 몇 개 알아보자. + +- SIGHUP(1) + + - SIGHUP을 프로세스에게 주면 해당 pid 프로세스가 다시 시작된다. 그래서 데몬 프로세스의 설정을 마치고 설정 내용을 재적용시킬 때 자주 사용된다. + +- SIGKILL(9) vs SIGTERM(15) + + - 둘 다 프로세스를 종료하는 signal이지만 sigkill은 자식 프로세스까지 동시에 즉시 삭제해버리고, sigterm은 자식 프로세스는 건드리지 않는 채 graceful하게 삭제한다. + +- SIGSTOP(19) vs SIGTSTP(20) + + - 둘 다 프로세스를 대기(정지) 시키는 명령어이다. + + - `SIGTSTP`는 유저가 키보드로 `Ctrl+Z`를 입력했을 때 쓰이는 signal이고 handler 처리가 가능한 반면에, `SIGSTOP`는 잡아서 처리할 수 없다. + +## signal 종류 + +- 아래는 31번까지의 signal의 종류와 동작을 나타내는 표이다. +- 이후 번호는 `SIGRTMIN`, `SIGRTMAX`인데, 실시간 통신과 같은 특정 용도로 Custom하여 사용할 수 있다. + +|번호|signal 이름|발생 및 용도|defailt action|리눅스 버전| +|-|-|-|-|-| +|1|SIGHUP(HUP)|hangup signal; 전화선 끊어짐|종료|POSIX| +|2|SIGINT(INT)|interrupt signal; `Ctrl + c`|종료|ANSI| +|3|SIGQUIT(QUIT)|quit signal; `Ctrl + \` |종료(코어덤프)|POSIX| +|4|SIGILL(ILL)|잘못된 명령||ANSI| +|5|SIGTRAP(TRAP)|트렙 추적||POSIX| +|6|SIGIOT(IOT)|IOT 명령||4.2 BSD| +|7|SIGBUS(BUS)|버스 에러||4.2 BSD| +|8|SIGFPE(FPE)|부동 소수점 에러|종료|ANSI| +|9|SIGKILL(KILL)|무조건적으로 즉시 중지한다.|종료|POSIX| +|10|SIGUSR1(USR1)|사용자 정의 signal1|종료|POSIX| +|11|SIGSEGV(SEGV)|세그멘테이션 위반||ANSI| +|12|SIGUSR2(USR2)|사용자 정의 signal2|종료|POSIX| +|13|SIGPIPE(PIPE)|읽으려는 프로세스가 없는데 파이프에 쓰려고 함|종료|POSIX| +|14|SIGALRM(ALRM)|경보(alarm) signal; alarm(n)에 의해 n초 후 생성됨|종료|POSIX| +|15|SIGTERM(TERM)|일반적으로 kill signal이 전송되기 전에 전송된다. 잡히는 signal이기 때문에 종료되는 것을 트랙할 수 있다.|종료|ANSI| +|16|SIGTKFLT|코프로세서 스택 실패||| +|17|SIGCHLD(CHLD)|프로세스 종료시 그 부모 프로세스에게 보내지는 signal|무시|POSIX| +|18|SIGCONT(CONT)|STOP signal 이후 계속 진행할 때 사용. ; 정지 되지 않은 경우 무시됨|POSIX| +|19|SIGSTOP(STOP)|정지 signal; SIGSTP과 같으나 잡거나 무시할 수 없음|프로세스 정지|POSIX| +|20|SIGTSTP(TSTP)|키보드에 의해 발생하는 signal로 `Ctrl + Z`로 생성된다. ; 터미널 정지 문자|프로세스 정지|POSIX| +|21|SIGTTIN|백그라운드에서의 제어터미널 읽기|프로세스 정지|POSIX| +|22|SIGTTOU|백그라운드에서의 제어터미널 쓰기|프로세스 정지|POSIX| +|23|SIGURG|소켓에서의 긴급한 상태||4.2 BSD| +|24|SIGXCPU|CPU 시간 제한 초과 `setrlimit(2)`||4.2 BSD| +|25|SIGXFSZ|파일 크기제한 초과 `setrlimit(2)`||4.2 BSD| +|26|SIGVTALRM|가상 시간 경고 `setitimer(2)`||4.2 BSD| +|27|SIGPROF|프로파일링 타이머 경고. `setitimer(2)` ||4.2 BSD| +|28|SIGWINCH|윈도우 사이즈 변경4.3 BSD, Sun| +|29|SIGIO|기술자에서 입출력이 가능함. `fcntl(2)`||4.2 BSD| +|30|SIGPWR|전원 실패||System V| +|31|UNUSED|사용 안함||| + +--- +참고 +- https://linuxhandbook.com/sigterm-vs-sigkill/ +- https://stackoverflow.com/questions/31882797/how-to-use-sigrtmax-and-sigrtmin +- linux man \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/top.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/top.md" new file mode 100644 index 00000000..1b0382ad --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/top.md" @@ -0,0 +1,75 @@ +--- +title: 'top' +lastUpdated: '2024-03-02' +--- + +- top 명령어를 사용하면 시스템의 상태를 전반적으로 가장 빠르게 파악할 수 있다. (CPU, Memory, Process) +- 옵션 없이 입력하면 interval 간격(기본 3초)으로 화면을 갱신하며 정보를 보여준다. + +```bash +$ top -help + procps-ng 3.3.17 +Usage: + top -hv | -bcEeHiOSs1 -d secs -n max -u|U user -p pid(s) -o field -w [cols] +``` + +```bash +top - 21:22:27 up 232 days, 2:22, 3 users, load average: 0.00, 0.04, 0.05 +Tasks: 134 total, 1 running, 133 sleeping, 0 stopped, 0 zombie +%Cpu(s): 0.8 us, 0.5 sy, 0.0 ni, 98.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +MiB Mem: 3712.0 total, 181.8 free, 2010.1 used, 1520.0 buff/cache +MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1419.4 avail Mem + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 1 root 20 0 174752 12136 8484 S 0.3 0.3 516:12.38 systemd + 2402 root 20 0 1498416 25636 7104 S 0.3 0.7 741:07.27 containerd + 705636 ubuntu 20 0 10892 3840 3144 R 0.3 0.1 0:00.01 top +1677876 root 20 0 712200 6344 3292 S 0.3 0.2 26:35.67 containerd-shim + 2 root 20 0 0 0 0 S 0.0 0.0 0:00.83 kthreadd + 3 root 0 -20 0 0 0 I 0.0 0.0 0:00.00 rcu_gp +... +``` + +- load average: 현재 시스템이 얼마나 일을 하는지를 나타냄. 3개의 숫자는 1분, 5분, 15분 간의 평균 실행/대기 중인 프로세스의 수. CPU 코어수 보다 적으면 문제 없음 +- Tasks: 프로세스 개수 +- KiB Mem, Swap: 각 메모리의 사용량 +- PR: 실행 우선순위 +- VIRT, RES, SHR: 메모리 사용량 => 누수 check 가능 +- S: 프로세스 상태(작업중, I/O 대기, 유휴 상태 등) + +- VIRT + - 프로세스가 사용하고 있는 virtual memory의 전체 용량 + - 프로세스에 할당된 가상 메모리 전체 + - SWAP + RES +- RES + - 현재 프로세스가 사용하고 있는 물리 메모리의 양 + - 실제로 메모리에 올려서 사용하고 있는 물리 메모리 + - 실제로 메모리를 쓰고 있는 RES가 핵심! +- SHR + - 다른 프로세스와 공유하고 있는 shared memory의 양 + - 예시로 라이브러리를 들 수 있음. 대부분의 리눅스 프로세스는 glibc라는 라이브러리를 참고하기에 이런 라이브러리를 공유 메모리에 올려서 사용 + +**top 실행 후 명령어** + +- `shift + p`: CPU 사용률 내림차순 +- `shit + m`: 메모리 사용률 내림차순 +- `shift + t`: 프로세스가 돌아가고 있는 시간 순 +- `k`: kill. k 입력 후 PID 번호 작성. signal은 9 +- `f`: sort field 선택 화면 -> q 누르면 RES순으로 정렬 +- `a`: 메모리 사용량에 따라 정렬 +- `b`: Batch 모드로 작동 +- `1`: CPU Core별로 사용량 보여줌 + +- ps와 top의 차이점 + - ps는 ps한 시점에 proc에서 검색한 cpu 사용량을 출력한다. + - top은 proc에서 일정 주기로 합산해 실시간 cpu 사용량을 출력한다. + +## Memory Commit + +- 프로세스가 커널에게 필요한 메모리를 요청하면 커널은 프로세스에 메모리 영역을 주고 실제로 할당은 하지 않지만 해당 영역을 프로세스에게 주었다는 것을 저장해둔다. +- 이런 과정을 Memory commit이라 부른다. + +- 왜 커널은 프로세스의 메모리 요청에 따라 즉시 할당하지 않고 Memory Commit과 같은 기술을 사용해 요청을 지연시킬까? + - `fork()`와 같은 새로운 프로세스를 만들기 위한 콜을 처리해야 하기 때문이다. + - `fork()` 시스템 콜을 사용하면 커널은 실행중인 프로세스와 똑같은 프로세스를 하나 더 만들고, `exec()` 시스템 콜을 통해 다른 프로세스로 변한다. + - 이 때 확보한 메모리가 쓸모 없어질 수 있으므로, `COW(Copy-On-Write)` 기법을 통해 복사된 메모리 영역에 실제 쓰기 작업이 발생한 후 실질적인 메모리 할당을 진행 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\352\264\200\353\246\254.md" new file mode 100644 index 00000000..a6ebdd3a --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/\355\224\204\353\241\234\354\204\270\354\212\244\342\200\205\352\264\200\353\246\254.md" @@ -0,0 +1,188 @@ +--- +title: '프로세스 관리' +lastUpdated: '2024-03-02' +--- + +- 시스템이 구동 될 때, 커널은 `/etc` 에 위치한 init 이라는 스크립트를 실행함으로써 시스템 서비스들을 차례대로 시작시킨다. 이 서비스들은 데몬 프로그램(background)으로 구현되어 있기 때문에 로그인하지 않은 상태에서도 필요 작업들을 수행한다. + +- 프로그램은 프로그램을 실행시킬 수 있는데, 이를 부모와 자식 프로세스라고 표현한다. + +### ps + +- 리눅스는 다중 사용자, 사용 작업 시스템이기 때문에 여러 개의 프로세스를 동시에 수행하기 때문에 항상 어떤 프로세스들이 실행되고 있는지 모니터링할 필요가 있다. ps는 이를 위해 현재 시스템에서 실행 중인 프로세스에 관한 정보를 출력하는 명령어이다. + +```bash +# /proc 디렉터리 이하에 프로세스와 연관된 가상 파일시스템의 내용을 토대로 프로세스 정보를 출력한다. +$ ps --help l + +Usage: + ps [options] + +Selection by list: + -C command name + -G, --Group real group id or name + -g, --group session or effective group name + -p, p, --pid process id + --ppid parent process id + -q, q, --quick-pid + process id (quick mode) + -s, --sid session id + -t, t, --tty terminal + -u, U, --user effective user id or name + -U, --User real user id or name + + The selection options take as their argument either: + a comma-separated list e.g. '-u root,nobody' or + a blank-separated list e.g. '-p 123 4567' +``` + +> 부모 프로세스와 자식 프로세스의 관계를 보려면 `ps -ef`, 프로세스 상태를 보는 용도로는 `ps aux`를 주로 사용한다. + +### pstree + +- 프로세스를 트리 형태로 출력한다. + +```bash +$ pstree [옵션] +systemd─┬─ModemManager───2*[{ModemManager}] + ├─acpid + ├─2*[agetty] + ├─amazon-ssm-agen─┬─ssm-agent-worke───8*[{ssm-agent-worke}] + │ └─8*[{amazon-ssm-agen}] + ... + +Display a tree of processes. + + -a, --arguments show command line arguments + -A, --ascii use ASCII line drawing characters + -c, --compact-not don't compact identical subtrees + -C, --color=TYPE color process by attribute +``` + +### pgrep + +- 지정한 패턴과 일치하는 프로세스의 정보를 출력한다. + +- 파일 패턴 검색 명령어인 grep 명령어의 프로세스 버전이라고 보면된다. + +```bash +$ pgrep --help + +Usage: + pgrep [options] + +Options: + -d, --delimiter specify output delimiter + -l, --list-name list PID and process name + -a, --list-full list PID and full command line + -v, --inverse negates the matching + -w, --lightweight list all TID + -c, --count count of matching processes + -f, --full use full process name to match +``` + +--- + +## 프로세스 전환 (foreground / background) + +- **foreground:** 보통 터미널에서 명령어를 입력하면 그 명령이 끝날때 까지 다른 명령어를 입력할 수가 없다. 이렇게 대화식으로 하나씩 주고 받게 되는데 이를 foreground라고 생각하면 된다. + +- **background:** 반면 background는 명령어를 입력하면 다른 명령어도 실행이 가능하다. background라는 의미에서 유추할 수 있듯이 뒤에서 실행된다고 생각하면 된다. 물론 터미널이 닫히거나 로그아웃 될경우 종료 된다. + +### jobs + +- `jobs` 명령어는 현재 돌아가고 있는 background 프로세스 리스트를 모두 출력해준다. + +- background 프로세스는 스택처럼 쌓이는데, +는 스택의 가장 위에 있다는 뜻이고 -는 바로 그다음 밑에 있다는 뜻이다. + +### background processing (&) + +- 명령어들을 background에서 처리하면 foreground에서는 다른 작업을 할 수 있으며 동시에 여러 작업을 수행할 수 있다. + +- 명령어의 맨 끝에 `&` 를 붙여 사용하면 background로 명령어가 실행된다. + +### foreground -> background (bg) + +- foreground에서 실행중인 명령에서 `Ctrl + z` 를 누른후 (일시정지) `bg [job번호]` 명령을 치면, background로 전환된다. + +### background -> foreground (fg) + +- fg는 background에서 foreground로 전환하는 명령어이다. job 번호를 생략하면 스택 제일 위에 있는 것이 foreground로 들어온다. + +- background로 실행시킨 프로세스를 일시중지 시키려면 fg로 가져와서 `Ctrl + z` 해주면 된다. + +```bash +# background에 멈춰있던 잡을 foreground로 보내 셸 프롬프트상에서 다시 실행하는 명령어 +$ fg [job 번호] +``` + +--- + +## 프로세스 우선순위 + +- 스케줄링시 반영되는 우선순위를 관리할 수 있다. + +### nice + +- 프로세스의 우선순위를 의미하는 nice 값을 설정한다. + +- nice 명령어의 우선순의 숫자 조절 연산은, 대시(`-`) 하나는 증가, 두개(`--`)는 감소이다. + +#### [우선순위 NI 값 범위] + +- 사용자가 지정할 수 있는 우선순위 값은 -20부터 19까지인데 **값이 낮아야 우선순위가 높으므로**, 가장 높은 우선순위는 -20이 되게 된다. + - 가장 높은 우선순위 NI 값 : -20 + - 가장 낮은 우선순위 NI 값 : 19 + - 기본적으로 주어지는 디폴트 값: 0 + +```bash +# 기존 우선순위 값에 NI만큼 더한 우선순위로 [프로세스명]을 실행시킨다. +$ nice -n [NI값] [프로세스명] + +# -n 옵션 없이 사용할 경우 NI값은 디폴트로 10을 갖는다. 즉 10을 더해 우선순위를 낮춰서 실행시키는 것 +$ nice [프로세스명] + +# nice -n N 한 것과 동일. NI값만큼 증가 +$ nice -[NI값] 프로세스명 + +# NI값만큼 감소 +$ nice --[NI값] 프로세스명 +``` + +### renice + +- 현재 실행중인 프로세스의 nice값을 변경한다. + +- nice는 프로세스명으로 우선순위를 조정하고, 명령을 실행하면 새로운 프로세스가 발생하지만, renice는 PID로 우선순위를 조정하고 기존의 프로세스 우선순위 값을 추가없이 바로 수정한다. + +```bash +$ renice [옵션] [변경할 NI값] [PID] + +$ renice --help + +Usage: + renice [-n] [-p|--pid] ... + renice [-n] -g|--pgrp ... + renice [-n] -u|--user ... + +Alter the priority of running processes. + +Options: + -n, --priority specify the nice value + -p, --pid interpret arguments as process ID (default) + -g, --pgrp interpret arguments as process group ID + -u, --user interpret arguments as username or user ID + + -h, --help display this help + -V, --version display version +``` + +### nohup + +- 일반적으로 시스템에서 로그아웃하면 로그아웃한 세션과 연관된 모든 프로세스에게 HUP 시그널(1)을 보내서 관련된 모든 프로세스가 자동으로 종료되지만, nohup을 사용하면 해당 시그널을 가로채 무시하기 때문에 로그아웃하더라도 프로세스를 계속 실행되게 된다. +- 즉, 사용자가 로그아웃해도 실행중인 프로세스를 백그라운드로 유지시켜주는 명령어이다. + +```bash +$ nohup command +``` + diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/\355\231\230\352\262\275\353\263\200\354\210\230\354\231\200\342\200\205\355\224\204\353\241\234\354\204\270\354\212\244.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/\355\231\230\352\262\275\353\263\200\354\210\230\354\231\200\342\200\205\355\224\204\353\241\234\354\204\270\354\212\244.md" new file mode 100644 index 00000000..225df43d --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Process/\355\231\230\352\262\275\353\263\200\354\210\230\354\231\200\342\200\205\355\224\204\353\241\234\354\204\270\354\212\244.md" @@ -0,0 +1,41 @@ +--- +title: '환경변수와 프로세스' +lastUpdated: '2024-03-02' +--- + +환경변수는 시스템의 속성을 기록하고 있는 변수이다. + +​일반적으로 프로세스의 환경변수는 프로세스 자신의 정보를 저장하거나 변경하는 용도로 사용된다. 즉, 프로세스 간 통신이 주 목적인 개체는 아니며, 이를 통한 IPC는 부가적인 기능이 된다. 환경변수는 아래 그림처럼 구성된다. + +![image](https://user-images.githubusercontent.com/81006587/225578535-e56a247f-779c-4990-af6f-5f7afbacdf47.png) + +이 환경변수는 보통 Shell에서 정의하고 저장하는,데 Shell에서 프로그램을 실행시키면 프로그램이 시작하는 순간 셸의 환경변수를 모두 복사해서 프로세스이 들고 있게 된다. + +따라서 그 이후에 셸의 환경변수를 바꾼다고 해도 이미 시작한 프로그램의 환경변수는 바뀌지 않는다. 이후 새로 시작하는 프로그램의 환경변수가 바뀔 뿐이다. + +해당 정보는 pseudo-filesystem인 /proc 안에서 확인할 수 있다. + +`/proc//environ`에서 `pid`를 원하는 프로세스의 식별자로 변경하여 cat해보자. + +```bash +# cat /proc/1475/environ | tr '\0' '\n' +HOME=/ +TERM=linux +PATH=/usr/sbin:/usr/bin:/sbin:/bin +``` + +한 변수의 값을 다음 변수와 구분하는 null 문자 '\0'를 개행문자로 바꿔주기 위해 tr을 같이 사용했다. 위 명령어에서는 PID 1475에 대해 HOME, TERM 및 PATH 변수가 설정되어 있음을 알 수 있다. + +ps로도 PID를 확인할 수 있다. + +```bash +$ ps eww 1475 + PID TTY STAT TIME COMMAND + 1475 ... ... ... processname HOME=/ TERM=linux PATH=/usr/sbin:/usr/bin:/sbin:/bin +``` + +### 참고 + +https://kldp.org/node/141033 + +https://www.baeldung.com/linux/process-environment-variables \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Redirection\354\231\200\342\200\205FD.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Redirection\354\231\200\342\200\205FD.md" new file mode 100644 index 00000000..6e600a00 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Redirection\354\231\200\342\200\205FD.md" @@ -0,0 +1,57 @@ +--- +title: 'Redirection와 FD' +lastUpdated: '2024-03-02' +--- + +- `>`는 overwrite를 뜻한다. +- `>>`는 append를 뜻한다. + +- 명령어의 결과(ls *)를 파일에 남기고 싶다면 아래와 같은 명령어를 사용하면 된다. + - `ls * > file.txt` + +- "hello"라는 값을 파일의 맨 밑부분에 추가하고 싶다면 아래와 같은 명령어를 사용하면 된다. + - `echo 'hello' >> file.txt` + +### standard stream + +- redirection은 stream을 통해 이뤄진다. + +image + +- `stream`이란 디스크상의 파일이나 컴퓨터에 연결되는 여러 장치들을 통일된 방식으로 다루기 위한 가상의 개념이다. +- 프로세스가 생성되면 기본적으로 입, 출력을 위한 채널을 가지게 되는데, 이것을 **standard stream**이라고 한다. +- stream은 각각 `/dev/stdin`, `/dev/stdout`, `/dev/stderr`에 파일로 저장된다. + +### FD (File Descriptor) + +- 우리가 사용하는 파일들은 실제 프로그램 실행시 FD(File Descriptor)라는 양의 정수 번호에 의해 입출력을 처리한다. +- 프로그램이 수행되면 운영체제는 실행되는 프로그램에게 3개의 기본 FD를 할당한다. 그리고 프로그램이 내부적으로 다른 파일을 open하게 되면 3번째 FD를 할당한다. + + |File Descripter|설명| + |-|-| + |0|표준 입력 (standard input)| + |1|표준 출력 (standard output)| + |2|표준 에러 (standard error)| + +- FD를 아래와 같이 활용할 수 있다. + - `hello.txt`라는 파일의 wc 결과를 `out.txt`에 출력하는 명령어이다. + + ```bash + $ cat hello.txt + hello + world + $ wc 0< hello.txt 1> out.txt + $ cat outfile + 2 2 12 + ``` + +- `<`의 좌측 기본 값은 0이고, `>`, `>>`의 좌측 기본값은 1이기 때문에 `wc < infile > outfile`처럼 입력해도 결과는 똑같다. +- 기호 오른쪽에 파일이름이 아닌 FD번호를 쓰고 싶을때는 `&`를 사용해서 표현할 수 있다. +- 만약 특정 명령어의 결과와 에러 출력 결과를 같은 파일에 넣고 싶으면 두 가지 방법이 있다. + - `wc asdfghh > tmpfile 2>&1`: 2(표준 에러)는 1(표준 출력)으로, 1(표준 출력)은 `tmpfile`에 redirect + - `wc asdfghh > tmpfile 2> tmpfile`: 1(표준 출력)과 2(표준 에러)를 각각 `tmpfile`에 redirect + +--- +참고 +- https://unix.stackexchange.com/questions/42728/what-does-31-12-23-do-in-a-script +- https://www.shells.com/l/en-US/tutorial/Difference-between-%E2%80%9C%3E%E2%80%9D-and-%E2%80%9C%3E%3E%E2%80%9D-in-Linux \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Runlevel.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Runlevel.md" new file mode 100644 index 00000000..200bfc55 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Runlevel.md" @@ -0,0 +1,54 @@ +--- +title: 'Runlevel' +lastUpdated: '2024-03-02' +--- + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/db6af408-ed8e-45bc-882c-789ae5c112d5) + +--- + +### Runlevel 확인 + +런레벨은 아래 경로에 runlevel로 검색하면 시스템에 등록된 Runlevel을 직접 확인할 수 있다. + +- 경로 : `/lib/systemd/system` +- 파일명 : `runlevel*.target` + +```bash +$ sudo ls -al /lib/systemd/system/runlevel*.target +lrwxrwxrwx 1 root root 15 Mar 2 21:58 /lib/systemd/system/runlevel0.target -> poweroff.target +lrwxrwxrwx 1 root root 13 Mar 2 21:58 /lib/systemd/system/runlevel1.target -> rescue.target +lrwxrwxrwx 1 root root 17 Mar 2 21:58 /lib/systemd/system/runlevel2.target -> multi-user.target +lrwxrwxrwx 1 root root 17 Mar 2 21:58 /lib/systemd/system/runlevel3.target -> multi-user.target +lrwxrwxrwx 1 root root 17 Mar 2 21:58 /lib/systemd/system/runlevel4.target -> multi-user.target +lrwxrwxrwx 1 root root 16 Mar 2 21:58 /lib/systemd/system/runlevel5.target -> graphical.target +lrwxrwxrwx 1 root root 13 Mar 2 21:58 /lib/systemd/system/runlevel6.target -> reboot.target +``` + +기본으로 지정된 런레벨 확인은 symbolic link가 `default.target`으로 걸려있는 파일을 찾으면 된다. + +```bash +$ sudo ls -al /lib/systemd/system/default.target +lrwxrwxrwx 1 root root 16 Mar 2 21:58 /lib/systemd/system/default.target -> graphical.target +``` + +--- + +### Runlevel 설정하는 방법 + +`default.target`의 심볼릭 링크 파일을 변경하면 기본 런레벨 변경을 할 수 있다. + +- 변경 전: 그래픽 모드 (런레벨 5) +- 변경 후: 콘솔 모드 (런레벨 3) + +```bash +$ ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target +$ ls -al /etc/systemd/system/default.target +lrwxrwxrwx. 1 root root 37 Sep 25 15:05 /etc/systemd/system/default.target -> /lib/systemd/system/multi-user.target +``` + +--- + +### 관련 명령어 + +- chkconfig : 간단한 유틸리티로 특정 run level에서 실행할 프로그램을 등록/설정/변경 할수 있음 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/bash_profile\352\263\274\342\200\205bashrc.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/bash_profile\352\263\274\342\200\205bashrc.md" new file mode 100644 index 00000000..571e1dd0 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/bash_profile\352\263\274\342\200\205bashrc.md" @@ -0,0 +1,60 @@ +--- +title: 'bash_profile과 bashrc' +lastUpdated: '2024-03-02' +--- + +리눅스에서 alias를 수정하거나 PATH를 변경할때, 주로 이 네가지의 파일들을 사용한다. + +```js +/etc/profile +/etc/bashrc +~\.bash_profile +~\.bash_rc +``` + +## profile + +- `/etc/profile` : 전역적인 파일, 모든 사용자가 로그인 시 실행 +- `~/.bash_profile` : 지역적인 파일, 해당하는 사용자가 로그인 시만 실행 + +또한 /etc/profile의 경우 어떠한 shell이든 상관없지만, ~/.bash_profile의 경우 bash shell일 경우에만 해당된다. + +Shell을 실행할 때 로그인이 필요한 Login Shell이 실헹되는 경우에 쓰인다. (ssh로 접속하거나, su 명령어로 다른계정을 들어갈 때 등이 해당된다.) + +## bashrc + +- `/etc/bashrc` : 모든 사용자가 shell을 실행시킬 때 마다 실행 +- `~/.bashrc` : 해당하는 사용자가 shell을 실행시킬 때 실행 + +profile과 달리 Login 과정이 없으므로 shell을 실행시키는 사용자로 구분한다. + +bashrc는 Non-Login Shell, Non-Login Shell 두 군데에서 모두 실행된다. + +profile의 경우 대부분 환경 변수같은 것을 명시하고 bashrc의 경우 alias 같은 것을 명시한다 + +```js ++----------------+-----------+-----------+------+ +| |Interactive|Interactive|Script| +| |login |non-login | | ++----------------+-----------+-----------+------+ +|/etc/profile | A | | | ++----------------+-----------+-----------+------+ +|/etc/bash.bashrc| | A | | ++----------------+-----------+-----------+------+ +|~/.bashrc | | B | | ++----------------+-----------+-----------+------+ +|~/.bash_profile | B1 | | | ++----------------+-----------+-----------+------+ +|~/.bash_login | B2 | | | ++----------------+-----------+-----------+------+ +|~/.profile | B3 | | | ++----------------+-----------+-----------+------+ +|BASH_ENV | | | A | ++----------------+-----------+-----------+------+ +| | | | | ++----------------+-----------+-----------+------+ +| | | | | ++----------------+-----------+-----------+------+ +|~/.bash_logout | C | | | ++----------------+-----------+-----------+------+ +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/shell.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/shell.md" new file mode 100644 index 00000000..bfa0e6fe --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/shell.md" @@ -0,0 +1,71 @@ +--- +title: 'shell' +lastUpdated: '2024-03-02' +--- + +쉘은 사용자가 운영체제의 서비스를 사용할 수 있도록 사용자의 입력을 기반으로 프로그램을 실행해 주는 역할을 한다. 즉, 커널과 사용자 사이의 인터페이스 역할을 담당한다. 커널은 쉘로부터 전달 받은 명령을 기계가 이해할 수 있는 언어로 변환하여 CPU, I/O, 메모리 등 다양한 리소스에 접근해 주는 역할을 한다. + +다시 말해, 쉘은 사용자(프로그램)에게 받은 명령을 전달받아 커널이 이해할 수 있도록 해석하여 전달하고, 커널은 하드웨어와 직접적으로 통신한다. 사용자는 시스템 손상 방지를 위해 접근할 수 있는 영역이 제한되어 있어 하드웨어에 엑세스하기 위해선 시스템 콜이라는 특정 작업을 수행해야 한다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/558363ec-93b8-46b2-a306-3d13066134bd) + +## 시스템 콜 (System Call) + +시스템 콜은 Mode bit를 기반으로 0이면 커널모드, 1이면 사용자 모드로 나뉘어서 작동한다. 사용자가 파일 생성, 프로그램 실행 등의 호출을 수행하려면 시스템 콜을 통해 서비스를 제공받을 수 있다. + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/c0d5e495-ccc5-4dd8-b162-00641684d30f) + +예를 들어 유저 프로그램이 I/O 요청을 하면 trap이 발동되면서 모드비트가 1에서 0으로 변경되고, 커널에 전달된다. 커널은 해당 서비스를 수행하고, 다시 trap을 통해 모드비트를 0에서 1로 변경하여 사용자모드로 전달해 준다. + +## 쉘 종류 + +`cat /etc/shells` 명령어를 통해 /etc/shells 파일을 보면 현재 운영체제 환경에서 사용할 수 있는 쉘의 종류를 확인할 수 있다. bash, zsh, ksh 등 다양한 쉘의 종류가 있다. + +다양한 쉘의 종류가 있으며 대부분 bash나 zsh를 많이 쓴다. + +```bash +# List of acceptable shells for chpass(1). +# Ftpd will not allow users to connect who are not using +# one of these shells. + +/bin/bash +/bin/csh +/bin/dash +/bin/ksh +/bin/sh +/bin/tcsh +/bin/zsh +``` + +|쉘 이름|위치|특징| +|-|-|-| +|sh (Bourne Shell)|`/bin/sh`|최초의 유닉스 쉘로 스크립트를 지원하며 sh로 표기한다. 본쉘은 논리 및 산술 연산을 처리하는 내장 기능이 없어 이전에 사용한 명령을 기억할 수 없다. (history 기능 제공하지 않음)| +|ksh (Korn Shell)|`/bin/ksh`|본 쉘을 개선한 상위집합으로 history, alias 등의 작업기능이 추가되었다. (csh, sh 보다 빠름)| +|csh (C Shell)|`/bin/csh`|ksh 처럼 본쉘의 개선버전으로 history, alias, ~ (홈디렉토리) 기능 추가, 명령어 편집 기능 제공 X +|tcsh|`/bin/tcsh`|csh 개선 버전으로 명령어 편집기능 제공, 자동완성, where 명령어 제공| +|bash (Bourne Again Shell)|`/bin/bash`|본쉘의 확장버전으로 만든 Unix 쉘로 Linux, Mac의 기본 쉘로 사용된다.
mv, cp, rm, touch, ls, mkdir, cd, rmdir 등의 명령어 들이 추가되었다.| +|zsh|`/bin/zsh`|bash, ksh, tcsh의 기능을 결합하여 맞춤법 검사, 로그인 감시, 자동 생성, 플러그인 및 테마가 지원되며, oh my zsh 등의 사용자 정의 테마를 지원한다.| + +#### 사용 중인 shell 확인 +```bash +echo $0 +echo $SHELL +ps | grep sh +env | grep SHELL +``` + +#### shell 변경 +```bash +chsh -s [shell이름] [사용자명] +chsh -s /bin/bash sasca37 +``` + +현재 사용중인 쉘을 변경하려면 그냥 실행하고자 하는 쉘 이름을 넣으면 된다고 한다. +```bash +$ tcsh +``` + +--- +참고 +- https://www.tutorialspoint.com/unix/unix-what-is-shell.htm +- https://linuxcommand.org/lc3_lts0010.php \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/zshrc.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/zshrc.md" new file mode 100644 index 00000000..b9af6846 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/Shell/zshrc.md" @@ -0,0 +1,41 @@ +--- +title: 'zshrc' +lastUpdated: '2024-03-02' +--- + +zsh는 bash와 같은 shell의 한 종류이다. 하지만 zshrc는 bash와 다르게 login이든 non-login이든 상관없이 항상 실행된다. 그래서 zshrc를 bashrc와 bash_profile처럼 구별해서 쓸 필요는 없다. 물론 zsh 에도 login에서만 동작하는 zprofile이 있긴 하다고 한다. + +zsh는 최신 macOS 버전의 기본 셸이기도 하다. + +```js ++----------------+-----------+-----------+------+ +| |Interactive|Interactive|Script| +| |login |non-login | | ++----------------+-----------+-----------+------+ +|/etc/zshenv | A | A | A | ++----------------+-----------+-----------+------+ +|~/.zshenv | B | B | B | ++----------------+-----------+-----------+------+ +|/etc/zprofile | C | | | ++----------------+-----------+-----------+------+ +|~/.zprofile | D | | | ++----------------+-----------+-----------+------+ +|/etc/zshrc | E | C | | ++----------------+-----------+-----------+------+ +|~/.zshrc | F | D | | ++----------------+-----------+-----------+------+ +|/etc/zlogin | G | | | ++----------------+-----------+-----------+------+ +|~/.zlogin | H | | | ++----------------+-----------+-----------+------+ +| | | | | ++----------------+-----------+-----------+------+ +| | | | | ++----------------+-----------+-----------+------+ +|~/.zlogout | I | | | ++----------------+-----------+-----------+------+ +|/etc/zlogout | J | | | ++----------------+-----------+-----------+------+ +``` + +https://www.zsh.org/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/file\342\200\205\352\264\200\353\240\250\342\200\205systemcall.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/file\342\200\205\352\264\200\353\240\250\342\200\205systemcall.md" new file mode 100644 index 00000000..b072256f --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/file\342\200\205\352\264\200\353\240\250\342\200\205systemcall.md" @@ -0,0 +1,316 @@ +--- +title: 'file 관련 systemcall' +lastUpdated: '2024-03-02' +--- + +### open + +- 파일을 열고 file descriptor 반환 + +**file descriptor** +- 실행중인 프로그램과 하나의 파일 사이에 연결된 개방 상태 +- 음수가 아닌 정수형 값 +- 파일 개방이 실패하면 -1이 됨 +- 커널에 의해서 관리 + +```bash +NAME + open, openat – open or create a file for reading or writing + +SYNOPSIS + #include + + int open(const char *path, int oflag, [mode_t mode], ...); + + int openat(int fd, const char *path, int oflag, ...); +``` + +- path: 개방할 파일의 경로 이름을 가지고 있는 문자열의 포인터 +- oflags: 파일의 개방 방식 지정 +- mode: 파일 오픈 모드 + - `O_RDONLY`: 읽기 전용 + - `O_WRONLY`: 쓰기 전용 + - `O_RDWR`: 둘 다 가능 + - `O_EXCL`: 이미 존재하는 파일을 개방할 때 개방을 막음 + - `O_CREAT`: 파일이 존재하지 않을 경우 파일 생성 + ```bash + filedes = open("temp.txt", O_CREAT | O_RDWR, 0644) + ``` + - `O_APPEND`: 파일의 맨 끝에 내용 추가만 가능 + - `O_TRUNC`: 파일을 생성할 때 이미 있는 파일이고, 쓰기 옵션으로 열었으면 내용을 모두 지우고 파일의 길이를 0으로 변경 + - `O_NONBLOCK/O_NDELAY`: 비블로킹(non-blocking) 입출력 옵션으로 파일을 읽거나 쓰고 난 후의 동작에 영향을 준다. 디스크의 파일 입출력보다는 FIFO 같은 특수 파일의 입출력에 의미가 있다. 디스크인 경우 읽거나 쓸 데이터가 없으면 `-1`을 리턴한다. + - `O_SYNC/O_DSYNC:` 파일에 쓰기 동작을 할 때 보통 버퍼에만 쓰고 나중에 디스크와 같은 저장 장치로 옮겨쓰는데, 이 옵션이 설정되어 있으면 저장 장치에 쓰기를 마쳐야 쓰기 동작을 완료한다. + - `O_SYNC` 플래그는 파일의 수정 시각 속성도 수정할 때까지 기다린다. 이 옵션을 설정하면 프로그램의 실행 속도는 느려질 수 있지만 디스크에 확실하게 저장됨을 보장한다. + +### close + +- 파일 닫기 + +```bash +NAME + close – delete a descriptor + +SYNOPSIS + #include + + int close(int fildes); +``` + +### read + +- 파일 읽기 + +```bash +NAME + pread, read, preadv, readv – read input + +LIBRARY + Standard C Library (libc, -lc) + +SYNOPSIS + #include + #include + #include + + ssize_t + pread(int d, void *buf, size_t nbyte, off_t offset); + + ssize_t + read(int fildes, void *buf, size_t nbyte); + + ssize_t + preadv(int d, const struct iovec *iov, int iovcnt, off_t offset); + + ssize_t + readv(int d, const struct iovec *iov, int iovcnt); + +``` + +### lseek + +- 지정한 파일에 대해서 read/write 포인터의 위치를 임의로 변경한다. + +**read/write pointer** +- 개방된 파일 내에서 읽기 작업이나 쓰기 작업을 수행할 바이트 단위의 위치 +- 특정 위치를 기준으로 한 상대적인 위치 -> Offset이라고 부름 +- 파일을 개방한 직후 read/write pointer는 0, 읽거나 내용 추가시 늘어남 + +```bash +NAME + lseek – reposition read/write file offset + +SYNOPSIS + #include + + off_t + lseek(int fildes, off_t offset, int whence); +``` + +- lseek을 활용해 다양한 코드를 작성할 수 있다. + - 파일 크기 구하기 + ```c + ... + int main(int argc, char **argv) { + int filedes; + off_t newpos; + + filedes = open(argv[1], O_RDONLY); + + // rw pointer을 EOF로 이동 + newpos = lseek(filedes, (off_t)0, SEEK_END); + printf("file size : %lld\n", newpos); + + close(filedes); + } + ``` + + - 파일의 내용을 읽고 소문자를 대문자로 치환하기 + ```c + ... + int main() { + char buffer[1]; + int fd = open("temp1.txt", O_RDWR); + while (read(fd, buffer, sizeof(buffer)) > 0) { + char* b = &buffer[0]; + if('a' <= *b && *b <= 'z') { + *b = *b - 'a' + 'A'; + lseek(fd, -1, SEEK_CUR); + if (write(fd, b, 1) < 1) { + break; + } + } + printf("%c", *b); + } + close(fd); + } + ``` + +### umask + +```c +#include #include +mode_t umask(mode_t mask); +``` + +```c +... +int main() { + mode_t oldmask = umask(023); + int fd = open("test.txt", O_CREAT, 0777); + close(fd); +} +``` + +### access + +```c +#include +int access(const char *pathname, int mode); +``` + +- `pathname`: 파일에 대한 경로이름이다. +- `mode`: 검사하려는 접근 권한으로 `R_OK`, `W_OK`, `X_OK`, `F_OK`를 사용할 수 있다. + +```c +#include +#include +#include + +int main(int argc, char **argv) { + + char *filename = argv[1]; + if ( access(filename, R_OK) == -1 ) { + fprintf( stderr, "User cannot read file %s \n", filename); + exit(1); + } + printf("%s readable, proceeding \n", filename); +} +``` + +image + +### link, symlink + +지정한 파일에 대한 하드 링크(link)와 소프트 링크(symlink)를 생성한다. + +```c +#include +int link(const char *oldpath, const char *newpath); int symlink(const char *oldpath, const char *newpath); +``` + +- `oldpath`: 원본 파일의 경로 이름이다. +- `newpath`: 하드 링크/소프트 링크의 경로 이름이다. +- 반환값: 호출이 성공하면 0을 반환하고, 실패하면 -1을 반환한다. + +`ls -l`의 정보는 i-node 블록에 저장됨. +- 하드링크 -> 같은 파일(같은 i-node 블록을 공유)인데 파일 이름만 다른 것 +- 소프트링크(심볼릭 링크) -> 원본파일의 경로를 가리킴 + +image + +```c +#include + +int main(int argc, char *argv[]) { + if(link(argv[1], argv[2])) + printf("hard-link failed\n"); +} +``` +```c +#include + +int main(int argc, char *argv[]) { + if(symlink(argv[1], argv[2])) printf("soft-link failed\n"); +} +``` + +### readlink + +소프트 링크 파일의 실제 내용을 읽는다 + +```c +#include int readlink(const char *path, char *buf, size_t bufsize); +``` +- `path`: 소프트 링크에 대한 경로 이름이다. +- `buf`: 소프트 링크의 실제 내용을 담을 공간이다. +- `bufsize`: buf의 크기이다 + +```c +#include +#include + +int main(int argc, char **argv) { + char buffer[1024]; + int nread; + nread = readlink(argv[1], buffer, 1024); + write(1, buffer, nread); +} +``` + +### stat + +지정한 파일에 대한 상세한 정보를 알아온다. + +```c +#include #include #include +int stat(const char *filename, struct stat *buf); int fstat(int filedes, struct stat *buf); +``` + +```c +#include +#include +#include +#include + + +struct stat { + dev_t st_dev + ino_t st_ino + mode_t st_mode + nlink_t st_ulink + uid_t st_uid + gid_t st_gid + dev_t st_rdev + off_t st_size + timestruc_t st_atim + timestruc_t st_mtim + timestruc_t st_ctim + blksize_t st_blksize + blkcnt_t st_blocks + char st__fstype[_ST_FSTYPSZ]; +}; + +int main(int argc, char *argv[]) { + struct stat finfo; + char fname[1024]; + + if(argc > 1) strcpy(fname, argv[1]); + else strcpy(fname, argv[0]); + + if(stat(fname, &finfo) == -1) { + fprintf(stderr, "Couldn't stat %s \n", fname); + exit(1); + } + + printf("%s \n", fname); + printf("ID of device: %d \n", finfo.st_dev); + printf("Inode number: %d \n", finfo.st_ino); + printf("File mode : %o \n", finfo.st_mode); + printf("Num of links: %d \n", finfo.st_nlink); + printf("User ID : %d \n", finfo.st_uid); + printf("Group ID : %d \n", finfo.st_gid); + printf("Files size : %d \n", finfo.st_size); + printf("Last access time : %u \n", finfo.st_atim); + printf("Last modify time : %u \n", finfo.st_mtim); + printf("Last stat change : %u \n", finfo.st_ctim); + printf("I/O Block size : %d \n", finfo.st_blksize); + printf("Num of blocks : %d \n", finfo.st_blocks); + printf("File system : %s \n", finfo.st_fstype); +} +``` + +### 과제 + +a.txt에 대해 b.txt라는 심링크 생성 +a.txt 삭제 +b.txt 존재 여부 검사 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/fork\354\231\200\342\200\205exec.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/fork\354\231\200\342\200\205exec.md" new file mode 100644 index 00000000..12cd245c --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/fork\354\231\200\342\200\205exec.md" @@ -0,0 +1,53 @@ +--- +title: 'fork와 exec' +lastUpdated: '2024-03-02' +--- + +## fork + +```c +pid_t fork(void); +``` + +- 새로운 자식 프로세스를 생성할 때 사용하는 시스템 호출 함수이다. +- 자식 프로세스는 부모 프로세스의 PCB를 그대로 상속받는다. +- 함수의 반환값은 자식 프로세스에게 `0`, 부모에게는 자식 프로세스의 id이다. + +```c +pid_t vfork(void); +``` + +- `vfork()`도 `fork()`와 마찬가지로 자식 프로세스를 생성하는 함수이다. +- `fork()`와 달리 자식 프로세스가 먼저 실행됨을 보장한다. 따라서 생성된 프로세스가 exec계열 함수를 이용하여 새 프로그램으로 실행하는 경우에 주로 사용한다. +- `vfork()`로 프로세스를 생성한 경우 부모의 주소 영역을 참조하지 않을 것이라고 생각하여 부모 프로세스의 공간을 자식에게 복사하지 않는다. + - 복사하는 시간이 소요되지 않으므로 fork 보다 약간의 성능 향상이 있다. +- 생성된 자식 프로세스는 exec계열 함수나 `exit()`을 호출할 때까지 부모 프로세스의 메모리 영역에서 실행되고, 부모 프로세스는 자식 프로세스가 exec계열 함수나 `exit()`을 호출할 때까지 기다린다. +- 자식 프로세스를 종료할 때 `_exit()`을 이용해야한다. +- 부모 프로세스의 표준 입출력 채널을 같이 사용하는데 자식 프로세스가 `exit()`을 호출할 경우 입출력 스트림을 모두 닫으므로, 부모 프로세스에서는 입출력을 하지 못한다. 따라서 입출력 스트림을 정리하지 않고 종료시키는 `_exit()`을 사용해야 한다. + +## exec + +- exec 계열 함수는 현재 실행되고 있는 프로세스를 다른 프로세스로 대신하여 새로운 프로세스를 실행하는 함수이다. +- 즉, 진행하던 프로세스의 pid와 정보를 물려주고 다른 프로세스로 대체되어 주어진 경로에 있는 새로운 프로세스 동작을 시작한다. + +- exec 계열 함수에는 다양한 것들이 있다. + + ```c + int execl(const char *pathname, const char *arg0, ... /* (char *)0*/); + int execv(const char *pathname, char *const argv[]); + int execle(const char *pathname, const char *arg0, .../*(char *)0, char *const envp[] */); + int execve(const char *pathname, char *const argv[], char *const envp[]); + int execlp(const char *filename, const c har *arg0, .../* (char *)0 */); + int execvp(const char *filename, char *const argv[]); + ``` + + - 뒤에 l이 붙은 경우 : 매개변수의 인자가 list 형태이다. 즉 `char*`형을 나열한다. + - 뒤에 v가 붙은 경우 : 매개변수의 인자가 vector 형태이다. 두 번째 인잘르 보면 2차원 배열로 `char*`형을 배열로 한 번에 넘긴다. + - 뒤에 e가 붙은 경우 : 환경변수를 포함하여 넘긴다. + - 뒤에 p가 붙은 경우 : 경로 정보가 없는 실행 파일 이름이다. 만약 filename에 `/`(슬래시)가 포함되어 있으면 filename을 경로로 취급하고 슬래시가 없으면 path로 취급한다. + +- exec를 실행할 때 본 프로세스는 서브 프로세스에게 pid, ppid, uid, gid, 세션 id, 제어 터미널, 현재 작업 디렉토리, 파일 생성 마스크 등 다양한 정보를 상속해준다. + +--- +참고 +- https://www.baeldung.com/linux/fork-vfork-exec-clone \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/mmap.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/mmap.md" new file mode 100644 index 00000000..8d0a2adf --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/mmap.md" @@ -0,0 +1,34 @@ +--- +title: 'mmap' +lastUpdated: '2024-03-02' +--- + +```c +#include +#include + +#ifdef _POSIX_MAPPED_FILES +void * mmap(void *start, size_t length, int prot, int + flags, int fd, off_t offset); +int munmap(void *start, size_t length); + +#endif +``` + +- `mmap`은 파일이나 장치를 메모리에 대응시키거나 푸는 시스템콜이다. +- `fd`로 지정된 파일(혹은 다른 객체)에서 `offset`을 시작으로 `length` 바이트 만큼을 `start` 주소로 대응시키도록 한다. + +--- + +- 명시적인 `mmap`을 사용해 운영체제에 가상 메모리를 요청하면 운영체제는 RAM의 페이지를 즉시 할당해주지 않고, 가상 주소 범위만 제공한다. + - 그리고 실제로 메모리에 접을할 때 MMU가 페이지 결함을 일으키면 그때 새로운 페이지를 할당해준다. + +- 운영체제에서는 메모리를 **오버커밋** 상태로 관리한다. + - 즉, 할당 가능한 RAM 용량을 물리적 최대 용량보다 크게 잡는다. + - 할당받았지만 메모리를 즉시 사용하지 않는 케이스가 생길 수 있기 때문이다. + - 그렇기 떄문에 `mmap`으로 메모리를 할당받았지만 운영체제의 실제 메모리가 부족해서 사용시 문제가 생길 수도 있다. + +--- +참고 +- [Go 성능 최적화 가이드](https://www.yes24.com/Product/Goods/122308121?pid=123487&cosemkid=go16946818029110592&gad_source=1&gclid=CjwKCAiApuCrBhAuEiwA8VJ6Jvu_E0svIWMux506LsLfl9VgN1bn_VY-dkqqHDe_2_XmZme9qAv4ahoC_6cQAvD_BwE) +- https://github.com/prometheus/prometheus/blob/39d79c3cfb86c47d6bc06a9e9317af582f1833bb/tsdb/fileutil/mmap.go \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/thread\342\200\205\352\264\200\353\240\250\342\200\205systemcall.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/thread\342\200\205\352\264\200\353\240\250\342\200\205systemcall.md" new file mode 100644 index 00000000..c2530dc5 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/thread\342\200\205\352\264\200\353\240\250\342\200\205systemcall.md" @@ -0,0 +1,130 @@ +--- +title: 'thread 관련 systemcall' +lastUpdated: '2024-03-02' +--- + +- 우선 스레드란, 프로세스 내에 실행되는 흐름의 단위이다. + - 프로세스는 반드시 1개 이상의 쓰레드를 가지고 있다. + - 프로세스와 다르게 한 프로세스 안의 스레드들은 메모리 자원을 공유한다. + +## `pthread_create` + +- `pthread_create`는 말 그대로 thread를 생성하는 시스템 콜이다. +- 이 시스템 콜을 사용하는 경우 `-pthread` 옵션과 함께 컴파일해야 한다. +- 각 옵션의 의미는 다음과 같다. + 1. **thread**: 성공적으로 함수가 호출되면 이 포인터에 thread ID가 저장된다. 이 인자로 넘어온 값을 통해서 `pthread_join`과 같은 함수를 사용할 수 있다. + 2. **attr**: 스레드의 특성을 정의한다. 만약 스레드의 속성을 지정하려고 한다면 `pthread_attr_init`등의 함수로 초기화해야한다. + 3. **start_routine**: 어떤 로직을 할지 함수 포인터를 매개변수로 받는다.  + 4. **arg**: start_routine에 전달될 인자를 말합니다. start_routine에서 이 인자를 변환하여 사용한다. + +```c +NAME + pthread_create - create a new thread + +SYNOPSIS + #include + + int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg); + + Compile and link with -pthread. +``` + +## `pthread_join` + +- thread ID를 받아서 해당 스레드가 종료될 때까지 기다려서 join하는 시스템 콜이다. + +```c +NAME + pthread_join - join with a terminated thread + +SYNOPSIS + #include + + int pthread_join(pthread_t thread, void **retval); + + Compile and link with -pthread. +``` + +### 예제 + +- `thread_routine`이라는 함수에 정의된 동작을 수행하는 스레드를 5개 생성하고, 해당 스레드가 모두 동작하는 동안 main 스레드에서 대기하는 예제이다. + + ```c + #include + #include + #include + #include + + void* thread_routine(void *arg){ + pthread_t tid; + + tid=pthread_self(); + + int i=0; + printf("\ttid:%x\n",tid); + while(i<10) { + printf("\tnew thread:%d\n",i); + i++; + sleep(1); + } + } + + int main() { + pthread_t thread; + pthread_create(&thread, NULL, thread_routine, NULL); + + int i=0; + printf("tid:%x\n",pthread_self()); + + while(i<5) { + printf("main:%d\n",i); + i++; + sleep(1); + } + pthread_join(thread,NULL); + } + ``` + +- 출력 결과 + + ``` + tid:f9e36500 + main:0 + tid:6f95f000 + new thread:0 + new thread:1 + main:1 + main:2 + new thread:2 + main:3 + new thread:3 + main:4 + new thread:4 + new thread:5 + new thread:6 + new thread:7 + new thread:8 + new thread:9 + ``` + +## `pthread-detach` + +```c +#define _OPEN_THREADS +#include + +int pthread_detach(pthread_t *thread); +``` + +- 실행중인 쓰레드를 detached(분리)상태로 만든다. +- 스레드가 독립적으로 동작하길 원하는 경우 사용한다. (join하지 않을 예정인 경우) +- `pthread-detach`는 쓰레드가 종료되는 즉시 쓰레드의 모든 자원을 되돌려(free)줄 것을 보장한다. + - 반면, `pthread_create`만으로 스레드를 생성하면 루틴이 끝나도 자원이 자동으로 반환되지 않는다. join시에 메인과 함께 메모리가 처리될 것으로 예상하기 때문이다. + +--- +참고 +- https://man7.org/linux/man-pages/man3/pthread_create.3.html +- https://stackoverflow.com/questions/6042970/pthread-detach-question +- https://www.joinc.co.kr/w/man/3/pthread_detach +- https://www.ibm.com/docs/en/zos/2.4.0?topic=functions-pthread-detach-detach-thread \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/wait\352\263\274\342\200\205waitpid.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/wait\352\263\274\342\200\205waitpid.md" new file mode 100644 index 00000000..256287dd --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/wait\352\263\274\342\200\205waitpid.md" @@ -0,0 +1,50 @@ +--- +title: 'wait과 waitpid' +lastUpdated: '2023-12-19' +--- +## wait + +```c +#include + +pid_t wait(int *_Nullable wstatus); +``` + +- wait은 child process가 종료될 때까지 기다렸다가 child process가 종료되면 종료된 child process의 값을 반환한다. + - 만약 실패하는 경우 `-1`을 반환한다. + +- status에는 child process가 어떤 방식으로 종료되었는지가 담겨있다. +- 관련 매크로로 상세한 정보를 알아낼 수 있다. + - `WIFEXITED(status)` : exit, _exit, _Exit 혹은 main에서의 return으로 종료되었는지 여부를 반환한다. + - `WEXITSTATUS(status)` : `WIFEXITED`의 값이 참인 경우 exit code를 반환한다. + - `WIFSIGNALED(status)` : signal에 의하여 terminate 되었는지 여부를 반환한다. + - `WTERMSIG(status)` : `WIFSIGNALED`의 값이 참인 경우 어느 signal에 의하여 terminate 되었는지를 반환한다. + +## waitpid + +```c +#include +#include + +pid_t waitpid(pid_t pid, int *status, int options); +``` + +- waitpid 함수는 인수로 주어진 pid 번호의 자식프로세스가 종료되거나, 시그널 함수를 호출하는 신호가 전달될때까지 waitpid를 호출한 영역에서 일시중지 된다. +- 만일 pid로 지정된 자식이 waitpid 함수 호출전에 이미 종료되었다면, 함수는 즉시 리턴하고 자식프로세스는 좀비프로세스로 남는다. + +- pid 값은 다음중 하나가 된다. + - `pid < -1`: 프로세서 그룹 ID가 pid의 절대값과 같은 자식 프로세스를 기다린다. + - `pid == -1`: 임의의 자식프로세스를 기다린다. (wait과 동일) + - `pid == 0`: 프로세스 그룹 ID가 현재 프로세스의 그룹 ID와 같은 자식프로세스를 기다린다. + - `pid > 0`: 프로세스 ID가 pid의 값과 같은 자식 프로세스를 기다린다. + +- options의 값은 0이거나 다음값들의 OR이다. + - **WNOHANG**: `waitpid()`를 실행했을 때, 자식 프로세스가 종료되어 있지 않으면 블록상태가 되지 않고 바로 리턴하게 해준다. + - 만약 기다리는 프로세스가 종료되지 않아 프로세스 회수가 불가능한 상황이라면 차단되지 않고 반환값으로 0을 받는다. + - **WUNTRACED**: pid에 해당하는 자식 프로세스가 중단 상태일 경우 그 상태를 리턴한다. 즉 프로세스의 종료뿐 아니라 프로세스의 멈춤상태도 찾아낸다. + - **WUNTRACED**: 중단된 자식 프로세스의 상태를 받는다. + +--- +참고 +- https://linux.die.net/man/2/waitpid +- https://www.ibm.com/docs/en/zos/2.1.0?topic=functions-waitpid-wait-specific-child-process-end \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/\353\246\254\353\210\205\354\212\244\342\200\205\354\213\234\354\212\244\355\205\234\342\200\205\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215\342\200\205\355\224\204\353\241\234\354\240\235\355\212\270.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/\353\246\254\353\210\205\354\212\244\342\200\205\354\213\234\354\212\244\355\205\234\342\200\205\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215\342\200\205\355\224\204\353\241\234\354\240\235\355\212\270.md" new file mode 100644 index 00000000..ba3764a3 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/System\342\200\205call/\353\246\254\353\210\205\354\212\244\342\200\205\354\213\234\354\212\244\355\205\234\342\200\205\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215\342\200\205\355\224\204\353\241\234\354\240\235\355\212\270.md" @@ -0,0 +1,369 @@ +--- +title: '리눅스 시스템 프로그래밍 프로젝트' +lastUpdated: '2024-03-02' +--- + +- 선택과목으로 리눅스 시스템 프로그래밍을 수강하며 진행한 프로젝트 코드 및 배운점에 대해 정리한다. +- 이 프로젝트에서 개발하는 Application은 아래의 기능을 포함한다. + +1. Text File(`swblocks.txt`)에 기록된 S/W 블록 정보로부터 여러 S/W 블록들을 초기화시킨다. + - 기록된 파일에는 파일 이름과 파라미터가 아래와 같이 세미콜론으로 구분되어 있다. + ``` + SwBlock1; Param1; Param2; Param3 + SwBlock2; Param1; Param2 + SwBlock3; Param1 + ``` +2. S/W 블록의 이상동작(블럭 다운) 발생 시 재초기화를 수행한다. + - 즉, 해당 블록에 해당하는 프로세스를 재시작한다. +3. 각 S/W 블록의 최종 초기화 시간 및 재초기화 횟수를 출력한다. + +- 프로젝트를 동작시켰을 때 출력되는 결과이다. + +image + +## 코드 설명 + +### S/W 블록 정보 저장 +- S/W 블록 정보를 저장하고 있는 파일로부터 S/W 블록 저장용 구조체에 저장한다. SwBlock구조체에 대한 배열을 정의한다. + +```c +typedef struct { + pid_t pid; + time_t lastRestartTime; + int restartCount; + char name[20]; + char parameters[MAX_PARAMETERS][20]; + char reason[50]; +} SwBlock; + +SwBlock blocks[MAX_SW_BLOCKS]; +``` + +### S/W 블록 정보 획득 + +- 파일로부터 S/W 블록 정보를 획득한다. +- 파일에 기록된 한 줄 마다 S/W 블록 정보가 기술되어 있고, S/W 블록 정보에 대한 S/W Name, Argument는 “;”로 구분되어 있으므로, “;”를 기준으로 문자열을 분리한 후 공백을 삭제하여 배열에 저장한다. + +```c +void trimStr(char *str) { + char *start = str; + char *end = str + (strlen(str) - 1); + while (isspace(*start)) start++; + while (isspace(*end)) end--; + memmove(str, start, end - start + 1); + str[end - start + 1] = '\0'; +} + +int readSwBlocks(FILE *file) { + char buf[256]; + int index = 0; + + while (index < MAX_SW_BLOCKS && fgets(buf, sizeof(buf), file)) { + char* token = strtok(buf, ";"); + trimStr(token); + strcpy(blocks[index].name, token); + strcpy(blocks[index].reason, "Init"); + blocks[index].lastRestartTime = time(NULL); + + for (int paramIndex = 0; paramIndex < MAX_PARAMETERS; paramIndex++) { + token = strtok(NULL, ";"); + if (token) { + trimStr(token); + strcpy(blocks[index].parameters[paramIndex], token); + } + } + index++; + } + + return index; +} +``` + +### S/W 블록 초기화 + +- 배열에 저장한 block을 순회하며 각 block에 해당하는 자식 프로세스를 생성한다. + + +```c +int main() { + ... + for (int i = 0; i < swBlockCount; i++) { + runSwBlock(&blocks[i]); + sleep(1); + } + ... +} +``` + +```c +void runSwBlock(SwBlock *block) { + pid_t pid = fork(); + + if (pid == 0) { + srand(time(NULL)); + sleep(rand() % 5); + + if ((rand() % 2) == 0) { + kill(getpid(), SIGTERM); + } else { + exit(0); + } + } else { + printLog(block); + block->pid = pid; + } +} +``` + + +### S/W 블록 재초기화 + +- `SIGCHLD` 시스템콜에 대한 sigaction으로 핸들러를 등록하여, 종료된 자식 프로세스가 있는 경우에 해당 프로세스에 대한 블록 정보를 찾는다. +- `WIFEXITED`, `WIFSIGNALED` 매크로를 통해 사유를 알아낸다. +- 해당 블록의 재시작 횟수, 시점, 사유 등을 로그파일에 기록하고 블록을 재시작한다. + +```c +void initSigaction() { + struct sigaction sa; + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + printf("sigaction"); + exit(1); + } +} +``` + +```c +void signalHandler(int signum) { + int status; + pid_t pid; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + SwBlock *block = NULL; + + for (int i = 0; i < swBlockCount; i++) { + if (blocks[i].pid == pid) { + block = &blocks[i]; + break; + } + } + + if (block) { + block->restartCount++; + block->lastRestartTime = time(NULL); + + if (WIFEXITED(status)) { + snprintf(block->reason, sizeof(block->reason), "Exit(%d)", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + int chstatus = WTERMSIG(status); + snprintf(block->reason, sizeof(block->reason), "Signal(%s)", strsignal(chstatus)); + } else { + snprintf(block->reason, sizeof(block->reason), "Unknown"); + } + + runSwBlock(block); + } else { + printf("자식 프로세스 %d 종료됨.\n", pid); + } + } +} +``` + + +### S/W 블록 기동 정보 조회 + +- 로그에 정보를 기록하고 출력하는 역할을 하는 함수를 만들어 기동시 호출한다. + +```c +void initLog() { + FILE *log = fopen("log.txt", "a"); + fprintf(log, "SW Block Name | Restart cnt | Start Time | Reason\n"); + fprintf(log, "========================================================================================\n"); + printf("SW Block Name | Restart cnt | Start Time | Reason\n"); + printf("========================================================================================\n"); + fclose(log); +} + +void printLog(SwBlock* block) { + FILE *log = fopen("log.txt", "a"); + char timeString[80]; + struct tm* timeInfo = localtime(&block->lastRestartTime); + strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", timeInfo); + fprintf(log, "%s %d %s %s\n", block->name, block->restartCount, timeString, block->reason); + printf("%s %d %s %s\n", block->name, block->restartCount, timeString, block->reason); + fclose(log); +} +``` + +### 전체 코드 + +> https://github.com/rlaisqls/Linux-Study/tree/main/project + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_SW_BLOCKS 10 +#define MAX_PARAMETERS 3 + +typedef struct { + pid_t pid; + time_t lastRestartTime; + int restartCount; + char name[20]; + char parameters[MAX_PARAMETERS][20]; + char reason[50]; +} SwBlock; + +SwBlock blocks[MAX_SW_BLOCKS]; +int swBlockCount; + +void initLog() { + FILE *log = fopen("log.txt", "a"); + fprintf(log, "SW Block Name | Restart cnt | Start Time | Reason\n"); + fprintf(log, "========================================================================================\n"); + printf("SW Block Name | Restart cnt | Start Time | Reason\n"); + printf("========================================================================================\n"); + fclose(log); +} + +void printLog(SwBlock* block) { + FILE *log = fopen("log.txt", "a"); + char timeString[80]; + struct tm* timeInfo = localtime(&block->lastRestartTime); + strftime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", timeInfo); + fprintf(log, "%s %d %s %s\n", block->name, block->restartCount, timeString, block->reason); + printf("%s %d %s %s\n", block->name, block->restartCount, timeString, block->reason); + fclose(log); +} + +void runSwBlock(SwBlock *block) { + pid_t pid = fork(); + + if (pid == 0) { + srand(time(NULL)); + sleep(rand() % 5); + + if ((rand() % 2) == 0) { + kill(getpid(), SIGTERM); + } else { + exit(0); + } + } else { + printLog(block); + block->pid = pid; + } +} + +void signalHandler(int signum) { + int status; + pid_t pid; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + SwBlock *block = NULL; + + for (int i = 0; i < swBlockCount; i++) { + if (blocks[i].pid == pid) { + block = &blocks[i]; + break; + } + } + + if (block) { + block->restartCount++; + block->lastRestartTime = time(NULL); + + if (WIFEXITED(status)) { + snprintf(block->reason, sizeof(block->reason), "Exit(%d)", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + int chstatus = WTERMSIG(status); + snprintf(block->reason, sizeof(block->reason), "Signal(%s)", strsignal(chstatus)); + } else { + snprintf(block->reason, sizeof(block->reason), "Unknown"); + } + + runSwBlock(block); + } else { + printf("자식 프로세스 %d 종료됨.\n", pid); + } + } +} + +void initSigaction() { + struct sigaction sa; + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + printf("sigaction"); + exit(1); + } +} + +void trimStr(char *str) { + char *start = str; + char *end = str + (strlen(str) - 1); + while (isspace(*start)) start++; + while (isspace(*end)) end--; + memmove(str, start, end - start + 1); + str[end - start + 1] = '\0'; +} + +int readSwBlocks(FILE *file) { + char buf[256]; + int index = 0; + + while (index < MAX_SW_BLOCKS && fgets(buf, sizeof(buf), file)) { + char* token = strtok(buf, ";"); + trimStr(token); + strcpy(blocks[index].name, token); + strcpy(blocks[index].reason, "Init"); + blocks[index].lastRestartTime = time(NULL); + + for (int paramIndex = 0; paramIndex < MAX_PARAMETERS; paramIndex++) { + token = strtok(NULL, ";"); + if (token) { + trimStr(token); + strcpy(blocks[index].parameters[paramIndex], token); + } + } + index++; + } + + return index; +} + +int main() { + srand(time(NULL)); + initLog(); + + FILE *fileList = fopen("swblocks.txt", "r"); + swBlockCount = readSwBlocks(fileList); + fclose(fileList); + + initSigaction(); + for (int i = 0; i < swBlockCount; i++) { + runSwBlock(&blocks[i]); + sleep(1); + } + + while (1) sleep(1); +} +``` + +--- +관련 TIL +- [wait과 waitpid](wait과 waitpid.md) \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/X\342\200\205window.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/X\342\200\205window.md" new file mode 100644 index 00000000..b2bc1958 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/X\342\200\205window.md" @@ -0,0 +1,66 @@ +--- +title: 'X window' +lastUpdated: '2024-03-02' +--- + +X Window는 컴퓨터 그래픽 사용자 인터페이스(GUI)를 제공하기 위한 Unix, Linux 계열 운영 체제에서 사용되는 시스템이다. X Window 시스템은 네트워크 환경에서 그래픽 화면을 표시하고 입력 장치와 상호 작용할 수 있는 기능을 제공한다. + + + +X window의 구조라고 한다. + +## 특징 + +- 클라이언트-서버 모델: X 클라이언트는 X 서버에서 동작하면서 서버에게 명령을 전달하고, X서버는 클라이언트에게 명령 요청의 결과를 디스플레이 장치에 출력해주거나 사용자 입력을 클라이언트에게 제공해주는 역할을 한다. 따라서 디스플레이 장치에 독립적이다. + +- 모듈화: X Window 시스템은 모듈화되어 있으며, 다양한 그래픽 라이브러리와 툴킷을 사용하여 응용 프로그램을 개발할 수 있다. 이로 인해 다양한 스타일과 기능을 가진 응용 프로그램을 개발할 수 있으며, 사용자는 선호도에 맞게 시스템을 사용할 수 있다. + +- 다중화면: 다중 화면을 지원하며 여러 개의 모니터에서 동시에 작업할 수 있다. 사용자는 화면 간에 창을 이동하거나 작업을 병렬로 수행할 수 있다. + +## X window 데스크톱 환경 종류 + +- KDE(K Desktop Environment) 2015(1) + - Qt 라이브러리를 사용 +- GNOME(GNU Network Object Model Environment) + - GTK 라이브러리 사용 + - GNU 프로젝트의 일부이며, 리눅스 계열에서 가장 많이 쓰인다. + +## X server 종류 + +- XFree86 + - Intel x86계열의 유닉스 계열 운영체계에서 동작하는 X 서버이다. + - XFree86은 X11R6가 발전하는데 많은 공헌을 한 X386의 영향을 받았다. + +- Xorg + - X.org에서 XFree86의 4.4rc2 버전을 바탕으로 개발한 것이다. + - 레드햇계열 및 한소프트 리눅스에서 사용되고 있다. + +## X window 소프트웨어 + +- Evince(에빈스) 2017(2) + - 문서 뷰어 프로그램 + - 지원 파일 형식 :PDF,PS,XPS,TIFF 등 +- LibreOffice(리브레 오피스) 2016(1) +- 오피스 프로그램 + - MS Office 등의 오피스 프로그램과 호환 + - Writer(워드), Calc(스프레드시트/엑셀), Impress(프레젠테이션/파워포인트), Base(DB 관리) 등의 프로그램 지원 +- Cheese Photo Booth(치즈) : 웹캠 프로그램 • Rhythmbox(리듬박스) : 오디오 플레이어 +- Shotwell(샷웰) : 사진 관리 프로그램 + +## 관련 명령어 + +- `tartx`: X 윈도 구동 + - `startx--:1`: 두 번째 윈도 터미널에 X 윈도를 구동 + - 명령어 오류 발생 시 **Xconfigurator**을 실행하여 설정 +- `xhost`: X 윈도 서버의 호스트 접근 제어를 하기 위한 명령어 + - `xhost + 192.168.100.100` : 해당 호스트에 대한 접근을 허용 +- `xauth` + - X 서버 연결에 사용되는 권한 부여 정보(`.Xauthority` 파일의 `MIT-MAGIC-COOKIEs` 값) 편집/출력 명령어 + - `xauth list $DISPLAY` : 현재 MIT-MAGIC-COOKIEs 값을 출력 + - `xauth add $DISPLAY . ‘쿠키 값’` : .Xauthority 파일에 MIT-MAGIC-COOKIEs 값을 추가 + +--- +참고 +- https://www.x.org/wiki/guide/concepts/ +- https://en.wikipedia.org/wiki/X_Window_System +- https://jhnyang.tistory.com/48 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/mail.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/mail.md" new file mode 100644 index 00000000..fd231d76 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/mail.md" @@ -0,0 +1,110 @@ +--- +title: 'mail' +lastUpdated: '2024-03-02' +--- + +linux에서 mail 서비스를 운영할 수 있다. + +### mail 관련 프로토콜 + +- SMTP(Simple Mail Transfer Protocol) + - MTA 클라이언트와 서버를 구성하는 프로토콜 (TCP/25) + - 송신자와 송신자의 메일 서버 사이, 메일 서버와 메일 서버 사이에서 사용된다. + +- POP3(Post Office Protocol) + - 메일 서버로부터 메일을 수신하기 위한 서버/클라이언트 프로토콜 (TCP/110) + - 메일 서버로부터 메일을 가져온 후 서버에서 메일을 삭제함 + +- IMAP4(Internet Mail Access Protocol) + - POP3와 비슷한 기능이지만 더 많은 기능을 포함하고 복잡함 (TCP/143) + - 메일 서버로부터 메일을 가져와도 서버에 메일이 유지됨. + +### 메일 서비스 + +**기본 컴포넌트 3가지** + + - MUA(Mail User Agent): 사용자들이 메일을 송/수신하기 위한 클라이언트 에이전트 + - MTA(Mail Transfer Agent): 메일 서버로 메일을 전송하기 위한 서버/클라이언트 에이전트 - MDA(Mail Delivery Agent): 수신된 메일을 해당 사용자에게 전달해주는 에이전트 + +**포워딩(forwarding) 방법** + + 1. virtusertable 파일 설정을 통한 포워딩 + 2. aliases 파일 설정을 통한 포워딩 + 3. `.forward` 파일을 통한 포워딩 : 일반 계정 사용자가 자신의 홈 디렉터리에 만들어 설정 + +**메일 관련 프로그램** + +- MTA 프로그램 + - Sendmail: SMTP를 기반으로 한 메일 전송 프로그램. 리눅스 배포판에 기본적으로 설치됨 - + - Qmail: Sendmail에 비교해 보안이 뛰어나며 모듈 방식으로 편리한 기능 + - Postfix: IBM 라이선스를 따르는 오픈소스 프로그램 + +- MUA 프로그램 + - Thunderbird(썬더버드): 모질라에서 만든 메일 클라이언트 프로그램 + - Evolution(에볼루션): GNOME의 메일 관리 프로그램 + +- MDA 프로그램 + - Procmail: 메일을 필터링하는 기본적인 프로그램 + - SpamAssassin: 아파치 재단에서 개발한 메일 필터링 프로그램. 펄(perl)로 제작됨. + +- 보안 프로그램 + - PGP(Pretty Good Privacy): PEM에 비해 보안성은 떨어지지만 구현이 쉽다. + - PEM(Privacy Enhanced Mail): 높은 보안성, 복잡한 구현 방식으로 잘 쓰이지 않음 + +- 기타 관련 프로그램 + - dovecot: POP3와 IMAP4 역할을 수행하는 프로그램 + +### sendmail 환경설정 파일 + +- `/etc/mail/sendmail.cf` + - Sendmail의 핵심. 메일 송/수신 시 이 파일을 해석하여 실행한다. + - m4 명령어로 생성할 수 있다. (`$ m4 sendmail.mc > snedmail.cf`) +- 설정 + - **Cw**: 호스트 지정 + - **Fw**: 파일 지정 + - **Dj**: 특정 도메인을 강제 지정 + +- `/etc/mail/local-host-names` + - 메일 서버에서 사용하는 호스트(도메인)을 등록하는 파일 + +- `/etc/mail/access` + - 각종 접근 제어 설정이 저장되는 파일 + - makemap 명령어: `/etc/mail/access` 파일 편집 후에 DB 파일(access.db)을 만드는 명령어 (`$makemap hash /etc/mail/access < /etc/mail/access`) +- 설정 + - **RELAY**: relay(중계) 허용 + - **OK**: 무조건 허용 + - **REJECT**: relay 차단 (주로 스팸 서버의 IP를 차단) + - **DISCARD**: relay 없이 `/etc/sendmail.cf`에 지정된 $#discard mailer에 폐기됨 (어떠한 답신도 보내지 않음) + +- 메일 거부패턴 옵션 + - **501**: 지정된 메일 주소와 일치하는 모든 메일의 수신 차단 + - **553**: 발신 메일주소에 호스트명이 없을 경우 메일 차단 + - **550** : 지정된 도메인과 관련된 모든 메일 수신 거부ㅋ + +- access 파일 예제 + - ```c + Connect:192.168.10.9 OK /* 192.168.10.9 호스트로 접속하는 클라이언트의 메일 허용 */ + Connect:localhost RELAY /* localhost로 접속하는 클라이언트의 RELAY 허용 */ + From:add@spam.com REJECT /* add@spam.com에서(발신) 오는 메일을 거절하고 거절 답신 보냄 */ + From:root@spam.co.kr DISCARD /* root@spam.co.kr에서(발신) 오는 메일을 거절하고 거절 답신을 보내지 않음 */ + To:log@shionista.com OK /* log@shionista.com으로(수신) 오는 메일을 허용 */ + ``` + +- `/etc/aliases` + - 특정 ID로 들어오는 메일을 여러 호스트에게 전달할 때 사용하는 파일 (작은 규모의 메일링 리스트) + - 사용자가 다른 메일 계정 (별칭)을 사용할 수 있도록 할 수 있다. + - `newaliases` 명령어 : `/etc/aliases` 파일의 변동 사항을 적용 + - `sendmail -bi` 명령어와 같은 기능 + +- `/etc/mail/virtusertable` + - 가상 메일 사용자의 설정이 저장되는 파일 + - access 파일과 마찬가지로 `makemap hash` 명령어로 DB 파일을 만들어 주어야 함 + - `webmaster@server.shionista.com admin` → 해당 메일 주소로 오는 메일을 admin 계정으로 수신 + - `webmaster@test.shionista.com test` → 해당 메일 주소로 오는 메일을 test 계정으로 수신 + +### 관련 명령어 + +- mailq 명령어 2015(1) 2016(1) +- 메일 큐 목록(/var/spool/mqueue) 출력 (sendmail -bp 명령어와 같은 기능) +- [-v] : 자세하게 출력 +- [-Ac] : /etc/spool/submit.cf 에 지정된 메일 큐 목록(/var/spool/clientmqueue)을 출력 diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/man.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/man.md" new file mode 100644 index 00000000..143f770a --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/man.md" @@ -0,0 +1,60 @@ +--- +title: 'man' +lastUpdated: '2024-03-02' +--- + +man은 manual을 의미하며, 설명서 페이지를 볼 수 있는 명령어이다. + +```bash + man [-adho] [-t | -w] [-M manpath] [-P pager] [-S mansect] + [-m arch[:machine]] [-p [eprtv]] [mansect] page [...] + man -f page [...] -- Emulates whatis(1) + man -k page [...] -- Emulates apropos(1) +``` + +## 옵션 + +|옵션|설명| +|-|-| +|`-k`|apropos에 해당하는 매뉴얼의 내용을 출력.
apropos란 완전히 일치하지 않아도 대략적으로 비슷한 단어를 뜻한다. (ex: mount의 apropos는 amount, mounted, mounts 등이 있다.)| +|`-f`|키워드와 완전히 일치하는 매뉴얼의 내용을 출력| +|`-a`|매치되는 모든 매뉴얼 페이지를 출력| +|`-s`, `-S`|특정 섹션 번호를 지정하여 출력| +|`-w`|매뉴얼 페이지 파일의 위치 출력| + +## 매뉴얼 섹션(Manual Section) + +- 리눅스의 매뉴얼 섹션은 총 9개의 섹션으로 구분되어 있다. + - 같은 이름이더라도 다른 용도인 경우 구분될 수 있도록 하기 위함이다. +- 섹션을 입력하지 않으면 번호가 낮은 섹션의 결과부터 보여진다. + +|섹션|내용| +|-|-| +|1|실행 가능한 프로그램 혹은 쉘 명령어| +|2|시스템 콜(System Calls)| +|3|라이브러리 콜(Library Calls)| +|4|Special File (관련 장치 및 드라이버, 소켓(socket), FIFO, `/dev`의 형식과 관련된 규약)| +|5|파일 포맷(File Formats)과 컨벤션(convention)| +|6|게임(Games)| +|7|Miscellanea (리눅스 시스템 파일 관련 표준, 프로토콜, 문자셋, 규칙 등에 대한 정보가 담긴 영역)| +|8|시스템 관리자 명령어 (root가 사용하는 명령어)| +|9|리눅스 커널 루틴(Linux Kernel Routines)| + +### man 페이지 사용법 + +- man 명령어로 man 페이지를 띄우고 나면 여러 키를 통해 페이지를 조작할 수 있다. +  +- [SPACE] : 한 페이지 밑으로 내려감 +- [위,아래 화살표] : 한 줄 단위로 움직임 +- [ENTER] : 한 줄 밑으로 내려감 +- [b] : 전 페이지로 올라감 +- [k] : 한 줄 위로 올라감 +- [q] : man 페이지 종료 +- [h] : 도움말 +- [/] + 키워드 : 키워드 검색 + - n키 입력 시 다음 검색 결과로 이동,  N(shift + n) 키 입력 시 이전 검색 결과로 이동 + +--- +참고 +- https://www.ibm.com/docs/ru/aix/7.2?topic=m-man-command +- https://man7.org/linux/man-pages/man1/man.1.html \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/\353\252\205\353\240\271\354\226\264\353\223\244.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/\353\252\205\353\240\271\354\226\264\353\223\244.md" new file mode 100644 index 00000000..a58aa728 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/linux/\353\252\205\353\240\271\354\226\264\353\223\244.md" @@ -0,0 +1,113 @@ +--- +title: '명령어들' +lastUpdated: '2023-06-26' +--- + +## 목차 +- cut +- more/less +- tail +- cat +- history +- date +- find +- grep + +--- + +## cut + +파일에서 필드를 뽑아내서 출력 + +- `-f` : 잘라낼 필드를 지정 +- `-d` : 필드를 구분하는 문자를 지정 +- `-c`: 잘라낼 곳의 글자 위치를 지정 + +- `cut -f 1,3,4 -d : /etc/passwd` → /etc/passwd 파일에서 필드를 :로 구분하여 1,3,4번째 필드를 출력 +- `cut-c1-10/etc/passwd` → /etc/passwd 파일에서 첫번째 문자부터 10번째 문자 까지만 출력 + +## more/less + +내용이 많은 파일을 출력할 때 사용하는 명령어 + +- `-f` / `SpaceBar` : 한 페이지 뒤로 이동 +- `-b`: 한 페이지 앞으로 이동 + +## tail + +파일의 내용을 뒷부분부터 출력 + +- `-n` : 지정한 줄만큼 출력 +- `-f` : 내용이 추가되면 계속 이어서 출력 (보통 로그 볼떄 많이 사용) + +## cat + +파일의 내용을 화면에 출력 +- `-n`: 행 번호를 붙여서 출력 +- `-b`: 행 번호를 붙여서 출력하되, 비어있는 행은 제외 +- `-s`: 비어있는 2개 이상의 빈 행은 하나의 행으로 출력 +- `-v`: 탭 문자와 End 문자를 제외한 제어 문자를 ‘^’로 출력 +- `-T` : 탭(tab) 문자(‘^’)를 출력 +- `-E` : (End) 행마다 끝에 ‘$’ 문자를 출력 + +## history + +콘솔에 입력하였던 명령어들의 히스토리를 출력 + +- `-c` : 기존 히스토리를 모두 삭제 +- `history 10` : 최근 10개의 히스토리를 출력 +- 관련 명령 + - !! : 바로 전에 사용한 명령을 다시 수행 + - !`숫자` : 해당 history 번호로 명령을 다시 수행 + - !`문자열`: 해당 문자열이 들어간 가장 최근 명령을 다시 수행 + +## date + +- 형식 : `date [옵션] [포맷]` +- 주요 포맷 + - `+%Y`: 년도를 출력 + - `+%m`: 월을 출력 + - `+%d`: 일을 출력 + - `+%H`: 시를 출력 + - `+%M`: 분을 출력 + - `echo date +%Y%m%d%H%M` → 현재 날짜가 2018년 7월 26일 22시 06분이면 201807262206 출력 + +## find: 파일찾기 + +- 파일 혹은 디렉토리를 대상으로 원하는 파일을 찾아주는 명령어 +- `find [경로] [찾기옵션] [결과옵션]` + +**찾기 옵션** + +|옵션|설명| +|-|-| +|`-name`|패턴과 동일한 이름 검색| +|`-type`|d: 디렉토리 검색
f: 파일 검색
l: 심볼릭링크 검색| +|`-size`|사이즈보다 큰 파일 및 디렉토리 검색| + +**결과 옵션** + +|옵션|설명| +|-|-| +|`-print`|화면에 출력(default)| +|`-exec`|결과를 이용해 외부 프로그램 실행| +|`-delete`|결과물 삭제| + +## grep : 패턴 검색하기 + +- 파일 혹은 일반적인 입력값을 검토하여 특정 패턴(문자열)이 존재하는지 확인하며, 확인된 줄을 출력 + +- grep은 파일 검색의 성공 여부를 종료 상태값으로 리턴 + - 패턴을 찾으면 ‘0‘, 패턴을 찾을 수 없으면 ‘1‘, 파일이 존재하지 않을 경우 ‘2‘ + - `grep [옵션] [패턴(문자열)] [검토할 파일명] ` + +|옵션|설명| +|-|-| +|`-s`|에러 메시지 외에는 출력하지 않음| +|`-v [패턴]`|패턴을 포함하지 않는 행을 리턴| +|`-c`|매치하는 행 수만을 리턴| +|`-n`|라인 번호를 붙여서 리턴| +|`-w`|단어 단위로 패턴 검색| +|`-r`|서브 디렉토리의 파일까지 모두 출력| +|`-b`|검색 결과의 각 행 앞에 검색된 위치의 블록 번호를 표시| +|`-i`|패턴에 대한 대소문자 구별 없이 검색| \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/memory/Memory\342\200\205Mapping.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/memory/Memory\342\200\205Mapping.md" new file mode 100644 index 00000000..b9a2b85c --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/memory/Memory\342\200\205Mapping.md" @@ -0,0 +1,130 @@ +--- +title: 'Memory Mapping' +lastUpdated: '2024-03-02' +--- + +프로세스에 전달한 데이터를 저장한 파일을 직접 프로세스의 가상 주소 공간으로 매핑하는 것 + +> Memory mapping is primarily concerned with associating portions of a process's virtual address space with external storage (e.g., files or devices), enabling efficient data access. It's often used for I/O operations and doesn't directly deal with the translation of logical to physical addresses. + +**관련 개념** + +- **주소 바인딩**: CPU가 프로세스의 작업을 실행하기 위해서는 논리적 주소만으로는 실제 메모리의 주소를 알 수 없기 때문에, 논리 주소와 위에서 물리적 주소를 매핑해주는 "과정" + - CPU가 주소를 참조할 때마다 해당 데이터가 물리적 메모리의 어느 위치에 존재하는지 확인하기위해 + 주소 매핑 테이블을 이용해 주소 바인딩을 점검함 + - Address binding deals with the translation of logical addresses generated by a program into physical addresses in memory. It's essential for program execution and involves techniques like compile time, load time, or run time binding. + - 컴파일 시간 바인딩 (컴파일시) + - 적재시간 바인딩 (링커 -> 로더) + - 실행시간 바인딩 (적재 후 실행시) + +- **Dynamic Loading** + - 모든 루틴(function)을 교체 가능한 형태로 디스크에 저장 + - 함수 호출시에만 가져오고 호출 전에는 적재 X + - 사용되지 않는 루틴들은 메모리를 점유하지 않게 되니 메모리 효율이 좋아짐 + +- **Overlay** + - 실행하려는 프로그램이 메모리보다 클 때 필요없는 영역에 중첩하여 사용 (그때 쓰는 것만 가져와서 씀) + - 운영체제에 의해 이뤄지는게 아니라 프로그래머가 구현했던 방식 + - VMM(Virtual memory management)가 나온 뒤로 사용되지 않음 + +- **Swapping(프로세스 교체)** + - 중기 스케줄러(suspend)에서 스케줄링을 위해 메모리에 올라온 프로세스의 수를 조정하는 방법 + - 자원 안쓰는 프로세스 메모리에서 내리기 + - **Swap In:** secondary memory (**Hard Drive**)에서 main memory (**RAM**)로 옮기기 + - **Swap Out:** main memory(**RAM**)에서 secondary memory(**hard drive**)로 옮기기 + - https://binaryterms.com/swapping-in-operating-system.html + +## Contiguous Allocation + +- 연속적인 메모리 공간을 프로세스에 할당하는 방식이다. 주소 변환으로 인한 CPU 오버헤드를 줄임으로써 프로세스 수행을 빠르게 만든다. +- 가장 쉬운 방법으로는 고정된 크기로 메모리를 나눠 프로세스에게 할당해주는 방식이 있고, 효율적인 메모리 분배를 위해 파티션을 프로세스 크기에 따라 나누는 방법이 있다. +- 내부 단편화(Internal Fragmentation)가 발생할 수 있다. + +### Partition + +- Contiguous Allocation에서 파티션으로 메모리를 관리하는 방식에는 두가지가 있다. + +- **Fixed partition Multiprogramming** + - 고정된 크기로 할당 (미리 분할되어 있다.) + - 똑같은 크기 X, 하지만 한번 정해진 간격이 바뀌진 않음 + - 각 프로세스가 도착한다면 적당한 공간에 넣어주면 된다. + - 각 프로세스는 하나의 분할(Partiotion)에 적재한다 + - 오버헤드 낮지만 Internal, External Fragmentation 둘 다 생김 + - 각 Partition 별로 Boundary register 있음 (서로 관여 X) + +- **Variable partition Multiprogramming** + - 요청시 동적으로 분할하여 할당 + - 종료되어도 파티션 유지 + - External Fragmentation만 있음 + - First fit (최초 적합), Best fit (최적 적합), Worst fit (최악 적합), Next fit (순차 최초 적합) 등의 최적화 방법이 있다 + +## Non-Contiguous Allocation + +- 프로그램을 블록으로 나눠서 할당한다. (프로그램에 연속적인 메모리 공간을 할당하지 않고, 블록 단위로 쪼개서 할당) +- 필요한 블록만 가져와서 사용 +- 나머지는 swap device에 존재 + +- **BMT** + - 블록위치 정보를 저장하는 매핑 테이블 + - Residence bit: 블록 적재 여부 + - Real address: 블록의 실제 주소 +- **BMT를 사용해 특정 메모리 주소 구하기** + 1. BMT의 block b 칸 찾기 + 2. redidence bit 검사 + - 0인 경우: swap device에서 블록 가져와 테이블 갱신 후 real addr 확인 + - 1인 경우: real addr 확인 + 3. 실제 주소 r(a+d) 계산 및 접근 + +크게 Non-Contiguous Allocation은 block의 크기를 정적(page)으로 정하냐, 동적으로 정하냐(segmentation)에 따라 나눈 두가지의 방법이 있고, 거기에 두 방법을 합친 Hybrid 방법이 있다. + +### **Paging System** + - page(p): 프로세스의 block + - page frame(p’): 메모리의 분할 영역, 페이지와 크기 같음 + - ex) 프로그램 756, 페이지 150 → 페이지 총 6개 + - There is no external fragmentation in paging but internal fragmentation exists + - simple and efficient + - PMT을 사용해 page mapping 정보를 저장함 + - **Direct Mapping** + - `b + p * entrysize` + - **진행순서** + 1. PMT가 저장된 주소 b에 접근 + 2. page p에 대한 entry 찾기 + 3. 찾은 entry의 존재비트 검사, p’ 번호 확인 + 4. p’와 변위 d를 사용해 주소 r 확인 + - **문제점** + - 접근 횟수 2배, 성능 저하 + - PMT를 위한 공간 필요 + - 해결 + - TLB를 이용한 Associate Mapping + - **Associate Mapping** + - PMT를 위한 전용 기억장치 사용 → 캐시 = AMT, P’를 찾을 때, AMT 부터 접근 + - page number를 병렬 탐색하여 p’를 빠르게 찾음 + - 하드웨어 비싼 대신 오버헤드 낮고 속도 빠름 + - **진행순서** + 1. AMT에 있는 경우 → residence bit로 p’확인 + 2. AMT에 없는 경우 → PMT 확인 후 AMT에 entry 적재 + 3. PMT에서 사용되는 entry 지역성을 따져 AMT에 적재 + +### **Segmentation** + - 서로 다른 크기를 갖는 논리적인 단위 + - 서브루틴이나 함수, 행렬, 스택 등 단위로 이름 붙여 적재 + - 미리 분할 X + - 공유 및 보호 용이 + - 주소 매핑 및 메모리 관리 오버헤드가 큼 + - Segment Map Table을 가짐 + - SMT를 통해 물리주소를 찾는 순서 + 1. SMT에 접근 + 2. segment에 대한 entry 찾기 + 3. 존재비트 검사 + - 없으면 적재 + - 변위 d가 segment 크기보다 크면 overflow 에러 + - protection bit 상 접근 불가능한 상태면 Protection Exception + 4. 가상 주소와 변위로 실주소 확인 + +### **Hybrid** + - 프로그램을 segment로 나누고 그걸 page로 나눔, 적재는 page 단위로. + - segmentation 방식에서 외부 단편화 없애기 위해 paging 방식과 섞음 + - 외부보다 내부 단편화가 좀 더 안정적이기 때문 + - SMT PMT 모두 사용 + - 메모리 소모 많고, 매핑 복잡해 접근시간 김 + - 외부단편화 X 내부단편화 O diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/memory/\355\216\230\354\235\264\354\247\200\342\200\205\352\265\220\354\262\264\342\200\205\354\225\214\352\263\240\353\246\254\354\246\230.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/memory/\355\216\230\354\235\264\354\247\200\342\200\205\352\265\220\354\262\264\342\200\205\354\225\214\352\263\240\353\246\254\354\246\230.md" new file mode 100644 index 00000000..249bb083 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/memory/\355\216\230\354\235\264\354\247\200\342\200\205\352\265\220\354\262\264\342\200\205\354\225\214\352\263\240\353\246\254\354\246\230.md" @@ -0,0 +1,75 @@ +--- +title: '페이지 교체 알고리즘' +lastUpdated: '2024-03-02' +--- + +1. **MIN(OPT)** + - 최근 미래에 안 쓰이는거 교체 + - 이론상 베스트인데 실제 상황에선 참조 예측이 안되니 구현 불가능 + - 비교 연구 목적 + +2. **Random 알고리즘** + - 진짜 랜덤 + +3. **FIFO 알고리즘** + - 가장 오래전에 들어온 페이지가 희생자 + - 자주 사용되는 페이지 교체가능성 큼 (지역성 고려 없음) + +4. **LFU(Least Frequently Used)** + - 사용횟수가 가장 적은 페이지가 희생자 + - 구현 비용이 비싸고 성능 별로 + - 초기에만 많이 쓰는건 교체 잘 안됨 + - 방금 들어온 페이지가 잘 교체됨 + +5. **LRU(Least Recently Used) 알고리즘** + - 가장 오래전에 참조한 페이지가 희생자 + - 지역성에 기반한 알고리즘 + - Stack, Counter 등을 추가 저장해야해서 HW 지원 필요 + +6. **NUR(Not Used Recently)** + - 이론적인 알고리즘 + - LRU보다 적은 오버헤드로 비슷한 성능 + - 참조비트와 변형비트 활용 + - 참조비트: 참조된 적 있는지 여부, 주기적으로 0으로 초기화 + - 변형비트: 변형, 수정된 적 있는지 여부 + - (R,M) (0,0),(0,1),(1,0),(1,1) 순서로 교체 우선순위 높음 + +7. **Clock 알고리즘** + - NUR 실 적용 예 + - 참조비트만 사용(참조시 1로 변경되는 비트) → 주기적 초기화없음 + - 프레임을 순차적으로 가리키는 포인터 사용 + - 포인터가 돌면서 0인 페이지를 교체 대상으로 선정 + - 먼저 적재되었으면서 최근에 참조되지 않은 페이지가 교체됨 + +8. **Second Chance 알고리즘** + - NUR 실 적용 예2 + - Clock + 변형비트 + - 검색 시간 길어짐 + - (0,1) to (0,0)으로 변하는 경우 write-block list에 추가됨 + +9. **Working Set algorithm** + - 특정 기간동안 사용했던 프레임 목록을 적재 + - 프레임 갯수 정해지지 않음 + - Working set: 어떤 시점에 자주 참조하는 page의 집합 (시간에 따라 변함) + - W(t-Δ, t): [t-Δ, t] 동안 참조된 page의 집합 (Δ는 고정) + - allocated: 적재된 페이지 수 + - page fault, 평균 allocated 수로 성능 평가함 + - 특징 + - 적재 없어도 반납, 적재 있어도 교체 없는 경우 있음 + - 단점 + - 모니터링으로 인한 오버헤드 + +10. **Page Fault Frequency algorithm** + + - residence set size를 page fault rate에 따라 결정 + - page fault 낮으면 frame 수 감소, 높으면 증가 + - Page fault 발생시 inter fault time 계산 (현재 fault 시간 - 이전 fault 시간) + - inter fault time이 기준치 이상이면 이전 fault 이후 ~ 현재 사이에 쓰였던 것만 유지 + - 기준치 이하면 추가 + - 메모리가 fault일 때만 변화해서 Working set보다 오버헤드 낮음 + +11. **VMIN algorithm** + + - optimal 처럼 이론적으로 최적인 알고리즘 + - 델타만큼의 미래 안에 다시 사용되는 메모리만 유지함 (t, t+델타] + - 있으면 유지 없으면 즉시 삭제 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/TAS.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/TAS.md" new file mode 100644 index 00000000..46c09695 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/TAS.md" @@ -0,0 +1,204 @@ +--- +title: 'TAS' +lastUpdated: '2024-03-02' +--- + +TAS은 메모리 위치를 write하고, 그것의 이전 값을 atomic으로 (thread safe)하게 반환하는 함수이다. + +한 프로세스가 TAS를 performing하고 있다면, 다른 프로세스는 그 프로세스의 작용이 끝나기 전에 TAS를 실행할 수 없다. + +## 동작 + +**동작 내용** +- 주어진 불리언 값의 현재 값을 반환하고, 그 불리언 값을 true 로 덮어쓴다. + +**반환 정보** +- true : 플래그가 원래 true 일 때. +- false : 플래그가 원래 false 일 때. ( flag = true 명령도 동시에 일어남) + +중간에 문맥교환이 발생한다면 결과 값은 달라질 수 있지만, 간단한 예제코드로 살펴보자면 아래와 같다. + +```c +bool flag = false; +testAndSet(flag); // false +testAndSet(flag); // true +testAndSet(flag); // true +flag = false; +testAndSet(flag); // false +``` + +image + + +## 하드웨어적 구현 방법 + +두개의 CPU가 있다. 그리고 두 CPU가 공유할 수 있는 공간인 [DPRAM](https://en.wikipedia.org/wiki/DPRAM)이 있다. + +CPU1이 TAS를 발행하면, DPRAM이 `internal note`라는 걸 내부에 만들어놓고 메모리 주소를 저장해놓는다. + +이때 나머지 CPU2가 같은 메모리에 TAS를 발행하면 DPRAM은 `internal note`를 체크해본다. 그곳에 뭔가 있다는걸 확인하면 `BUSY` interrrupt를 발행해서 CPU2가 기다린 후에 retry하도록 한다. 이것이 바로 interrupt mechanism를 사용해서 busy waiting과 spinlock을 구현하는 방법이다. + +CPU2가 메모리공간에 접근을 시도하든 그렇지 않든, DPRAM은 CPU 1이 제공하는 `test`를 수행한다 그 test가 성공하면 DPRAM은 메모리 주소를 CPU1이 줬던 값으로 바꾸고, `internal note`를 지워서 다른 CPU나 프로세스가 접근할 수 있도록 한다. + +## 소프트웨어적 구현 방법 + +TAS 명령어는 bool과 함께 사용될 때 함수가 atomic하게 실행되어야 한다는 점을 제외하고, 아래 코드와 같은 로직을 사용한다. + +```c +function TestAndSet(boolean_ref lock) { + boolean initial = lock; + lock = true; + return initial; +} +``` + +여기서 설정되는 값과 테스트하는 값은 고정적이고 불변적이며 테스트 결과에 관계없이 값이 업데이트되는 반면, 위에서 봤던 DPRAM TAS의 경우 메모리는 테스트가 성공할 때만 설정되며, 설정할 값과 테스트 조건은 CPU에 의해 지정된다. + +여기서는 설정할 값이 1만 될 수 있도록 했지만 0과 1이 메모리 위치에 대한 유일한 유효한 값으로 간주되고 "값이 0이 아니다"가 유일하게 허용되는 테스트인 경우, 이는 DPRAM 하드웨어에 대해 설명된 경우와 동일하다. 그러한 관점에서, 이것은 TAS라고 정의할 수 있다. + +중요한 점은 테스트 앤 세트의 일반적인 의도와 원칙이다: 값은 테스트되고 하나의 atomic한 연산에서 설정되므로 다른 프로그램 스레드나 프로세스는 그것이 test한 다음 set되기 전에 대상 메모리 위치를 변경할 수 없다. (메모리 값은 무조건 특정 값을 하나만 가지기 때문이다.) + +## SpinLock + +TAS의 성질을 활용하여 SpinLock을 구현할 수 있다. + +```c +#include +#include +#include +#include +#define SIZE 5 +using namespace std; + +atomic_flag flag; +void foo(int id) { + //! get lock. + while (flag.test_and_set()); + + //! critical section. + cout << id << " enter ciritical section." << endl; + this_thread::sleep_for(chrono::milliseconds(1500)); + + //! release lock. + cout << id << " release lock." << endl; + flag.clear(); +} + +int main() { + vector t_arr; + for (int i = 0; i < SIZE; i++) t_arr.emplace_back(foo, i); + for (int i = 0; i < SIZE; i++) t_arr[i].join(); + cout << "done" << endl; +} +``` + +제일 먼저 TAS를 실행한 쓰레드만 반복문을 빠져나올 수 있고, 이외의 다른 쓰레드는 clear 가 발생될 때 까지 반복문에 갇혀서 대기하게 된다. + +즉, while을 빠져나온 쓰레드는 프로세스 내에서 1개만 존재하며, + +상호배제 원리에 의하여 while 이하에 임계영역이 형성된다. + +## counter + +TAS로 atomic한 카운터도 구현할 수 있다. + +먼저 쓰레드에 안전하지 않은 버전부터 살펴보자. + +```c +#include +#include +#include +#include +using namespace std; + +#define THREAD_N 10000 +#define LOOPED_N 10000 + +class Counter { + int value; +public: + Counter() :value(0) {}; + void count() { value += 1; } + int get() { return value; } +}; + +Counter counter; + +void multiCount() { + int T = LOOPED_N; + while (T--) counter.count(); +} + +int main() { + vector t_arr; + + for (int i = 0; i < THREAD_N; i++) t_arr.emplace_back(multiCount); + for (int i = 0; i < THREAD_N; i++) t_arr[i].join(); + + printf("Desired : %10d \n", THREAD_N * LOOPED_N); + printf("Acquire : %10d \n", counter.get()); + + // Result (22.2790s) + // Desired : 100000000 + // Acquire : 85096169 +} +``` + +10000개의 스레드가 각각 10,000번 count 하도록 했으니 결과값은 100,000,000이 나와야 한다. + +하지만 위 코드에서 정확한 값이 나오지 않은 이유는, 여러 스레드의 동작이 겹쳤기 때문이다. + +예를 들어 `v = 100`인 상태에서, 이미 어떤 쓰레드가 `v = 100 + 1` 으로 갱신중하려 함에도 불구하고, `v = 100 + 1` 로 갱신하려는 다른 쓰레드가 있었기에 발생한 것 이다. + +```c +#include +#include +#include +#include +using namespace std; + +#define THREAD_N 10000 +#define LOOPED_N 10000 + +class AtomicCounter { + atomic_flag flag; // 달라진 부분 + int value; +public: + AtomicCounter() :value(0) {}; + void count() { // 달라진 부분 + while (flag.test_and_set()); + value += 1; + flag.clear(); + } + int get() { return value; } +}; + +AtomicCounter counter; + +void multiCount() { + int T = LOOPED_N; + while (T--) counter.count(); +} + +int main() { + vector t_arr; + for (int i = 0; i < THREAD_N; i++) t_arr.emplace_back(multiCount); + for (int i = 0; i < THREAD_N; i++) t_arr[i].join(); + + printf("Desired : %10d \n", THREAD_N * LOOPED_N); + printf("Acquire : %10d \n", counter.get()); + + // Result is, (75.7520s) + // Desired : 100000000 + // Acquire : 100000000 +} +``` + +값을 그냥 증가시키지 않고, TAS를 통해서 한 쓰레드만 값을 증가시킬 수 있도록 순서를 직렬화하면 문제를 해결할 수 있다. + +--- + +참고 + +- https://en.wikipedia.org/wiki/Test-and-set +- https://gobyexample.com/atomic-counters \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\352\265\220\354\260\251\354\203\201\355\203\234\354\231\200\342\200\205\354\212\244\354\274\200\354\244\204\353\247\201.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\352\265\220\354\260\251\354\203\201\355\203\234\354\231\200\342\200\205\354\212\244\354\274\200\354\244\204\353\247\201.md" new file mode 100644 index 00000000..920b538e --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\352\265\220\354\260\251\354\203\201\355\203\234\354\231\200\342\200\205\354\212\244\354\274\200\354\244\204\353\247\201.md" @@ -0,0 +1,88 @@ +--- +title: '교착상태와 스케줄링' +lastUpdated: '2023-12-19' +--- +### 탐지 detection +- 교착상태를 탐지하는 알고리즘 필요 +- 즉시할당하지 못하거나 CPU 사용률이 40% 이하로 떨어지는 경우 호출 +- 탐지 자주실행하면 성능 저하 +- 교착상태에 빠진 프로세스를 빨리 발견 -> 유휴상태 방지 가능 +- 자원할당 그래프를 활용하여 탐지 +- 교착 탐지시 회복(복구)과정 필요 + +### 회복 recovery +1. 강제종료 + 1. 교착을 일으킨 모든 프로세스 중지 (비용큼) + 2. 교착이 해결될 때 까지 하나씩 중지 (오버헤드 큼) +2. 교착 프로세스가 점유하는 자원을 다른 프로세스가 선점하게 함 + 우선순위가 낮은 프로세스 + 매번 결과를 기록해서 선점 횟수에 따라 할당해주지 않으면 기아 발생 가능성 있음 + +### 프로세스 스케줄링 +- 다중프로그래밍 시스템 +- I/O Burst, CPU Burst + +### 종류 +- 장기스케줄러 (준비-생성) + - 어떤 프로세스를 준비큐에 넣을 것인지 결정 (시분할에선 X) + - 메모리에 동시에 올라간 ps의 수를 조정 +- 중기스케줄러 (중단) + - 메모리에 올라간 프로세스 수 관리 + - 메모리 부족하면 swap in(디스크 스왑영역에 저장), 여유 생기면 swap in +- 단기스케줄러 + - 메모리 내 준비상태 작업중 실행할 ps에 CPU 할당 + - 미리 정한 알고리즘에 따라 선택 + - 매우 빈번하게 호출 + +### 알고리즘 선택기준 +- 프로세서 사용률 +- 처리율(단위시간당 완료 작업 수) +- 반환시간(모두완료) +- 대기시간(in 준비큐) +- 반응시간(요청 후첫반응까지) + +### 비선점스케줄링 +- 한 프로세스가 자원을 선택했을때 다른 프로세스가 빼앗을 수 없음 +- 공정성 O 융통성 X + +### 비선점스케줄링 알고리즘 +- FCFS +- SJF(Shortest Job First) + - 최단시간 작업 우선 + - 준비큐에서 기다리는 ps중 실행시간이 가장 짧은 작업에 할당 + - 평균대기시간을 줄일 수 있음 + - 긴 작업의 ps는 기아상태가 될 가능성이 있음 +- HRRN(Highest Response Ratio Next) + - SJF를 보완한 기법 + - 우선순위 계산값이 큰 수부터 CPU 할당 + - (대기시간/예상실행시간) +1 + +### 선점 스케줄링 +- 현재 실행중인 ps를 인터럽트하거나 준비상태로 이동시킬 수 있음 +- 대화식 시분할, 실시간 시스템 등 빠른 응답시간 위해 필요 +- context switching으로 인한 오버헤드 큼 +- 우선순위 잘 부여해야함 +- 우선순위 높은 ps의 긴급처리요청 가능 + +### 선점스케줄링 알고리즘 +- RR(Round Robin) + - 시분할시스템 시간단위(quantam/time slice)로 CPU 할당 + - 할당 크면 FIFO랑 같아짐, 작으면 문맥교환 오버헤드 커짐 + - 응답시간 짧음 - 실시간 시스템 유지 +- SRTF(Shortest Remaining Time First) + - 선점 SJF + - 현재 ps랑 준비큐의 top중 더 짧은애 실행 + - 시분할에 효과적 + - 실행시간을 추적하여 보유하므로 오버헤드 큼 +- MLQ(Multilevel queue) + - 그룹마다 준비큐 형성 + - 큐마다 다른 Quantum 설정 (우선순위 높으로 tq 짧음) +- MFQ(Multileven Feedback Queue) + - 다단계큐 + Feedback + - 사용 후 한칸씩 내려감 + - 가장 하위큐에서 오래 대기할 경우 위로 이동(에이징) + - 하위큐는 FIFO + +### 우선순위 스케줄링 +- 우선순위에 따라 실행 +- 선점, 비선점 방식 모두 존재 diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\354\203\235\354\202\260\354\236\220\342\200\205\354\206\214\353\271\204\354\236\220\342\200\205\353\254\270\354\240\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\354\203\235\354\202\260\354\236\220\342\200\205\354\206\214\353\271\204\354\236\220\342\200\205\353\254\270\354\240\234.md" new file mode 100644 index 00000000..07327878 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\354\203\235\354\202\260\354\236\220\342\200\205\354\206\214\353\271\204\354\236\220\342\200\205\353\254\270\354\240\234.md" @@ -0,0 +1,103 @@ +--- +title: '생산자 소비자 문제' +lastUpdated: '2024-03-02' +--- + +생산자-소비자 문제(producer-consumer problem)는 여러 개의 프로세스를 어떻게 동기화할 것인가에 관한 고전적인 문제이다. 한정 버퍼 문제(bounded-buffer problem)라고도 한다. + +유한한 개수의 물건(데이터)을 임시로 보관하는 보관함(버퍼)에 여러 명의 생산자들과 소비자들이 접근한다. 생산자는 물건이 하나 만들어지면 그 공간에 저장한다. 이때 저장할 공간이 없는 문제가 발생할 수 있다. 소비자는 물건이 필요할 때 보관함에서 물건을 하나 가져온다. 이 때는 소비할 물건이 없는 문제가 발생할 수 있다. + +이 문제를 해결하는 것을 생산자-소비자 협동이라 하며, 버퍼가 동기화되어 정상적으로 동작하는 상태를 뜻한다. 문제를 해결하기 위해 세마포어를 활용할 수 있다. + +## 방법 1 + +변수 +- Empty : 버퍼 내에 저장할 공간이 있는지를 나타낸다. (초기값은 n) +- Full : 버퍼 내에 소비할 아이템이 있는지를 나타낸다. (초기값은 0) +- Mutex : 버퍼에 대한 접근을 통제한다. (초기값은 1) + +### 생산자 프로세스 + +```c +do { + ... + // 아이템을 생산한다. + ... + wait(empty); // 버퍼에 빈 공간이 생길 때까지 기다린다. + wait(mutex); // 임계 구역에 진입할 수 있을 때까지 기다린다. + ... + // 아이템을 버퍼에 추가한다. + ... + signal(mutex); //임계 구역을 빠져나왔다고 알려준다. + signal(full); //버퍼에 아이템이 있다고 알려준다. +} while (1); +``` + +### 소비자 프로세스 + +```c +do { + wait(full); // 버퍼에 아이템이 생길 때까지 기다린다. + wait(mutex); + ... + // 버퍼로부터 아이템을 가져온다. + ... + signal(mutex); + signal(empty); // 버퍼에 빈 공간이 생겼다고 알려준다. + ... + // 아이템을 소비한다. + ... +} while (1); +``` + +## 방법 2 - 모니터 + +```c +monitor ProducerConsumer { + int itemCount = 0; + condition full; + condition empty; + + procedure add(item) { + if (itemCount == BUFFER_SIZE) { + wait(full); // 버퍼에 빈 공간이 생길 때까지 기다린다. + } + + putItemIntoBuffer(item); + itemCount = itemCount + 1; + + if (itemCount == 1) { + notify(empty); + } + } + + procedure remove() { + if (itemCount == 0) { + wait(empty); // 버퍼에 아이템이 생길 때까지 기다린다. + } + + item = removeItemFromBuffer(); + itemCount = itemCount - 1; + + if (itemCount == BUFFER_SIZE - 1) { + notify(full); + } + + return item; + } +} + +procedure producer() { + while (true) { + item = produceItem(); + ProducerConsumer.add(item); + } +} + +procedure consumer() { + while (true) { + item = ProducerConsumer.remove(); + consumeItem(item); + } +} +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\354\236\204\352\263\204\354\230\201\354\227\255\352\263\274\342\200\205\354\203\201\355\230\270\353\260\260\354\240\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\354\236\204\352\263\204\354\230\201\354\227\255\352\263\274\342\200\205\354\203\201\355\230\270\353\260\260\354\240\234.md" new file mode 100644 index 00000000..ea351bb4 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\354\236\204\352\263\204\354\230\201\354\227\255\352\263\274\342\200\205\354\203\201\355\230\270\353\260\260\354\240\234.md" @@ -0,0 +1,184 @@ +--- +title: '임계영역과 상호배제' +lastUpdated: '2023-12-18' +--- +### 임계 영역(Critical Section) + +- 공유자원에 접근하는 프로세스 내부의 코드 영역으로 어떤 한 프로세스가 한 영역의 데이터를 사용하고 있을 때, 다른 프로세스가 그 영역의 데이터를 같이 사용한다면 코드상에서 문제가 발생할 수 있다. + +- 따라서 문제가 발생하지 않도록 특정 영역 내의 한번에 하나의 프로세스만 이용하게끔 보장해야해한다. 그러한 영역을 임계영역이라고 부든다. + +- 임계 영역의 문제를 해결하기 위해서는 아래 3가지 조건을 충족해야 한다. + +- **상호배제** + - 하나의 프로세스가 임계 영역에 들어가있다면 다른 프로세스는 들어갈 수 없어야 한다. +- **진행** + - 임계 영역에 들어간 프로세스가 없는 상태에서 들어가려 하는 프로세스가 여러개라면 어느 것이 들어갈지 결정해주어야 한다. +- **한정 대기** + - 다른 프로세스의 기아를 방지하기 위해, 한 번 임계 영역에 들어간 프로세스는 다음번 임계 영역에 들어갈 때 제한을 두어야 한다. + +- 임계 영역의 동시접근을 해결하기 위한 방법으로 뮤텍스 (Mutex), 세마포어(semaphore), 모니터(monitor)등이 있다. + +# 뮤텍스 (Mutex, 상호배제) + +- 다른 프로세스 간 동기화에 사용된다. +- 임계 구역에 단 하나의 스레드만 접근 가능하다. +- 다중 프로세스들의 공유 리소스에 대한 접근을 조율하기 위해 Locking과 Unlocking을 사용한다. + 한 스레드가 임계영역에 들어가기 위해서 lock을 하고, 나올 땐 unlock을 한다. +- priority inheritence의 특성을 가진다. (mutex를 가진, 즉 잠금을 건 프로세스를 우선적으로 실행하여 락을 빨리 풀 수 있도록 함) + +## 뮤텍스 알고리즘 +### 1. 데커(Dekker) 알고리즘 + +flag와 turn 변수를 통해 임계 구역에 들어갈 프로세스/스레드를 결정하는 방식이다. +- flag : 프로세스 중 누가 임계영역에 진입할 것인지 나타내는 변수 +- turn : 누가 임계구역에 들어갈 차례인지 나타내는 변수 + +```c +while (true) { + flag[i] = true; // 프로세스 i가 임계 구역 진입 시도 + while (flag[j]) { // 프로세스 j가 현재 임계 구역에 있는지 확인 + if(turn == j) { // j가 임계 구역 사용 중이면 + flag[i] = false; // 프로세스 i 진입 취소 + while(turn == j); // turn이 j에서 변경될 때까지 대기 + flag[i] = true; // j turn이 끝나면 다시 진입 시도 + } + } +} + +// ------- 임계 구역 --------- + +turn = j; // 임계 구역 사용 끝나면 turn을 넘김 +flag[i] = false; // flag 값을 false로 바꿔 임계 구역 사용 완료를 알림 +``` + +### 2. 피터슨(Peterson) 알고리즘 + +데커와 유사하지만, 상대방 프로세스/스레드에게 진입 기회를 양보하는 것에 차이가 있다. + +```c +while (true) { + flag[i] = true; // 프로세스 i가 임계 구역 진입 시도 + turn = j; // 다른 프로세스에게 진입 기회 양보 + while (flag[j] && turn == j) { } // 다른 프로세스가 진입 시도하면 대기 +} + +// ------- 임계 구역 --------- + +flag[i] = false; // flag 값을 false로 바꿔 임계 구역 사용 완료를 알림 +``` + +### 3. Lamport's bakery algorithm + +여러 프로세스/스레드에 대한 처리가 가능한 알고리즘. + +가장 작은 수의 번호표를 가지고 있는 프로세스가 임계 구역에 진입한다. + +```c +while (true) { + + isReady[i] = true; // 번호표 받을 준비 + number[i] = max(number[0~n-1]) + 1; // 현재 실행 중인 프로세스 중에 가장 큰 번호 배정 + isReady[i] = false; // 번호표 수령 완료 + + for(j = 0; j < n; j++) { // 모든 프로세스 번호표 비교 + while(isReady[j]); // 비교 프로세스가 번호표 받을 때까지 대기 + while(number[j] && number[j] < number[i] && j < i); + + // 프로세스 j가 번호표 가지고 있어야 함 + // 프로세스 j의 번호표 < 프로세스 i의 번호표 + } +} + +// ------- 임계 구역 --------- + +number[i] = 0; // 임계 구역 사용 종료 +``` + +# 세마포어 (Semaphore) + +- 뮤텍스가 임계 영역에 들어가는 스레드가 하나라면, 세마포어는 복수개가 가능하다. +- wait과 signal을 통해 구현된다. +- wait이 먼저 호출되어 임계영역에 들어갈 수 있는지 확인 or 먼저 실행되어야 하는 프로세스가 실행되는지 확인 +- 조건에 만족하면 wait을 빠져나와 임계영역으로 들어간다. +- 이후 signal이 호출되어 임계영역에서 빠져나왔음을 알린다. + +### 뮤텍스와 세마포어의 차이 + +- 세마포어는 자원의 상태를 나타내는 일종의 '변수'로써 소유 개념이 아니지만, 뮤텍스는 자원을 점유한 프로세스나 쓰레드가 잠시 소유하였다가 작업이 끝나면 반환하는 개념이다. +- 세마포어는 뮤텍스가 될 수 있지만, 뮤텍스는 세마포어가 될 수 없다. +- 세마포어는 시스템 범위에 걸쳐있고 파일 시스템 상의 파일 형태로 존재한다. 반면, 뮤텍스는 프로세스 범위를 가지고, 프로그램이 종료될 때 자동으로 지워진다. +- 세마포어는 동기화 대상이 여러개일 때, 뮤텍스는 동기화 대상이 오로지 하나일때 사용된다. + +# 모니터 (Monitor) + +- **하나의 프로세스 내의 다른 스레드 간 동기화**에 사용된다. (뮤텍스는 다른 프로세스간의 동기화) + +- 모니터 큐에 작업을 쌓아놓고 한번에 하나의 프로세스만 임계영역에 접근할 수 있도록 한다. + - 모니터만을 통해 데이터에 접근할 수 있는 것이다. + +- 다른 방법들보다 구현체가 더 자세히 정의되어있다. + - **wait**: 자기 자신 큐에 넣고 대기 + - **signal**: 대기중인 스레드 하나를 깨움 + - **brodcast**: 모두를 깨움 + +- Java에는 모니터를 사용한 일련의 동기화 작업들이 캡슐화되어 있어서 `synchronized`, `wait()`, `notify()`등의 키워드와 함수를 통해 편하게 사용할 수 있다. 함수 앞에 `synchronized`를 붙여주기만 하면 함수의 작업을 상호배제하며 수행한다. + +### 구현 + +- 모니터는 세마포어를 활용해 구현할 수 있다. + +- `x.wait()`은 아래와 같이 구현할 수 있다. + + ```c + semaphore x_sem; // (initially = 0) + int x_count = 0; // number of process waiting on condition (x) + + /* + * This is used to indicate that some process is issuing a wait on the + * condition x, so in case some process has sent a signal x.signal() + * without no process is waiting on condition x the signal will be lost signal (has no effect). + */ + x_count++; + + /* + * if there is some process waiting on the ready queue, + * signal(next) will increase the semaphore internal counter so other processes can take the monitor. + */ + if (next_count > 0) + signal(next); + /* + * Otherwise, no process is waiting. + * signal(mutex) will release the mutex. + */ + else + signal(mutex); + /* + * now the process that called x.wait() will be blocked until other process will release (signal) the + * x_sem semaphore: signal(x_sem) + */ + wait(x_sem); + // process is back from blocking. + // we are done, decrease x_count. + x_count--; + ``` + +- `x.signal()`은 아래와 같이 구현할 수 있다. + + ```c + // if there are processes waiting on condition x. + if (x_count > 0) { + // increase the next count as new blocked process has entered the queue (the one who called x.wait()). remember (wait(x_sem)) + next_count++; + // release x_sem so the process waiting on x condition resume. + signal(x_sem); + // wait until next process is done. + wait(next); + // we are done. + next_count--; + } + ``` + +--- +참고 +- https://stackoverflow.com/questions/46919797/why-is-a-monitor-implemented-in-terms-of-semaphores-this-way \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\355\224\204\353\241\234\354\204\270\354\212\244\354\235\230\342\200\205\352\260\234\353\205\220.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\355\224\204\353\241\234\354\204\270\354\212\244\354\235\230\342\200\205\352\260\234\353\205\220.md" new file mode 100644 index 00000000..209c8f68 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\355\224\204\353\241\234\354\204\270\354\212\244\354\235\230\342\200\205\352\260\234\353\205\220.md" @@ -0,0 +1,107 @@ +--- +title: '프로세스의 개념' +lastUpdated: '2024-03-02' +--- + +프로세스(process)는 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램을 의미한다. + +프로세스 내부에는 최소 하나의 스레드(thread)를 가지고있는데, 실제로는 스레드(thread)단위로 스케줄링이 이뤄진다. + +하드디스크에 있는 프로그램을 실행하면, 실행을 위해서 메모리 할당이 이루어지고, 할당된 메모리 공간으로 바이너리 코드가 올라가게 된다. 이 순간부터 프로세스라 불린다. + +## Processs memory + +image + +- Code 영역 : 프로그램을 실행시키는 실행 파일 내의 명령어(소스코드) +- Data 영역 : 전역변수, static 변수 +- Heap 영역 : 동적할당을 위한 메모리 영역. + - C언어 : malloc & free // C++ : new & delete // JAVA : new 등 +- Stack 영역 : 지역변수, 함수 호출시 전달되는 인자(파라미터)를 위한 메모리 영역. + +## Process state + +프로세스의 상태는 프로세스가 실행됨에 따라 달라진다. + +1. New : 프로세스가 처음 생성된 상태 +2. Ready : 프로세스가 CPU에 할당되기를 기다리는 상태 (메모리 등 다른 조건을 모두 만족하고) +3. Running : 프로세스가 할당되어 CPU를 잡고 명령을 수행 중인 상태 +4. Waiting : 프로세스가 어떠한 이벤트가 발생하기를 기다리는 상태. CPU를 할당해도 당장 명령을 수행할 수 없는 상태. +5. Terminated : 프로세스가 실행을 마쳤을 때. 아직 완전히 프로세스가 제거되진 않은 상태. + +image + +Ready, Waiting 상태의 프로세스는 실행중인 것은 아니지만 메인 메모리에 올라와 있어야 한다. + +하지만 긴 시간동안 대기해야해서 대기 시간동안 메모리 사용이 불필요하거나 메모리에 너무 많은 프로세스가 올라가 있는 경우, 프로세스가 Suspended Ready(중단된 준비)나 Suspended Waiting(중단된 대기) 상태로 변할 수 있다. 혹은 부모가 자식을 중단 상태로 만들 수도 있다. + +Waiting 상태와 유사하지만 차이가 있는데, Waiting은 자신이 요청한 이벤트가 만족되면 Ready 상태로 돌아가지만, Suspended는 외부에서 다시 시작을 해주어야 활성화가 된다. + +따라서 이를 반영한 프로세스의 상태표는 다음과 같다. + +image + +## Process Control Block (PCB) + +PCB(Process Control Block)는 운영체제가 각 프로세스를 관리하기 위해 프로세스별로 보유하고 있는 자신의 정보 묶음이다. 커널의 주소 공간에 있으며 다음의 구성 요소를 갖는다. + +1. 운영체제가 관리상 사용하는 정보 + - Process state + - Process ID + - Scheduling information : 프로세스의 중요도, 스케줄링 큐 포인터 등 스케줄링 파라미터 정보 + - Priority : 프로세스의 우선순위 + +2. CPU 수행 관련 하드웨어 값 + - Program counter : 해당 프로세스가 이어서 실행해야 할 명령의 주소를 가리키는 포인터 + - Register : 프로세스가 인터럽트 이후 올바르게 작업을 이어가기 위해 참조하는 CPU 레지스터 값 + +3. 메모리 관련 + - Code, Data, Stack의 위치 정보, base/limit 레지스터 값 + +4. 파일 관련 + - open file descriptors : 열린 파일 목록 + +## Context Switch + +- 이전 프로세스의 상태 레지스터 내용을 보관하고 다른 프로세스의 레지스터를 적제하여 프로세스를 교환하는 과정이다. +- 운영체제는 CPU를 내어주는 프로세스의 상태를 그 프로세스의 PCB에 저장하고, CPU를 새롭게 얻는 프로세스의 상태를 PCB에서 읽어온다. +- 즉, CPU입장에서 Context는 PCB이기 때문에 **PCB 정보가 바뀌는 것이 Context Switch**라고 볼 수 있다. + +- 다만, 시스템 콜이나 인터럽트가 발생한다고 반드시 Context Switch가 일어나는 건 아니다. 다른 프로세스에 프로세서가 넘어가야 Context Switch이다. + +image + +image + +## Process Scheduling + +**멀티프로그래밍(Multiprogramming)**의 목적은 CPU를 최대한 사용하기 위해 몇몇 프로세스를 항상 실행시키는 것이다. **시간 공유(Time Sharing)**의 목적은 프로세스 간에 CPU를 빠르게 전환함으로써 사용자가 각 프로그램이 실행되는 동안 서로 상호작용할 수 있도록 만드는 것이다. + +이러한 목적을 달성하기 위해 프로세스 스케줄러는 CPU에서 프로그램 실행을 위해 사용 가능한 프로세스를 선택한다. 이렇게 어떤 프로세스를 프로세서에 할당할 것인가를 결정하는 일을 프로세스 스케줄링(Process Scheduling)이라고 한다. + +프로세서가 하나인 시스템은 오직 하나의 running 프로세스를 가질 수 있고, 여러 프로세스가 존재하는 경우 나머지는 CPU가 free 상태가 될 때까지 기다려야 하기 때문에 적절한 프로세스 스케줄링이 필요하다. + +프로세스를 스케줄링하기 위한 큐(Queue)로는 Job Queue, Ready Queue, Device Queue가 있다. 프로세스가 이 큐들을 이용하여 수행된다. + +Job Queue는 하드디스크에 있는 프로그램이 실행되기 위해 메인 메모리의 할당 순서를 기다리는 큐이다. Ready Queue는 현재 메모리 내에 있으면서 CPU를 잡아서 실행되기를 기다리는 프로세스의 집합이다. 그리고 Device Queue는 I/O 장치를 기다리는 프로세스의 집합이다. + +image + +스케줄러의 종류로는 다음과 같은 3가지가 있다. + +### 1. Long-Term Scheduler (Job Scheduler) + +- 시작 프로세스 중 어떤 프로세스를 Ready Queue로 보낼지를 결정하며, 프로세스에 메모리 및 각종 자원을 할당한다. 자주 발생하지는 않는다. 또 Degree of Multiprogramming(메모리에 몇 개의 프로세스가 존재하는지)를 제어한다. + +- Time-sharing 시스템에서는 보통 Long-Term Scheduler가 존재하지 않고 무조건 Ready Queue로 올라가는 방식이다. + +### 2. Short-Term Scheduler (단기 스케줄러 or CPU Scheduler) + +- 어떤 프로세스를 다음에 실행시킬지를 선택하며, 프로세스에 CPU를 할당한다. 자주 발생하는 작업이므로 충분히 빨라야 한다. + +### 3. Medium-Term Scheduler (중기 스케줄러 or Swapper) + +- 프로세스를 수행하다가 메모리에서 잠시 제거했다가, 시간이 지난 후 다시 메모리에 넣고 수행을 이어나가는 것이 더 이득이 될 수 있는 경우가 존재할 수 있다. +- 이를 위해 프로세스를 통째로 메모리에서 디스크로 쫓아내서 여유 공간을 마련하는 작업을 Swapping이라고 한다. 즉, 프로세스에게서 메모리를 빼앗는 것이다. 이 작업을 Medium-Term Scheduler가 수행한다. + +- 메모리를 빼앗긴 프로세스는 위에서 봤던 중단된(sudpended) 상태의 프로세스가 된다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\355\224\204\353\241\234\354\204\270\354\212\244\354\235\230\342\200\205\352\264\200\353\246\254.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\355\224\204\353\241\234\354\204\270\354\212\244\354\235\230\342\200\205\352\264\200\353\246\254.md" new file mode 100644 index 00000000..97c71a24 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/process/\355\224\204\353\241\234\354\204\270\354\212\244\354\235\230\342\200\205\352\264\200\353\246\254.md" @@ -0,0 +1,68 @@ +--- +title: '프로세스의 관리' +lastUpdated: '2024-03-02' +--- + +대부분의 시스템에서 프로세스는 동시에 실행될 수 있고, 이들은 동적으로 생성되거나 삭제될 수 있다. + +## Process Creation (프로세스 생성) + +- 프로세스는 트리(계층) 구조로 되어있다. +- 프로세스는 PCB에 저장된 pid(process identifier)값을 통해서 식별되고 관리된다. +- 또 프로세스는 자원이 필요한데, 자원은 운영체제로 부터 받거나 부모와 공유한다. + +- 프로세스 생성 세부 작업 순서 + 1. 새로운 프로세스에 PID 할당 + 2. 주소 공간과 PCB 공간 할당 + 3. PCB 초기화(프로세스 상태, 카운터 등 초기화, 자원 요청 등) + 4. 링크 걸기 (해당 큐에 삽입) + +### fork() + +프로세스의 생성은 `fork()` 시스템 콜을 이용한다. `fork()`를 이용하면 부모를 그대로 복사하여 현재 프로세스와 pid만 다른 프로세스를 생성한다. 즉, 같은 동작을 하는 프로세스가 두 개 존재하게 되며, 새로운 프로세스는 원래의 프로세스 주소 공간의 복사본을 포함한다. + +fork는 무거운 시스템 콜이다. 부모 프로세스의 전체 복사본을 생성하고, 이를 자식 프로세스로 실행시키기 때문이다. 따라서 이 수행에 필요한 노력을 줄이기 위해 `copy-on-write` 기술을 사용한다. + +`copy-on-write`는 copy 작업을 부모나 자식이 쓰기 작업을 하기 전까지 지연시킴으로써 효율성을 높여주는 기술이다. 부모 프로세스가 fork 하여 생긴 자식 프로세스의 page를 공유하다가 자식이 page에 쓰기 작업을 할 때 해당 page만을 copy 하는 방식이다. 이를 통해 전체가 복사되는 현상을 방지한다. + +`copy-on-write`는 프로세스들이 일반적으로 메모리에서 page의 일부분만을 사용한다는 사실을 이용한 기술이다. + +반면 단점도 있다. 많은 양의 RAM이 사용되며, copy를 하는 시간이 오래 걸린다. 그리고 프로세스를 copy 하자마자 exec을 통해 새로운 프로그램을 로드하는 경우 단점이 극대화된다. + +이러한 단점을 커널이 프로세스의 전체 주소 공간이 아니라 **오직 page table만 복사함**으로써 극복한다. + +### exec() + +`fork()` 다음에 이어지는 `exec()` 시스템 콜은 새로운 프로그램을 메모리에 올려 실행시킨다. `exec()` 시스템 콜은 어떤 프로그램에 새 정보를 완전히 덮어씌운다. + +```c +int main() { + int pid = fork(); + if (pid == 0) { // child + exec(); + } else { // parent + wait(); + } + /* code */ +} +``` + +> 이처럼 fork 함수의 반환 값을 이용해서 만약 child인 경우 새로운 프로그램을 실행시키고 싶다면 exec() 시스템 콜을 사용하면 된다. exec() 시스템 콜을 사용하는 경우, 그 뒤의 명령어는 수행하지 않는다. 즉, /* CODE */ 에 해당하는 부분은 수행하지 않는다. + +### wait + +`wait()` 시스템 콜은, 만약 프로세스 A가 `wait()`를 호출하면 커널은 자식이 종료될 때까지 A를 Sleep(blocked)시킨다. 그리고 자식 프로세스가 종료되면 커널이 A를 깨워 Ready 상태로 만든다. + +따라서 만약 자식이 먼저 수행되기를 원하면 위에 있는 코드처럼 `wait()`를 else문에 넣어주면 된다. + +### Process Termination + +1. 자발적 종료 + - 현재 프로세스가 마지막 statement를 수행하면, 운영체제에 `exit()` 명령어를 통해서 이를 알려준다. + - 그러면 부모 프로세스가 현재 프로세스의 실행을 종료시키고, wait를 통해 자식으로부터 상태 값(status value)을 수집한다. 프로세스의 각종 자원들은 운영체제에 반납된다. + +2. 비자발적 종료 (kill) + - 부모 프로세스가 자식 프로세스의 수행을 종료시킬 수도 있다. 자식이 할당된 자원의 한계치를 넘어가거나, 자식에세 할당된 작업이 더 이상 필요하지 않거나, 부모 프로세스가 종료되는 경우이다. + - 프로세스의 비정상적인 종료로 인해 Zombie process나 Orphan process 같은 유형의 프로세스가 존재할 수 있다. + - Zombie process는 실행이 끝났지만 프로세스 테이블에 엔트리(Entry)를 가지고 있는, 정보가 메모리에 남아있는 프로세스를 말한다. 프로세스가 종료되었지만 버그나 에러로 인해 해당 프로세스의 부모가 아직 wait를 통해서 상태를 수집하지 못한 경우이다. 모든 프로세스는 아주 잠깐 좀비의 상태로 존재할 수 있다. + - Orphan process는 부모가 wait를 호출하지 않고 종료되었을 때의 자식 프로세스를 의미한다. 즉, 부모는 종료되었지만 자식은 수행 중인 경우이다. 이런 경우엔 init process가 orphan process들의 새로운 부모로 할당되고, init process가 주기적으로 wait를 호출해서 orphan process들의 exit status를 수집한다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/window/Active\342\200\205Directory\342\200\205Domain\342\200\205Services.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/window/Active\342\200\205Directory\342\200\205Domain\342\200\205Services.md" new file mode 100644 index 00000000..ed4baa80 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/window/Active\342\200\205Directory\342\200\205Domain\342\200\205Services.md" @@ -0,0 +1,39 @@ +--- +title: 'Active Directory Domain Services' +lastUpdated: '2024-03-02' +--- + +- A directory is a **hierarchical structure** that stores information about objects on the network. + +- A directory service, such as Active Directory Domain Services (AD DS), provides the methods for storing directory data and making this data available to network users and administrators. + +- For example, AD DS stores information about user accounts, such as names, passwords, phone numbers, and so on, and enables other authorized users on the same network to access this information. + +- Active Directory stores information about objects on the network and makes this information easy for administrators and users to find and use. Active Directory uses a structured data store as the basis for a logical, hierarchical organization of directory information. + +### Characteristics + +- The AD DS is a true directory service that uses a hierarchical 'X.500' infrastructure. +- The AD DS uses Domain Name System (DNS) to locate resources, such as domain controllers. +- You can query and manage ADDS using Lightweight Directory Access Protocol (LDAP) calls. +- The AD DS primarily uses the Kerberos protocol for authentication. +- The AD DS uses OU and GPO for management. +- The AD DS includes computer objects that represent computers joining the Active Directory domain. +- The AD DS uses interdomain trusts for delegated management. + +### A key element + +![image](https://github.com/rlaisqls/rlaisqls/assets/81006587/cab61ca5-4d34-4c48-8e0a-91d3abe131de) + +- **A set of rules, the schema**, that defines the classes of objects and attributes contained in the directory, the constraints and limits on instances of these objects, and the format of their names. For more information about the schema, see Schema. + +- **A global catalog** that contains information about every object in the directory. This allows users and administrators to find directory information regardless of which domain in the directory actually contains the data. For more information about the global catalog, see Global catalog. + +- **A query and index mechanism**, so that objects and their properties can be published and found by network users or applications. For more information about querying the directory, see Searching in Active Directory Domain Services. + +- **A replication service** that distributes directory data across a network. All domain controllers in a domain participate in replication and contain a complete copy of all directory information for their domain. Any change to directory data is replicated to all domain controllers in the domain. For more information about Active Directory replication, see Active Directory Replication Concepts. + +--- +reference +- https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc736627(v=ws.10) +- https://learn.microsoft.com/ko-kr/training/modules/understand-azure-active-directory/3-compare-azure-active-directory-domain-services \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\224\224\354\212\244\355\201\254\342\200\205\354\213\234\354\212\244\355\205\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\224\224\354\212\244\355\201\254\342\200\205\354\213\234\354\212\244\355\205\234.md" new file mode 100644 index 00000000..a34ce00d --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\224\224\354\212\244\355\201\254\342\200\205\354\213\234\354\212\244\355\205\234.md" @@ -0,0 +1,98 @@ +--- +title: '디스크 시스템' +lastUpdated: '2024-03-02' +--- + +- 프로세서: 논리적 상호작용 +- 드라이버: 구동모터, 엑세스암, 입출력 헤드로 구성 +- 제어기: 드라이버의 인터페이스 + - 명령받아 작동 + - 드라이버 번호, 표면 번호, 트랙 번호 사용 + +### 디스크 구조 + +image + +- 트랙: 동심원 +- 실린더: 헤드 안움직이고 접근 가능한 범위, 동일 위치 트랙 집합 +- 섹터: 부채꼴 모양으로 나눈 조각, 블록의 집합 + +### 디스크 주소 + +- 물리주소: 제조사마다 다름 + - Cylinder, Surface Sector +- 논리주소: Block 번호 +- Disk Driver가 변환해줌 + +### 디스크 접근 시간 + +- 탐색시간: 현재 to 목적트랙 +- 회전지연시간: 트랙 to 목적섹터 +- 전송시간: 데이터 읽고 전송 +- 데이터 전송시간: 탐색시간 + 회전지연시간 + 전송시간 + +### 디스크 스케줄링 평가기준 + +- 처리량: 시간당 처리수 +- 탐색시간: 헤드 이동시간 +- 평균 반응시간: 요청 후 서비스까지 +- 반응시간 변화: 적정 시간안에 서비스하도록 함. 무기한 연기 방지 + +### 스케줄링 방법 + +**시간 최적화** + +- **FCFS** + - 선입선출 + - 구현쉽고 공정 + + image + +- **SSTF** + - 현재 헤드에서 가까운 요구 우선처리 + - 공정 X 무기한 연기(기아) 가능 + - 대화형에 부적절 + + image + +- **SCAN** + - 헤드가 왔다갔다함(엘리베이터) + - 가까우면 굿, 멀면 요청시간 증가 + + image + +- **C-SCAN** + - 헤드가 계속 도는데, SCAN이랑 다르게 역방향으로 꺾지 않고 쭉 감 + - 바깔, 안쪽 차별 X + - 반응시간 균형 + - 동일 트랙 요청이 연속적으로 발생하면 무기한 연기 가능성 + + image + +- **Look** + - SCAN을 변형한 알고리즘 + - 현재 방향에 요청이 없을 때 반대 방향으로 감 + +- **C-Look** + - C-SCAN을 변형한 알고리즘 + + image + + +**회전지연 회적화** + +- **SLTF** + - latency(도는 거리) 우선 + - 헤더에 가까운 요청 먼저 처리 +- **SPTF** + - latency + seek time(헤더 앞뒤로 거리) 고려 + - 더한 값이 작은 요청 먼저 처리 + +### 디스크의 상태 + +- 활동(Active): 헤드가 데이터를 읽거나 쓰고 있는 상태 +- 공회전(Idle): 디스크가 회전중 but 읽거나 쓰지는 않는 상태 +- 준비(Standby): 디스크 회전 X, 인터페이스 활성화 +- 휴면(Sleep): 디스크 회전 X, 인터페이스 비활성화 + +- 비활성화(준비+휴면) 상태에서 활성화(활동+공회전) 상태로 가는데는 시간과 전력이 많이 소요되기 때문에, 자주 전환하는 것은 좋지 않다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\260\230\353\217\204\354\262\264\342\200\2058\353\214\200\342\200\205\352\263\265\354\240\225.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\260\230\353\217\204\354\262\264\342\200\2058\353\214\200\342\200\205\352\263\265\354\240\225.md" new file mode 100644 index 00000000..90e7ecd5 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\260\230\353\217\204\354\262\264\342\200\2058\353\214\200\342\200\205\352\263\265\354\240\225.md" @@ -0,0 +1,61 @@ +--- +title: '반도체 8대 공정' +lastUpdated: '2024-03-02' +--- + +image + +### 1. 웨이퍼 제조 +- 반도체 집적회로의 핵심 재료인 웨이퍼가 만들어지는 공정이다. + +- 모래를 고온으로 녹인 후 정제하여 정제된 실리콘을 추출한다. +- 진공의 도가니에 실리콘을 넣고 단결정 실리콘인 시드를 돌려가며 꺼내면 잉곳이라고 불리는 실리콘 기둥이 만들어진다. + + image + + +- 다이아몬드 톱을 이용하여 잉곳을 원통 모양으로 다듬은 후, 얇은 슬라이스로 절단하고 연마한다. +- 아래는 완성된 웨이퍼의 모습이다. + + image + + +### 2. 산화 공정 +- 이후 공정을 위해 웨이퍼 위에 산화막을 만들어주는 단계이다. +- 800~1200도의 고온에서 산소나 수증기를 표면과 반응시켜 만든다. + +### 3. 포토 공정 +- 빛을 이용하여 회로 패턴이 담긴 마스크를 웨이퍼 위에 그리는 공정이다. +- 감광액을 씌우고 빛을 통과 시켜 회로 패턴을 그린다. +- 이후 웨이퍼에 현상액을 뿌려가며, 노광된 영역과 노광되지 않은 영역을 선택적으로 제거해 회로 패턴을 만든다. + + image + + 감광층을 씌우고 마스크를 이용해 필요한 부분의 감광층만 남기는 과정 + +### 4. 에칭 공정 +- 불필요한 회로를 벗겨 내는 과정이다. +- 식각 공정은 건식 식각, 습식 식각으로 나뉜다. + - 건식식각: 플라즈마 상태를 이용해 불필요한 부분을 선택적으로 없앰 + - 습식 식각: 용액이과 접촉하게하여 감광액이 없는 부분만 깎아냄 + +### 5. 박막 공정 +- 게이트, N형 반도체를 만들기 위해 층을 쌓고 이온을 주입하는 과정이다. + +### 6. 배선 공정 +- 금속 배선 공정은 반도체의 회로패턴을 따라 금속선을 이어 주는 작업이다. +- 소자들을 동작 시키면서 각각의 신호가 섞이지 않고 잘 전달되도록 선을 연결한다. +- 금, 백금, 은, 알루미늄, 텅스텐 등의 재료를 사용한다. + + image + +### 7. 테스트 공정 (EDS 공정) +- 전기적 특성 검사를 통해 칩이 잘 제작되었는지 테스트하는 과정이다. + +### 8. 패키징 +- 외부 환경으로부터 안전하게 보호될 수 있도록 기판에 패키징하는 작업이다. + +--- +참고 +- https://www.youtube.com/watch?v=bAXxxmXCk1o +- https://blog.naver.com/lionixglobal/221150577455 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\263\264\354\225\210/\354\227\221\354\204\270\354\212\244\342\200\205\354\240\234\354\226\264.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\263\264\354\225\210/\354\227\221\354\204\270\354\212\244\342\200\205\354\240\234\354\226\264.md" new file mode 100644 index 00000000..eb69f2f9 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\353\263\264\354\225\210/\354\227\221\354\204\270\354\212\244\342\200\205\354\240\234\354\226\264.md" @@ -0,0 +1,79 @@ +--- +title: '엑세스 제어' +lastUpdated: '2024-03-02' +--- + +- 적절한 권한을 가진 사람만 특정 시스템이나 정보에 접근할 수 있도록 통제하는 것 +- 컴퓨팅, 통신, 정보자원 등에 대하여 허가되지 않은 접근을 방어할 수 있다. +- 시스템의 보안 수준을 갖추는 기본 수단 +- 사용자가 자원에 대해 얼마만큼의 권한을 가질지 지정해야 한다. +- 사용자 엑세스 제어와 데이터 엑세스 제어로 나뉜다. + +## **사용자 접근 제어** + +- 사용자가 시스템에 접근하거나 정보를 요청할 때 그 사용자가 누구인지 확인하고 +그 사용자가 해당 정보에 접근할 권한이 있는지 확인하는 과정 +- 식별할 수 없는 사용자가 침투하는 것을 방지함 +- 주로 토큰을 사용해 사용자 권한 구별 + - 웹 서버로 치면 JWT로 유저 식별하고 인가하는 동작이 여기에 속함 +- **Window의 예시** + + image + + + - 사용자가 로그인하면 시스템이 해당 사용자에 대한 액세스 토큰을 생성한다 + - 액세스 토큰에 있는 정보: 보안 식별자, 권한 등 사용자에게 부여된 액세스 수준 + - 관리자가 로그인하면 기본 액세스 토큰과 관리자 액세스 토큰을 생성한다. + - 관리자 권한이 필요 없는 경우엔 기본 토큰을 쓰고, 그렇지 않은 경우 관리자 토큰을 쓰는 식으로 작동 + + https://learn.microsoft.com/en-us/windows/security/application-security/application-control/user-account-control/how-it-works + + +## 데이터 엑세스 제어 + +- 사용자 접근 제어와 다르게, 데이터 입장에서 설정하는 엑세스 제어 +- 효율성이나 접근성을 훼손하지 않아야 함 +- 데이터 엑세스 제어 모델에서는 엑세스 행렬로 권한을 저장한다. + +image + + +- 엑세스 행렬을 표현하는 데는 접근 제어 리스트(ACL), 권한 리스트, 락/키 방법 등 다양한 방법이 있음 + +### 1. 접근 제어 리스트(ACL) + +- 객체에 대한 사용자의 접근 권한을 나열 +- 어떤 사용자가 어떤 객체에 어떤 작업을 수행할 수 있는지를 정의한다. +- 파일 개체에 ACL을 [앨리스:읽기,쓰기; 밥:읽기] 이런 식으로 설정한 경우 + - 앨리스는 파일을 읽고 쓸 수 있고 밥은 파일을 읽을 수 있다. +- 사용자가 아닌 그룹 단위로 설정할 수도 있다. + + image + + +### 2. 권한 리스트 + +- 접근 제어 리스트의 각 행을 해당 영역과 결합한 것 +- 각 객체에 권한과 액세스 가능 여부를 나타내는 태그가 부여된다. + - 파일의 주소 공간을 권한 리스트 저장 공간과 파일 데이터 저장 공간으로 나눠 저장한다. + - 권한 리스트 공간은 운영체제만 접근 가능 + +image + +- 탐색 시간이 불필요하다. + +### 3. 락/키 방법 + +- 각 객체는 락(lock)이라 불리는 유일하고 독특한 비트 패턴의 리스트를 가진다. +- 각 영역은 키(key)라고 불리는 비트 패턴의 리스트를 가진다. +- 사용자는 객체의 “락”과 일치하는 키를 가지고 있을 때만 해당 객체에 액세스할 수 있음 + - 키와 락에는 운영체제만 접근 가능 + + image + +- 액세스제어 리스트와 권한 리스트보다 효율적임 +- 많은 시스템에서 사용하는 방법 + +--- +참고 +- https://m.hanbit.co.kr/store/books/book_view.html?p_code=B3239422381 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205\354\234\240\355\230\225.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205\354\234\240\355\230\225.md" new file mode 100644 index 00000000..7815d20f --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205\354\234\240\355\230\225.md" @@ -0,0 +1,45 @@ +--- +title: '운영체제 유형' +lastUpdated: '2024-03-02' +--- + +## 다중 프로그래밍 시스템 (Multi Programming System) + +- 장점 + - CPU 사용률이 증가하여 많은 사용자의 프로그램이 거의 동시에 할당받는 듯한 느낌을 준다. + - 즉, 프로세서 사용을 최대화한다. +- 단점 + - 메모리 관리나 프로세스 정보 보관이 복잡하다 + - 여러 작업이 준비를 갖추고 있을 떄 다음 작업을 선택할 알고리즘이 정의되어있어야 한다. -> 스케줄링 + +## 시분할 시스템 (TSS, Time Sharing System) + +- 다중 프로그래밍을 논리적으로 확장, CPU가 다중 작업을 교대로 수행해서 다수의 사용자가 자원을 공유할 수 있도록 한다. +- 각 프로그램에 일정한 CPU 사용시간(time slice) 또는 규정 시간량 (quantum) 할당을 주고 컴퓨터와 대화하는 형식으로 실행한다. +- 응답시간을 최소화할 수 있다. + +image + +|장/단점|내용| +|-|-| +|장점|- 빠른 응답 제공
- 소프트웨어의 중복 회피 가능
- CPU 유휴시간 감소| +|단점|- 신뢰성 문제
- 보안 의문 및 사용자 프로그램과 데이터의 무결성
- 데이터 통신의 문제| + +## 다중 처리 시스템 (Multi Processing System) + +- 단일 컴퓨터 시스템 내에서 둘 이상의 CPU를 사용하여 여러 프로세스를 동시에 실행하는 것이다. +- CPU 하나가 고장나도 다른 CPU를 사용하여 작업을 계속한다. - 신뢰성 높음 +- CPU 간의 연결, 상호작용, 역할 분담 등을 고려해야한다. + +## 실시간 처리 시스템 (Real time Processing Sytem) + +- 데이터 처리 시스템으로 정의 +- 항상 온라인 상태여야 한다. +- 고정 시간 제약을 잘 정의하지 않으면 시스템 에러가 생긴다. + +- 특성에 따라 경성과 연성으로 나뉜다. +- 경성 + - 작업의 실행 시작이나 완료에 대한 시간 제약 조건을 지키지 못할 때 시스템에 치명적인 영향을 주는 시스템. + - 시간의 정확성과 컴퓨팅 예측성이 필요하다. +- 연성 + - 시간 제약 조건은 있으나 이를 지키지 못해도 치명적인 영향을 미치지 않는 시스템. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\354\234\240\354\240\200,\342\200\205\354\273\244\353\204\220\353\240\210\353\262\250\342\200\205\354\212\244\353\240\210\353\223\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\354\234\240\354\240\200,\342\200\205\354\273\244\353\204\220\353\240\210\353\262\250\342\200\205\354\212\244\353\240\210\353\223\234.md" new file mode 100644 index 00000000..66df8e30 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\354\234\240\354\240\200,\342\200\205\354\273\244\353\204\220\353\240\210\353\262\250\342\200\205\354\212\244\353\240\210\353\223\234.md" @@ -0,0 +1,42 @@ +--- +title: '유저, 커널레벨 스레드' +lastUpdated: '2024-03-02' +--- + +## 유저레벨 스레드(Green Thread) + +유저 레벨 스레드는 VM이나 Library 등에서 관리되는 스레드이다. 프로그래밍언어를 사용해서 Thread 라이브러리를 당겨와 스레드를 만들면 유저레벨 스레드가 생성된다. + +커널은 사용자 레벨 스레드의 정보를 가지고 있지 않고, 사용자 레벨 스레드가 실행되기 위해서는 커널 스레드의 도움을 받아야 한다. + +## 커널레벨 스레드(Native Thread) + +커널 레벨에서 생성되는 스레드로, 실제 스레드 동작을 관리하는 주체이다. 커널이 직접 스케줄링하고, 스레드에 관한 정보를 커널이 직접 가지고 있다. + +커널 레벨 스레드를 여러개 만들면 동시에 여러 CPU코어에 CPU burst를 요청할 수 있다. 만약 유저레벨 스레드가 커널 레벨 스레드와 1:1로 연결되어있고, CPU코어가 8개면 한 시점에 8개의 코어가 내 프로그래밍 언어의 스레드를 위해 일할 수 있는 것이다. + +유저 모드에서 커널 모드로의 전환이 빈번하게 이뤄지면 성능 저하가 발생한다. + +## Multithreading Model 3가지 + +유저 레벨 스레드와 커널 레벨 스레드는 서로 연결되어 동작하는데, 그 연결 구조는 아래와 같은 세가지 유형으로 분류할 수 있다. + +image + +### 1. Many-to-one + +여러개의 유저 스레드가 하나의 커널 스레드에 매핑되는 방식이다. + +유저 레벨 라이브러리로 관리되므로 속도가 빠르고 이식성이 좋지만 스레드 하나가 중지되면 나머지도 영향을 끼칠 수 있다. + +### 2. One-to-one + +하나의 유저 스레드를 하나의 커널 스레드와 매핑하는 방식이다. + +한 스레드가 중지되어도 다른 스레드에 영향이 없으므로 동시성이 좋지만 자원 효율이 안좋다. + +### 3. Many-to-many + +여러개의 유저 스레드를 여러개의 커널 스레드로 매핑하는 방식이다. + +1 & 2의 장점들을 모두 가지지만 구현이 어렵다. \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\355\214\214\354\235\274\342\200\205\354\213\234\354\212\244\355\205\234.md" "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\355\214\214\354\235\274\342\200\205\354\213\234\354\212\244\355\205\234.md" new file mode 100644 index 00000000..51faadd5 --- /dev/null +++ "b/src/content/docs/TIL/\354\232\264\354\230\201\354\262\264\354\240\234\342\200\205Operating\342\200\205System/\355\214\214\354\235\274\342\200\205\354\213\234\354\212\244\355\205\234.md" @@ -0,0 +1,88 @@ +--- +title: '파일 시스템' +lastUpdated: '2024-03-02' +--- + +- 파일: 논리적 저장 단위 +- 파일을 정리하고 메모리에 매핑 +- 디렉터리로 계층적 연결 (폴더와 비슷) + - 디렉토리란?/ 디스크에 존재하는 파일에 대한 정보를 가진 테이블 + - DOS, Linux, Unix 등 텍스트 기반은 디렉토리 + - GUI에선 폴더, 폴더에는 네트워크 설정 같은 특수 폴더가 포함됨 +- 사용자가 생성, 수정 삭제 가능 + +### 파일 구성 + +- 비트 < 바이트 < 워드 < 필드 < 레코드 < 파일 +- 레코드 단위 작업 + - Read, Write, Update, Insert, Delete, Search +- 파일 단위 작업 + - Open, Close, Copy, Destroy, Rename, List + +### File Descripter + +- 열린 파일의 목록을 관리하는 테이블의 인덱스 +- 0 또는 양수 값을 가짐 +- 정보 + - 파일이름 및 크기 + - 보조기억장치 위치 + - 파일 구조(순차, 색인 순차, 색인 파일) + - 엑세스 제어 정보 + - 엑세스 횟수 + - 파일 유형 + - 생성, 수정, 제거 날짜 + +### 파일 시스템 특징 + +- 무결성 보장 +- 백업과 복구 +- 암호화 + +### 디렉토리 + +계층 구조에 따라 여러 분류가 있음 + +- **1단계(단일)** + - 루트에 모든 파일이 속함 + - 관리 어렵고 중복이름 X +- **2단계** + - 루트 및에 유저별 폴더 + - 파일이름은 폴더명 안에서만 유니크하면 됨 +- **트리 구조** + - 폴더 안의 폴더 + - 절대경로: 루트부터 + - 상대경로: 현재 dir 부터 +- **비순환 그래프** + - 하위파일이나 dir 공유(바로가기)를 지원함 + - 다른 경로로 2번 이상 찾아 성능 저하 가능성 있음 + - 공유된 파일을 삭제했는데 포인터가 남아있으면 허상포인터 문제 발생 +- **일반 그래프** + - 트리 구조에 링크 추가 + - 순환 가능 + - 파일이나 디렉터리 공유 가능 + - 접근 용이 + +### 파일 할당 방식 + +- 디스크에 여러 파일이 저장될 때 배치하는 방법 +- **연속할당** + - 배열처럼 인접하여 할당 + - 물리적으로 연속이라 엑세스 시간 감소 + - 새 파일공간의 크기를 미리 결정해야 함 + - 공간 확볼르 위해 재배치 및 압축 필요 +- **연결할당** + - 파일을 블록 크기로 나누고 디스크 내에서 포인터로 연결 + - 외부단편화X, 재배치 및 압축 불필요 + - 포인터 잃으면 파일 날아감 + - 크기 고정 불필요 +- **색인 할당** + - 색인(index)로 각 블록의 주조 지정 + - 모든 블록 크기 같음 + - 작은 파일에 대한 색인할당은 오버헤드 좀 있음 + +### 디스크 자유 공간 관리 + +- **비트벡터** + - 각 블록을 비트로 표현해서 자유블록 여부를 배열로 저장 +- **연결리스트** + - 모든 자유블록을 연결리스트로 연결 \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/CNN.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/CNN.md" new file mode 100644 index 00000000..3edf0ccc --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/CNN.md" @@ -0,0 +1,73 @@ +--- +title: 'CNN' +lastUpdated: '2024-03-02' +--- + +`CNN`이 나오기 이전, 이미지 인식은 2차원으로 된 이미지(채널까지 포함해서 3차원)를 1차원배열로 바꾼 뒤 `FC(Fully Connected)`신경망으로 학습시키는 방법이었다. + +image + + +> 단순 FC 방식은 위와 같이 이미지의 형상을 고려하지 않고, raw data를 직접 처리하기 때문에 많은 양의 학습데이터가 필요하고 학습시간이 길어진다. 또한 이미지가 회전하거나 움직이면 새로운 입력으로 데이터를 처리해줘야 한다. 이미지의 특성을 이해하지 못하고 단순 1D 데이터로 보고 학습을하는것이 특징이다. + +이러한 방법은 이미지 데이터를 평면화 시키는 과정에서 공간정보가 손실될 수밖에 없다. 즉, 신경망이 특징을 추출하고 학습하는데 있어 비효율적이고 정확도를 높이는데 한계가 있다. + +이런 단점을 보완하여 **이미지의 공간정보를 유지한채 학습**을 하게하는 모델이 `CNN`이다. + +# CNN의 동작 + +`CNN`의 가장 핵심적인 개념은 이미지의 공간정보를 유지한채 학습을 한다는 것이다. + +용어를 설명하면서 CNN의 구성요소들이 어떻게 동작하는지 알아보자. + +## 컨볼루션 층 + +**특징을 추출하는 층**으로 일정 영역의 값들에 대해 가중치를 적용하여 하나의 값을 만드는 연산이 실행된다. 이때 이 가중치 행렬을 **컨볼루션 필터**라 부른다. + +또한 가중치 필터 연산 후에 활성화 함수를 적용하여 이후 연산에 넘겨진다. 이때 CNN의 경우 대부분 ReLU 활성화 함수를 사용한다. + +아래 이미지에서는 4x4 행렬의 이미지에 대해 3x3 필터를 1 스트라이드(연산마다 필터가 움직이는 칸 수)로 특징 추출하고 있다. 만약 스트라이드의 간격을 조절하면, 특징 맵의 차원을 출일 수도 있다. + +각 픽셀을 모두 분리해서 보는게 아니라, 필터를 통해 주변 픽셀과의 관계나 특징을 담은 값으로 학습하기 때문에 보다 정확한 결과를 낼 수 있다. 이렇게 나온 결과 행렬을 **Convolved Feature Map, 특징지도**라고 한다. + +image + + +### 패딩 + +이때, 합성곱 계층을 거치면서 이미지의 크기는 점점 작아지게 되어 이미지의 가장자리에 위치한 픽셀들의 정보는 점점 사라지게 된다. + +이러한 문제점을 해결하기 위해 이용되는것이 패딩 (Padding)이다. 패딩은 이미지의 가장자리에 특정값으로 설정된 픽셀들을 추가함으로써 입력 이미지와 출력이미지의 크기를 같거나 비슷하게 만드는 역할을 수행한다. 아래 그림은 0 값을 갖는 픽셀을 추가하는 zero-padding을 적용한 예이며, CNN에서는 주로 zero-padding이 이용된다. + +image + + +## 풀링 층 + +일정 크기의 블록을 통합하여 하나의 대표값으로 대체하는 연산으로 컨볼루션 층에서 출력된 특징 지도를 압축하여 특정 데이터를 강조하는 역할을 한다. + +image + +특징지도를 Max Pooling, Average Pooling 할 경우 결과 행렬 + +풀링 방식에는 + +- 블록 내의 원소들 중 최대값을 대표값으로 선택하는 Max Pooling +- 블록 내의 원소들의 평균값을 대표값으로 선택하는 Average Pooling +- 블록 내 원소의 크기를 선택 확률로 변환 후 확률에 따라 선택하는 Stochastic Pooling + +등 다양한 방법이 있지만 일반적으로 CNN에서는 Max Pooling의 효율이 가장 좋아 많이 사용된다. (뉴런이 가장 큰 신호에 반응하는것과 유사, 노이즈가 감소하고 속도가 빨라진다*.*) + +풀링 작업을 수행하는 풀링 계층은 아래와 같은 특징이 있다. + +- 풀링은 커널안의 값에서 최대, 평균, 최소값만을 얻어내는 과정이어서 학습대상 파라미터가 없다. +- 풀링 계층을 통과하면 행렬의 크기가 감소하여 다음번 계층에서의 연산 회수나 학습해야할 파라미터 수를 줄일 수 있다. +- 풀링 계층을 통과하더라도 채널 수 (특징 맵 수)에는 변경이 없다. + +# 전체 구조 + +image + +image + +flatten : 벡터로 펼쳐주는 함수 diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/DropOut.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/DropOut.md" new file mode 100644 index 00000000..6e00d51d --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/DropOut.md" @@ -0,0 +1,42 @@ +--- +title: 'DropOut' +lastUpdated: '2024-03-02' +--- + + image + +- Drop-out은 서로 연결된 연결망(layer)에서 0부터 1 사이의 확률로 뉴런을 제거(drop)하는 기법이다. +- 예를 들어, 위의 그림 1 과 같이 drop-out rate가 0.5라고 가정하자. + - Drop-out 이전에 4개의 뉴런끼리 모두 연결되어 있는 전결합 계층(Fully Connected Layer)에서 4개의 뉴런 각각은 0.5의 확률로 제거될지 말지 랜덤하게 결정된다. + - 위의 예시에서는 2개가 제거된 것을 알 수 있다. +- 즉, 꺼지는 뉴런의 종류와 개수는 오로지 랜덤하게 drop-out rate에 따라 결정된다. +- Drop-out Rate는 하이퍼파라미터이며 일반적으로 0.5로 설정한다. + +## 사용 목적 + +- Drop-out은 어떤 특정한 설명변수 Feature만을 과도하게 집중하여 학습함으로써 발생할 수 있는 과대적합(Overfitting)을 방지하기 위해 사용된다. + +- 위의 그림에서 노란색 박스 안에 있는 Drop-Out이 적용된 전결합계층은 하나의 Realization 또는 Instance라고 부른다. +- 각 realization이 일부 뉴런만으로도 좋은 출력값을 제공할 수 있도록 최적화되었다고 가정했을 때, 모든 realization 각각의 출력값에 평균을 취하면(=ensemble) 그림의 오른쪽과 같이 모든 뉴런을 사용한 전결합계층의 출력값을 얻을 수 있다. +- 특히 이 출력값은 Drop-out을 적용하기 전과 비교했을 때, 더욱 편향되지 않은 출력값을 얻는 데 효과적이다. + +- Drop-out을 적용하지 않고 모델을 학습하면 해당 Feature에 가중치가 가장 크게 설정되어 나머지 Feature에 대해서는 제대로 학습되지 않을 것이다. +- 반면 Drop-out을 적용하여 상관관계가 강한 Feature를 제외하고 학습해도 좋은 출력값을 얻을 수 있도록 최적화되었다면, 해당 Feature에만 출력값이 좌지우지되는 과대적합(overfitting)을 방지하고 나머지 Feature까지 종합적으로 확인할 수 있게 된다. +- 이것이 모델의 일반화(Generalization) 관점에서 Drop-out을 사용하는 이유이다. + +## Mini-batch 학습 시 Drop-out + + image + +- 위의 그림과 같이 전결합 계층에서 Mini-batch 학습 시 Drop-out을 적용하면 각 batch별로 적용되는 것을 알 수 있다. +- Drop-out Rate를 0.5로 설정했기 때문에 뉴런별로 0.5의 확률로 drop 될지 여부가 결정된다. +- 첫 번째 batch에서는 위에서 2, 3번 뉴런이 꺼졌고, 2번째 batch에서는 3번 뉴런 1개만 꺼졌고, 3번째 batch에서는 1, 2, 3번 뉴런 3개가 꺼질 수 있다. + +## Test 시 Drop-out + + image + +- Test 단계에서는 모든 뉴런에 scaling을 적용하여 동시에 사용한다. +- 여기서 a는 activation function, 알파는 drop-out rate를 의미한다. +- Drop-out rate를 활용해 scaling 하는 이유는 기존에 모델 학습 시 drop-out rate 확률로 각 뉴런이 꺼져 있었다는 점을 고려하기 위함이다. +- 즉, 같은 출력값을 비교할 때 학습 시 적은 뉴런을 활용했을 때(상대적으로 많은 뉴런이 off 된 경우)와 여러 뉴런을 활용했을 때와 같은 scale을 갖도록 보정해 주는 것이다. diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/Keras.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/Keras.md" new file mode 100644 index 00000000..f45fa3d7 --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/Keras.md" @@ -0,0 +1,183 @@ +--- +title: 'Keras' +lastUpdated: '2023-10-18' +--- +### 케라스 + +케라스는 파이썬으로 구현된 쉽고 간결한 딥러닝 라이브러리로, 내부적으로 텐서플로우Tensorflow, 티아노Theano,CNTK 등의 딥러닝 전용 엔진이 구동되지만 내부엔진을 알 필요 없이 직관적인 API로 쉽게 다층퍼셉트론 신경망 모델, 컨벌루션 신경망 모델, 순환 신경망 모델 등 다양한 구성을 할 수 있다. + +케라스의 가장 핵심적인 데이터 구조는 바로 **모델**이다. 케라스에서 제공되는 시퀀스 모델을 사용하면 원하는 레이어를 쉽게 순차적으로 정의할 수 있고, 다중 출력과 같이 좀 더 복잡한 모델을 구성하려면 케라스 함수 API를 사용하면 된다. 케라스로 딥러닝 모델을 만들 때는 다음과 같은 순서로 작성한다. + +|과정|설명| +|-|-| +|**데이터셋 전처리**|원본 데이터를 불러오거나 시뮬레이션을 통해 데이터 생성. Training set, Test set, Validation set을 생성하며 이 때 모델의 학습 및 평가를 할 수 있도록 format 변환을 한다.| +|**모델 구성**|시퀀스 모델을 생성한 뒤 필요한 레이어를 추가하여 구성하며 좀 더 복잡한 모델이 필요할 때 케라스 함수 API를 사용한다.| +|**모델 학습
설정**|학습하기 전 학습에 대한 설정을 수행하는데 Loss 함수(ex. cross-entropy) 및 최적화 방법(ex. Gradient Descent)을 정의하고 케라스에서는 compile() 함수를 사용한다.| +|**모델 학습**|구성한 모델을 Training dataset으로 학습시키는데 fit() 함수를 사용한다.| +|**학습 과정
살펴보기**|모델 학습 시 Training dataset, Validation dataset의 Loss 및 Accuracy를 측정하고 반복 횟수(epoch)에 따른 Loss 및 Accuracy 추이를 보며 학습 상황을 판단한다.| +|**모델 평가**|준비된 Test dataset으로 학습한 모델을 평가하는데 evaluate() 함수를 사용한다.| +|**모델 사용**|임의의 입력으로 모델의 출력을 얻는데 predict() 함수를 사용한다.| + +### Sequential 모델 + +케라스에서는 층을 조합하여 모델을 생성한다. 레이어 인스턴스를 생성자에게 넘겨줌으로써 `Sequential` 모델을 구성할 수 있다. + +```python +from keras.models import Sequential +from keras.layers import Dense, Activation + +model = Sequential() +model.add(Dense(32, input_dim=784, activation='sigmoid')) # 층 갯수, 입력값 수, 활성화 함수 +``` + +### 컴파일 + +모델을 학습시키기 이전에, `compile` 메소드를 통해서 학습 방식에 대한 환경설정을 해야 한다. 다음의 세 개의 인자를 입력으로 받는다. + +- **정규화기 (optimizer)** + - `rmsprp`나 `adagrad`와 같은 [optimizer](https://keras.io/optimizers)에 대한 문자열 식별자 또는 `Optimizer` 클래스의 인스턴스를 사용할 수 있다. +- **손실 함수 (loss function)** + - 모델 최적화에 사용되는 목적 함수이다. + - `categorical_crossentropy` 또는 `mse`와 같은 기존의 손실 함수의 문자열 식별자 또는 목적 함수를 사용할 수 있다. 참고: [손실](https://keras.io/losses) +- **Metrics** + - 분류 문제에 대해서는 `metrics=['accuracy']`로 설정한다. 기준은 문자열 식별자 또는 사용자 정의 기준 함수를 사용할 수 있다. + +```python +# For a multi-class classification problem +model.compile(optimizer='rmsprop', + loss='categorical_crossentropy', + metrics=['accuracy']) + +# For a binary classification problem +model.compile(optimizer='rmsprop', + loss='binary_crossentropy', + metrics=['accuracy']) + +# For a mean squared error regression problem +model.compile(optimizer='rmsprop', + loss='mse') + +# For custom metrics +import keras.backend as K + +def mean_pred(y_true, y_pred): + return K.mean(y_pred) + +model.compile(optimizer='rmsprop', + loss='binary_crossentropy', + metrics=['accuracy', mean_pred]) +``` + +### 모델 학습 + +- `fit()` 은 오차로부터 매개변수를 업데이트시키는 과정을 학습 과정을 수행하는 역할을 한다. +- **epoch:** 전체 학훈련데이터 학습을 몇 회 반복할지 결정 +- **batch_szie:** 계산하는 단위 크기 +- **validation_data:** 모델의 성능을 모니터링하기 위해서 사용. 입력과 정답 데이터로 이루어진 검증 데이터를 전달하면 1회 epoch이 끝날때마다 정달된 검증데이터에서의 손실과 평가지표를 출력함 + +```python +model.fit(x,y,epochs=3000,batch_size=1) +``` + +훈련데이터(x, y)와는 별도로 모델을 계속 학습하면서 각 에폭마다 검증데이터로 현재 모델에 대한 유효성(성능)을 검증할 수도 있다. 보통은 전체 훈련 데이터와 검증데이터를 8:2 비율로 분할하여 사용하며, 이때는 다음과 같이 코드를 구성하면 된다. + +```python +model.fit(x,y,epochs=3000,batch_size=1,validation_split=0.2) +``` + +### 모델 학습 및 구성에 대한 예시 + +```python +# For a single-input model with 2 classes (binary classification): + +model = Sequential() +model.add(Dense(32, activation='relu', input_dim=100)) +model.add(Dense(1, activation='sigmoid')) +model.compile(optimizer='rmsprop', + loss='binary_crossentropy', + metrics=['accuracy']) + +# Generate dummy dataimport numpyas np +data = np.random.random((1000, 100)) +labels = np.random.randint(2, size=(1000, 1)) + +# Train the model, iterating on the data in batches of 32 samples +model.fit(data, labels, epochs=10, batch_size=32) + +``` + +```python +# For a single-input model with 10 classes (categorical classification): + +model = Sequential() +model.add(Dense(32, activation='relu', input_dim=100)) +model.add(Dense(10, activation='softmax')) +model.compile(optimizer='rmsprop', + loss='categorical_crossentropy', + metrics=['accuracy']) + +# Generate dummy dataimport numpyas np +data = np.random.random((1000, 100)) +labels = np.random.randint(10, size=(1000, 1)) + +# Convert labels to categorical one-hot encoding +one_hot_labels = keras.utils.to_categorical(labels, num_classes=10) + +# Train the model, iterating on the data in batches of 32 samples +model.fit(data, one_hot_labels, epochs=10, batch_size=32) +``` + +### 모델 평가 + +- `evaluate()` : 테스트 데이터를 통해 학습한 모델에 대한 정확도를 평가 +- `predict()` : 임의의 입력에 대한 모델의 출력값을 확인 + +--- + +### XOR + +keras로 XOR을 분류하는 인공지능을 만들어보자 + +```python +import numpy as np +import tensorflow as tf + +x = np.array([[1,1], [1,0], [0,1], [0,0]]) +y = np.array([[0],[1],[1],[0]]) + +model = tf.keras.Sequential() +model.add(tf.keras.layers.Dense(units=2, input_dim=2, activation='sigmoid')) +model.add(tf.keras.layers.Dense(units=1, activation='sigmoid')) + +model.compile(optimizer=tf.optimizers.SGD(learning_rate=0.1), loss='mse') # mse는 값을 이분화시켜줌. 여러개면 categorical_entropy 사용 + +model.summary() + +history = model.fit(x, y, epochs=3000, batch_size=1) + +for weight in model.weights: + print(weight) + +loss = model.evaluate(x,y,batch_size=1) +print(loss) + +print("====================================") +print(x) +print(model.predixt(x)) +print("====================================") +``` + +```python +# 출력값 +==================================== +[[1 1] + [1 0] + [0 1] + [0 0]] +1/1 [==============================] - 0s 256ms/step +[[0.09821586] + [0.92452645] + [0.92447394] + [0.06521357]] +==================================== +``` diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/Optimizer.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/Optimizer.md" new file mode 100644 index 00000000..ef70174e --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/Optimizer.md" @@ -0,0 +1,176 @@ +--- +title: 'Optimizer' +lastUpdated: '2024-03-02' +--- + 손실함수를 줄여나가면서 학습하는 방법은 여러가지가 있는데, 이를 최적화 하는 방법들을 Optimizer라고 부른다. + +## 경사 하강법 + +경사하강법은 손실 함수또는 비용 함수라 불리는 목적 함수를 정의하고, 이 함수의 값이 최소화되는 파라미터를 찾는 방법이다. + +> 손실 (loss) : 실제값과 모델로 예측한 값이 얼마나 차이가 나는가를 나타내는 값으로, 손실이 작을수록 예측값이 정확한 것이다.
비용(cost, error)은 손실을 전체 데이터에 대해 구한 경우이며 비용을 함수로 나타낸 것을 손실 함수또는 비용 함수라고 한다. + +함수의 최소값을 찾기 위해 임의의 위치에서 시작해서 기울기를 따라 조금씩 더 낮은 위치로 내려가며 극값에 이를 때까지 반복시킨다. + +손실 함수는 인공지능의 파라미터를 통하여 나온 예측 값과 실제 값의 차이이기 때문에, 특정 파라미터를 통하여 나온 손실 함수 값이 가장 낮은 곳이 바로 **최적의 파라미터**라고 할 수 있다. + +**경사 하강법 동작 순서** + +1. 가중치 초기화 + - 0이나 랜덤 값으로 초기화해준다. +2. 비용함수 계산 + - 만약 현재 위치의 기울기가 **음수**라면 파라미터를 **증가**시키면 최솟값을 찾을 수 있다. + - 반대로 기울기가 **양수**라면 파라미터를 **감소**시키면 최솟값을 찾을 수 있다. + - **따라서 해당 파라미터에서 학습률 * 기울기를 빼면 최솟값이 되는 장소를 찾을 수 있다.** + + image + +3. 가중치 갱신 + - 기울기는 음수 값을 가진다. (오른쪽으로 이동(하강)하게 하기 위해 기울기에 - 를 붙여준다.) + - 가중치를 조금씩 움직이는 것을 반복하다보면 최저점에 접근할 수 있다. +4. 2~3 과정을 지정한 횟수나 비용함수값이 일정 임계값 이하로 수렴할때까지 반복한다. + - 전체를 한번 도는걸 1 epoch이라 한다. + + + --- + + +## 배치 경사 하강법 + +image + + +배치 경사 하강법은 경사 하강법의 손실 함수의 기울기 계산에 **전체 학습 데이터셋**에 대한 에러를 구한 뒤 기울기를 한 번만 계산하여 모델의 파라미터를 업데이트하는 방식을 의미한다. + +**배치 경사 하강법의 문제점** + +- 모든 데이터를 적용하여 변화량을 구하기 떄문에 연산량이 많이 필요하다. +- 초기 W값에 따라 지역적 최소값에 빠지는 경우가 발생한다. + - 이를 해결하기 위해 학습률을 상태에 따라 적응적으로 조절할 필요가 있다. (Stochastic Gradient Descent) + + image + + + --- + + +## 확률적 경사 하강법 (Stochastic Gradient Descent, SGD) + + image + + +- 전체 학습 데이터를 사용하지 않고 확률적으로 선택한 샘플의 일부만을 사용하고, 진동하며 결과값으로 수렴한다. +- 일부 데이터만 사용하기 때문에 학습 속도가 매우 빠르다. +- 일반적인 경사 하강법과 반대로 local minimum에 빠지더라도 쉽게 빠져나올 수 있어서 global minimum을 찾을 가능성이 더 크다. +- 손실 함수가 최솟값에 가는 과정이 불안정하다 보니 최적해(global minimum)에 정확히 도달하지 못할 가능성이 있다. +- 결과의 진폭이 크고 불안하다는 단점이 있다. (오차율이 크다) + +```python +weight[i] += - learning_rate * gradient +``` + +```python +keras.optimizers.SGD(lr=0.1) +``` + +확률적 경사 하강법의 노이즈를 줄이면서도 전체 배치보다 더 효율적인 방법으로는 미니배치 경사 하강법이 있다. + + + --- + + +## 미니배치 경사 하강법 + + image + +SGD와 BGD의 절충안으로 배치 크기를 줄여 확률적 경사 하강법을 이용하는 방법이다. + +전체 데이터를 작은 그룹으로 나누고, 작은 그룹 단위로 가중치를 갱신한다. + +전체 데이터를 batch_size개씩 나눠 배치로 학습 시키는 방법이고, 배치 크기는 사용자가 지정한다. 일반적으로는 메모리가 감당할 수 있는 정도로 결정한다. + +**미니배치 경사 하강법 특징** +- 전체 데이터셋을 대상으로 한 SGD 보다 parameter 공간에서 shooting이 줄어든다.(미니배치의 손실 값 평균에 대해 경사 하강을 진행하기 때문에) +- BGD에 비해 Local Minima를 어느정도 회피할 수 있다. +- 최적해에 더 가까이 도달할 수 있으나 local optima 현상이 발생할 수 있다. local optima의 문제는 무수히 많은 임의의 parameter로부터 시작하면 해결된다. (학습량 늘리기) + + + --- + + +## 모멘텀 + +- 모멘텀은 SGD의 높은 편차를 줄이고 수렴을 부드럽게 하기 위해 고안되었다. + +- 관련 방향으로의 수렴을 가속화하고 관련 없는 방향으로의 변동을 줄여준다. 말 그대로 이동하는 방향으로 나아가는 '관성'을 주는 것이다. + +- γ는 현재 기울기 값(현재 가속도)뿐만 아니라 (과거의 가속도로 인한) 현재 속도를 함께 고려하여 이동 속도를 나타내는 momentum term이다. + + image + +- 이전 gradient들의 영향력을 매 업데이트마다 γ배 씩 감소 +- momentum term γ는 보통 0.9 정도로 정함 + +- SGD에 비해 파라미터의 분산이 줄어들고 덜 oscillate한다는 장점이 있고, 빠르게 수렴한다. 하지만 γ라는 새로운 하이퍼 파라미터가 추가되었으므로 적절한 값을 설정해줘야 한다는 단점이 있다. + + image + +## NAG (Nesterov Accelerated Gradient) + +- 모멘텀은 좋은 방법일 수 있지만, 모멘텀이 너무 높으면 알고리즘이 minima를 놓치고 건너뛰어버릴 우려가 있다. +- NAG는 '앞을 내다보는' 알고리즘이다. NAG에서는 momentum step을 먼저 고려하여, momentum step을 먼저 이동했다고 생각한 후 그 자리에서의 gradient를 구해서 gradient step을 이동한다. + + image + +- 식을 보면 gradient와 momentum step이 독립적으로 계산되는 것이 아님을 알 수 있다. + +- 모멘텀에 비해 멈춰야 할 지점(minima)에서 제동을 걸기 쉽다. 일단 모멘텀으로 이동을 반 정도 한 후, 어떤 방식으로 이동해야 할 지 결정할 수 있다. 하지만 여전히 하이퍼 파라미터 값을 수동으로 결정해줘야 한다는 단점이 존재한다. + + + --- + + +## Adagrad + +![image](https://github.com/rlaisqls/TIL/assets/81006587/59b475fd-6a40-4f61-94a7-dede7ebaeb20) + +- 위에 설명한 모든 옵티마이저의 단점 중 하나는 학습률이 모든 파라미터와 각 cycle에 대해 일정하다는 것이다. + +- Adagrad는 각 파라미터와 각 단계마다 학습률을 변경할 수 있다. second-order 최적화 알고리즘의 유형으로, 손실함수의 도함수에 대해 계산된다. + +- 이 알고리즘의 기본적인 아이디어는 ‘지금까지 많이 변화하지 않은 변수들은 step size를 크게 하고, 지금까지 많이 변화했던 변수들은 step size를 작게 하자’ 라는 것이다. + + - 자주 등장하거나 변화를 많이 한 변수들의 경우 optimum에 가까이 있을 확률이 높기 때문에 작은 크기로 이동하면서 세밀한 값을 조정하고, 적게 변화한 변수들은 optimum 값에 도달하기 위해서는 많이 이동해야할 확률이 높기 때문에 먼저 빠르게 loss 값을 줄이는 방향으로 이동하려는 방식이라고 생각할 수 있겠다. + +- 각 학습 파라미터에 대해 학습률이 바뀌기 때문에 수동으로 조정할 필요가 없지만, 이계도함수를 계산해야 하기 때문에 계산 비용이 많이 든다. +- 또, Adagrad에는 학습을 진행하면 진행할 수록 학습률이 줄어든다는 문제점이 있다. +- Gt에 계속 제곱한 값을 넣어주기 때문에 값이 계속 커지므로, 학습이 오래 진행될 경우 학습률이 너무 작아져 결국 거의 움직이지 않게 된다. 즉, 최솟값에 도달하기도 전에 학습률이 0에 수렴해버릴 수도 있다. + + image + + +--- + + ## Adam (Adaptive Moment Estimation) + +- Adagrad나 RMSProp처럼 각 파라미터마다 다른 크기의 업데이트를 진행하는 방법이다. +- Adam의 원리를 간단히 설명하면 local minima를 뛰어넘을 수 있다는 이유만으로 빨리 굴러가는 것이 아닌, minima의 탐색을 위해 조심스럽게 속도를 줄이고자 하는 것이다. +- Adam은 AdaDelta와 같이 decaying average of squared gradients를 저장할 뿐만 아니라, 과거 gradient +의 decaying average도 저장한다. + + image + +- mt와 vt가 학습 초기에 0으로 biased 되는 것을 방지하기 위해 uncentered variance of the gradients인 ^mt, ^vt를 계산해준다. + + image + +- 이 보정된 값들을 가지고 파라미터를 업데이트한다. 기존에 Gt 자리에 ^vt를 넣고, gradient 자리에 ^mt를 넣으면 된다. + + image + +- β1의 값은 0.9, β2는 0.999, ε은 (10 x exp(-8))이다. + +- loss가 최솟값으로 빠르게 수렴하고 vanishing learning rate 문제, high variance 문제를 해결하였다. +- 계산 비용이 많이 든다는게 단점이다. + + diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/RNN.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/RNN.md" new file mode 100644 index 00000000..88f4118b --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/RNN.md" @@ -0,0 +1,48 @@ +--- +title: 'RNN' +lastUpdated: '2024-03-02' +--- + +- 은닉층 안에 하나 이상의 순환 계층을 갖는 신경망 +- 기존 신경망 구조: 모든 입력과 출력이 각각 독립적이라 가정하고, 시간에 따른 입출력 간의 관계를 고려되지 않았음 +- RNN은 현재 입력을 유추할 때 이전 결과를 반영하여 계산하기 때문에 시간적 순서를 가지는 Sequence 기반 데이터, 연속적인 시계열(time series) 데이터를 잘 다룸 +- 시간 순서를 기반으로 데이터들의 상관관계를 파악해서 그를 기반으로 현재 및 과거 데이터를 통해서 미래에 발생될 값을 예측 +- 활성화 함수로 탄젠트 하이퍼볼릭을 많이 사용함 +- Cell 안에 Unit이 여러개 들어가고, 각 Cell마다 은닉상태를 가짐 + +image + +## 유형 + +영향을 주는 셀과 영향받는 셀의 관계에 따라 One-to-One, One-to-Many, Many-to-Many 등으로 나뉜다. 사용하는 데이터나 개발한 모델에 따라 다른 종류를 사용한다. + +image + +## LSTM + +- RNN은 과거의 정보를 기억할 수 있다. 하지만 멀리 떨어져있는 문맥은 기억할 수 없다. +- LSTM은 이러한 "긴 기간의 의존성(long-term dependencies)"를 완벽하게 다룰 수 있도록 개선한 버전이다. + +- 모든 RNN은 neural network 모듈을 반복시키는 체인과 같은 형태를 하고 있다. 기본적인 RNN에서 이렇게 반복되는 모듈은 굉장히 단순한 구조를 가지고 있다. 예를 들어 tanh layer 한 층을 들 수 있다. + - 아래는 RNN의 일반적인 모습이다. + + image + +- LSTM도 똑같이 체인과 같은 구조를 가지고 있지만, 각 반복 모듈은 다른 구조를 갖고 있다. 단순한 neural network layer 한 층 대신에, 3개의 게이트가 특별한 방식으로 서로 정보를 주고 받도록 되어 있다. + - LSTM 반복 모듈의 모습이다. + + image + +- LSTM의 게이트 3개 + - 삭제 게이트(forget gate layer) : 셀 상태에서 감소 및 삭제시킬 기억을 결정 + - cell state로부터 어떤 정보를 버릴 것인지를 정하는 것으로, sigmoid layer에 의해 결정된다. + - 입력 게이트(input gate layer): 현재 입력된 정보 중 어떤 것을 저장할지 제어 + - sigmoid layer가 어떤 값을 업데이트할지 정한다. + - tanh layer가 새로운 후보 값들인 ct라는 vector를 만들고, cell state에 더할 준비를 한다. 이렇게 두 단계에서 나온 정보를 합쳐서 state를 업데이트할 재료를 만든다. + - 출력 게이트(output gate layer): 업데이트된 셀 상태를 기반으로 특정 부분을 읽어 현재의 은닉 상태 제어 + - 이미 이전 단계에서 어떤 값을 얼마나 업데이트해야 할 지 다 정해놨으므로 여기서는 그 일을 실천만 하면 된다. + +--- +참고 +- https://www.ibm.com/kr-ko/topics/recurrent-neural-networks +- https://dgkim5360.tistory.com/entry/understanding-long-short-term-memory-lstm-kr diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\204\240\355\230\225\355\232\214\352\267\200.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\204\240\355\230\225\355\232\214\352\267\200.md" new file mode 100644 index 00000000..cb832ee0 --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\204\240\355\230\225\355\232\214\352\267\200.md" @@ -0,0 +1,90 @@ +--- +title: '선형회귀' +lastUpdated: '2023-10-18' +--- +## 선형회귀 : 최소제곱법 + +```python +import tensorflow as tf +import matplotlib.pyplot as plt +import numpy as np + +X = [0, 0.5, 1.0, 1.5, 2.0, 2.5] +Y = [0.3, 1.9, 2.4, 4.1, 6.8, 7.9] + +x_mean = sum(X)/len(X) +y_mean = sum(Y)/len(Y) + +division = sum((y-y_mean)*(x-x_mean) for y, x in list(zip(Y,X))) +divisor = sum((x-x_mean)**2 for x in X) + +a = division / divisor +b = y_mean - a * x_mean + +new_X = np.arange(0, 3, 0.05) +new_Y = a * new_X + b + +print('a:', a, 'b:', b) + +plt.plot(X, Y, 'ro', label='Sample Data') +plt.plot(new_X, new_Y, 'b-', label='Test Data') +plt.xlabel('X') +plt.ylabel('Y') +plt.legend() +plt.show() +``` + +최소 제곱법은 손실을 구하기 위해 사용한다. + +image + +### 결과물 + +image + +## 선형회귀 : [경사하강법](./Optimizer.md) + +```python +import tensorflow as tf +import matplotlib.pyplot as plt + +X = [0, 0.5, 1.0, 1.5, 2.0, 2.5] +Y = [0.3, 1.9, 2.4, 4.1, 6.8, 7.9] + +W = tf.Variable(tf.random.uniform([1], -1.0, 1.0)) +b = tf.Variable(tf.random.uniform([1], -1.0, 1.0)) + +optimizer = tf.compat.v1.train.GradientDescentOptimizer(learning_rate=0.1) + +@tf.function() +def cost_eval(): + hypothesis = W * X + b + cost = tf.reduce_mean(tf.square(hypothesis - Y)) + return cost + +print("\n W b cost") +for epoch in range(10): + optimizer.minimize(cost_eval, var_list=[W, b]) + print(epoch, W.numpy(), b.numpy(), cost_eval().numpy()) + +print("\n=====test=====") +x=5 +print("X:", x, "Y:", (W * x + b).numpy()) +x=2.5 +print("X:", x, "Y:", (W * x + b).numpy()) + +new_X = tf.range(0, 3, 0.05) +new_Y = W * new_X + b + +plt.plot(X, Y, 'ro', label='Sample Data') +plt.plot(new_X, new_Y, 'b-') +plt.xlabel('X') +plt.ylabel('Y') +plt.legend() +plt.show() +``` + +### 결과물 + +image + diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/CNN,\342\200\205RNN.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/CNN,\342\200\205RNN.md" new file mode 100644 index 00000000..ef8beddb --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/CNN,\342\200\205RNN.md" @@ -0,0 +1,232 @@ +--- +title: 'CNN, RNN' +lastUpdated: '2024-03-02' +--- + +mnist 알파벳 데이터를 식별하는 CNN 모델 예제이다. + +```python +import tensorflow as tf +import numpy as np + +import matplotlib.pylab as plt + +(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data() + +plt.figure(figsize=(6,1)) + +for i in range(36): + plt.subplot(3,12,i+1) + plt.imshow(train_images[i], cmap="gray") + plt.axis("off") + +plt.show() +# 28 * 28의 벡터 이미지 60000개, 채널은 1개 +train_images = train_images.reshape((60000, 28, 28, 1)) +test_images = test_images.reshape((10000, 28, 28, 1)) +train_images, test_images = train_images / 255.0, test_images / 255.0 + +print(train_labels[:10]) + +one_hot_train_labels = tf.keras.utils.to_categorical(train_labels, 10) +one_hot_test_labels = tf.keras.utils.to_categorical(test_labels, 10) + +print(one_hot_train_labels[:10]) + +model = tf.keras.models.Sequential() + +model.add(tf.keras.layers.Conv2D(32, (3,3), activation='relu', strides=(1,1), input_shape=(28, 28, 1))) +model.add(tf.keras.layers.MaxPooling2D((2,2))) +model.add(tf.keras.layers.Conv2D(64, (3,3), activation='relu')) +model.add(tf.keras.layers.Flatten()) +model.add(tf.keras.layers.Dense(64, activation='relu')) +model.add(tf.keras.layers.Dense(10, activation='softmax')) + +model.compile(optimizer=tf.optimizers.Adam(learning_rate=0.01),loss='categorical_crossentropy', metrics=['accuracy']) + +model.summary() +history = model.fit(train_images, one_hot_train_labels, epochs=2, batch_size=10) + +plt.figure(figsize=(12,4)) +plt.subplot(1,1,1) +plt.plot(history.history['loss'], 'b--', label='loss') +plt.plot(history.history['accuracy'], 'g-', label='Accuracy') +plt.xlabel('Epoch') +plt.legend() +plt.show() +print("최적화 완료") + +print("\n================================================\n") +labels = model.predict(test_images) +print("accuracy: %.4f"% model.evaluate(test_images, one_hot_test_labels, verbose=2)[1]) + +fig = plt.figure() +for i in range(36): + subplot = fig.add_subplot(3,12,i+1) + subplot.set_xticks([]) + subplot.set_yticks([]) + subplot.set_title('%d' % np.argmax(labels[i])) + subplot.imshow(test_images[i].reshape((28, 28)), cmap=plt.cm.gray_r) + +plt.show() + +print("\n================================================\n") +``` + + +#### 출력 +image + +```js +[5 0 4 1 9 2 1 3 1 4] +[[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] + [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.] + [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] + [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] + [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] + [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]] +Model: "sequential_1" +_________________________________________________________________ + Layer (type) Output Shape Param # +================================================================= + conv2d_2 (Conv2D) (None, 26, 26, 32) 320 + + max_pooling2d_1 (MaxPooling (None, 13, 13, 32) 0 + 2D) + + conv2d_3 (Conv2D) (None, 11, 11, 64) 18496 + + flatten_1 (Flatten) (None, 7744) 0 + + dense_2 (Dense) (None, 64) 495680 + + dense_3 (Dense) (None, 10) 650 + +================================================================= +Total params: 515,146 +Trainable params: 515,146 +Non-trainable params: 0 +_________________________________________________________________ +Epoch 1/2 +6000/6000 [==============================] - 94s 16ms/step - loss: 0.1885 - accuracy: 0.9462 +Epoch 2/2 +6000/6000 [==============================] - 95s 16ms/step - loss: 0.1358 - accuracy: 0.9648 +``` + +image + +```js +최적화 완료 + +================================================ + +313/313 [==============================] - 3s 9ms/step +313/313 - 3s - loss: 0.1350 - accuracy: 0.9671 - 3s/epoch - 8ms/step +accuracy: 0.9671 +``` + +image + +# RNN + +mnist 알파벳 데이터를 식별하는 RNN 모델 예제이다. + +Keras의 SimpleRNN을 사용하여 간단하게 구현하였다. + +```python +import tensorflow as tf +import numpy as np +from tensorflow.keras.layers import SimpleRNN, Dense +import matplotlib.pylab as plt + +(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data() + +train_images, test_images = train_images / 255.0, test_images / 255.0 + +print(train_labels[:10]) + +one_hot_train_labels = tf.keras.utils.to_categorical(train_labels, 10) +one_hot_test_labels = tf.keras.utils.to_categorical(test_labels, 10) + +model = tf.keras.models.Sequential() +model.add(SimpleRNN(units=64, input_shape = (28, 28), return_sequences=False)) +model.add(Dense(10, activation='softmax')) + +model.compile(optimizer=tf.optimizers.Adam(learning_rate=0.01),loss='categorical_crossentropy', metrics=['accuracy']) + +model.summary() +history = model.fit(train_images, one_hot_train_labels, epochs=2, batch_size=10) + +plt.figure(figsize=(12,4)) +plt.subplot(1,1,1) +plt.plot(history.history['loss'], 'b--', label='loss') +plt.plot(history.history['accuracy'], 'g-', label='Accuracy') +plt.xlabel('Epoch') +plt.legend() +plt.show() +print("최적화 완료") + +print("\n================================================\n") +labels=model.predict(test_images) +print("accuracy: %.4f"% model.evaluate(test_images, one_hot_test_labels, verbose=2)[1]) + +fig = plt.figure() +for i in range(36): + subplot = fig.add_subplot(3,12,i+1) + subplot.set_xticks([]) + subplot.set_yticks([]) + subplot.set_title('%d' % np.argmax(labels[i])) + subplot.imshow(test_images[i].reshape((28, 28)), cmap=plt.cm.gray_r) + +plt.show() + +print("\n================================================\n") +``` + +### 실행결과 + +```js +[5 0 4 1 9 2 1 3 1 4] +Model: "sequential_4" +_________________________________________________________________ + Layer (type) Output Shape Param # +================================================================= + simple_rnn_2 (SimpleRNN) (None, 64) 5952 + + dense_6 (Dense) (None, 10) 650 + +================================================================= +Total params: 6,602 +Trainable params: 6,602 +Non-trainable params: 0 +_________________________________________________________________ +Epoch 1/5 +1875/1875 [==============================] - 14s 7ms/step - loss: 0.4983 - accuracy: 0.8468 +Epoch 2/5 +1875/1875 [==============================] - 13s 7ms/step - loss: 0.2367 - accuracy: 0.9312 +Epoch 3/5 +1875/1875 [==============================] - 13s 7ms/step - loss: 0.1963 - accuracy: 0.9415 +Epoch 4/5 +1875/1875 [==============================] - 13s 7ms/step - loss: 0.1778 - accuracy: 0.9492 +Epoch 5/5 +1875/1875 [==============================] - 12s 7ms/step - loss: 0.1622 - accuracy: 0.9534 +``` + +image + + +```js +최적화 완료 + +================================================ + +313/313 [==============================] - 1s 3ms/step +313/313 - 1s - loss: 0.1581 - accuracy: 0.9550 - 1s/epoch - 3ms/step +accuracy: 0.9550 +``` + +image diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/MNIST\342\200\205\354\210\253\354\236\220\342\200\205\355\214\220\353\263\204.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/MNIST\342\200\205\354\210\253\354\236\220\342\200\205\355\214\220\353\263\204.md" new file mode 100644 index 00000000..39fd52c4 --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/MNIST\342\200\205\354\210\253\354\236\220\342\200\205\355\214\220\353\263\204.md" @@ -0,0 +1,126 @@ +--- +title: 'MNIST 숫자 판별' +lastUpdated: '2023-10-18' +--- +```python +import tensorflow as tf +import numpy as np + +import matplotlib.pylab as plt + +(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data() + +plt.figure(figsize=(6,1)) + +for i in range(36): + plt.subplot(3,12,i+1) + plt.imshow(train_images[i], cmap="gray") + plt.axis("off") + +plt.show() +train_images = train_images.reshape((60000, 28*28)) +test_images = test_images.reshape((10000, 28*28)) +train_images, test_images = train_images / 255.0, test_images / 255.0 + +print(train_labels[:10]) + +one_hot_train_labels = tf.keras.utils.to_categorical(train_labels, 10) +one_hot_test_labels = tf.keras.utils.to_categorical(test_labels, 10) + +print(one_hot_train_labels[:10]) + +model = tf.keras.models.Sequential() +model.add(tf.keras.layers.Dense(input_dim=784, units=128, activation='relu')) +model.add(tf.keras.layers.Dropout(0.2)) +model.add(tf.keras.layers.Dense(units=10, activation='softmax')) + +model.compile(optimizer=tf.optimizers.Adam(learning_rate=0.01), loss='categorical_crossentropy', metrics=['accuracy']) + +model.summary() +history = model.fit(train_images, one_hot_train_labels, epochs=1, batch_size=10) + +plt.figure(figsize=(12,4)) +plt.subplot(1,1,1) +plt.plot(history.history['loss'], 'b--', label='loss') +plt.plot(history.history['accuracy'], 'g-', label='Accuracy') +plt.xlabel('Epoch') +plt.legend() +plt.show() +print("최적화 완료") + +print("\n================================================\n") +labels=model.predict(test_images) +print("accuracy: %.4f"% model.evaluate(test_images, one_hot_test_labels, verbose=2)[1]) + +fig = plt.figure() +for i in range(10): + subplot = fig.add_subplot(2,5,i+1) + subplot.set_xticks([]) + subplot.set_yticks([]) + subplot.set_title('%d' % np.argmax(labels[i])) + subplot.imshow(test_images[i].reshape((28, 28)), cmap=plt.cm.gray_r) + +plt.show() + +print("\n================================================\n") + +``` + +## Drop out + +```python +... +model = tf.keras.models.Sequential() +model.add(tf.keras.layers.Dense(input_dim=784, units=128, activation='relu')) +model.add(tf.keras.layers.Dropout(0.2)) +model.add(tf.keras.layers.Dense(units=10, activation='softmax')) +... +``` + +## 배치정규화 + +```python +... +model = tf.keras.models.Sequential() +model.add(tf.keras.layers.Dense(input_dim=784, units=128, activation='relu')) +# 배치 정규화 시행 +model.add(tf.keras.layers.BatchNormalization()) +# 정규화된 결과값에 relu 활성화 함수 적용 +model.add(tf.keras.layers.Activation('relu')) +model.add(tf.keras.layers.Dense(units=10, activation='softmax')) +... +``` + +image + +```bash +[5 0 4 1 9 2 1 3 1 4] +[[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] + [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.] + [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] + [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] + [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] + [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.] + [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]] +Model: "sequential" +_________________________________________________________________ + Layer (type) Output Shape Param # +================================================================= + dense (Dense) (None, 128) 100480 + + dropout (Dropout) (None, 128) 0 + + dense_1 (Dense) (None, 10) 1290 + +================================================================= +Total params: 101,770 +Trainable params: 101,770 +Non-trainable params: 0 +``` + +--- +참고 +- https://keras.io/ko/getting-started/sequential-model-guide/ \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/\353\260\260,\342\200\205\354\236\220\353\217\231\354\260\250,\342\200\205\353\271\204\355\226\211\352\270\260\342\200\205\353\266\204\353\245\230\355\225\230\352\270\260.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/\353\260\260,\342\200\205\354\236\220\353\217\231\354\260\250,\342\200\205\353\271\204\355\226\211\352\270\260\342\200\205\353\266\204\353\245\230\355\225\230\352\270\260.md" new file mode 100644 index 00000000..20e444ab --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\354\213\244\354\212\265/\353\260\260,\342\200\205\354\236\220\353\217\231\354\260\250,\342\200\205\353\271\204\355\226\211\352\270\260\342\200\205\353\266\204\353\245\230\355\225\230\352\270\260.md" @@ -0,0 +1,57 @@ +--- +title: '배, 자동차, 비행기 분류하기' +lastUpdated: '2023-10-18' +--- +```python +import numpy as np +import tensorflow as tf + +x = np.array([[0,0], [1,0], [1,1], [0,0], [0,0], [0,1]]) # 바퀴, 날개 +y = np.array([ + [1,0,0], # 배 + [0,1,0], # 자동차 + [0,0,1], # 비행기 + [1,0,0], + [1,0,0], + [0,0,1] +]) + +model = tf.keras.Sequential() +model.add(tf.keras.layers.Dense(input_dim=2, units=10, activation='relu')) # input_dim : 입력값 갯수 +model.add(tf.keras.layers.Dense(units=5, activation='relu')) # units : 출력값 갯수 +model.add(tf.keras.layers.Dense(units=3, activation='softmax')) + +model.compile(optimizer=tf.optimizers.Adam(learning_rate=0.01), loss='categorical_crossentropy', metrics=['accuracy']) + +# model.summary() + +history = model.fit(x, y, epochs=100, batch_size=1) + +for weight in model.weights: + print(weight) + +loss = model.evaluate(x,y,batch_size=1) +print(loss) + +print("====================================") +print(x) +print(model.predict(x)) +print("Accuracy: %.4f" % model.evaluate(x,y)[1]) +print("====================================") +``` + +epoch 횟수에 따른 loss와 accuracy를 그래프로 표현하면 다음과 같다. + +```python +import matplotlib.pyplot as plt + +plt.figure(figsize=(12,4)) +plt.subplot(1,1,1) +plt.plot(history.history['loss'], 'b--', label='loss') +plt.plot(history.history['accuracy'],'g-',label='Accuracy') +plt.xlabel('Epoch') +plt.legend() +plt.show() +``` + +image \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\355\215\274\354\205\211\355\212\270\353\241\240.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\355\215\274\354\205\211\355\212\270\353\241\240.md" new file mode 100644 index 00000000..eed1ec4a --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\355\215\274\354\205\211\355\212\270\353\241\240.md" @@ -0,0 +1,146 @@ +--- +title: '퍼셉트론' +lastUpdated: '2023-10-18' +--- +## **퍼셉트론의 구조** + +입력값마다 다른 weight을 곱한 값을 모두 더하고 여기에 bias라고 불리는 값을 더한다. + +더해진 총 합은 [활성화 함수](%ED%99%9C%EC%84%B1%ED%99%94%ED%95%A8%EC%88%98.md)에 적용, 활성화 수준을 계산한 값이 출력된다. + +image + +여기서, 출력값과 목표 값이 다른 경우 Error를 통해 가중치를 업데이트한다. + +결국 학습이라는 것은 이 가중치를 반복적으로 조정하면서 알맞은 가중치와 bias, 즉 학습 목표인 두 부류로 선형분리하기 위한 학습 벡터를 찾아내는 과정이라고 볼 수 있다. + + +## 단층 퍼셉트론의 구현 + +```python +import tensorflow as tf +import numpy as np + +def step_func(x): # 계단함수 + return (x >= 0) * 1 + +def sigmoid(x): + return 1 / (1 + np.exp(-x)) + +fun = step_func + +x = np.array([[1,1],[1,0],[0,1],[0,0]]) +y = np.array([[1], [0], [0], [0]]) +w = tf.random.normal([2], 0, 1) # 초기 가중치 +b = 1.0 # bias 초기값 +w0 = 0.5 +a = 0.1 # 학습률 + +for i in range(1000): + error_sum = 0 + for j in range(4): + output = fun(np.sum(x[j] * w) + b * w0) + error = y[j][0] - output # 오차 = 예측값 - 실제 출력값 + w = w + x[j] * a * error + b = b + a * error + error_sum += error + + if i % 50 == 0: + print(i, error_sum) + +for i in range(4): + if fun(np.sum(x[i] * w) + b * w0) > 0.0: # sigmoid인 경우 0.5로 설정 + output = 1.0 + else: + output = 0.0 + + print('X: ', x[i], ' Y: ', y[i], ' Output: ', fun(np.sum(x[i] * w) + b * w0), " result: ", output) +``` + +```log +# 출력 : step_func +X: [1 1] Y: [1] output: 1 result: 1.0 +X: [1 0] Y: [0] output: 0 result: 0.0 +X: [0 1] Y: [0] output: 0 result: 0.0 +X: [0 0] Y: [0] output: 0 result: 0.0 +``` + +```log +# 출력 : sigmoid +X: [1 1] Y: [1] Output: 0.9058413807262921 result: 1.0 +X: [1 0] Y: [0] Output: 0.07412882460706796 result: 0.0 +X: [0 1] Y: [0] Output: 0.07442403891386311 result: 0.0 +X: [0 0] Y: [0] Output: 0.000668736638863177 result: 0.0 +``` + +결과가 정확하거나 유사하게 나오는 것을 볼 수 있다. + +## 단층 퍼셉트론의 한계 + +xor과 같은 경우에는 하나의 직선으로 선형분리할 수 없다는 한계가 있다. + +```log +X: [1 1] Y: [0] Output: 0.5095529382491947 result: 1.0 +X: [1 0] Y: [1] Output: 0.5095529568688468 result: 1.0 +X: [0 1] Y: [1] Output: 0.5031846645024431 result: 1.0 +X: [0 0] Y: [0] Output: 0.5031846831281389 result: 1.0 +``` + +![image](https://user-images.githubusercontent.com/81006587/233521278-d3fd17a0-e4f7-4966-a3a1-e4ed69747026.png) + +## 다층 퍼셉트론 + +NAND와 OR을 AND한 결과를 내어, 계층을 두개로 만들면 (다층 퍼셉트론을 만들면) 해결할 수 있다. + +```python +import numpy as np + +def AND(x1,x2): + x = np.array([x1,x2]) + w = np.array([0.5,0.5]) + b = -0.7 + tmp = np.sum(w*x)+b + if tmp <= 0 : + return 0 + else : + return 1 + +def NAND(x1,x2): + x = np.array([x1,x2]) + w = np.array([-0.5,-0.5]) + b = 0.7 + tmp = np.sum(w*x)+b + if tmp <= 0 : + return 0 + else : + return 1 + +def OR(x1,x2): + x = np.array([x1,x2]) + w = np.array([0.5,0.5]) + b = -0.2 + tmp = np.sum(w*x)+b + if tmp <= 0 : + return 0 + else : + return 1 + +def XOR(x1, x2): + s1 = NAND(x1, x2) + s2 = OR(x1, x2) + y = AND(s1, s2) + return y + +print(XOR(0,0)) +print(XOR(0,1)) +print(XOR(1,0)) +print(XOR(1,1)) +``` + +``` +// 결과 +0 +1 +1 +0 +``` \ No newline at end of file diff --git "a/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\355\231\234\354\204\261\355\231\224\355\225\250\354\210\230.md" "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\355\231\234\354\204\261\355\231\224\355\225\250\354\210\230.md" new file mode 100644 index 00000000..0710b32d --- /dev/null +++ "b/src/content/docs/TIL/\354\235\270\352\263\265\354\247\200\353\212\245\342\200\205AI/\355\231\234\354\204\261\355\231\224\355\225\250\354\210\230.md" @@ -0,0 +1,151 @@ +--- +title: '활성화함수' +lastUpdated: '2024-03-02' +--- + +```python +import numpy as np +import matplotlib.pyplot as plt +``` + +## 1. 계단 함수(Step function) + +```python +def step(x): + return np.array(x > 0, dtype=np.int) +x = np.arange(-5.0, 5.0, 0.1) # -5.0부터 5.0까지 0.1 간격 생성 +y = step(x) +plt.title('Step Function') +plt.plot(x,y) +plt.show() +``` + +![image](https://github.com/rlaisqls/TIL/assets/81006587/adaefd27-4edc-4bb9-970d-05aad72115c4) + +음수면 0, 양수면 1을 반환하는 간단한 함수이다. 실제로는 거의 사용되지 않는다. + +## 2. 시그모이드 함수(Sigmoid function) +시그모이드 함수를 사용한 인공 신경망이 있다고 가정해보자. + +위 인공 신경망의 학습 과정은 다음과 같다. + +1. 우선 인공 신경망은 입력에 대해서 순전파(forward propagation) 연산을 한다. +2. 그리고 순전파 연산을 통해 나온 예측값과 실제값의 오차를 손실 함수(loss function)을 통해 계산한다. +3. 이 손실(오차라고도 부릅니다. loss)을 미분을 통해서 기울기(gradient)를 구한다. +4. 이를 통해 출력층에서 입력층 방향으로 가중치와 편향을 업데이트 하는 과정인 역전파(back propagation)를 수행한다. + +이 시그모이드 함수의 문제점은 미분을 해서 기울기(gradient)를 구할 때 발생한다. + +```python +def sigmoid(x): + return 1/(1+np.exp(-x)) +x = np.arange(-5.0, 5.0, 0.1) +y = sigmoid(x) + +plt.plot(x, y) +plt.plot([0,0],[1.0,0.0], ':') # 가운데 점선 추가 +plt.title('Sigmoid Function') +plt.show() +``` + +![image](https://github.com/rlaisqls/TIL/assets/81006587/468520e6-3ef2-49f7-9297-79b0e8cd4be2) + +위 그래프는 시그모이드 함수의 그래프이다. 시그모이드 함수의 출력값이 0 또는 1에 가까워지면, 그래프의 기울기가 완만해지는 모습을 볼 수 있다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/293b472d-cbb2-44a2-aed8-9cb484ed5295) + +주황색 구간에서는 미분값이 0에 가까운 아주 작은 값이다. 초록색 구간에서의 미분값은 최대값이 0.25이다. 다시 말해 시그모이드 함수를 미분한 값은 적어도 0.25 이하의 값이다. 시그모이드 함수를 활성화 함수로하는 인공 신경망의 층을 쌓는다면, 가중치와 편향을 업데이트 하는 과정인 역전파 과정에서 0에 가까운 값이 누적해서 곱해지게 되면서, 앞단에는 기울기(미분값)가 잘 전달되지 않게 된다. 이러한 현상을 **기울기 소실(Vanishing Gradient)** 문제라고 한다. + +시그모이드 함수를 사용하는 은닉층의 개수가 다수가 될 경우에는 0에 가까운 기울기가 계속 곱해지면 앞단에서는 거의 기울기를 전파받을 수 없게 된다. 다시 말해 매개변수가 업데이트 되지 않아 학습이 되지 않는다. + +image + +위의 그림은 은닉층이 깊은 신경망에서 기울기 소실 문제로 인해 출력층과 가까운 은닉층에서는 기울기가 잘 전파되지만, 앞단으로 갈수록 기울기가 제대로 전파되지 않는 모습을 보여준다. 결론적으로 시그모이드 함수의 은닉층에서의 사용은 지양된다. 시그모이드 함수는 주로 이진 분류를 위해 출력층에서 사용한다. + +## 3. 하이퍼볼릭탄젠트 함수(Hyperbolic tangent function) + +하이퍼볼릭탄젠트 함수(tanh)는 입력값을 -1과 1사이의 값으로 변환한다. + +``` +x = np.arange(-5.0, 5.0, 0.1) # -5.0부터 5.0까지 0.1 간격 생성 +y = np.tanh(x) + +plt.plot(x, y) +plt.plot([0,0],[1.0,-1.0], ':') +plt.axhline(y=0, color='orange', linestyle='--') +plt.title('Tanh Function') +plt.show() +``` + +![image](https://github.com/rlaisqls/TIL/assets/81006587/6a30bb1c-6b06-4fc8-8331-cc08e340cb17) + +하이퍼볼릭탄젠트 함수도 -1과 1에 가까운 출력값을 출력할 때, 시그모이드 함수와 같은 문제가 발생한다. 그러나 하이퍼볼릭탄젠트 함수의 경우에는 시그모이드 함수와는 달리 0을 중심으로 하고있으며 하이퍼볼릭탄젠트 함수를 미분했을 때의 최대값은 1로 시그모이드 함수의 최대값인 0.25보다는 크다. + +다시 말해 미분했을 때 시그모이드 함수보다는 전반적으로 큰 값이 나오게 되므로 시그모이드 함수보다는 기울기 소실 증상이 적은 편이며 은닉층에서 시그모이드 함수보다는 선호되는 함수다. + +## 4. 렐루 함수(ReLU) + +인공 신경망의 은닉층에서 가장 인기있는 함수이다. + +```python +def relu(x): + return np.maximum(0, x) + +x = np.arange(-5.0, 5.0, 0.1) +y = relu(x) + +plt.plot(x, y) +plt.plot([0,0],[5.0,0.0], ':') +plt.title('Relu Function') +plt.show() +``` + +렐루 함수는 음수를 입력하면 0을 출력하고, 양수를 입력하면 입력값을 그대로 반환하는 것이 특징인 함수로(`f(x) = max(0,x)`) 출력값이 특정 양수값에 수렴하지 않고 입력값이 0 이상이면 미분값이 항상 1이다. 깊은 신경망의 은닉층에서 시그모이드 함수보다 훨씬 더 잘 작동한다. 뿐만 아니라, 렐루 함수는 시그모이드 함수와 하이퍼볼릭탄젠트 함수와 같이 어떤 연산이 필요한 것이 아니라 단순 임계값이므로 연산 속도도 빠르다. + +하지만 여전히 문제점이 존재하는데, 입력값이 음수면 기울기. 즉, 미분값도 0이 된다. 그리고 이 뉴런은 다시 회생하는 것이 매우 어렵다. 이 문제를 죽은 렐루(dying ReLU)라고 한다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/7decdfe0-093b-4d24-ac2a-4a57afec3589) + +## 5. 리키 렐루(Leaky ReLU) + +죽은 렐루를 보완하기 위해 ReLU의 변형 함수들이 등장하기 시작했다. Leaky ReLU는 입력값이 음수일 경우에 0이 아니라 0.001과 같은 매우 작은 수를 반환하도록 되어있다. (`f(x) = max(ax,x)`) a는 하이퍼파라미터로 Leaky('새는') 정도를 결정하며 일반적으로는 0.01의 값을 가진다. + +```python +a = 0.1 + +def leaky_relu(x): + return np.maximum(a*x, x) + +x = np.arange(-5.0, 5.0, 0.1) +y = leaky_relu(x) + +plt.plot(x, y) +plt.plot([0,0],[5.0,0.0], ':') +plt.title('Leaky ReLU Function') +plt.show() +``` + +기울기가 0이 되지 않으면 ReLU는 죽지 않는다. + +![image](https://github.com/rlaisqls/TIL/assets/81006587/7c020c4b-f188-4975-bdde-d5199f437191) + + +## 6. 소프트맥스 함수(Softmax function) + +은닉층에서는 ReLU(또는 ReLU 변형) 함수들을 사용하는 것이 일반적이다. 반면, 소프트맥스 함수는 시그모이드 함수처럼 출력층에서 주로 사용된다. 시그모이드 함수가 두 가지 선택지 중 하나를 고르는 이진 분류 (Binary Classification) 문제에 사용된다면 소프트맥스 함수는 세 가지 이상의 (상호 배타적인) 선택지 중 하나를 고르는 다중 클래스 분류(MultiClass Classification) 문제에 주로 사용된다. 다시 말해서 딥 러닝으로 이진 분류를 할 때는 출력층에 앞서 배운 로지스틱 회귀를 사용하고, 딥 러닝으로 다중 클래스 분류 문제를 풀 때는 출력층에 소프트맥스 회귀를 사용한다고 생각할 수 있다. + +```python +x = np.arange(-5.0, 5.0, 0.1) # -5.0부터 5.0까지 0.1 간격 생성 +y = np.exp(x) / np.sum(np.exp(x)) + +plt.plot(x, y) +plt.title('Softmax Function') +plt.show() +``` + +![image](https://github.com/rlaisqls/TIL/assets/81006587/715430ef-8463-4abc-89d6-7b06db867c58) + +--- + +참고 +- https://wikidocs.net/24987