diff --git a/00-Howto/01-Overview.html b/00-Howto/01-Overview.html new file mode 100644 index 0000000000..a2f63a36d2 --- /dev/null +++ b/00-Howto/01-Overview.html @@ -0,0 +1,61 @@ + + + + + + + + + + docmoa 활용 가이드 | docmoa + + + + + +
본문으로 건너뛰기

docmoa 활용 가이드

1분 미만

docmoa 활용 가이드

문서가 인터넷상에 공개되는 목적은 접근성을 극대화 하기 위함 입니다. 또한 로컬환경에서 빠르게 문서를 검색하기 위해 해당 git repo를 clone 받거나 download 받아서 별도의 마크다운 툴과 연동하는 것도 가능합니다. VuePress 기반으로 구성되었기 때문에 이외의 방식은 문서 표기에 제약이 있을 수 있습니다.

github page

docmoaopen in new window의 공개된 페이지를 통해 문서를 읽을 수 있습니다.

VuePress (Local 실행)

공개된 페이지는 내 로컬환경에서도 실행할 수 있습니다. Nodejs가 필요합니다.

npm
# git clone
+git clone https://github.com/docmoa/docs.git
+
+# npm install
+cd docs
+npm install
+
+# start VuePress writing
+npm run dev
+

 


 
 


 

실행이 완료되면 로그에 다음과 같은 메시지와 접속할 수 있는 링크가 나타납니다.

success [10:48:28] Build 6f9dd7 finished in 1179 ms! ( http://localhost:8000/ )
+

웹브라우저에서 실행시 표기되는 로그의 링크를 입력하면 공개된 웹화면과 동일한 환경을 확인할 수 있습니다.

typora

typora는 멀티 OS를 지원하는 마크다운 에디터/뷰어 입니다. VuePress의 플러그인과 일부 호환되지 않는 표기들이 있으나, 전역 검색이 가능하고 개인 노트를 활용하듯 관리할 수 있습니다.

  1. typora.io를 통해 에디터를 다운로드 받고 설치합니다.
  2. docmoa의 소스를 clone/download 합니다.
    git clone https://github.com/docmoa/docs.git
    +

3.typora를 실행하고 옵션에서 [파일] > [열기] 를 클릭하여 앞서 받은 소스 디렉토리의 docs 디렉토리를 열어줍니다.

  1. 전역검색은 다음과 같이 사용 가능합니다.
    • Mac: +f
    • Windows: +f

VS Code

Visual Studio Code에서는 마크다운의 프리뷰를 지원합니다. 앞서 typora는 마크다운으로 작성하는 즉시 마크다운 형태로 변경되었다면, VS Code에서는 좌우 비교하면서 작성한 표현이 어떻게 반영되는지 확인할 수 있습니다.

  1. VS Code Downloadopen in new window 사이트에서 환경에 맞는 설치 파일을 다운로드 받고 설치합니다.
  2. docmoa의 소스를 clone/download 합니다.
    git clone https://github.com/docmoa/docs.git
    +
  3. VS Code를 실행하고 [파일] > [열기] 를 클릭하여 앞서 받은 소스 디렉토리의 docs 디렉토리를 열어줍니다.
  4. Preview 버튼을 클릭하면 양쪽으로 비교하면서 글을 확인/편집 할 수 있습니다.
  5. 전역검색은 다음과 같이 사용 가능합니다.
    • Mac: +f
    • Windows: +f
+ + + diff --git a/00-Howto/02-Guide/01-Start.html b/00-Howto/02-Guide/01-Start.html new file mode 100644 index 0000000000..db2e8157ec --- /dev/null +++ b/00-Howto/02-Guide/01-Start.html @@ -0,0 +1,95 @@ + + + + + + + + + + 문서작성 '시작' | docmoa + + + + + +
본문으로 건너뛰기

문서작성 '시작'

1분 미만

문서작성 '시작'

docmoa에 문서 기여하기위한 가이드를 설명합니다.

다양한 방법으로 문서를 작성하고 기여할 수 있습니다.
얽매이지 마세요.

문서는 모두 git으로 관리되며 공개되어있습니다. 문서 기여를 위한 방식은 별도 안내로 구분하여 설명합니다.

git clone https://github.com/docmoa/docs.git
+

또는 github에서 fork하여 별도 관리 후 pull request 하여도 좋습니다.

디렉토리 구조 이해하기

clone 받은 구조는 VuePress의 구조를 갖고 있습니다. 문서의 기준이 되는 디렉토리는 docs 입니다.

디렉토리 구조

.
+├── .gitignore
+├── LICENSE.md
+├── README.md
+├── `docs`
+│   ├── .vuepress
+│   ├── 00-Howto
+│   ├── 01-Infra
+│   ├── 02-PrivatePlatform
+│   ├── 03-PublicCloud
+│   ├── 04-HashiCorp
+│   ├── 05-etc
+│   ├── 98-tag
+│   ├── 99-about
+│   └── README.md
+└── package.json
+

기존 환경을 로컬에서 실행하고 확인하기

브라우저에서 보여지는 화면을 실시간으로 확인하기 위해 로컬환경에서 Vewpress를 실행합니다. Nodejs가 필요합니다.

npm
# 클론 받은 디렉토리 이동 후 npm install
+cd docs
+npm install
+
+# start VuePress writing
+npm run dev
+

 


 
 

실행이 완료되면 로그에 다음과 같은 메시지와 접속할 수 있는 링크가 나타납니다.

실행 후 접속 링크 출력

✔ Initializing and preparing data - done in 12.34s
+
+  vite v4.4.9 dev server running at:
+
+  ➜  Local:   http://localhost:8080/
+  ➜  Network: http://192.168.0.9:8080/
+

웹브라우저에서 실행시 표기되는 로그의 링크를 입력하면 공개된 웹화면과 동일한 환경을 확인할 수 있습니다.

문서 트리

docs 디렉토리 내에 기존에 구성된 항목 내에 작성도 가능하고 새로운 카테고리를 생성할 수도 있습니다. 디렉토리와 파일은 생성된 순서와 관계없이 정렬되기 때문에 좌측 Sidebar 표기시 원하는 순서대로 표기되기를 원하는 경우 {숫자}- 을 파일명 앞에 붙여 의도한대로 표시되도록 구성가능합니다.

예를 들어 Howto에 작성된 내용을 바탕으로 설명합니다.

문서 트리

00-Howto
+├── `01-Overview.md`
+├── `02-Guide`
+│   ├── 01-Start.md
+│   ├── 02-PullRequest.md
+│   └── 03-Fork.md
+├── `03-Tips`
+│   ├── CodeBlock.md
+│   ├── Link.md
+│   └── TipBox.md
+└── `README.md`
+
  • 파일과 디렉토리 구분없이 같은 Depth에 위치하는 경우 함께 표기 됩니다.
  • README.md는 index.html과 같이 해당 디렉토리의 기본 페이지로 등록됩니다. 디렉토리 경로만 입력하는 경우 해당 페이지가 나타납니다.
    • 요청하는 페이지 경로 : https://docmoa.github.io/00-Howto/
    • 실제하는 페이지 경로 : https://docmoa.github.io/00-Howto/README.md

디렉토리 이름

디렉토리 이름에 공백이 있는 경우 다른 문서에서 참조 시 공백 문자인 %20 를 표기해야 하는 이슈가 발생할 수 있습니다.

[](https://docmoa.github.io/00-Howto/공백이%20있는%20디렉토리)
+

첫번째 글 쓰기

여기서는 SSH Too many authentication failures 와 관련한 간단한 글을 개시하는 것을 예로 설명하겠습니다.
실제 VuePress 환경으로 어떤 내용이 표기되는지 동적인 확인을 위해 앞서 기존 환경을 로컬에서 실행하고 확인하기에서 처럼 화면을 띄우고 작업하면 실제 작성하는 글들이 어떻게 보여지는지도 함께 확인할 수 있습니다.

  1. Linux 카테고리로 가정하고 01.Infra > Linux > TroubleShooting 이라는 디렉토리를 생성합니다.

  2. 문서 작성을 위한 SSH Too many authentication failures.md 파일을 생성합니다.

    • 파일 이름과는 별개로 본문상의 Title이 메뉴에 표기됩니다.
  3. 문서 내용에는 문서 기본 서문(Frontmatter)을 작성하기 위한 ---로 구성된 블록이 최상단에 명시됩니다. 내용은 기존 마크다운 문서를 작성하는 것과 동일하게 구성합니다.

    ---
    +
    +---
    +
    +# SSH Too many authentication failures (제목인 Title은 h1 스타일로)
    +
    +너무많은 인증 실패로 인한 SSH 접속이 안되는 메시지를 간혹 보게되는 경우가 있다.
    +<생략>
    +

내가 쓴 문서를 docmoa에 개시 요청하기

작성한 문서를 docmoa에 기여하는 방법은 다음 Contribute 항목에서 설명합니다.

+ + + diff --git a/00-Howto/02-Guide/02-Contribute.html b/00-Howto/02-Guide/02-Contribute.html new file mode 100644 index 0000000000..5fcd5c2ec1 --- /dev/null +++ b/00-Howto/02-Guide/02-Contribute.html @@ -0,0 +1,68 @@ + + + + + + + + + + Contribute | docmoa + + + + + +
본문으로 건너뛰기

Contribute

약 1 분

Contribute

docmoa에 문서 기여하기위한 가이드를 설명합니다. 일반적인 github 상에서의 코드 기여 방식과 동일합니다.

git 설치(Option)

로컬 환경에서 git 명령을 수행하기 위해 설치합니다. github 브라우저 환경에서 수정하는 것도 가능하지만, 로컬에서 문서를 활용하고 오프라인 작업을 위해서는 설치하시기를 권장합니다.

Git 설치 방법 안내open in new window를 참고하여 아래 설명합니다.

Mac
  • Mavericks(10.9)부터는 Terminal에 단지 처음으로 'git’을 실행하는 것으로 설치가 시작됩니다.
  • 공식 배포판 : http://git-scm.com/download/macopen in new window 에서 다운로드 받은 설치파일을 실행하여 설치합니다.

github Fork

문서는 github상에서 관리됩니다. 우선 문서를 추가구성하고 수정할 수 있도록 원본 github repo를 Fork 합니다.

  1. https://github.com/docmoa/docs로 이동합니다.
  2. 우측 상단의 Fork를 클릭하고 나의 github Org를 선택합니다.
    Fork
    Fork-Target

git Fetch or Pull

Fork의 원본 Repo에 변경에 대해 작업중인 Repo에 변경사항을 적용해야 하는 필요성이 있습니다. 여러 편집자가 동일한 시점에 동일 문서를 편집하게 되면 편집에 충돌이 발생할 수 있습니다.

Conflict
참고 : https://en.wikipedia.org/wiki/Edit_conflictopen in new window

충돌을 사전에 최대한 방지하기 위해서 편집 전에 원본의 문서를 가져오고 병합하는 과정이 필요합니다.

CLI base

CLI 컨트롤을 위해서는 앞서 git 유틸 설치가 필요합니다.

# 1. Fork 받은 본인 소유의 Repo를 Clone 받습니다.
+git clone https://github.com/docmoa/docs
+
+# 2. 해당 소스 디렉토리로 이동하여 remote 를 확인합니다.
+cd docs
+git remote -v
+origin	https://github.com/myorg/docs.git (fetch)
+origin	https://github.com/myorg/docs.git (push)
+
+# 3. 문서 원본 Repo와의 병합을 위해 `upstream` repo remote를 추가합니다.
+git remote add upstream https://github.com/docmoa/docs
+
+# 4-1. pull 을 수행하거나
+git pull upstream main
+
+# 4-2. fetch & merge 를 수행합니다.
+git fetch upstream
+git merge upstream/main
+

git add and commit

문서 작성은 앞서 문서작성 '시작'을 참고하세요. 문서 또는 문서 작성에 필요했던 이미지 등 준비가 끝나면 해당 파일을 본인 소유의 Repo에 추가합니다. 이 때 사용하는 것은 CLI도 가능하고, UI 기반에서 작성한 경우에는 저장 즉시 해당 Repo에 저장됩니다. Commit의 경우 필요에 따라 브랜치를 별도 생성하여 본인의 Repo를 기준으로 관리하는 것 또한 가능합니다.

CLI base

CLI 컨트롤을 위해서는 앞서 git 유틸 설치가 필요합니다.

# 1. 작성한 파일을 git의 관리 대상으로 add 합니다.
+git add path/문서.md
+
+# 2. 로컬 Repo에서 변경사항을 Commit 합니다.
+git commit -m "커밋메시지를 남깁니다.(예를들어 : 문서를 수정)"
+
+# 3. 원격 Repo로 변경사항을 Push 합니다.
+git push origin main
+

Contribute

최종적으로 Fork 한 Repo에 docmoa에 기여할 문서가 준비가 된경우 해당 Repo의 github ui에서 커밋된 정보가 있다는 문구와 Contribute를 클릭하여 Pull request를 진행 할 수 있습니다. Open pull request를 클릭하여 Upstream Repo에 요청을 보냅니다.

Pull request
Pull request

Pull request를 생성하면 본인 소유의 Repo(Branch)로 부터 docmoa에 지금까지의 변경사항을 병합할 것을 요청할 수 있습니다.

상단에 표기되는 repo의 방향과 branch를 다시한번 확인해주세요.
아래 어떤 항목이 어떻게 변경되는지 내용을 확인할 수 있습니다.

create pr
create pr

Create pull request 버튼을 클릭하면 디테일한 설명을 추가할 수 잇습니다. 문서 자체의 변경사항만으로 의도를 전달하기 힘든경우 해당 설명이 이해하는데 큰 도움이 됩니다.

create pr detail
create pr detail

이제 Pull request가 받아들여지고나면 docmoa에 기여해주신 내용이 반영됩니다.

Build 주기

2021년 10월 4일 기준 매 5분마다 변경사항이 있으면 docmoa의 빌드가 수행됩니다.

+ + + diff --git a/00-Howto/02-Guide/04-Template.html b/00-Howto/02-Guide/04-Template.html new file mode 100644 index 0000000000..1ac4853abb --- /dev/null +++ b/00-Howto/02-Guide/04-Template.html @@ -0,0 +1,49 @@ + + + + + + + + + + Template | docmoa + + + + + +
본문으로 건너뛰기

Template

1분 미만

Template

docmoa에 문서 템플릿을 설명합니다.

주의

기본 템플릿 가이드를 잘 지켜주세요. 함께 만드는 문서 모음이므로, 기본적인 형식이 필요합니다.

최소 Template

---
+
+---
+
+# h1 제목 = Title 입니다.
+내용은 마크다운 형식으로 작성합니다.
+
+## h2 제목
+
+
 
 



 
 


  • Frontmatter(서문) : Frontmatter는 --- 로 구분되는 내용입니다. 자세한 내용은 공식 가이드open in new window를 참고하세요. 여기서 주로 사용하는 내용은 다음과 같습니다.
    • title : 생략되면 h1으로 지정되는(#) 제목이 기본으로 적용됩니다.
    • meta : html 구성시 meta 에 추가 기입되는 정보입니다. 검색 엔진에 활용됩니다.
    • author : 작성자를 표기합니다.
    • tag : 해당 문서에 태깅을 지정합니다. 다중으로 적용 가능하며, 적용된 태그의 목록은 Tag에서 확인할 수 있습니다.
  • # 제목 : Frontmatter에서 title을 명시하지 않는 것이 문서 작성에 복잡함을 줄여줄 것으로 예상되어 둘 중에 h1 스타일의 제목을 넣는 것을 권장합니다.
+ + + diff --git a/00-Howto/02-Guide/index.html b/00-Howto/02-Guide/index.html new file mode 100644 index 0000000000..cd238ee746 --- /dev/null +++ b/00-Howto/02-Guide/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Guides | docmoa + + + + + +
본문으로 건너뛰기

Guides

1분 미만

+ + + diff --git a/00-Howto/03-Tips/Chart.html b/00-Howto/03-Tips/Chart.html new file mode 100644 index 0000000000..2780293763 --- /dev/null +++ b/00-Howto/03-Tips/Chart.html @@ -0,0 +1,104 @@ + + + + + + + + + + Chart | docmoa + + + + + +
본문으로 건너뛰기

Chart

1분 미만

Chart

문서 작성시 차트를 추가하는 방법을 안내합니다.

차트 구성 방식은 ChartJSopen in new window를 따릅니다.

::: chart:::로 처리합니다.

기본 사용법 - Bar

A bar chart
CODE
::: chart A bar chart
+
+```json
+{
+  "type": "bar",
+  "data": {
+    "labels": ["Red", "Orange", "Yellow", "Green", "Blue", "Purple"],
+    "datasets": [{
+      "label": "My First Dataset",
+      "data": [12, 19, 3, 5, 2, 3],
+      "backgroundColor": [
+        "rgba(255, 99, 132, 0.2)",
+        "rgba(255, 159, 64, 0.2)",
+        "rgba(255, 205, 86, 0.2)",
+        "rgba(75, 192, 192, 0.2)",
+        "rgba(54, 162, 235, 0.2)",
+        "rgba(153, 102, 255, 0.2)"
+      ],
+      "borderColor": [
+        "rgb(255, 99, 132)",
+        "rgb(255, 159, 64)",
+        "rgb(255, 205, 86)",
+        "rgb(75, 192, 192)",
+        "rgb(54, 162, 235)",
+        "rgb(153, 102, 255)"
+      ],
+      "borderWidth": 1
+    }]
+  },
+  "options": {
+    "scales": {
+      "y": {
+        "ticks": {
+          "beginAtZero": true,
+          "callback": "function(value){ return '$' + value + 'k'; }"
+        },
+        "beginAtZero": true
+      }
+    }
+  }
+}
+```
+
+
+

기본 사용법 - Bubble

A Bubble Chart
CODE
::: chart A Bubble Chart
+
+```json
+{
+  "type": "bubble",
+  "data": {
+    "datasets": [
+      {
+        "label": "First Dataset",
+        "data": [
+          { "x": 20, "y": 30, "r": 15 },
+          { "x": 40, "y": 10, "r": 10 }
+        ],
+        "backgroundColor": "rgb(255, 99, 132)"
+      }
+    ]
+  }
+}
+```
+
+
+ + + diff --git a/00-Howto/03-Tips/CodeBlock.html b/00-Howto/03-Tips/CodeBlock.html new file mode 100644 index 0000000000..03bc6d6c58 --- /dev/null +++ b/00-Howto/03-Tips/CodeBlock.html @@ -0,0 +1,114 @@ + + + + + + + + + + Code Block | docmoa + + + + + +
본문으로 건너뛰기

Code Block

1분 미만

Code Block

마크다운 기본 사용 법과 거의 동일합니다.

기본 사용법

코드블록은 ``` 과 ``` 사이에 코드를 넣어 로 표기합니다. 아래와 같이 md 파일 내에 작성하면

```
+# Code block e.g.
+This is my code
+```
+

다음과 같이 표기됩니다.

# Code block e.g.
+This is my code
+

``` 대신 ~~~ 도 사용 가능합니다.

코드 구문 강조

VuePress는 Prism Javascript 라이브러리를 통해 키워드 강조 표시를 지원 합니다. 목록에 대한 확인은 components.jsonopen in new windowlanguages를 참고할 수 있습니다.

```python
+print("hello, world.")
+```
+
print("hello, world.")
+

코드 강조

문서 작성 시 코드블록에서 강조하고 싶은 경우 코드블럭의 스타일 에 강조할 라인 번호를 명시합니다.

```python {2,4-5}
+print("nomal")
+print("highlight!")
+print("nomal")
+print("highlight!")
+print("highlight!")
+```
+
print("nomal")
+print("highlight!")
+print("nomal")
+print("highlight!")
+print("highlight!")
+

 

 
 

다중 코드블록

상황에 따라 동일한 구성 및 동작을 위한 코드블록을 옵션을 주어 선택적으로 표기하고 싶은 경우 <code-group><code-block> 을 활용 합니다.

::: code-tabs#shell
+@tab Option1
+```bash {2}
+# This is Option 1
+chmod 755 ./file.txt
+```
+
+@tab Option2
+```bash {2}
+# This is Option 2
+chmod +x ./file.txt
+```
+:::
+
Option1
# This is Option 1
+chmod 755 ./file.txt
+

 

Code Demo

https://vuepress-theme-hope.github.io/md-enhance/guide/demo/#open in new window

::: normal-demo Demo
+
+```html
+<h1>Mr.Hope</h1>
+<p>is <span id="very">very</span> handsome</p>
+```
+
+```js
+document.querySelector("#very").addEventListener("click", () => {
+  alert("Very handsome");
+});
+```
+
+```css
+span {
+  color: red;
+}
+```
+
+:::
+
Demo
<h1>Mr.Hope</h1>
+<p>is <span id="very">very</span> handsome</p>
+<button>hello</button>
+
document.querySelector("#very").addEventListener("click", () => {
+  alert("Very handsome");
+});
+
span {
+  color: red;
+}
+
+ + + diff --git a/00-Howto/03-Tips/Link.html b/00-Howto/03-Tips/Link.html new file mode 100644 index 0000000000..5f2264ed81 --- /dev/null +++ b/00-Howto/03-Tips/Link.html @@ -0,0 +1,50 @@ + + + + + + + + + + Link | docmoa + + + + + +
본문으로 건너뛰기

Link

1분 미만

Link

문서 작성시 외부 링크를 포함하는 예를 설명합니다. 참고 문서open in new window

텍스트에 링크 달기

설명하던 글의 특정 단어에 대해 외부 링크를 추가하고자 하는 경우 브라킷[ ]과 괄호를 사용합니다. domain을 같이 기입하는 경우 새창에서 열기로 표기됩니다.

새창으로 이동하는 [링크 달기](http://docmoa.github.io/00-Howto/03-Tips/Link.html)
+현재 창에서 이동하는 [링크 달기](/00-Howto/03-Tips/Link.html)
+

다음과 같이 표기됩니다.

링크 자체를 표기

별도의 연결된 단어 없이 링크 자체를 넣는 경우 < >를 사용합니다.

<https://docmoa.github.io>
+<docmoa@gmail.com>
+

다음과 같이 표기됩니다.

이미지

이미지는 !를 기존 링크 문법 앞에 추가합니다.

  • 대체 텍스트는 이미지 링크에 접속할 수 없는 경우 표기됩니다.
  • 이미지 설명은 마우스 오버시 확인하실 수 있습니다.
![대체 텍스트](이미지 링크 "이미지 설명")
+
+![](https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/layout-link-icon.png)
+![대체 텍스트](https://img.icons8.com/ios/2x/깨진링크)
+![대체 텍스트](https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/report-link-icon.png "이미지 설명")
+

다음과 같이 표기됩니다.

  • 대체 텍스트
    대체 텍스트
  • 대체 텍스트
    이미지 설명

동영상

외부 동영상은 html 문법을 활용하여 추가할 수 있습니다. 여기서는 유튜브를 예를 들어 설명합니다. 다른 여러가지 방식은 참고 링크open in new window를 확인해주세요.

  • 공유버튼을 누르고 퍼가기 를 클릭하면 우측에 동영상 퍼가기 코드가 나타납니다.
    Youtube Share 설명
  • 코드를 복사하고 그대로 파일 내에 붙여넣습니다.
<iframe width="560" height="315" src="https://www.youtube.com/embed/StTqXEQ2l-Y" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+

다음과 같이 표기됩니다.

+ + + diff --git a/00-Howto/03-Tips/PlantUML.html b/00-Howto/03-Tips/PlantUML.html new file mode 100644 index 0000000000..ebdc051287 --- /dev/null +++ b/00-Howto/03-Tips/PlantUML.html @@ -0,0 +1,239 @@ + + + + + + + + + + UML | docmoa + + + + + +
본문으로 건너뛰기

UML

약 3 분

UML

markdown-it-plantumlopen in new window 플러그인을 활성화 하여 UML 작성이 가능합니다. 아래는 플러그인 개발자의 안내를 풀어 일부 설명합니다.

기본 사용법

UML 블록은 @startuml@enduml 사이에 UML 구성을 위한 구성을 넣어 표기합니다. 아래와 같이 md 파일 내에 작성하면

@startuml
+Bob -> Alice : hello
+@enduml
+

다음과 같이 표기됩니다.

@startuml
Bob -> Alice : hello
@enduml

다양한 예제는 plantuml.comopen in new window에서 확인할 수 있습니다.

PlantUml 예제

Sample Terraform Action

@startuml
+actor User
+interface Terraform
+cloud CLOUD
+
+User ->> Terraform : Apply
+User <<- Terraform : State
+Terraform ->> CLOUD : Probisioning
+CLOUD ->> Terraform : Response
+@enduml
+

@startuml
actor User
interface Terraform
cloud CLOUD

User ->> Terraform : Apply
User <<- Terraform : State
Terraform ->> CLOUD : Probisioning
CLOUD ->> Terraform : Response
@enduml

Sequence Diagram

http://plantuml.com/sequence-diagramopen in new window

@startuml
+Alice -> Bob: Authentication Request
+Bob --> Alice: Authentication Response
+
+Alice -> Bob: Another authentication Request
+Alice <-- Bob: another authentication Response
+@enduml
+

@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response

Alice -> Bob: Another authentication Request
Alice <-- Bob: another authentication Response
@enduml

UseCase Diagram

http://plantuml.com/use-case-diagramopen in new window

@startuml
+:Main Admin: as Admin
+(Use the application) as (Use)
+
+User -> (Start)
+User --> (Use)
+
+Admin ---> (Use)
+
+note right of Admin : This is an example.
+
+note right of (Use)
+  A note can also
+  be on several lines
+end note
+
+note "This note is connected\nto several objects." as N2
+(Start) .. N2
+N2 .. (Use)
+@enduml
+

@startuml
:Main Admin: as Admin
(Use the application) as (Use)

User -> (Start)
User --> (Use)

Admin ---> (Use)

note right of Admin : This is an example.

note right of (Use)
A note can also
be on several lines
end note

note "This note is connected\nto several objects." as N2
(Start) .. N2
N2 .. (Use)
@enduml

Class Diagram

http://plantuml.com/class-diagramopen in new window

@startuml
+Object <|-- Dummy
+
+class Dummy {
+  String data
+  void methods()
+  -field1
+  #field2
+  ~method1()
+  +method2()
+}
+
+class Flight {
+   flightNumber : Integer
+   departureTime : Date
+}
+
+class Car
+
+Driver - Car : drives >
+Car *- Wheel : have 4 >
+Car -- Person : < owns
+@enduml
+

@startuml
Object <|-- Dummy

class Dummy {
String data
void methods()
-field1
#field2
~method1()
+method2()
}

class Flight {
flightNumber : Integer
departureTime : Date
}

class Car

Driver - Car : drives >
Car *- Wheel : have 4 >
Car -- Person : < owns
@enduml

Activity Diagram

http://plantuml.com/activity-diagram-betaopen in new window

@startuml
+start
+partition Initialization {
+    :read config file;
+    :init internal variable;
+}
+partition Running {
+    if (multiprocessor?) then (yes)
+    fork
+        :Treatment 1;
+    fork again
+        :Treatment 2;
+        detach
+    end fork
+    else (monoproc)
+    :Treatment 1;
+    :Treatment 2;
+    endif
+}
+
+stop
+@enduml
+

@startuml
start
partition Initialization {
:read config file;
:init internal variable;
}
partition Running {
if (multiprocessor?) then (yes)
fork
:Treatment 1;
fork again
:Treatment 2;
detach
end fork
else (monoproc)
:Treatment 1;
:Treatment 2;
endif
}

stop
@enduml

Component Diagram

http://plantuml.com/component-diagramopen in new window

@startuml
+package "Some Group" {
+  HTTP - [First Component]
+  [Another Component]
+}
+
+node "Other Groups" {
+  FTP - [Second Component]
+  [First Component] --> FTP
+}
+
+cloud {
+  [Example 1]
+}
+
+
+database "MySql" {
+  folder "This is my folder" {
+    [Folder 3]
+  }
+  frame "Foo" {
+    [Frame 4]
+  }
+}
+
+
+[Another Component] --> [Example 1]
+[Example 1] --> [Folder 3]
+[Folder 3] --> [Frame 4]
+@enduml
+

@startuml
package "Some Group" {
HTTP - [First Component]
[Another Component]
}

node "Other Groups" {
FTP - [Second Component]
[First Component] --> FTP
}

cloud {
[Example 1]
}

database "MySql" {
folder "This is my folder" {
[Folder 3]
}
frame "Foo" {
[Frame 4]
}
}

[Another Component] --> [Example 1]
[Example 1] --> [Folder 3]
[Folder 3] --> [Frame 4]
@enduml

State Diagram

http://plantuml.com/state-diagramopen in new window

@startuml
+[*] --> State1
+State1 --> [*]
+State1 : this is a string
+State1 : this is another string
+
+State1 -> State2
+State2 --> [*]
+
+scale 350 width
+[*] --> NotShooting
+
+state NotShooting {
+  [*] --> Idle
+  Idle --> Configuring : EvConfig
+  Configuring --> Idle : EvConfig
+}
+
+state Configuring {
+  [*] --> NewValueSelection
+  NewValueSelection --> NewValuePreview : EvNewValue
+  NewValuePreview --> NewValueSelection : EvNewValueRejected
+  NewValuePreview --> NewValueSelection : EvNewValueSaved
+
+  state NewValuePreview {
+     State1 -> State2
+  }
+}
+@enduml
+

@startuml
[] --> State1
State1 --> [
]
State1 : this is a string
State1 : this is another string

State1 -> State2
State2 --> [*]

scale 350 width
[*] --> NotShooting

state NotShooting {
[*] --> Idle
Idle --> Configuring : EvConfig
Configuring --> Idle : EvConfig
}

state Configuring {
[*] --> NewValueSelection
NewValueSelection --> NewValuePreview : EvNewValue
NewValuePreview --> NewValueSelection : EvNewValueRejected
NewValuePreview --> NewValueSelection : EvNewValueSaved

state NewValuePreview {
State1 -> State2
}
}
@enduml

Network Diagram

https://plantuml.com/nwdiagopen in new window

@startuml
+nwdiag {
+  network dmz {
+      address = "210.x.x.x/24"
+
+      // set multiple addresses (using comma)
+      web01 [address = "210.x.x.1, 210.x.x.20"];
+      web02 [address = "210.x.x.2"];
+  }
+  network internal {
+      address = "172.x.x.x/24";
+
+      web01 [address = "172.x.x.1"];
+      web02 [address = "172.x.x.2"];
+      db01;
+      db02;
+  }
+}
+@enduml
+

@startuml
nwdiag {
network dmz {
address = "210.x.x.x/24"

  // set multiple addresses (using comma)
+  web01 [address = "210.x.x.1, 210.x.x.20"];
+  web02 [address = "210.x.x.2"];
+

}
network internal {
address = "172.x.x.x/24";

  web01 [address = "172.x.x.1"];
+  web02 [address = "172.x.x.2"];
+  db01;
+  db02;
+

}
}
@enduml

Gantt Diagram

https://plantuml.com/gantt-diagramopen in new window

@startuml
+@startgantt
+[Prototype design] lasts 15 days
+[Test prototype] lasts 10 days
+-- All example --
+[Task 1 (1 day)] lasts 1 day
+[T2 (5 days)] lasts 5 days
+[T3 (1 week)] lasts 1 week
+[T4 (1 week and 4 days)] lasts 1 week and 4 days
+[T5 (2 weeks)] lasts 2 weeks
+@endgantt
+@enduml
+

@startuml
@startgantt
[Prototype design] lasts 15 days
[Test prototype] lasts 10 days
-- All example --
[Task 1 (1 day)] lasts 1 day
[T2 (5 days)] lasts 5 days
[T3 (1 week)] lasts 1 week
[T4 (1 week and 4 days)] lasts 1 week and 4 days
[T5 (2 weeks)] lasts 2 weeks
@endgantt
@enduml

MindMap

https://plantuml.com/mindmap-diagramopen in new window

@startuml
+@startmindmap
+* Debian
+** Ubuntu
+*** Linux Mint
+*** Kubuntu
+*** Lubuntu
+*** KDE Neon
+** LMDE
+** SolydXK
+** SteamOS
+** Raspbian with a very long name
+*** <s>Raspmbc</s> => OSMC
+*** <s>Raspyfi</s> => Volumio
+@endmindmap
+@enduml
+
+

@startuml
@startmindmap

  • Debian
    ** Ubuntu
    *** Linux Mint
    *** Kubuntu
    *** Lubuntu
    *** KDE Neon
    ** LMDE
    ** SolydXK
    ** SteamOS
    ** Raspbian with a very long name
    *** Raspmbc => OSMC
    *** Raspyfi => Volumio
    @endmindmap
    @enduml
+ + + diff --git a/00-Howto/03-Tips/Tabs.html b/00-Howto/03-Tips/Tabs.html new file mode 100644 index 0000000000..cf5b851c3f --- /dev/null +++ b/00-Howto/03-Tips/Tabs.html @@ -0,0 +1,78 @@ + + + + + + + + + + Tabs | docmoa + + + + + +
본문으로 건너뛰기

Tabs

1분 미만

Tabs

컨텐츠에 탭을 추가하여 상황에 따라 선택적으로 문서를 읽을 수 있도록 합니다.
상세 내용은 Markdown EnhanceTabsopen in new window, Code Tabsopen in new window 를 확인해보세요.

Tabs 기본 사용법

::: tabs
+@tab title
+__markdown content__
+
+@tab javascript
+``` javascript
+() => {
+  console.log('Javascript code example')
+}
+```
+:::
+

다음과 같이 표기됩니다.

title

markdown content

Code Tabs 기본 사용법

Code Tabs는 코드 블록만을 표기하는 탭을 제공합니다.

::: code-tabs#shell
+
+@tab pnpm
+
+```bash
+pnpm add -D vuepress-plugin-md-enhance
+```
+
+@tab yarn
+
+```bash
+yarn add -D vuepress-plugin-md-enhance
+```
+
+@tab:active npm
+
+```bash
+npm i -D vuepress-plugin-md-enhance
+```
+
+:::
+

다음과 같이 표기됩니다.

npm
npm i -D vuepress-plugin-md-enhance
+
+ + + diff --git a/00-Howto/03-Tips/TipBox.html b/00-Howto/03-Tips/TipBox.html new file mode 100644 index 0000000000..1618333b85 --- /dev/null +++ b/00-Howto/03-Tips/TipBox.html @@ -0,0 +1,66 @@ + + + + + + + + + + Tip Box | docmoa + + + + + +
본문으로 건너뛰기

Tip Box

1분 미만

Tip Box

문서 작성시 팁과 주의사항을 표기하는 방법을 설명합니다.
공식 문서open in new window

기본 사용법

::: tip
+This is a tip
+:::
+
+::: warning
+This is a warning
+:::
+
+::: danger
+This is a dangerous warning
+:::
+
+::: details
+This is a details block, which does not work in IE / Edge
+:::
+

다음과 같이 표기됩니다.

This is a tip

경고

This is a warning

위험

This is a dangerous warning

세부사항

This is a details block, which does not work in IE / Edge

응용

타입 우측에 타이틀명을 추가하여 기본 값을 변경합니다.

::: danger STOP
+Danger zone, do not proceed
+Go to [here](https://vuepress.vuejs.org/guide/markdown.html#:~:text=markdown.toc%20option.-,%23,-Custom%20Containers)
+:::
+
+::: details Click me to view the code
+```js
+console.log('Hello, VuePress!')
+```
+:::
+

STOP

Danger zone, do not proceed
Go to hereopen in new window

Click me to view the code
console.log('Hello, VuePress!')
+
+ + + diff --git a/00-Howto/03-Tips/index.html b/00-Howto/03-Tips/index.html new file mode 100644 index 0000000000..40045f83d9 --- /dev/null +++ b/00-Howto/03-Tips/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Tips | docmoa + + + + + +
본문으로 건너뛰기

Tips

1분 미만

+ + + diff --git a/00-Howto/index.html b/00-Howto/index.html new file mode 100644 index 0000000000..50543d584e --- /dev/null +++ b/00-Howto/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + How to "docmoa" | docmoa + + + + + +
본문으로 건너뛰기

How to "docmoa"

1분 미만

How to "docmoa"

문서를 올바르게 작성하고 공유하기 위한 몇가지 사항을 안내합니다.

안내

문서 기여 시 문서 작성 가이드를 꼭 한번 확인해주세요.

활용 가이드

docmoa를 활용할 수 있는 몇가지 가이드를 docmoa 활용 가이드 에서 설명합니다.

  • 로컬 환경에서 문서를 받아 자유롭게 사용 가능합니다.
  • 기여자 분들이 업데이트 하는 내용에 대해서는 내용 검토 후 git pageopen in new window에 반영 됩니다.

문서 작성 가이드

docmoa에 문서 기여를 위한 기본적인 몇가지 가이드를 설명합니다.

  • 문서작성 '시작' : docmoa에 문서를 추가하기위한 시작 단계를 설명합니다.
  • [Contribute]](/00-Howto/02-Guide/02-Contribute.html) : 작성된 문서를 개시될 git repo에 요청하는 단계 입니다.
  • Template : 문서의 기본 틀에 대해 설명 합니다.

문서 작성 팁

  • Code Block : 코드블록을 작성하기위한 설명과 몇가지 예제를 확인합니다.
  • Link : 문서 내에서 외부 링크, 이미지, 동영상을 표기하는 방법을 확인합니다.
  • PlantUML : UML, Gant, Mindmap 등의 도표를 작성하기 위한 PlantUML에 대한 기본적인 사용법을 가이드 합니다.
  • Tabs : 문서내에 동일 라인 상에 내용을 탭 형식으로 표현할 때 사용가능한 방법을 확인합니다.
  • Tip Box : 안내, 경고 등의 표기를 위한 팁박스 입니다.
+ + + diff --git a/01-Infrastructure/Container/container_runtime_sheet.html b/01-Infrastructure/Container/container_runtime_sheet.html new file mode 100644 index 0000000000..0c3a1b76bc --- /dev/null +++ b/01-Infrastructure/Container/container_runtime_sheet.html @@ -0,0 +1,40 @@ + + + + + + + + + + Container Runtimes 비교 표 | docmoa + + + + + +
본문으로 건너뛰기

Container Runtimes 비교 표

1분 미만containerdockerpodman

Container Runtimes 비교 표

update : 2021. 12. 23.

CRI-OContainerd CRI pluginDocker EnginegVisor CRI pluginCRI-O Kata Containers
sponsorsCNCFCNCFDocker IncGoogleIntel
started20162015Mar 201320152017
version1.231.1920.10release-20211129.01.13
runtimerunc (default)containerd managing runcruncrunsckata-runtime
kernelsharedsharedsharedpartially sharedisolated
syscall filteringnononoyesno
kernel blobsnonononoyes
footprint----30mb
start time<10ms<10ms<10ms<10ms<100ms
io performancehost performancehost performancehost performanceslowhost performance
network performancehost performancehost performancehost performanceslow (see comment)close to host performance
Docshttps://github.com/kubernetes-sigs/cri-o/open in new windowhttps://github.com/containerd/criopen in new windowhttps://github.com/moby/mobyopen in new windowhttps://github.com/google/gvisoropen in new windowhttps://github.com/kata-containers/runtimeopen in new window
장점경량의 쿠버네티스 전용 Docker 데몬이 필요하지 않음 OpenShift의 기본 컨테이너 런타임 아마도 최고의 컨테이너 기본 런타임최신 Docker Engine과 함께 기본적으로 설치됨 Kubernetes는 ContainerD를 직접 사용할 수 있으며, Docker또한 동일한 호스트에서 직접 사용할 수도 있음 DockerD 데몬을 실행할 필요가 없음방대한 수의 사용자가 테스트하고 반복 한 가장 성숙한 런타임 seccomp, SELinux 및 AppArmor를 사용하여 강화할 수 있음 가장 빠른 시작 시간 메모리 사용량이 가장 적음gcloud appengine에서 고객 간의 격리 계층으로 사용함 상태를 저장하지 않는 웹 앱에 적합 표준 컨테이너에 두 개의 보안 계층을 추가함아마도 가장 안전한 옵션 보안에 대한 주요 절충안으로 오버헤드가 발생하는것은 그렇게 나쁘지 않은 것으로 보임
단점Docker Engine이 같고 있는 동일한 보안 이슈를 가지고 있음 보안정책을 별도로 관리해야 함This is slightly newer as it has been through a few iterations of being installed differently.Kubernetes는 CRI 플러그인 아키텍처로 이동하고 있음 보안을 강화하고 관리하는것은 너무 복잡함버전이 지정되지 않았으며 아직 Kubernetes에서 프로덕션에 사용해서는 안됨 많은 syscall을 만드는 응용 프로그램에는 적합하지 않음 400 개 Linux syscall이 모두 구현되어 일부 앱이 작동하지 않을 수 있음 (예 : postgres).kata-runtime 자체는 v1이지만 이것이 Kubernetes 상에서 어떻게 준비 되어 있는지 확인이 필요 30MB 메모리 오버 헤드로 인한 비효율적 패킹 시작 시간
+ + + diff --git a/01-Infrastructure/Container/index.html b/01-Infrastructure/Container/index.html new file mode 100644 index 0000000000..03fea1c3aa --- /dev/null +++ b/01-Infrastructure/Container/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Container | docmoa + + + + + +
본문으로 건너뛰기

Container

1분 미만

+ + + diff --git a/01-Infrastructure/Container/rancher-desktop-disk-resize-mac.html b/01-Infrastructure/Container/rancher-desktop-disk-resize-mac.html new file mode 100644 index 0000000000..c359732233 --- /dev/null +++ b/01-Infrastructure/Container/rancher-desktop-disk-resize-mac.html @@ -0,0 +1,52 @@ + + + + + + + + + + Rancher Desktop Disk Resize on MAC | docmoa + + + + + +
본문으로 건너뛰기

Rancher Desktop Disk Resize on MAC

1분 미만rancherdockermac

Rancher Desktop Disk Resize on MAC

Private docker registry
Rancher Desktop
MacOS
Src : https://slack-archive.rancher.com/t/8508077/on-my-m1-mac-i-ve-started-getting-this-error-and-it-wont-go-#3e8d178c-aee8-46e6-b4cc-094c2339cbaaopen in new window

issue

$ docker run abc
+f631eb8a0078: Already exists
+d04f82e55126: Already exists
+0b1212f566e8: Already exists
+8e7d076cd7f0: Already exists
+62aa9a741295: Already exists
+f3e65750a6be: Extracting  22.02GB/22.02GB
+docker: failed to register layer: Error processing tar file(exit status 1): write /0000.dat: no space left on device.
+See 'docker run --help'.
+

위험

Check the Rancher desktop stopped

Resize

$ /Applications/Rancher\ Desktop.app/Contents/Resources/resources/darwin/lima/bin/qemu-img resize $HOME/Library/Application\ Support/rancher-desktop/lima/0/diffdisk +10G
+
+Image resized.
+
+ + + diff --git a/01-Infrastructure/Container/rancher-desktop-insecure-setup-mac.html b/01-Infrastructure/Container/rancher-desktop-insecure-setup-mac.html new file mode 100644 index 0000000000..dc3f3379fb --- /dev/null +++ b/01-Infrastructure/Container/rancher-desktop-insecure-setup-mac.html @@ -0,0 +1,67 @@ + + + + + + + + + + Rancher Desktop Insecure Setup on MAC | docmoa + + + + + +
본문으로 건너뛰기

Rancher Desktop Insecure Setup on MAC

1분 미만rancherdockermac

Rancher Desktop Insecure Setup on MAC

Private docker registry
Rancher Desktop
MacOS
Setup insecure-registries

issue

$ docker push 192.168.60.11:5000/example-python:1.0
+Error response from daemon: Get https://192.168.60.11:5000/v1/example-python: http: server gave HTTP response to HTTPS client
+

Edit docker.json within Rancher desktop

MAC-user-terminal $ LIMA_HOME="$HOME/Library/Application Support/rancher-desktop/lima" "/Applications/Rancher Desktop.app/Contents/Resources/resources/darwin/lima/bin/limactl" shell 0
+
+lima-rancher-desktop $ sudo vi /etc/conf.d/docker
+
...
+# DOCKER_OPTS="" to
+DOCKER_OPTS="--insecure-registry=192.168.60.11:5000"
+

Restart docker service

lima-rancher-desktop $ sudo service docker restart
+ * Caching service dependencies .. [ ok ]
+ * Stopping Docker Daemon ..       [ ok ]
+ * Starting Docker Daemon ...      [ ok ]
+
+lima-rancher-desktop $ exit
+

Try Push/Pull

$ docker push 192.168.60.11:5000/example-python:1.0
+The push refers to repository [192.168.60.11:5000/example-python]
+259faf3d45f5: Pushed
+d25777b53a01: Pushed
+b28e272ad893: Pushed
+517c4790d67b: Pushed
+40dc2004f48f: Pushed
+df63783f60fd: Pushed
+7e1c5e1a1242: Pushed
+b4440cc8e4ff: Pushed
+26a1b0864fd3: Pushed
+867d0767a47c: Pushed
+1.0: digest: sha256:dc8ebd032c0c1cd8fd991926eaea5a9d8f3cf66286c048e3bac6336969c524b5 size: 2414
+
+ + + diff --git a/01-Infrastructure/Linux/TroubleShooting/Oom_killer.html b/01-Infrastructure/Linux/TroubleShooting/Oom_killer.html new file mode 100644 index 0000000000..9d7f2aec56 --- /dev/null +++ b/01-Infrastructure/Linux/TroubleShooting/Oom_killer.html @@ -0,0 +1,40 @@ + + + + + + + + + + OOM Killer가 일하는 방식 | docmoa + + + + + +
본문으로 건너뛰기

OOM Killer가 일하는 방식

1분 미만linuxoomoom_killer

OOM Killer가 일하는 방식

OOM Killer의 주요 업무는 다음 두 가지입니다.

  • 실행 중인 모든 프로세스를 살펴보며 각 프로세스의 메모리 사용량에 따라 OOM 점수를 산출합니다.
  • OS에서 메모리가 더 필요하면 점수가 가장 높은 프로세스를 종료시킵니다.

각 프로세스의 oom_score 관련 정보는 /proc/(pid) 디렉토리 하위에서 찾을 수 있습니다.

  • oom_adj (oom_adjust)
  • oom_score_adj
  • oom_score

oom_killer는 점수를 나타내는 oom_score만 있으면 임무를 완수할 수 있습니다. 그렇다면 oom_adj와 oom_score_adj의 역할은 무엇일까요? man proc을 이용해 확인해 보겠습니다.

  • /proc/(pid)/oom_adj (Linux 2.6.11 이후): 점수를 조정하는 데 사용합니다. 일반적으로 -16에서 +15 사이의 값을 갖고, 특수 값 -17이 적용된 프로세스는 OOM killer 대상에서 제외됩니다. Linux 2.6.36 이후엔 /proc/(pid)/oom_score_adj로 대체됐고 더 이상 이 파일을 사용하지 않습니다.
  • /proc/(pid)/oom_score (Linux 2.6.11 이후): 현재 프로세스의 OOM 점수를 나타냅니다. 점수가 높을수록 OOM Killer의 대상이 될 확률이 높아집니다.
  • /proc/(pid)/oom_score_adj (Linux 2.6.36 이후): OOM 상황에서 어떤 프로세스를 종료할지 선택하는 기준이 되는 ‘badness 값’을 조정하는 데 사용합니다. -1000(oom_score_adj_MIN)에서 +1000(oom_score_adj_MAX) 사이의 값을 갖습니다. 이전 커널과의 하위 호환성을 위해 /proc/(pid)/oom_adj으로도 점수를 조정할 수 있으며 이때는 oom_score_adj에 비례해 값이 정해집니다. /proc/(pid)/oom_score_adj나 /proc/(pid)/oom_adj 중 하나의 값을 변경하면 다른 하나의 값도 비례하여 변경됩니다.

위의 설명에 따르면 OOM Killer가 유일하게 의존하는 변수는 oom_score이고, oom_adj 또는 oom_score_adj을 이용해 그 값을 조정할 수 있습니다. 현재 사용하고 있는 커널 버전은 kernel-3.10.0-957.el7입니다. 리눅스 저장소에서 버전에 맞는 커널 소스 코드를 찾았습니다.

+ + + diff --git a/01-Infrastructure/Linux/TroubleShooting/SSH Too many authentication failures.html b/01-Infrastructure/Linux/TroubleShooting/SSH Too many authentication failures.html new file mode 100644 index 0000000000..f1a282ae40 --- /dev/null +++ b/01-Infrastructure/Linux/TroubleShooting/SSH Too many authentication failures.html @@ -0,0 +1,53 @@ + + + + + + + + + + SSH Too many authentication failures | docmoa + + + + + +
본문으로 건너뛰기

SSH Too many authentication failures

1분 미만linuxssh

SSH Too many authentication failures

직역하자면 너무많은 인증 실패로 인한 SSH 접속이 안된다. 는 메시지를 간혹 보게되는 경우가 있다.

$ ssh myserver
+Received disconnect from 192.168.0.43 port 22:2: Too many authentication failures
+Connection to 192.168.0.43 closed by remote host.
+Connection to 192.168.0.43 closed.
+

특히나 클라우드나 VM을 새로 프로비저닝 해서 사용하려고 할때 IP가 중복되어 재사용되어야 하는 경우에 주로 발생하는 걸로 추측된다.

위 메시지의 발생 원인은 이미 SSH로 접속하려고 하는 클라이언트 환경에 많은 SSH ID 정보가 저장되어있고, SSH Client를 실행할 때 ssh-agent로 이미 알고있는 모든 SSH 키와 다른 모든 키에 대해 접속을 시도하게 된다. 이때 SSH로 접속하고자 하는 원격 서버는 특정 ID 키로 맵핑되어있고, 기존의 키 정보와 맞지 않거나 동일한 대상에 대한 SSH ID 정보와 달라진 것이 원인으로 확인된다.

해결 방법 1

접속하고자 하는 Client 환경에서 SSH 키를 초기화 하는 방법

$ ssh-add -D
+

위와 같이 했을 때 Could not open a connection to your authentication agent. 와 같은 오류가 발생한다면 다음 방법으로 초기화 한다.

$ exec ssh-agent bash
+$ ssh-add -D
+All identities removed.
+

해결 방법 2

SSH 옵션으로 Public Key를 이용한 접속을 일시적으로 사용하지 않도록 하는 방법

$ ssh -p 22 -o PubkeyAuthentication=no username@myserver
+

해결 방법 3

~/.ssh/config의 대상 호스트에 IdentitiesOnly=yes를 추가하는 벙법
많은 ID를 제공 하더라도 ssh가 ssh_config 파일에 구성된 인증 ID 파일만 사용하도록 지정한다고 함

Host myserver
+IdentityFile ~/.ssh/key_rsa
+IdentitiesOnly yes
+Port 22
+


 

+ + + diff --git a/01-Infrastructure/Linux/TroubleShooting/docker_bridge_netstat.html b/01-Infrastructure/Linux/TroubleShooting/docker_bridge_netstat.html new file mode 100644 index 0000000000..a3b867530e --- /dev/null +++ b/01-Infrastructure/Linux/TroubleShooting/docker_bridge_netstat.html @@ -0,0 +1,89 @@ + + + + + + + + + + docker나 nomad를 이용하여 bridge모드로 기동된 컨테이너의 port 체크 | docmoa + + + + + +
본문으로 건너뛰기

docker나 nomad를 이용하여 bridge모드로 기동된 컨테이너의 port 체크

약 1 분linuxdockerbridgenetstat

docker나 nomad를 이용하여 bridge모드로 기동된 컨테이너의 port 체크

  • 단순 netstat만으로 bridge모드로 기동된 docker의 port를 체크할 수 없다
  • 그래서 아래와 같은 절차가 필요하다.

먼저 찾으려는 컨테이너의 port를 확인한다. (nomad로 배포되어 있는 컨테이너임)

nomad alloc status d78d5b32
+ID                  = d78d5b32-00c3-5468-284a-8c201058c53a
+Eval ID             = c6c9a1d9
+Name                = 08_grafana.08_grafana[0]
+Node ID             = e11b7729
+Node Name           = slave1
+Job ID              = 08_grafana
+Job Version         = 0
+Client Status       = running
+Client Description  = Tasks are running
+Desired Status      = run
+Desired Description = <none>
+Created             = 18h42m ago
+Modified            = 2h36m ago
+
+Allocation Addresses (mode = "bridge")
+Label                   Dynamic  Address
+*http                   yes      10.0.0.161:25546
+*connect-proxy-grafana  yes      10.0.0.161:29382 -> 29382
+

먼저 일반적으로 사용하는 netstat에는 grafana 컨테이너의 http port인 25546이 확인되지 않는다.

$ netstat -ntlp
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
+tcp        0      0 127.0.0.1:8502          0.0.0.0:*               LISTEN      780/consul
+tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      813/sshd: /usr/sbin
+tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN      2354/haproxy
+tcp        0      0 127.0.0.1:8600          0.0.0.0:*               LISTEN      780/consul
+tcp        0      0 10.0.0.161:24185        0.0.0.0:*               LISTEN      22324/docker-proxy
+tcp        0      0 10.0.0.161:9090         0.0.0.0:*               LISTEN      2496/docker-proxy
+tcp        0      0 172.30.1.112:8301       0.0.0.0:*               LISTEN      780/consul
+tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      1/init
+tcp        0      0 0.0.0.0:1936            0.0.0.0:*               LISTEN      2354/haproxy
+tcp        0      0 127.0.0.1:8500          0.0.0.0:*               LISTEN      780/consul
+tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      33902/systemd-resol
+tcp6       0      0 :::22                   :::*                    LISTEN      813/sshd: /usr/sbin
+tcp6       0      0 :::4646                 :::*                    LISTEN      9827/nomad
+tcp6       0      0 :::111                  :::*                    LISTEN      1/init
+tcp6       0      0 :::6100                 :::*                    LISTEN      22827/java
+

기동 중인 docker에서 inspect로 pid를 검색해오고 그 정보로 netstat를 다시하면 이제 LISTEN된 정보를 얻어올 수 있다.

$ docker ps | grep grafana
+0371b4c5f500   grafana/grafana                            "/run.sh"                50 minutes ago   Up 50 minutes                                                            grafana-d78d5b32-00c3-5468-284a-8c201058c53a
+62a46e08d426   envoyproxy/envoy:v1.20.0                   "/docker-entrypoint.…"   50 minutes ago   Up 50 minutes                                                            connect-proxy-grafana-d78d5b32-00c3-5468-284a-8c201058c53a
+
+$ docker inspect -f '{{.State.Pid}}' 0371b4c5f500
+22741
+
+$ nsenter -t 22741 -n netstat -ntl
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address           Foreign Address         State
+tcp        0      0 127.0.0.2:19001         0.0.0.0:*               LISTEN
+tcp6       0      0 :::25546                :::*                    LISTEN
+

* 위 결과로 알 수 있는 것은 가상 네트워크에 물려 기동되는 컨테이너의 LISTEN정보를 알기위해선 조금의 과정이 필요함

+ + + diff --git a/01-Infrastructure/Linux/TroubleShooting/index.html b/01-Infrastructure/Linux/TroubleShooting/index.html new file mode 100644 index 0000000000..82146a1598 --- /dev/null +++ b/01-Infrastructure/Linux/TroubleShooting/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Trouble Shooting | docmoa + + + + + +
본문으로 건너뛰기

Trouble Shooting

1분 미만

+ + + diff --git a/01-Infrastructure/Linux/index.html b/01-Infrastructure/Linux/index.html new file mode 100644 index 0000000000..ade5072878 --- /dev/null +++ b/01-Infrastructure/Linux/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Linux | docmoa + + + + + +
본문으로 건너뛰기

Linux

1분 미만

+ + + diff --git a/01-Infrastructure/index.html b/01-Infrastructure/index.html new file mode 100644 index 0000000000..edbbc357f3 --- /dev/null +++ b/01-Infrastructure/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Infrastructure | docmoa + + + + + +
본문으로 건너뛰기

Infrastructure

1분 미만Infrastructure

+ + + diff --git a/02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.html b/02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.html new file mode 100644 index 0000000000..026d7f8084 --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.html @@ -0,0 +1,42 @@ + + + + + + + + + + kubernetes 스케쥴러 알고리즘 | docmoa + + + + + +
본문으로 건너뛰기

kubernetes 스케쥴러 알고리즘

1분 미만kubernetesscheduler알고리즘

kubernetes 스케쥴러 알고리즘

노드 필터링

  • 노드를 필터링하는 목적은 포드의 특정 요구 사항을 충족하지 않는 노드를 필터링하는 것입니다. 예를 들어 노드의 사용 가능한 리소스 (노드에서 이미 실행 된 모든 Pod의 리소스 요청 합계를 뺀 용량으로 측정)가 Pod의 필수 리소스보다 적은 경우 순위에서 노드를 고려해서는 안됩니다. 단계로 필터링됩니다. 현재 다음을 포함하여 서로 다른 필터링 정책을 구현하는 여러 "술어"가 있습니다.

    • NoDiskConflict: 포드가 요청한 볼륨과 이미 마운트 된 볼륨으로 인해 포드가 맞을 수 있는지 평가합니다. 현재 지원되는 볼륨은 AWS EBS, GCE PD, ISCSI 및 Ceph RBD입니다. 지원되는 유형에 대한 영구 볼륨 클레임 만 확인됩니다. 포드에 직접 추가 된 영구 볼륨은 평가되지 않으며이 정책에 의해 제한되지 않습니다.
    • NoVolumeZoneConflict: 영역 제한을 고려하여 포드가 요청하는 볼륨을 노드에서 사용할 수 있는지 평가합니다.
    • PodFitsResources: 여유 리소스 (CPU 및 메모리)가 Pod 요구 사항을 충족하는지 확인합니다. 여유 리소스는 용량에서 노드에있는 모든 포드의 요청 합계를 뺀 값으로 측정됩니다. Kubernetes의 리소스 QoS에 대해 자세히 알아 보려면 QoS 제안을 확인하세요 .
    • PodFitsHostPorts: Pod에 필요한 HostPort가 이미 노드에서 사용 중인지 확인합니다.
    • HostName: PodSpec의 NodeName 필드에 지정된 노드를 제외한 모든 노드를 필터링합니다.
    • MatchNodeSelector: 노드의 라벨이 Pod의 nodeSelector필드에 지정된 라벨 과 일치 nodeAffinity하는지, Kubernetes v1.2부터 존재 하는 경우 에도 일치 하는지 확인합니다. 둘 다에 대한 자세한 내용 은 여기 를 참조 하십시오 .
    • MaxEBSVolumeCount: 연결된 ElasticBlockStore 볼륨의 수가 최대 값을 초과하지 않는지 확인합니다 (Amazon은 루트 볼륨 용으로 예약 된 40 개 중 하나를 사용하여 최대 40 개를 권장하므로 기본적으로 39 개입니다 . Amazon 설명서 참조 ). 최대 값은 KUBE_MAX_PD_VOLS환경 변수 를 설정하여 제어 할 수 있습니다 .
    • MaxGCEPDVolumeCount: 연결된 GCE PersistentDisk 볼륨의 수가 최대 값을 초과하지 않는지 확인합니다 (기본적으로 GCE에서 허용하는 최대 값 인 16 ). GCE 설명서 참조 최대 값은 KUBE_MAX_PD_VOLS환경 변수 를 설정하여 제어 할 수 있습니다 .
    • CheckNodeMemoryPressure: 메모리 부족 상태를보고하는 노드에서 포드를 예약 할 수 있는지 확인합니다. 현재 BestEffortkubelet에 의해 자동으로 제거되므로 메모리 부족 상태에서 노드에 포드를 배치해서는 안됩니다.
    • CheckNodeDiskPressure: 디스크 압력 상태를보고하는 노드에서 포드를 예약 할 수 있는지 확인합니다. 현재 kubelet에 의해
      자동으로 제거되므로 디스크 압력이있는 노드에 포드를 배치해서는 안됩니다.
      위 술어의 세부 사항은 pkg / scheduler / algorithm / predicates / predicates.go 에서 찾을 수 있습니다 . 위에서 언급 한 모든 술어를 조합하여 정교한 필터링 정책을 수행 할 수 있습니다. Kubernetes는 기본적으로 이러한 술어 중 일부만 사용합니다. pkg / scheduler / algorithmprovider / defaults / defaults.go 에서 기본적으로 사용되는 항목을 확인할 수 있습니다 .

노드 순위 지정

  • 필터링 된 노드는 Pod를 호스팅하는 데 적합한 것으로 간주되며 두 개 이상의 노드가 남아있는 경우가 많습니다. Kubernetes는 나머지 노드의 우선 순위를 지정하여 포드에 가장 적합한 노드를 찾습니다. 우선 순위 지정은 일련의 우선 순위 기능에 의해 수행됩니다. 나머지 각 노드에 대해 우선 순위 함수는 "가장 선호"를 나타내는 10과 "최저 선호"를 나타내는 0으로 0에서 10까지의 점수를 제공합니다. 각 우선 순위 함수는 양수로 가중치가 부여되고 각 노드의 최종 점수는 모든 가중치를 합산하여 계산됩니다. 예를 들어, 두 개의 우선 순위 기능이 있습니다 가정 priorityFunc1및 priorityFunc2가중 요인 weight1과 weight2각각은, 일부 NodeA에의 최종 점수는 다음과 같습니다
finalScoreNodeA = (weight1 * priorityFunc1) + (weight2 * priorityFunc2)
+
+
  • 모든 노드의 점수가 계산 된 후 점수가 가장 높은 노드가 포드의 호스트로 선택됩니다. 동일한 최고 점수를 가진 노드가 두 개 이상있는 경우 그중에서 임의의 노드가 선택됩니다.

  • 현재 Kubernetes 스케줄러는 다음과 같은 실용적인 우선 순위 기능을 제공합니다.

    • LeastRequestedPriority: 노드는 새 Pod가 노드에 예약 된 경우 무료가 될 노드 비율을 기준으로 우선 순위가 지정됩니다. (즉, (용량-노드에 이미있는 모든 포드의 요청 합계-예약중인 포드의 요청) / 용량). CPU와 메모리의 가중치는 동일합니다. 자유 비율이 가장 높은 노드가 가장 선호됩니다. 이 우선 순위 함수는 리소스 소비와 관련하여 노드에 Pod를 분산시키는 효과가 있습니다.
    • BalancedResourceAllocation:이 우선 순위 함수는 Pod가 배포 된 후 CPU 및 메모리 사용률이 균형을 이루도록 노드에 Pod를 배치하려고합니다.
    • SelectorSpreadPriority: 동일한 노드에서 동일한 서비스, 복제 컨트롤러 또는 복제본 세트에 속하는 Pod 수를 최소화하여 Pod를 분산합니다. 노드에 영역 정보가있는 경우 포드가 영역과 노드에 분산되도록 우선 순위가 조정됩니다.
    • CalculateAntiAffinityPriority: 특정 라벨에 대해 동일한 값을 가진 노드에서 동일한 서비스에 속하는 Pod 수를 최소화하여 Pod를 분산합니다.
    • ImageLocalityPriority: 포드에서 요청한 이미지의 위치에 따라 노드의 우선 순위가 지정됩니다. 팟 (Pod)에 필요한 이미 설치된 패키지의 크기가 더 큰 노드는 팟 (Pod)에 필요한 이미 설치된 패키지가없는 노드 또는 팟 (Pod)에 필요한 이미 설치된 패키지의 작은 총 크기보다 선호됩니다.
    • NodeAffinityPriority: (Kubernetes v1.2) preferredDuringSchedulingIgnoredDuringExecution노드 선호도를 구현 합니다. 자세한 내용 은 여기 를 참조하십시오.
      위의 우선 순위 기능에 대한 자세한 내용은 pkg / scheduler / algorithm / priorities 에서 찾을 수 있습니다 . Kubernetes는 기본적으로 이러한 우선 순위 기능 중 일부를 사용하지만 전부는 아닙니다. pkg / scheduler / algorithmprovider / defaults / defaults.go 에서 기본적으로 사용되는 항목을 확인할 수 있습니다 . 술어와 유사하게, 위의 우선 순위 기능을 결합하고 원하는대로 가중치 요소 (양수)를 할당 할 수 있습니다 ( 사용자 정의 방법은 scheduler.mdopen in new window 확인 ).
+ + + diff --git a/02-PrivatePlatform/Kubernetes/01-Information/index.html b/02-PrivatePlatform/Kubernetes/01-Information/index.html new file mode 100644 index 0000000000..2d5ee39d50 --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/01-Information/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Information | docmoa + + + + + +
본문으로 건너뛰기

01 Information

1분 미만

+ + + diff --git a/02-PrivatePlatform/Kubernetes/02-Config/index.html b/02-PrivatePlatform/Kubernetes/02-Config/index.html new file mode 100644 index 0000000000..1185652acc --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/02-Config/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Config | docmoa + + + + + +
본문으로 건너뛰기

02 Config

1분 미만

+ + + diff --git a/02-PrivatePlatform/Kubernetes/02-Config/kubernetes_with_out_docker.html b/02-PrivatePlatform/Kubernetes/02-Config/kubernetes_with_out_docker.html new file mode 100644 index 0000000000..3a5f8ef8ce --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/02-Config/kubernetes_with_out_docker.html @@ -0,0 +1,117 @@ + + + + + + + + + + containerd를 런타임으로 사용한 Kubernetes 설치 | docmoa + + + + + +
본문으로 건너뛰기

containerd를 런타임으로 사용한 Kubernetes 설치

1분 미만kubernetesdocker아님containerd

containerd를 런타임으로 사용한 Kubernetes 설치

  • docker가 없어도 k8s를 올릴 수 있다!
# 먼저 설치하여 환경파일을 가져오고 원하는 버전을 설치한다.
+sudo apt-get install containerd -y
+
+sudo mkdir -p /etc/containerd
+
+containerd config default | sudo tee /etc/containerd/config.toml
+
+sudo systemctl stop containerd
+
+curl -LO https://github.com/containerd/containerd/releases/download/v1.4.4/containerd-1.4.4-linux-amd64.tar.gz
+
+tar xvf containerd-1.4.4-linux-amd64.tar.gz
+
+rm containerd-1.4.4-linux-amd64.tar.gz
+
+sudo cp bin/* /usr/bin/
+
+sudo systemctl start containerd
+
+rm -rf bin
+
+sudo systemctl status containerd --lines 1
+
+# k8s 설치시작
+curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
+
+sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
+
+sudo apt-get install kubeadm kubelet kubectl -y
+
+sudo apt-mark hold kubeadm kubelet kubectl containerd
+
+#echo 'net.bridge.bridge-nf-call-iptables = 1' | sudo tee -a /etc/sysctl.conf
+
+SOURCE_FILE="/etc/sysctl.conf"
+LINE_INPUT="net.bridge.bridge-nf-call-iptables = 1"
+
+grep -qF "$LINE_INPUT" "$SOURCE_FILE"  || echo "$LINE_INPUT" | sudo tee -a "$SOURCE_FILE"
+
+sudo echo '1' | sudo tee /proc/sys/net/ipv4/ip_forward
+
+cat /proc/sys/net/ipv4/ip_forward
+
+sudo sysctl --system
+
+sudo modprobe overlay
+sudo modprobe br_netfilter
+
+sudo swapoff -a
+
+sudo sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab
+
+cat /etc/fstab
+

후속작업

# k8s master server 설정
+sudo kubeadm config images pull
+
+IP_ADDR=`hostname -I | awk '{print $1}'`
+
+sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=${IP_ADDR}
+
+mkdir -p $HOME/.kube
+
+sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
+
+sudo chown $(id -u):$(id -g) $HOME/.kube/config
+
+# cni 설치(weave)
+kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"
+
+#kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
+
+sudo cp ./crictl.yaml /etc/crictl.yaml
+
+sudo crictl images
+
+watch -n 5 "kubectl get nodes"
+
+
+ + + diff --git a/02-PrivatePlatform/Kubernetes/02-Config/vagrant_k8s.html b/02-PrivatePlatform/Kubernetes/02-Config/vagrant_k8s.html new file mode 100644 index 0000000000..302e98d07f --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/02-Config/vagrant_k8s.html @@ -0,0 +1,152 @@ + + + + + + + + + + Kubernetes, Vagrant로 로컬 환경 구성 | docmoa + + + + + +
본문으로 건너뛰기

Kubernetes, Vagrant로 로컬 환경 구성

약 2 분kubernetesvagrantdockerinstall

Kubernetes, Vagrant로 로컬 환경 구성

github : https://github.com/Great-Stone/vagrant-k8sopen in new window

  • Virtualbox를 활용하여 로컬 환경에서 Kubernetes(K8s) 환경을 쉽게 만들고 부실수(?) 있는 환경을 구성하기위해 Vagrant를 활용
  • Intel Mac (Catalina / Big Sur)에서 테스트
  • Windows는 테스트 필요

실행 전 필요 소프트웨어

Virtualbox 네트워크 구성

  • K8s vm이 사용하기 위한 네트워크를 추가하여 구성
  • 기존 네트워크를 사용하고 싶다면 vagrantfile*.vm.network 부분의 ip에 수정 필요
    • vagrantfile 구성시 해당 ip 설정 부분을 상단의 NETWORK_SUB 부분에 정의함
    • 네트워크 구성 설정에 따라 클러스터 간 통신이 안될 수 있음에 주의
    • vagrantfileSTART_IP를 활용하여 마스터 노드 및 워커 노드의 ip를 부여하는 방식으로 구성되었으나 변경 가능

구성 설명

디렉토리/파일 구조

실행 전

디렉토리/파일 구조

.
+├── `1.18`
+│   ├── files
+│   │   └── `pv.yaml`
+│   ├── scripts
+│   │   ├── `kube.sh`
+│   │   └── `pv.sh`
+│   └── `vagrantfile`
+├── 1.19
+<반복>
+
  • 버전별로 디렉토리가 분류되어있음
    • 1.18~1.20 은 ubuntu 18.04 LTS 기반
    • 1.21~1.23 은 utuntu 20.04 LTS 기반
  • vagrantfile
    • vagrant 실행 정의
    • version2 사용
  • kube.shopen in new window
    • vagrantfile에서 provision으로 호출
    • K8s 설치에 필요한 패키지 설치 및 실행
  • pv.shopen in new window, pv.yaml
    • vagrantfile에서 provision으로 호출
    • pv.sh는 K8s master 노드 구성 후 디렉토리 생성 후 pv.yaml을 통해 pv 구성

실행 후

실행 후

├── 1.18
+│   ├── `.kube`
+│   ├── `.vagrant`
+│   ├── files
+│   │   └── pv.yaml
+│   ├── `join.sh`
+│   ├── `k8s-pv`
+│   │   ├── pv01
+│   │   ├── pv02
+│   │   └── pv03
+│   ├── scripts
+│   │   ├── kube.sh
+│   │   └── pv.sh
+│   └── vagrantfile
+├── 1.19
+<반복>
+
  • .kube 디렉토리 : kubernetes credential 및 접속 정보 생성
  • .vagrant 디렉토리 : vagrant 실행 후 vm 정보 업데이트
  • join.shopen in new window : 워커노드의 클러스터 조인을 위한 스크립트
  • k8s-pv 디렉토리 : pv를 위한 디렉토리 구성

vagrantfile - variable

IMAGE_NAME = "bento/ubuntu-20.04"
+
+K8S_MINOR_VERSION = "21"
+NETWORK_SUB = "192.168.60."
+START_IP = 130
+POD_CIDR = "10.#{K8S_MINOR_VERSION}.0.0/16"
+
+cluster = {
+  "master" => { :cpus => 2, :mem => 2048 },
+  "node" => { :cpus => 1, :mem => 1024 }
+}
+
+NODE_COUNT = 1
+
+VM_GROUP_NAME = "k8s-1.#{K8S_MINOR_VERSION}"
+DOCKER_VER = "5:20.10.12~3-0~ubuntu-focal"
+KUBE_VER = "1.#{K8S_MINOR_VERSION}.8-00"
+
Variable namevalue
IMAGE_NAMEvagrant에서 사용할 기본 이미지로 vagrant cloud 참조
K8S_MINOR_VERSIONK8s 설치의 마이너 버전 지정
NETWORK_SUBVirtualbox network ip
START_IP각 K8s 클러스터의 master에 할당되며 워커노드는 +1 씩 증가
POD_CIDRkubeadm init 의 --pod-network-cidr 에 지정되는 CIDR
cluster={}클러스터 리소스를 정의한 오브젝트 형태의 변수
NODE_COUNT워커 노드 개수
VM_GROUP_NAMEVirtualbox에 등록될 그룹 이름
DOCKER_VERdocker-ce 버전
KUBE_VERK8s 관련 패키지 버전

vagrantfile - vm

Vagrant.configure("2") do |config|
+  config.vm.box = IMAGE_NAME
+
+  config.vm.define "master", primary: true do |master|
+    master.vm.box = IMAGE_NAME
+    master.vm.network "private_network", ip: "#{NETWORK_SUB}#{START_IP}"
+    master.vm.hostname = "master"
+    master.vm.provision "kube", type: "shell", privileged: true, path: "scripts/kube.sh", env: {
+      "docker_ver" => "#{DOCKER_VER}",
+      "k8s_ver" => "#{KUBE_VER}"
+    }
+    master.vm.provision "0", type: "shell", preserve_order: true, privileged: true, inline: <<-SHELL
+      OUTPUT_FILE=/vagrant/join.sh
+      rm -rf /vagrant/join.sh
+      rm -rf /vagrant/.kube
+      sudo kubeadm init --apiserver-advertise-address=#{NETWORK_SUB}#{START_IP} --pod-network-cidr=#{POD_CIDR}
+      sudo kubeadm token create --print-join-command > /vagrant/join.sh
+      chmod +x $OUTPUT_FILE
+      mkdir -p $HOME/.kube
+      sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
+      sudo chown $(id -u):$(id -g) $HOME/.kube/config
+      cp -R $HOME/.kube /vagrant/.kube
+      #kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
+      kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
+      kubectl completion bash >/etc/bash_completion.d/kubect
+      echo 'alias k=kubectl' >>~/.bashrc
+    SHELL
+
+    master.vm.provision "file", preserve_order: true, source: "files", destination: "/tmp"
+    master.vm.provision "3", type: "shell", preserve_order: true, privileged: true, path: "scripts/pv.sh"
+
+    master.vm.provider "virtualbox" do |v|
+      <생략>
+    end # end provider
+  end
+
+  (1..NODE_COUNT).each do |i|
+    config.vm.define "node-#{i}" do |node|
+      node.vm.box = IMAGE_NAME
+      node.vm.network "private_network", ip: "#{NETWORK_SUB}#{i + START_IP}"
+      node.vm.hostname = "node-#{i}"
+        <생략>
+
+      node.vm.provider "virtualbox" do |v|
+        <생략>
+      end # end provider
+    end
+  end
+end
+
  • vm 생성을 위한 정의는 크게 masternode-#로 구분됨
  • master
    • master의 경우 1개만을 생성하도록 지정
    • *.vm.provision을 통해 스크립트를 실행하거나 파일을 복사하여 프로비저닝
    • master.vm.provision "0"에서 kubeconfig파일과 워커노드 조인을 위한 토큰 커맨드를 join.sh파일로 생성
  • node-#
    • 변수 NODE_COUNT 에서 지정된 개수에 따라 반복 수행
    • 앞서 master 프로비저닝시 생성된 join.sh를 이용하여 클러스터에 조인

실행 및 확인

vagrant cli

cli doc : https://www.vagrantup.com/docs/cliopen in new window

  • vagrant up : 프로비저닝을 실행하며, 이미 프로비저닝 된 경우 다시 프로비저닝하지 않고 vm만 기동
  • vagrant up vm-name : config.vm.define 에 선언된 아이디에 해당하는 vm만 기동
    • e.g. : vagrant up master
  • vagrant halt : vm 정지
  • vagrant halt vm-name : config.vm.define 에 선언된 아이디에 해당하는 vm만 정지
    • e.g. : vagrant halt node-1
  • vagrant status : vm 상태 확인
  • vagrant ssh vm-name : config.vm.define 에 선언된 아이디에 해당하는 vm에 ssh 접속
    • e.g. : vagrant ssh master
  • vagrant destroy : 프로비저닝된 vm 삭제

Check kubeconfig

master노드 프로비저닝 과정에서 .kube/config를 생성하므로, 해당 kubeconfig를 사용하여 호스트 환경에서 kubectl 사용 가능

~/vagrant-k8s/1.23> kubectl --kubeconfig=./.kube/config get nodes
+NAME     STATUS   ROLES                  AGE   VERSION
+master   Ready    control-plane,master   68m   v1.23.1
+node-1   Ready    <none>                 63m   v1.23.1
+node-2   Ready    <none>                 59m   v1.23.1
+node-3   Ready    <none>                 54m   v1.23.1
+

패키지 버전 확인 방법

#apt-cache policy <packagename>
+apt-cache policy kubelet | grep 1.2
+  Candidate: 1.23.1-00
+     1.23.1-00 500
+     1.23.0-00 500
+     1.22.5-00 500
+     1.22.4-00 500
+     1.22.3-00 500
+     1.22.2-00 500
+     1.22.1-00 500
+     1.22.0-00 500
+     1.21.8-00 500
+     1.21.7-00 500
+     ...
+
+ + + diff --git a/02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.html b/02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.html new file mode 100644 index 0000000000..e92b4e1239 --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.html @@ -0,0 +1,248 @@ + + + + + + + + + + [PKOS] 1편 - AWS Kops 설치 및 기본 사용 | docmoa + + + + + +
본문으로 건너뛰기

[PKOS] 1편 - AWS Kops 설치 및 기본 사용

약 4 분KubernetesKopsEKSPKOS

[PKOS] 1편 - AWS Kops 설치 및 기본 사용

💡 본 글은 PKOS(Production Kubernetes Online Study) 2기 스터디의 일부로 작성된 내용입니다.
실제 Production Kubernetes 환경에서 활용 가능한 다양한 정보를 전달하기 위한 시리즈로 작성 예정입니다.

1. 실습환경 사전준비

본 스터디는 AWS 환경에서 Kops(Kubernetes Operations)를 활용한 실습으로 진행할 예정입니다.

📌 참고 : 필자는 개인적인 이유로 Route 53 계정과, kOps 클러스터 운영 계정을 나눠서 진행합니다.
하나의 계정에서 실습을 진행할 경우에는 사전 환경구성이 다를 수 있는 점 참고 부탁드립니다.

1) Route 53 도메인 구매

AWS의 DNS 웹 서비스인 Route 53open in new window을 통해 도메인을 구입합니다.
필자는 hyungwook.link 도메인을 구입하였으며, 초기 구입 후 상태: 도메인 등록 진행 중 인 화면을 확인할 수 있습니다,

image-20230305211458121
image-20230305211458121

구입 시 등록했던 이메일 계정으로 발송된 verify 메일 링크를 클릭하여 활성화 합니다.

(1) Route53 Verify mail

image-20230305211833451
image-20230305211833451

일정시간이 지나면 정상적으로 도메인이 활성화 되된 것을 확인할 수 있습니다.

(2) 도메인 등록완료 화면

image-20230305212617366
image-20230305212617366
  • 등록된 도메인 확인
# 자신의 도메인에 NS 타입 조회
+# dig ns <구입한 자신의 도메인> +short
+dig ns hyungwook.link +short
+ns-939.awsdns-53.net.
+ns-1575.awsdns-04.co.uk.
+ns-233.awsdns-29.com.
+ns-1466.awsdns-55.org.
+

2) Route 53 등록

필자는 서두에서 언급한 것 처럼 Route 53 구매한 계정과, kOps 클러스터를 생성할 계정이 다르므로 다음과 같은 과정을 추가적으로 수행하였습니다.

(2) Route 53을 구매한 AWS 계정 : NS 레코드 등록

  • Kops 클러스터를 생성할 계정에서 등록한 레코드를 정보를 Route 53 구매 계정의 NS 레코드에 등록합니다.
image-20230305223503518
image-20230305223503518

📌 참고 : How to manage Route53 hosted zones in a multi-account environmentopen in new window


2. Kops 클러스터 배포 전 사전준비

이제 실습에서 사용할 도메인 준비가 완료되었으므로, Kops 클러스터 생성을 위한 준비 단계로 넘어갑니다.

1) AWS Credentials 설정

# IAM User 자격 구성 : 실습 편리를 위해 administrator 권한을 가진 IAM User 의 자격 증명 입력
+aws configure
+

2) S3 버킷 생성

# k8s 설정 파일이 저장될 버킷 생성
+## aws s3 mb s3://버킷<유일한 이름> --region <S3 배포될 AWS 리전>
+aws s3 mb s3://버킷<유일한 이름> --region $REGION
+aws s3 ls
+
+## 예시)
+aws s3 mb s3://hyungwook-k8s-s3 --region ap-northeast-2
+

3. 클러스터 배포

# 변수설정
+export AWS_PAGER=""
+export REGION=ap-northeast-2
+export KOPS_CLUSTER_NAME=pkos.hyungwook.link
+export KOPS_STATE_STORE=s3://hyungwook-k8s-s3
+echo 'export AWS_PAGER=""' >>~/.bashrc
+echo 'export REGION=ap-northeast-2' >>~/.bashrc
+echo 'export KOPS_CLUSTER_NAME=pkos.hyungwook.link' >>~/.bashrc
+echo 'export KOPS_STATE_STORE=s3://hyungwook-k8s-s3' >>~/.bashrc
+
+# kops 설정 파일 생성(s3) 및 k8s 클러스터 배포 : 6분 정도 소요
+## CNI는 aws vpc cni 사용, 마스터 노드 1대(t3.medium), 워커 노드 2대(t3.medium), 파드 사용 네트워크 대역 지정(172.30.0.0/16)
+## --container-runtime containerd --kubernetes-version 1.24.0 ~ 1.25.6
+kops create cluster --zones="$REGION"a,"$REGION"c --networking amazonvpc --cloud aws \
+--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \
+--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version "1.24.10" --dry-run -o yaml > mykops.yaml
+
+kops create cluster --zones="$REGION"a,"$REGION"c --networking amazonvpc --cloud aws \
+--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \
+--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version "1.24.10" -y
+

1) kOps 클러스터 배포 시 Route 53 A 레코드 화면(등록중)

A 레코드값이 자동으로 추가된 모습을 확인할 수 있습니다. 하지만 실제 api 서버와 내부 controller의 IP 주소가 등록되지 않았기 때문에, 실제 클러스터가 정상적으로 구성된 이후에는 자동으로 A 레코드가 업데이트 됩니다.

image-20230305223938653
image-20230305223938653
  • A 레코드 조회 CLI 명령 : aws route53
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq
+
+[
+  "api.pkos.hyungwook.link.",
+  "api.internal.pkos.hyungwook.link.",
+  "kops-controller.internal.pkos.hyungwook.link."
+]
+

이때, kops validate 명령으로 확인하면 아직까지 api.pkos.hyungwook.link 가 relov 되지 않는 것을 확인할 수 있습니다.

kops validate cluster --wait 10m
+Validating cluster pkos.hyungwook.link
+
+W0305 22:38:08.780600    4256 validate_cluster.go:184] (will retry): unexpected error during validation: unable to resolve Kubernetes cluster API URL dns: lookup api.pkos.hyungwook.link: no such host
+W0305 22:38:18.788067    4256 validate_cluster.go:184] (will retry): unexpected error during validation: unable to resolve Kubernetes cluster API URL dns: lookup api.pkos.hyungwook.link: no such host
+

어느정도 시간이 지난 후 정상적으로 A 레코드 값이 변경된 것을 확인할 수 있습니다.

2) kOps 클러스터 배포 시 Route 53 A 레코드 화면(등록완료 )

image-20230306000758876
image-20230306000758876
  • A 레코드 및 값 반복조회
# A 레코드 및 값 반복조회
+while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
+[
+  {
+    "Name": "api.pkos.hyungwook.link.",
+    "Type": "A",
+    "TTL": 60,
+    "ResourceRecords": [
+      {
+        "Value": "43.201.33.161"
+      }
+    ]
+  },
+  {
+    "Name": "api.internal.pkos.hyungwook.link.",
+    "Type": "A",
+    "TTL": 60,
+    "ResourceRecords": [
+      {
+        "Value": "172.30.37.41"
+      }
+    ]
+  },
+  {
+    "Name": "kops-controller.internal.pkos.hyungwook.link.",
+    "Type": "A",
+    "TTL": 60,
+    "ResourceRecords": [
+      {
+        "Value": "172.30.37.41"
+      }
+    ]
+  }
+]
+202335일 일요일 224146초 KST
+

이제 정상적으로 A 레코드가 등록된 것을 확인할 수 있으며 설치가 자동으로 진행됩니다.

3) kops validate cluster 명령(생성확인)

실제 kops 클러스터가 정상적으로 배포된 것을 확인할 수 있습니다.

kops validate cluster
+Validating cluster pkos.hyungwook.link
+
+INSTANCE GROUPS
+NAME			ROLE	MACHINETYPE	MIN	MAX	SUBNETS
+master-ap-northeast-2a	Master	t3.medium	1	1	ap-northeast-2a
+nodes-ap-northeast-2a	Node	t3.medium	1	1	ap-northeast-2a
+nodes-ap-northeast-2c	Node	t3.medium	1	1	ap-northeast-2c
+
+NODE STATUS
+NAME			ROLE	READY
+i-089062ff9f50789ee	node	True
+i-096a645be0dd932b6	node	True
+i-0dce8997b4633b806	master	True
+
+Your cluster pkos.hyungwook.link is ready
+

📌 참고 : Kops 클러스터 kubeconfig 파일 업데이트 명령

# 권한이 없을 경우
+kubectl get nodes -o wide
+error: You must be logged in to the server (Unauthorized)
+
+# kubeconfig 업데이트
+kops export kubeconfig --name pkos.hyungwook.link --admin
+

4. 샘플 애플리케이션 배포

1) Service / Pod with CLB : Mario 게임

  • Deployment, SVC 배포
# 수퍼마리오 디플로이먼트 배포
+curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/1/mario.yaml
+kubectl apply -f mario.yaml
+cat mario.yaml | yh
+deployment.apps/mario created
+service/mario created
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: mario
+  labels:
+    app: mario
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: mario
+  template:
+    metadata:
+      labels:
+        app: mario
+    spec:
+      containers:
+      - name: mario
+        image: pengbai/docker-supermario
+---
+apiVersion: v1
+kind: Service
+metadata:
+   name: mario
+spec:
+  selector:
+    app: mario
+  ports:
+  - port: 80
+    protocol: TCP
+    targetPort: 8080
+  type: LoadBalancer
+
  • Deploy, SVC, EP 확인
# 배포 확인 : CLB 배포 확인 >> 5분 이상 소요
+kubectl get deploy,svc,ep mario
+watch kubectl get svc mario
+
+# Watch 명령 실행 후 <pending>
+Every 2.0s: kubectl get svc mario                                                                                                     hyungwook-Q9W5QX7FGY: Sat Mar 11 21:50:41 2023
+NAME    TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
+mario   LoadBalancer   100.67.138.178   <pending>     80:30624/TCP   92s
+
+# External-IP 할당
+kubectl get svc mario
+NAME    TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)        AGE
+mario   LoadBalancer   100.67.138.178   a643cc3e6e2c54ed8989c95d0481f48c-113657418.ap-northeast-2.elb.amazonaws.com   80:30624/TCP   3m7s
+
  • CLB로 접속
# 마리오 게임 접속 : CLB 주소로 웹 접속
+kubectl get svc mario -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" | awk '{ print "Maria URL = http://"$1 }'
+
+# 결과 값
+Maria URL = http://a643cc3e6e2c54ed8989c95d0481f48c-113657418.ap-northeast-2.elb.amazonaws.com
+
  • 마리오 게임 화면
image-20230311215543207
image-20230311215543207

5. (추가) External DNS

External DNS은 K8s Service / Ingress 생성 시 도메인을 설정하면 자동으로 AWS Route53의 A 레코드(TXT 레코드)에 자동 생성/삭제를 제공합니다.

img
img

1) External DNS - Addon 설치

# 정책 생성 -> 마스터/워커노드에 정책 연결
+curl -s -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/AKOS/externaldns/externaldns-aws-r53-policy.json
+aws iam create-policy --policy-name AllowExternalDNSUpdates --policy-document file://externaldns-aws-r53-policy.json
+
+# ACCOUNT_ID 변수 지정
+export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
+
+# EC2 instance profiles 에 IAM Policy 추가(attach)
+aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --role-name masters.$KOPS_CLUSTER_NAME
+aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --role-name nodes.$KOPS_CLUSTER_NAME
+
+# 설치
+kops edit cluster
+--------------------------
+spec:
+  certManager:    # 없어도됨!
+    enabled: true # 없어도됨!
+  externalDns:
+    provider: external-dns
+--------------------------
+
+# 업데이트 적용
+kops update cluster --yes && echo && sleep 3 && kops rolling-update cluster
+
+# externalDns 컨트롤러 파드 확인
+kubectl get pod -n kube-system -l k8s-app=external-dns
+NAME                          READY   STATUS    RESTARTS   AGE
+external-dns-5bc8fcf8-7vznp   1/1     Running   0          14s
+

2) Mario 서비스에 도메인 연결

# CLB에 ExternanDNS 로 도메인 연결
+kubectl annotate service mario "external-dns.alpha.kubernetes.io/hostname=mario.$KOPS_CLUSTER_NAME"
+

(1) Route 53 A 레코드 등록 확인

Mario 도메인
Mario 도메인

(2) 등록된 A 레코드에 대한 도메인 체크

도메인 체크
도메인 체크
  • 명령을 통한 확인
# external-dns 등록로그 확인
+kubectl logs -n kube-system -l k8s-app=external-dns
+
+time="2023-03-11T14:54:51Z" level=info msg="Applying provider record filter for domains: [pkos.hyungwook.link. .pkos.hyungwook.link.]"
+time="2023-03-11T14:54:51Z" level=info msg="All records are already up to date"
+...(생략)
+
+# 확인
+dig +short mario.$KOPS_CLUSTER_NAME
+
+# 웹 접속 주소 확인 및 접속
+echo -e "Maria Game URL = http://mario.$KOPS_CLUSTER_NAME"
+
+# 도메인 체크
+echo -e "My Domain Checker = https://www.whatsmydns.net/#A/mario.$KOPS_CLUSTER_NAME"
+

6. 마무리

1) 리소스 삭제

  • 마리오 리소스 삭제
kubectl delete deploy,svc mario
+
  • Kops 클러스터 삭제
kops delete cluster --yes
+

본 편에서는 Kops 클러스터를 구성방안 및 External DNS를 연동한 외부 서비스 노출에 대한 방법을 살펴보았습니다.

다음편에서는 네트워크 및 스토리지에 대한 활용방안을 살펴보겠습니다.

+ + + diff --git a/02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.html b/02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.html new file mode 100644 index 0000000000..4184ef06a8 --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.html @@ -0,0 +1,475 @@ + + + + + + + + + + [PKOS] 2편 - 네트워크 & 스토리지 | docmoa + + + + + +
본문으로 건너뛰기

[PKOS] 2편 - 네트워크 & 스토리지

약 7 분KubernetesKopsEKSPKOS

[PKOS] 2편 - 네트워크 & 스토리지

지난 1주차 스터디에이어 2주차 스터디를 진행하였습니다. 이번 스터디에서는 "쿠버네티스 네트워크" 및 "쿠버네티스 스토리지"를 중심으로 학습하였습니다.

참고 :
원활한 실습을 위해 인스턴스 타입을 변경한 후 진행합니다.

0. 사전준비

1) Kops 클러스터의 인스턴 그룹 변경

kops get ig
+NAME			ROLE	MACHINETYPE	MIN	MAX	ZONES
+master-ap-northeast-2a	Master	t3.medium	1	1	ap-northeast-2a
+nodes-ap-northeast-2a	Node	t3.medium	1	1	ap-northeast-2a
+nodes-ap-northeast-2c	Node	t3.medium	1	1	ap-northeast-2c
+
kops edit ig master-ap-northeast-2a
+
+# 예제화면
+apiVersion: kops.k8s.io/v1alpha2
+kind: InstanceGroup
+metadata:
+  creationTimestamp: "2023-03-05T13:37:26Z"
+  labels:
+    kops.k8s.io/cluster: pkos.hyungwook.link
+  name: master-ap-northeast-2a
+spec:
+  image: 099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20230112
+  instanceMetadata:
+    httpPutResponseHopLimit: 3
+    httpTokens: required
+  machineType: t3.medium #기존 t3.medium에서 c5d.large로 변경
+  maxSize: 1
+  minSize: 1
+  role: Master
+  subnets:
+  - ap-northeast-2a
+
  • 확인
kops get ig
+NAME			ROLE	MACHINETYPE	MIN	MAX	ZONES
+master-ap-northeast-2a	Master	c5d.large	1	1	ap-northeast-2a
+nodes-ap-northeast-2a	Node	c5d.large	1	1	ap-northeast-2a
+nodes-ap-northeast-2c	Node	c5d.large	1	1	ap-northeast-2c
+
  • Cluster Update 수행
kops update cluster --name pkos.hyungwook.link --yes
+
+kops rolling-update cluster --yes
+

2. NLB

1) NLB Mode 정리

(1) 인스턴스 유형

  1. externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
  2. externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP 유지, 워커 노드의 iptables 사용함
  • 이미지 출처 : PKOS 스터디 - 가시다님
NLB 이미지1
NLB 이미지1
NLB 이미지2
NLB 이미지2

(2) IP 유형

반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요함!

  1. Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가능
  2. Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)

2) 서비스 배포

# 작업용 EC2 - 디플로이먼트 & 서비스 생성
+cat ~/pkos/2/echo-service-nlb.yaml | yh
+kubectl apply -f ~/pkos/2/echo-service-nlb.yaml
+
+# 확인
+kubectl get deploy,pod
+kubectl get svc,ep,ingressclassparams,targetgroupbindings
+
+NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP                                                                         PORT(S)        AGE
+service/kubernetes        ClusterIP      100.64.0.1       <none>                                                                              443/TCP        7d
+service/svc-nlb-ip-type   LoadBalancer   100.64.191.200   k8s-default-svcnlbip-bfcad9371a-250be02681485d95.elb.ap-northeast-2.amazonaws.com   80:31206/TCP   97s
+
+NAME                        ENDPOINTS                             AGE
+endpoints/kubernetes        172.30.37.41:443                      7d
+endpoints/svc-nlb-ip-type   172.30.55.31:8080,172.30.71.86:8080   97s
+
+NAME                                   GROUP-NAME   SCHEME   IP-ADDRESS-TYPE   AGE
+ingressclassparams.elbv2.k8s.aws/alb                                           122m
+
+NAME                                                        SERVICE-NAME      SERVICE-PORT   TARGET-TYPE   AGE
+targetgroupbinding.elbv2.k8s.aws/k8s-default-svcnlbip-c54bafee9a   svc-nlb-ip-type   80      ip            95s
+
+kubectl get targetgroupbindings -o json | jq
+
  • Listener 확인
Listener
Listener
  • Target Group 확인
TargetGroup 확인
TargetGroup 확인
  • 실제 Pod의 IP 정보 확인
k get pods -o wide
+NAME               READY    STATUS   RESTARTS   AGE     IP         NODE        NOMINATED NODE   READINESS GATES
+deploy-echo-(생략)   1/1     Running   0    7m50s   172.30.55.31   i-089062ff9f50789ee   <none>           <none>
+deploy-echo-(생략)   1/1     Running   0    7m50s   172.30.71.86   i-096a645be0dd932b6   <none>           <none>
+

3) 접속확인

접속확인
접속확인

3. NLB에 TLS 적용하기(feat. ACM)

사전 준비 :
공인도메인 소유, AWS Route53 도메인등록 상태, NLB 가 위치한 리전(서울)의 인증서 요청/발급 완료상태, ExternalDNS 준비완료 상태

1) 환경구성

  • ACM 인증서 및 ARN 확인
# 사용 리전의 인증서 ARN 확인
+aws acm list-certificates
+aws acm list-certificates --max-items 10
+aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text
+CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
+echo $CERT_ARN
+
  • NLB와 ACM에서 사용할 도메인 등록
# 자신의 도메인 변수 지정
+MyDomain=<자신의 도메인>
+MyDomain=websrv.$KOPS_CLUSTER_NAME
+

2) 샘플 애플리케이션 배포

  • Deploy, SVC YAML
cat <<EOF | kubectl create -f -
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: deploy-echo
+spec:
+  replicas: 2
+  selector:
+    matchLabels:
+      app: deploy-websrv
+  template:
+    metadata:
+      labels:
+        app: deploy-websrv
+    spec:
+      terminationGracePeriodSeconds: 0
+      containers:
+      - name: akos-websrv
+        image: k8s.gcr.io/echoserver:1.5
+        ports:
+        - containerPort: 8080
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-nlb-ip-type
+  annotations:
+    external-dns.alpha.kubernetes.io/hostname: "${MyDomain}"
+    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
+    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
+    service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
+    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
+    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
+    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: ${CERT_ARN}
+    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
+spec:
+  ports:
+    - port: 80
+      targetPort: 8080
+      protocol: TCP
+      name: http
+    - port: 443
+      targetPort: 8080
+      protocol: TCP
+      name: https
+  type: LoadBalancer
+  loadBalancerClass: service.k8s.aws/nlb
+  selector:
+    app: deploy-websrv
+EOF
+
  • 생성된 SVC의 annotation 확인
kubectl describe svc svc-nlb-ip-type | grep Annotations: -A8
+
+Annotations:              external-dns.alpha.kubernetes.io/hostname: websrv.pkos.hyungwook.link
+                          service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
+                          service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: true
+                          service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: 8080
+                          service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
+                          service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
+                          service.beta.kubernetes.io/aws-load-balancer-ssl-cert:
+                            arn:aws:acm:ap-northeast-2:856117747411:certificate/208e809e-9ebf-4bb5-92c2-795868429e88
+                          service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https
+

3) 인증서 적용 확인

(1) CLI 확인

  • insecure 옵션 없이 정상적으로 curl 응답 하는 것을 확인할 수 있습니다.
curl -s http://websrv.pkos.hyungwook.link | grep Hostname
+Hostname: deploy-echo-5c4856dfd6-267pf
+
+curl -s  https://websrv.pkos.hyungwook.link | grep Hostname
+Hostname: deploy-echo-5c4856dfd6-k9277
+

(2) 웹 브라우저 확인

  • "이 연결은 안전합니다." 메시지를 통해 실제 ACM 퍼블릭 인증서가 적용된 것을 확인할 수 있습니다.
인증서 적용 확인
인증서 적용 확인

3. Ingress

클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할

ingress
ingress

1) 환경구성

  • AWS LoadBalancerControllerIAMPolicy 정책 생성 및 추가
# EC2 instance profiles 에 IAM Policy 추가(attach)
+aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
+aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME
+
+# EC2 instance profiles 에 IAM Policy 추가(attach)
+aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
+aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME
+
  • Kops 클러스터 ExternalDNS 및 certManager 설정 추가
# kOps 클러스터 편집 : 아래 내용 추가
+kops edit cluster
+-----
+spec:
+  certManager:
+    enabled: true
+  awsLoadBalancerController:
+    enabled: true
+  externalDns:
+    provider: external-dns
+
  • 변경된 설정 업데이트 반영
# 업데이트 적용
+kops update cluster --yes && echo && sleep 3 && kops rolling-update cluster
+

2) 서비스/파드 배포 테스트 with Ingress(ALB)\

  • 서비스 배포
# 게임 파드와 Service, Ingress 배포
+kubectl apply -f ~/pkos/3/ingress1.yaml
+
  • 서비스 배포 후 Target Type이 IP로 생성된 것 확인
kubectl get targetgroupbindings -n game-2048
+NAME                               SERVICE-NAME   SERVICE-PORT   TARGET-TYPE   AGE
+k8s-game2048-service2-e48050abac   service-2048   80             ip            87s
+
  • Ingress 정보 확인
kubectl describe ingress -n game-2048 ingress-2048
+
+Name:             ingress-2048
+Labels:           <none>
+Namespace:        game-2048
+Address:          k8s-game2048-ingress2-fdfe8009a9-1424012699.ap-northeast-2.elb.amazonaws.com
+Ingress Class:    alb
+Default backend:  <default>
+Rules:
+  Host        Path  Backends
+  ----        ----  --------
+  *
+              /   service-2048:80 (172.30.44.132:80,172.30.65.100:80)
+Annotations:  alb.ingress.kubernetes.io/scheme: internet-facing
+              alb.ingress.kubernetes.io/target-type: ip
+Events:
+  Type    Reason                  Age    From     Message
+  ----    ------                  ----   ----     -------
+  Normal  SuccessfullyReconciled  8m56s  ingress  Successfully reconciled
+
  • ALB 접속 확인
# 게임 접속 : ALB 주소로 웹 접속
+kubectl get ingress -n game-2048 ingress-2048 -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" | awk '{ print "Game URL = http://"$1 }'
+Game URL = http://k8s-game2048-ingress2-fdfe8009a9-1424012699.ap-northeast-2.elb.amazonaws.com
+
게임화면
게임화면
  • 리소스 삭제
kubectl delete ingress ingress-2048 -n game-2048
+kubectl delete svc service-2048 -n game-2048 && kubectl delete deploy deployment-2048 -n game-2048 && kubectl delete ns game-2048
+

4. 쿠버네티스 스토리지 : EBS

1) 환경구성

c5d.large 의 EC2 인스턴스 스토어(임시 블록 스토리지) 설정 작업 - 링크open in new window , NVMe SSD - 링크open in new window

  • 데이터 손실 : 기본 디스크 드라이브 오류, 인스턴스가 중지됨, 인스턴스가 최대 절전 모드로 전환됨, 인스턴스가 종료됨
       Amazon EC2 인스턴스 스토리지
Amazon EC2 인스턴스 스토리지
  • 임시스토리지 실습을 위한 환경 구성
# 인스턴스 스토어 볼륨이 있는 c5 모든 타입의 스토리지 크기
+aws ec2 describe-instance-types \
+ --filters "Name=instance-type,Values=c5*" "Name=instance-storage-supported,Values=true" \
+ --query "InstanceTypes[].[InstanceType, InstanceStorageInfo.TotalSizeInGB]" \
+ --output table
+--------------------------
+|  DescribeInstanceTypes |
++---------------+--------+
+|  c5d.large    |  50    |
+|  c5d.12xlarge |  1800  |
+...
+
+# 워커 노드 Public IP 확인
+aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value}" --filters Name=instance-state-name,Values=running --output table
+
+-------------------------------------------------------------------------
+|                           DescribeInstances                           |
++------------------------------------------------------+----------------+
+|                     InstanceName                     |  PublicIPAdd   |
++------------------------------------------------------+----------------+
+|  nodes-ap-northeast-2c.pkos.hyungwook.link           |  13.209.75.228 |
+|  master-ap-northeast-2a.masters.pkos.hyungwook.link  |  3.38.117.78   |
+|  nodes-ap-northeast-2a.pkos.hyungwook.link           |  52.79.61.228  |
++------------------------------------------------------+----------------+
+
+# 워커 노드 Public IP 변수 지정
+W1PIP=<워커 노드 1 Public IP>
+W2PIP=<워커 노드 2 Public IP>
+W1PIP=13.209.75.228
+W2PIP=52.79.61.228
+echo "export W1PIP=$W1PIP" >> /etc/profile
+echo "export W2PIP=$W2PIP" >> /etc/profile
+
  • 워커 노드 스토리지 확인 : nvme 명령도구 설치 및 확인
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo apt install -y nvme-cli
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo apt install -y nvme-cli
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo nvme list
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo nvme list
+
nvme_list
nvme_list
# 워커 노드 스토리지 확인 : NVMe SSD 인스턴스 스토어 볼륨 확인
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP lsblk -e 7
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP lsblk -e 7
+NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
+nvme1n1      259:0    0  46.6G  0 disk
+nvme0n1      259:1    0   128G  0 disk
+├─nvme0n1p1  259:2    0 127.9G  0 part /
+├─nvme0n1p14 259:3    0     4M  0 part
+└─nvme0n1p15 259:4    0   106M  0 part /boot/efi
+
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP df -hT -t ext4
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP df -hT -t ext4
+Filesystem     Type  Size  Used Avail Use% Mounted on
+/dev/root      ext4  124G  4.2G  120G   4% /
+
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP lspci | grep Non-Volatile
+00:04.0 Non-Volatile memory controller: Amazon.com, Inc. Device 8061
+00:1f.0 Non-Volatile memory controller: Amazon.com, Inc. NVMe SSD Controller
+
+# 파일시스템 생성
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo mkfs -t xfs /dev/nvme1n1
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo mkfs -t xfs /dev/nvme1n1
+
+# /data 디렉토리 생성 
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo mkdir /data
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo mkdir /data
+
+# /data 마운트
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo mount /dev/nvme1n1 /data
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo mount /dev/nvme1n1 /data
+
+# 파일시스템 포맷 및 마운트 확인
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP df -hT -t ext4 -t xfs
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP df -hT -t ext4 -t xfs
+Filesystem     Type  Size  Used Avail Use% Mounted on
+/dev/root      ext4  124G  4.2G  120G   4% /
+/dev/nvme1n1   xfs    47G  365M   47G   1% /data
+

2) HostPath 실습

(1) HostPath스토리지 클래스 배포

  • 호스트 Path 를 사용하는 PV/PVC : local-path-provisioner 스트리지 클래스 배포 - 링크open in new window
# 마스터노드의 이름 확인해두기
+kubectl get node | grep control-plane | awk '{print $1}'
+i-066cdb714fc6545c0
+
+# 배포 : vim 직접 편집할것
+curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.23/deploy/local-path-storage.yaml
+vim local-path-storage.yaml
+----------------------------
+# 아래 빨간 부분은 추가 및 수정
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: local-path-provisioner
+  namespace: local-path-storage
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: local-path-provisioner
+  template:
+    metadata:
+      labels:
+        app: local-path-provisioner
+    spec:
+      nodeSelector:
+        kubernetes.io/hostname: "<각자 자신의 마스터 노드 이름 입력>"
+      tolerations:
+        - effect: NoSchedule
+          key: node-role.kubernetes.io/control-plane
+          operator: Exists
+...
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: local-path-config
+  namespace: local-path-storage
+data:
+  config.json: |-
+    {
+            "nodePathMap":[
+            {
+                    "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
+                    "paths":["/data/local-path"]
+            }
+            ]
+    }
+----------------------------
+
+# 배포
+kubectl apply -f local-path-storage.yaml
+
+# 확인 : 마스터노드에 배포됨
+kubectl get-all -n local-path-storage
+NAME                                                   NAMESPACE           AGE
+configmap/kube-root-ca.crt                             local-path-storage  12s
+configmap/local-path-config                            local-path-storage  12s
+pod/local-path-provisioner-6bff65dcd8-vgwfk            local-path-storage  12s
+serviceaccount/default                                 local-path-storage  12s
+serviceaccount/local-path-provisioner-service-account  local-path-storage  12s
+deployment.apps/local-path-provisioner                 local-path-storage  12s
+replicaset.apps/local-path-provisioner-6bff65dcd8      local-path-storage  12s
+
+kubectl get pod -n local-path-storage -owide
+NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
+local-path-provisioner-6bff65dcd8-vgwfk   1/1     Running   0          17s   172.30.63.103   i-072786762169226a7   <none>           <none>
+
+kubectl describe cm -n local-path-storage local-path-config
+
+kubectl get sc local-path
+NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
+local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  34s
+

(2) PV / PVC 샘플 배포

# PVC 생성
+kubectl apply -f ~/pkos/3/localpath1.yaml
+
+# PVC 확인 : 아직 Pod Boud가 되지 않았으므로 Pending
+kubectl describe pvc
+kubectl get pvc
+
+NAME              STATUS    VOLUME                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
+localpath-claim   Pending                                                        local-path      58s
+
+# 파드 생성
+kubectl apply -f ~/pkos/3/localpath2.yaml
+
+# 파드확인 : 정상적으로 Bound된 것으로 확인
+kubectl get pod,pv,pvc
+
+NAME                READY   STATUS    RESTARTS   AGE
+pod/app-localpath   1/1     Running   0          56s
+
+NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS    REASON   AGE
+persistentvolume/pvc-37743f20-e30d-491c-b11c-5e5b7d33a476   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path               49s
+
+NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
+persistentvolumeclaim/localpath-claim   Bound    pvc-37743f20-e30d-491c-b11c-5e5b7d33a476   1Gi        RWO            local-path      3m57s
+
  • 데이터 생성확인
# 파드 데이터 확인 : app-localpath 파드에서 데이터 쌓이는 것 확인
+kubectl exec -it app-localpath -- tail -f /data/out.txt
+Sun Jan 29 05:13:45 UTC 2023
+
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP tree /data
+/data
+0 directories, 0 files
+
+ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP tree /data
+/data
+└── local-path
+    └── pvc-37743f20-e30d-491c-b11c-5e5b7d33a476_default_localpath-claim
+        └── out.txt
+
+2 directories, 1 file
+
+# 노드 데이터 확인 : app-localpath 파드가 배포된 노드에 접속 후 데이터 쌓이는 것 확인
+ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP tail -f /data/local-path/pvc-ce742b90-755a-4b52-9693-595cbf55dfb0_default_localpath-claim/out.txt
+Sun Jan 29 05:13:45 UTC 2023
+...
+
데이터확인
데이터확인

LocalPath 성능측정은 추후 별도 정리 후 업로드 예정

3) AWS EBS Controller

Volume (ebs-csi-controller) : EBS CSI driver 동작 : 볼륨 생성 및 파드에 볼륨 연결 - 링크open in new window

  • 이미지 출처 : PKOS 스터디 - 가시다
EBS Controller
EBS Controller

(1) EBS Driver 배포확인

# EBS Driver 확인 Kops 설치 시 기본 배포
+kubectl get pod -n kube-system -l app.kubernetes.io/instance=aws-ebs-csi-driver
+
+NAME                                  READY   STATUS    RESTARTS   AGE
+ebs-csi-controller-6d8fd64c78-q5qfn   5/5     Running   0          5d23h
+ebs-csi-node-9cfss                    3/3     Running   0          5d23h
+ebs-csi-node-crhbx                    3/3     Running   0          5d23h
+ebs-csi-node-zbjgj                    3/3     Running   0          5d23h
+
+# 스토리지 클래스 확인
+kubectl get sc kops-csi-1-21 kops-ssd-1-17
+
+kubectl describe sc kops-csi-1-21 | grep Parameters
+Parameters:            encrypted=true,type=gp3
+kubectl describe sc kops-ssd-1-17 | grep Parameters
+Parameters:            encrypted=true,type=gp2
+
+

(2) PV / PVC 샘플 배포

# PVC 생성
+kubectl apply -f ~/pkos/3/awsebs-pvc.yaml
+
+# 파드 생성
+kubectl apply -f ~/pkos/3/awsebs-pod.yaml
+
+# PVC, 파드 확인
+kubectl get pvc,pv,pod
+NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
+persistentvolumeclaim/ebs-claim   Bound    pvc-fb5b5e1c-76ef-4a43-9b94-9af2b1b1e5f7   4Gi        RWO            kops-csi-1-21   41m
+
+NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS    REASON   AGE
+persistentvolume/pvc-fb5b5e1c-76ef-4a43-9b94-9af2b1b1e5f7   4Gi        RWO            Delete           Bound    default/ebs-claim   kops-csi-1-21            40m
+
+NAME      READY   STATUS    RESTARTS   AGE
+pod/app   1/1     Running   0          3m15s
+
+# 실제 쌓이는 데이터 확인
+kubectl exec app -- tail -f /data/out.txt
+
+Sat Mar 18 14:49:25 UTC 2023
+Sat Mar 18 14:49:30 UTC 2023
+Sat Mar 18 14:49:35 UTC 2023
+...
+
+# 파드 내에서 볼륨 정보 확인
+kubectl exec -it app -- sh -c 'df -hT --type=ext4'
+Filesystem     Type  Size  Used Avail Use% Mounted on
+/dev/nvme1n1   ext4  3.9G   16M  3.8G   1% /data
+/dev/root      ext4  124G  4.9G  120G   4% /etc/hosts
+
+# 추가된 EBS 볼륨 상세 정보 확인 
+while true; do aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" --output text; date; sleep 1; done
+
+(중략)
+i-078613f7b7cd9e352	attached	vol-071ebb777dc2ac3cd	gp3 # 시간이 지난 후 추가되는 것 확인
+

(3) (번외) PVC 데이터 증설

# 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경
+kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
+kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
+kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
+
+# 확인 : 볼륨 용량 수정 반영이 되어야 되니, 수치 반영이 조금 느릴수 있다
+kubectl exec -it app -- sh -c 'df -hT --type=ext4'
+kubectl df-pv
+aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq
+
  • 리소스 삭제
kubectl delete pod app & kubectl delete pvc ebs-claim
+

5. 마무리

이번 글에서는 Kops 환경에서의 네트워크와 스토리지를 활용하는 방법을 정리해보았습니다.

일반적인 K8s와 달리 AWS 환경에서 EKS, Kops 등을 활용하게 된다면, AWS 리소스와의 연계를 통해 관리의 효율성과 편의성을 체감할 수 있었습니다.

다만, K8s만 알고 AWS는 잘 모르거나 또 그 반대의 상황에는 진입장벽이 있을 수 있겠다는 생각이 들었네요. 물론 이러한 부분만 해소된다면 관리형 쿠버네티스를 200% 활용할 수 있을 것 같습니다.

다음시간에는 GitOps와 관련된 주제로 찾아올 예정입니다.

+ + + diff --git a/02-PrivatePlatform/Kubernetes/05-Kops/index.html b/02-PrivatePlatform/Kubernetes/05-Kops/index.html new file mode 100644 index 0000000000..01f921113c --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/05-Kops/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Kops | docmoa + + + + + +
본문으로 건너뛰기

05 Kops

1분 미만

+ + + diff --git a/02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.html b/02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.html new file mode 100644 index 0000000000..8d845aadfe --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.html @@ -0,0 +1,320 @@ + + + + + + + + + + AEWS 1주차 - Amzaon EKS 설치 및 기본 사용 | docmoa + + + + + +
본문으로 건너뛰기

AEWS 1주차 - Amzaon EKS 설치 및 기본 사용

약 5 분KubernetesEKSPKOS

AEWS 1주차 - Amzaon EKS 설치 및 기본 사용

이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

img
img

필자는 기본적인 스터디내용을 이번 시리즈에 연재할 예정이며, 추가적으로 HashiCorp의 Consul, Vault 등을 샘플로 배포하며 연동하는 내용을 조금씩 다뤄볼 예정이다.

AWS Workshop - EKS 관련

참고 : AWS EKS 관련 핸즈온 워크샵을 해볼 수 있는 다양한 링크 모음이다.

여담이지만, HashiCorp 솔루션에 대한 다양한 HOL Workshop 실습도 사용자들이 많이 만들고 기여할 수 있도록 플랫폼을 오픈하면 좋을 것 같다.

Amazon EKS 소개

img
img

Amazon Elastic Kubernetes Service는 자체 Kubernetes 컨트롤 플레인 또는 노드를 설치, 운영 및 유지 관리할 필요 없이 Kubernetes 실행에 사용할 수 있는 관리형 서비스이다.

EKS를 사용하는 다양한 이유가 있겠지만 대표적으로 여러 AWS 서비스와 통합할 수 있다는 장점이 크다.

  • 컨테이너 이미지 저장소 Amazon ECR
  • 로드 분산을 위한 ELB
  • 고가용성 스토리지 서비스를 위한 EBS/EFS
  • 인증 IAM
  • 격리를 위한 Amazon VPC

다만 단점(?)이라고 할 수 있는 부분은 지원 버전이 보통 4개의 마이너 버전 지원(현재 1.22~1.26), 평균 3개월마다 새 버전 제공, 각 버전은 12개월 정도 지원한다는 것이다. 링크open in new window

이 부분이 단점이라고 이야기하는 이유는 실제 서비스를 운영하보면 EKS 클러스터 업그레이드를 하기위한 운영조직 체계가 갖춰져 있지 않은 상황에서 강제로 EKS 구버전에 대한 업그레이드를 해야하는 상황을 직면할 수 있기 때문이다.

물론, 보안, 서비스 지원 등 다양한 이유로 인해 클러스터 업그레이드는 불가피하지만 때때로 업그레이드를 강제해야하는 것은 특정 서비스를 운영하는 조직에게는 큰 걸림돌이 될 수 있다.

기회가 된다면 이번 스터디중에 EKS 업그레이드로 인한 어려움을 겪었던 사례를 발표해보려 한다.

참고 : EKS 업데이트 캘린더 : https://endoflife.date/amazon-eksopen in new window

Amazon EKS 클러스터 구성 방안

EKS 클러스터는 다음과 같은 방식으로 배포할 수 있다.

  • 웹 관리 콘솔
  • eksctl
  • 기타(CDK, CloudFormation, Terraform 등)

이번 EKS 스터디 시리즈에서는 eksctl 을 활용한 배포방식을 활용할 예정이다.

eksctl : EKS 클러스터 구축 및 관리를 하기 위한 오프소스 명령줄 도구 - 링크open in new window

img
img

실습환경 구성

실습환경은 외부에서 접근 가능한 Bastion 역할을 하는 EC2와 퍼블릭 서브넷 2개에 워커노드 두 대를 구성한다.

참고 : 실습환경 변경 챕터에서 노드를 3대로 증설예정

  • 실습환경 아키텍처(출처:가시다님 스터디)
img
  • Cloudformantion을 통한 Bastion 노드 배포

간단하게 VPC, Security Group, EC2 등을 구성하는 CF 코드를 통해 사전 환경을 구성한다.

aws cloudformation deploy --template-file myeks-1week.yaml \
+     --stack-name myeks --parameter-overrides KeyName=hw-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
+
+Waiting for changeset to be created..
+Waiting for stack create/update to complete
+Successfully created/updated stack - myeks
+
+# Public IP 확인
+aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[*].OutputValue' --output text
+13.124.14.182
+
+# ec2 에 SSH 접속
+ssh -i ~/.ssh/id_rsa ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
+
  • VPC 구성 확인
img
img

필자의 경우에는 AWS IAM 계정 정책에 대한 제약사항이 있어, aws configure 명령등으로 access_key, secret_key 설정을 하지않고 EC2 인스턴스에 admin 권한을 부여하여 사용하였다.

  • Cloudformation 으로 배포한 myeks-host인스턴스에 eks-admin 이라고하는 admin 권한의 역할을 생성 후 부여
img
img
  • 환경변수 설정 및 EKS 클러스터 배포
# EKS 배포할 VPC 정보 확인
+export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" | jq -r .Vpcs[].VpcId)
+echo "export VPCID=$VPCID" >> /etc/profile
+echo VPCID
+
+## 퍼블릭 서브넷 ID 확인
+export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
+export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
+echo "export PubSubnet1=$PubSubnet1" >> /etc/profile
+echo "export PubSubnet2=$PubSubnet2" >> /etc/profile
+echo $PubSubnet1
+echo $PubSubnet2
+
+# EKS 클러스터 배포
+eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium --node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.24 --ssh-access --external-dns-access --verbose 4
+

EKS 클러스터를 명령형으로 배포하지 않고 YAML로 작성하여 언언적으로 배포하는 것도 가능하다. 다음은 앞서 실행한 eksctl create cluster 명령의 --dry-run 옵션을 통해 추출한 명세이다.

  • myeks-sample.yaml
apiVersion: eksctl.io/v1alpha5
+cloudWatch:
+  clusterLogging: {}
+iam:
+  vpcResourceControllerPolicy: true
+  withOIDC: false
+kind: ClusterConfig
+kubernetesNetworkConfig:
+  ipFamily: IPv4
+managedNodeGroups:
+- amiFamily: AmazonLinux2
+  desiredCapacity: 2
+  disableIMDSv1: false
+  disablePodIMDS: false
+  iam:
+    withAddonPolicies:
+      albIngress: false
+      appMesh: false
+      appMeshPreview: false
+      autoScaler: false
+      awsLoadBalancerController: false
+      certManager: false
+      cloudWatch: false
+      ebs: false
+      efs: false
+      externalDNS: true
+      fsx: false
+      imageBuilder: false
+      xRay: false
+  instanceSelector: {}
+  instanceType: t3.medium
+  labels:
+    alpha.eksctl.io/cluster-name: myeks
+    alpha.eksctl.io/nodegroup-name: myeks-nodegroup
+  maxSize: 2
+  minSize: 2
+  name: myeks-nodegroup
+  privateNetworking: false
+  releaseVersion: ""
+  securityGroups:
+    withLocal: null
+    withShared: null
+  ssh:
+    allow: true
+    publicKeyPath: ~/.ssh/id_rsa.pub
+  tags:
+    alpha.eksctl.io/nodegroup-name: myeks-nodegroup
+    alpha.eksctl.io/nodegroup-type: managed
+  volumeIOPS: 3000
+  volumeSize: 30
+  volumeThroughput: 125
+  volumeType: gp3
+metadata:
+  name: myeks
+  region: ap-northeast-2
+  version: "1.24"
+privateCluster:
+  enabled: false
+  skipEndpointCreation: false
+vpc:
+  autoAllocateIPv6: false
+  cidr: 192.168.0.0/16
+  clusterEndpoints:
+    privateAccess: false
+    publicAccess: true
+  id: vpc-0521fc003559b2f2c
+  manageSharedNodeSecurityGroupRules: true
+  nat:
+    gateway: Disable
+  subnets:
+    public:
+      ap-northeast-2a:
+        az: ap-northeast-2a
+        cidr: 192.168.1.0/24
+        id: subnet-0fdff27653277aaf0
+      ap-northeast-2c:
+        az: ap-northeast-2c
+        cidr: 192.168.2.0/24
+        id: subnet-084a8752d4c7ddf6c
+
  • EKS 클러스터 배포 확인

정상적으로 클러스터가 구성된 것을 확인할 수 있다.

# eks클러스터 확인
+eksctl get cluster
+NAME	REGION		EKSCTL CREATED
+myeks	ap-northeast-2	True
+
+# 노드확인
+kubectl get node -v=6
+I0423 22:10:48.050969    2339 loader.go:374] Config loaded from file:  /root/.kube/config
+
+I0423 22:10:48.880262    2339 round_trippers.go:553] GET https://6E205513BA73EEBC3CA693BADEEC5294.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 819 milliseconds
+NAME                                               STATUS   ROLES    AGE   VERSION
+ip-192-168-1-139.ap-northeast-2.compute.internal   Ready    <none>   61m   v1.24.11-eks-a59e1f0
+ip-192-168-2-225.ap-northeast-2.compute.internal   Ready    <none>   61m   v1.24.11-eks-a59e1f0
+
+# 파드확인
+k get pods -A
+NAMESPACE     NAME                      READY   STATUS    RESTARTS   AGE
+kube-system   aws-node-2bpxr            1/1     Running   0          62m
+kube-system   aws-node-s7p5b            1/1     Running   0          62m
+kube-system   coredns-dc4979556-knkkh   1/1     Running   0          68m
+kube-system   coredns-dc4979556-m789b   1/1     Running   0          68m
+kube-system   kube-proxy-lkp8f          1/1     Running   0          62m
+kube-system   kube-proxy-z6hbk          1/1     Running   0          62m
+

참고 : eksctl 명령 예제

# eksctl help
+eksctl
+eksctl create
+eksctl create cluster --help
+eksctl create nodegroup --help
+

실습환경 변경

앞선 과정을 통해 실습을 위한 클러스터 구성이 완성되었다. 필자는 향후 샘플 애플리케이션으로 Vault, Consul 등을 배포할 예정이다. 때문에, 최소 3대 이상의 노드가 필요하여 기본 실습 노드를 3대로 구성한다.

EKS는 nodegraup 개수의 최소/최대 개수를 선언적으로 관리할 수 있다. 다음은 노드 개수를 변경/확인 하는 방법이다.

# eks 노드 그룹 정보 확인
+eksctl get nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup
+CLUSTER	NODEGROUP	STATUS		CREATED			MIN SIZE	MAX SIZE	DESIRED CAPACITY	INSTANCE TYPE	IMAGE ID	ASG NAME							TYPE
+myeks	myeks-nodegroup	UPDATING	2023-04-23T12:07:57Z	2		2		2			t3.medium	AL2_x86_64	eks-myeks-nodegroup-fcc3d701-b90a-9c83-7907-fca8459770b9	managed
+
+# 노드 2개 → 3개 증가
+eksctl scale nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup --nodes 3 --nodes-min 3 --nodes-max 6
+
+# 노드 그룹 변경 확인
+eksctl get nodegroup --cluster myeks --region ap-northeast-2 --name myeks-nodegroup
+CLUSTER	NODEGROUP	STATUS		CREATED			MIN SIZE	MAX SIZE	DESIRED CAPACITY	INSTANCE TYPE	IMAGE ID	ASG NAME							TYPE
+myeks	myeks-nodegroup	UPDATING	2023-04-23T12:07:57Z	3		6		3			t3.medium	AL2_x86_64	eks-myeks-nodegroup-fcc3d701-b90a-9c83-7907-fca8459770b9	managed
+
+# 노드 확인
+kubectl get nodes -o wide
+
+NAME                                               STATUS   ROLES    AGE    VERSION                INTERNAL-IP     EXTERNAL-IP      OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
+ip-192-168-1-139.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0   192.168.1.139   43.201.51.34     Amazon Linux 2   5.10.176-157.645.amzn2.x86_64   containerd://1.6.19
+ip-192-168-1-76.ap-northeast-2.compute.internal    Ready    <none>   53s    v1.24.11-eks-a59e1f0   192.168.1.76    13.124.158.208   Amazon Linux 2   5.10.176-157.645.amzn2.x86_64   containerd://1.6.19
+ip-192-168-2-225.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0   192.168.2.225   52.79.236.227    Amazon Linux 2   5.10.176-157.645.amzn2.x86_64   containerd://1.6.19
+
+kubectl get nodes -l eks.amazonaws.com/nodegroup=$CLUSTER_NAME-nodegroup
+NAME                                               STATUS   ROLES    AGE    VERSION
+ip-192-168-1-139.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0
+ip-192-168-1-76.ap-northeast-2.compute.internal    Ready    <none>   74s    v1.24.11-eks-a59e1f0
+ip-192-168-2-225.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0
+

샘플 애플리케이션 배포

필자는 본 글을 작성하던 시기에 고객사 환경에 ArgoCD + Helm을 기반으로 Consul Cluster 구성 테스트 요청이 있어 해당 클러스터를 활용해 보았다.

img
img

ArgoCD 배포

참고 : PKOS 2기에 사용한 ArgoCD 배포 가이드를 참고하여 배포한다.

  • argocd-application-controller : 실행 중인 k8s 애플리케이션의 설정과 깃 저장소의 소스 파일에 선언된 상태를 서로 비교하는 컨트롤러. 상태와 다르면 OutOfSync 에러를 출력.
  • argocd-dex-server : 외부 사용자의 LDAP 인증에 Dex 서버를 사용할 수 있음
  • argocd-repo-server : 원격 깃 저장소의 소스 코드를 아르고시디 내부 캐시 서버에 저장합니다. 디렉토리 경로, 소스, 헬름 차트 등이 저장.
# 네임스페이스 생성
+kubectl create ns argocd
+
+# argocd-helm 설치
+cd
+helm repo add argo https://argoproj.github.io/argo-helm
+helm repo update
+helm install argocd argo/argo-cd --set server.service.type=LoadBalancer --namespace argocd --version 5.19.14
+
+# 확인
+helm list -n argocd
+kubectl get pod,pvc,svc,deploy,sts -n argocd
+kubectl get-all -n argocd
+
+kubectl get crd | grep argoproj
+applications.argoproj.io              2023-03-19T11:39:26Z
+applicationsets.argoproj.io           2023-03-19T11:39:26Z
+appprojects.argoproj.io               2023-03-19T11:39:26Z
+
+# admin 계정의 암호 확인
+ARGOPW=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
+echo $ARGOPW
+mf8bOtNEq7iHMqq1
+
+# 웹 접속 로그인 (admin) CLB의 hostname으로 접속
+k get svc -n argocd argocd-server -o jsonpath='{.status.loadBalancer.ingress[].hostname}'
+
  • ArgoCD Web UI 화면
img
img

GitHub 저장소 준비

필자는 개인 GitHub에 개인 퍼블릭 저장소open in new window를 만들어서 실습을 진행하였다.

사전에 로컬에서 다운로드 받은 Consul Helm Chart 파일에 새로 재정의 할 values 파일을 GitHub 저장소에 업로드 한다.

img
img
  • override-values.yaml

참고 : 다음은 실제 작성된 Values 파일이다. 각 항목에 대한 상세한 설명은 향후 Consul Deploy on K8s 가이드를 작성해서 업로드 예정이다.

client:
+  grpc: true
+connectInject:
+  consulNamespaces:
+    mirroringK8S: true
+  enabled: true
+controller:
+  enabled: true
+global:
+  acls:
+    manageSystemACLs: true
+  enableConsulNamespaces: true
+  enterpriseLicense:
+    secretKey: key
+    secretName: license
+  gossipEncryption:
+    autoGenerate: true
+  image: hashicorp/consul-enterprise:1.13.7-ent
+  imageEnvoy: envoyproxy/envoy:v1.22.5
+  imageK8S: hashicorp/consul-k8s-control-plane:0.49.5
+  metrics:
+    enabled: false
+  tls:
+    enableAutoEncrypt: true
+    enabled: true
+    httpsOnly: false
+    verify: false
+ingressGateways:
+  defaults:
+    replicas: 1
+    service:
+      type: LoadBalancer
+  enabled: true
+  gateways:
+  - name: ingress-gateway
+meshGateway:
+  enabled: false
+  replicas: 1
+  service:
+    enabled: true
+    nodePort: 32000
+    type: NodePort
+server:
+  replicas: 3
+terminatingGateways:
+  defaults:
+    replicas: 1
+  enabled: false
+ui:
+  enabled: true
+  service:
+    port:
+      http: 80
+      https: 443
+    type: LoadBalance
+

ArgoCD + GitHub 연동

ArgoCD CLI를 통해 필자의 GitHub을 연동한다

argocd login <argocd 주소> --username admin --password $ARGOPW
+...
+'admin:login' logged in successfully
+
+argocd repo add https://github.com/<깃헙 계정명>/<레파지토리명> --username <깃헙 계정명> --password <깃헙 계정 암호>
+ 
+argocd repo list
+TYPE  NAME  REPO                                   INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
+git         https://github.com/chosam2/gitops.git  false     false  false  true   Successful
+
+# 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
+argocd cluster list
+SERVER                          NAME        VERSION  STATUS      MESSAGE  PROJECT
+https://kubernetes.default.svc  in-cluster  1.24+    Successful
+

ArgoCD Application 생성 및 배포

앞서 생성한 GitHub 저장소에 업로드한 consul helm values 파일을 통해 배포하기 위해 Application CRD를 생성 및 배포한다.

  • consul-helm-argo-application.yaml
apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+  name: consul-helm
+  namespace: argocd
+  finalizers:
+  - resources-finalizer.argocd.argoproj.io
+spec:
+  destination:
+    namespace: consul
+    server: https://kubernetes.default.svc
+  project: default
+  source:
+    repoURL: https://github.com/chosam2/gitops.git
+    path: argocd
+    targetRevision: main
+    helm:
+      valueFiles:
+      - override-values.yaml
+  syncPolicy:
+    syncOptions:
+    - CreateNamespace=true
+
  • Application CRD 배포
k apply -f consul-helm-argo-application.yaml
+application.argoproj.io/consul-helm created
+

최초 배포 시 OutOfSync 상태로 배포되었지만, 동기화 버튼을 클릭하여 강제로 동기화해준 뒤 정상적으로 배포된 것을 확인할 수 있다.
다만, 최초 OutOfSync 상태로 배포되는 부분에 대해서는 Application YAML 작성 시 옵션을 통해 해결이 가능하지만, 실제 운영시 영향도 체크가 필요해 보인다. 이 부분은 다음 블로깅시에 조금 더 테스트 및 확인해볼 예정이다.

  • 최초 구성 후 OutOfSync 상태
img
img
  • 강제 동기화 이후 Sync 된 화면
img
img
  • 실제 배포된 Consul UI 화면
img
img

마무리

1주차 스터디는 EKS에 대한 전반적인 컨셉과 기본적으로 클러스터를 구성하고 Consul Cluster를 간단하게 배포해보았다. 다음주에는 네트워킹을 주제로 찾아올 예정이다.

+ + + diff --git a/02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.html b/02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.html new file mode 100644 index 0000000000..a0219cc548 --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.html @@ -0,0 +1,316 @@ + + + + + + + + + + AEWS 2주차 - Amzaon EKS Networking | docmoa + + + + + +
본문으로 건너뛰기

AEWS 2주차 - Amzaon EKS Networking

약 4 분KubernetesEKSPKOS

AEWS 2주차 - Amzaon EKS Networking

이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

img
img

0. 실습환경 준비

2주차 부터는 원클릭으로 EKS 실습환경을 배포할 수 있는 코드를 사용한다. 필자는 사용중인 AWS IAM 권한 제약사항으로 기존 CF 코드를 변경하여 베스천용 EC2에 관리자 권한을 위임하여 배포할 예정이다.

참고 : https://cloudkatha.com/attach-an-iam-role-to-an-ec2-instance-with-cloudformation/open in new window

  • 원클릭 배포
# YAML 파일 다운로드
+curl -O https://gist.githubusercontent.com/hyungwook0221/238d96b3b751362cc03ea40494d15313/raw/49de0a9056688b206a41349fc90727d2375f4f02/aews-eks-oneclick-with-ec2-profile.yaml
+
+# CloudFormation 스택 배포
+# aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 MyIamUserAccessKeyID=<IAM User의 액세스키> MyIamUserSecretAccessKey=<IAM User의 시크릿 키> ClusterBaseName='<eks 이름>' --region ap-northeast-2
+예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=hw-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 ClusterBaseName=myeks --region ap-northeast-2 --capabilities CAPABILITY_NAMED_IAM
+
+# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
+aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text
+
+# 마스터노드 SSH 접속
+ssh -i ~/.ssh/kp-gasida.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
+

1. AWS VPC CNI

일반적으로 Calico와 같은 K8s CNI의 경우는 Node - Pod의 IP 대역이 다르지만 AWS VPC CNI의 경우에는 Node-Pod 대역을 동일하게 해서 통신이 가능하도록 구성할 수 있다.

일반적으로 Outer 패킷을 감싸서 오버레이로 통신하지만 AWS VPC CNI는 오히려 심플한 구조를 가진다. 이로인해 간단하고 효율적인 통신이 가능하다!

K8s Calico CNI vs AWS VPC CNI 비교

  • 네트워크 통신의 최적화(성능, 지연)를 위해서 노드와 파드의 네트워크 대역을 동일하게 설정 (그림출처-가시다)
img
  • 파드간 통신 시
    • K8S CNI는 오버레이(VXLAN, IP-IP 등) 통신
    • AWS VPC CNI는 동일 대역으로 직접 통신
img
  • 노드간 통신(추후 업데이트)
  • 파드간 통신(추후 업데이트)

2. Service & AWS LoadBalancer Controller

K8s 환경에서는 내/외부 통신을 위한 서비스를 크게 3가지 형태로 제공한다.

  • Cluster IP
  • NodePort
  • LoadBalancer

필자는 그 중에서 LoadBalancer 타입을 AWS 환경에서 어떻게 활용할 수 있는지를 집중적으로 확인하고 Consul 샘플 예제와 함께 적용해볼 예정이다.

3. LoadBalancer NLB 모드

LoadBalancer 배포 시 NLB 모드는 다음 두 가지 유형을 사용할 수 있다.

유형1. 인스턴스

  1. externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
  2. externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP 유지, 워커 노드의 iptables 사용함
img

유형2. IP

참고 : 반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요함!

  1. Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가능
  2. Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)
img

AWS LoadBalancer Controller 배포 with IRSA

참고 : AWS Load Balancer Controller 추가 기능 설치open in new window

# AWSLoadBalancerControllerIAMPolicy 생성
+curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
+aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
+
+# 업데이트가 필요한 경우 
+# aws iam update-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
+
+# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 
+eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \
+--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
+
+## IRSA 정보 확인
+eksctl get iamserviceaccount --cluster $CLUSTER_NAME
+
+## 서비스 어카운트 확인
+kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh
+
+# Helm Chart 설치
+helm repo add eks https://aws.github.io/eks-charts
+helm repo update
+helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
+  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
+  
+# 설치 확인
+kubectl get crd
+kubectl get deployment -n kube-system aws-load-balancer-controller
+kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
+  Service Account:  aws-load-balancer-controller
+ 
+# 클러스터롤, 클러스터 롤바인딩 확인
+kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
+kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
+
  • 생성된 ClusterRole 확인

AWS LoadBalancer Controller가 동작하기 위해 필요한 SA를 생성 후 연결된 ClusterRole과 ClusterRoleBinding을 화인

img
img

샘플 애플리케이션 테스트

LoadBalancer 타입의 서비스와 및 파드를 배포하고 NLB 모드에 따라서 Client IP가 어떻게 확인되는지 확인해본다.

# 모니터링
+watch -d kubectl get pod,svc,ep
+
+# 작업용 EC2 - 디플로이먼트 & 서비스 생성
+curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
+cat echo-service-nlb.yaml | yh
+kubectl apply -f echo-service-nlb.yaml
+
+# 파드 로깅 모니터링
+kubectl logs -l app=deploy-websrv -f
+
+# 분산 접속 확인
+NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
+curl -s $NLB
+
  • NLB 확인 : Target IP 정보 확인(UI)
img
img

NLB에 등록된 Target IP 정보는 생성된 샘플 Pod의 IP인 것을 확인할 수 있다.

img
img

이제 NLB를 통해서 Pod를 호출할 경우 Client IP가 어떻게 확인되는지 확인해보자.

  • Client IP 확인 결과
img
img

다음 정보는 각 Node의 정보가 아닌 다른 IP 정보가 확인된다.

  • 각 Node의 IP 정보 확인
img
img

그렇다면 Client IP의 정체는? 바로 NLB에 할당된 네트워크 인터페이스의 IP 이다.

img
img

이제 실제로 Client IP를 추적하기 위한 방법을 알아본다.

NLB IP Target & Proxy Protocol v2 활성화

앞선 실습에서 NLB로 SNAT되어서Client IP 확인되지 못하는 것을 확인하였다. 이번에는 Proxy Protocol v2을 활성화 하여 IP 정보를 유지하는 방법을 알아본다. (이미지 출처 : 가시다님 스터디)

img
  • 샘플코드 배포

이때 중요한 부분은 SVC 생성 시 service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" 어노테이션을 활성화 하는 것이다.

# 생성
+cat <<EOF | kubectl create -f -
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gasida-web
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gasida-web
+  template:
+    metadata:
+      labels:
+        app: gasida-web
+    spec:
+      terminationGracePeriodSeconds: 0
+      containers:
+      - name: gasida-web
+        image: gasida/httpd:pp
+        ports:
+        - containerPort: 80
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: svc-nlb-ip-type-pp
+  annotations:
+    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
+    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
+    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
+    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
+spec:
+  ports:
+    - port: 80
+      targetPort: 80
+      protocol: TCP
+  type: LoadBalancer
+  loadBalancerClass: service.k8s.aws/nlb
+  selector:
+    app: gasida-web
+EOF
+
+---
+
+# apache에 proxy protocol 활성화 확인
+kubectl exec deploy/gasida-web -- apachectl -t -D DUMP_MODULES
+kubectl exec deploy/gasida-web -- cat /usr/local/apache2/conf/httpd.conf
+
+# 접속 확인
+NLB=$(kubectl get svc svc-nlb-ip-type-pp -o jsonpath={.status.loadBalancer.ingress[0].hostname})
+curl -s $NLB
+
+# 지속적인 접속 시도
+while true; do curl -s --connect-timeout 1 $NLB; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
+
+# 로그 확인
+kubectl logs -l app=gasida-web -f
+
  • Client IP 확인

IP를 확인해본 결과 동일한 공인 IP로 찍히는 것으로 확인된다.

img
img

그렇다면 해당 IP는 무엇일까? 바로 현재 curl -s 명령을 수행한 Bastion 노드의 정보이다.

img
img

이렇게 NLB를 통해 호출하더라도 정상적으로 Client IP를 유지하는 방법을 알아보았다. 실제로 온프레미스 환경에서 3-Tier 기반의 WEB/WAS를 구성하다 보면 Client IP를 유지하기 위해 XFF 설정을 하는 것이 일반적이다. 다만, NLB의 경우에는 L4 계층까지만 패킷에 대한 이해와 분석이 가능하므로 Proxy Protocol을 써야한다는 새로운 정보를 알 수 있는 좋은 기회였다.

4. Consul IngressGateway 샘플예제

다음 예제는 Consul IngressGateway를 통한 ServiceMesh의 단일 진입점을 테스트해볼 예정이다. Consul 1.15x 버전에는 Envoy의 Access Log 기능이 추가되어 이번 스터디를 통해 학습한 NLB의 IP 유지방안에 대한 테스트를 진행해본다.

참고 : Consul Gateway에서 envoy access log 활성화 기능

실습1. NLB IP Target & Proxy Protocol v2 비활성화

1) Consul deploy via Helm

  • Consul Chart YAML

처음 설정시에는 PPv2를 사용하지 않고 NLB를 적용해볼 예정이다. => Client IP가 어떻게 찍히는지 확인!

client:
+  grpc: true
+connectInject:
+  consulNamespaces:
+    mirroringK8S: true
+  enabled: true
+controller:
+  enabled: true
+global:
+  acls:
+    manageSystemACLs: true
+  enableConsulNamespaces: true
+  enterpriseLicense:
+    secretKey: key
+    secretName: license
+  gossipEncryption:
+    autoGenerate: true
+  image: hashicorp/consul-enterprise:1.15.1-ent
+  #imageEnvoy: envoyproxy/envoy:v1.22.5
+  #imageK8S: hashicorp/consul-k8s-control-plane:0.49.5
+  metrics:
+    enabled: false
+  tls:
+    enableAutoEncrypt: true
+    enabled: true
+    httpsOnly: false
+    verify: false
+ingressGateways:
+  defaults:
+    replicas: 1
+    service:
+      type: LoadBalancer
+      annotations: |
+        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
+        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
+        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
+        #service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
+  enabled: true
+  gateways:
+  - name: ingress-gateway
+meshGateway:
+  enabled: false
+  replicas: 1
+  service:
+    enabled: true
+    nodePort: 32000
+    type: NodePort
+server:
+  replicas: 3
+terminatingGateways:
+  defaults:
+    replicas: 1
+  enabled: false
+ui:
+  enabled: true
+  service:
+    port:
+      http: 80
+      https: 443
+    type: LoadBalancer
+

2) Consul CRD 배포

  • IngressGateway
apiVersion: consul.hashicorp.com/v1alpha1
+kind: IngressGateway
+metadata:
+  name: ingress-gateway
+spec:
+  listeners:
+    - port: 8080
+      protocol: http
+      services:
+        - name: static-server
+
  • ProxyDefaults

spec.accessLogs를 통해 AccessLog 활성화 및 파일경로 추가

apiVersion: consul.hashicorp.com/v1alpha1
+kind: ProxyDefaults
+metadata:
+  name: global
+spec:
+  accessLogs:
+    enabled: true
+#    type: file
+#    path: "/var/log/envoy/access-logs.txt" 
+
  • ServiceDefaults
apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceDefaults
+metadata:
+  name: static-server
+spec:
+  protocol: http
+
  • ServiceIntentions
apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: static-server
+spec:
+  destination:
+    name: static-server
+  sources:
+    - name: ingress-gateway
+      action: allow
+
  • 샘플 SVC/Deploy : static-server
apiVersion: v1
+kind: Service
+metadata:
+  name: static-server
+spec:
+  selector:
+    app: static-server
+  ports:
+    - protocol: TCP
+      port: 80
+      targetPort: 8080
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: static-server
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: static-server
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: static-server
+  template:
+    metadata:
+      name: static-server
+      labels:
+        app: static-server
+      annotations:
+        'consul.hashicorp.com/connect-inject': 'true'
+    spec:
+      containers:
+        - name: static-server
+          image: hashicorp/http-echo:latest
+          args:
+            - -text="hello world"
+            - -listen=:8080
+          ports:
+            - containerPort: 8080
+              name: http
+      serviceAccountName: static-server
+

3) 샘플 애플리케이션 호출

  • IngressGateway에 연결된 NLB 주소로 호출
EXTERNAL_IP=$(kubectl get services --selector component=ingress-gateway --output jsonpath="{range .items[*]}{@.status.loadBalancer.ingress[*].hostname}{end}")
+echo "Connecting to \"$EXTERNAL_IP\""
+curl --header "Host: static-server.ingress.consul" "http://$EXTERNAL_IP:8080"
+

호출결과 앞서 실습에서 확인해본 것과 동일하게 NLB IP Target & Proxy Protocol v2 비활성화 일 경우에는 로드밸런서 인터페이스 IP가 확인된다.

img
img
  • NLB 인터페이스 IP
img
img

실습2. NLB IP Target & Proxy Protocol v2 활성화

1) Consul deploy via Helm

이번에는 위와 동일하지만 NLB의 어노테이션만 PPv2를 활성화 한다.

  • service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
#(생략)
+ingressGateways:
+  defaults:
+    replicas: 1
+    service:
+      type: LoadBalancer
+      annotations: |
+        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
+        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
+        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
+		    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
+  enabled: true
+  gateways:
+  - name: ingress-gateway
+#(생략)
+

2) Consul CRD 배포(생략)

위와 동일하게 사용

3) 샘플 애플리케이션 호출

  • IngressGateway에 연결된 NLB 주소로 호출
EXTERNAL_IP=$(kubectl get services --selector component=ingress-gateway --output jsonpath="{range .items[*]}{@.status.loadBalancer.ingress[*].hostname}{end}")
+echo "Connecting to \"$EXTERNAL_IP\""
+curl --header "Host: static-server.ingress.consul" "http://$EXTERNAL_IP:8080"
+

하지만 PPv2 설정 후 static-server 앱을 테스트해본 결과 정상적으로 동작하지 않는 것으로 보인다.

img
img

위와 관련해서 확인해본 결과 Istio의 경우에는 EnvoyFilter 등을 통해 해결하는 방안(?)이 있는 것으로 보이며, 일반적으로 PPv2를 사용하기 위해서는 애플리케이션 단에서 사용할 수 있도록 설정이 필요한 것으로 확인되었다.

참고 :

📌 정보공유:
해당 이슈에 대하여 Consul Product Manager를 통해 FR(Feture Request)로 등록 후 신규 기능으로 추가할 수 있도록 지원할 것으로 답변받았다. 추후 업데이트에 대한 변동사항이 있으면 할 예정이다.

img

위에서 언급한 것 처럼 Consul Native하게는 PPv2 기능을 사용하기 어려운 상황이라 Apache 애플리케이션에서 PPv2 설정을 통해 해결이 가능한지 확인을 해보았다.

확인결과 결과적으로 테스트가 불가능 한 것으로 확인되었다. Apache 애플리케이션에 PPv2를 활성화 하고 Consul CRD를 적용하여 IngressGateway에서 호출하였으나, 400 에러가 발생한다. (NLB PPv2 활성화 시 발생)

아쉽지만 본 테스트는 FR이 진행된 이후에 업데이트 하도록 하겠다.

+ + + diff --git a/02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.html b/02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.html new file mode 100644 index 0000000000..b2e4f3620e --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.html @@ -0,0 +1,40 @@ + + + + + + + + + + AEWS 3주차 - Amzaon EKS Storage | docmoa + + + + + +
본문으로 건너뛰기

AEWS 3주차 - Amzaon EKS Storage

1분 미만

+ + + diff --git a/02-PrivatePlatform/Kubernetes/06-EKS/index.html b/02-PrivatePlatform/Kubernetes/06-EKS/index.html new file mode 100644 index 0000000000..cf575aaf14 --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/06-EKS/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 06 EKS | docmoa + + + + + +
본문으로 건너뛰기

06 EKS

1분 미만

+ + + diff --git a/02-PrivatePlatform/Kubernetes/index.html b/02-PrivatePlatform/Kubernetes/index.html new file mode 100644 index 0000000000..90fb425d5e --- /dev/null +++ b/02-PrivatePlatform/Kubernetes/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Kubernetes | docmoa + + + + + +
본문으로 건너뛰기

Kubernetes

1분 미만

+ + + diff --git a/02-PrivatePlatform/OpenShift/deploying_specificnode_by_namespace.html b/02-PrivatePlatform/OpenShift/deploying_specificnode_by_namespace.html new file mode 100644 index 0000000000..a271320f39 --- /dev/null +++ b/02-PrivatePlatform/OpenShift/deploying_specificnode_by_namespace.html @@ -0,0 +1,58 @@ + + + + + + + + + + OpenShift 3.x - 프로젝트 별로 특정 노드에 배포하기 | docmoa + + + + + +
본문으로 건너뛰기

OpenShift 3.x - 프로젝트 별로 특정 노드에 배포하기

1분 미만openshiftocp

OpenShift 3.x - 프로젝트 별로 특정 노드에 배포하기

원문 : https://blog.openshift.com/deploying-applications-to-specific-nodes/open in new window

Deployment나 Deployment Config에서 Nodeselect를 지정하는 방법 외에 Project 단위로 설정하는 방법을 설명합니다.

  1. Node Label확인

    ~$ oc get node --show-labels
    +
  2. Node에 Label 업데이트

    Label 업데이트 (링크open in new window)

    ~$ oc label node [노드이름] region=primary
    +

    primary는 구분자 입니다. 예로

    region=tk 이런식으로 테스트계 설정을 하게 됩니다.

    ex)

    To add a label to a node or pod:

    # oc label node node001.krenger.ch mylabel=myvalue
    +# oc label pod mypod-34-g0f7k mylabel=myvalue
    +

    To remove a label (in the example “mylabel”) from a node or pod:

    # oc label node node001.krenger.ch mylabel-
    +# oc label pod mypod-34-g0f7k mylabel-
    +
  3. Master에 기본 NodeSelector Label 지정

    Master 노드의 defaultNodeSelector 설정을 변경하고 Master서비스를 재시작 합니다.

    NodeSelector가 없는 경우 Pod가 Deploy되는 Node 입니다.

    대상 파일: /etc/origin/master/master-config.yaml

    projectConfig:
    +  defaultNodeSelector: "region=primary” 
    +

    Master Node 재기동 필요

    ~$ systemctl restart atomic-openshift-master
    +~$ systemctl restart atomic-openshift-node
    +
  4. 프로젝트 Node Selector 설정

    ~$ oc edit namespace [프로젝트이름]
    +
    ...
    +  annotations:
    +    openshift.io/node-selector: “region=primary"
    +    openshift.io/description: ""
    +    openshift.io/display-name: ""
    +    openshift.io/node-selector: “"
    +...    
    +

Appendix

  • 위 과정만으로 Project 에 대한 기본 Node Selector가 설정되어야 하며, 이같이 설정되지 않는 경우 각 Pod 별로, 혹은 template에서 설정해야합니다.
+ + + diff --git a/02-PrivatePlatform/OpenShift/index.html b/02-PrivatePlatform/OpenShift/index.html new file mode 100644 index 0000000000..2fe0775b33 --- /dev/null +++ b/02-PrivatePlatform/OpenShift/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Open Shift | docmoa + + + + + +
본문으로 건너뛰기

Open Shift

1분 미만

+ + + diff --git a/02-PrivatePlatform/OpenShift/openshift3.11_custom_metric_with_jboss.html b/02-PrivatePlatform/OpenShift/openshift3.11_custom_metric_with_jboss.html new file mode 100644 index 0000000000..0c9a80126f --- /dev/null +++ b/02-PrivatePlatform/OpenShift/openshift3.11_custom_metric_with_jboss.html @@ -0,0 +1,520 @@ + + + + + + + + + + OpenShift 3.11 - Custom metric with JBoss | docmoa + + + + + +
본문으로 건너뛰기

OpenShift 3.11 - Custom metric with JBoss

약 6 분openshiftocpjboss

OpenShift 3.11 - Custom metric with JBoss

Autoscaling applications using custom metrics on OpenShift Container Platform 3.11 with JBoss EAP or Wildfly

Red Hat OpenShift Container Platform 3.11 (OCP) 은 기본적으로 CPU에 대한 애플리케이션 자동 확장을 지원합니다. 추가적으로 apis/autoscaling/v2beta1를 활성화하여 Memory의 메트릭을 기반으로 한 기능도 지원 합니다. CPU나 Memory의 경우 애플리케이션에 종속되지 않은 기본적인 메트릭이나, 때로는 추가적인 메트릭 요소를 기반으로 확장할 필요성이 있습니다.

Prometheus Adaptor를 사용하면 기본 메트릭 외에도 사용자가 지정한 애플리케이션의 메트릭을 기반으로 자동확장하는 기능을 추가 할 수 있습니다.

Prometheus Adaptor는 OCP 4.1 부터 에서 Tech Preview 대상이 되었습니다. 기능이 완전해지면 정식 지원상태로 변경 될 것입니다.

Prometheus Adaptor는 custom.metrics.k8s.io API를 구현하여 Prometheus에 연결합니다. Prometheus에서 수집되는 메트릭 기반으로 Horizontal Pod Autoscaler (HPA)가 쿼리하여 애플리케이션 메트릭을 검색할 수 있습니다.

이글은 다음의 과정을 안내 합니다.

  1. JBoss EAP 애플리케이션 샘플과 JMX exporter
  2. OCP 3.11에 Operator를 사용하여 Prometheus를 배포
  3. Prometheus Adapter 설정

각 내용의 상세 정보는 참고 자료의 내용이 도움이 됩니다.

준비 사항 및 조건

  • OpenShift 3.11 클러스터
  • 리소스 생성 권한이 있는 cluster-admin 권한이 있는 계정
  • 리소스 생성 방법을 포함한 OpenShift 기본 지식
  • Kubernetes 에 대한 기본 지식

OpenShift 3.11 Operator 환경 구성

OpenShift Container Platform 3.11 환경에 Operator를 활성화 하기 위한 작업을 수행합니다. 기존에 이미 Operator 구성을 설치 한경우 해당 과정을 넘어갑니다.

  • OCP 3.11 설치를 진행한 hosts 파일의 [OSEv3:vars]항목에 다음을 추가

    openshift_additional_registry_credentials=[{'host':'registry.connect.redhat.com','user':'<your_user_name>','password':'<your_password>','test_image':'mongodb/enterprise-operator:0.3.2'}]
    +
  • registry 추가를 위한 Ansible playbook 실행

    $ cd /usr/share/ansible/openshift-ansible
    +$ ansible-playbook -i <inventory_file> playbooks/updates/registry_auth.yml
    +
  • Operator framework 설치를 위한 Ansible playbook 실행

    $ cd /usr/share/ansible/openshift-ansible
    +$ ansible-playbook -i <inventory_file> playbooks/olm/config.yml
    +

Operator framework가 설치되면 Cluster Console 에서 좌측 메뉴에 추가된 Operators 를 확인 할 수 있습니다.

image-20200129150607944
image-20200129150607944

애플리케이션 샘플과 JMX exporter

JMX export는 Prometheus로 Java 기반의 애플리케이션에서 수집 가능한 JMX의 mBean을 전달가능하도록 하는 수집기 입니다. Java 애플리케이션과 함께 실행되며 HTTP 엔드포인트를 노출시켜 JVM의 메트릭 정보를 제공합니다.

JMX export를 javaagent 방식으로 적용하면 애플리케이션에 별도의 수정이나 추가 코딩 없이 JMX로 수집되는 mBean 값들을 노출 시킬 수 있습니다.

다음의 샘플 애플리케이션을 기반으로 설명합니다.

https://github.com/Great-Stone/webappopen in new window

JBoss EAP 배포

애플리케이션 구조는 다음과 같습니다.

webapp
+├ configuration
+│	├ standalone-openshift.xml
+│	└ jmx_exporter_conf.yaml
+├ modules
+│	└ jmx_prometheus_javaagent-0.12.0.jar
+└ ROOT.war
+

Red Hat에서 제공되는 JBoss EAP는 S2I 빌드 배포를 지원합니다. 애플리케이션 소스 또는 바이너리를 별도의 이미지 빌드(e.g. Docker build) 없이 바로 OpenShift 상에서 사용 가능한 컨테이너 이미지로 빌드하는 기능입니다.

  • root 또는 deployment 디렉토리 내의 바이너리는 JBoss EAP 의 deployments 디렉토리로 복사되어 빌드 됩니다.
  • configuration 디렉토리 내의 파일은 JBoss EAP 의 configuration 디렉토리로 복사되어 빌드 됩니다. 일반적으로 JBoss EAP의 기본 standalone.xml은 해당 S2I이미지 내에서는 standalone-openshift.xml로 대체되었습니다. configuration 디렉토리에 해당 xml 파일을 위치시키면 사용자 정의 xml을 사용할 수 있습니다.
    이같은 특징을 이용하여 jmx-exporter에서 읽어들일 yaml 파일인 jmx_exporter_conf.yaml 파일을 해당 디렉토리에 위치시켜 S2I 빌드시 빌드 이미지 내에 복사되도록 합니다.
  • modules 디렉토리는 JBoss에서 사용할 module 라이브러리를 추가 할 수 있도록 복사해주는 위치 입니다. jmx_prometheus_javaagent-0.12.0.jar 를 빌드 시 이미지 내부에 복사 할 수 있도록 해당 디렉토리내에 위치시킵니다.

jmx_exporter_conf.yaml

jmx_exporter_conf.yaml 에서 예시로 설정한 내용은 다음과 같습니다.

---
+startDelaySeconds: 30
+lowercaseOutputName: true
+lowercaseOutputLabelNames: true
+whitelistObjectNames: 
+  - "jboss.as:subsystem=request-controller"
+rules:
+  - pattern: "^jboss.as<subsystem=request-controller><>(active_.+|max_.+): (.*)"
+    attrNameSnakeCase: true
+    name: jboss_$1
+    help: "jboss Request Controller : $1"
+    labels:
+      namespace: 'my-eap-project'
+      pod: 'sample-eap'
+      service: 'sample-eap'
+
NameDescription
whitelistObjectNames"jboss.as:subsystem=request-controller"의 경우 mBean 값을 기준으로 확인 가능합니다. jconsole이나 jvisualVM 툴을 사용하여 확인 가능하며, 필요시 로컬이나 리모트의 JBoss EAP / Wildfly에서 원하는 값 정의 가능
patternmBean ObjectName의 Attribute 값의 패턴을 정의 합니다. 단일 또는 복수의 Name을 정의 가능
namejmx_exporter 에서 표기할 이름 규칙을 설정
labelsOpenShift 환경에서 식별할 수 있는 label을 추가합니다. namespace, pod, service 는 기본 JMX exporter로는 수집되지 않으므로 해당 애플리케이션이 배포될 OpenShift 환경에 맞춰 설정

기타 상세 옵션은 다음의 url에서 확인 가능합니다.

https://github.com/prometheus/jmx_exporter#configurationopen in new window

JBoss EAP / Wildfly 배포

  • OpenShift Application Console에서 작업 진행

  • 배포 할 프로젝트를 생성 (e.g. my-eap-project)

  • 해당 프로젝트를 선택 후 좌측 메뉴의 Catalog를 선택

  • JBoss EAP 7.2를 선택

    • Information Next> 클릭
    • Configuration (앞서 jmx_exporter_conf.yaml의 레이블 설정 참고)
    • Results
      • 생성 확인 후 Close 버튼 클릭
  • 좌측 메뉴에서 Builds클릭 후 생성한 sample-eap 확인

  • 좌측 메뉴에서 Applications > Deployments 클릭 후 생성된 Deployment Config sample-eap 클릭

    • Environment 탭을 선택하고 다음을 추가하고 하단 Save 버튼 클릭

      NameValue
      JAVA_OPTS_APPEND-javaagent:/opt/eap/modules/jmx_prometheus_javaagent-0.12.0.jar=58080:/opt/eap/standalone/configuration/jmx_exporter_conf.yaml
      • JAVA_OPTS_APPEND 환경 변수에 값을 기입하면 해당 JBoss EAP 7.2 S2I 빌드 시 실행되는 서버의 Java Option 값 뒤에 해당 값이 추가됨

      • javaagent로 빌드시 해당 컨테이너 이미지 내부로 복사된 modules 디렉토리의 jmx_prometheus_javaagent-0.12.0.jar를 지정하고 =58080은 엔드포인트 포트를 정의

      • 추가로 컨테이너 이미지 내부로 복사된 configuration 디렉토리의 설정 파일인 jmx_exporter_conf.yaml을 정의

    • 우측 상단의 Actions > Edit YAML을 클릭하여 spec > template > metadata > annotationsprometheus.io/scrape: 'true'를 추가하고 하단의 Save 버튼 클릭

      apiVersion: apps.openshift.io/v1
      +kind: DeploymentConfig
      +...
      +spec:
      +  ...
      +  template:
      +    metadata:
      +      annotations:
      +        prometheus.io/scrape: 'true'
      +      creationTimestamp: null
      +      ...
      +
  • 좌측 메뉴에서 Applications > Services 클릭 후 생성된 Service sample-eap 클릭

    • 우측 상단의 Actions > Edit YAML을 클릭하여 prometheus.io/scrape: "true" 항목과 export를 위한 port를 추가하고 하단 Save 버튼을 클릭하여 저장합니다. port는 eap를 위한 서비스를 위한 port와 exporter를 위한 port 두개가 필요함

      apiVersion: v1
      +kind: Service
      +metadata:
      +  annotations:
      +    description: The web server's http port.
      +    prometheus.io/scrape: 'true'
      +  ...
      +spec:
      +  ...
      +  ports:
      +    - name: eap
      +      port: 8080
      +      protocol: TCP
      +      targetPort: 8080
      +    - name: exporter
      +      port: 58080
      +      protocol: TCP
      +      targetPort: 58080
      +  ...
      +
    • Service에 새로운 포트를 추가하면 기존 route를 올바른 포트에 연결하고, exporter의 데이터 확인을 위한 새로운 route를 다음 단계에서 추가

  • 좌측 메뉴에서 Applications > Routes 클릭 후 생성된 route sample-eap 클릭

    • 우측 상단의 Actions > Edit을 클릭 하고 Target Port8080→8080(TCP)임을 확인 후 하단 Save버튼 클릭
    • 다시 Routes 목록 화면으로 이동하여 우측 상단의 Create Route 클릭하여 다음을 설정하고 하단 Save버튼 클릭
      • Name: sample-eap-exporter
      • Target Port: 58080→58080(TCP)
      • Security(선택사항)
        • Secure route: Checked
        • TLS Termination: Edge
        • Insecure Traffic: None
  • JMX exporter가 적용되었는지 확인을 위해 사로 생성한 sample-eap-exporter의 Hostname을 클릭하여 정보 확인

    ...
    +# HELP jboss_active_requests jboss Request Controller : active_requests
    +# TYPE jboss_active_requests untyped
    +jboss_active_requests{namespace="my-eap-project",pod="sample-eap",service="sample-eap",} 0.0
    +# HELP jboss_max_requests jboss Request Controller : max_requests
    +# TYPE jboss_max_requests untyped
    +jboss_max_requests{namespace="my-eap-project",pod="sample-eap",service="sample-eap",} -1.0
    +...
    +
    • jmx_exporter_conf.yaml에서 정의한 JMX 내용이 표기되는지 확인
    • 예제에서는 jboss_를 prefix로 정의하였고 active_*, max_* 항목의 Attribute 데이터를 표기
    • 데이터의 label인 namespace, pod, service가 표기되는지 확인

Setting up Prometheus

Operator framework를 활용하여 애플리케이션을 모니터링 하도록 설정할 수 있습니다. 각 yaml로 작성된 설정은 CLI 또는 OpenShift Console 상에서 진행 할 수 있습니다. 적용하는 각 방법은 OpenShift에 리소스 배포 를 참고하세요.

Prometheus 구성요소를 배포하기 위해 프로젝트를 구성합니다. 여기서는 custom-metric 프로젝트에서 진행합니다.

1) Operator subscription 생성

$ oc create -f - <<EOF
+apiVersion: operators.coreos.com/v1alpha1
+kind: Subscription
+metadata:
+  generateName: prometheus-
+  namespace: custom-metric
+spec:
+  source: rh-operators
+  name: prometheus
+  startingCSV: prometheusoperator.0.22.2
+  channel: preview
+EOF
+

2) Service monitor 생성

oc apply -f - <<EOF
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  name: sample-app
+  labels:
+    app: sample-app
+  namespace: prometheus-metric
+spec:
+  selector:
+    matchLabels:
+      app: sample-app
+    namespaceSelector:
+      matchNames:
+        - my-eap-project
+  endpoints:
+    - port: exporter
+EOF
+

3) Prometheus Operator가 제공하는 사용자 정의 오브젝트 생성

oc apply -f - <<EOF
+apiVersion: monitoring.coreos.com/v1
+kind: Prometheus
+metadata:
+  name: example
+  labels:
+    prometheus: k8s
+  namespace: prometheus-metric
+spec:
+  replicas: 1
+  version: v2.3.2
+  serviceAccountName: prometheus-k8s
+  securityContext: {}
+  serviceMonitorSelector:
+    matchLabels:
+      app: sample-app
+EOF
+

4) Prometheus Adapter가 쿼리 할 수 있도록 Prometheus Pod를 서비스를 작성

$ oc apply -f - <<EOF
+apiVersion: v1
+kind: Service
+metadata:
+  name: prometheus
+spec:
+  ports:
+  - name: web
+    port: 9090
+    protocol: TCP
+    targetPort: web
+  selector:
+    prometheus: example
+EOF
+

Prometheus Adapter setup

Prometheus가 설정된 상태에서 다음 리소스에 대한 설정을 추가하여 Prometheus Adopter 설정합니다. RBAC 접근제어, Adapter 구성, API 확장, Deployment 항목들이 포함되어있습니다.

kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: custom-metrics-apiserver
+  namespace: prometheus-metric
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: custom-metrics-server-resources
+rules:
+- apiGroups:
+  - custom.metrics.k8s.io
+  resources: ["*"]
+  verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: custom-metrics-resource-reader
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - namespaces
+  - pods
+  - services
+  verbs:
+  - get
+  - list
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: custom-metrics:system:auth-delegator
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: system:auth-delegator
+subjects:
+- kind: ServiceAccount
+  name: custom-metrics-apiserver
+  namespace: prometheus-metric
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: custom-metrics-auth-reader
+  namespace: kube-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: extension-apiserver-authentication-reader
+subjects:
+- kind: ServiceAccount
+  name: custom-metrics-apiserver
+  namespace: prometheus-metric
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: custom-metrics-resource-reader
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: custom-metrics-resource-reader
+subjects:
+- kind: ServiceAccount
+  name: custom-metrics-apiserver
+  namespace: prometheus-metric
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: hpa-controller-custom-metrics
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: custom-metrics-server-resources
+subjects:
+- kind: ServiceAccount
+  name: horizontal-pod-autoscaler
+  namespace: prometheus-metric
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: adapter-config
+  namespace: prometheus-metric
+data:
+  config.yaml: |
+    rules:
+    - seriesQuery: '{__name__="jboss_active_requests",namespace!="",pod!=""}'
+      resources:
+        overrides:
+          namespace: {resource: "namespace"}
+          pod: {resource: "pod"}
+          service: {resource: "service"}
+      name:
+        matches: "^(.*)_requests"
+        as: "${1}_avg"
+      metricsQuery: '<<.Series>>{<<.LabelMatchers>>}'
+---
+apiVersion: v1
+kind: Service
+metadata:
+  annotations:
+    service.alpha.openshift.io/serving-cert-secret-name: prometheus-adapter-tls
+  labels:
+    name: prometheus-adapter
+  name: prometheus-adapter
+  namespace: prometheus-metric
+spec:
+  ports:
+  - name: https
+    port: 443
+    targetPort: 6443
+  selector:
+    app: prometheus-adapter
+  type: ClusterIP
+---
+apiVersion: apiregistration.k8s.io/v1beta1
+kind: APIService
+metadata:
+  name: v1beta1.custom.metrics.k8s.io
+spec:
+  service:
+    name: prometheus-adapter
+    namespace: prometheus-metric
+  group: custom.metrics.k8s.io
+  version: v1beta1
+  insecureSkipTLSVerify: true
+  groupPriorityMinimum: 100
+  versionPriority: 100
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: prometheus-adapter
+  name: prometheus-adapter
+  namespace: prometheus-metric
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: prometheus-adapter
+  template:
+    metadata:
+      labels:
+        app: prometheus-adapter
+      name: prometheus-adapter
+    spec:
+      serviceAccountName: custom-metrics-apiserver
+      containers:
+      - name: prometheus-adapter
+        image: directxman12/k8s-prometheus-adapter-amd64
+        args:
+        - --secure-port=6443
+        - --tls-cert-file=/var/run/serving-cert/tls.crt
+        - --tls-private-key-file=/var/run/serving-cert/tls.key
+        - --logtostderr=true
+        - --prometheus-url=http://prometheus-operated:9090/
+        - --metrics-relist-interval=1m
+        - --v=4
+        - --config=/etc/adapter/config.yaml
+        ports:
+        - containerPort: 6443
+        volumeMounts:
+        - mountPath: /var/run/serving-cert
+          name: volume-serving-cert
+          readOnly: true
+        - mountPath: /etc/adapter/
+          name: config
+          readOnly: true
+        - mountPath: /tmp
+          name: tmp-vol
+      volumes:
+      - name: volume-serving-cert
+        secret:
+          secretName: prometheus-adapter-tls
+      - name: config
+        configMap:
+          name: adapter-config
+      - name: tmp-vol
+        emptyDir: {}
+

RBAC의 정의는 Adopter가 동작하는데 필요한 ServiceAccount, ClusterRole, RoleBinding, ClusterRoleBinding 을 작성합니다.

Prometheus 수집 확인

  • Prometheus 구성이 배포된 prometheus-metric 프로젝트에서 수행

  • 좌측 Overview 클릭 후 목록에서 prometheus-example의 route 클릭하여 Prometheus Console 확인

  • Execute우측의 -insert metric at cursor- 목록에서 추가된 jboss_active_requests 항목 선택 후 Execute 클릭 하여 값 확인

    ElementValue
    jboss_active_requests0
    • namespace, pod, service는 jmx_exporter_conf.yaml에서 부여 한 값
    • 기본 jmx-exporter의 경우 pod 정보를 동적으로 가져올 수 없음
    • namespace로 메트릭을 확인

Test automatic scaling

Adopter가 애플리케이션 Metric을 검색할 수 있는지 확인하여 정상적으로 동작하고 있는지 확인이 필요합니다.

  • Adopter가 애플리케이션 Metric 정보를 검색하도록 구성되었는지 확인

    $  oc get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
    +{
    +  "kind": "APIResourceList",
    +  "apiVersion": "v1",
    +  "groupVersion": "custom.metrics.k8s.io/v1beta1",
    +  "resources": [
    +    {
    +      "name": "namespaces/jboss_active_avg",
    +      "singularName": "",
    +      "namespaced": false,
    +      "kind": "MetricValueList",
    +      "verbs": [
    +        "get"
    +      ]
    +    },
    +    {
    +      "name": "pods/jboss_active_avg",
    +      "singularName": "",
    +      "namespaced": true,
    +      "kind": "MetricValueList",
    +      "verbs": [
    +        "get"
    +      ]
    +    },
    +    {
    +      "name": "services/jboss_active_avg",
    +      "singularName": "",
    +      "namespaced": true,
    +      "kind": "MetricValueList",
    +      "verbs": [
    +        "get"
    +      ]
    +    }
    +  ]
    +}
    +
  • 애플리케이션 Metric인 jboss_active_avg가 검색되는 지 확인

    $ oc get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/my-eap-project/metrics/jboss_active_avg" | jq .
    +{
    +  "kind": "MetricValueList",
    +  "apiVersion": "custom.metrics.k8s.io/v1beta1",
    +  "metadata": {
    +    "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/my-eap-project/metrics/jboss_active_avg"
    +  },
    +  "items": [
    +    {
    +      "describedObject": {
    +        "kind": "Namespace",
    +        "name": "my-eap-project",
    +        "apiVersion": "/v1"
    +      },
    +      "metricName": "jboss_active_avg",
    +      "timestamp": "2020-01-30T07:12:01Z",
    +      "value": "3",
    +      "selector": null
    +    }
    +  ]
    +}
    +

    결과 값은 JBoss 상의 Active Request가 3개임을 의미 합니다.

  • Horizontal Pod Autoscaler (HPA) 리소스를 적용합니다.

    $ oc apply -f - <<EOF
    +kind: HorizontalPodAutoscaler
    +apiVersion: autoscaling/v2beta1
    +metadata:
    +  name: sample-eap
    +  namespace: my-eap-project
    +spec:
    +  scaleTargetRef:
    +    apiVersion: apps.openshift.io/v1
    +    kind: DeploymentConfig
    +    name: sample-eap
    +  minReplicas: 1
    +  maxReplicas: 5
    +  metrics:
    +  - type: Object
    +    object:
    +      target:
    +        kind: Namespace
    +        name: my-eap-project
    +      metricName: jboss_active_avg
    +      targetValue: 10
    +EOF
    +

    namespace를 기준으로 쿼리하기 때문에 Object 형태의 metrics를 구성합니다.

  • 적용된 HPA를 확인하여 적용된 JBoss EAP에 요청에 따라 값이 변화하고 Pod의 수가 변화하는지 확인합니다.

    $ watch oc describe hpa/sample-eap -n my-eap-project
    +Every 2.0s: oc describe hpa/sample-eap -n my-eap-pr...  Thu Jan 30 07:23:47 2020
    +
    +Name:                                              sample-eap
    +Namespace:                                         my-eap-project
    +Labels:                                            <none>
    +Annotations:                                       kubectl.kubernetes.io/last-ap
    +plied-configuration={"apiVersion":"autoscaling/v2beta1","kind":"HorizontalPodAut
    +oscaler","metadata":{"annotations":{},"name":"sample-eap","namespace":"my-eap-pr
    +oject"},"sp...
    +CreationTimestamp:                                 Thu, 30 Jan 2020 07:18:17 +00
    +00
    +Reference:                                         DeploymentConfig/sample-eap
    +Metrics:                                           ( current / target )
    +  "jboss_active_avg" on Namespace/my-eap-project:  16 / 10
    +Min replicas:                                      1
    +Max replicas:                                      5
    +DeploymentConfig pods:                             1 current / 2 desired
    +Conditions:
    +  Type            Status  Reason              Message
    +  ----            ------  ------              -------
    +  AbleToScale     True    SucceededRescale    the HPA controller was able to upd
    +ate the target scale to 2
    +  ScalingActive   True    ValidMetricFound    the HPA was able to successfully c
    +alculate a replica count from Namespace metric jboss_active_avg
    +  ScalingLimited  False   DesiredWithinRange  the desired count is within the ac
    +ceptable range
    +Events:
    +  Type    Reason             Age   From                       Message
    +  ----    ------             ----  ----                       -------
    +  Normal  SuccessfulRescale  17s   horizontal-pod-autoscaler  New size: 2; reaso
    +n: Namespace metric jboss_active_avg above target
    +

Appendix

1. 참고 자료

2. Memory based HPA

다음의 정보가 도움이 됩니다. :

v2beta1 api를 적용하는 방법은 다음과 같습니다.

  • master 노드의 master-config.yaml수정

    $ vi /etc/origin/master/master-config.yaml
    +
  • apiServerArgumentsruntime-config 항목으로 apis/autoscaling/v2beta1=true 추가

    kubernetesMasterConfig:
    +...
    +  apiServerArguments:
    +	...
    +    runtime-config:
    +    - apis/autoscaling/v2beta1=true
    +  controllerArguments:
    +...
    +
  • master 구성요소 재시작

    $ master-restart api
    +$ master-restart controllers
    +$ systemctl restart atomic-openshift-node.service
    +
  • api 응답 확인

    $ oc get --raw /apis/autoscaling/v2beta1
    +{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"autoscaling/v2beta1","resources":[{"name":"horizontalpodautoscalers","singularName":"","namespaced":true,"kind":"HorizontalPodAutoscaler","verbs":["create","delete","deletecollection","get","list","patch","update","watch"],"shortNames":["hpa"],"categories":["all"]},{"name":"horizontalpodautoscalers/status","singularName":"","namespaced":true,"kind":"HorizontalPodAutoscaler","verbs":["get","patch","update"]}]}
    +
  • HPA 적용 :
    예를들어 mnist-app 이라고 하는 DeploymentConfig 를 타겟으로 함

    apiVersion: autoscaling/v2beta1
    +kind: HorizontalPodAutoscaler
    +metadata:
    +  name: mnist-app
    +  namespace: my-namespace
    +spec:
    +  maxReplicas: 10
    +  minReplicas: 1
    +  scaleTargetRef:
    +    apiVersion: apps.openshift.io/v1
    +    kind: DeploymentConfig
    +    name: mnist-app
    +  metrics:
    +  - type: Resource
    +    resource:
    +      name: memory
    +      targetAverageUtilization: 10
    +  - type: Resource
    +    resource:
    +      name: memory
    +      targetAverageValue: 1G
    +
    • targetAverageUtilization : Pod 들의 가용한 Memory를 100으로 산정하여 그중 사용량을 기준
    • targetAverageValue : Pod 들의 평균 사용량을 기준
  • https://docs.openshift.com/container-platform/4.1/monitoring/exposing-custom-application-metrics-for-autoscaling.htmlopen in new window)

3. Registry account

Red Hat Container Image Registry에 접속하기 위한 전용 계정을 생성할 수 있습니다.

  • https://registry.redhat.ioopen in new window 에 접속하여 로그인
  • 우측 상단의 Service Accounts를 클릭
  • New Service Account 버튼을 클릭
  • Create a New Registry Service Account
    • Name : "A-z 0-9 .-_" 조건에 맞는 이름을 설정 (e.g. registry)
    • Description : 해당 계정 정보에 대한 설명을 기입 (e.g. for image registry login)
    • CREATE 버튼 클릭
  • 생성된 Token Information 확인
    • Token Information 탭의 정보를 확인
    • Username is 00000000|registry and... 설명의 숫자+파이프+이름 형태가 Username
    • 아래 Token 값(매우 긴)이 Password 로 동작

생성한 Token으로 본래의 계정 접속 정보를 노출 하지 않고 Red Hat Container Image Registry에 접속할 수 있는 접속 정보로 사용할 수 있습니다.

4. OpenShift에 리소스 배포

OpenShift에 리소스를 배포하는 방법은 oc CLI를 사용하는 방법과 Application Console을 활용하는 두가지 방법이 있습니다.

다음과 같은 prometheus-operator관련 리소스 설정이 있다고 가정합니다.

apiVersion: operators.coreos.com/v1alpha1
+kind: Subscription
+metadata:
+  generateName: prometheus-
+  namespace: custom-metric
+spec:
+  source: rh-operators
+  name: prometheus
+  startingCSV: prometheusoperator.0.22.2
+  channel: preview
+

yaml로 작성된 리소스 설정 파일을 적용하기위해 다음 두가지 형태를 활용합니다.

1) CLI 활용

oc 로그인 상태에서 yaml로 작성된 리소스 설정을 파일로 저장합니다. 예를 들어 prometheus-operator.yaml 인 경우 다음과 같이 적용 가능합니다.

$ oc apply -f prometheus-operator.yaml
+

또는 다음과 같이 인라인으로 수행할 수 있습니다.

$ $ oc create -f - <<EOF
+apiVersion: operators.coreos.com/v1alpha1
+kind: Subscription
+metadata:
+  generateName: prometheus-
+  namespace: custom-metric
+spec:
+  source: rh-operators
+  name: prometheus
+  startingCSV: prometheusoperator.0.22.2
+  channel: preview
+EOF
+

2) Application Console 활용

OpenShift Application Console 에 접속 합니다. 적용할 프로젝트를 클릭하고 Overview 화면의 우측 상단에 Add to Project를 클릭합니다.
Import YAML/JSON을 클릭하여 리소스 설정을 입력받는 창에 앞서의 예로 작성된 설정을 붙여넣습니다. 또는 파일로 저장된 파일을 선택하여 업로드/적용 가능합니다. Create 버튼을 클릭하여 결과를 확인합니다.

+ + + diff --git a/02-PrivatePlatform/Vsphere/Vsphere_template_issue.html b/02-PrivatePlatform/Vsphere/Vsphere_template_issue.html new file mode 100644 index 0000000000..ae904e11a4 --- /dev/null +++ b/02-PrivatePlatform/Vsphere/Vsphere_template_issue.html @@ -0,0 +1,64 @@ + + + + + + + + + + VSphere 템플릿 생성 이슈 | docmoa + + + + + +
본문으로 건너뛰기

VSphere 템플릿 생성 이슈

1분 미만vspheretemplate

VSphere 템플릿 생성 이슈

  1. redhat 계열
  • 아래 네개의 패키지의 설치가 필요하다. 특히 perl은 거의 설치가 안되어 있음
  • open-vm-tools, open-vm-tools-deploypkg, net-tools, perl
  • 설치 후 template 생성하고 배포하면 됨
  1. debian 계열
  • /etc/systemd/system/vmtoolsd.service 파일에 구문 추가
  • 18.04은 추가하여도 가끔 NIC, hostname이 기존에 템플릿의 정보를 가져올때가 있음
#수정 할 파일
+vi /etc/systemd/system/vmtoolsd.service
+
+[Unit]
+Description=Service for virtual machines hosted on VMware
+Documentation=http://open-vm-tools.sourceforge.net/about.php
+ConditionVirtualization=vmware
+DefaultDependencies=no
+Before=cloud-init-local.service
+#아래 After=dbus.service추가
+After=dbus.service
+After=vgauth.service
+After=apparmor.service
+RequiresMountsFor=/tmp
+After=systemd-remount-fs.service systemd-tmpfiles-setup.service systemd-modules-load.service
+
+[Service]
+ExecStart=/usr/bin/vmtoolsd
+TimeoutStopSec=5
+
+[Install]
+WantedBy=multi-user.target
+Alias=vmtoolsd.service
+
+
+ + + diff --git a/02-PrivatePlatform/Vsphere/index.html b/02-PrivatePlatform/Vsphere/index.html new file mode 100644 index 0000000000..4dd3c2b56b --- /dev/null +++ b/02-PrivatePlatform/Vsphere/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Vsphere | docmoa + + + + + +
본문으로 건너뛰기

Vsphere

1분 미만

+ + + diff --git a/02-PrivatePlatform/index.html b/02-PrivatePlatform/index.html new file mode 100644 index 0000000000..c33ebf365c --- /dev/null +++ b/02-PrivatePlatform/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Private Platform | docmoa + + + + + +
본문으로 건너뛰기

Private Platform

1분 미만Platform

+ + + diff --git a/03-PublicCloud/AlibabaCloud/CredentialConfig.html b/03-PublicCloud/AlibabaCloud/CredentialConfig.html new file mode 100644 index 0000000000..3ad40fa587 --- /dev/null +++ b/03-PublicCloud/AlibabaCloud/CredentialConfig.html @@ -0,0 +1,86 @@ + + + + + + + + + + Alibaba CLI 설정 | docmoa + + + + + +
본문으로 건너뛰기

Alibaba CLI 설정

약 1 분alibabaaliyun

Alibaba CLI 설정

1. CLI 설치

1.1 Download 방식

1.2 MAC - brew 설치

2. 구성 설정

인증 메커니즘이 4가지이며 구성 설정시 --mode 에 Credential type을 넣어서 구성하게 됨

Credential typeLimitInteractive credential configuration (fast)Non-interactive credential configuration
AKUse an AccessKey ID and an AccessKey secret to authorize access.Configure AccessKey credentialopen in new windowConfigure AccessKey credentialopen in new window
StsTokenUse a Security Token Service (STS) token to authorize access.Configure STS token credentialopen in new windowConfigure STS token credentialopen in new window
RamRoleArnUse the role of a Resource Access Management (RAM) user to authorize access.Configure RamRoleArn credentialopen in new windowConfigure RamRoleArn credentialopen in new window
EcsRamRoleUse the RAM role of an Elastic Compute Service (ECS) instance to authorize password-free access.Configure EcsRamRole credentialopen in new windowConfigure EcsRamRole credentialopen in new window

2.1 AK 생성

AccessKey 방식의 인증 정보가 없는 경우 아래와 같이 생성

  • Alibaba Cloud 홈페이지의 우측 상단(변경될 수 있음) 클릭하여 Accesskey Management 클릭
  • AccessKey Pair 항목에서 Create AccessKey Pair 를 클릭하여 AK(AccessKey)를 신규로 생성

2.2 CLI 구성

aliyun cli의 configure 를 실행

  • --mode 에는 Credential Type을 지정
  • --profile 에는 사용할 이름을 사용자가 지정, default는 기본 값
$ aliyun configure --mode AK --profile myprofile
+Configuring profile 'myprofile' in 'AK' authenticate mode...
+Access Key Id []: LTAI5**********************t88V
+Access Key Secret []: *******************************
+Default Region Id []: ap-southeast-1
+Default Output Format [json]: json (Only support json)
+Default Language [zh|en] en: en
+Saving profile[gslee] ...Done.
+
+Configure Done!!!
+

구성 완료 후 home 디렉토리의 .aliyun 디렉토리 내의 config.json에서 구성된 정보를 확인 가능

{
+    "current": "myprofile",
+    "profiles": [
+        {
+            "name": "default",
+        },
+        {
+            "name": "gslee",
+            "mode": "AK",
+            "access_key_id": "LTAI5**********************t88V",
+            "region_id": "ap-southeast-1",
+            "output_format": "json",
+            "language": "en",
+        }
+    ],
+    "meta_path": ""
+}
+

2.3 CLI 사용 예제

Region 목록 확인

profile이 default인 경우 --profile or -p 생략 가능

$ aliyun -p myprofile ecs DescribeRegions 
+{
+    "Regions": {
+        "Region": [
+            {
+                "LocalName": "华北1(青岛)",
+                "RegionEndpoint": "ecs.aliyuncs.com",
+                "RegionId": "cn-qingdao"
+            },
+            {
+                "LocalName": "华北2(北京)",
+                "RegionEndpoint": "ecs.aliyuncs.com",
+                "RegionId": "cn-beijing"
+            },
+        ]
+    },
+    "RequestId": "2304DF19-CABF-54DF-BDC6-F889C3A73E4F"
+}
+
+ + + diff --git a/03-PublicCloud/AlibabaCloud/index.html b/03-PublicCloud/AlibabaCloud/index.html new file mode 100644 index 0000000000..947ebcaa86 --- /dev/null +++ b/03-PublicCloud/AlibabaCloud/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Alibaba Cloud | docmoa + + + + + +
본문으로 건너뛰기

Alibaba Cloud

1분 미만

+ + + diff --git a/03-PublicCloud/Azure/index.html b/03-PublicCloud/Azure/index.html new file mode 100644 index 0000000000..4fbb933ad2 --- /dev/null +++ b/03-PublicCloud/Azure/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Azure | docmoa + + + + + +
본문으로 건너뛰기

Azure

1분 미만

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.html new file mode 100644 index 0000000000..83017f5e3e --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.html @@ -0,0 +1,40 @@ + + + + + + + + + + Workshop 안내 | docmoa + + + + + +
본문으로 건너뛰기

Workshop 안내

1분 미만ncloudncpterraformworkshop

Workshop 안내


과정 안내

워크샵을 진행시 참여자 소개 (옵션)

서로의 기술적 백그라운드를 이해하고 기술배경에 맞춰 워크샵이 진행됩니다.

  • 이름
  • 담당 업무와 기술적 백그라운드
  • 자동화 경험 (Terraform, Ansible, Bash Script, Powershell 등)
  • 주로 사용하는 편집기(editor)

참고 링크

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.html new file mode 100644 index 0000000000..54e08bff58 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.html @@ -0,0 +1,66 @@ + + + + + + + + + + 01. 테라폼 소개 | docmoa + + + + + +
본문으로 건너뛰기

01. 테라폼 소개

1분 미만ncloudncpterraformworkshop

01. 테라폼 소개

NCP 서버를 어떻게 프로비저닝 하죠?

새로운 NCP의 인스턴스를 프로비저닝 할 수있는 몇 가지 다른 방법을 살펴 보겠습니다. 시작하기 전에 다음을 포함한 몇 가지 기본 정보를 수집해야합니다 (더 많은 옵션이 있습니다).

  • 서버 이름
  • 운영 체제 (Image)
  • VM 크기
  • 지리적 위치 (Region)
  • 보안 그룹

서버 만들기 Method 1: nCloud Console (GUI)

Start

서버 만들기 Method 2: nCloud CLI

서버 생성을 위한 CLI 가이드 : https://cli.ncloud-docs.com/docs/cli-server-createserverinstancesopen in new window

ncloud server createServerInstances \
+  --serverImageProductCode SPSW0LINUX000046 \
+  --serverProductCode SPSVRSTAND000003 \
+  --serverName ncloud-mktest
+
CLI Options
파라미터 명필수 여부타입제약사항
serverImageProductCodeConditionalStringMin:1, Max:20
serverProductCodeNoStringMin:1, Max:20
memberServerImageNoConditionalString
serverNameNoStringMin:3, Max:30
serverDescriptionNoStringMin:1, Max:1000
loginKeyNameNoStringMin:3, Max:30
isProtectServerTerminationNoBoolean
serverCreateCountNoIntegerMin:1, Max:20
serverCreateStartNoNoInteger
internetLineTypeCodeNoStringMin:1, Max:5
feeSystemTypeCodeNoStringMin:1, Max:5
zoneNoNoString
accessControlGroupConfigurationNoListNoListMin:0, Max:5
raidTypeNameConditionalString
userDataNoStringMin:1, Max:21847
initScriptNoNoString
instanceTagList.tagKeyNoString
instanceTagList.tagValueNoString
isVaccineInstallNoBoolean
blockDevicePartitionList.N.mountPointNoString"/" (root) 경로로 시작하는 마운트 포인트를 입력합니다. 첫 번째 마운트 포인트는 반드시 "/" (root) 파티션이어야 합니다. "/" (root) 하위 명칭은 소문자와 숫자만 허용되며, 소문자로 시작해야합니다. OS 종류에 따라서 /root, /bin, /dev 등의 특정 키워드는 사용 불가능 할 수 있습니다.
blockDevicePartitionList.N.partitionSizeNoStringMin : 50 GiB

nCloud CLI는 자동화할 수 있는 스크립트 방식을 제공합니다. 하지만 이 작업을 실행하기 전에 예측할 수 있나요?

서버 만들기 Method 3: Provision with Terraform

nCloud 서버를 생성하는 Terraform 예제 코드 : https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/resources/serveropen in new window

resource "ncloud_server" "server" {
+  name = "tf-test-vm1"
+  server_image_product_code = "SPSW0LINUX000032"
+  server_product_code = "SPSVRSTAND000004"
+
+  tag_list {
+    tag_key = "samplekey1"
+    tag_value = "samplevalue1"
+  }
+
+  tag_list {
+    tag_key = "samplekey2"
+    tag_value = "samplevalue2"
+  }
+}
+

Terraform 이란?

resource "ncloud_server" "server" {
+  name = "tf-test-vm1"
+  server_image_product_code = "SPSW0LINUX000032"
+  server_product_code = "SPSVRSTAND000004"
+}
+
  • 실행 가능한 문서
  • 인간과 기계 모두 해독 가능
  • 배우기 쉬움
  • 테스트, 공유, 재사용, 자동화
  • 모든 주요 클라우드 제공 업체에서 작동 가능

Infrastructure as Code (IaC)

IaC (Infrastructure as Code)는 컴퓨터에서 읽을 수있는 정의 파일을 사용하여 클라우드 인프라를 관리하고 프로비저닝하는 프로세스입니다.

실행 가능한 '문서'라고 생각하시면 됩니다.

Infrastructure as Code는 다음과 같은 특징이 있습니다.

  • 인프라 프로비저닝을 위한 체계화된 워크 플로우 제공
  • 기존 인프라 변경 및 업데이트
  • terraform plan 을 사용하여 변경 사항을 안전하게 테스트 및 확인
  • 애플리케이션 코드 워크플로우 도구 (Git, CI/CD)와 통합
  • 간편한 공유 및 공동 작업을 위해 재사용 가능한 모듈 제공
  • 보안 정책(취약점 검사) 및 조직 표준 시행
  • 서로 다른 팀 간의 협업 활성화

다른 운영 도구들

  • 이러한 도구는 OS나 애플리케이션 구성에 적합합니다.
  • 하지만 클라우드 인프라 및 플랫폼 서비스를 프로비저닝하기 위해 특별히 제작 된 것은 아닙니다.

Native-Cloud 프로비저닝 도구들

  • 각 클라우드에는 자체 YAML 또는 JSON 기반 프로비저닝 도구가 있습니다.
  • Terraform은 모든 주요 클라우드 제공 업체 및 VM 하이퍼 바이저에서 공통으로 사용할 수 있습니다.

Terraform vs. JSON

JSON:

"name": "{ "Fn::Join" : [ "-", [ PilotServerName, vm ] ] }",
+

Terraform:

name = "${var.PilotServerName}-vm"
+

Terraform 코드 (HCL)는 배우기 쉽고 읽기 쉽습니다. 또한 동등한 JSON 구성보다 50-70 % 더 간결합니다.

NCP를 위해 Terraform을 쓰는 이유

  • 다중 클라우드 및 하이브리드 인프라 지원
  • 다른 클라우드 제공 업체로부터 마이그레이션
  • 프로비저닝 속도 향상
  • 효율성 향상
  • 위험 최소화
+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.html new file mode 100644 index 0000000000..8c24bd906d --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.html @@ -0,0 +1,98 @@ + + + + + + + + + + 02. 테라폼 기본 | docmoa + + + + + +
본문으로 건너뛰기

02. 테라폼 기본

약 1 분ncloudncpterraformworkshop

02. 테라폼 기본

Terraform 이란?

logo
logo

Terraform Command Line

  • 기본적으로 Terraform 오픈소스는 커맨드라인 도구입니다.

  • Terraform 명령은 수동으로 입력하거나 스크립트에서 자동으로 실행됩니다.

  • 명령은 Linux, Windows 또는 MacOS에 상관없이 동일합니다.

  • Terraform에는 다른 작업을 수행하는 하위 명령들이 있습니다.

# Basic Terraform Commands
+terraform version
+terraform help
+terraform init
+terraform plan
+terraform apply
+terraform destroy
+

Terraform Help

$ terraform help
+Usage: terraform [-version] [-help] <command> [args]
+...
+Common commands:
+    apply              Builds or changes infrastructure
+    console            Interactive console for Terraform interpolations
+    destroy            Destroy Terraform-managed infrastructure
+    env                Workspace management
+    fmt                Rewrites config files to canonical format
+    graph              Create a visual graph of Terraform resources
+
 









특정 하위 명령에 대한 도움말을 보려면 terraform <subcommand> help 를 입력합니다.

Terraform Code

resource "ncloud_vpc" "vpc" {
+    ipv4_cidr_block = "10.0.0.0/16"
+}
+

Terraform 코드는 HCL2 툴킷open in new window을 기반으로합니다. HCL은 HashiCorp Configuration Language를 나타냅니다.

Terraform 코드는 모든 클라우드 또는 플랫폼에서 인프라를 프로비저닝하기 위해 특별히 설계된 선언적 언어입니다.

Terraform Comments

줄 주석은 *(octothorpe, 별표) 또는 #(파운드) 기호로 시작합니다....샵! #

# This is a line comment.
+

블록 주석은 /**/ 기호 사이에 포함됩니다.

/* This is a block comment.
+Block comments can span multiple lines.
+The comment ends with this symbol: */
+

Terraform Workspaces

Workspace는 Terraform 코드가 포함 된 폴더 또는 디렉토리입니다.

Terraform 파일은 항상* .tf 또는* .tfvars 확장자로 끝납니다. 실행 시 해당 파일들은 하나로 동작합니다.

대부분의 Terraform Workspaces에는 일반적으로 아래 3개정도의 파일을 둡니다. (정해진건 아닙니다.)

Terraform Init

$ terraform init
+Initializing the backend...
+
+Initializing provider plugins...
+- Finding navercloudplatform/ncloud versions matching ">= 2.1.2"...
+- Installing navercloudplatform/ncloud v2.1.2...
+- Installed navercloudplatform/ncloud v2.1.2 (signed by a HashiCorp partner, key ID 9DCE24305722E9C9)
+...
+Terraform has been successfully initialized!
+
 








Terraform은 필요한 Provider(공급자)와 Module(모듈)을 가져와 .terraform 디렉터리에 저장합니다. 모듈 또는 공급자를 추가, 변경 또는 업데이트하는 경우 init를 다시 실행해야합니다.

Terraform Plan

$ terraform plan
+...
+Terraform will perform the following actions:
+
+  # ncloud_vpc.vpc will be created
+  + resource "ncloud_vpc" "vpc" {
+      + default_access_control_group_no = (known after apply)
+      + default_network_acl_no          = (known after apply)
+      + default_private_route_table_no  = (known after apply)
+      + default_public_route_table_no   = (known after apply)
+      + id                              = (known after apply)
+      + ipv4_cidr_block                 = "10.0.0.0/16"
+      + name                            = (known after apply)
+      + vpc_no                          = (known after apply)
+    }
+
+Plan: 1 to add, 0 to change, 0 to destroy.
+
 
















변경 사항을 적용하기 전에 terraform plan 으로 미리 구성의 변경을 살펴봅니다.

변수는 어디에?

Terraform 변수는 variables.tf라는 파일에 일반적으로 위치 시킵니다.(이름은 변경 가능) 변수는 기본 설정을 가질 수 있습니다. 기본값을 생략하면 사용자에게 값을 입력하라는 메시지가 표시됩니다. 여기서 우리는 사용하려는 변수를 선언 합니다.

variable "prefix" {
+  description = "This prefix will be included in the name of most resources."
+}
+
+variable "vpc_cidr" {
+  description = "A cidr option for instances into the VPC."
+  default     = "10.0.0.0/16"
+}
+

변수에 값을 할당하는 방식과 우선순위

일부 변수를 정의한 후에는 다른 방법으로 설정하고 재정의 할 수 있습니다. 다음은 각 방법의 우선 순위입니다.

이 목록은 가장 높은 우선 순위 (1)에서 가장 낮은 순위 (5)로 나타냅니다.

즉, CLI 실행시 -var 로 지정되는 Command line flag가 가장 우선합니다.

  1. Command line flag - 명령 줄 스위치로 실행
  2. Configuration file - terraform.tfvars 파일에 설정
  3. Environment variable - 쉘 환경의 일부
  4. Default Config - variables.tf의 기본값
  5. User manual entry - 지정되지 않은 경우 사용자에게 입력을 요청합니다.

실습을 위해 다음장으로 이동하세요.

💻 Lab - Setup and Basic Usage

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html new file mode 100644 index 0000000000..684cec7ef3 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html @@ -0,0 +1,101 @@ + + + + + + + + + + 💻 Lab - Setup and Basic Usage | docmoa + + + + + +
본문으로 건너뛰기

💻 Lab - Setup and Basic Usage

약 2 분ncloudncpterraformworkshop

💻 Lab - Setup and Basic Usage


🏡 Moving in - Explore Your Workspace

@slidestart blood

Terraform 명령줄 도구는 MacOS, FreeBSD, OpenBSD, Windows, Solaris 및 Linux에서 사용할 수 있습니다.


Terraform 언어는 사람과 기계가 읽을 수 있도록 설계되었습니다.


대부분의 최신 코드 편집기는 Terraform 구문 강조 표시를 지원합니다.

@slideend

테라폼 설치 및 구성

  1. 테라폼 다운로드 사이트 https://www.terraform.io/downloads.htmlopen in new window 로 접속하여, 자신의 환경에 맞는 Terraform을 다운로드 받습니다.

  2. 압축을 해제하여 terraform 바이너리 파일을 확인합니다.

  • Linux/Mac : terraform
  • Windows : terraform.exe
  1. 파일을 적절한 위치에 넣고 PATH로 지정합니다.
bash
mkdir ~/hashicorp/bin
+mv terraform ~/hashicorp/bin
+cd ~/hashicorp/bin
+echo $(pwd) >> ~/.bash_profile
+source ~/.bash_profile
+

편집기 구성

VSCode 편집기를 사용할 준비가 되었다면, 코드의 시인성을 위해 extension을 설치 합니다.

1. Extention 설치
  • 좌측 사이드 메뉴에서 Extentions 를 클릭하여 Terraform을 검색합니다.
  • HashiCorp Terraform을 설치합니다.

실습을 위한 코드 받기

이 실습에서는 Terraform을 실행하기 위한 IDE 설정과 Terraform CLI를 사용하고, NCP를 위한 기본 구성을 수행합니다.

실습에서 사용할 코드는 github에서 받습니다.

링크 : https://github.com/ncp-hc/workshop-ossopen in new window

  • git 이 설치되어있는 경우 git clone을 통해 코드를 받습니다.
    $ git clone https://github.com/ncp-hc/workshop-oss.git

  • Download만을 원하는 경우 아래 Download ZIP을 선택합니다.

편집기에서 열기

  • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
  • 앞서 받은 디렉토리내의 lab01을 열어줍니다.

👋 Getting to Know Terraform

@slidestart blood

Terraform 오픈 소스는 랩톱 또는 가상 워크스테이션에서 다운로드하여 실행할 수 있는 명령줄 응용 프로그램입니다.


Go로 작성되었으며 macOS, Linux 또는 Windows에서 실행됩니다.


https://www.terraform.io/downloads.htmlopen in new window 에서 항상 최신 버전의 Terraform을 다운로드할 수 있습니다.


노트북이나 워크스테이션에 Terraform을 설치하는 것은 쉽습니다. zip 파일을 다운로드하고 압축을 풀고 PATH의 어딘가에 배치하기만 하면 됩니다.


단계별 지침은 이 튜토리얼을 확인하세요. https://learn.hashicorp.com/terraform/getting-started/install.htmlopen in new window

@slideend

편집기가 준비가 되었으면 터미널을 열고 몇가지 기본적인 Terraform 명령을 수행합니다.

  • Linux/Mac의 경우 터미널에서 수행하거나 Windows의 경우 명령 프롬프트에서 실행하게 됩니다.

  • VSCode 편집기를 사용하게 되면, 편집기의 터미널(Terminal) 기능으로 함께 사용할 수 있습니다.

💻 컴퓨터에서 Terraform의 버전을 확인 하세요.

terraform version
+

💻 명령 구문이 궁금하면 언제든지 도움을 받을 수 있습니다.

terraform help
+

🔐 Terraform을 NCP에 연결하기

@slidestart blood

HCL이

"HashiCorp Configuration Language"

의 약자라는 것을 알고 계신가요?

@slideend

NCP 자격증명 받기

NCP에 인증하고 리소스를 빌드하기 위해 Terraform은 적절한 자격 증명 세트를 제공하도록 요구합니다.

💻 Terraform에서 사용할 NCP의 자격증명을 얻기위해 다음 단계를 수행합니다.

1. 인증키 관리

NCP 자격증명 환경변수로 저장하기

이 교육 환경을 위해 NCP의 자격 증명을 준비하여 환경 변수로 저장합니다. Terraform은 쉘 환경에 구성된 환경 변수를 자동으로 읽고 사용합니다.

💻 터미널에서 다음 명령을 실행합니다.

  • NCLOUD_ACCESS_KEY NCLOUD_SECRET_KEY NCLOUD_REGION 를 환경변수로 등록합니다.
bash
export NCLOUD_ACCESS_KEY="XXXXXXXXXXXXX"
+export NCLOUD_SECRET_KEY="XXXXXXXXXXXXXXXXXXXXXXXXXX"
+

위험

API 자격증명정보는 실수로 공개된 저장소에 노출되거나 복사되면 위헐합니다.
자격증명(API 인증키)를 코드에 저장하지 않는 것을 권장합니다.


👨‍💻 Terraform 코드는 어떻게 생겼나요?

@slidestart blood

Terraform은 현재 디렉토리에서 *.tf 또는 *.tfvars 로 끝나는 모든 것을 읽습니다.


일반적으로 Terraform Workspace는 main.tf, variables.tf, outputs.tf 파일로 구성됩니다.


Terraform 코드를 목적에 따라 파일로 그룹화할 수도 있습니다.

예를 들어 모든 로드 밸런서 구성 코드를 load_balancer.tf 에 구성하기

@slideend

코드 편집기의 파일 목록이 보이십니까?

Terraform 코드는 항상 .tf 확장자로 끝납니다. 원하는 만큼 Terraform 파일을 가질 수 있지만 일반적으로 다음 세 가지를 구성합니다.

  • main.tf - 대부분의 Terraform 코드가 저장되는 위치입니다. 이것은 자원을 구축하는 부분입니다.
  • variables.tf - 이 파일을 사용하여 사용자가 사용할 수 있는 변수를 정의합니다.
  • output.tf - 이 파일에는 성공적인 Terraform 실행이 끝날 때 표시될 출력이 포함되어 있습니다.

Terraform에서 *.tf*.tfvars로 끝나지 않는 파일은 무시됩니다.


🏡 Terraform Init - Provider 설치

@slidestart blood

Terraform Core 프로그램은 그 자체로는 그다지 유용하지 않습니다.


Terraform은 클라우드 API와 통신할 수 있도록 Provider(공급자) 의 도움이 필요합니다.


Terraform에는 수백 개의 다양한 Provider가 있습니다. 여기에서 Provider 목록을 찾아볼 수 있습니다.

https://registry.terraform.io/browse/providersopen in new window


오늘 우리는 몇 가지 다른 Provider를 사용할 것이지만 주요 Provider는 ncloud provider 입니다.

@slideend

우리는 이 실습에서 사용할 Terraform 코드를 다운로드 했습니다. 나머지 실습에서 이 소스코드를 사용할 것입니다.

Terraform으로 무엇이든 하기 전에 Workspace를 초기화 해야 합니다.

💻 터미널에서 init 명령을 수행합니다.

terraform init
+
+...
+Terraform has been successfully initialized!
+

`terraform init 명령은 Terraform 코드를 스캔하고 필요한 Provider를 식별하고 다운로드합니다.

💻 ncloud provider가 .terraform 디렉토리에 설치되었는지 확인합니다.

Linux/Mac
ls .terraform/providers/registry.terraform.io/navercloudplatform
+

이 숨겨진 디렉토리는 모든 모듈과 플러그인이 저장되는 곳입니다.

😱 Quiz Time 1. Provider와 Module

Q. Terraform은 모듈과 공급자를 어디에 저장합니까?


👩‍⚖️ Terraform Validate - 코드 테스트

@slidestart blood

Terraform에는 구문 검사기가 내장되어 있습니다.


terraform validate 명령으로 실행할 수 있습니다.

@slideend

Terraform에는 validate 라는 하위 명령이 내장되어 있습니다. 이것은 코드가 올바르게 구문 분석되는지 확인하기 위해 코드의 빠른 구문 검사를 수행하려는 경우에 유용합니다.

💻 main.tfopen in new window 파일을 편집합니다.

main.tf 16번째 행의 사이에 큰 따옴표를 제거 ncloud_vpchashicat 사이의 큰 따옴표를 제거하고 저장합니다.

terraform {
+  required_providers {
+    ncloud = {
+      source  = "NaverCloudPlatform/ncloud"
+      version = ">= 2.1.2"
+    }
+  }
+}
+
+provider "ncloud" {
+  region      = var.region
+  site        = var.site
+  support_vpc = true
+}
+
+resource "ncloud_vpc" "hashicat" { → resource "ncloud_vpc" hashicat" {
+  ipv4_cidr_block = "10.0.0.0/16"
+  name            = lower("${var.prefix}-vpc-${var.region}")
+}
+
+...생략...
+















 





terraform validate 명령을 터미널에서 실행합니다.

terraform validate
+

다시 따옴표 표기를 넣고 저장한 다음 terraform validate 명령을 실행합니다. 이번에는 검증을 통과해야 합니다.

terraform validate 명령은 자동화된 CI/CD 테스트 파이프라인에서 가장 자주 사용됩니다. 다른 단계를 수행하기 전에 코드에서 오류를 빠르게 포착할 수 있습니다.


🤔 Terraform Plan - Dry run mode

@slidestart blood

terraform plan을 통해 환경에 대한 변경 사항을 안전한 방법으로 미리 볼 수 있습니다.


이렇게 하면 이미 빌드된 후가 아니라 배포하기 전에 예기치 않은 변경 사항을 식별하는 데 도움이 될 수 있습니다.

@slideend

💻 terraform plan명령을 실행합니다.

$ terraform plan
+var.prefix
+  This prefix will be included in the name of most resources.
+
+  Enter a value:
+
 




이 명령을 실행하면 Terraform에서 prefix변수 를 입력하라는 메시지를 표시 합니다.

소문자 또는 숫자의 짧은 문자열을 입력합니다. 영문 이니셜 소문자를 사용하는 것이 좋습니다.

prefix는 현재 Terraform 코드 구성에서 VPC, 서브넷, 서버 등의 리소스 이름의 일부가 됩니다.

🎛️ Terraform 변수로 작업하기

@slidestart blood

terraform.tfvars 파일은 사용자가 변수를 구성할 수 있는 편리한 위치입니다.

@slideend

Terraform에서 모든 변수는 사용하기 전에 선언되어야 합니다. 변수는 다른 *.tf 파일에서도 선언될 수 있지만 일반적으로 variables.tf 파일에서 선언됩니다. (default)

variable "address_space" {
+  description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created."
+  default     = "10.0.0.0/8"
+}
+


 

해당 값은 terraform.tfvars 파일 및 나중에 다른 방법으로 설정할 수 있습니다.

💻 terraform.tfvars 파일을 수정합니다.

terraform.tfvars 파일을 열고 prefix의 줄 시작 부분에 주석 기호 # 를 제거합니다.

yourname을 원하는 소문자 또는 숫자의 짧은 문자열을 입력합니다.

# terraform.tfvars
+prefix = "yourname"
+

이제 terraform plan을 다시 실행 합니다. 이번에는 prefix를 수동으로 입력할 필요가 없습니다.

🗼 cidr_block 변경

@slidestart blood

개개인은 terraform.tfvars 파일에 설정하여 variables.tf 파일에 정의된 모든 변수를 재정의할 수 있습니다.


이번 실습에서는 ncloud 리소스를 배포하는 위치를 지정합니다.

@slideend

이전 실습에서 prefixterraform.tfvars 파일에서 변수를 설정했습니다. ncloud 인프라가 배포될 vpc의 cidr를 결정할 또 다른 변수를 설정해 보겠습니다.

먼저 다른 계획을 실행하여 위치를 변경한 후 어떻게 되는지 비교할 수 있습니다.

terraform plan
+

💻 기본 address_space 정보를 수정합니다.

default로 선언되어있는 값 외에 사용자 지정 변수로 변경해봅니다. terraform.tfvars파일을 열어 address_space을 추가하고 다시 terraform plan을 실행해 봅니다. 이번엔 무엇이 다른가요?

# terraform.tfvars
+prefix        = "yourname"
+address_space = "10.0.0.0/16"
+

terraform.tfvars 파일은 variables.tf 파일에 선언된 모든 변수에 대한 값을 설정할 수 있음을 기억하십시오.


😱 Quiz Time 2. Variables

Q. Terraform 변수는 일반적으로 어디에 선언 됩니까?


이 장에서 우리는 :

  • terraform init 명령을 확인했습니다.
  • terraform plan 명령을 확인했습니다.
  • 변수에 대해 배웠습니다.
  • prefix(접두사) 설정을 했습니다.
+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html new file mode 100644 index 0000000000..8a1cd55e3b --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html @@ -0,0 +1,155 @@ + + + + + + + + + + 03. 테라폼 실행 | docmoa + + + + + +
본문으로 건너뛰기

03. 테라폼 실행

약 1 분ncloudncpterraformworkshop

03. 테라폼 실행

리소스 분석

모든 Terraform으로 구성되는 리소스는 정확히 동일한 방식으로 구성됩니다.

resource type "name" {
+  parameter = "foo"
+  parameter2 = "bar"
+  list = ["one", "two", "three"]
+}
+
  • resource : 최상위 키워드
  • type : 리소스 타입. Example: ncloud_vpc
  • name : 이 리소스를 참조하는 임의의 이름입니다. terraform에서 내부적으로 사용합니다. 이 필드는 변수가 될 수 없습니다.

Terraform Provider 구성

Terraform Core는 무엇이든 빌드하려면 하나 이상의 Provider가 필요합니다.

사용하려는 Provider의 버전을 쑤동으로 구성 할 수 있습니다. 이 옵션을 비워두면 Terraform은 기본적으로 사용 가능한 최신 버전의 Provider를 사용합니다.

terraform {
+  required_providers {
+    ncloud = {
+      source  = "NaverCloudPlatform/ncloud"
+      version = ">= 2.1.2"
+    }
+  }
+}
+
+provider "ncloud" { }
+




 





버전관리 연산자

  • = (or no operator): 정확한 버전 동등성
  • !=: 버전이 같지 않음
  • >, >=, <, <=: 버전 비교
  • ~>: 약한 제약, 허용되는 가장 오래된 버전과 최신 버전을 모두 제한합니다.
    ~> 0.9 설정은 다음과 같습니다. >= 0.9, < 1.0
    ~> 0.8.4 설정은 다음과 같습니다. >= 0.8.4, < 0.9

Terraform Apply

$ terraform apply
+...
+Terraform will perform the following actions:
+
+  # ncloud_vpc.hashicat will be created
+  + resource "ncloud_vpc" "hashicat" {
+      + default_access_control_group_no = (known after apply)
+      ...
+      + ipv4_cidr_block                 = "10.0.0.0/16"
+      + vpc_no                          = (known after apply)
+    }
+
+Plan: 1 to add, 0 to change, 0 to destroy.
+
+Do you want to perform these actions?
+  Terraform will perform the actions described above.
+  Only 'yes' will be accepted to approve.
+
+  Enter a value:
+
 


















terraform apply는 우선 plan을 실행하고, 승인하면 변경 사항을 적용합니다.

Terraform Destroy

$ terraform apply
+...
+Terraform will perform the following actions:
+
+  # ncloud_vpc.hashicat will be destoryed
+  - resource "ncloud_vpc" "hashicat" {
+      ...
+      - ipv4_cidr_block   = "10.0.0.0/16" -> null
+    }
+
+Plan: 0 to add, 0 to change, 1 to destroy.
+
+Do you want to perform these actions?
+  Terraform will perform the actions described above.
+  Only 'yes' will be accepted to approve.
+
+  Enter a value:
+
 
















terraform destroyaction과 반대 입니다. 승인하면 인프라가 제거됩니다.

Terraform Format

Terraform은 내장 된 코드 포맷터/클리너와 함께 제공됩니다. 모든 여백과 목록 들여 쓰기를 깔끔하고 깔끔하게 만들 수 있습니다. 아름다운 코드가 더 잘 동작하는 것(?) 같습니다.

terraform fmt
+

*.tf 파일이 포함 된 디렉토리에서 실행하기 만하면 코드가 정리됩니다.

Terraform Data Sources

data "ncloud_member_server_images" "prod" {
+ filter {
+    name = "name"
+    values = [data.terraform_remote_state.image_name.outputs.image_name]
+  }
+}
+
+resource "ncloud_server" "server" {
+  name                      = "${var.server_name}${random_id.id.hex}"
+  member_server_image_no    = data.ncloud_member_server_images.prod.member_server_images.0
+  server_product_code       = "SPSVRGPUSSD00001" 
+  login_key_name            = ncloud_login_key.key.key_name
+  zone                      = var.zone
+}
+

Data Source(data)는 Provider가 기존 리소스를 반환하도록 쿼리하는 방법입니다.

생성되어있는 리소스나 Provider로 조회할 수 있는 리소스 정보를 다른 리소스 구성에서 접근할 수 있습니다.

Terraform Dependency Mapping

data "ncloud_member_server_images" "prod" {
+ filter {
+    name = "name"
+    values = [data.terraform_remote_state.image_name.outputs.image_name]
+  }
+}
+
+resource "ncloud_server" "server" {
+  name                      = "${var.server_name}${random_id.id.hex}"
+  member_server_image_no    = data.ncloud_member_server_images.prod.member_server_images.0
+  server_product_code       = "SPSVRGPUSSD00001" 
+  login_key_name            = ncloud_login_key.key.key_name
+  zone                      = var.zone
+}
+



 




 
 

 
 

Terraform은 자동으로 종속성을 추적 할 수 있습니다. 앞서 설명된 리소스를 살펴보십시오. ncloud_server 리소스에서 강조 표시된 줄을 확인합니다. 이것이 테라 폼에서 한 리소스가 다른 리소스를 참조하도록하는 방법입니다.

Terraform 코드 구성

Terraform은 Workspace에서 .tf 확장자로 끝나는 모든 파일을 읽지만 대표적으로는 main.tf, variables.tf, outputs.tf를 갖는 것입니다. 원하는 경우 더 많은 tf 파일을 추가 할 수 있습니다.

파일 구조

Workspace
+├── `main.tf`
+├── `outputs.tf`
+├── terraform.tfvars
+└── `variables.tf`
+

이러한 각 파일을 자세히 살펴 보겠습니다.

main.tf 파일

첫 번째 파일은 main.tf입니다. 일반적으로 테라 폼 코드를 저장하는 곳입니다. 더 크고 복잡한 인프라를 사용하면이를 여러 파일로 나눌 수 있습니다.

resource "ncloud_vpc" "main" {
+  ipv4_cidr_block = var.address_space
+  name            = lower("${var.prefix}-vpc-${var.region}")
+}
+
+resource "ncloud_subnet" "main" {
+  name           = "${var.name_scn02}-public"
+  vpc_no         = ncloud_vpc.vpc_scn_02.id
+  subnet         = cidrsubnet(ncloud_vpc.main.ipv4_cidr_block, 8, 0)
+  zone           = "KR-2"
+  network_acl_no = ncloud_network_acl.network_acl_02_public.id
+  subnet_type    = "PUBLIC"
+}
+
+...생략...
+

variable.tf 파일

두 번째 파일은 variables.tf입니다. 여기에서 변수를 정의하고 선택적으로 일부 기본값을 설정합니다.

variable "prefix" {
+  description = "This prefix will be included in the name of most resources."
+}
+
+variable "region" {
+  description = "The region where the resources are created."
+  default     = "KR"
+}
+

output.tf 파일

output.tf 파일은 테라 폼 적용이 끝날 때 표시 할 메시지 또는 데이터를 구성하는 곳입니다.

output "acl_public_id" {
+  value = ncloud_network_acl.network_acl_public.id
+}
+
+output "public_addr" {
+  value = "http://${ncloud_public_ip.main.public_ip}:8080"
+}
+

Terraform Dependency Graph

terraform 리소스 그래프는 리소스 간의 종속성을 시각적으로 보여줍니다.

RegionPrefix 변수는 리소스 그룹을 만드는 데 필요하며 이는 가상 네트워크를 구축하는 데 필요합니다.


실습을 위해 다음장으로 이동하세요.

💻 Lab - Terraform in Action

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html new file mode 100644 index 0000000000..3d130431d0 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html @@ -0,0 +1,95 @@ + + + + + + + + + + 💻 Lab - Terraform in Action | docmoa + + + + + +
본문으로 건너뛰기

💻 Lab - Terraform in Action

약 2 분ncloudncpterraformworkshop

💻 Lab - Terraform in Action

편집기에서 열기

  • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
  • 앞서 받은 디렉토리내의 lab02을 열어줍니다.

📈 Terraform Graph

@slidestart blood

Terraform Graph는 모든 인프라에 대한 시각적 표현을 제공할 수 있습니다.


이는 변경의 영향을 받을 종속성 문제 또는 리소스를 찾는 데 유용합니다.

@slideend

💻 다음 terraform graph명령을 실행해 보세요.

새로운 Workspace 이므로, terraform init을 수행합니다.

terraform init
+

terraform graph를 수행합니다.

terraform graph
+

그러면 digraph로 시작하는 인프라의 시각적 맵을 만드는 데 사용할 수 있는 코드가 생성됩니다. 그래프 데이터는 DOT 그래프 설명 언어 형식 입니다. 무료 Blast Radius 도구를 포함하여 이 데이터를 시각화하는 데 사용할 수 있는 몇 가지 그래프 도구가 있습니다.

온라인에 Terraform의 작업을 시각화해주는 여러가지 툴이 있습니다. 간혹 plan 파일을 요구하는 툴이 있다면 주의하십시오. 민감한 정보가 포한된 plan의 경우 보안적으로 위험할 수 있습니다. 주의하여 사용하세요.

경고

plan 정보에는 인증키, 패스워드같은 노출하고 싶지 않은 정보가 포함될 수 있습니다.


👨‍💻 Terraform Plan & Terraform Apply

@slidestart blood

기본적으로 terraform apply 명령은 Terraform Plan을 실행하여 원하는 변경 사항을 보여줍니다.


이는 변경의 영향을 받을 종속성 문제 또는 리소스를 찾는 데 유용합니다.

@slideend

💻 필수 변수를 구성했으므로 변경 사항을 적용할 수 있습니다.

어떤 일이 일어날지 보려면 먼저 terraform plan 명령을 실행하십시오 .

terraform plan
+

계획 출력에 적절한 prefix, subnet cidr이 표시되는지 확인합니다. 원한다면 terraform.tfvars 혹은 variables.tf에 정의된 default값을 변경해보세요.

그런 다음 terraform apply를 실행하고 리소스가 구축되는 것을 지켜보십시오.

terraform apply
+

Terraform에서 "Do you want to perform these actions?"라는 메시지가 표시되면 yes 를 입력해야 합니다.

Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 메시지를 확인하였습니까? 에러가 발생하였다면 무엇이 문제인지 찾아보세요.

지금 우리 코드는 VPC만 정의합니다. 우리는 진행되는 실습에서 이 코드와 프로비저닝된 상태 기반으로 시작 할 것입니다.

NCP Consulopen in new window 화면에 접속해 보세요. 구성한 자원이 생성된 것이 확인되나요?

1. Products & Services

👩‍💻 Test and Repair

@slidestart blood

Terraform은 멱등성(idempotent)을 갖습니다.


멱등은 수학 및 컴퓨터 과학의 특정 연산의 속성으로, 초기 적용을 넘어 동일하다면 결과를 변경하지 않고 여러 번 적용할 수 있습니다.
참고 : https://en.wikipedia.org/wiki/Idempotenceopen in new window

@slideend

💻 멱등성의 검증을 해봅니다.

어떤 일이 일어날지 보려면 먼저 terraform plan 명령을 실행하십시오.

terraform plan
+

VPC가 이미 구축되었으므로 Terraform은 변경이 필요하지 않다고 보고합니다.

이는 정상적이며 예상된 것입니다. 이제 다른 명령인 terraform apply를 실행하고 지켜보십시오.

terraform apply
+

이미 올바르게 프로비저닝된 경우 VPC를 다시 생성하지 않습니다.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

🛫 Change Your Prefix

@slidestart blood

Terraform은 인프라를 Create, Destroy, Update, re-Create 합니다.


리소스 내용 변경 시

  • 일부 유형의 리소스는 내용 변경 시 삭제하지 않고 업데이트할 수 있습니다.
  • 또 어떤 경우는 Destroy 후 Create 되는 과정이 발생합니다.

Terraform은 항상 현재 인프라를 코드에 정의된 것과 일치시키려고 합니다.

@slideend

💻 terraform.tfvars를 변경합니다.

terraform.tfvars 파일을 편집하여 prefix를 기존과 다른 값으로 변경합니다.

변경 후 terraform apply를 실행하고 지켜보십시오.

terraform plan
+

VPC가 이미 구축되었으므로 Terraform은 변경이 필요하지 않다고 보고합니다.

이는 정상적이며 예상된 것입니다. 이제 다른 명령인 terraform apply를 실행하고 지켜보십시오.

terraform apply
+

Terraform에서 "Do you want to perform these actions?"라는 메시지가 표시되면 yes를 입력하고 완료되기를 기다립니다. 출력의 결과가 어떤가요?

VPC Update

2021년 10월 20일 기준으로, VPC의 이름이 변경되면 NCP에서는 이 자원을 재생성 합니다.
리소스에 대한 구성값의 변경이 유지된 채로 변경되기도 하지만, 때에 따라서는 삭제 후 재생성 합니다.

$ terraform apply
+ncloud_vpc.hashicat: Refreshing state... [id=13888]
+
+Terraform used the selected providers to generate the following execution plan.
+Resource actions are indicated with the following symbols:
+-/+ destroy and then create replacement
+
+Terraform will perform the following actions:
+
+  # ncloud_vpc.hashicat must be replaced
+-/+ resource "ncloud_vpc" "hashicat" {
+      ~ default_access_control_group_no = "26594" -> (known after apply)
+      ~ default_network_acl_no          = "19325" -> (known after apply)
+      ~ default_private_route_table_no  = "25834" -> (known after apply)
+      ~ default_public_route_table_no   = "25833" -> (known after apply)
+      ~ id                              = "13888" -> (known after apply)
+      ~ name                            = "yourname-vpc-kr" -> "hashicat-vpc-kr" # forces replacement
+      ~ vpc_no                          = "13888" -> (known after apply)
+        # (1 unchanged attribute hidden)
+    }
+
+Plan: 1 to add, 0 to change, 1 to destroy.
+
+Do you want to perform these actions?
+  Terraform will perform the actions described above.
+  Only 'yes' will be accepted to approve.
+
+  Enter a value: yes
+
+ncloud_vpc.hashicat: Destroying... [id=13888]
+ncloud_vpc.hashicat: Still destroying... [id=13888, 10s elapsed]
+ncloud_vpc.hashicat: Still destroying... [id=13888, 20s elapsed]
+ncloud_vpc.hashicat: Destruction complete after 23s
+ncloud_vpc.hashicat: Creating...
+ncloud_vpc.hashicat: Still creating... [10s elapsed]
+ncloud_vpc.hashicat: Still creating... [20s elapsed]
+ncloud_vpc.hashicat: Creation complete after 23s [id=13902]
+
+Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
+
 







































🛫 Create and Change ACL

@slidestart blood

Terraform은 인프라를 Create, Destroy, Update, re-Create 합니다.


리소스 내용 변경 시

  • 일부 유형의 리소스는 내용 변경 시 삭제하지 않고 업데이트할 수 있습니다.
  • 또 어떤 경우는 Destroy 후 Create 되는 과정이 발생합니다.

Terraform은 항상 현재 인프라를 코드에 정의된 것과 일치시키려고 합니다.


Terraform 코드는 한 번에 하나 또는 두 개의 리소스를 사용하여 점진적으로 빌드할 수 있습니다.

@slideend

💻 ncloud_network_acl을 추가합니다.

main.tf 파일을 열고 리소스 블록의 주석처리를 제거하려고 합니다.
리소스 유형은 ncloud_network_acl이고 이름은 public 입니다.

  • 각 줄의 시작 부분에서 # 문자를 제거하여 코드의 주석 처리를 제거합니다.

  • 코드편집기에서는 주석처리를 위해 해당 라인을 선택하고 활성/비활성 할 수 있습니다.

    • Mac : + /

    • Win : Ctrl + /

  • 주석 제거 후 파일을 저장하세요.

변경 후 terraform apply를 실행하고 yes를 입력하여 추가된 리소스가 생성되는지 확인하세요.

ncloud_network_acl리소스 내부의 vpc_no 파라메터를 확인합니다. 어떻게 가르키고 있나요?

해당 리소스는 VPC의 설정을 상속 받습니다.

Terraform은 수백개의 상호 연결되 리소스 간의 복잡한 종송석을 맵핑할 수 있습니다.

💻 ncloud_network_acl을 설정을 변경합니다.

ncloud_network_acl항목에 대해 description 의 내용을 수정해 보세요.

resource "ncloud_network_acl" "public" {
+  vpc_no      = ncloud_vpc.hashicat.id
+  name        = "${var.prefix}-acl-public"
+  description = "for Public"
+}
+



 

변경 후 terraform apply를 실행하고 yes를 입력하여 변경된 사항에 대해 리소스가 어떻게 되는지 확인하세요.

올바르게 프로비저닝된 경우 ncloud_network_acl를 삭제 후 다시 생성하지 않습니다.

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.


🏗️ Complete the Build

@slidestart blood

-auto-approve 플래그

해당 플래그를 사용하여 "Do you want to perform these actions?" 에 한 질문을 오버라이드(Override) 할 수 있습니다.


사용에 주의가 필요하니다.

검토 단계인 Plan을 건너뛰고 바로 Create/Update/Destroy 합니다.

@slideend

💻 애플리케이션을 배포해보세요.

  • main.tf의 모든 주석을 제거하세요.
  • outputs.tf의 모든 주석을 제거하세요.

terraform plan를 실행하여 구성할 리소스 항목을 확인합니다.

terraform plan
+

이제 Apply를 실행하여 HashiCat 애플리케이션을 빌드합니다.

terraform apply -auto-approve
+

애플리케이션이 배포를 완료하는데 5~10분이 소요될 수 있습니다. 실행이 끝날 때 애플리케이션 URL이 포함된 Terraform 출력을 보면 완료되었음을 알 수 있습니다.

catapp_url 출력 에서 URL을 클릭하여 새 브라우저 탭에서 웹 애플리케이션을 엽니다.

경고

응용 프로그램이 로드되지 않으면 terraform apply 다시 실행 하십시오. 이렇게 하면 웹 서버를 다시 설치하고 실행 중이 아닌 경우 응용 프로그램을 시작하려고 합니다.

💻 변경된 사항을 확인하기 위해 그래프를 다시 살펴봅니다.

terraform graph를 수행합니다.

terraform graph
+

https://dreampuf.github.io/GraphvizOnline/open in new window에 앞서 digraph로 시작하는 내용을 복사하여 붙여넣고 어떤 그림이 나오는지 확인해 봅니다.

인프라에 대한 Terraform 그래프를 살펴보십시오. 종속성이 자동으로 매핑됩니다.

Terraform은 이 그래프를 사용하여 최대 효율성을 위해 병렬로 구축할 수 있는 리소스를 결정합니다.


😱 Quiz Time 3. Terraform Apply

Q. Plan 파일을 지정하지 않고 terraform apply를 실행하면 어떻게 됩니까?


이 장에서 우리는 :

  • Terraform 리소스에 대해 배웠습니다.
  • Terraform Plan, Graph, Apply, Destory
  • 종속성에 대해 배웠습니다.
  • 실습에서 그래프를 확인해보았습니다.
  • main.tfopen in new window, variables.tfopen in new window, outputs.tf를 살펴보았습니다.
  • Meow World 애플리케이션을 구축하였습니다.
+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html new file mode 100644 index 0000000000..9fd999f528 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html @@ -0,0 +1,58 @@ + + + + + + + + + + 04. 테라폼 프로비저닝 도구 사용 및 구성 | docmoa + + + + + +
본문으로 건너뛰기

04. 테라폼 프로비저닝 도구 사용 및 구성

1분 미만ncloudncpterraformworkshop

04. 테라폼 프로비저닝 도구 사용 및 구성

Terraform 프로비저닝 도구 사용

Terraform을 사용하여 가상 머신 또는 컨테이너를 세우고 나면 운영 체제와 애플리케이션을 구성 할 수 있습니다.

여기에서 Provisioner 가 등장합니다.

Terraform은 Bash, Powershell, Chef, Puppet, Ansible 등을 포함한 여러 유형의 Provisioner를 지원합니다.

https://www.terraform.io/docs/provisioners/index.htmlopen in new window

File Provisioner

Terraform 파일 프로비저닝 도구는 원격 시스템에 파일을 복사합니다.

provisioner "file" {
+  source        = "files/"
+  destination   = "/home/${var.admin_username}/"
+  connection {
+    type        = "ssh"
+    user        = var.username
+    private_key = file(var.ssh_key)
+    host        = ${self.ip}
+  }
+}
+

provisioner 블록 안에있는 코드의 connection 블록에 주목하세요. 파일 프로비저닝 도구는 SSH, WinRM 연결을 모두 지원합니다.

Remote Exec Provisioner

Remote Exec Provisioner를 사용하면 대상 호스트에서 스크립트 또는 기타 프로그램을 실행할 수 있습니다.

자동으로 실행할 수있는 경우 (예 : 소프트웨어 설치 프로그램) remote-exec로 실행할 수 있습니다.

provisioner "remote-exec" {
+  inline = [
+    "sudo chown -R ${var.admin_username}:${var.admin_username} /var/www/html",
+    "chmod +x *.sh",
+    "PLACEHOLDER=${var.placeholder} WIDTH=${var.width} HEIGHT=${var.height} PREFIX=${var.prefix} ./deploy_app.sh",
+  ]
+...
+}
+

이 예에서는 일부 권한 및 소유권을 변경하고 일부 환경 변수가있는 스크립트를 실행하기 위해 몇 가지 명령을 실행합니다.

Terraform & Config Management Tools

Terraform은 Chef, Puppet, Ansible과 같은 일반적인 구성 관리 도구와 잘 작동합니다.

Terraform Provisioner에 대한 도움말

remote-exec와 같은 Terraform 프로비저닝 도구는 몇 가지 간단한 명령이나 스크립트를 실행해야 할 때 유용합니다. 더 복잡한 구성 관리의 경우 Chef 또는 Ansible과 같은 도구가 필요합니다.

Provisioner는 Terraform 실행이 처음 실행될 때 만 실행됩니다. 이러한 의미에서 그 동작들은 멱등성을 띄지 않습니다.

수명이 긴 VM 또는 서버의 지속적인 상태 관리가 필요한 경우 이같은 구성 관리 도구를 활용할 수 있습니다.

반면에 변경 불가능한 인프라를 원하면 Packeropen in new window 같은 이뮤터블을 위한 빌드 도구를 사용하는 것이 좋습니다.


실습을 위해 다음장으로 이동하세요.

💻 Lab - Provisioners, Variables, Outputs

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.html new file mode 100644 index 0000000000..b1fb75c2c9 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.html @@ -0,0 +1,64 @@ + + + + + + + + + + 💻 Lab - Provisioners, Variables, Outputs | docmoa + + + + + +
본문으로 건너뛰기

💻 Lab - Provisioners, Variables, Outputs

1분 미만ncloudncpterraformworkshop

💻 Lab - Provisioners, Variables, Outputs

편집기에서 열기

  • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
  • 앞서 실습을 진행한 lab02을 열어줍니다.

🛠️ Use a Provisioner

@slidestart blood

Terraform 프로비저닝 도구는 생성 시 한 번 실행됩니다.


특별한 상황을 제외하고는 후속 적용에서 실행되지 않습니다. (이 실습실처럼...)


terraform apply 를 입력할 때마다 프로비저닝 도구가 강제로 실행되도록 몇 가지 특별한 조정을 했습니다.

이는 변경할 때마다 가상 머신을 파괴하고 다시 생성하지 않고 프로비저닝 도구를 사용하여 연습할 수 있도록 하기 위한 것입니다.

triggers = {
+    build_number = timestamp()
+}
+

______________________
+< Cows love Terraform! >
+ ----------------------
+         \   ^__^
+          \  (oo)\_______
+             (__)\       )\/\
+                 ||----w |
+                 ||     ||
+=============================
+

@slideend

💻 Provisioner를 수정합니다.

main.tf 파일을 열어 remote-exec 항목이 있는 곳으로 이동합니다.

inline 항목에 다음을 두줄 추가합니다.

"sudo apt -y install cowsay",
+"cowsay Mooooooooooo!",
+

terraform fmt명령을 사용하여 코드를 멋지게 정렬 할 수 있는 좋은 시간 입니다.

이제 변경 사항을 적용하십시오.

terraform apply -auto-approve
+

로그 출력을 뒤로 스크롤합니다. "Moooooooo!"라고 말하는 ASCII 아트 암소가 보일 것입니다.

Result example

🖨️ Add an Output

@slidestart blood

출력에서 Terraform 데이터와 함께 일반 텍스트를 혼합할 수 있습니다.

출력은 실행이 끝날 때 사용자에게 유용한 정보를 전달하는 데 사용할 수 있습니다.


terraform refresh 명령은 상태 파일을 인프라에 있는 파일과 동기화합니다.

이 명령은 인프라를 변경하지 않습니다.


Terraform 출력을 다시 보고 싶을 때 언제든지 terraform output 명령을 실행할 수 있습니다.

단일 출력을 보려면 terraform output <output_name>을 실행합니다.

@slideend

💻 Output을 수정합니다.

output.tf 파일을 열어 아래 항목을 추가합니다.

output "ssh_info" {
+  value = nonsensitive("sshpass -p '${data.ncloud_root_password.hashicat.root_password}' ssh root@${ncloud_public_ip.hashicat.public_ip} -oStrictHostKeyChecking=no")
+}
+

해당 output의 이름은 ssh_info 입니다.

어떤 유형의 출력이 유효한지 보려면 문서 페이지를 참조하세요.

output.tf에 새로운 내용을 저장하고 terraform refresh 명령을 실행하여 새로운 출력을 확인합니다.

terraform refresh
+

terraform output 명령을 실행하여 모든 출력을 볼 수도 있습니다.

terraform output
+

🐶 Fun With Variables

@slidestart blood

Terraform 변수에는 5가지 수준의 우선 순위가 있습니다. 1=최고 5=최저:

  1. 명령줄 플래그 - 명령줄 스위치로 실행
  2. 구성 파일 - terraform.tfvars 파일에 설정
  3. 환경 변수 - 쉘 환경의 일부
  4. 기본 구성 - variables.tf의 기본값
  5. 사용자 수동 입력 - 지정하지 않은 경우 사용자에게 입력을 요청합니다.

다음은 placeholder 변수로 시도할 수 있는 다른 재미있는 사이트입니다.

@slideend

💻 변수를 조정합니다.

Terraform 변수를 구성하는 방법에는 여러 가지가 있습니다. 지금까지 terraform.tfvars 파일을 사용하여 변수를 설정했습니다.

명령줄에서 기본값과 다른 height, width 변수를 사용 하여 애플리케이션을 다시 배포해 보십시오.

변경 사항을 관찰하기 위해 적용할 때마다 웹 앱을 다시 로드합니다.

terraform apply -auto-approve -var height=600 -var width=800
+

다음으로 Terraform이 읽을 수 있는 환경 변수를 설정해 보십시오. 다음 명령을 실행하여 placeholder 변수를 설정합니다.

export TF_VAR_placeholder=placedog.net
+

환경 변수 적용 후 terraform apply -auto-approve를 실행하여 다시 적용해 봅니다.

terraform apply -auto-approve
+

이제 명령줄에서 동일한 변수를 다르게 설정하여 다시 시도하십시오.

terraform apply -auto-approve -var placeholder=placebear.com
+

어떤 변수가 우선시 되었습니까? 잘 이해 되셨나요?

다음 공식문서open in new window를 참고할 수 있습니다.


😱 Quiz Time 4. Terraform Variables

Q. *.tfvars 파일과 환경 변수에 동일한 변수가 설정되어 있습니다. 어느 것이 우선합니까?


이 장에서 우리는 :

  • Terraform Provisioners에 대해 배웠습니다.
  • fileremote-exec 프로비저닝 도구에 대해 알아보았습니다.
  • 새로운 프로비저닝 단계로 웹서버 재구성 해보았습니다.
+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html new file mode 100644 index 0000000000..ea24feef83 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html @@ -0,0 +1,64 @@ + + + + + + + + + + 05. 테라폼 상태파일(State) | docmoa + + + + + +
본문으로 건너뛰기

05. 테라폼 상태파일(State)

1분 미만ncloudncpterraformworkshop

05. 테라폼 상태파일(State)

Terraform State

Terraform은 stateful 애플리케이션입니다. 즉, state file 내부에서 빌드 한 모든 내용을 추적합니다.

앞서의 실습에서 반복된 Apply 작업 간에 Workspace 디렉토리에 나타난 terraform.tfstateterraform.tfstate.backup 파일을 보셨을 것입니다.

상태 파일은 Terraform이 알고있는 모든 것에 대한 기록 소스입니다.

파일 구조

WORKSPACE
+├── files
+│   └── deploy_app.sh
+├── main.tf
+├── outputs.tf
+├── `terraform.tfstate`
+├── `terraform.tfstate.backup`
+├── terraform.tfvars
+└── variables.tf
+

State 파일 내부는 JSON 형식으로 구성되어있습니다.

{
+  "version": 4,
+  "terraform_version": "0.12.7",
+  "serial": 14,
+  "lineage": "452b4191-89f6-db17-a3b1-4470dcb00607",
+  "outputs": {
+    "catapp_url": {
+      "value": "http://go-hashicat-5c0265179ccda553.workshop.aws.hashidemos.io",
+      "type": "string"
+    },
+

Terraform Refresh

때때로 인프라는 Terraform이 통제하는 범위 밖에서 변경 될 수 있습니다. (수동으로 UI에서 변경 등)

State 파일은 인프라의 마지막으로 갱신된 상태를 나타냅니다. 상태 파일이 빌드 한 파일과 여전히 일치하는지 확인하고 확인하려면 terraform refresh 명령을 사용할 수 있습니다.

이것은 인프라를 업데이트하지 않는 상태 파일 만 업데이트합니다.

terraform refresh
+

기존 인프라 변경

계획을 실행하거나 적용 할 때마다 Terraform은 세 가지 데이터 소스를 조정합니다.

  1. 코드에 작성한 내용
  2. 상태 파일
  3. 실제로 존재하는 것

Terraform은 *.tf 파일에있는 내용을 기반으로 기존 리소스를 추가, 삭제, 변경 또는 교체하기 위해 최선 을 다합니다. 다음은 Plan/Apply 중에 각 리소스에 발생할 수있는 네 가지 사항입니다.

+   create
+-   destroy
+-/+ replace
+~   update in-place
+

경고

무엇인가 변경할때 -/+ replace가 발생하는지 확인하세요. 이것은 기존 리소스를 삭제하고 다시 생성합니다.

😱 Terraform State Quiz :

각 시나리오에서 어떤 일이 발생합니까? 논의해 볼까요?

Configuration(.tf)StateRealityOperation
ncloud_server???
ncloud_serverncloud_server???
ncloud_serverncloud_serverncloud_server???
ncloud_serverncloud_server???
ncloud_server???
ncloud_server???
Configuration(.tf)StateRealityOperation
ncloud_servercreate
ncloud_serverncloud_servercreate
ncloud_serverncloud_serverncloud_server-
ncloud_serverncloud_serverdelete
ncloud_server-
ncloud_serverupdate state
+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html new file mode 100644 index 0000000000..bf0bc7e250 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html @@ -0,0 +1,40 @@ + + + + + + + + + + 06. Terraform Cloud | docmoa + + + + + +
본문으로 건너뛰기

06. Terraform Cloud

1분 미만ncloudncpterraformworkshop

06. Terraform Cloud

Terraform Cloud

Terraform Cloud는 Terraform을 사용하여 코드로 인프라를 작성하고 구축하기위한 최고의 워크 플로를 제공하는 무료 로 시작하는 SaaS 애플리케이션입니다.

  • State 저장 및 관리
  • Terraform 실행을보고 승인하기위한 웹 UI
  • 개인 모듈 레지스트리
  • VCS (Version Control System) 통합
  • CLI, API 또는 GUI 기반 작업
  • 실행 이벤트 알림
  • 자동화를위한 전체 HTTP API

Terraform Cloud는 Remote State 관리, API 기반 실행, 정책 관리 등과 같은 기능을 제공하는 호스팅 된 애플리케이션입니다. 많은 사용자가 클라우드 기반 SaaS 솔루션을 선호하는 이유 중 한가지는 인프라를 유지하여 실행하는 것이 부담될 때 입니다.

Terraform Enterprise는 동일한 애플리케이션이지만 클라우드 환경이나 데이터 센터에서 실행됩니다. 일부 사용자는 Terraform Enterprise 애플리케이션에 대한 더 많은 제어가 필요하거나 회사 방화벽 뒤의 제한된 네트워크에서 실행하려고합니다.

이 두 제품의 기능 목록은 거의 동일합니다. 다음 실습에서는 Terraform Cloud 계정을 사용할 것입니다.

Terraform Remote State

기본적으로 Terraform은 랩톱 또는 워크스테이션의 Workspace 디렉토리에 State 파일을 저장합니다. 이것은 개발 및 테스트에는 괜찮지만 프로덕션 환경에서는 상태 파일을 안전하게 보호하고 저장해야합니다.

Terraform에는 상태 파일을 원격으로 저장하고 보호하는 옵션이 있습니다. Terraform Cloud 계정은 이제 오픈 소스 사용자에게도 무제한 상태 파일 스토리지를 제공합니다.

모든 상태 파일은 암호화되어 (HashiCorp Vault 사용) Terraform Cloud 계정에 안전하게 저장됩니다. 상태 파일을 다시 잃어 버리거나 삭제하는 것에 대해 걱정할 필요가 없습니다.

Terraform Cloud Execution Modes

  • Local Run - Terraform 명령은 랩톱 또는 워크 스테이션에서 실행되며 모든 변수는 로컬로 구성됩니다. 테라 폼 상태 만 원격으로 저장됩니다.

  • Remote Run - Terraform 명령은 Terraform Cloud 컨테이너 환경에서 실행됩니다. 모든 변수는 원격 작업 공간에 저장됩니다. 코드는 Version Control System 저장소에 저장할 수 있습니다. 프리 티어 사용자의 경우 동시 실행이 1 회로 제한됩니다.

  • Agent Run - Terraform Cloud에서 내부네트워크에 있는 환경(VM, ldap 등)을 프로비저닝 하고자 할 때 내부에 실행을 위한 에이전트를 구성할 수 있습니다. Terraform Enterprise에서는 프로비저닝을 위한 프로세스를 여러 서버로 분산시킵니다.


실습을 위해 다음장으로 이동하세요.

💻 Lab - Terraform Cloud 연결

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html new file mode 100644 index 0000000000..7bf0bc49f7 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html @@ -0,0 +1,74 @@ + + + + + + + + + + 💻 Lab - Terraform Cloud 연결 | docmoa + + + + + +
본문으로 건너뛰기

💻 Lab - Terraform Cloud 연결

약 1 분ncloudncpterraformworkshop

💻 Lab - Terraform Cloud 연결

편집기에서 열기

  • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
  • 앞서 실습을 진행한 lab02을 열어줍니다.

☁️ Terraform Configuration

@slidestart blood

Terraform Cloud

Remote State 저장소는 모든 사용자에게 무료입니다.

@slideend

Terraform Cloud 계정

Terraform Cloud는 다른 SaaS 서비스와 같이 개인을 위한 무료 플랜이 준비되어있습니다.

아직 계정이 없는 경우 계성을 생성하고 다음 실습을 진행합니다.

  1. 계정 생성을 위해 https://app.terraform.io/signup/accountopen in new window로 접속합니다.
  2. 필요한 정보를 입력하고 확인하여 신규 계정을 생성합니다.
  3. 가입한 이메일로 계정 생성 확인 메시지가 도착합니다. 링크를 확인하면 Terraform Cloud를 사용할 준비가 끝났습니다.

💻 Terraform Cloud를 설정합니다.

  1. Terraform Cloud에 로그인하면 YOURNAME-training 이라는 새 조직을 만듭니다. YOURNAME을 자신의 이름이나 다른 텍스트로 바꾸십시오.

  2. 다음으로 Workspace를 생성하라는 메시지가 표시됩니다. CLI 기반 워크플로 패널을 클릭하여 VCS 통합 단계를 건너뛸 수 있습니다.

  3. 작업 공간의 이름을 hashicat-ncp 로 지정 하고 Create workspace를 클릭하여 새로운 Workspace를 생성합니다.

  4. 터미널에서 terraform version 을 실행하여 버전을 확인합니다.

  5. Terraform Cloud 상에 생성한 hashicat-ncpSettings > General 로 이동하여 Terraform Version을 동일한 버전으로 구성합니다. 그리고 Execution Mode를 Local로 설정합니다.

Setting 위치
  1. Settings 페이지 하단에 버튼을 클릭하여 저장합니다.

🎛️ Configure Remote State

@slidestart blood

"Local" 실행 모드는 Terraform의 구성과 변수는 모두 워크스테이션에 남아있습니다.


"Remote" 실행 모드로 전환하게 되면 Terraform Cloud 환경의 컨테이너 환경에서 실행됩니다.


"Remote" 실행 모드로 전환 시, 변수구성은 Terraform Cloud 환경에 설정해야 합니다.

@slideend

이번 실습에서는 Terraform Cloud를 Remote State Backend로 구성하여 기존 State 파일을 Terraform Cloud 환경으로 마이그레이션 합니다.

💻 Remote Backend 구성하기

Workspace 디렉토리에 (main.tf와 같은 위치) 아래와 같은 내용으로 remote_backend.tf 파일을 생성합니다.

# remote_backend.tf
+terraform {
+  backend "remote" {
+    hostname = "app.terraform.io"
+    organization = "YOURORGANIZATION"
+    workspaces {
+      name = "hashicat-ncp"
+    }
+  }
+}
+

YOURORGANIZATION을 생성한 Organization 이름으로 수정합니다.

이후 터미널에서 terraform login 을 입력합니다. 로컬 환경에 Terraform Cloud와 API 인증을 위한 Token을 생성하는 과정입니다. yes를 입력하면 Terraform Cloud의 토큰 생성화면이 열립니다.

$ terraform login
+Terraform will request an API token for app.terraform.io using your browser.
+...
+Do you want to proceed?
+  Only 'yes' will be accepted to confirm.
+
+  Enter a value: 
+
 






Create API token 화면이 나오면 Description에 적절한 값(예: ncp workshop)을 입력한 후 버튼을 클릭하여 새로운 Token을 생성합니다.

Token 생성하기

생성된 Token을 복사하여 앞서 터미널에 새로운 입력란인 Enter a value: 에 붙여넣고 (엔터)를 입력합니다. (입력된 값은 보이지 않습니다.)

...
+Generate a token using your browser, and copy-paste it into this prompt.
+
+Terraform will store the token in plain text in the following file
+for use by subsequent commands:
+    /Users/yourname/.terraform.d/credentials.tfrc.json
+
+Token for app.terraform.io:
+  Enter a value: ****************************************** 
+

해당 토큰은 터미널에 표기된 credentials.tfrc.json 파일에 저장됩니다.

터미널에서 terraform init을 실행합니다.

State를 Terraform Cloud로 마이그레이션하라는 메시지가 표시되면 "yes"를 입력합니다.

backend가 remote로 구성됨이 성공함을 확인합니다.

$ terraform init
+...
+Initializing the backend...
+
+Successfully configured the backend "remote"! Terraform will automatically
+use this backend unless the backend configuration changes.
+...
+
 






이제 상태가 Terraform Cloud에 안전하게 저장됩니다. TFC UI에서 작업 영역의 "State" 탭에서 이를 확인할 수 있습니다.

변수들을 변경하면서 terraform apply -auto-approve를 실행하고, 상태 파일이 리소스가 변경될 때마다 변경되는 것을 지켜보십시오. Terraform Cloud UI를 사용하여 이전 상태 파일을 탐색할 수 있습니다.


🔥 Terraform Destroy

@slidestart blood

Terraform은 인프라를 구축하는 것만큼 쉽게 인프라를 파괴할 수 있습니다.


"terraform destroy"는 주의하여 사용하세요.

@slideend

💻 리소스 삭제하기

다음 명령을 실행하여 인프라를 삭제하세요.

terraform destroy
+

인프라를 삭제한다는 메시지가 표시되면 "yes"를 입력해야 합니다. 중요한 리소스가 실수로 삭제되는 것을 방지하기 위한 안전 기능입니다.

확인 버튼을 클릭하기 전에 리소스 삭제 작업이 완전히 끝날 때까지 기다리십시오.


Intro
Intro
+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/index.html b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/index.html new file mode 100644 index 0000000000..69d524ebf3 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Intro to Terraform on Ncp | docmoa + + + + + +
본문으로 건너뛰기

01 Intro to Terraform on Ncp

1분 미만

+ + + diff --git a/03-PublicCloud/NCP/09-Terraform-Workshop/index.html b/03-PublicCloud/NCP/09-Terraform-Workshop/index.html new file mode 100644 index 0000000000..4b2d91ca11 --- /dev/null +++ b/03-PublicCloud/NCP/09-Terraform-Workshop/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 09 Terraform Workshop | docmoa + + + + + +
본문으로 건너뛰기

09 Terraform Workshop

1분 미만

+ + + diff --git a/03-PublicCloud/NCP/index.html b/03-PublicCloud/NCP/index.html new file mode 100644 index 0000000000..a7e6505296 --- /dev/null +++ b/03-PublicCloud/NCP/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + NCP(Naver Cloud Platform) | docmoa + + + + + +
본문으로 건너뛰기

NCP(Naver Cloud Platform)

1분 미만

+ + + diff --git a/03-PublicCloud/index.html b/03-PublicCloud/index.html new file mode 100644 index 0000000000..82717cf68c --- /dev/null +++ b/03-PublicCloud/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Public Cloud | docmoa + + + + + +
본문으로 건너뛰기

Public Cloud

1분 미만Cloud

+ + + diff --git a/04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.html b/04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.html new file mode 100644 index 0000000000..b02d6293d3 --- /dev/null +++ b/04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.html @@ -0,0 +1,84 @@ + + + + + + + + + + HCP Packer 소개 | docmoa + + + + + +
본문으로 건너뛰기

HCP Packer 소개

약 1 분PackerHCPTerraform

HCP Packer 소개

HashiCorp의 제품은 설치형과 더불어 SaaS 모델로도 사용가능한 모델이 제공됩니다. 여기에는 지금까지 Terraform Cloud, HCP Vault, HCP Consul 이 제공되었습니다. HCP는 HashiCorp Cloud Platform의 약자 입니다.

여기에 최근 HCP Packer가 공식적으로 GA(General Available)되었습니다. HashiCorp의 솔루션들에 대해서 우선 OSS(Open Source Software)로 떠올려 볼 수 있지만 기업을 위해 기능이 차별화된 설치형 엔터프라이즈와 더불어 클라우드형 서비스도 제공되고 있으며 향후 새로운 솔루션들이 추가될 전망입니다.

Packer

Packer는 HashiCorp 에서 Vagrant에 이어 두번째로 릴리즈된 OSS 제품 입니다. 기존에는 표준 이미지(골든 이미지) 생성을 위해 사용자가 OS 설치 이후 접속하여 패키지, 파일, 설정 들을 수행 후 빠져나와 이미지를 생성하였다면, Packer는 미리 정의된 구성파일로 이미지를 생성하고 여러 플랫폼에 동시적으로 생성할 수 있습니다.

Amazone EC2(AMI), AZure VM, GCP GCE(Image)와 같은 주요 클라우드 밴더는 물론 국내 클라우드 밴더인 Naver Cloud Platform을 지원하고, 프라이빗 환경의 OpenStack과 VMware, 컨테이너인 Docker를 지원합니다.

플러그인이 다양하게 준비되어 있어 빌드 작업시 스크립트는 당연하고, Ansible 같은 구성관리 코드 툴과도 조합하여 이미지를 생성하는 동작을 코드화하고 자동화 합니다.

HCP Packer

HCP Packer가 제공하는 기능은 Packer가 생성한 이미지 Metadata에 대한 Registry 기능입니다. 개념적인 이해가 필요한 부분은 Packer로 생성되는 이미지 자체는 해당 플랫폼에 저장되며 HCP Packer는 해당 이미지에 대한 정보를 저장한다는 것으로 기존 Packer OSS와 함께 사용된다는 점입니다.

HCP Packer Registry의 활용

이미지 Metadata에 대한 Registry로서의 기능이 서비스로 제공된다는 것이 어떤 문제를 해결하기 위함인지에 대해 이해가 필요합니다.

기업 환경에서 표준 이미지에 대한 관리 및 관련하여 Packer를 이용하면 이미지는 자동화되어 쉽게 발생하지만 작성된 이미지를 활용하는데에 어려움이 발생합니다. 몇가지 예를 들면 다음과 같은 문제점이 있습니다.

  • 어떤 이미지가 관리되는 최신의 이미지인지 확인 필요
  • 생성된 이미지가 어떤 시점의 코드로 작성된 것인지
  • IaC와 연계시 이미지 ID는 사람이 알아보기 어려움
  • 다중 플랫폼, 다중 리전에 이미지를 통합으로 관리해야 함
  • 생성되는 VM 이미지의 생성자와 이용자 모두에게 이미지 관리에 대한 목록관리와 비용 이슈

여러 문제를 해결하기 위해 Packer에서 빌드 시 HCP Packer Registry에 Metadata를 동시에 등록하고 이미지의 속성 정보를 확인할 수 있게 되어 관리성을 높이고 외부 도구에서 명확한 이미지 ID를 쉽게 얻는 인터페이스를 제공할 수 있습니다.

Iterations / Channels

HCP Packer Registry의 주요 개념은 이미지 순환(Iterations)과 이미지 채널(Channels)입니다.

Packer의 빌드마다 Iterations에 작성된 이미지의 정보가 추가됩니다.

이렇게 추가된 Iteration 정보는 빌드시마다 기록되어 기존 Packer OSS 대비 이미지 생성에 대한 기록을 확인할 수 있습니다.

각 Iteration 항목을 클릭하면 빌드의 세부적보를 확인 할 수 있고 아래 이미지에서는 AWS와 Azure에 대한 각 멀티 클라우드, 멀티 리전에 대한 생성 정보를 확인 할 수 있습니다.

Channels는 특정 Channel에 대해 기존 작성된 Iteration을 할당할 수 있는 객체 입니다. Channel을 통해 Terraform을 포함한 외부 툴은 Iteration의 버전을 신경쓰지 않고 원하는 Channel의 이름만 알고 있으면 항상 유효한 이미지 정보를 취득할 수 있습니다. 아래 이미지에서는 Channel을 사용자가 알기 쉬운 이름으로 구성하고 작성된 Iteration 의 버전을 맵핑하는 것을 확인 할 수 있습니다.

HCP Packer Template

HCP Packer에 이미지 Metadata를 등록하는 방법은 기존 Packer로 작성된 선언의 build 블록에 hip_packer_registry 속성을 정의하는 것입니다. 관련 수행을 위한 안내는 learn.hashicorp.com의 내용을 확인할 수 있습니다.

build {
+  hcp_packer_registry {
+    bucket_name = "learn-packer-ubuntu"
+    description = <<EOT
+Some nice description about the image being published to HCP Packer Registry.
+    EOT
+    bucket_labels = {
+      "owner"          = "platform-team"
+      "os"             = "Ubuntu",
+      "ubuntu-version" = "Focal 20.04",
+    }
+
+    build_labels = {
+      {/* "build-time"   = timestamp()
+      "build-source" = basename(path.cwd) */}
+    }
+  }
+  sources = [
+    "source.amazon-ebs.basic-example-east",
+    "source.amazon-ebs.basic-example-west"
+  ]
+}
+

현재 모든 Packer Plugin이 HCP Packer를 지원하는 것은 아니므로 Plugin 페이지에서 HCP Packer Ready 표시가 되어있는지 확인이 필요합니다. 예를들어 Docker Plugin의 페이지를 확인해보면 지원되고 있는 표시를 확인 할 수 있습니다.

기업의 Governance/Policy를 준수

기업내에서는 이미지에 대한 보안 규정 준수를 위해 Image의 revoke(취소)를 지원합니다. revoke된 Iteration은 관리자에 의해 완전 삭제되지 않는다면 복구하는 것도 가능합니다. 예를 들어 작성된 이미지이용을 중단하고 싶은 경우 Revoke Immediately 요청과 관련 설명을 추가할 수 있습니다.

Terraform 연계

HCP Packer의 정보는 외부 솔루션에서도 활용 가능합니다. Terraform과의 워크플로우에서 사용시에도 hcp 프로바이더가 추가되어 저장된 정보를 데이터 소스로 활용 가능합니다.

# This assumes HCP_CLIENT_ID and HCP_CLIENT_SECRET env variables are set
+provider "hcp" { }
+
+data "hcp_packer_iteration" "ubuntu" {
+  bucket_name = "learn-packer-ubuntu"
+  channel     = "development"
+}
+
+data "hcp_packer_image" "ubuntu_us_west_1" {
+  bucket_name    = "learn-packer-ubuntu"
+  cloud_provider = "aws"
+  iteration_id   = data.hcp_packer_iteration.ubuntu.ulid
+  region         = "us-west-1"
+}
+
+output "ubuntu_iteration" {
+  value = data.hcp_packer_iteration.ubuntu
+}
+
+output "ubuntu_us_west_1" {
+  value = data.hcp_packer_image.ubuntu_us_west_1
+}
+

Terraform Cloud Run Task

Terraform Cloud Business를 사용하는 경우 HCP Packer에서 제공하는 Terraform Cloud Run Tasks기능과 통합시킬 수 있습니다. Terraform Apply시 HCP Packer에서 제공하는 Run Tasks 정책이 적용되면 Plan과 Apply 단계 중간에 명확한 이미지에 대한 확인 및 오류 메시지를 발견할 수 있습니다.

또한 이미지사 사용되는 AWS, Azure, GCP의 리소스에서 하드코딩되는 이미지 ID를 검색하거나 사용하지 못하게 경고 또는 실패하는 동작을 수행 할 수 있습니다.

사용 요금

무료 플랜이 제공되며 최대 10개의 이미지와 월 250건의 API 요청을 지원합니다. Standard 플랜 부터는 이미지 제한은 없고 시간 당 추적되는 이미지 총 개수와 요청 건에 대해 부과 되며 기술지원이 포함됩니다.

+ + + diff --git a/04-HashiCorp/01-Packer/01-Information/index.html b/04-HashiCorp/01-Packer/01-Information/index.html new file mode 100644 index 0000000000..8678aa40a6 --- /dev/null +++ b/04-HashiCorp/01-Packer/01-Information/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Information | docmoa + + + + + +
본문으로 건너뛰기

01 Information

1분 미만

+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.html b/04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.html new file mode 100644 index 0000000000..0324781922 --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.html @@ -0,0 +1,132 @@ + + + + + + + + + + Alibaba Cloud Packer Sample | docmoa + + + + + +
본문으로 건너뛰기

Alibaba Cloud Packer Sample

1분 미만PackerSampleAlibaba

Alibaba Cloud Packer Sample

packer.pkr.hcl

# packer build -force .
+
+locals {
+  access_key = vault("/kv-v2/data/alicloud", "access_key")
+  secret_key = vault("/kv-v2/data/alicloud", "secret_key")
+}
+
+variable "region" {
+  default     = "ap-southeast-1"
+  description = "https://www.alibabacloud.com/help/doc-detail/40654.htm"
+}
+
+source "alicloud-ecs" "basic-example" {
+  access_key           = local.access_key
+  secret_key           = local.secret_key
+  region               = var.region
+  image_name           = "ssh_otp_image_1_5"
+  source_image         = "centos_7_9_x64_20G_alibase_20210623.vhd"
+  ssh_username         = "root"
+  instance_type        = "ecs.n1.tiny"
+  io_optimized         = true
+  internet_charge_type = "PayByTraffic"
+  image_force_delete   = true
+}
+
+build {
+  sources = ["sources.alicloud-ecs.basic-example"]
+
+  provisioner "file" {
+    source      = "./files/"
+    destination = "/tmp"
+  }
+
+# Vault OTP
+  provisioner "shell" {
+    inline = [
+      "cp /tmp/sshd /etc/pam.d/sshd",
+      "cp /tmp/sshd_config /etc/ssh/sshd_config",
+      "mkdir -p /etc/vault.d",
+      "cp /tmp/vault.hcl /etc/vault.d/vault.hcl",
+      "cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper",
+      "/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev",
+      "sudo adduser test",
+      "echo password | passwd --stdin test",
+      "echo 'test ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers",
+      "sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config"
+    ]
+  }
+
+# Apache
+  provisioner "shell" {
+    inline = [
+      "sudo yum -y update",
+      "sleep 15",
+      "sudo yum -y update",
+      "sudo yum -y install httpd",
+      "sudo systemctl enable httpd",
+      "sudo systemctl start httpd",
+      "chmod +x /tmp/deploy_app.sh",
+      "PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh",
+      # "sudo firewall-cmd --zone=public --permanent --add-port=80/tcp",
+      # "sudo firewall-cmd --reload",
+    ]
+  }
+}
+
+variable "placeholder" {
+  default     = "placekitten.com"
+  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
+}
+

deploy_app.sh (option)

#!/bin/bash
+# Script to deploy a very simple web application.
+# The web app has a customizable image and some text.
+
+cat << EOM > /var/www/html/index.html
+<html>
+  <head><title>Meow!</title></head>
+  <body>
+  <div style="width:800px;margin: 0 auto">
+
+  <!-- BEGIN -->
+  <center><img src="http://${PLACEHOLDER}/${WIDTH}/${HEIGHT}"></img></center>
+  <center><h2>Meow World!</h2></center>
+  Welcome to ${PREFIX}'s app. Replace this text with your own.
+  <!-- END -->
+
+  </div>
+  </body>
+</html>
+EOM
+
+echo "Script complete."
+
+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/Azure.html b/04-HashiCorp/01-Packer/05-SamplePkr/Azure.html new file mode 100644 index 0000000000..52745276d2 --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/Azure.html @@ -0,0 +1,170 @@ + + + + + + + + + + Azure Packer Sample | docmoa + + + + + +
본문으로 건너뛰기

Azure Packer Sample

약 1 분PackerSampleAzure

Azure Packer Sample

packer.pkr.hcl

# packer init -upgrade .
+# packer build -force .
+
+locals {
+  client_id = vault("/kv/data/azure", "client_id")
+  client_secret = vault("/kv/data/azure", "client_secret")
+  tenant_id = vault("/kv/data/azure", "tenant_id")
+  subscription_id = vault("/kv/data/azure", "subscription_id")
+  resource_group_name = var.resource_name
+  virtual_network_name = "kbid-d-krc-vnet-002"
+  virtual_network_subnet_name  = "d-mgmt-snet-001"
+  virtual_network_resource_group_name  = "kbid-d-krc-mgmt-rg"
+  timestamp = formatdate("YYYYMMDD_hhmmss", timeadd(timestamp(), "9h")) #생성되는 이미지 이름을 time 기반으로 생성
+}
+
+variable "placeholder" {
+  default     = "placekitten.com"
+  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
+}
+
+# Basic example : https://www.packer.io/docs/builders/azure/arm#basic-example
+# MS Guide : https://docs.microsoft.com/ko-kr/azure/virtual-machines/linux/build-image-with-packer
+source "azure-arm" "basic-example" {
+  client_id = local.client_id
+  client_secret = local.client_secret
+  subscription_id = local.subscription_id
+  tenant_id = local.tenant_id
+
+  # shared_image_gallery {
+  #   subscription = local.subscription_id
+  #   resource_group = "myrg"
+  #   gallery_name = "GalleryName"
+  #   image_name = "gs_pkr_${local.timestamp}"
+  #   image_version = "1.0.0"
+  # }
+  managed_image_resource_group_name = local.resource_group_name
+  managed_image_name = "${var.image_name}-${local.timestamp}"
+
+  os_type = "Linux"
+  # az vm image list-publishers --location koreacentral --output table
+  image_publisher = "RedHat"
+  # az vm image list-offers --location koreacentral --publisher RedHat --output table
+  image_offer = "RHEL"
+  # az vm image list-skus --location koreacentral --publisher RedHat --offer RHEL --output table
+  image_sku = "8_4"
+
+  azure_tags = {
+    dept = "KBHC Terraform POC"
+  }
+  
+  # az vm list-skus --location koreacentral --all --output table
+  build_resource_group_name = local.resource_group_name
+
+  #########################################
+  # 기존 생성되어있는 network 를 사용하기 위한 항목 #
+  #########################################
+  virtual_network_name = local.virtual_network_name
+  virtual_network_subnet_name = local.virtual_network_subnet_name
+  virtual_network_resource_group_name = local.virtual_network_resource_group_name
+  
+  # location = "koreacentral"
+  vm_size = "Standard_A2_v2"
+}
+
+build {
+  sources = ["sources.azure-arm.basic-example"]
+
+  provisioner "file" {
+    source      = "./files/"
+    destination = "/tmp"
+  }
+
+# Vault OTP
+  provisioner "shell" {
+    inline = [
+      "sudo cp /tmp/sshd /etc/pam.d/sshd",
+      "sudo cp /tmp/sshd_config /etc/ssh/sshd_config",
+      "sudo mkdir -p /etc/vault.d",
+      "sudo cp /tmp/vault.hcl /etc/vault.d/vault.hcl",
+      "sudo cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper",
+      "echo \"=== Vault_Check ===\"",
+      "curl http://10.0.9.10:8200",
+      "/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev",
+      "echo \"=== Add User ===\"",
+      "sudo adduser jboss",
+      "echo password | sudo passwd --stdin jboss",
+      "echo 'jboss ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /etc/sudoers",
+      "echo \"=== SELINUX DISABLE ===\"",
+      "sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config"
+    ]
+  }
+
+# Apache
+  provisioner "shell" {
+    inline = [
+      "sudo yum -y update",
+      "sleep 15",
+      "sudo yum -y update",
+      "sudo yum -y install httpd",
+      "sudo systemctl enable httpd",
+      "sudo systemctl start httpd",
+      "chmod +x /tmp/deploy_app.sh",
+      "sudo PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh",
+      "sudo firewall-cmd --zone=public --permanent --add-port=80/tcp",
+      "sudo firewall-cmd --reload",
+    ]
+  }
+}
+

deploy_app.sh (option)

#!/bin/bash
+# Script to deploy a very simple web application.
+# The web app has a customizable image and some text.
+
+cat << EOM > /var/www/html/index.html
+<html>
+  <head><title>Meow!</title></head>
+  <body>
+  <div style="width:800px;margin: 0 auto">
+
+  <!-- BEGIN -->
+  <center><img src="http://${PLACEHOLDER}/${WIDTH}/${HEIGHT}"></img></center>
+  <center><h2>Meow World!</h2></center>
+  Welcome to ${PREFIX}'s app. Replace this text with your own.
+  <!-- END -->
+
+  </div>
+  </body>
+</html>
+EOM
+
+echo "Script complete."
+
+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/GCP.html b/04-HashiCorp/01-Packer/05-SamplePkr/GCP.html new file mode 100644 index 0000000000..ade21a41c6 --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/GCP.html @@ -0,0 +1,95 @@ + + + + + + + + + + Google Cloud Platform Packer Sample | docmoa + + + + + +
본문으로 건너뛰기

Google Cloud Platform Packer Sample

1분 미만PackerSampleGCP

Google Cloud Platform Packer Sample

packer.pkr.hcl

variable "base_image" {
+  default = "ubuntu-1804-bionic-v20210415"
+}
+variable "project" {
+  default = "gs-test-282101"
+}
+variable "region" {
+  default = "asia-northeast2"
+}
+variable "zone" {
+  default = "asia-northeast2-a"
+}
+variable "image_name" {
+  
+}
+variable "placeholder" {
+  default     = "placekitten.com"
+  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
+}
+
+source "googlecompute" "basic-example" {
+  project_id = var.project
+  source_image = var.base_image
+  ssh_username = "ubuntu"
+  zone = var.zone
+  disk_size = 10
+  disk_type = "pd-ssd"
+  image_name = var.image_name
+}
+
+build {
+  name = "packer"
+  source "sources.googlecompute.basic-example" {
+      name = "packer"
+  }
+
+  provisioner "file"{
+    source = "./files"
+    destination = "/tmp/"
+  }
+
+  provisioner "shell" {
+    inline = [
+      "sudo apt-get -y update",
+      "sleep 15",
+      "sudo apt-get -y update",
+      "sudo apt-get -y install apache2",
+      "sudo systemctl enable apache2",
+      "sudo systemctl start apache2",
+      "sudo chown -R ubuntu:ubuntu /var/www/html",
+      "chmod +x /tmp/files/*.sh",
+      "PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/files/deploy_app.sh",
+    ]
+  }
+}
+
+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/aws-ubuntu.html b/04-HashiCorp/01-Packer/05-SamplePkr/aws-ubuntu.html new file mode 100644 index 0000000000..e81a0259fd --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/aws-ubuntu.html @@ -0,0 +1,104 @@ + + + + + + + + + + AWS Packer Sample - Ubuntu | docmoa + + + + + +
본문으로 건너뛰기

AWS Packer Sample - Ubuntu

1분 미만PackerSampleaws

AWS Packer Sample - Ubuntu

ubuntu.pkr.hcl

# packer init client.pkr.hcl
+# packer build -force .
+
+variable "region" {
+  default = "ap-northeast-2"
+}
+
+variable "cni-version" {
+  default = "1.0.1"
+}
+
+packer {
+  required_plugins {
+    amazon = {
+      version = ">= 0.0.2"
+      source  = "github.com/hashicorp/amazon"
+    }
+  }
+}
+
+source "amazon-ebs" "example" {
+  ami_name      = "gs_demo_ubuntu_{{timestamp}}"
+  instance_type = "t3.micro"
+  region        = var.region
+  source_ami_filter {
+    filters = {
+      name                = "ubuntu/images/*ubuntu-bionic-18.04-amd64-server-*"
+      root-device-type    = "ebs"
+      virtualization-type = "hvm"
+    }
+    most_recent = true
+    owners      = ["099720109477"]
+  }
+  ssh_username = "ubuntu"
+}
+
+build {
+  sources = ["source.amazon-ebs.example"]
+
+  provisioner "file" {
+    source      = "./file/"
+    destination = "/tmp"
+  }
+
+  provisioner "shell" {
+    inline = [
+      "set -x",
+      "echo Connected via Consul/Nomad client at \"${build.User}@${build.Host}:${build.Port}\"",
+      "sudo apt-get update",
+      "sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release",
+      "sudo apt-get update",
+      "curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -",
+      "sudo apt-add-repository \"deb [arch=amd64] https://apt.releases.hashicorp.com bionic main\"",
+      "sudo apt-get update && sudo apt-get -y install consul nomad netcat nginx",
+      "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -",
+      "sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable\"",
+      "sudo apt-get update",
+      "sudo apt-get install -y docker-ce openjdk-11-jdk",
+      "curl -sL -o cni-plugins.tgz https://github.com/containernetworking/plugins/releases/download/v${var.cni-version}/cni-plugins-linux-amd64-v${var.cni-version}.tgz",
+      "sudo mkdir -p /opt/cni/bin",
+      "sudo tar -C /opt/cni/bin -xzf cni-plugins.tgz",
+    ]
+  }
+}
+
+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/aws-windows.html b/04-HashiCorp/01-Packer/05-SamplePkr/aws-windows.html new file mode 100644 index 0000000000..d4a6b11f82 --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/aws-windows.html @@ -0,0 +1,158 @@ + + + + + + + + + + AWS Packer Sample - Windows | docmoa + + + + + +
본문으로 건너뛰기

AWS Packer Sample - Windows

약 2 분PackerSampleaws

AWS Packer Sample - Windows

참고 : Build a Windows Imageopen in new window

windows.pkr.hcl

variable "region" {
+  default = "ap-northeast-2"
+}
+
+variable "cni-version" {
+  default = "1.0.1"
+}
+
+locals {
+  nomad_url  = "https://releases.hashicorp.com/nomad/1.2.3/nomad_1.2.3_windows_amd64.zip"
+  consul_url = "https://releases.hashicorp.com/consul/1.11.1/consul_1.11.1_windows_amd64.zip"
+  jre_url    = "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jre_x64_windows_hotspot_11.0.13_8.zip"
+}
+
+packer {
+  required_plugins {
+    amazon = {
+      version = ">= 0.0.2"
+      source  = "github.com/hashicorp/amazon"
+    }
+  }
+}
+
+source "amazon-ebs" "example" {
+  ami_name      = "gs_demo_windows_{{timestamp}}"
+  communicator  = "winrm"
+  instance_type = "t2.micro"
+  region        = var.region
+  source_ami_filter {
+    filters = {
+      name                = "*Windows_Server-2019-English-Full-Base*"
+      root-device-type    = "ebs"
+      virtualization-type = "hvm"
+    }
+    most_recent = true
+    owners      = ["amazon"]
+  }
+  user_data_file = "./bootstrap_win.txt"
+  winrm_password = "SuperS3cr3t!!!!"
+  winrm_username = "Administrator"
+}
+
+build {
+  sources = ["source.amazon-ebs.example"]
+
+  provisioner "powershell" {
+    inline = [
+      "New-Item \"C:\\temp\" -ItemType Directory",
+    ]
+  }
+
+  // provisioner "file" {
+  //   source = "./file/"
+  //   destination = "/tmp"
+  // }
+
+  provisioner "powershell" {
+    inline = [
+      "New-Item \"C:\\hashicorp\\jre\\\" -ItemType Directory",
+      "New-Item \"C:\\hashicorp\\consul\\bin\\\" -ItemType Directory",
+      "New-Item \"C:\\hashicorp\\consul\\data\\\" -ItemType Directory",
+      "New-Item \"C:\\hashicorp\\consul\\conf\\\" -ItemType Directory",
+      "New-Item \"C:\\hashicorp\\nomad\\bin\\\" -ItemType Directory",
+      "New-Item \"C:\\hashicorp\\nomad\\data\\\" -ItemType Directory",
+      "New-Item \"C:\\hashicorp\\nomad\\conf\\\" -ItemType Directory",
+      "Invoke-WebRequest -Uri ${local.jre_url} -OutFile $env:TEMP\\jre.zip",
+      "Invoke-WebRequest -Uri ${local.consul_url} -OutFile $env:TEMP\\consul.zip",
+      "Invoke-WebRequest -Uri ${local.nomad_url} -OutFile $env:TEMP\\nomad.zip",
+      "Expand-Archive $env:TEMP\\jre.zip -DestinationPath C:\\hashicorp\\jre\\",
+      "Expand-Archive $env:TEMP\\consul.zip -DestinationPath C:\\hashicorp\\consul\\bin\\",
+      "Expand-Archive $env:TEMP\\nomad.zip -DestinationPath C:\\hashicorp\\nomad\\bin\\",
+      "[Environment]::SetEnvironmentVariable(\"Path\", $env:Path + \";C:\\hashicorp\\jre\\jdk-11.0.13+8-jre\\bin;C:\\hashicorp\\nomad\\bin;C:\\hashicorp\\consul\\bin\", \"Machine\")",
+      // "$old = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\Environment' -Name path).path",
+      // "$new = \"$old;C:\\hashicorp\\jre\\jdk-11.0.13+8-jre\\bin;C:\\hashicorp\\nomad\\bin;C:\\hashicorp\\consul\\bin\"",
+      // "Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\Environment' -Name path -Value $new",
+    ]
+  }
+}
+

bootstrap_win.txt

<powershell>
+# Set administrator password
+net user Administrator SuperS3cr3t!!!!
+wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
+
+# First, make sure WinRM can't be connected to
+netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
+
+# Delete any existing WinRM listeners
+winrm delete winrm/config/listener?Address=*+Transport=HTTP  2>$Null
+winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
+
+# Disable group policies which block basic authentication and unencrypted login
+
+Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
+Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
+Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
+Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1
+
+
+# Create a new WinRM listener and configure
+winrm create winrm/config/listener?Address=*+Transport=HTTP
+winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
+winrm set winrm/config '@{MaxTimeoutms="7200000"}'
+winrm set winrm/config/service '@{AllowUnencrypted="true"}'
+winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
+winrm set winrm/config/service/auth '@{Basic="true"}'
+winrm set winrm/config/client/auth '@{Basic="true"}'
+
+# Configure UAC to allow privilege elevation in remote shells
+$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
+$Setting = 'LocalAccountTokenFilterPolicy'
+Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
+
+# Configure and restart the WinRM Service; Enable the required firewall exception
+Stop-Service -Name WinRM
+Set-Service -Name WinRM -StartupType Automatic
+netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
+Start-Service -Name WinRM
+</powershell>
+
+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/index.html b/04-HashiCorp/01-Packer/05-SamplePkr/index.html new file mode 100644 index 0000000000..071c56dbbb --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Sample Pkr | docmoa + + + + + +
본문으로 건너뛰기

05 Sample Pkr

1분 미만

+ + + diff --git a/04-HashiCorp/01-Packer/05-SamplePkr/nCloud.html b/04-HashiCorp/01-Packer/05-SamplePkr/nCloud.html new file mode 100644 index 0000000000..8a5145d2e3 --- /dev/null +++ b/04-HashiCorp/01-Packer/05-SamplePkr/nCloud.html @@ -0,0 +1,97 @@ + + + + + + + + + + Naver Cloud Platform Packer Sample | docmoa + + + + + +
본문으로 건너뛰기

Naver Cloud Platform Packer Sample

1분 미만PackerSampleNCP

Naver Cloud Platform Packer Sample

packer.pkr.hcl

packer {
+  required_plugins {
+    ncloud = {
+      version = ">= 0.0.1"
+      source  = "github.com/hashicorp/ncloud"
+    }
+  }
+}
+
+source "ncloud" "example-linux" {
+  access_key                = var.access_key
+  secret_key                = var.secret_key
+  server_image_product_code = "SPSW0LINUX000139"
+  server_product_code       = "SPSVRGPUSSD00001"
+  server_image_name         = var.image_name
+  server_image_description  = "server image description"
+  region                    = "Korea"
+  communicator              = "ssh"
+  ssh_username              = "root"
+}
+
+build {
+  sources = ["source.ncloud.example-linux"]
+
+  provisioner "file" {
+    source = "jupyter.service"
+    destination = "/etc/systemd/system/jupyter.service"
+  }
+
+  provisioner "shell" {
+    inline = [
+      "yum clean all",
+      "yum -y install epel-release",
+      "yum -y install python3",
+      "yum -y install python-pip",
+      "pip3 install --upgrade pip",
+      "adduser jupyter",
+      "su - jupyter",
+      "pip3 install --user jupyter jupyter",
+      "systemctl enable jupyter",
+      "systemctl start jupyter"
+    ]
+  }
+}
+
+variable "access_key" {
+  type    = string
+}
+
+variable "secret_key" {
+  type    = string
+}
+
+variable "image_name" {
+  type    = string
+  default = "test"
+}
+
+ + + diff --git a/04-HashiCorp/01-Packer/index.html b/04-HashiCorp/01-Packer/index.html new file mode 100644 index 0000000000..66dc31588f --- /dev/null +++ b/04-HashiCorp/01-Packer/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Packer | docmoa + + + + + +
본문으로 건너뛰기

01 Packer

1분 미만

+ + + diff --git a/04-HashiCorp/02-Vagrant/02-Config/index.html b/04-HashiCorp/02-Vagrant/02-Config/index.html new file mode 100644 index 0000000000..2e1e05d600 --- /dev/null +++ b/04-HashiCorp/02-Vagrant/02-Config/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Config | docmoa + + + + + +
본문으로 건너뛰기

02 Config

1분 미만

+ + + diff --git a/04-HashiCorp/02-Vagrant/02-Config/multi-linux-sample.html b/04-HashiCorp/02-Vagrant/02-Config/multi-linux-sample.html new file mode 100644 index 0000000000..3f87f80132 --- /dev/null +++ b/04-HashiCorp/02-Vagrant/02-Config/multi-linux-sample.html @@ -0,0 +1,117 @@ + + + + + + + + + + 다양한 Linux 생성 샘플 | docmoa + + + + + +
본문으로 건너뛰기

다양한 Linux 생성 샘플

1분 미만vagrantvirtualboxlinux

다양한 Linux 생성 샘플

# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# base image : https://app.vagrantup.com/bento
+# Cluster IP have to set subnetting on private network subnet of VM
+
+$debianip = 50
+$centip = 60
+$suseip = 70
+
+debian_cluster = {
+  "ubuntu" => { :image => "bento/ubuntu-18.04"}
+}
+cent_cluster = {
+  "centos" => { :image => "centos/7"},
+  "rocky" => { :image => "rockylinux/8"},
+}
+suse_cluster =  {
+  "suse" => { :image => "opensuse/Tumbleweed.x86_64" }
+}
+
+Vagrant.configure("2") do |config|
+
+  config.vm.synced_folder '.', '/vagrant', disabled: true
+
+  debian_cluster.each_with_index do |(hostname, info), i|
+    config.vm.define hostname do |server|
+      server.vm.box = info[:image]
+      server.vm.hostname = hostname
+      server.vm.network "private_network", name: "vboxnet1", ip: "172.28.128.#{i + $debianip}"
+
+      server.vm.provider "virtualbox" do |v|
+        v.name = hostname
+        v.gui = false
+        v.memory = 1024
+        v.cpus = 1
+
+        v.customize ["modifyvm", :id, "--vram", "9"]
+      end # end provider
+    end # end config
+  end # end cluster foreach
+
+  suse_cluster.each_with_index do |(hostname, info), i|
+    config.vm.define hostname do |server|
+      server.vm.box = info[:image]
+      server.vm.hostname = hostname
+      server.vm.network "private_network", name: "vboxnet1", ip: "172.28.128.#{i + $suseip}"
+      server.vm.provider "virtualbox" do |v|
+        v.name = hostname
+        v.gui = false
+        v.memory = 1024
+        v.cpus = 1
+
+        v.customize ["modifyvm", :id, "--vram", "9"]
+      end # end provider
+    end # end config
+  end # end cluster foreach
+
+  cent_cluster.each_with_index do |(hostname, info), i|
+    config.vm.define hostname do |server|
+      server.vm.box = info[:image]
+      server.vm.hostname = hostname
+      server.vm.network "private_network", name: "vboxnet1", ip: "172.28.128.#{i + $centip}"
+
+      server.vm.provider "virtualbox" do |v|
+        v.name = hostname
+        v.gui = false
+        v.memory = 1024
+        v.cpus = 1
+
+        v.customize ["modifyvm", :id, "--vram", "9"]
+      end # end provider
+    end # end config
+  end # end cluster foreach
+  
+end
+
+
+ + + diff --git a/04-HashiCorp/02-Vagrant/04-TroubleShooting/hostonlynetworkissue.html b/04-HashiCorp/02-Vagrant/04-TroubleShooting/hostonlynetworkissue.html new file mode 100644 index 0000000000..818319e390 --- /dev/null +++ b/04-HashiCorp/02-Vagrant/04-TroubleShooting/hostonlynetworkissue.html @@ -0,0 +1,41 @@ + + + + + + + + + + Network : Code E_ACCESSDENIED (0x80070005) | docmoa + + + + + +
본문으로 건너뛰기

Network : Code E_ACCESSDENIED (0x80070005)

1분 미만vagrantvirtualbox

Network : Code E_ACCESSDENIED (0x80070005)

https://discuss.hashicorp.com/t/vagrant-2-2-18-osx-11-6-cannot-create-private-network/30984/9open in new window
https://discuss.hashicorp.com/t/vagran-can-not-assign-ip-address-to-virtualbox-machine/30930open in new window

환경

테스트 환경은 MacOS이나 HashiCorp Discuss의 글을 확인해보면 Linux에서도 동일하게 발생하는 것으로 보임

  • MacOS 카탈리나에서 빅서로 업그레이드
  • Vagrant 업데이트 : 2.2.18
  • VirtualBox 업데이트 : 6.1.28 r147628

현상

기존에 VirtualBox에 Host Network Manager에서 vboxnet# 사용중

  • IPv4 Address : 172.28.128.1
  • IPv4 Network Mask : 255.255.255.0
  • DHCP
    • Server Address : 172.28.128.2
    • Server Mask : 255.255.255.0
    • Lower Address Bound: 172.28.128.3
    • Upper Address Bound: 172.28.128.254

Vagrant up 시 에러 발생
VM의 Network에 Host-only Network로 해당 vboxnet#이 있어야 하나 목록에 표기 안됨

해결방안

Host Network Manager에서 vboxnet#를 삭제후 다시 172.x.x.x로 생성하려고 하니 에러 발생

Stderr: VBoxManage: error: Code E_ACCESSDENIED (0x80070005) - Access denied (extended info not available)
+

기본으로 네트워크 생성시 부여받는 IP(e.g. 192.168.56.1) 로는 가능하여 Vagrant의 구성을 해당 IP에 맞게 변경

+ + + diff --git a/04-HashiCorp/02-Vagrant/04-TroubleShooting/index.html b/04-HashiCorp/02-Vagrant/04-TroubleShooting/index.html new file mode 100644 index 0000000000..2ddfa366c8 --- /dev/null +++ b/04-HashiCorp/02-Vagrant/04-TroubleShooting/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 04 Trouble Shooting | docmoa + + + + + +
본문으로 건너뛰기

04 Trouble Shooting

1분 미만

+ + + diff --git a/04-HashiCorp/02-Vagrant/index.html b/04-HashiCorp/02-Vagrant/index.html new file mode 100644 index 0000000000..d0ac61f34c --- /dev/null +++ b/04-HashiCorp/02-Vagrant/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Vagrant | docmoa + + + + + +
본문으로 건너뛰기

02 Vagrant

1분 미만

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/00-introduction.html b/04-HashiCorp/03-Terraform/01-Information/00-introduction.html new file mode 100644 index 0000000000..7a734fb683 --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/00-introduction.html @@ -0,0 +1,40 @@ + + + + + + + + + + Terraform 개념 소개 | docmoa + + + + + +
본문으로 건너뛰기

Terraform 개념 소개

1분 미만terraformIaC

Terraform 개념 소개

1. Provision

프로비저닝과 관련하여 우리는 Day 0부터 Day 2까지의 여정이 있습니다.

  • Day 0에 요구사항을 분석하고 아키텍쳐를 설계하고 훈련을 합니다.
  • Day 1에 드디어 설계된 아키텍쳐를 구현하지요. 인프라, 네트워크, 서비스 구성 등등 말이죠.
  • Day 2는 이제 Day 1에서 구성된 요소를 유지하고 관리하고 모니터링하면서 더나은 아키텍쳐로 변경하거나 추가 서비스를 붙이는 반복적 작업을 합니다.

우선은 프로비저닝할 준비가 되었다고 가정하고 Day 1에 드디어 인프라를 구성합니다. (VPC, Securty Group, VM, LB 등등) 그리고 이렇게 뭔가를 실행하면 실제 Day 2에서는 기존 인프라 집합에 추가로 비즈니스 요구사항에 따라 새로운 서비스를 추가하면서 그 시간이 지남에 따라 기존 인프라가 점점 변화합니다. 서비스를 제거하고 일반적으로 인프라의 모습을 발전시키는 이같은 활동은 테라폼에서 근본적으로 코드 접근 방식으로서의 인프라 즉 Infrastructure as Code 로 접근합니다.직역하면 코드가 인프라이고 인프라가 코드인 상태이죠.

일련의 선언적으로 구성파일을 정의하고 읽을수 있는 이런 구성파일로 토폴로지를 구성합니다. 테라폼 configopen in new window라고 설명해놓겠습니다. 보안 그룹 규칙을 프로비저닝하거나 네트워크 보안을 설정한 다음 해당 환경 내에서 가상 머신 세트 정의를 프로비저닝하려는 VM이 있고 로드밸런서가 있고...

복잡성에 따라 매주 또는 매일 인프라가 점차 변화하고 발전합니다. 테라폼 구성으로 이런 환경을 캡쳐해두는 것은 매우 쉽고 바른 방식입니다.

img
img

그럼 테라폼 설정이 어떻게 동작하는지 알아보겠습니다.

Refresh

우선 첫번째로, 테라폼은 Refresh를 통해 테라폼으로 만들어질 세상이 어떻게 생겼는지 조정합니다. 이를 통해 테라폼 View가 나오고 실제와 어떻게 다른지 비교합니다. VMware나 AWS, Azure, GCP 같은 인프라에 실제 무엇이 실행 중인지 API로 물어보고 각 상태를 확인할 수 있습니다.

Plan

플랜은 현재의 상태를 원하는 상태로 구성하는 단계 입니다. 실제 예상되는 무언가를 알려주고 우리는 미리 확인할 수 있습니다. 앞서 정의한 TF Config의 현재와 다른것이 무엇이있고 어떤 변화가 있는지 확인하고 앞으로의 변경점을 예측해주죠.

Apply

Apply는 원하는 상태를 만들기 위해 실행을 하는 단계 입니다. 필요한 것이 무엇인지 어떤것이 정의되었는지를 말이지요. 예를 들면 VM을 생성하기 전에 보안그룹을 정의하는 것 같은 순차적인 것이 무엇인지 병렬로 진행하는 것이 무엇인지를 이미 알고 있습니다. 그래프 이론open in new window에 기반한 이런 프로비저닝 방식은 사용자가 각 자원의 선후 관계를 명시하지 않아도 순차로 진행할 것과 병렬로 진행할 작업을 구분합니다.

이렇게 구성하여 우리가 원하는 인프라를 정의하고 생성하고 적용합니다.

Day 2에 들어서서 기존에 없던 로드발란서와 연결되는 DNS구성이나 CDN, 또는 내 VM을 모니터링하고 싶은 시스템에 연결하는 작업이 필요할 수 있습니다.기존에 가지고 있는 구성을 업데이트하고 다시 실행하여 처음의 리소스에 추가로 새로운 것들을 추가합니다. Day 2에 중요한 것은 계속 변화하는 환경에서 변화될 것들만 추적하고 변경할 수 있다는 것입니다. 그리고 더이상 필요하지 않을 때 원래의 상태로 돌아올 수 있습니다.

Destory

코드로 정의된 각 인프라 리소스는 Destroy를 통해 다시 제로의 상태로 돌아옵니다. 이같은 방식은 언제나 일관된 상태와 리소스 정의를 만들어줍니다.

2. Providers

tf-how-it-works_core-extensible.png
tf-how-it-works_core-extensible.png

프로바이더는 테라폼 코어와 연동되는 플러그인으로, 각 플랫폼에서 제공하거나 누구나 개발해서 테라폼과 연결할 수 있습니다. 이런 플러그인들을 프로바이더라고 부릅니다.

  • AWS/Azure/GCP 같은 퍼블릭 IaaS 클라우드 환경 프로바이더
  • 오픈스텍, VMware 같은 프라이빗 IaaS나 VM환경 프로바이더
  • Heroku, 쿠버네티스, 람다같은 PaaS 프로바이더
  • Datadog, Fastly, Github 같은 SaaS 프로바이더

프로바이더는 필요에 따라 인프라, 플랫폼, 서비스를 연계해서 사용하게도 가능합니다. 예를들어 앞서 인프라를 수행했다고 보고, Day2 에 쿠버네티스 연결이 필요하면 기존 설정을 확장해서 추가 리소스와 자원을 구성합니다. 그리고 그 위에 서비스가 DNS를 요구하거나 CDN을 요구하면 이런 서비스를 추가로 애드온 하게 됩니다.

테라폼 문서open in new window에보면 이미 100여개 이상의 프로바이더가 존재하고 커뮤니티의 프로바이더 까지 합치면 테라폼으로 관리가능한 코드기반 자원들은 무궁무진합니다.

3. Workflow

tf-features_consistent-workflow.png
tf-features_consistent-workflow.png

테라폼을 로컬에서 사용하는 사용자의 워크 플로우는 테라폼 구성을 실행하고나서 plan이 만들어집니다. 구성에 대한 State 관리나 변수, 각각의 설정과 관련한 코드를 로컬에서 관리됩니다.

이제 다른 팀원을 추가합니다. 우리는 인프라 작업이 일관되게 변화하는 것을 기대합니다. 새로운 VM 이 생기거나 새로운 리소스를 생기지 않도록 하려면 어떻게 해야할까요? 이런 문제는 우리가 코드 관리를 위해 코드 버전 관리 서비스를 사용하는 현상과 유사합니다. Git기반의 테라폼은 코드를 중앙에서 관리하고 이를 테라폼 엔터프라이즈에서 관리하고 워크플로우의 헛점이 발생하지 않도록 도와줍니다.

테라폼 엔터프라이즈는 VCS와 연동open in new window하여 개인 로컬 환경이 아닌 중앙에서 프로비저닝을 하도록 관리하는 역할을 합니다. 이제 로컬에서 실행하는 대신 제어시스템, github나 빗버킷이나 깃랩같은 VCS를 활용하여 중앙에서 상태관리를 합니다.

기업의 운영 환경에서 요구되는 건 또 무엇이 있을까요? 아마도 정책open in new window이 필요할 것입니다. 예를 들면 태깅을 해야한다거나 특정 리전에만 프로비저닝을 해야하는 조건을 달 수 있습니다.

또한가지, 인프라를 구성하는 작업자는 혼자서 운영할 때는 필요한 변수들을 로컬에 저장하고 활용합니다. 이런 변수에는 키같은 민감한 정보토 포함될 수 있습니다. 엔터프라이즈에서는 변수를 중앙에서 관리하고 필요한 경우 암호화 해주는 기능이 필요합니다.

Google Shape;5711;p420
Google Shape;5711;p420

이런 엔터프라이즈 기능이 워크 플로우를 관리하고 작업자가 안전하게 협업할 수 있게 도와줍니다.이렇게 반복적으로 작업이 되다 보면 일련을 동작을 모듈화하여 관리할 수 있습니다. 일종의 입출력 예제와 같이 모듈을 쉽게 정의 할 수 있습니다. 예를 들어 Java애플리케이션을 배포하고자 하는 모듈에는 배포할 Jar파일이 무엇인지, 몇개나 띄울지 물어봅니다. 이런 모듈을 기업내에서 관리하는 프라이빗 레지스트리도 기업환경에서는 요구되곤 합니다.

테라폼만으로 좋은데 엔터프라이는 왜 사용하는가에 대한 대답은 이런 협업과 정책, 관리되는 static한 변수들과 해당 조직이나 기업을 위한 모듈을 관리할 수 있도록 만들어준다는 점입니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.html b/04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.html new file mode 100644 index 0000000000..22831748fa --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.html @@ -0,0 +1,40 @@ + + + + + + + + + + 인프라의 변화와 적응 | docmoa + + + + + +
본문으로 건너뛰기

인프라의 변화와 적응

1분 미만terraformIaC

인프라의 변화와 적응

이번에는 인프라의 변화와 적응이라는 제목으로 인프라의 성숙도와 관련한 이야기를 나누고자 합니다.

image-20200707110714298
image-20200707110714298

HashiCorp의 테라폼을 이야기하면서 함께 이야기되는 것은 언제나 Infrastructure as Code라는 IaC 입니다. 즉, 인프라는 코드로 설명되고로 테라폼은 이를 지원하는 멋진 툴입니다.

IaC는 많은 의미를 갖고 있지만, 이 이야기를 하기에 앞서 결국 이것도 코드 이기 때문에 최근의 DevOps 같은 맥락의 이야기를 할수 밖에 없을 것 같습니다.

사람이 과거에서 오늘날 까지 진화를 했든 우리의 인프라도 각 시기마다 적절한 운영 성숙도를 갖추었습니다.

자동화와 그 변화에 대해

1. Manual Everything

image-20200701184003515
image-20200701184003515

첫단계에서는 매뉴얼을 사용합니다. 인프라에 대한 모든 정보와 구성 방법, 변경 방법, 기존 아키텍쳐에 대한 내용은 문서로 관리되었습니다. 여전히 엑셀 시트를 가지고 인프라 아이피와 아이디, 서비스가 어떤게 올라가 있는지 관리하는 곳도 많이 있지요. 변경에 대한 모든 사항은 문서로 남겨야 하고, 그렇지 못한다면 기억에 의존해야 합니다.

  • 브레인 스토밍
  • 아이디어 전달
  • 컴퓨터가 이해할 수 없음
  • 작업 계획을 기반으로 CLI 사용
  • 낮은 재사용 성과 버전 관리
  • 현재 상태를 유지할 수 없음

2. Basic Automation

File:Script Shell.png - Wikimedia Commons
File:Script Shell.png - Wikimedia Commons

나름의 노하우를 담아 스크립팅 언어를 사용하여 작업합니다. 새로운 서버를 설정하거나 기능을 수행하기위한 작업으로 상당히 간단하고 편리합니다. 이단계에서는 비슷한 인프라나 애플리케이션 런타임이 일반적이고 조직간에 많은 의사 소통이 필요하지 않기 때문에 상당히 유용합니다. 일단 노가다를 많이 줄여줍니다. 특징은 다음과 같습니다.

  • 인적 오류 감소
  • 반복 된 작업 자동화
  • 시스템의 결과 또는 상태를 고려하여 실행
  • 순서대로 각 단계를 실행
  • 낮은 재사용 성과 버전 관리
  • 현재 상태를 유지 불가
  • 동일한 스크립트를 실행하고 분기를 통해 두 가지 혹은 그 이상의 결과
  • 스크립트가 최종 상태를 이해하지 못함

3. Machine Virtualization

image-20200706221237156
image-20200706221237156

이제 이미 설정된 머신을 가상화를 통해 미리 저장해두고 사용합니다. 미리 필요한 패키지나 솔루션을 설치해두고 바로 사용 가능합니다. 미리 이미지들을 만드는 작업을 수행하기 때문에 가상머신을 사용하면 자동화를 향상시킬 수 있고 공동작업도 향상됩니다.

  • 성숙된 툴링
  • 재사용성 확보
  • 스냅샷을 통한 수동적 상태 유지
  • 가상화 되지 않은 부분은?
  • 하이퍼 바이저에 의존적
  • 서로 다른 API
  • 이미지에 변경시 수작업 또는 스크립트 필요

4. Commoditization of Infrastructure

img
img

다음은 이제 우리가 기본적으로 컨트롤 가능한 클라우드 리소스와 함께하는 시대입니다. 인프라를 더이상 소유하지 않고 상품화된 인프라, 데이터 센터를 사용하기 위해서 우리는 자동화를 요구받기 시작합니다. API를 통해 더 많은 기술 계층을 가상화 하고 더 많은 소프트웨어와 자동화 기술로 지금의 데이터 센터의 컴퓨터를 포함한 인프라를 대신하는 환경을 제공합니다.

  • 빠른 시작
  • 확장성
  • 거의 모든 자동화 API 제공
  • 제공자마다 상이한 API
  • 애플리케이션 파편화
  • 인적 자원 확보 필요

5. Datacenter as Computer

image-20200706222116243
image-20200706222116243

이제 더 나아가 머신이 아닌 OS를 가상화 하기 시작합니다. 데이터 센터의 컴퓨터를 사용하는 대신 컴퓨터를 자원 풀의 하나로 여기고, 어디인지는 몰라도 애플리케이션을 적당한 리소스가 있는 그 어딘가에 배포합니다. 리소스 활용율을 높이고 이전 보다 더더더 자동화합니다. Agile이 요구되고 DevOps를 해야한다고 합니다.

  • 엄청 빠른 배포 전략
  • 가용한 모든 리소스에 확장
  • 자동화를 위한 자동화
  • 자동화를 위한 인력 필요
  • 기존 레거시의 보존

자동화 딜레마

image-20200707180213752
image-20200707180213752

더 많은 자동화를 하면 이론적으로 우리는 더 적은 일을 해야합니다. 하지만 전반적으로 자동화의 단계를 살펴보면 이론적으로 가상화를 늘리고 컨테이너화를 하는 것은 우리의 삶을 더 좋게 만들어야 하는데, 정말 그런가요? 때때로 자동화는 정말 무수히 많은 반복과 검증 작업이 필요하고 이전보다 더 많은 시간과 노력을 요구합니다. 자동화나 DevOps에대한 장난스럽지만 마음을 후비는 글들도 넘쳐나죠. 자동화를 했음에도 불구하고 다시 새로운 플랜을 실행해야 하는 상황이나 여전히 우리가 인프라를 대하는 마음가짐이 하드웨어로 바라보고 있는 시각 처럼 말입니다.

image-20200707085213583
image-20200707085213583

아마도 유발하라리의 소설 사피엔스를 재미있게 보신 분들은 호모사피엔스 뿐만아니라 다른 종도 함께 살았었다는 흥미로운 가정을 하는 이야기를 보셨을 것입니다. (다른종을 멸종시켰다고..) 그리고 소설에서의 이야기 처럼 앞서 다룬 자동화외에도 수십, 혹은 수백가지의 자동화를 위한 내 한몸 편하고자 하는 몸부림이 현 시점에도 병렬로 존재합니다. 퍼블릭과 프라이빗 클라우드 환경이 도입되고 있고 VM과 베어메탈 환경은 여전히 존재합니다. 더불어 더작은 엣지, IoT 인프라도 관리의 대상이 되고 있습니다.

Infrastructure as Code

인프라의 자동화를 이야기 함에 있어 빼뜨릴 수 없는것이 Infrastructure as Code, IoC 입니다. 코드로서의 인프라스트럭쳐, IaC가 취하는 전략이 무엇일까요? 다년간의 경험을 가진 팀이 보유한 시스템 환경을 코드로 바꾸면 무엇이 달라질까요?

그럼 몇가지 상황을 제시해보겠습니다.

  • 수동으로 생성 한 환경을 관리해 보셨나요?
  • 변경을 해야하는데 엔터를 누르기가 겁이 났었나요?
  • 백업이 손상되고 서버가 충돌하는 상황이 우려스럽나요?
  • 같은 환경을 재현할수 없는 상황이 있었나요?

코드로 인프라를 관리한다는 것은 자유롭게 변경하고 환경을 이해하고 반복적으로 동일한 상황으로 만들 수 있다는 점입니다. 그리고 그 명세를 별도의 문서로 정리할 필요 없이 명확하게 인프라가 정의되어 남아있습니다.

image-20200707091154907
image-20200707091154907

좋은 코드

우선 잘 만들어지는, 좋은 인프라 자동화를 이야기 하기 앞서 그 조건을 만드는 좋은 코드의 특징을 찾아보고 몇가지 공통된 항목을 뽑아보고 다음과 같이 정리해보았습니다.

  • 잘 작동함
  • 읽기 쉬움
  • 모듈화됨
  • 테스트 가능함
  • 보기 좋음 (우아함)
  • 관리가 쉬움
  • 변경이 쉬움
  • 간결함 (명확함)
  • 효율적임

이외에도 몇가지 더 있겠지만 이런 좋은 코드의 특징과 IaC가 어떤 관계가 있을까요? 인프라도 마치 좋은 코드처럼 관리가 가능하다는 것입니다. 앞서 자동화를 위해 문서화 했어야 했고, 종속성을 분석하여 관리하고 인프라 자원의 변경이 있을때마다 변경하고 그 결과물, 혹은 결과물을 만들어내는 도구를 관리하고 다시 사용할 수 있게 만드는 것. 그것은 좋은 코드와 좋은 인프라 자동화 방식이 같은 맥락으로 이어질 수 있도록 만들어주는 IaC의 특징입니다. 하지만 '좋은' 이라는 수식이 붙는 인프라의 자동화가 그저 쉽게만 되지는 않을 수 있습니다. 물론 인프라를 위한 좋은 코드는 연습이 필요합니다.

IaC starting with Terraform

인프라 프로비저닝의 최우선 목표는 재현 가능한 인프라를 코드로 제공하는 것입니다. DevOps팀이 CI/CD 워크플로우 내에서 익숙한 도구를 사용하여 리소스를 계획하고 프로비저닝을 할 수 있는 방법을 제공하는 것입니다. Terraform은 DevOps 팀이나 클라우드 팀에서 구성한 아키텍쳐를 코드로 템플릿화 하고 기본적인 리소스와 세분화된 프로비저닝을 처리할 수 있습니다. 이런 구성은 주요 인프라 관리 도구와 통합되고 모니터링, APM 시스템, 보안 도구, 네트워크 등을 포함하여 다른 많은 ISV 공급자의 서비스로 확장 할 수 있습니다. 정의된 템플릿은 자동화된 방식으로 필요에 따라 프로비저닝 하고, 이를 통해 Terraform은 퍼블릭과 프라이빗 클라우드에 리소스를 프로비저닝 하는 공통 워크플로우를 생성합니다.

Terraform Enterprise Workflow

그리고 엔터프라이즈 환경을 위한 지원은 무엇이 있을까요? Terraform Enterprise는 오픈 소스의 코드 프로비저닝으로 인프라 스트럭처에서 협업, 거버넌스 및 셀프 서비스 워크 플로우를 제공합니다. Terraform Enterprise는 팀이 협력하여 인프라를 구축 할 수 있도록 워크스페이스, 모듈과 모듈 레지스트리, 거버넌스를 위한 구성을 제공합니다.

모듈의 활용은 기존에 티켓 방식으로 수행하던 인프라 프로비저닝 워크플로우에서 인프라를 재사용 가능한 모듈에 코드로 패키지하여 개발자가 셀프 서비스 방식으로 신속하게 프로비저닝 할 수 있는 환경을 만들어 줍니다.

그리고 프로비저닝과 관련한 정책 및 코드 로깅을 통해 조직은 전체 배포를 보호, 관리 및 감사로그를 확인 할 수 있습니다.

Conclusion

끊임없이 성장하고 확장되는 인프라 환경에서 Infrastructure as Code라는 아이디어가 탄생했습니다. IaC에 기반한 가치는 관리되는 프로비저닝으로 기존 레거시 환경과 퍼블릭/프라이빗 클라우드에 대한 비용을 최적화하고 기존 대비 빠른 속도와 위험 감소라는 측면에서 지금의 우리가 맞이하고 있는 현 시대에 적합한 모델이라고 볼 수 있습니다. DevOps의 핵심 구성요소기기도 하나 좋은 코드가 좋은 인프라를 만드는 것처럼 품질관릴와 체계적인 거버넌스를 구성하기 위한 노력이 반드시 필요한 영역입니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/02-hcl.html b/04-HashiCorp/03-Terraform/01-Information/02-hcl.html new file mode 100644 index 0000000000..3050bed2f2 --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/02-hcl.html @@ -0,0 +1,73 @@ + + + + + + + + + + HCL - HashiCorp Configuration Language | docmoa + + + + + +
본문으로 건너뛰기

HCL - HashiCorp Configuration Language

1분 미만terraformusecaseIaCHCL

HCL - HashiCorp Configuration Language

Terraform의 가장 주요한 기능으로 Infrastructure as Code 를 이야기 할 수 있습니다. 그리고 이를 지원하는 HCL에 대해 알아보고자 합니다.

Terraform에서는 당연히 동작해야하는 필연적 기능이기 때문에 오픈소스를 포함하여 모든 유형의 Terraform에서 제공되는 기능입니다.


유형지원여부
Terraform OSS (Open Source Software)✔︎
Terraform Cloud✔︎
Terraform Cloud for Business✔︎
Terraform Enterprise✔︎

Infrastructure as Code에 대해 간단히 소개하자면 수작업으로 프로비저닝 하던 방식, 예를 들면 UI 클릭이나 개별적인 스크립트를 사용하여 프로비저닝하는 방식은 자동화하기 어렵고, 충분히 숙달되지 않거나 밤샘작업과 스트레스로 잠시 집중력이 떨어지면 실수가 발생할 수 있습니다. 그리고 스크립트 방식은 나름 잘 정의되어있지만 순차적으로 수행되고 중간에 오류가 나면 다시 돌이키기 힘든 방식이였습니다.

Terraform에서는 이전의 프로비저닝 방식을 개선하여 좀더 안정적인고 체계적인 관리 방식을 도입할 수 있는 메커니즘과 Infrastructure as Code의 핵심인 Code를 잘 만들고 관리할 수 있는 도구를 제공합니다.

기본적으로 Terraform은 HCL이라고하는 HashiCorp Configuration Language와 JSON가 코드의 영역을 담당하고 있습니다. 특히 HCL은 쉽게 읽을수 있고 빠르게 배울 수 있는 언어의 특성을 가지고 있습니다.

인프라가 코드로 표현되고, 이 코드는 곧 인프라이기 때문에 선언적 특성을 갖게 되고 튜링 완전한 언어적 특성을 갖습니다. 즉, 일반적인 프로그래밍 언어의 일반적인 조건문 처리같은 동작이 가능하다는 것입니다.

이렇게 코드화된 인프라는 주 목적인 자동화와 더불어 쉽게 버저닝하여 히스토리를 관리하고 함께 작업할 수 있는 기반을 제공하게 됩니다.

Why HCL?

사실 이미 테라폼을 조금이라도 써보신분들은 당연하게도 HCL을 써보셨을 수도 있지만 처음 접하시는 경우 뭔가 또 배워야 하는건가? 어려운건가? 라는 마음의 허들이 생길 수 있습니다.

Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-27-59
Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-27-59

앞서 설명드렸듯 HCL은 JSON과 호환되고 이런 방식이 더 자연스러우신 분들은 JSON으로 관리가 가능합니다. 하지만 HCL에 대한 일반적인 질문은 왜 JSON이나 YAML같은 방식이 아닌지 입니다.

HCL 이전에 HashiCorp에서 사용한 도구는 Ruby같은 여타 프로그래밍 언어를 사용하여 JSON같은 구조를 만들어내기 위해 다양한 언어를 사용했습니다. 이때 알게된 점은 어떤 사용자는 인간 친화적인 언어를 원했고 어떤 사람들은 기계 친화적 언어를 원한다는 것입니다.

JSON은 이같은 요건 양쪽에 잘 맞지만 상당히 구문이 길어지고 주석이 지원되지 않는 다는 단점이 있습니다. YAML을 사용하면 처음 접하시는 분들은 실제 구조를 만들어내고 익숙해지는데 어려움이 발생하였습니다. 더군다나 일반적 프로그래밍 언어는 허용하지 않아야하는 많은 기능을 내장하고 있는 문제점도 있었습니다.

이런 여러 이유들로 JSON과 호환되는 자체 구성언어를 만들게 되었고 HCL은 사람이 쉽게 작성하고 수정할 수 있도록 설계되었습니다. 덩달아 HCL용 API가 JSON을 함께 호환하기 때문에 기계 친화적이기도 합니다.

Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-28-30
Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-28-30

HCL을 익히고 사용하는 건 어떨까요?

예를 들어 Python 코드로 비슷하게 정의를 내려보았습니다. 우리가 사용할 패키지를 Import하고 해당 패키지가 기본적으로 필요로하는 값을 넣어 초기화 합니다. 여기서는 aws라는 패키지에 regionprofile 이름을 넣어서 기본적으로 동작 할 수 있는 설정으로 초기화 하였습니다. 이후에 해당 패키지가 동작할 수 있는 여러 서브 펑션들에 대한 정의를 하고 마지막으로는 실행을 위한 큐에 넣습니다.

HCL도 거의 이런 일반적 프로그래밍의 논리와 비슷합니다. 우선 사용할 프로바이더 라고하는 마치 라이브러리나 패키지 같은 것을 정의 합니다. 이 프로바이더에는 기본적으로 선언해주어야 하는 값들이 있습니다.

그리고 이 프로바이더가 제공하는 기본적인 모듈들, 즉, 클래스나 구조체와 비슷한 형태로 정의 합니다. resource에 대한 정의는 마치 aws_instance라는 클래스를 example로 정의하는 것과 비슷한 메커니즘을 갖습니다. 그리고 해당 리소스의 값들을 사용자가 재정의 하는 방식입니다.

HCL Syntax

실제 HCL의 몇가지 예는 다음과 같습니다. (githubopen in new window)

// 한줄 주석 방법1
+# 한줄 주석 방법2
+
+/*
+다중
+라인
+주석
+*/
+
+locals {
+  key1 =      "value1" // = 를 기준으로 키와 값이 구분되며
+  key2     = "value2"  // = 양쪽의 공백은 중하지 않습니다.
+  myStr = "TF ♡ UTF-8" // UTF-8 문자를 지원합니다.
+  multiStr = <<FOO     // <<EOF 같은 여러줄의 스트링을 지원합니다.
+  Multi
+  Line
+  String
+  with <<ANYTEXT
+  FOO                  // 앞과 끝 문자만 같으면 됩니다.
+  
+  boolean1 = true      // boolean true
+  boolean2 = false     // boolean false를 지원합니다.
+
+  deciaml = 123        // 기본적으로 숫자는 10진수,
+  octal = 0123         // 0으로 시작하는 숫자는 8진수,
+  hexadecimal = "0xD5" // 0x 값을 포함하는 스트링은 16진수,
+  scientific = 1e10    // 과학표기 법도 지원합니다.
+
+  //funtion 들이 많이 준비되어있습니다.
+  myprojectname = format("%s is myproject name", var.project)
+  //인라인 조건문도 지원합니다.
+  credentials = var.credentials == "" ? file(var.credentials_file) : var.credentials
+}
+

Funtion Docopen in new window


Terraform의 가장 기본적인 Infrastructure as Code에 대한 소개와 이를 구현하는 HCL에 대해 알아보았습니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/index.html b/04-HashiCorp/03-Terraform/01-Information/index.html new file mode 100644 index 0000000000..c1eb01cb25 --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Information | docmoa + + + + + +
본문으로 건너뛰기

01 Information

1분 미만

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/remoteruns.html b/04-HashiCorp/03-Terraform/01-Information/remoteruns.html new file mode 100644 index 0000000000..bdceb97a28 --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/remoteruns.html @@ -0,0 +1,88 @@ + + + + + + + + + + Remote Runs | docmoa + + + + + +
본문으로 건너뛰기

Remote Runs

1분 미만terraformIaC

Remote Runs

Terraform의 Remote Runs이라는 기능에 대해 확인합니다.

Terraform Cloud와 Terraform Enterprise는 원격으로 트리거링 되어 동작하는 메커니즘을 제공하고 있습니다.

Enterprise에서는 수행의 주체가 중앙 서버이며, 등록된 VCS나 Terraform 설정 파일의 구성을 통해 원격으로 트리거링할 수 있습니다.


유형지원여부
Terraform OSS (Open Source Software)
Terraform Cloud✔︎
Terraform Cloud for Business✔︎
Terraform Enterprise✔︎

원격으로 실행시키는 메커니즘은 총 3가지 형태가 있습니다.

  • VCS에서 Pull 요청시
  • CI/CD 과정에서 API를 통한 호출
  • CLI로 수행

VCS Pull Request

워크스페이스는 VCS와 연동하는 것이 일반적이며, 이 경우 VCS에 Pull이 발생하면 이를 감지하여 해당 워크스페이스의 Run이 수행되는 케이스 입니다.

Terraform Cloud 2020-07-15 23-01-23
Terraform Cloud 2020-07-15 23-01-23

VCS에 Pull이 발생하는 것은 최종적으로 검증된 코드가 올라왔다고 판단되어 동작하며, 특정 파일이나 경로에 관련 동작이 발생했을 경우에만 Run이 수행되도록 설정 가능합니다.

API call

별도의 CI/CD 파이프라인과 연계하여 사용하는 경우에는 API를 호출하여 실행시키는 방식을 제공합니다. 인프라의 변경 뿐만 아니라 애플리케이션과 상호 작용하는 경우에 유용하며, 인가된 사용자를 구분하기 위해 Token을 필요로 합니다. 주의해야 할 점은 문서상에 나와있는 것 처럼 Organization Token이 아닌 User나 Team의 Token으로 요청해야 합니다. 문서 링크open in new window

처음 워크스페이스에 Run을 요청하는 JSON 형태의 data의 예는 다음과 같습니다.

{
+  "data": {
+    "attributes": {
+      "is-destroy": false,
+      "message": "Remote Run - API"
+    },
+    "type": "runs",
+    "relationships": {
+      "workspace": {
+        "data": {
+          "type": "workspaces",
+          "id": "ws-53JSjeBcXFTCVQis"
+        }
+      }
+    }
+  }
+}
+

작성된 json파일을 POST 로 요청하는 형태는 다음과 같습니다.

curl \
+  --header "Authorization: Bearer $TF_CLOUD_TOKEN" \
+  --header "Content-Type: application/vnd.api+json" \
+  --request POST \
+  --data @run.json \
+  https://app.terraform.io/api/v2/runs
+

Run을 요청한 응답 데이터에는 관련 ìd와 유형, 기타 정보들이 담겨있습니다.

{
+  "data": {
+    "id": "run-CZcmD7eagjhyX0vN",
+    "type": "runs",
+    "attributes": {
+      "auto-apply": false,
+      "error-text": null,
+      ...
+

Terraform 웹 콘솔에 접속하여 확인해보면 해당 ìd값을 갖는 Run이 수행됨을 확인 할 수 있습니다.

이후 apply를 위해 comment가 담긴 데이터를 생성하고, 앞서 응답받은 Run의 id 경로로 요청을 보냅니다.

{
+  "comment":"Looks good to me"
+}
+
curl \
+  --header "Authorization: Bearer $TF_CLOUD_TOKEN" \
+  --header "Content-Type: application/vnd.api+json" \
+  --request POST \
+  --data @apply.json \
+  https://app.terraform.io/api/v2/runs/run-CZcmD7eagjhyX0vN/actions/apply
+

API 호출을 사용하면 다양한 시스템을 활용하여 Terraform을 수행할 수 있습니다.

CLI 수행

Terraform을 OSS로 사용하시는 분들은 커맨드 항목 중에 login, logout이 있는 것을 보셨을 수 있습니다. 해당 커맨드는 Terraform Cloud나 Terraform Enterprise를 활용하여 프로비저닝을 수행할 수 있는 커맨드 입니다. 원격으로 실행시키기 위해서는 우선 대상에 대한 정의가 필요합니다.

terraform {
+  backend "remote" {
+    organization = "great-stone"
+    workspaces {
+      name = "random-pet-demo"
+    }
+  }
+}
+

tf 파일에 backend에 대한 설정을 추가하면 테라폼 실행시 원격의 서버와 통신하여 작업을 수행합니다. 워크스페이스의 경우 별도 VCS를 연동하지 않고 로컬과 연계하여 동작하게 됩니다. 또한 Apply 시 yes에 대한 동작도 웹콘솔에서 수행던 로컬에서 수행하던 서로 동기화 되어 실행 됩니다.

마치며

VCS, API, CLI 모두 Terraform의 Run을 수행하는 동일한 동작을 수행합니다. 각 팀, 조직에서의 프로비저닝을 위한 관리 방식에 따라 가장 알맞은 방식으로 접근할 수 있는 메커니즘을 잘 활용하면 효율적인 자원 관리가 가능합니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/remotestate.html b/04-HashiCorp/03-Terraform/01-Information/remotestate.html new file mode 100644 index 0000000000..2b85fe630c --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/remotestate.html @@ -0,0 +1,78 @@ + + + + + + + + + + Remote State | docmoa + + + + + +
본문으로 건너뛰기

Remote State

1분 미만terraformIaC

Remote State

Terraform을 수행하고나면 실행되고난 뒤의 상태가 저장됩니다. 로컬에서 OSS로 실행 했을 때의 terraform.tfstate 파일이 그것 입니다. 서로 다른 팀이 각자의 워크스페이스에서 작업하고 난뒤 각 상태 공유하면 변경된 내역에 따라 다음 작업을 이어갈 수 있습니다. Terraform은 Terraform Cloud, HashiCorp Consul, Amazon S3, Alibaba Cloud OSS 등에 상태 저장을 지원합니다.

Remote State, 즉 원격으로 워크스페이스의 상태 정보를 읽을 수 있다는 의미는 각 팀이 갖는 워크스페이스의 결과를 다른 팀에 노출시켜 새로 프로비저닝 된 정보를 바탕으로 다른 작업을 수행할 수 있도록 합니다. 해당 기능은 오픈소스 환경에서는 지원되지 않습니다.


유형지원여부
Terraform OSS (Open Source Software)
Terraform Cloud✔︎
Terraform Cloud for Business✔︎
Terraform Enterprise✔︎

워크프페이스의 상태를 공유하는 워크플로우의 예를 들면 다음과 같습니다.

  • 네트워크 워크스페이스 : AWS에 배포되는 VPC, 서브넷, NAT 등의 네트워크 작업 워크스페이스를 실행
  • VM 배포 워크스페이스 : 네트워크 작업이 완료된 상태 값을 기반으로 EC2 인스턴스를 프로비저닝

읽어야할 상태를 생성하는 도중에 이를 참조하는 다른 워크스페이스가 실행된다면? 이 경우 참조할 대상의 State는 잠긴 상태가 되기 때문에 해당 작업이 완료될 때까지 이를 바라보는 워크스페이스는 대기하게 됩니다.

워크스페이스가 인프라별, 혹은 프로비저닝 대상으로 인해 세분화 되는 경우에도 각 상태의 변화를 다른 워크스페이스에서 원격으로 불러옴으로서 종속적인 변경사항을 적용한 포스트 프로비저닝 프로세스가 가능하도록 하는 기능입니다.

terraform_remote_state datasource

공식 가이드에 따르면 Remote State는 terraform_remote_state 데이터소스를 통해 상태 값을 가져오게 됩니다. Remote State 가이드 보기open in new window

설정에 대한 예시와 항목에 대한 설명은 다음과 같습니다.

data "terraform_remote_state" "vpc" {
+  backend = "remote"
+
+  config = {
+    organization = "hashicorp"
+    workspaces = {
+      name = "vpc-prod"
+    }
+  }
+}
+
+# Terraform >= 0.12
+resource "aws_instance" "foo" {
+  # ...
+  subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id
+}
+
+# Terraform <= 0.11
+resource "aws_instance" "foo" {
+  # ...
+  subnet_id = "${data.terraform_remote_state.vpc.subnet_id}"
+}
+
  • data의 항목은 terraform_remote_state 로 정의합니다. 뒤에 id 값을 임의로 넣어줍니다.

    • backend : 백엔드를 리모트로 사용할지의 여부입니다. (필수)
    • config : backend로 정의할 값을 선언합니다. (옵션)
      https://www.terraform.io/docs/backends/types/remote.htmlopen in new window
      • organization : Remote State 대상 워크스페이스가 존재하는 org.
      • workspaces : Remote State 대상 워크스프에스 정의
        • name : 워크스페이스 이름
  • 값 읽어오기는 예제에서처럼 0.12버전 이상과 0.11버전 이하로 나뉘어 호출가능하며 0.11버전 이하의 경우 output의 데이터만 활용 가능합니다. 0.12버전 이상으로 다음의 상태값을 갖는 데이터를 예로 설명해보겠습니다.

    {
    +  "backend" = "remote"
    +  "config" = {
    +    "organization" = "great-stone"
    +    "workspaces" = {
    +      "name" = "terraform-examples-sensitive"
    +    }
    +  }
    +  "outputs" = {
    +    "random_server_id" = "definite-mudfish"
    +    "sense" = "123456"
    +  }
    +  "workspace" = "default"
    +}
    +

    상태 데이터는 Json 형태로 출력되며 마치 javascript에서 데이터를 불러오듯 활용하면 됩니다.

    예를들어 config의 워크스페이스 이름을 불러오고자 한다면

    data.terraform_remote_state.<id>.config.workspaces.name
    +

    outputsrandom_server_id 값을 가져오고자 하면

    data.terraform_remote_state.<id>.outputs.random_server_id
    +

    위와 같이 데이터 구조를 확인하여 가져올 수 있습니다.

마치며

Remote State는 타 워크스페이스에서 동작한 상태 값을 기반으로 관련 데이터에 종속성이 있는 작업을 수행하기에 필요한 데이터를 제공받을 수 있는 기능입니다. 워크플로우를 정의할 때 각 팀간, 혹은 각 프로비저닝을 담당하는 주체가 서로 약속한 데이터를 주고 받을 수 있도록 코드로 정의할 수 있는 IaC의 협업 기능으로 활용 가능합니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/sentinel.html b/04-HashiCorp/03-Terraform/01-Information/sentinel.html new file mode 100644 index 0000000000..3a8a5e85cf --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/sentinel.html @@ -0,0 +1,62 @@ + + + + + + + + + + Policy as Code : Sentinel | docmoa + + + + + +
본문으로 건너뛰기

Policy as Code : Sentinel

1분 미만terraformIaC

Policy as Code : Sentinel

Terraform은 인프라의 코드화 측면에서 그 기능을 충실히 실현해줍니다. 하지만 팀과 조직에서는 단지 인프라의 코드적 관리와 더불어 다른 기능들이 필요하기 마련입니다. 그중 하나로 정책을 꼽을 수 있습니다.

Infrastructure as Code를 구현하면 이전보다 빠른 프로비저닝이 가능합니다. 하지만, 여기에 팀웍과 조직 내 거버넌스를 유지하기 위해 또다른 도구나 무언가가 필요하다면? 관리를 위한 관리, 도구를 위한 도구들이 필요하게 될 것입니다. 그래서 테라폼의 클라우드 버전 부터는 팀이나 조직의 정책을 코드로 관리하여 자산화 시킬 수 있는 도구를 제공합니다.


유형지원여부
Terraform OSS (Open Source Software)
Terraform Cloud✔︎
Terraform Cloud for Business✔︎
Terraform Enterprise✔︎

Sentinel 구성

Sentinel 이 하시코프의 솔루션들의 정책 코드화를 위한 도구 입니다. 앞서의 IaC에 더불어 Policy 또한 코드로 정의하고 체계화 할 수 있게 도와주는 도구입니다. Sentinel 또한 VCS로 관리되고 하나 이상의 워크스페이스에 정책을 적용할 수 있게 설계 되었습니다. Sentinel의 구성은 다음과 같습니다.

└── Sentinel_Root
+    ├── sentinel.hcl
+    ├── [abc].sentinel
+    ├── [def].sentinel
+    ├── [...].sentinel
+

sentinel.hcl에서 해당 정책 묶음을 관리합니다. 정책은 단일 또는 다중의 조건이 있으며, 각 조건은 필수인지 아닌지에 대한 조건도 있을 것입니다. 각 정책은 policy.sentinel 확장자 앞의 이름이 정의 됩니다.

정책을 선언할 때 각각의 강제 정도를 정의할 수 있습니다.

policy "restrict-output-sensitive" {
+    # enforcement_level = "advisory"
+    # enforcement_level = "soft-mandatory"
+    enforcement_level = "hard-mandatory"
+}
+
  • Advisory: 정책 위반시 경고
  • Soft Mandatory: 규정 위반 시 사용자가 재정의(통과) 가능
  • Hard Mandatory: 정책 규정 위반 불가 (에러 처리)

Policy

정책은 Plan에 따른 stateconfig 에 작성된 조건들, 또는 시간과 같은 항목에 따라 작성가능하고, 리턴되는 값이 true 인 경우 해당 정책 조건을 만족하는 것으로 판단합니다. 작성하는 기법에 따라 여러 조건은 하나의 파일에 작성할 수도 있고 별도로 구분하여 sentinel.hcl 파일에 각각 작성하는 것도 가능합니다. 아래 예제는 테라폼 구성에 정의된 모든 output 에 대해 sensitive = true 를 강제화 하기 위한 정책 입니다.

import "tfconfig"
+
+check_outputs = func() {
+  for tfconfig.outputs as k, v {
+    if v.sensitive == false {
+      return false
+    }
+  }
+  return true
+}
+
+main = rule { check_outputs() }
+

마치며

테라폼의 IaC에 추가로 팀 내의 정책을 코드화하는, Policy as Code 의 기능으로 Sentinel을 이용하면 기존에는 별도의 문서나 구두로 존재하던 조직내 정책을 자산화 할 수 있다는 점에서 프로비저닝 관련 도구를 통합할 수 있다는 것에 의미가 있습니다.

Cloud의 경우 관련 기능을 활성화하여 1달정도 무료로 사용해 볼 수 있습니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/variables.html b/04-HashiCorp/03-Terraform/01-Information/variables.html new file mode 100644 index 0000000000..6b76b49897 --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/variables.html @@ -0,0 +1,142 @@ + + + + + + + + + + Variables | docmoa + + + + + +
본문으로 건너뛰기

Variables

1분 미만terraformIaC

Variables

Terraform은 코드로 인프라를 관리하기위한 그 '코드'의 핵심 요소인 변수처리를 다양하게 지원합니다.

Terraform에서는 다양한 변수와 작성된 변수를 관리하기 위한 메커니즘을 제공합니다. 가장 기본이되는 기능 중 하나이며 오픈소스와 엔터프라이즈 모두에서 사용가능합니다.


유형지원여부
Terraform OSS (Open Source Software)✔︎
Terraform Cloud✔︎
Terraform Cloud for Business✔︎
Terraform Enterprise✔︎

코드에서 변수를 사용할 수 없다면 매번 다른 값이 필요할 때마다 하드코딩된 코드 한벌씩을 만들어야 할 것입니다. 테라폼을 활용한 인프라 프로비저닝에서도 코드의 특징을 십분 활용 가능하고, 변수도 그 기능 중 하나 입니다.

테라폼 설정 파일을 작성하는 운영자와 개발자는 변수 선언을 통해 때에 따라 적절한 값을 할당하여 재 정의할 수 있습니다.

Types

지원되는 변수의 범주와 형태는 다음과 같습니다.

  • Primitive Types
    • string
    • number
    • bool
  • Collection Types
    • list
    • map
    • set
  • Structural Types
    • object
    • tuple

기본이 되는 형태를 하나 예를 들어보겠습니다.

variable "name" {								// 변수 이름
+    type = string								// 변수 타입
+    description = "var String"	// 변수 설명
+    default = "myString"				// 변수 기본 값
+}
+
  • 변수 이름 : variable "이름"으로 선언 합니다. 일반적인 코드의 int i 에서의 i 를 의미합니다. 변수 선언 시 타입과 기본 값 선언 외에도 설정 가능한 값이 많이 있어서 variable로 선언합니다. 변수에 대한 이름 선언은 다른 위치에서 해당 변수를 사용할 수 있도록 하는 유일한 값입니다. 같은 이름의 변수를 선언하면 오류가 발생하게 됩니다.
  • type : 해당 변수가 어떤 형태인지 선언해 줍니다. default에 정의되는 값의 형태를 보고 자동으로 추측해주기도 하지만 변수 값의 명확한 형태를 지정해주는 것이 권장됩니다. 또한 해당 유형에 맞지 않는 값이 입력되는 것을 방지해 줍니다.
  • description : 이 변수가 무엇을 의미하는지 설명을 기입할 수 있습니다. 지속적으로 관리하고 협업을 위해 도움이 되는 항목입니다.
  • default : 변수의 기본값을 지정할 수 있습니다. 여기 지정된 값을 보고 type이 없더라도 추측할 수 있습니다. default에 설정된 값이 없고 이후 다른 코드 상에도 값이 비어 있으면 terraform이 실행 될 때 값을 물어봅니다.

각 형태에 대한 예제는 아래와 같습니다.

variable "string" {
+    type = string
+    description = "var String"
+    default = "myString"
+}
+
+variable "number" {
+    type = number
+    default = "123"
+}
+
+variable "boolean" {
+    default = true
+}
+
+variable "list" {
+    default = [
+        "google",
+        "vmware",
+        "amazon",
+        "microsoft"
+    ]
+}
+
+output "list_index_0" {
+  value = var.list.0
+}
+
+output "list_all" {
+  value = [
+    for name in var.list :
+        upper(name)
+  ]
+}
+
+variable "map" {				# Sorting
+    default = {
+        aws = "amazon",
+        azure = "microsoft",
+        gcp = "google"
+    }
+}
+
+output "map" {
+  value = var.map.aws
+}
+  
+variable "set" {				# Sorting
+    type = set(string)
+    default = [
+        "google",
+        "vmware",
+        "amazon",
+        "microsoft"
+    ]
+}
+
+output "set" {
+  value = var.set
+}
+
+variable "object" {
+    type = object({name=string, age=number})
+    default = {
+        name = "abc"
+        age = 12
+    }
+}
+
+variable "tuple" {
+    type = tuple([string, number, bool])
+    default = ["abc", 123, true]
+}
+

Validation

아직은 실험적인 Terraform의 확장 기능이지만, 변수의 유효성 체크가 가능합니다.

terraform {
+  experiments = [variable_validation]
+}
+

Terraform에서 선언되는 변수가 variable로 되어있던 것은 관련 변수를 확장시키는데 의미가 있습니다. 변수 밖에서 별도의 로직을 답는 것이 아니라 내부에 미리 선언하여 사용 가능합니다.

variable "myVar" {
+  type        = string
+  description = "for test"
+  default     = "myVar"
+
+  validation {
+    condition     = length(var.myVar) > 4
+    error_message = "The myVar length up to 4."
+  }
+}
+
+variable "yourVar" {
+  type        = string
+  description = "for test"
+  default     = "yourVar"
+
+  validation {
+    condition     = can(regex("^your", var.yourVar))
+    error_message = "The yourVar must be starting with \"your\"."
+  }
+}
+

내부에 validation 를 추가하고 조건과 에러시의 메시지를 정의합니다. 조건은 true/false로 확인될 수 있는 대부분의 설정이 가능하고 string 의 경우에는 정규표현식(regex)도 확인 가능합니다.

Ordering

변수를 선언하는 방식에는 여러가지가 있지만 각각의 우선순위가 있기 때문에 설정에 주의해야 합니다. 아래 순서대로 마지막에 정의된 변수가 위의 설정 값을 엎어씁니다.

  1. 환경변수 (e.g. TF_VAR_변수이름)
  2. terraform.tfvars
  3. terraform.tfvars.json
  4. *.auto.tfvars / *.auto.tfvars.json
  5. 명령 줄 상의 -var 또는 -var-file

예를들어 myvar 변수가 정의되어있고 시스템의 환경변수로 TF_VAR_myvar 에 값이 선언되었습니다. 이후에 terraform.tfvars 에서 myvar에 값을 선언하면 최종적으로는 terraform.tfvars의 값이 반영됩니다.

마치며

변수에 대한 자유로운 재할당을 통해 기존 인프라에 대한 리소스, 데이터, 모듈을 변경하지 않고 이미 정해진 코드적 인프라를 재활용할 수 있습니다. 코드가 복잡해지고 여러개로 나눠진다 해도 변수에 대한 타입 정의나 조건 확인 같은 기능을 활용하고 변수가 반영 되는 우선순위를 잘 고려한다면 나이스한 IaC가 구현가능합니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/01-Information/workspace.html b/04-HashiCorp/03-Terraform/01-Information/workspace.html new file mode 100644 index 0000000000..acbed5f326 --- /dev/null +++ b/04-HashiCorp/03-Terraform/01-Information/workspace.html @@ -0,0 +1,46 @@ + + + + + + + + + + Workspace | docmoa + + + + + +
본문으로 건너뛰기

Workspace

1분 미만terraformIaC

Workspace

Terraform의 워크스페이스(Workspace)는 일종의 원하는 인프라의 프로비저닝 단위로서, 하나의 state를 갖는 공간입니다. Terraform에서의 plan 혹은 apply 가 이뤄지는 단위이기도 합니다.

워크스페이스는 유형에 따라 하나의 디렉토리이거나 VCS, 혹은 VCS의 브렌치나 측정 위치가 될 수 있습니다. OSS 유형과 Cloud, Enterprise의 가장 큰 차이점은 UI로의 관리와 공동 작업을 위한 워크스페이스별 기능일 것입니다.


유형지원여부
Terraform OSS (Open Source Software)✔︎
Terraform Cloud✔︎
Terraform Cloud for Business✔︎
Terraform Enterprise✔︎

처음에는 워크스페이스 하나에 원하는 인프라의 모든 형태, 예를 들면 VM, 네트워크, 디스크 등의 모든 정보를 하나이 파일, 혹은 여러개의 파일로 나누어 관리합니다. 하지만 프로비저닝은 한번에 이뤄지기 때문에 하나의 작업 공간인 디렉토리 안에서 이루어 지게 됩니다.

커뮤니티 버전에서는 주로 특정 애플리케이션 인프라 구성요소를 한번에 프로비저닝 할수 있는 단위로 관리하게 되는 것이 일반적입니다. 특정 클라우드에 배포되는 특정 서비스를 위한 VM, 네트워크, 디스크 관련 내용이 모두 들있는 단일 배포 단위가 그 예입니다.

  • AWS에 배포되는 고객관리 서비스를 위한 모든 인프라에 대한 프로비저닝
  • VMware에 배포되는 내부 사용자용 개발 서버 요청에 대한 프로비저닝
  • Kubernetes에 하나의 서비스를 위한 마이크로서비스 Pod 배포와 Ingress 구성을 프로비저닝

Terraform Cloud나 Enterprise에서는 각 조직 구성원이 각자의 책임에 맞도록 VM요소, 보안, 네트워크 등의 각 요소를 별도의 워크스페이스로 관리할 수 있습니다. 그 이유는 트리거링이나 워크스페이스의 결과값을 다른 워크스페이스에서 읽을 수 있는 기능들이 좀더 워크스페이스를 팀이나 조직단위로 운영할 수 있도록 제공합니다.

Workspace의 예

OSS 유형에서는 앞서의 설명처럼 Terraform이 실행되는 하나의 디렉토리 단위로 워크스페이스를 정의합니다. 기본적으로는 다음과 같은 구조를 확인 할 수 있습니다. terraform init 이후 한번 apply 한 상태의 예입니다.

└── GettingStarted
+    ├── .terraform
+    ├── main.tf
+    ├── terraform.tfstate
+    ├── terraform.tfstate.backup
+    └── variables.tf
+
  • GettingStarted : 워크스페이스 이름
  • .terraform : 플러그인이 설치되는 위치
  • main.tfopen in new window : Terraform 구성 파일 (main이 아니여도 됨)
  • terraform.tfstate : 실세계에 반영된 현재 상태
  • terraform.tfstate.backup : 실세계에 반영되었던 직전 상태
  • variables.tfopen in new window : 변수를 정의하는 구성 파일을 분리하여 명명한 파일 (variables가 아니여도 됨)

Terraform은 실행시 .tf 확장자의 파일은 모두 읽어들이고 Graph Theory 에 따라 실행 순서를 결정하게 됩니다. 일반적으로 위 구조를 갖는 디렉토리가 각각의 목적에 따라 여러개를 두고 운영할 수 있고, 협업을 하는 경우 각 디렉토리를 하나의 Root를 갖는 레포지토리로 구성하거나 각각을 레포지토리로 구성하여 Git이나 SVN 같은 VCS에 저장하여 공유하게 됩니다.

Terraform Cloud / Enterprise 에서의 Workspace

Terraform Cloud와 Terraform Enterprise에서는 워크스페이스를 웹 UI를 통해 관리하게 됩니다. Terraform의 실행 주체가 로컬 환경이 아닌 리모트 환경이기 때문에 중앙에서 프로비저닝을 위한 워크스페이스를 관리하고 VCS와 연동합니다. 각 워크스페이스 마다 RBAC 적용이나 워크스페이스 트리거링 같은 조직에서 필요한 기능들이 추가됩니다.

TerraformWorkspaces
TerraformWorkspaces

마치며

워크스페이스는 Terraform의 기본 작업 단위로, 한번에 apply 하게되는 범위로 볼 수 있습니다. 협업을 하게 되면 워크스페이스를 git기반, 혹은 여타 VCS로 공유하여 팀 또는 조직에서의 공통 워크스페이스 관리를 하게 됩니다. 워크스페이스의 관리를 위한 기능 요건들은 Terraform Cloud, Terraform Enterprise에서 추가로 제공되는 기능으로 실제 Terraform의 실행 위치, 변수관리, 이력관리 등을 중앙에서 할 수 있다는 점에서 워크스페이스의 활용도를 높여줍니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.html b/04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.html new file mode 100644 index 0000000000..5e31bd6ad5 --- /dev/null +++ b/04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.html @@ -0,0 +1,61 @@ + + + + + + + + + + Terraform Enterprise 사용자 비밀번호 변경 | docmoa + + + + + +
본문으로 건너뛰기

Terraform Enterprise 사용자 비밀번호 변경

jsp1분 미만terraformadminpassword

Terraform Enterprise 사용자 비밀번호 변경

Terraform Enterprise를 사용할 때, UI(https://TFE_SERVER) 상으로 접속할 수 없는 상황에서 비밀번호 변경이 필요한 경우, 아래와 같이 작업할 수 있다.

Admin 계정의 경우

다음과 같이 수정 가능.

# 이전 버전의 TFE
+sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
+## 수정 최신 버전의 TFE에서는 Container 이름이 변경됨 (2022.6.21)
+sudo docker exec -it tfe-atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
+
irb(main):050:0> admin_user = User.find_by(username: "tfe-local-admin")
+=> #<User id: 33, email: "tfe-local-admin@test.com", username: "tfe-local-admin", is_admin: false, created_at: "2020-06-24 05:12:12", updated_at: "2020-07-01 09:12:25", suspended_at: nil, two_factor_delivery: nil, two_factor_sms_number: nil, two_factor_secret_key: nil, two_factor_recovery_index: 0, two_factor_recovery_secret_key: nil, two_factor_verified_at: nil, two_factor_enabled_at: nil, is_service_account: false, used_recovery_codes_encrypted: nil, last_auth_through_saml: nil, external_id: "user-361SGA3yMg3P1nGT", accepted_terms_at: nil, accepted_privacy_policy_at: nil, invitation_token: nil, invitation_created_at: nil, is_cyborg: false, onboarding_status: nil>
+irb(main):051:0> admin_user.password = '<<Password>>'
+=> "<<Password>>"
+irb(main):052:0> admin_user.password_confirmation = '<<Password>>'
+=> "<<Password>>"
+irb(main):053:0> admin_user.save
+2020-07-01 10:03:32 [DEBUG] {:msg=>"SettingStorage::Postgres failed to look up setting 'basic.base_domain'"}
+=> true
+

일반 사용자의 경우

sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
+user = User.find_by(email: "user@example.com")
+user.update(:password => '<<PASSWORD>>')
+user.save!
+

일반 사용자를 Admin으로 승격이 필요할 때

sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
+user = User.find_by(email: "user@example.com")
+user.update(is_admin: true)
+user.save!
+
+ + + diff --git a/04-HashiCorp/03-Terraform/02-Config/index.html b/04-HashiCorp/03-Terraform/02-Config/index.html new file mode 100644 index 0000000000..0e2f526e7e --- /dev/null +++ b/04-HashiCorp/03-Terraform/02-Config/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Config | docmoa + + + + + +
본문으로 건너뛰기

02 Config

1분 미만

+ + + diff --git a/04-HashiCorp/03-Terraform/02-Config/terraform-cloud-agent-guide-custom.html b/04-HashiCorp/03-Terraform/02-Config/terraform-cloud-agent-guide-custom.html new file mode 100644 index 0000000000..ec46a21e0c --- /dev/null +++ b/04-HashiCorp/03-Terraform/02-Config/terraform-cloud-agent-guide-custom.html @@ -0,0 +1,69 @@ + + + + + + + + + + Terraform Cloud Agent 가이드 | docmoa + + + + + +
본문으로 건너뛰기

Terraform Cloud Agent 가이드

약 2 분Terraform

Terraform Cloud Agent 가이드

Terraform Cloud Agent(Agent)는 Terraform Enterprise/Cloud(TFE/C)에서 사용가능한 사용자 정의 Terraform 실행 환경을 제공합니다. 사용자는 Agent를 사용하여 Terraform 실행을 위해 기본 제공되는 이미지 대신 커스텀 패키지가 설치된 별도 이미지를 사용할 수 있고, 이미지 실행 위치를 네트워크 환경에서 자체 호스팅 할 수 있습니다.

Monosnap Terraform Agent | onemodel 2024-01-08 14-40-18
Monosnap Terraform Agent | onemodel 2024-01-08 14-40-18

Terraform Cloud Agentsopen in new window

Terraform Cloud Agents on TFEopen in new window

Manage private environments with Terraform Cloud agentsopen in new window

Terraform Enterprise/Cloud 요구사항

Agent는 Pull 기반이므로 Agent→ TFE/C 로 네트워크 연결이 발생합니다. 실행되는 모든 Agent는 Terraform 작업 수행을 위해 TFE/C를 폴링하고 해당 작업을 로컬에서 실행합니다.

TFE/C의 제약 및 차이는 다음과 같습니다.

TFETFC
지원 릴리즈v202109-1
Agent 수 제한제한 없음계약에 따라 (1/3/10~)
TFE/C 호스트 이름 등록TFE 호스트 이름 정의 필요기본 app.terraform.ioopen in new window
사용자 정의 번들번들 지원미지원

Agent 실행을 위안 네트워크 요구사항은 다음 대상으로의 Outbound가 허용되어야 합니다.

대상(Hostname)포트/프로토콜용도
TFE/C Hostname(e.g. app.terraform.ioopen in new window)tcp/443, HTTPS워크로드 폴링, State 업데이트 제공, TFE/C 프라이빗 모듈 레지스트리에서 프라이빗 모듈 다운로드
registry.terraform.ioopen in new windowtcp/443, HTTPS공개되어있는 프로바이더 및 모듈 다운로드하기
releases.hashicorp.comopen in new windowtcp/443, HTTPSTerraform 바이너리 다운로드
archivist.terraform.ioopen in new windowtcp/443, HTTPSBlob Storage

Terraform Agent 구성 (Custom)

Agent 구성을 위한 단계는 다음과 같습니다. 여기서는 사용자 정의 Agent를 포함하여 단계별로 예를 들어 설명합니다. Option은 사용자 정의 단계로, 필요시 진행합니다.

단계설명구분
1사용자 정의 Agent 요건 정의 및 요청 (Option)TF 실무자
2사용자 정의 Agent를 생성 및 레지스트리 등록 (Option)TFE/C 관리자
3VM 또는 K8s에 Agent 실행 환경 구성TFE/C 관리자
4Agent Pool 생성TFE/C 관리자
5Agent 실행 및 Pool 등록 확인TFE/C 관리자
6Workspace에서 Agent 실행 환경 설정TF 실무자

1. 사용자 정의 Agent 요건 정의 (Option)

TF 실무자는 TFE/C에서 워크로드 실행시 필요한 추가 요소에 대해 정의하고, TFE/C 관리자에게 사용자 정의 Agent 생성을 요청합니다. 예를 들어 다음의 패키지/실행 요소가 필요하다고 정의 합니다.

  • aws cli
  • ansible
  • jq

2. 사용자 정의 Agent를 생성 (Option)

TFE/C 관리자는 사용자 정의 Agent에 대한 요구가 있는 경우 Dockerfile을 작성하여 TFE/C에서 사용할 이미지를 생성할 수 있습니다.

FROM hashicorp/tfc-agent:latest
+
+# Switch the to root user in order to perform privileged actions such as
+# installing software.
+USER root
+
+# Install sudo. The container runs as a non-root user, but people may rely on
+RUN apt-get -y install sudo
+# Permit tfc-agent to use sudo apt-get commands.
+RUN echo 'tfc-agent ALL=NOPASSWD: /usr/bin/apt-get , /usr/bin/apt' >> /etc/sudoers.d/50-tfc-agent
+
+# the ability to apt-get install things.
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends unzip curl ca-certificates ansible jq python3-pip && wget -qO awscliv2.zip https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip && unzip awscliv2.zip && ./aws/install && rm -rf ./aws && rm -rf /var/lib/apt/lists/*
+
+# Add CA certificates and ca-trust.
+ADD cert.crt /usr/local/share/ca-certificates
+RUN update-ca-certificates
+
+# Switch back to the tfc-agent user as needed by Terraform agents.
+USER tfc-agent
+

다음과 같이 이미지 빌드를 수행합합니다.

docker build --no-cache -t cumstom.image.host/tfc-agent:v1 .
+

생성된 이미지를 관리하는 이미지 레지스트리에 생성된 이미지를 저장합니다.

docker push https://cumstom.image.host/hashicorp/tfc-agent:v1
+

3. Agent 실행 환경 구성

Agent는 TFE/C에 내장된 기본 실행하는 기본 워커 이미지 대신 사용자 정의로 구성된 워커 이미지를 사용하도록 구성합니다. Agent는 TFE/C에 대한 지속적인 폴링을 수행하는 지속적인 실행 프로세스로 실행됩니다. 오류 발생 시 Agent가 자동으로 재시작 되도록 구성하는 것이 좋습니다.

컨테이너로 실행 되므로 Agent가 실행되기 위한 Docker, Podman 같은 컨테이너 런타임이 구성된 BM/VM 환경, Nomad, 또는 Kubernetes 환경이 필요합니다.

Terraform Agent Kubernetes Operator : https://developer.hashicorp.com/terraform/tutorials/cloud/cloud-agentsopen in new window

4. Agent Pool 생성

Agent를 위한 Pool을 TFE/C에 구성하고, 워크스페이스에서는 이 Pool을 선택하여 작업을 수행합니다. Agent Pool을 생성하려면 TFE/C의 Organazation의 Settings 에서 Agents 에서 수행합니다.

image-20240108162153120
image-20240108162153120

Create agent pool을 선택하여 새로운 Agent Pool Name을 입력하고 Continue를 클릭합니다.

image-20240108162258806
image-20240108162258806

다음으로 Token management에서 Description을 입력하고 Create token 버튼을 클릭합니다.

image-20240108163026960
image-20240108163026960

Agent Token과 Agent를 실행하기 위한 예제 명령이 표시 됩니다. 생성된 Token은 Agent를 실행하고 구분짓는데 사용됩니다.

Monosnap Image 2024-01-08 16-36-31
Monosnap Image 2024-01-08 16-36-31

5. Agent 실행 및 Pool 등록 확인

agent1.list 파일이라는 파일을 생성하고 아래 내용을 붙여넣습니다.

TFC_AGENT_TOKEN=<YOUR TOKEN>
+TFC_AGENT_NAME=agent1
+

이전 단계에서 만든 토큰으로 TFC_AGENT_TOKEN의 값을 업데이트합니다. Agent의 이름은 agent1로 지정합니다. 이 이름은 TFE/C의 Agent 관리 UI와 실행 시 표시되므로 나중에 특정 Agent를 식별할 수 있습니다.

다음과 같이 Agent를 실행합니다. 사용자 정의 Agent 이미지를 사용하는 경우 해당 이미지 주소를 넣어줍니다.

# 생성한 agent1.list 파일을 환경변수 파일로 지정하는 경우
+docker run --env-file agent1.list hashicorp/tfc-agent:latest
+
+# 환경변수를 CLI에 inline으로 지정하는 경우
+docker run -e TFC_AGENT_TOKEN -e TFC_AGENT_NAME hashicorp/tfc-agent:latest
+

6. Workspace에서 Agent 실행 환경 설정

사용자는 워크스페이스의 설정에서 General 항목에서 Execution ModeCustom으로 설정하고, Agent에서 해당 워크스페이스가 실행됨을 지정할 수 있습니다. Agent 선택 시 앞서 생성된 Agent pool의 항목을 설정합니다.

image-20240108172038279
image-20240108172038279

해당 워크스페이스에서 실행시 지정한 Agent에서 실행되는지 확인합니다. 아래 동작은 aws cli, python이 설치된 Agent에서 local_exec로 버전을 확인한 결과 입니다.

img
img
+ + + diff --git a/04-HashiCorp/03-Terraform/03-Sample/hashicat-azure.html b/04-HashiCorp/03-Terraform/03-Sample/hashicat-azure.html new file mode 100644 index 0000000000..54b9f665ad --- /dev/null +++ b/04-HashiCorp/03-Terraform/03-Sample/hashicat-azure.html @@ -0,0 +1,509 @@ + + + + + + + + + + Intro to Terraform on Azure | docmoa + + + + + +
본문으로 건너뛰기

Intro to Terraform on Azure

약 4 분TerraformTerraform on AzureAzureHashiCatTerraform OSSTerraform CloudTerraform EnterpriseTerraform 샘플IaC

Intro to Terraform on Azure

본 글은 HashiCorp의 공식 워크샵인 "Intro to Terraform on Azure" 내용을 발췌하여 작성한 글입니다. 참고open in new window

실습 원본 소스코드는 hashicat-azure 저장소open in new window에서 확인할 수 있습니다.

img
img

Azure 자격증명 설정

자격증명 설정을 위한 상세 설명은 생략합니다.

Terraform에서는 해당 CSP에서 리소스를 배포하기 위해 자격증명이 필요합니다. 자신의 Azure 구독정보를 연동하기 위해 credentials를 설정합니다.

  • ARM_SUBSCRIPTION_ID
  • ARM_CLIENT_ID
  • ARM_CLIENT_SECRET
  • ARM_TENANT_ID
  • ARM_ENVIRONMENT (옵션)
env | grep ARM
+ARM_CLIENT_ID=xxx
+ARM_CLIENT_SECRET=xxx
+ARM_SUBSCRIPTION_ID=xxx
+ARM_TENANT_ID=xxx
+

실습) 시나리오1. azurerm_resource_group 생성

Azure에서는 기본적으로 리소스를 관리하기 위해 리소스 그룹을 생성해야 합니다. 이번 사니리오에서는 리소스 그룹을 생성해보겠습니다.

HCL 구성파일

가장 기본이 되는 main.tf 코드의 구조는 다음과 같습니다.

terraform {
+  required_providers {
+    azurerm = {
+      source  = "hashicorp/azurerm"
+      version = "=2.60.0"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}
+
+resource "azurerm_resource_group" "myresourcegroup" {
+  name     = "${var.prefix}-workshop"
+  location = var.location
+  
+  tags = {}
+}
+

해당 샘플코드에서는 prefix 라는 변수만 필요하므로 다음과 같이 선언합니다.

variable "prefix" {
+  description = "This prefix will be included in the name of most resources."
+  default = "unknown"
+}
+
  • terraform.tfvars

앞서 variables.tf 에서 default = "unknown" 으로 선언하였습니다. 이때, 사용자화된 값으로 대체하기 위해서 변수의 우선순위를 활용하여 덮어쓸 수 있습니다.

필자는 terraform.tfvars 파일을 사용하여 덮어쓰는 방식을 사용해보겠습니다.

# prefix에 자신의 이름을 작성하세요
+prefix = "hyungwook"
+

테라폼 초기화(init)

  • terraform init 명령을 통해 azurerm 프로바이더를 사용하기 위해 초기화를 진행합니다.
terraform init
+

초기화 명령 이후에 azurerm 에서 사용할 데이터가 .terraform 디렉토리 하위에 생성되었는지 확인합니다.

ls .terraform/providers/registry.terraform.io/hashicorp
+azurerm
+

테라폼 계획 & 배포(plan & apply)

  • terraform plan 명령을 통해 배포되기 전 계획을 확인합니다. 해당 실습에서는 최초 배포이므로 한 개의 리소스가 create 됩니다.
terraform plan
+
+Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+  + create
+
+Terraform will perform the following actions:
+
+  # azurerm_resource_group.myresourcegroup will be created
+  + resource "azurerm_resource_group" "myresourcegroup" {
+      + id       = (known after apply)
+      + location = "koreacentral"
+      + name     = "hyungwook-workshop"
+    }
+
+Plan: 1 to add, 0 to change, 0 to destroy.
+
  • terraform apply 명령을 통해 실제 리소스를 배포합니다.
terraform apply
+
+Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+  + create
+
+Terraform will perform the following actions:
+
+  # azurerm_resource_group.myresourcegroup will be created
+  + resource "azurerm_resource_group" "myresourcegroup" {
+      + id       = (known after apply)
+      + location = "koreacentral"
+      + name     = "hyungwook-workshop"
+    }
+
+Plan: 1 to add, 0 to change, 0 to destroy.
+
+Do you want to perform these actions?
+  Terraform will perform the actions described above.
+  Only 'yes' will be accepted to approve.
+
+  Enter a value: yes
+
+azurerm_resource_group.myresourcegroup: Creating...
+azurerm_resource_group.myresourcegroup: Creation complete after 5s [id=/subscriptions/0222cb06-f803-4f66-a922-a0957813a90c/resourceGroups/hyungwook-workshop]
+

실습) 시나리오 2. vnet 생성

시나리오 1에서 생성한 리소스 그룹에 vnet을 추가합니다.

HCL 구성파일

앞서 사용했던 main.tf 파일에 다음과 같이 추가할 azurerm_virtual_network 절을 추가합니다.

# 생략
+resource "azurerm_virtual_network" "vnet" {
+  name                = "${var.prefix}-vnet"
+  location            = azurerm_resource_group.myresourcegroup.location
+  address_space       = [var.address_space]
+  resource_group_name = azurerm_resource_group.myresourcegroup.name
+}
+
  • variables.tfopen in new window 에서는 다음과 같은 두 가지 변수를 사용합니다.
    • prefix : 리소스의 가장 앞에 선언할 변수명
    • address_space : 기본 CIDR 정의
variable "prefix" {
+  description = "This prefix will be included in the name of most resources."
+  default = "unknown"
+}
+
+variable "address_space" {
+  description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created."
+  default     = "10.0.0.0/16"
+}
+

테라폼 계획 & 배포(plan & apply)

azurerm_virtual_network 리소스가 추가로 생성되는 것을 확인합니다.

# 생략
+Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+  + create
+
+Terraform will perform the following actions:
+
+  # azurerm_virtual_network.vnet will be created
+  + resource "azurerm_virtual_network" "vnet" {
+      + address_space         = [
+          + "10.0.0.0/16",
+        ]
+      + guid                  = (known after apply)
+      + id                    = (known after apply)
+      + location              = "koreacentral"
+      + name                  = "hyungwook-vnet"
+      + resource_group_name   = "hyungwook-workshop"
+      + subnet                = (known after apply)
+      + vm_protection_enabled = false
+    }
+

실습) 시나리오 3. Subnet & security group 생성

이번 시나리오에서는 vnet 내부에 subnet과 security group을 추가로 생성해보겠습니다.

HCL 구성파일

  • main.tfopen in new window 에서는 다음 두 가지 리소스를 추가로 생성하는 절을 추가합니다.
    • azurerm_subnet
    • azurerm_network_security_group
# 생략
+resource "azurerm_subnet" "subnet" {
+  name                 = "${var.prefix}-subnet"
+  virtual_network_name = azurerm_virtual_network.vnet.name
+  resource_group_name  = azurerm_resource_group.myresourcegroup.name
+  address_prefixes     = [var.subnet_prefix]
+}
+
+resource "azurerm_network_security_group" "catapp-sg" {
+  name                = "${var.prefix}-sg"
+  location            = var.location
+  resource_group_name = azurerm_resource_group.myresourcegroup.name
+
+  security_rule {
+    name                       = "HTTP"
+    priority                   = 100
+    direction                  = "Inbound"
+    access                     = "Allow"
+    protocol                   = "Tcp"
+    source_port_range          = "*"
+    destination_port_range     = "80"
+    source_address_prefix      = "*"
+    destination_address_prefix = "*"
+  }
+
+  security_rule {
+    name                       = "HTTPS"
+    priority                   = 102
+    direction                  = "Inbound"
+    access                     = "Allow"
+    protocol                   = "Tcp"
+    source_port_range          = "*"
+    destination_port_range     = "443"
+    source_address_prefix      = "*"
+    destination_address_prefix = "*"
+  }
+
+  security_rule {
+    name                       = "SSH"
+    priority                   = 101
+    direction                  = "Inbound"
+    access                     = "Allow"
+    protocol                   = "Tcp"
+    source_port_range          = "*"
+    destination_port_range     = "22"
+    source_address_prefix      = "*"
+    destination_address_prefix = "*"
+  }
+}
+
variable "subnet_prefix" {
+  description = "The address prefix to use for the subnet."
+  default     = "10.0.10.0/24"
+}
+

테라폼 계획 & 배포(plan & apply)

다음 두 리소스가 추가적으로 생성되는 것을 확인합니다.

  • azurerm_network_security_group.catapp-sg
  • azurerm_subnet.subnet
#(생략)
+Terraform will perform the following actions:
+
+  # azurerm_network_security_group.catapp-sg will be created
+  + resource "azurerm_network_security_group" "catapp-sg" {
+      + id                  = (known after apply)
+      + location            = "koreacentral"
+      + name                = "hyungwook-sg"
+      + resource_group_name = "hyungwook-workshop"
+      + security_rule       = [
+          + {
+              + access                                     = "Allow"
+              + description                                = ""
+              + destination_address_prefix                 = "*"
+              + destination_address_prefixes               = []
+              + destination_application_security_group_ids = []
+              + destination_port_range                     = "22"
+              + destination_port_ranges                    = []
+              + direction                                  = "Inbound"
+              + name                                       = "SSH"
+              + priority                                   = 101
+              + protocol                                   = "Tcp"
+              + source_address_prefix                      = "*"
+              + source_address_prefixes                    = []
+              + source_application_security_group_ids      = []
+              + source_port_range                          = "*"
+              + source_port_ranges                         = []
+            },
+#(중략)            
+  
+  # azurerm_subnet.subnet will be created
+  + resource "azurerm_subnet" "subnet" {
+      + address_prefix                                 = (known after apply)
+      + address_prefixes                               = [
+          + "10.0.10.0/24",
+        ]
+      + enforce_private_link_endpoint_network_policies = false
+      + enforce_private_link_service_network_policies  = false
+      + id                                             = (known after apply)
+      + name                                           = "hyungwook-subnet"
+      + resource_group_name                            = "hyungwook-workshop"
+      + virtual_network_name                           = "hyungwook-vnet"
+    }
+
+Plan: 2 to add, 0 to change, 0 to destroy.
+

실습) 시나리오 4. hashicat 웹 애플리케이션 배포

참고 : 원본 소스코드는 hashicat-azure 저장소open in new window에서 확인할 수 있습니다.

이번 시나리오는 실제 VM 인스턴스에 초기화 스크립트를 사용하여 웹 애플리케이션을 배포해보도록 하겠습니다.

HCL 구성파일

  • main.tfopen in new window
    • azurerm_network_interface : Network Interface 생성
    • azurerm_network_interface_security_group_association : Network Interface에 Security Group 할당
    • azurerm_public_ip : 가상머신에 접속하기 위한 Public IP
    • azurerm_virtual_machine : 가상머신
    • null_resource : 가상머신 배포 후 provisioner 를 통해 웹 서비스 설치를 위해 사용
terraform {
+  required_providers {
+    azurerm = {
+      source  = "hashicorp/azurerm"
+      version = "=2.60.0"
+    }
+  }
+}
+
+provider "azurerm" {
+  features {}
+}
+
+resource "azurerm_resource_group" "myresourcegroup" {
+  name     = "${var.prefix}-workshop"
+  location = var.location
+
+  tags = {
+    environment = "Production"
+  }
+}
+
+resource "azurerm_virtual_network" "vnet" {
+  name                = "${var.prefix}-vnet"
+  location            = azurerm_resource_group.myresourcegroup.location
+  address_space       = [var.address_space]
+  resource_group_name = azurerm_resource_group.myresourcegroup.name
+}
+
+resource "azurerm_subnet" "subnet" {
+  name                 = "${var.prefix}-subnet"
+  virtual_network_name = azurerm_virtual_network.vnet.name
+  resource_group_name  = azurerm_resource_group.myresourcegroup.name
+  address_prefixes     = [var.subnet_prefix]
+}
+
+resource "azurerm_network_security_group" "catapp-sg" {
+  name                = "${var.prefix}-sg"
+  location            = var.location
+  resource_group_name = azurerm_resource_group.myresourcegroup.name
+
+  security_rule {
+    name                       = "HTTP"
+    priority                   = 100
+    direction                  = "Inbound"
+    access                     = "Allow"
+    protocol                   = "Tcp"
+    source_port_range          = "*"
+    destination_port_range     = "80"
+    source_address_prefix      = "*"
+    destination_address_prefix = "*"
+  }
+
+  security_rule {
+    name                       = "HTTPS"
+    priority                   = 102
+    direction                  = "Inbound"
+    access                     = "Allow"
+    protocol                   = "Tcp"
+    source_port_range          = "*"
+    destination_port_range     = "443"
+    source_address_prefix      = "*"
+    destination_address_prefix = "*"
+  }
+
+  security_rule {
+    name                       = "SSH"
+    priority                   = 101
+    direction                  = "Inbound"
+    access                     = "Allow"
+    protocol                   = "Tcp"
+    source_port_range          = "*"
+    destination_port_range     = "22"
+    source_address_prefix      = "*"
+    destination_address_prefix = "*"
+  }
+}
+
+resource "azurerm_network_interface" "catapp-nic" {
+  name                      = "${var.prefix}-catapp-nic"
+  location                  = var.location
+  resource_group_name       = azurerm_resource_group.myresourcegroup.name
+
+  ip_configuration {
+    name                          = "${var.prefix}ipconfig"
+    subnet_id                     = azurerm_subnet.subnet.id
+    private_ip_address_allocation = "Dynamic"
+    public_ip_address_id          = azurerm_public_ip.catapp-pip.id
+  }
+}
+
+resource "azurerm_network_interface_security_group_association" "catapp-nic-sg-ass" {
+  network_interface_id      = azurerm_network_interface.catapp-nic.id
+  network_security_group_id = azurerm_network_security_group.catapp-sg.id
+}
+
+resource "azurerm_public_ip" "catapp-pip" {
+  name                = "${var.prefix}-ip"
+  location            = var.location
+  resource_group_name = azurerm_resource_group.myresourcegroup.name
+  allocation_method   = "Dynamic"
+  domain_name_label   = "${var.prefix}-meow"
+}
+
+resource "azurerm_virtual_machine" "catapp" {
+  name                = "${var.prefix}-meow"
+  location            = var.location
+  resource_group_name = azurerm_resource_group.myresourcegroup.name
+  vm_size             = var.vm_size
+
+  network_interface_ids         = [azurerm_network_interface.catapp-nic.id]
+  delete_os_disk_on_termination = "true"
+
+  storage_image_reference {
+    publisher = var.image_publisher
+    offer     = var.image_offer
+    sku       = var.image_sku
+    version   = var.image_version
+  }
+
+  storage_os_disk {
+    name              = "${var.prefix}-osdisk"
+    managed_disk_type = "Standard_LRS"
+    caching           = "ReadWrite"
+    create_option     = "FromImage"
+  }
+
+  os_profile {
+    computer_name  = var.prefix
+    admin_username = var.admin_username
+    admin_password = var.admin_password
+  }
+
+  os_profile_linux_config {
+    disable_password_authentication = false
+  }
+
+  tags = {}
+
+  # Added to allow destroy to work correctly.
+  depends_on = [azurerm_network_interface_security_group_association.catapp-nic-sg-ass]
+}
+
+resource "null_resource" "configure-cat-app" {
+  depends_on = [
+    azurerm_virtual_machine.catapp,
+  ]
+
+  # Terraform 0.12
+  triggers = {
+    build_number = timestamp()
+  }
+
+  provisioner "file" {
+    source      = "files/"
+    destination = "/home/${var.admin_username}/"
+
+    connection {
+      type     = "ssh"
+      user     = var.admin_username
+      password = var.admin_password
+      host     = azurerm_public_ip.catapp-pip.fqdn
+    }
+  }
+
+  provisioner "remote-exec" {
+    inline = [
+      "sudo apt -y update",
+      "sleep 15",
+      "sudo apt -y update",
+      "sudo apt -y install apache2",
+      "sudo systemctl start apache2",
+      "sudo chown -R ${var.admin_username}:${var.admin_username} /var/www/html",
+      "chmod +x *.sh",
+      "PLACEHOLDER=${var.placeholder} WIDTH=${var.width} HEIGHT=${var.height} PREFIX=${var.prefix} ./deploy_app.sh",
+    ]
+
+    connection {
+      type     = "ssh"
+      user     = var.admin_username
+      password = var.admin_password
+      host     = azurerm_public_ip.catapp-pip.fqdn
+    }
+  }
+}
+
+
variable "prefix" {
+  description = "This prefix will be included in the name of most resources."
+  default = "unknown"
+}
+
+variable "location" {
+  description = "The region where the virtual network is created."
+  default     = "eastus"
+}
+
+variable "address_space" {
+  description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created."
+  default     = "10.0.0.0/16"
+}
+
+variable "subnet_prefix" {
+  description = "The address prefix to use for the subnet."
+  default     = "10.0.10.0/24"
+}
+
+variable "vm_size" {
+  description = "Specifies the size of the virtual machine."
+  default     = "Standard_B1s"
+}
+
+variable "image_publisher" {
+  description = "Name of the publisher of the image (az vm image list)"
+  default     = "Canonical"
+}
+
+variable "image_offer" {
+  description = "Name of the offer (az vm image list)"
+  default     = "0001-com-ubuntu-server-jammy"
+}
+
+variable "image_sku" {
+  description = "Image SKU to apply (az vm image list)"
+  default     = "22_04-LTS-gen2"
+}
+
+variable "image_version" {
+  description = "Version of the image to apply (az vm image list)"
+  default     = "latest"
+}
+
+variable "admin_username" {
+  description = "Administrator user name for linux and mysql"
+  default     = "hashicorp"
+}
+
+variable "admin_password" {
+  description = "Administrator password for linux and mysql"
+  default     = "Password123!"
+}
+
+variable "height" {
+  default     = "400"
+  description = "Image height in pixels."
+}
+
+variable "width" {
+  default     = "600"
+  description = "Image width in pixels."
+}
+
+variable "placeholder" {
+  default     = "placekitten.com"
+  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
+}
+

테라폼 계획 & 배포(plan & apply)

main.tf에서 추가했던 각종 리소스가 추가적으로 배포되는 것을 확인합니다.
해당 시나리오에서는 가상머신 생성 후 null_resource 리소스를 통해 아파치 웹 서버를 설치하는 과정이 포함되어 있으므로 3~5분정도 소요됩니다.

결과 확인 : output

# 생략
+null_resource.configure-cat-app (remote-exec): Script complete.
+null_resource.configure-cat-app: Creation complete after 31s [id=7198378208770846330]
+
+Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
+
+Outputs:
+
+catapp_url = "http://hyungwook-meow.koreacentral.cloudapp.azure.com"
+

결과확인 : WEB UI

catapp_url 을 통해 접속해본 결과 정상적으로 웹 애플리케이션이 배포되고 고양이 이미지를 출력하는 것을 확인할 수 있다.

img
img

정리

본 실습을 통해서 간략하게 Azure의 azurerm 프로바이더를 통해 각종 리소스를 생성하는 방안을 알아보았습니다.
잘못된 정보나 수정이 필요한 부분이 있다면 언제든 피드백 부탁드립니다!

작성자 : 유형욱(hyungwook.yu@hashicorp.com)

+ + + diff --git a/04-HashiCorp/03-Terraform/03-Sample/index.html b/04-HashiCorp/03-Terraform/03-Sample/index.html new file mode 100644 index 0000000000..142960b7ab --- /dev/null +++ b/04-HashiCorp/03-Terraform/03-Sample/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 03 Sample | docmoa + + + + + +
본문으로 건너뛰기

03 Sample

1분 미만

+ + + diff --git a/04-HashiCorp/03-Terraform/03-Sample/nomad-csi-sample.html b/04-HashiCorp/03-Terraform/03-Sample/nomad-csi-sample.html new file mode 100644 index 0000000000..977a0353c7 --- /dev/null +++ b/04-HashiCorp/03-Terraform/03-Sample/nomad-csi-sample.html @@ -0,0 +1,272 @@ + + + + + + + + + + Nomad CSI Sample | docmoa + + + + + +
본문으로 건너뛰기

Nomad CSI Sample

약 2 분NomadterrafomCSI

Nomad CSI Sample

nomad Architecture with CSI
nomad Architecture with CSI

ec2(nomad client node)에 efs의 volume관련 권한이 필요합니다.

  • ec2와 efs는 같은 subnet이여야 합니다.
  • vpc에는 dns관련 권한설정이 필요합니다.
ec2 iam policy

+resource "aws_vpc" "nomad_demo" {
+  cidr_block = var.vpc_cidr_block
+  #dns 권한설정이 필요함
+  enable_dns_support   = true
+  enable_dns_hostnames = true
+  tags = {
+    env = "nomad"
+  }
+}
+
+resource "aws_subnet" "nomad_demo" {
+  cidr_block = var.vpc_cidr_block
+  vpc_id     = aws_vpc.nomad_demo.id
+  #efs와 az가 같아야함
+  availability_zone = "ap-northeast-2a"
+}
+
+############
+# Policy
+
+data "aws_iam_policy_document" "instance_role" {
+  statement {
+    effect = "Allow"
+    actions = [
+      "sts:AssumeRole",
+    ]
+
+    principals {
+      type        = "Service"
+      identifiers = ["ec2.amazonaws.com"]
+    }
+  }
+}
+
+resource "aws_iam_role" "instance_role" {
+  name_prefix        = "${var.prefix}-nomad"
+  assume_role_policy = data.aws_iam_policy_document.instance_role.json
+}
+
+resource "aws_iam_role" "instance_role" {
+  name_prefix        = "${var.prefix}-nomad"
+  assume_role_policy = data.aws_iam_policy_document.instance_role.json
+}
+
+resource "aws_iam_instance_profile" "test_profile" {
+  name = "test_profile"
+  role = aws_iam_role.instance_role.name
+}
+
+resource "aws_iam_role_policy" "cluster_discovery" {
+  name   = "${var.prefix}-nomad-cluster_discovery"
+  role   = aws_iam_role.instance_role.id
+  policy = data.aws_iam_policy_document.cluster_discovery.json
+}
+
+data "aws_iam_policy_document" "cluster_discovery" {
+  # allow role with this policy to do the following: list instances, list tags, autoscale
+  statement {
+    effect = "Allow"
+    actions = [
+      "ec2:DescribeInstances",
+      "autoscaling:CompleteLifecycleAction",
+      "ec2:DescribeTags",
+      "ecs:ListClusters",
+      "ecs:DescribeClusters",
+      "ecs:DeregisterContainerInstance",
+      "ecs:ListContainerInstances",
+      "ecs:RegisterContainerInstance",
+      "ecs:SubmitContainerStateChange",
+      "ecs:SubmitTaskStateChange",
+      "ecs:DescribeContainerInstances",
+      "ecs:DescribeTasks",
+      "ecs:ListTasks",
+      "ecs:UpdateContainerAgent",
+      "ecs:StartTask",
+      "ecs:StopTask",
+      "ecs:RunTask",
+      "elasticfilesystem:ClientMount",
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess"
+    ]
+    resources = ["*"]
+  }
+}
+
efs volume and iam policy

+############
+#EFS
+
+resource "aws_iam_role_policy" "mount_efs_volumes" {
+  name   = "mount-efs-volumes"
+  role   = aws_iam_role.instance_role.id
+  policy = data.aws_iam_policy_document.mount_efs_volumes.json
+}
+
+data "aws_iam_policy_document" "mount_efs_volumes" {
+  statement {
+    effect = "Allow"
+
+    actions = [
+      "ec2:DescribeInstances",
+      "ec2:DescribeTags",
+      "ec2:DescribeVolumes",
+      "ec2:AttachVolume",
+      "ec2:DetachVolume",
+    ]
+    resources = ["*"]
+  }
+}
+
+# csi efs volume
+resource "aws_efs_file_system" "nomad_csi" {
+  creation_token = "nomad-csi"
+  performance_mode = "generalPurpose"
+  throughput_mode  = "bursting"
+
+  tags = {
+    Name = "nomad-csi"
+  }
+  #az가 ec2와 동일해야함
+  availability_zone_name = "ap-northeast-2a"
+}
+
+#ec2와 subnet이 같아야함
+resource "aws_efs_mount_target" "nomad_efs" {
+  file_system_id = aws_efs_file_system.nomad_csi.id
+  subnet_id      = aws_subnet.nomad_demo.id
+  security_groups = [ aws_security_group.efs.id ] 
+}
+
+resource "aws_security_group" "efs" {
+  name        = "allow_efs"
+  description = "Allow EFS inbound traffic"
+  vpc_id      = aws_vpc.nomad_demo.id
+
+  ingress {
+    description = "TLS from VPC"
+    from_port   = 443
+    to_port     = 443
+    protocol    = "tcp"
+    cidr_blocks = [ "0.0.0.0/0" ]
+  }
+
+  egress {
+    from_port   = 0
+    to_port     = 0
+    protocol    = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+
+  tags = {
+    Name = "allow_tls"
+  }
+}
+

nomad csi create

  • nomad csi job을 배포합니다.
  • plugins을 생성합니다.
nomad csi create
#efs csi job을 생성
+resource "nomad_job" "nomad_csi_node_job" {
+  jobspec = file("./job_file/csi-node.tpl")
+}
+
+resource "time_sleep" "wait_30_seconds" {
+  depends_on = [nomad_job.nomad_csi_node_job]
+  create_duration = "30s"
+}
+
+#생성된 plugin을 기다림
+data "nomad_plugin" "efs" {
+  depends_on = [time_sleep.wait_30_seconds]
+  plugin_id        = "aws-efs0"
+  wait_for_registration = true
+}
+
+#efs volume을 nomad에서 사용할 수 있게 plugins을 생성
+resource "nomad_volume" "efs_csi_volume" {
+  depends_on  = [data.nomad_plugin.efs]
+  type        = "csi"
+  plugin_id   = "aws-efs0"
+  volume_id   = "efs_csi_volume"
+  name        = "efs_csi_volume"
+  external_id = data.terraform_remote_state.net.outputs.nomad_efs_name.id
+
+  capability {
+    access_mode     = "single-node-writer"
+    attachment_mode = "file-system"
+  }
+
+  mount_options {
+    fs_type = "ext4"
+  }
+}
+

efs test job 배포

job "efs_csi_job" {
+  datacenters = ["dc1"]
+
+  type        = "system"
+
+  group "cache" {
+    count = 1
+
+    network {
+      port "db" {
+        to = 6379
+      }
+    }
+    # 생성한 volume id 값을 명시한 volume을 선언
+    volume "cache" {
+      type            = "csi"
+      source          = "efs_csi_volume"
+      attachment_mode = "file-system"
+      access_mode     = "single-node-writer"
+      read_only    = false
+    }
+
+    task "redis" {
+      driver = "docker"
+
+      config {
+        image = "redis:6.2.6-alpine3.15"
+        ports = ["db"]
+      }
+
+      resources {
+        cpu    = 500
+        memory = 511
+      }
+      #선언한 volume을 사용할 위치에 mount
+      volume_mount {
+        volume      = "cache"
+        destination = "/data"
+        read_only    = false
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.html b/04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.html new file mode 100644 index 0000000000..e005b910ce --- /dev/null +++ b/04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.html @@ -0,0 +1,71 @@ + + + + + + + + + + The Admin Username specified is not allowed. | docmoa + + + + + +
본문으로 건너뛰기

The Admin Username specified is not allowed.

1분 미만TerraformAzure

The Admin Username specified is not allowed.

Log
Error : compute.VirtualMachinesClient#CreateOrUpdate: Failure sending request: StatusCode=400 – Original Error: Code=“InvalidParameter” Message=“The Admin Username specified is not allowed.” Target="adminUsername"

Azure(azurerm) 프로바이더를 사용하여 Virtual Machine을 프로비저닝하는 경우 OSProfile에서 Admin User Name을 잘못된 조건으로 구성하는 경우 발생 할 수 있음

Azure API - OSProfile.AdminUsername Property

https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.models.osprofile.adminusername?view=azure-dotnetopen in new window

Azure의 API에서 정의하는 OSProfile 내의 AdminUsername은 온라인 문서에서처럼 몇가지 룰이 있다.

  • Windows VM 제약
    • . 으로 끝날 수 없음
    • 20자 제한
  • Linux VM 제약
    • 1~64자
  • 다음의 이름은 AdminUser로 허용되지 않음
    • administrator
    • adm
    • admin
    • admin1
    • admin2
    • user
    • user1
    • user2
    • user3
    • user4
    • user5
    • test
    • test1
    • test2
    • test3
    • 1
    • 123
    • a
    • actuser
    • aspnet
    • backup
    • console
    • david
    • guest
    • john
    • owner
    • root
    • server
    • sql
    • support
    • support_388945a0
    • sys

Terraform Error Code Sample

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machineopen in new window

resource "azurerm_virtual_machine" "main" {
+  name                  = "${var.prefix}-vm"
+  location              = azurerm_resource_group.main.location
+  resource_group_name   = azurerm_resource_group.main.name
+  network_interface_ids = [azurerm_network_interface.main.id]
+  vm_size               = "Standard_DS1_v2"
+
+  storage_image_reference {
+    publisher = "Canonical"
+    offer     = "UbuntuServer"
+    sku       = "16.04-LTS"
+    version   = "latest"
+  }
+  storage_os_disk {
+    name              = "myosdisk1"
+    caching           = "ReadWrite"
+    create_option     = "FromImage"
+    managed_disk_type = "Standard_LRS"
+  }
+  os_profile {
+    computer_name  = "hostname"
+    admin_username = "test"
+    admin_password = "Password1234!"
+  }
+  os_profile_linux_config {
+    disable_password_authentication = false
+  }
+  tags = {
+    environment = "staging"
+  }
+}
+





















 









+ + + diff --git a/04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.html b/04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.html new file mode 100644 index 0000000000..37733e054c --- /dev/null +++ b/04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.html @@ -0,0 +1,73 @@ + + + + + + + + + + State rm | docmoa + + + + + +
본문으로 건너뛰기

State rm

1분 미만TerraformState

State rm

  • 현상

    ... googleapi: Error 400: Invalid request: Invalid request since instance is not running.
    +

    : Terraform을 통하지 않고 리소스가 삭제되어, 해당 리소스를 찾지 못하는 상황 발생

  • State 삭제

    Local 환경의 terraform에 remote를 Terraform cloud로 지정

    terraform {
    +  required_version = ">= 0.12"
    +  backend "remote" {
    +    hostname = "app.terraform.io"
    +    organization = "lguplus"
    +
    +    workspaces {
    +      name = "kids_library"
    +    }
    +  }
    +}
    +

    state 리스트 확인 terraform state list

    my-workspace > terraform state list
    +random_pet.sql
    +module.Cluster_GKE.google_container_cluster.k8sexample
    +module.Cluster_GKE.google_container_node_pool.pool_1
    +module.Cluster_GKE.google_container_node_pool.pool_2
    +module.gcs_buckets.google_storage_bucket.buckets[0]
    +module.sql-db.google_sql_database.default
    +module.sql-db.google_sql_database_instance.default
    +module.sql-db.google_sql_user.default
    +module.sql-db.null_resource.module_depends_on
    +module.sql-db.random_id.user-password
    +module.network.module.routes.google_compute_route.route["egress-internet"]
    +module.network.module.subnets.google_compute_subnetwork.subnetwork["asia-northeast3/fc-kidslib-stg-subnet-1"]
    +module.network.module.vpc.google_compute_network.network
    +

    존재하지 않는 resource를 삭제 terraform state rm [resource_name]

    my-workspace > terraform state rm module.sql-db
    +Removed module.sql-db.google_sql_database.default
    +Removed module.sql-db.google_sql_database_instance.default
    +Removed module.sql-db.google_sql_user.default
    +Removed module.sql-db.null_resource.module_depends_on
    +Removed module.sql-db.random_id.user-password
    +Successfully removed 5 resource instance(s).
    +
+ + + diff --git a/04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.html b/04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.html new file mode 100644 index 0000000000..4ad4161d0c --- /dev/null +++ b/04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.html @@ -0,0 +1,40 @@ + + + + + + + + + + TFE Release v202111-1 (582) Issue | docmoa + + + + + +
본문으로 건너뛰기

TFE Release v202111-1 (582) Issue

1분 미만TerraformEnterprise

TFE Release v202111-1 (582) Issue

v202111-1 (582) 버전 이상으로 설치, 또는 업그레이드 시 발생하는 이슈

현상

error_console
error_console
Nginx access Log
2021/12/17 02:58:31 [error] 10#10: *913 connect(0mfailed (111: Connection refused) while connecting to upstream, client: 10.10.10.100, server:tfe.mydomain.com, reguest: "GET / HTTP/1.1", upstream: "http://172.11.0.1:9292/open in new window", host: "tfe.mydomain.comopen in new window"
Ptfe_atlas Log
ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. see https://github.com/rails/execjsopen in new window for a list fo available runtimes.

v202111-1 (582) Release

v202111-1 (582) Release를 기점으로 TFE 내 포함된 container 들에 대한 update 가 있었으며, 해당 update 는 Alpine 3.14 를 사용하게 되는데 이는 특정 버전 이상의 'docker' 와 'libsecomp' 를 요구합니다. (docker - 20.10.0 이상 / libsecomp - 2.4.4 이상)

+ + + diff --git a/04-HashiCorp/03-Terraform/04-TroubleShooting/error-state-snapshot-was-created-by-terraform-version.html b/04-HashiCorp/03-Terraform/04-TroubleShooting/error-state-snapshot-was-created-by-terraform-version.html new file mode 100644 index 0000000000..e1d56249a4 --- /dev/null +++ b/04-HashiCorp/03-Terraform/04-TroubleShooting/error-state-snapshot-was-created-by-terraform-version.html @@ -0,0 +1,65 @@ + + + + + + + + + + Error: state snapshot was created by Terraform vX.Y.Z | docmoa + + + + + +
본문으로 건너뛰기

Error: state snapshot was created by Terraform vX.Y.Z

1분 미만TerraformAzure

Error: state snapshot was created by Terraform vX.Y.Z

Log
Error: state snapshot was created by Terraform v0.13.2, which is newer than current v0.12.26; upgrade to Terraform v0.13.2 or greater to work with this state
  • 버전관련 에러 메시지에 대해 몇가지 테스트 해본 결과, 상위버전으로 Terraform State가 생성 된 이후 하위 버전으로 Refresh/Plan/Apply 를 수행하는 경우에 발생하는 것으로 확인
  • terraform_remote_state 는 버전에 관계 없이 워크스페이스 간에 output을 읽어올 수 있음을 확인

Example

  • 0.12.x 로 생성된 state에서 0.13.x로 Refresh/Plan/Apply 수행 시 state의 버전이 업그레이드 됨
  • 0.13.x 로 생성된 state에서 0.12.x로 Refresh/Plan/Apply 수행 시 동일한 에러 발생
  • 0.13.x로 생성된 state를 "terraform_remote_state"로 다른 0.12.x인 워크스페이스에서 읽어오는 경우 정상적으로 동작

따라서, 관련 에러가 발생한 워크스페이스의 설정에서 최종적으로 생서된 state의 버전과 동일한 실행 버전인지 확인이 필요하다. (아래 캡쳐와 같이 State 버전이 설정의 Terraform Version 보다 높은 경우 에러 발생)

{
+  "version": 4,
+  "terraform_version": "0.13.7",
+  "serial": 3,
+  "lineage": "83b1b4d5-826a-ecc9-ce4a-ea41f06f93af",
+  "outputs": {
+    "uuid_from_0_13": {
+      "value": "34ee63e3-860b-1a1b-c0fa-1546f922be5a-0.12",
+      "type": "string"
+    }
+  },
+  "resources": [
+    {
+      "mode": "data",
+      "type": "terraform_remote_state",
+      "name": "v0_13",
+      "provider": "provider[\"terraform.io/builtin/terraform\"]",
+      "instances": [
+        
+      ]
+    }
+  ]
+}
+

 





















CLI
$ terraform version
+Terraform v0.12.31
+
+ + + diff --git a/04-HashiCorp/03-Terraform/04-TroubleShooting/index.html b/04-HashiCorp/03-Terraform/04-TroubleShooting/index.html new file mode 100644 index 0000000000..b60365f225 --- /dev/null +++ b/04-HashiCorp/03-Terraform/04-TroubleShooting/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 04 Trouble Shooting | docmoa + + + + + +
본문으로 건너뛰기

04 Trouble Shooting

1분 미만

+ + + diff --git a/04-HashiCorp/03-Terraform/04-TroubleShooting/re-install.html b/04-HashiCorp/03-Terraform/04-TroubleShooting/re-install.html new file mode 100644 index 0000000000..434e0131fc --- /dev/null +++ b/04-HashiCorp/03-Terraform/04-TroubleShooting/re-install.html @@ -0,0 +1,40 @@ + + + + + + + + + + TFE 재설치 주의사항 | docmoa + + + + + +
본문으로 건너뛰기

TFE 재설치 주의사항

jsp1분 미만TerraformEnterpriseTFE

TFE 재설치 주의사항

관련 Knowledge Base Article : https://support.hashicorp.com/hc/en-us/articles/4409044739859-Container-ptfe-base-startup-failedopen in new window

기존에 설치되어 있는 TFE를 백업받고, 업그레이드 작업을 하는 경우 반드시 enc_password를 저장 후 작업하시기 바랍니다. 그렇지 않은 경우, 아래와 같이 ptfe_base_startup failed 때문에 구성이 불가할 수 있습니다.

error_console
error_console

보통 Automated Install (silent install)을 하는 경우 Application Settings에 지정 후 설치하게 되어 해당 파일에 enc_password 값이 저장됩니다. (https://www.terraform.io/enterprise/install/automated/automating-the-installer#application-settingsopen in new window)

설치 시 사용한 TF 코드나 수작업을 생성한 settings.json 파일을 잘 보관하셔서 upgrade나 복구 시 이용할 수 있도록 준비하시는 게 나을 듯 합니다.

+ + + diff --git a/04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.html b/04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.html new file mode 100644 index 0000000000..eff71fa761 --- /dev/null +++ b/04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.html @@ -0,0 +1,110 @@ + + + + + + + + + + Terraform Provider - 번들링 | docmoa + + + + + +
본문으로 건너뛰기

Terraform Provider - 번들링

1분 미만terraformprovider

Terraform Provider - 번들링

https://github.com/hashicorp/terraform/tree/main/tools/terraform-bundleopen in new window
Terraform Enterprise에서 동작하는 기능입니다.

Airgap 환경에서 사용할 특정 버전의 Terraform과 여러 제공자 플러그인을 모두 포함하는 zip 파일 인 "번들 아카이브"를 생성하는 툴을 사용합니다. 일반적으로 Terraform init을 통해 특정 구성 작업에 필요한 플러그인을 다운로드하고 설치하지만 Airgap 환경에서는 공식 플러그인 저장소에 액세스 할 수 없는 경우가 발생합니다. Bundle 툴을 사용하여 Terraform 버전과 선택한 공급자를 모두 설치하기 위해 대상 시스템에 압축을 풀 수있는 zip 파일이 생성되므로 즉석 플러그인 설치가 필요하지 않습니다.

주의

번들로 작성된 zip파일을 url로 등록하기 때문에 번들을 다운받을 수 있는 웹서버나 넥서스 같은 원격 저장소가 필요합니다.

build 툴 준비

# Ubuntu
+# Install Go: https://github.com/golang/go/wiki/Ubuntu
+$ sudo add-apt-repository ppa:longsleep/golang-backports
+$ sudo apt update -y
+$ sudo apt install golang-go -y
+#sudo apt install golang-1.14-go -y
+
+# Build terraform-bundle from a release tag that matches your TF version
+# Otherwise you might get an error like:
+# "Failed to read config: this version of terraform-bundle can only build bundles for . . ."
+$ git clone https://github.com/hashicorp/terraform.git
+$ cd terraform
+$ go install ./tools/terraform-bundle
+
+#verify that terraform-bundle tool is there
+$ ls ~/go/bin/ 
+$ export PATH=${PATH}:$HOME/go/bin/
+$ terraform-bundle --version
+0.13.0
+

bundle 용 hcl 파일 구성 및 생성

bundle 구성할 명세를 hcl로 작성합니다. (e.g. tf-bundle.hcl)

공식(Official) 프로바이더의 경우 source 정의를 생략할 수 있습니다. 그렇지 않는 경우에는 반드시 source에 대한 정의가 필요합니다.

terraform {
+  # Version of Terraform to include in the bundle. An exact version number is required.
+  version = "0.15.4"
+}
+
+# Define which provider plugins are to be included
+providers {
+  null = {
+    versions = ["= 3.1.0"]
+  }
+  time = {
+    versions = ["= 0.7.1"]
+  }
+  random = {
+    versions = ["= 3.1.0"]
+  }
+  template = {
+    versions = ["= 2.2.0"]
+  }
+  tfe = {
+    versions = ["= 0.25.3"]
+  }
+  vsphere = {
+    versions = ["= 1.26.0"]
+  }
+  vault = {
+    versions = ["= 2.20.0"]
+  }
+  consul = {
+    versions = ["= 2.12.0"]
+  }
+  kubernetes = {
+    versions = ["= 2.2.0"]
+  }
+  ad = {
+    versions = ["=0.4.2"]
+  }
+  openstack = {
+    versions = ["= 1.42.0"]
+    source = "terraform-provider-openstack/openstack"
+  }
+  nsxt = {
+    versions = ["= 3.1.1"]
+    source = "vmware/nsxt"
+  }
+  vra7 = {
+    versions = ["= 3.0.2"]
+    source = "vmware/vra7"
+  }
+}
+

번들 생성은 다음과 같이 커맨드로 실행 합니다.

terraform-bundle package -os=linux -arch=amd64 tf-bundle.hcl
+

이 작업을 통해 Terraform Enterprise에서 기존 Terraform을 다운로드 받고 Provider를 다운로드 받던 동작을 미리 수행한 번들이 생성 됩니다.

생성된 번들 파일(zip)은 TFE Admin Console을 통해 적용

  • TFE Console 에서 Site Admin으로 이동
  • Terraform Versions > Add terraform version
  • Version 이름, Url, Checksum을 기입하고 저장
    Checksum e.g. : shasum -a256 ./terraform_0.15.4-bundle2021060202_linux_amd64.zip
+ + + diff --git a/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.html b/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.html new file mode 100644 index 0000000000..b45edcf153 --- /dev/null +++ b/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.html @@ -0,0 +1,149 @@ + + + + + + + + + + Terraform Provider - 로컬 디렉토리 | docmoa + + + + + +
본문으로 건너뛰기

Terraform Provider - 로컬 디렉토리

약 1 분terraformprovider

Terraform Provider - 로컬 디렉토리

https://www.terraform.io/docs/cli/config/config-file.html#implied-local-mirror-directoriesopen in new window
https://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providersopen in new window

환경

  • OS : CentOS7

    NAME="CentOS Linux"
    +VERSION="7 (Core)"
    +ID="centos"
    +ID_LIKE="rhel fedora"
    +VERSION_ID="7"
    +PRETTY_NAME="CentOS Linux 7 (Core)"
    +ANSI_COLOR="0;31"
    +CPE_NAME="cpe:/o:centos:centos:7"
    +HOME_URL="https://www.centos.org/"
    +BUG_REPORT_URL="https://bugs.centos.org/"
    +
    +CENTOS_MANTISBT_PROJECT="CentOS-7"
    +CENTOS_MANTISBT_PROJECT_VERSION="7"
    +REDHAT_SUPPORT_PRODUCT="centos"
    +REDHAT_SUPPORT_PRODUCT_VERSION="7"
    +
  • Terraform

    $ terraform version
    +Terraform v1.0.0
    +

Test Provider

구성 절차

  1. 필요한 Provider zip파일을 https://releases.hashicorp.comopen in new window 에서 미리 다운 받습니다. 받아놓은 zip 파일이 있는 경우 대상 시스템에 복사해둡니다.

    $ wget https://releases.hashicorp.com/terraform-provider-nsxt/3.2.1/terraform-provider-nsxt_3.2.1_linux_amd64.zip
    +$ wget https://releases.hashicorp.com/terraform-provider-random/3.1.0/terraform-provider-random_3.1.0_linux_amd64.zip
    +
  2. Plugin 디렉토리 구성
    로컬 Provider를 찾기위한 디렉토리 구조를 생성합니다. host_name은 환경마다 상이할 수 있습니다.

    ~/.terraform.d/plugins/${host_name}/${namespace}/${type}/${version}/${target}

    $ mkdir -p ~/.terraform.d/plugins/localhost.localdomain/vmware/nsxt/3.2.1/linux_amd64
    +$ mkdir -p ~/.terraform.d/plugins/localhost.localdomain/hashicorp/random/3.1.0/linux_amd64
    +
  3. Provider 바이너리 파일 구성

    기존에 받아놓은 zip 파일을 압축 해제하고, 생성한 Provider 디렉토리 각각에 맞는 프로바이더를 복사합니다.

    $ unzip terraform-provider-random_3.1.0_linux_amd64.zip
    +Archive:  terraform-provider-random_3.1.0_linux_amd64.zip
    +  inflating: terraform-provider-random_v3.1.0_x5
    +  
    +$ mv ./terraform-provider-random_v3.1.0_x5 ~/.terraform.d/plugins/localhost.localdomain/hashicorp/random/3.1.0/linux_amd64
    +
    +$ unzip terraform-provider-nsxt_3.2.1_linux_amd64.zip
    +Archive:  terraform-provider-nsxt_3.2.1_linux_amd64.zip
    +  inflating: CHANGELOG.md
    +  inflating: LICENSE.txt
    +  inflating: README.md
    +  inflating: terraform-provider-nsxt_v3.2.1
    +  
    +$ mv ./terraform-provider-nsxt_v3.2.1 ~/.terraform.d/plugins/localhost.localdomain/vmware/nsxt/3.2.1/linux_amd64
    +
  4. 로컬 Provider 구성 확인

파일 구조

$ tree -a ~/.terraform.d/
+/root/.terraform.d/
+├── `plugins`
+│   └── localhost.localdomain
+│       ├── hashicorp
+│       │   └── random
+│       │       └── 3.1.0
+│       │           └── linux_amd64
+│       │               └── terraform-provider-random_v3.1.0_x5
+│       └── vmware
+│           └── nsxt
+│               └── 3.2.1
+│                   └── linux_amd64
+│                       └── terraform-provider-nsxt_v3.2.1
+├── checkpoint_cache
+└── checkpoint_signature
+
  1. 워크스페이스 생성 (디렉토리) - airgapped 는 임의의 이름 입니다.

    $ mkdir ./airgapped
    +$ cd ./airgapped
    +
  2. tf 파일 작성

    $ cat <<EOF> terraform.tf
    +terraform {
    +  required_providers {
    +    nsxt = {
    +      source = "localhost.localdomain/vmware/nsxt"
    +      version = "3.2.1"
    +    }
    +    random = {
    +      source = "localhost.localdomain/hashicorp/random"
    +      version = "3.1.0"
    +    }
    +  }
    +}
    +
    +provider "nsxt" {
    +  # Configuration options
    +}
    +
    +provider "random" {
    +  # Configuration options
    +}
    +
    +resource "random_id" "test" {
    +  byte_length = 8
    +}
    +
    +output "random_id" {
    +  value = random_id.test
    +}
    +EOF
    +
  3. Terraform init을 수행하여 정상적으로 로컬 Provider를 가져오는지 확인합니다.

    $ terraform init
    +
    +Initializing the backend...
    +
    +Initializing provider plugins...
    +- Finding localhost.localdomain/vmware/nsxt versions matching "3.2.1"...
    +- Finding localhost.localdomain/hashicorp/random versions matching "3.1.0"...
    +- Installing localhost.localdomain/vmware/nsxt v3.2.1...
    +- Installed localhost.localdomain/vmware/nsxt v3.2.1 (unauthenticated)
    +- Installing localhost.localdomain/hashicorp/random v3.1.0...
    +- Installed localhost.localdomain/hashicorp/random v3.1.0 (unauthenticated)
    +
    +Terraform has created a lock file .terraform.lock.hcl to record the provider
    +selections it made above. Include this file in your version control repository
    +so that Terraform can guarantee to make the same selections by default when
    +you run "terraform init" in the future.
    +
    +Terraform has been successfully initialized!
    +
    +You may now begin working with Terraform. Try running "terraform plan" to see
    +any changes that are required for your infrastructure. All Terraform commands
    +should now work.
    +
    +If you ever set or change modules or backend configuration for Terraform,
    +rerun this command to reinitialize your working directory. If you forget, other
    +commands will detect it and remind you to do so if necessary.
    +
+ + + diff --git a/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.html b/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.html new file mode 100644 index 0000000000..14a8b5dbe1 --- /dev/null +++ b/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.html @@ -0,0 +1,54 @@ + + + + + + + + + + Terraform Provider - 로컬 미러링 | docmoa + + + + + +
본문으로 건너뛰기

Terraform Provider - 로컬 미러링

1분 미만terraformprovider

Terraform Provider - 로컬 미러링

https://www.terraform.io/docs/cli/config/config-file.html#provider_installationopen in new window

Terraform CLI를 사용할 때, 기본적으로 코드 상에서 사용하는 플러그인은 registry.terraform.ioopen in new window에서 다운로드 받게 되어 있습니다.

하지만 네트워크이 느리거나 폐쇄망인 경우, 직접 다운로드가 아닌 다른 방법으로 프로바이더를 사용할 수 있습니다.

CLI 설정 파일에 명시적으로 설정하는 방법과 설정하지 않고 사용하는 방법이 있습니다.

상대적으로 설정이 간편한 filesystem_mirror 설정 방법은 다음과 같습니다.

  1. Terraform 사용 환경에 맞춰 terraform configuration 파일 구성하기

    • Windows : 사용자의 %APPDATA% 디렉토리 상에 terraform.rc
    • Linux/MacOS : 사용자 홈 디렉토리 상에 .terraformrc
  2. 다음 처럼 'provider_installation' 설정하기

    provider_installation {
    +  filesystem_mirror {
    +    path    = "/usr/share/terraform/providers"
    +    include = ["*/*"] # registry.terrafom.io/hashicorp/*
    +  }
    +}
    +
  3. 대상 디렉토리 설정하기

    • 예를 들어 aws provider는 다음과 같이 코드 상에 사용

      terraform {
      +  required_providers {
      +    aws = {
      +      source  = "hashicorp/aws"
      +      version = "3.36.0"
      +    }
      +  }
      +}
      +
    • 지정된 경로 상에 다음과 같은 HOSTNAME/NAMESPACE/TYPE/VERSION/TARGET 형태로 디렉토리 구조를 지정

      • HOSTNAME = "registry.terraform.ioopen in new window", NAMESPACE="hashicorp", TYPE="aws", VERSION="3.36.0", TARGET은 클라이언트 환경에 대한 것으로 현재 실행 환경에 따라 "darwin_amd64", "linux_arm" "windows_amd64" 등으로 설정하시면 됩니다.
    • 사용하시고자 하는 프로바이어더의 다운로드는 다음 링크에서 가능합니다. https://releases.hashicorp.comopen in new window

+ + + diff --git a/04-HashiCorp/03-Terraform/05-Airgap/index.html b/04-HashiCorp/03-Terraform/05-Airgap/index.html new file mode 100644 index 0000000000..f603ae0fa7 --- /dev/null +++ b/04-HashiCorp/03-Terraform/05-Airgap/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Airgap | docmoa + + + + + +
본문으로 건너뛰기

05 Airgap

1분 미만

+ + + diff --git a/04-HashiCorp/03-Terraform/index.html b/04-HashiCorp/03-Terraform/index.html new file mode 100644 index 0000000000..7395445b83 --- /dev/null +++ b/04-HashiCorp/03-Terraform/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 03 Terraform | docmoa + + + + + +
본문으로 건너뛰기

03 Terraform

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/01-Information/Consul Enterprise Feature.html b/04-HashiCorp/04-Consul/01-Information/Consul Enterprise Feature.html new file mode 100644 index 0000000000..d63bfc89b5 --- /dev/null +++ b/04-HashiCorp/04-Consul/01-Information/Consul Enterprise Feature.html @@ -0,0 +1,40 @@ + + + + + + + + + + Consul Enterprise Feature | docmoa + + + + + +
본문으로 건너뛰기

Consul Enterprise Feature

1분 미만ConsulEnterprise

Consul Enterprise Feature

  • Enterprise Global Visibility & Scale
    • Network Segments
    • Advanced Federation
    • Redendancy Zones
    • Enganced Read Scalability
  • Governance & Policy
    • Namespaces
    • Single Sign On
    • Audit Logging
    • Sentinel

Enterprise Global Visibility & Scale

1. Network Segments

하나의 Consul 서버에서 서로다른 Agent 들의 묶음을 관리할 수 있도록 하는 개념입니다. 여러개의 Consul 클러스터를 만드는 것이 아닌, 하나의 클러스터 내에서 Agent 들을 분류합니다.

참고 Url : https://learn.hashicorp.com/tutorials/consul/network-partition-datacentersopen in new window

2. Advanced Federation

페더레이션을 위해서는 RPC(8300/tcp), SerfWAN(8302/tcp, 8302/udp)를 통해야하므로 여러 테이터센터, 혹은 클러스터를 관리하기에 어려워집니다. 이를 보완하기 위해 지역간 트래픽을 모두 RPC(8300/tcp)를 통해 수행하여 TLS만으로 보안을 유지하도록 합니다.

참고 Url : https://www.consul.io/docs/enterprise/federationopen in new window

3. Redendancy Zones

일반적으로 클러스터의 관리 서버는 3중화하여 구성하며 Raft 알고리즘을 통해 리더 선출을 하는 방식을 취합니다. HA를 위해 해당 투표에 참여하지 않은 추가 잉여 서버 노드를 "상시 대기" 시킬 수 있으며 장애 발생 시 해당 노드는 투표 구성원으로 승격 됩니다. 이는 서버 노드에 대한 추가 구성원임과 동시에 복구 기능을 제공합니다.

참고 Url : https://learn.hashicorp.com/tutorials/consul/autopilot-datacenter-operations#redundancy-zonesopen in new window

4. Enganced Read Scalability

앞서의 Redendancy Zone의 비투표 노드는 투표에는 참여하지 않지만 데이터를 복제하고 데이터를 읽을 수 있는 동작을 지원합니다. 이를 통해 Consul 클러스터를 확장할 수 있고 읽기/쓰기 지연시간을 줄이고 용량을 늘일 수 있습니다.

참고 Url : https://www.consul.io/docs/agent/options#_non_voting_serveropen in new window

Governance & Policy

관련 블로그 : https://www.hashicorp.com/blog/enterprise-compliance-and-governance-with-hashicorp-consul-1-8/open in new window

1. Namespaces

Namespaces 기능은 사용자나 팀간의 데이터 격리를 제공합니다. 각각의 Namespace로 구분된 서비스와 정보는 서로 다른 구성원같에 조회가 불가능합니다. 하나의 클러스터로 논리적 분할을 가능하게 합니다.

참고 Url : https://www.consul.io/docs/enterprise/namespacesopen in new window

2. Single Sign On

Open ID Connect를 사용하여 인증을 처리합니다.

참고 Url : https://www.consul.io/docs/acl/auth-methods/oidcopen in new window

3. Audit Logging

사용자의 이벤트 시도와 처리에 대한 로그를 캡쳐하고 기록하는 것을 지원합니다. 1.8.0 기준으로 'file'을 지원하며, 향후 추가 타입이 지원될 예정입니다.

참고 Url : https://www.consul.io/docs/agent/options#auditopen in new window

4. Sentinel

Consul에 반영되는 서비스나 KV에 대한 정책을 정의할 수 있습니다. 예를 들면 서비스 등록이나 업데이트에 대한 시간을 강제하거나 Key에 이름 패턴을 강제화 할 수 있습니다.

참고 Url : https://www.consul.io/docs/agent/sentinelopen in new window

+ + + diff --git a/04-HashiCorp/04-Consul/01-Information/consul-sizing.html b/04-HashiCorp/04-Consul/01-Information/consul-sizing.html new file mode 100644 index 0000000000..213b8298a0 --- /dev/null +++ b/04-HashiCorp/04-Consul/01-Information/consul-sizing.html @@ -0,0 +1,40 @@ + + + + + + + + + + Consul Sizing | docmoa + + + + + +
본문으로 건너뛰기

Consul Sizing

1분 미만consulsizing

Consul Sizing

https://learn.hashicorp.com/tutorials/consul/reference-architectureopen in new window

Consul은 Server/Client 구조로 구성되며, Client의 경우 자원사용량이 매우 미미하므로 자원산정은 Server를 기준으로 산정

On-prem

SizeCPUMemoryDisk CapacityDisk IODisk Throughput
최소2-4 core8-16 GB RAM100 GB3000+ IOPS75+ MB/s
권장8-16 core32-64 GB RAM100 GB7500+ IOPS250+ MB/s
  • HA 구성을 위해 3대 이상의 홀수 구성

Cloud

ProviderSizeInstance/VM TypesDisk Volume Specs
AWS최소m5.large, m5.xlarge100+GB gp3, 3000 IOPS, 125MB/s
권장m5.2xlarge, m5.4xlarge200+GB gp3, 10000 IOPS, 250MB/s
Azure최소Standard_D2s_v3, Standard_D4s_v31024GB* Premium SSD, 5000 IOPS, 200MB/s
권장Standard_D8s_v3, Standard_D16s_v32048GB* Premium SSD, 7500 IOPS, 200MB/s
GCP최소n2-standard-2, n2-standard-4512GB* pd-balanced, 15000 IOPS, 240MB/s
권장n2-standard-8, n2-standard-161000GB* pd-ssd, 30000 IOPS, 480MB/s
  • HA 구성을 위해 3대 이상 홀수 구성

기본 아키텍처

Recommended architecture diagram
Recommended architecture diagram

Raft 구성 장애 극복을 위한 Non-Voting 서버 구성

Redundancy Zones diagram
Redundancy Zones diagram
+ + + diff --git a/04-HashiCorp/04-Consul/01-Information/index.html b/04-HashiCorp/04-Consul/01-Information/index.html new file mode 100644 index 0000000000..be2f835969 --- /dev/null +++ b/04-HashiCorp/04-Consul/01-Information/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Information | docmoa + + + + + +
본문으로 건너뛰기

01 Information

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/01-Information/port-info.html b/04-HashiCorp/04-Consul/01-Information/port-info.html new file mode 100644 index 0000000000..5707ffdcb3 --- /dev/null +++ b/04-HashiCorp/04-Consul/01-Information/port-info.html @@ -0,0 +1,40 @@ + + + + + + + + + + Consul Port | docmoa + + + + + +
본문으로 건너뛰기

Consul Port

1분 미만consulportrequirement

Consul Port

https://www.consul.io/docs/install/portsopen in new window

Consul 포트

arc
arc

Port Table

UseDefault Ports
DNS (TCP and UDP)8600
HTTP (TCP Only)8500
HTTPS (TCP Only)disabled (8501)*
gRPC (TCP Only)disabled (8502)*
LAN Serf (TCP and UDP)8301
Wan Serf (TCP and UDP)8302
server (TCP Only)8300
Sidecar Proxy Min: 자동으로 할당된 사이드카 서비스 등록에 사용할 포함 최소 포트 번호21000
Sidecar Proxy Max: 자동으로 할당된 사이드카 서비스 등록에 사용할 포괄적인 최대 포트 번호21255

Port 정보

  • DNS : DNS 쿼리 포트
  • HTTP API : API 요청 포트
  • HTTPS API (옵션) : API 요청 포트로 TLS 적용시 활성화
  • gRPC API (옵션) : xDS API를 Envoy 프록시에서 연결하는데 사용되며, 서비스메시 구성시 필수
  • Serf LAN : Gossip 프로토콜을 위한 포트로 LAN 구성에서 사용
  • Serf WAN : Gossip 프로토콜을 위한 포트로 WAN 구성(Federation)에서 사용
  • Server RPC : Consul Client가 Server에 연결하기위한 포트
+ + + diff --git a/04-HashiCorp/04-Consul/02-Configuration/ForwardDns.html b/04-HashiCorp/04-Consul/02-Configuration/ForwardDns.html new file mode 100644 index 0000000000..a0138f524f --- /dev/null +++ b/04-HashiCorp/04-Consul/02-Configuration/ForwardDns.html @@ -0,0 +1,74 @@ + + + + + + + + + + ForwardDns | docmoa + + + + + +
본문으로 건너뛰기

ForwardDns

1분 미만ConsulEnterpriseConfigurationForwardDns

ForwardDns

Consul dns를 local에서도 사용해야 할 경우에는 dns forward를 해줘야한다. 아래는 ubuntu 환경에서 진행하였음

설정 명령어

#systemd-resolved 설정파일 추가 및 변경
+mkdir -p /etc/systemd/resolved.conf.d
+(
+cat <<-EOF
+[Resolve]
+DNS=127.0.0.1
+DNSSEC=false
+Domains=~consul
+EOF
+) | sudo tee /etc/systemd/resolved.conf.d/consul.conf
+(
+cat <<-EOF
+nameserver 127.0.0.1
+options edns0 trust-ad
+EOF
+) | sudo tee /etc/resolv.conf
+#iptables에 consul dns port 추가
+iptables --table nat --append OUTPUT --destination localhost --protocol udp --match udp --dport 53 --jump REDIRECT --to-ports 8600
+iptables --table nat --append OUTPUT --destination localhost --protocol tcp --match tcp --dport 53 --jump REDIRECT --to-ports 8600
+#service 재시작
+systemctl restart systemd-resolved
+

확인 명령어

#Global domain에 consul 확인 
+$ resolvectl domain
+Global: ~consul
+Link 5 (docker0):
+Link 4 (eth2):
+Link 3 (eth1):
+Link 2 (eth0):
+#consul service확인, 해당 클러스터에는 consul server가 3대임
+$ resolvectl query consul.service.consul
+consul.service.consul: 172.30.1.100
+                       172.30.1.101
+                       172.30.1.102
+
+
+ + + diff --git a/04-HashiCorp/04-Consul/02-Configuration/acl-sample.html b/04-HashiCorp/04-Consul/02-Configuration/acl-sample.html new file mode 100644 index 0000000000..296f85ce7b --- /dev/null +++ b/04-HashiCorp/04-Consul/02-Configuration/acl-sample.html @@ -0,0 +1,71 @@ + + + + + + + + + + docmoa + + + + + +
본문으로 건너뛰기

1분 미만ConsulAclPolicy

Consul ACL Policy sample

Consul ACL을 활성화 할 경우 default를 deny로 할 지 allow를 할 지 정할 수 있다.
deny로 할 경우에는 하나하나 policy로 tokne을 만들어서 사용해야 한다.

Consul이 Vault의 Storage로 되어야 할 경우

key_prefix "vault/" {
+  policy = "write"
+}
+service "vault" {
+   policy = "write"
+}
+agent_prefix "" {
+   policy = "read"
+}
+session_prefix "" {
+   policy = "write"
+}
+

Consul Dns query가 필요할 경우

node_prefix "" {
+  policy = "read"
+}
+service_prefix "" {
+  policy = "read"
+}
+# only needed if using prepared queries
+query_prefix "" {
+  policy = "read"
+}
+

Consul UI 접근권한이 필요할 경우

service_prefix "" {
+  policy = "read"
+}
+key_prefix "" {
+  policy = "read"
+}
+node_prefix "" {
+  policy = "read"
+}
+
+ + + diff --git a/04-HashiCorp/04-Consul/02-Configuration/client.html b/04-HashiCorp/04-Consul/02-Configuration/client.html new file mode 100644 index 0000000000..106608514f --- /dev/null +++ b/04-HashiCorp/04-Consul/02-Configuration/client.html @@ -0,0 +1,105 @@ + + + + + + + + + + Consul 클라이언트 설정 | docmoa + + + + + +
본문으로 건너뛰기

Consul 클라이언트 설정

1분 미만ConsulEnterpriseConfigurationClient

Consul 클라이언트 설정

최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 Client설정 파일입니다.
네트워크는 프라이빗(온프레이머스) 환경입니다.

#consul client 설정
+server = false
+
+acl = {
+  enabled = true
+  default_policy = "deny"
+  enable_token_persistence = true
+  tokens = {
+    agent = "f820514a-5215-e741-fcb3-c00857405230"
+  }
+}
+
+license_path = "/opt/license/consul.license"
+
+retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
+
+rejoin_after_leave = true
+
+
+#tls 설정
+ca_file = "/opt/ssl/consul/consul-agent-ca.pem"
+auto_encrypt = {
+  tls = true
+}
+
+verify_incoming = false
+verify_outgoing = true
+verify_server_hostname = true
+

Consul 클라이언트 최소 설정 (20220807기준)

data_dir = "/opt/consul"
+
+client_addr = "0.0.0.0"
+
+datacenter = "my-dc"
+
+# client
+server = false
+
+# Bind addr
+bind_addr = "0.0.0.0" # Listen on all IPv4
+# Advertise addr - if you want to point clients to a different address than bind or LB.
+advertise_addr = "node ip"
+
+# Enterprise License
+license_path = "/opt/consul/consul.lic"
+
+# encrypt
+encrypt = "7w+zkhqa+YD4GSKXjRWETBIT8hs53Sr/w95oiVxq5Qc="
+
+# retry_join
+retry_join = ["server ip"]
+
+ca_file = "/opt/consul/consul-agent-ca.pem"
+cert_file = "/opt/consul/my-dc-client-consul-0.pem"
+key_file = "/opt/consul/my-dc-client-consul-0-key.pem"
+
+verify_incoming = false
+verify_incoming_rpc = false
+verify_outgoing = false
+verify_server_hostname = false
+
+ports {
+  http = 8500
+  dns = 8600
+  server = 8300
+}
+
+ + + diff --git a/04-HashiCorp/04-Consul/02-Configuration/common.html b/04-HashiCorp/04-Consul/02-Configuration/common.html new file mode 100644 index 0000000000..43a2d39ccf --- /dev/null +++ b/04-HashiCorp/04-Consul/02-Configuration/common.html @@ -0,0 +1,154 @@ + + + + + + + + + + Consul 공통 설정 | docmoa + + + + + +
본문으로 건너뛰기

Consul 공통 설정

1분 미만ConsulEnterpriseConfigurationCommon

Consul 공통 설정

최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server, client의 공통설정 파일입니다.
저는 agent.hcl파일안에 다 넣고 실행하지만 나눠서 추후에는 기능별로 나눠서 사용할 예정입니다.

#node name에는 _금지
+#node_name
+ 
+client_addr = "0.0.0.0"
+bind_addr = "{{ GetInterfaceIP `ens192` }}"
+advertise_addr = "{{ GetInterfaceIP `ens224` }}"
+ 
+#ipv4, ipv6를 나눠서 설정할 수 있음.
+#advertise_addr_ipv4
+#advertise_addr_ipv6
+ 
+ports {
+  #http = 8500
+  http = -1
+  dns = 8600
+  #https = -1
+  https = 8500
+  serf_lan = 8301
+  grpc = 8502
+  server = 8300
+}
+ 
+#gossip ip 지정
+#serf_lan
+#gossip 대역대 지정
+#serf_lan_allowed_cidrs
+ 
+#사용자 감사, 사용자가 consul에서 사용한 행동을 기록
+#audit {
+#  enabled = true
+#  sink "My sink" {
+#    type   = "file"
+#    format = "json"
+#    path   = "data/audit/audit.json"
+#    #consul의 감사작성방법 규칙, 현재는 best-effort만지원
+#    delivery_guarantee = "best-effort"
+#    rotate_duration = "24h"
+#    rotate_max_files = 15
+#    rotate_bytes = 25165824
+#  }
+#}
+ 
+#consul 서버관리 설정 변경
+#autopoilt {
+#    #새로운 서버가 클러스터에 추가될 때 죽은 서버 자동제거
+#    cleanup_dead_servers = ture
+#
+#    last_contact_threshold = 200ms
+#    #최소 quorm 수 지정
+#    min_quorum = ni
+#    #클러스터에 서버가 추가될 시 안정상태로 되어야 하는 최소 시간
+#    server_stabilization_time = 10s
+#}
+ 
+#동시에 처리할 수 있는 인증서 서명 요청 제한
+#csr_max_concurrent = 0
+#서버가 수락할 인증서 서명 요청(CSR)의 최대 수에 대한 속도 제한을 설정
+#csr_max_per_second = 50
+#클러스터에서 이전 루트 인증서를 교체할 때 사용
+#leaf_cert_ttl = 72h
+#CA 키 생성 타입
+#private_key_type = ec
+#CA 키 생성될 길이
+#private_key_bits = 256
+ 
+#서버에서만 client를 join할 수 있게 함
+#disable remote exec
+ 
+#enable syslog = true
+log_level = "DEBUG"
+data_dir = "/var/log/consul/consul"
+log_file = "/var/log/consul/consul.log"
+log_rotate_duration = "24h"
+log_rotate_bytes = 104857600
+log_rotate_max_files = 100
+ 
+license_path = "/opt/license/consul.license"
+ 
+acl {
+  enabled = true
+  default_policy = "allow"
+  enable_token_persistence = true
+ 
+  #acl policy ttl, 줄이면 새로고침 빈도 상승, 성능에 영향을 미칠 수 있음
+  #policy_ttl = 30s
+  #acl role ttl, 줄이면 새로고침 빈도 상승, 성능에 영향을 미칠 수 있음
+  #role_ttl = 30s
+}
+ 
+connect {
+  enabled = true
+  #vault 연동 옵션
+  #ca_provider
+}
+ 
+dns_config {
+  allow_stale = true,
+  max_stale = "87600h"
+}
+ 
+#block_endpoints할성화시 restapi 차단
+#http_config {
+#    block_endpoints = false
+#}
+ 
+#segments
+ 
+rpc {
+  enable_streaming = true
+}
+ 
+encrypt = "7VY2fVm0p6vJUYNS/oex/mr2e59dy4AaGMefTKtUGi0="
+encrypt_verify_incoming = false
+encrypt_verify_outgoing = false
+
+ + + diff --git a/04-HashiCorp/04-Consul/02-Configuration/index.html b/04-HashiCorp/04-Consul/02-Configuration/index.html new file mode 100644 index 0000000000..7ef4d618e5 --- /dev/null +++ b/04-HashiCorp/04-Consul/02-Configuration/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Configuration | docmoa + + + + + +
본문으로 건너뛰기

02 Configuration

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/02-Configuration/server.html b/04-HashiCorp/04-Consul/02-Configuration/server.html new file mode 100644 index 0000000000..886fa00eed --- /dev/null +++ b/04-HashiCorp/04-Consul/02-Configuration/server.html @@ -0,0 +1,132 @@ + + + + + + + + + + Consul 서버 설정 | docmoa + + + + + +
본문으로 건너뛰기

Consul 서버 설정

1분 미만ConsulEnterpriseConfigurationServer

Consul 서버 설정

최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server설정 파일입니다.
네트워크는 프라이빗(온프레이머스) 환경입니다.

#consul server 설정
+server = true
+ui_config {
+  enabled = true
+}
+bootstrap_expect = 3
+  
+license_path = "/opt/license/consul.license"
+ 
+retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
+ 
+performance {
+  raft_multiplier = 1
+}
+ 
+#raft protocal 버전, consul 업데이트 시 1씩 증가
+raft_protocol = 3
+ 
+#node가 완전히 삭제되는 시간
+reconnect_timeout = "72h"
+ 
+raft_snapshot_interval = "5s"
+ 
+#해당 서버를 non-voting server로 지정
+#read_replica = false
+ 
+limits {
+  http_max_conns_per_client = 200
+  rpc_handshake_timeout = "5s"
+}
+ 
+key_file = "/opt/ssl/consul/dc1-server-consul-0-key.pem"
+cert_file = "/opt/ssl/consul/dc1-server-consul-0.pem"
+ca_file = "/opt/ssl/consul/consul-agent-ca.pem"
+auto_encrypt {
+  allow_tls = true
+}
+ 
+verify_incoming = false,
+verify_incoming_rpc = true
+verify_outgoing = true
+verify_server_hostname = false
+

Consul 서버 최소 설정 (20220807기준)

최소한의 설정만 있는 consul 설정입니다.

data_dir = "/opt/consul"
+
+client_addr = "0.0.0.0"
+
+datacenter = "my-dc"
+
+#ui
+ui_config {
+  enabled = true
+}
+
+# server
+server = true
+
+# Bind addr
+bind_addr = "0.0.0.0" # Listen on all IPv4
+# Advertise addr - if you want to point clients to a different address than bind or LB.
+advertise_addr = "node ip"
+
+# Enterprise License
+license_path = "/opt/consul/consul.lic"
+
+# bootstrap_expect
+bootstrap_expect=1
+
+# encrypt
+encrypt = "7w+zkhqa+YD4GSKXjRWETBIT8hs53Sr/w95oiVxq5Qc="
+
+# retry_join
+retry_join = ["Server ip"]
+
+key_file = "/opt/consul/my-dc-server-consul-0-key.pem"
+cert_file = "/opt/consul/my-dc-server-consul-0.pem"
+ca_file = "/opt/consul/consul-agent-ca.pem"
+auto_encrypt {
+  allow_tls = true
+}
+
+verify_incoming = false
+verify_incoming_rpc = false
+verify_outgoing = false
+verify_server_hostname = false
+
+ports {
+  http = 8500
+  dns = 8600
+  server = 8300
+}
+
+
+
+ + + diff --git a/04-HashiCorp/04-Consul/03-UseCase/Consul Enterprise Feature.html b/04-HashiCorp/04-Consul/03-UseCase/Consul Enterprise Feature.html new file mode 100644 index 0000000000..8fc8b78514 --- /dev/null +++ b/04-HashiCorp/04-Consul/03-UseCase/Consul Enterprise Feature.html @@ -0,0 +1,424 @@ + + + + + + + + + + Consul Mesh Gateway - K8S x BMs/VMs | docmoa + + + + + +
본문으로 건너뛰기

Consul Mesh Gateway - K8S x BMs/VMs

약 8 분ConsulHybridKubetenetesk8sVM

Consul Mesh Gateway - K8S x BMs/VMs

이 문서에서는 Consul을 사용하여 상이한 두 Consul로 구성된 클러스터(마스터가 별개)의 서비스를 연계하는 방법을 설명합니다.

1. 개요

1.1 아키텍처

네트워크 영역이 분리되어있는 두 환경의 애플리케이션 서비스들을 Service Mesh로 구성하는 방법을 알아 봅니다. 이번 구성 예에서는 Kubernetes와 Baremetal(BM)이나 VirtualMachine(VM)에 Consul Cluster(Datacenter)를 구성하고 각 환경의 애플리케이션 서비스를 Mesh Gateway로 연계합니다.

Mesh Gateway를 사용하면 서로다른 클러스터간에 mTLS 환경의 통신과 서비스 간의 트래픽 통로를 단일화 하여 구성할 수 있습니다. 또한 mTLS내의 데이터가 Gateway에서 해동되지 않기 때문에 두 클러스터간 안전하게 데이터를 송수신 합니다.

Consul의 각 Cluster는 Datacenter라는 명칭으로 구분됩니다. 이번 구성에서는 Kubernetes의 Consul Datacenter가 Primary의 역할을 합니다.

HashiCorp General Presentation Template (KR) - Apr 2020 - Google Slides 2020-11-12 15-58-54
HashiCorp General Presentation Template (KR) - Apr 2020 - Google Slides 2020-11-12 15-58-54
  • 각 Application을 위한 Sidecar를 구성합니다.
  • Mesh Gateway를 구성하기 위해서는 모든 Sidecars는 Envoy 로 구성되어야 합니다.
  • Mesh Gateway를 구성하기 위해서는 Sidecar와 Consul이 TLS로 통신해야 합니다.

1.2 Port 구성 참고

Port 구성에 대한 문서는 다음을 참고합니다.
https://www.consul.io/docs/install/portsopen in new window

UseDefault PortsCLI
DNS: The DNS server (TCP and UDP)8600-dns-port
HTTP: The HTTP API (TCP Only)8500-http-port
HTTPS: The HTTPs APIdisabled (8501)*-https-port
gRPC: The gRPC APIdisabled (8502)*-grpc-port
LAN Serf: The Serf LAN port (TCP and UDP)8301-serf-lan-port
Wan Serf: The Serf WAN port (TCP and UDP)8302-sert-wan-port
server: Server RPC address (TCP Only)8300-server-port
Sidecar Proxy Min: Sidecar 서비스 등록에 사용되는 범위의 최소 포트21000Configration file
Sidecar Proxy Max: Sidecar 서비스 등록에 사용되는 범위의 최대 포트21255Configration file

Federation을 위한 포트로는

  • Consul Server & Agent
    • 8301 : Gassip 프로토콜로 서로간의 상태를 확인하는데 사용됩니다.
  • Consul Server
    • 8500 : Server의 API통신을 위한 포트입니다.
    • 8501 : Mesh Gateway와 TLS로 통신합니다.
    • 8300 : Agent가 서버와의 RPC통신을 합니다.
    • 8600 : Consul DNS를 위해 사용됩니다.
  • Consul Agent
    • 21000~21255 : Sidecar 서비스가 할당받는 포트 범위 입니다.

포트 구성설정 가이드open in new window는 다음과 같습니다.

ports {
+  dns = 8600
+  http = 8500
+  https = -1
+  grpc = -1
+  serf_lan = 8301
+  serf_wan = 8302
+  server = 8300
+  sidecar_min_port = 21000
+  sidecar_max_port = 21255
+  expose_min_port = 21500
+  expose_max_port = 21755
+}
+

2. Kubernetes상에 Consul설치

2.1 사전 준비 사항

2.2 설치

Kubernetes에서 Consul을 실행하는 권장 방법은 Helm 차트를 사용하는 것open in new window 입니다. Consul을 실행하는 데 필요한 모든 구성 요소를 설치하고 구성합니다. Helm 2를 사용하는 경우 Helm 2 설치 가이드open in new window 에 따라 Tiller를 설치해야합니다.

2.2.1 helm Repository 추가

HashiCorp helm Repository를 추가합니다.

$ helm repo add hashicorp https://helm.releases.hashicorp.com
+"hashicorp" has been added to your repositories
+

Consul 차트에 접근가능한지 확인합니다.

$ helm search repo hashicorp/consul
+NAME                CHART VERSION   APP VERSION DESCRIPTION
+hashicorp/consul    0.32.1          1.10.0       Official HashiCorp Consul Chart
+
CHART LIST

Consul 차트마다의 기본 매칭되는 버전정보는 다음과 같이 리스트로 확인 가능합니다.

$ helm search repo hashicorp/consul -l
+NAME            	CHART VERSION	APP VERSION	DESCRIPTION
+hashicorp/consul	0.32.1       	1.10.0     	Official HashiCorp Consul Chart
+hashicorp/consul	0.32.0       	1.10.0     	Official HashiCorp Consul Chart
+hashicorp/consul	0.31.1       	1.9.4      	Official HashiCorp Consul Chart
+hashicorp/consul	0.31.0       	1.9.4      	Official HashiCorp Consul Chart
+hashicorp/consul	0.30.0       	1.9.3      	Official HashiCorp Consul Chart
+hashicorp/consul	0.29.0       	1.9.2      	Official HashiCorp Consul Chart
+hashicorp/consul	0.28.0       	1.9.1      	Official HashiCorp Consul Chart
+hashicorp/consul	0.27.0       	1.9.0      	Official HashiCorp Consul Chart
+hashicorp/consul	0.26.0       	1.8.5      	Official HashiCorp Consul Chart
+hashicorp/consul	0.25.0       	1.8.4      	Official HashiCorp Consul Chart
+hashicorp/consul	0.24.1       	1.8.2      	Official HashiCorp Consul Chart
+hashicorp/consul	0.24.0       	1.8.1      	Official HashiCorp Consul Chart
+hashicorp/consul	0.23.1       	1.8.0      	Official HashiCorp Consul Chart
+hashicorp/consul	0.23.0       	1.8.0      	Official HashiCorp Consul Chart
+hashicorp/consul	0.22.0       	1.8.0      	Official HashiCorp Consul Chart
+hashicorp/consul	0.21.0       	1.7.3      	Official HashiCorp Consul Chart
+hashicorp/consul	0.20.1       	1.7.2      	Official HashiCorp Consul Chart
+

2.2.2 Gossip 프토토콜을 위한 Kubernetes Secret 생성

Kubernetes상에서 Consul Datacenter의 Gossip 프로토콜에서 사용할 키를 생성합니다. 미리 생성하여 값을 넣어도 되고, 생성시 값이 생성되도록 하여도 관계 없습니다.

$ kubectl create secret generic consul-gossip-encryption-key --from-literal=key=$(consul keygen)
+
+--- or ---
+
+$ consul keygen
+h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o=
+$ kubectl create secret generic consul-gossip-encryption-key --from-literal=key=h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o=
+

2.2.3 설치를 위한 사용자 지정 yaml 작성

Helm 차트로 설치할 때 기본 설정을 엎어쓰는 파일을 생성하여 원하는 구성으로 설치되도록 준비합니다. 각 구성에 대한 설정은 Helm Chart Configurationopen in new window 를 참고합니다.

# consul.yaml
+global:
+  name: consul
+  # 기본이미지(OSS 최신 버전)가 아닌 다른 버전의 컨테이너 이미지 또는 별도의 레지스트리를 사용하는 경우 명시합니다.
+  image: 'hashicorp/consul-enterprise:1.8.5-ent'
+  datacenter: 'tsis-k8s'
+  tls:
+    # Federation 구성을 위해서는 TLS가 반드시 활성화되어야 합니다.
+    enabled: true
+    verify: false
+    httpsOnly: false
+
+  federation:
+    enabled: true
+    # Kubernetes가 Primary Datacenter이기 때문에 이 환경에서 Federation을 위한 시크릿을 생성하도록 합니다.
+    # https://www.consul.io/docs/k8s/installation/multi-cluster/kubernetes#primary-datacenter
+    createFederationSecret: true
+  gossipEncryption:
+    # gossip프로토콜은 암호화되어야 하며, 해당 키는 미리 Kubernetes에 Secret으로 구성합니다.
+    secretName: consul-gossip-encryption-key
+    secretKey: key
+  enableConsulNamespaces: true
+server:
+  enterpriseLicense:
+    secretName: consul-enterprise-license-key
+    secretKey: key
+connectInject:
+  enabled: true
+  centralConfig:
+    enabled: true
+ui:
+  service:
+	  # UI에 접속을 위한 타입을 정의합니다.
+	  # 보안상의 이유로 LoadBalancer기본적으로 서비스를 통해 노출되지 않으므로 kubectl port-forward를 사용하거나
+    # NodePort로 UI에 접속하는 데 사용해야 합니다.
+    type: NodePort
+dns:
+  enabled: true
+meshGateway:
+  # 메시 게이트웨이는 데이터 센터 간의 게이트웨이입니다.
+  # 데이터 센터 간의 통신이 메시 게이트웨이를 통과하므로 Kubernetes에서 페더레이션을 위해 활성화되어야합니다.
+  enabled: true
+  service:
+    type: NodePort
+    nodePort: 31001
+
+# Ingress Gateway는 Kubernets로 요청되는 주요 관문을 Consul에서 설정하고 Service Mesh기능을 활성화 합니다.
+# 이번 시나리오에서는 필수 설정이 아닙니다.
+ingressGateways:
+  enabled: true
+  gateways:
+    - name: ingress-gateway
+      service:
+        type: NodePort
+        ports:
+          - port: 31000
+            nodePort: 31000
+

2.2.4 Consul Helm Chart 실행

Helm3을 사용하여 사용자 구성으로 Consul을 설치하려면 다음을 실행합니다. (사용자 구성 파일 : consul.yaml)

$ helm install consul hashicorp/consul -f consul.yaml --debug
+

설치가 완료되고 얼마안있어 Pod를 확인해보면 다음과 같이 확인 가능합니다.

$ kubectl get pods
+consul-consul-mesh-gateway-754fbc5575-d8dgt                       2/2     Running   0          2m
+consul-consul-mesh-gateway-754fbc5575-wkvjh                       2/2     Running   0          2m
+consul-consul-mh5h6                                               1/1     Running   0          2m
+consul-consul-mx4mn                                               1/1     Running   0          2m
+consul-consul-rlb5x                                               1/1     Running   0          2m
+consul-consul-server-0                                            1/1     Running   0          2m
+consul-consul-server-1                                            1/1     Running   0          2m
+consul-consul-server-2                                            1/1     Running   0          2m
+consul-consul-tbngg                                               1/1     Running   1          2m
+consul-consul-tz9ct                                               1/1     Running   0          2m
+
  • consul-server: 3중화되어 구성됩니다.
  • consul: Consul Client가 DaemonSet 형태로 모든 노드에 구성됩니다.
  • consul-mesh-gateway: 기본 2중화 설정으로, 2개 구성됩니다.

2.2.5 설치 후 추가 가이드(Option)

  • port-forward
    Consul UI에 접혹하기 위해 port-forward 를 사용하는 경우 다음과 같이 설정하여 접근가능합니다.

    # HTTP
    +$ kubectl port-forward service/consul-server 8500:8500
    +
    +# HTTPS (TLS)
    +$ kubectl port-forward service/consul-server 8501:8501
    +
  • ACL
    ACL이 활성화된 경우 ACL토큰이 필요합니다. 전체 권한이 있는 bootstrap토큰은 다음과 같이 확인할 수 있습니다. (값의 마지막 % 는 제외)

    $ kubectl get secrets/consul-bootstrap-acl-token --template={{.data.token}} | base64 -D
    +e7924dd1-dc3f-f644-da54-81a73ba0a178%
    +

2.2.6 테스트를 위한 Pod 생성(Option)

Kubertnetes상에서 Mesh Gateway를 사용하기 위한 설정을 확인할 수 있도록 테스트를 위한 Pod를 생성합니다.

APP SAMPLE
# k8s-consul-app.yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: counting
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: counting
+  annotations:
+    'consul.hashicorp.com/service-tags': servicemesh, consul, counting, v1
+    'consul.hashicorp.com/connect-inject': 'true'
+spec:
+  containers:
+    - name: counting
+      image: hashicorp/counting-service:0.0.2
+      ports:
+        - containerPort: 9001
+          name: http
+  serviceAccountName: counting
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: dashboard
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: dashboard
+  labels:
+    app: 'dashboard'
+  annotations:
+    'consul.hashicorp.com/service-tags': servicemesh, consul, dashiboard, v1
+    'consul.hashicorp.com/connect-inject': 'true'
+    'consul.hashicorp.com/connect-service-upstreams': 'counting:9001'
+spec:
+  containers:
+    - name: dashboard
+      image: hashicorp/dashboard-service:0.0.4
+      ports:
+        - containerPort: 9002
+          name: http
+      env:
+        - name: COUNTING_SERVICE_URL
+          value: 'http://localhost:9001'
+  serviceAccountName: dashboard
+---
+apiVersion: 'v1'
+kind: 'Service'
+metadata:
+  name: 'dashboard-service-nodeport'
+  namespace: 'default'
+  labels:
+    app: 'dashboard'
+spec:
+  ports:
+    - protocol: 'TCP'
+      port: 80
+      targetPort: 9002
+  selector:
+    app: 'dashboard'
+  type: 'NodePort'
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: dns
+spec:
+  containers:
+    - name: dns
+      image: anubhavmishra/tiny-tools
+      command:
+        - sleep
+        - infinity
+
  • 'consul.hashicorp.com/connect-inject': 'true' 해당 annotations가 선언되면 consul api를 통해 해당 Pod가 배포될 때 Sidecar가 함께 생성됩니다.
  • 'consul.hashicorp.com/connect-service-upstreams': 'counting:9001' 설정은 side가에 9001포트로 요청이 오면 counting 으로 정의된 서비스로 해당 요청을 전달합니다.
  • Consul 내부에서의 Service Mesh기능은 Consul의 Service Discovery 기능에 따라, Sidecar들이 서로를 찾을 수 있습니다.
  • 예제의 dashboard 앱은 frontend앱으로 UI를 제공하며, counting 앱은 backend앱으로 호출시 내부적으로 counting을 추가합니다.
  • dashboard앱은 백엔드로 localhost의 9001에 요청합니다. Pod 내의 container는 동일한 ip를 갖기 때문에 upstream 설정으로 9001에 대한 목적지를 알고 있는 sidecar container proxy가 해당 요청을 전달합니다.

3. VM/BM상에 Consul설치

3.1 사전 준비 사항

3.2 Consul 바이너리 다운로드와 PATH 설정

각 환경(Linux/Windows/Mac/FreeBSC/Solaris)에 맞는 바이너리를 받고 압축을 풉니다. consul 혹은 Windows의 경우 consul.exe를 시스템의 적절한 위치에 이동시키고 PATH에 추가시키면 어느곳에서든 접근할 수 있습니다.

3.2.1 Linux 기반 또는 Mac

쉘 설정 파일을 편집하여 PATH에 영구적으로 추가할 수 있습니다. 일반적으로 . + 쉘이름 + rc 로 구성되며 bash쉘을 사용하는 경우 ~/.bashrc 가 해당 파일입니다. 해당 파일에서 export PATH=으로 시작하는 :(콜론)으로 구분된 위치에 consul 바이너리 파일 위치를 넣어주거나 없는 경우 기존 PATH에 추가로 기입할 수 있습니다. /tools/consul_dir 디렉토리인경우 다음의 예와 같습니다.

...
+export PATH=$PATH:/tools/consul_dir
+

root 권한이 있다면 시스템의 기본 PATH로 지정되어있는 /usr/local/bin디렉토리에 consul을 복사하는 것도 하나의 방법이 될 수 있습니다.

3.2.2

Windows

시스템 설정에서 GUI를 통해 PATH를 추가합니다. 마우스 클릭으로 진행하는 경우 Windows 설정 > 시스템 > 정보 > 시스템 정보 > 고급 시스템 설정 > 고급 탭 > 환경 변수의 단계로 진행합니다. 작업표시줄의 검색창에서 고급 시스템 설정 을 검색하여 고급 탭 > 환경변수 로 이동할 수도 있습니다. 환경 변수 GUI에서 USER 또는 시스템 변수의 Path에 Consul 디렉토리 경로를 추가합니다.

DESKTOP-LenovoMini 2020-11-15 20-36-36
DESKTOP-LenovoMini 2020-11-15 20-36-36

3.3 Primary(k8s) 환경에서 인증서 가져오기

Federation Between VMs and Kubernetes : https://www.consul.io/docs/k8s/installation/multi-cluster/vms-and-kubernetesopen in new window

Kubernetes에 구성된 Consul Datacenter가 Primary이기 때문에 해당 환경에서 TLS 인증서를 가져옵니다. 앞서 구성된 Kubernetes 환경에서 CA(Certificate authority cert)와 서명 키(Certificate Authority signing key)를 가져옵니다.

$ kubectl get secrets/consul-ca-cert \
+  --template='{{index .data "tls.crt" }}' | base64 -d > consul-agent-ca.pem
+$ kubectl get secrets/consul-ca-key \
+  --template='{{index .data "tls.key" }}' | base64 -d > consul-agent-ca-key.pem
+

두 파일이 생성된 위치에서 consul tls명령을 사용하여 서버에서 사용할 인증서를 생성합니다.

$ consul tls cert create -server -dc=vm-dc
+==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
+==> Saved vm-dc-server-consul-0.pem
+==> Saved vm-dc-server-consul-0-key.pem
+

동일한 위치에서 Client를 위한 인증서를 생성합니다.

$ consul tls cert create -client -dc=vm-dc
+==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
+==> Saved vm-dc-client-consul-0.pem
+==> Saved vm-dc-client-consul-0-key.pem
+

CA 파일과 새로 생성한 파일을 Server와 Client 각 환경에 복사합니다. (e.g. /home/consul/consul-cert/vm-dc-server-consul-0.pem)

앞서 생성한 파일 이름을 기준으로 복사 대상은 다음과 같습니다.

  • Server
    • consul-agent-ca.pem
    • vm-dc-server-consul-0.pem
    • vm-dc-server-consul-0-key.pem
  • Client
    • consul-agent-ca.pem
    • vm-dc-client-consul-0.pem
    • vm-dc-client-consul-0-key.pem

3.4 Consul 구성 파일 작성

CLI를 활용하여 Consul을 구동할 때 구성 옵션을 사용하는 것도 가능하나 여기서는 구성 파일을 작성하여 Consul Server나 Consul Client가 기동할 수 있도록 합니다. Server와 Client에 대한 설정에 약간의 차이가 있을 뿐 대부분이 동일합니다.

3.4.1 Server

server = true
+ui = true
+bootstrap_expect = 3
+node_name = "consul_server_01"
+datacenter = "vm-dc"
+client_addr = "0.0.0.0"
+bind_addr = "192.168.100.51"
+encrypt = "h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o="
+data_dir = "/var/lib/consul"
+retry_join = ["192.168.100.51","192.168.100.52","192.168.100.83"]
+ports {
+  https = 8501
+  http = 8500
+  grpc = 8502
+}
+enable_central_service_config = true
+connect {
+  enabled = true
+  enable_mesh_gateway_wan_federation = true
+}
+primary_datacenter = "k8s-dc"
+primary_gateways = ["172.16.1.111:31001","172.16.1.116:31001"]
+cert_file = "/root/consul-cert/vm-dc-server-consul-0.pem"
+key_file = "/root/consul-cert/vm-dc-server-consul-0-key.pem"
+ca_file = "/root/consul-cert/consul-agent-ca.pem"
+
  • server : server로 구성되는 Consul의 경우에 true로 설정합니다.

  • node_name, bind_addr는 각 Server에 맞게 구성합니다. 여기서는 3개의 서버로 구성하였습니다.

  • encrypt : consul keygen을 생성한 값입니다. Server와 Client모두 동일한 값을 설정합니다.

  • data_dir : Consul의 데이터를 저장할 경로이며 미리 생성해야 합니다.

  • retry_join : Consul 서버의 IP를 기입합니다.

  • ports: Mesh Gateway구성을 위해 https를 활성화 합니다.

  • enable_central_service_config : federation 구성을 위해 true 로 설정합니다.

  • connect : Service Mesh 구성 활성화를 위해 구성합니다. enable_mesh_gateway_wan_federation는 Federation에서 Mesh Gateway를 활성화 시켜줍니다.

  • primary_datacenter : kubernetes 환경의 Datacenter이름을 기입합니다.

  • primary_gateways : Kubernetes 환경의 Mesh Gateway 의 IP와 Port를 기입합니다. 여기 예제에서는 Nodeport로 구성된 Consul Mesh Gateway의 값이 확인됩니다.

    $ kubectl exec statefulset/consul-server -- sh -c   'curl -sk https://localhost:8501/v1/catalog/service/mesh-gateway | jq ".[].ServiceTaggedAddresses.wan"'
    +{
    +  "Address": "172.16.1.111",
    +  "Port": 31001
    +}
    +{
    +  "Address": "172.16.1.116",
    +  "Port": 31001
    +}
    +
  • cert_file / key_file / ca_file : 앞서 생성한 Server 인증서들의 경로와 파일명을 기입합니다.

3.4.2 Client

node_name = "consul_client_01"
+datacenter = "vm-dc"
+client_addr = "0.0.0.0"
+bind_addr = "192.168.100.54"
+encrypt = "h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o="
+data_dir = "/var/lib/consul"
+retry_join = ["192.168.100.51","192.168.100.52","192.168.100.53"]
+cert_file = "/root/consul-cert/vm-dc-client-consul-0.pem"
+key_file = "/root/consul-cert/vm-dc-client-consul-0-key.pem"
+ca_file = "/root/consul-cert/consul-agent-ca.pem"
+
  • node_name, bind_addr는 각 Client 맞게 구성합니다.
  • data_dir : Consul의 데이터를 저장할 경로이며 미리 생성해야 합니다.
  • retry_join : Consul 서버의 IP를 기입합니다.
  • cert_file / key_file / ca_file : 앞서 생성한 Client 인증서들의 경로와 파일명을 기입합니다.

3.4.3 Consul 서비스 구성 (Option)

Linux 환경이나 Windows환경에서 서비스로 구성하면 시스템 부팅 시 자동으로 시작할 수 있기 때문에 선호되는 설치 방식 중 하나입니다. 이미 설치된 상태라면 앞서 구성을 변경하고 consul reload를 사용하여 구성을 다시 읽어오거나 리스타트 합니다.

LINUX

/etc/systemd/system/consul.service에 다음의 서비스 파일을 작성합니다. 필요에 따라 User와 Group을 추가하여 구성하는 것도 가능합니다. 여기서는 consul User를 구성하여 사용하였습니다.

[Unit]
+Description=Consul Service Discovery Agent
+Documentation=https://www.consul.io/
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=consul
+Group=consul
+ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d
+
+ExecReload=/bin/kill -HUP $MAINPID
+KillSignal=SIGINT
+TimeoutStopSec=5
+Restart=on-failure
+SyslogIdentifier=consul
+
+[Install]
+WantedBy=multi-user.target
+

등록된 서비스를 활성화 하고 시작하여 상대를 확인합니다.

$ systemctl enable consul
+$ systemctl start consul
+$ systemctl status consul
+● consul.service - Consul Service Discovery Agent
+   Loaded: loaded (/etc/systemd/system/consul.service; enabled; vendor preset: disabled)
+   Active: active (running) since 토 2020-11-14 19:05:39 UTC; 17h ago
+     Docs: https://www.consul.io/
+ Main PID: 1020 (consul)
+   CGroup: /system.slice/consul.service
+           └─1020 /usr/local/bin/consul agent -config-dir=/etc/consul.d
+           
+$ journalctl -u consul -f
+1115 13:06:12 cl01-consul-vault-0 consul[1020]: 2020-11-15T13:06:12.265Z [INFO]  agent.server: Handled event for server in area: event=member-join server=consul-consul-server-1.tsis-k8s area=wan
+1115 13:06:18 cl01-consul-vault-0 consul[1020]: 2020-11-15T13:06:18.119Z [INFO]  agent.server.memberlist.wan: memberlist: Suspect consul-consul-server-0.tsis-k8s has failed, no acks received
+
WINDOWS

Powershell을 활용하여 서비스를 구성합니다.

> sc.exe create "Consul" binPath= "consul agent -config-dir=C:\ProgramData\consul\config" start= auto
+[SC] CreateService SUCCESS
+
+> sc.exe start "Consul"
+
+SERVICE_NAME: Consul
+        TYPE               : 10  WIN32_OWN_PROCESS
+        STATE              : 4  RUNNING (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
+        WIN32_EXIT_CODE    : 0  (0x0)
+        SERVICE_EXIT_CODE  : 0  (0x0)
+        CHECKPOINT         : 0x0
+        WAIT_HINT          : 0x0
+        PID                : 8008
+        FLAGS              :
+

3.4.4 Federation 확인 (Option)

Secondary Datacenter인 BM/VM 환경에서 primary_datacenter를 지정하였기 때문에 기동 후 Kubernetes의 Consul과 Join되어 Federation이 구성됩니다.

Consul-Federation
Consul-Federation

4. BM/VM 환경의 Mesh Gateway 구성

Mesh Gateway를 구성하여 Service Mesh 환경이 멀티/하이브리드 Datacenter 환경을 지원하도록 합니다. Mesh Gateway는 Consul의 내장 Proxy로는 동작하지 못하므로 Envoy 를 설치하여 이를 활용합니다.

4.1 Envoy 설치

Consul의 각 버전별 지원하는 Envoy 버전은 다음 표와 같습니다.

Consul VersionCompatible Envoy Versions
1.10.x1.18.3, 1.17.3, 1.16.4, 1.15.5
1.9.x1.16.0, 1.15.2, 1.14.5‡, 1.13.6‡
1.8.x1.14.5, 1.13.6, 1.12.7, 1.11.2
1.7.x1.13.6, 1.12.7, 1.11.2, 1.10.0*
1.6.x, 1.5.3, 1.5.21.11.1, 1.10.0, 1.9.1, 1.8.0†
1.5.1, 1.5.01.9.1, 1.8.0†
1.4.x, 1.3.x1.9.1, 1.8.0†, 1.7.0†

경고

‡ Consul 1.9.x는 1.15.0+의 Envoy를 권장합니다.
† 1.9.1 버전 이하의 Envoy는 CVE-2019-9900open in new window, CVE-2019-9901open in new window 취약점이 보고되었습니다.
* Consul 1.7.x에서 Envoy 1.10.0을 사용하는 경우 consul connect envoy 커맨드 사용시 -envoy-version 옵션을 포함해야합니다.

Envoy 웹사이트open in new window 에서 직접 Envoy의 컨테이너 기반 빌드를 얻거나 func-e.ioopen in new window 와 같은 3rd party 프로젝트에서 Envoy 바이너리 빌드 패키지를 얻을 수 있습니다.

다음 명령을 실행하여 Envoy를 가져와 설치하는 func-e 유틸리티를 다운로드하고 설치합니다.

curl -L https://func-e.io/install.sh | bash -s -- -b /usr/local/bin
+

다음과 같이 대상 환경을 지정할 수 있습니다.

export FUNC_E_PLATFORM=darwin/amd64
+
Go (Golang) GOOS and GOARCH

A list of GOOS/GOARCH supported by go out of the box

  • aix/ppc64
  • darwin/386
  • darwin/amd64
  • dragonfly/amd64
  • freebsd/386
  • freebsd/amd64
  • freebsd/arm
  • freebsd/arm64
  • illumos/amd64
  • js/wasm
  • linux/386
  • linux/amd64
  • linux/arm
  • linux/arm64
  • linux/ppc64
  • linux/ppc64le
  • linux/mips
  • linux/mipsle
  • linux/mips64
  • linux/mips64le
  • linux/riscv64
  • linux/s390x
  • netbsd/386
  • netbsd/amd64
  • netbsd/arm
  • netbsd/arm64
  • openbsd/386
  • openbsd/amd64
  • openbsd/arm
  • openbsd/arm64
  • plan9/386
  • plan9/amd64
  • plan9/arm
  • solaris/amd64
  • windows/386
  • windows/amd64
  • windows/arm

A list of 32-bit GOOS/GOARCH supported by go out of the box

  • darwin/386
  • freebsd/386
  • freebsd/arm
  • linux/386
  • linux/arm
  • linux/mips
  • linux/mipsle
  • netbsd/386
  • netbsd/arm
  • openbsd/386
  • openbsd/arm
  • plan9/386
  • plan9/arm
  • windows/386
  • windows/arm

A list of 64-bit GOOS/GOARCH supported by go out of the box

  • aix/ppc64
  • darwin/amd64
  • dragonfly/amd64
  • freebsd/amd64
  • freebsd/arm64
  • illumos/amd64
  • js/wasm
  • linux/amd64
  • linux/arm64
  • linux/ppc64
  • linux/ppc64le
  • linux/mips64
  • linux/mips64le
  • linux/riscv64
  • linux/s390x
  • netbsd/amd64
  • netbsd/arm64
  • openbsd/amd64
  • openbsd/arm64
  • plan9/amd64
  • solaris/amd64
  • windows/amd64

특정 버전을 명시하여 다운로드 하려면 다음 명령을 실행합니다.

func-e use 1.18.3
+

Envoy 바이너리를 $PATH의 위치에 복사합니다. 이를 통해 Consul은 바이너리 위치를 지정하지 않고 Envoy를 자동으로 시작할 수 있습니다.

sudo cp ~/.func-e/versions/1.18.3/bin/envoy /usr/local/bin/
+

다음 명령을 실행하여 Envoy가 $PATH에 있는지 확인합니다.

envoy --version
+

4.2 Mesh Gateway 실행

Mesh Gateway는 TLS를 필요로하며 Consul과도 TLS로 통신 합니다. 따라서 Consul로의 기본 접속 방식과 포트를 SSL기준으로 설정하여 실행합니다. 또한 앞서 Consul Client를 위해 생성한 인증서를 활용합니다.

$ export CONSUL_HTTP_SSL=true
+$ export CONSUL_HTTP_ADDR=https://127.0.0.1:8501
+$ consul connect envoy -gateway=mesh -register -expose-servers \
+  -service "mesh-gateway-secondary" \
+  -ca-file=/root/consul-cert/consul-agent-ca.pem \
+  -client-cert=/root/consul-cert/vm-dc-client-consul-0.pem \
+  -client-key=/root/consul-cert/vm-dc-client-consul-0-key.pem \
+  -address '{{ GetInterfaceIP "lo" }}:9100' \
+  -wan-address '{{ GetInterfaceIP "eth0" }}:9100' -admin-bind=127.0.0.1:19001 &
+
  • service : Consul에 등록되는 Mesh Gateway의 서비스 이름 입니다.
  • GetInterfaceIP : Consul에서 사용하는 템플릿 값입니다. 이렇게 작성하면 Network설정에서 해당 인터페이스에 지정된 IP를 받아올 수 있습니다.
  • admin-bind : Envoy의 관리자 바인딩을 설정합니다. 동일한 호스트에서 여러개의 Envoy를 실행하는 경우 Admin Port가 중복될 수 있습니다. 기본값은 19000 입니다.
  • 백그라운드 실행을 위해 끝에 &를 붙였습니다. 원하지 않으시면 제거하여 포그라운드로 띄우셔도 됩니다.

실행 후에는 Consul UI에서도 해당 Mesh Gateway를 확인할 수 있습니다.

5 TEST (Option)

Frontend 애플리케이션을 BM/VM 환경에 구성하고 Backend를 Kubernetes에 구성하는 예제입니다.
https://github.com/hashicorp/demo-consul-101open in new window

FOR TEST

5.1 Counting Service(Backend) on Kubernetes

앞서 2.2.6 테스트를 위한 Pod 생성의 counting 서비스를 활용합니다.

apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: counting
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: counting
+  annotations:
+    'consul.hashicorp.com/service-tags': servicemesh, consul, counting, v1
+    'consul.hashicorp.com/connect-inject': 'true'
+spec:
+  containers:
+    - name: counting
+      image: hashicorp/counting-service:0.0.2
+      ports:
+        - containerPort: 9001
+          name: http
+  serviceAccountName: counting
+

5.2 DashBoard Service(Frontend) on BM/VM

BM/VM 환경에서 Frontend 애플리케이션을 구성합니다. Envoy Proxy를 Sidecar로 구성하여 Service Mesh를 위한 구성을 하고, 다른 Consul 데이터센터에 있는 서비스를 찾을 수 있도록 합니다.

https://github.com/hashicorp/demo-consul-101/tree/master/services/dashboard-serviceopen in new window

애플리케이션 실행을 위해서는 golang이 설치되어야 합니다.

Dashboard 애플리케이션을 실행합니다.

$ PORT=9003 COUNTING_SERVICE_URL="http://localhost:5000" go run main.go &
+
  • PORT : Dashboard 애플리케이션에서 사용하는 포트를 정의합니다.
  • COUNTING_SERVICE_URL : Backend 애플리케이션인 Counting Service에 대한 정보입니다. 없는 경우 기본 값은 "http://localhost:9001open in new window" 입니다.

9003으로 실행된 Dashboard 애플리케이션을 Consul에 서비스로 등록합니다. Consul의 기본 Configuration 디렉토리 위치에 해당 서비스 구성을 작성하고 읽어올 수 있습니다. (e.g. /etc/consul.d)

# /etc/consul.d/dashboard.hcl
+service {
+  name = "dashboard-vm"
+  port = 9003
+
+  connect {
+    sidecar_service {
+      proxy {
+        upstreams = [
+          {
+            destination_name = "counting"
+            datacenter = "k8s-dc"
+            local_bind_port = 5000
+            mesh_gateway {
+              mode = "local"
+            }
+          }
+        ]
+      }
+    }
+  }
+
+  check {
+    id       = "dashboard-check"
+    http     = "http://localhost:9003/health"
+    method   = "GET"
+    interval = "1s"
+    timeout  = "1s"
+  }
+}
+
  • connect에 Service Mesh 설정을 추가합니다.
    • destination_name : local_bind_port로 오는 요청을 어떤 서비스로 보낼지 정의합니다.
    • datacenter : 데이터센터 이름이 해당 서비스가 속한 데이터센터 이름과 다르면 외부 Mesh Gateway를 찾고, Federation 된 해당 Mesh Gateway로 요청을 전송합니다.

Dashboard 애플리케이션에서 COUNTING_SERVICE_URL의 대상을 5000번 포트로 지정하였기 때문에 upstream에서 바인딩되는 포트를 맞춰줍니다. 구성이 완료되면 consul reload 명령을 통해 구성 디렉토리의 파일을 반영하고 추가된 서비스를 확인합니다.

$ consul reload
+Configuration reload triggered
+
+$ consul catalog services
+consul
+dashboard-vm
+

다음으로 Dashboard 서비스를 위한 Sidecar를 실행하고 추가된 서비스를 확인합니다.

$ consul connect envoy -sidecar-for dashboard-vm \
+  -ca-file=/root/consul-cert/consul-agent-ca.pem \
+  -client-cert=/root/consul-cert/vm-dc-client-consul-0.pem \
+  -client-key=/root/consul-cert/vm-dc-client-consul-0-key.pem &
+  
+$ consul catalog services
+consul
+dashboard-vm
+dashboard-vm-sidecar-proxy
+

이제 구성된 9003번 포트를 통해 Frontend에서 외부 데이터센터의 Backend로 요청이 되는지 확인합니다.

Dashiboard
Dashiboard

5.3 Intention

Sidecar기능이 활성화 되면서 Consul의 Intention기능을 사용할 수 있습니다. Intention을 통해 동적으로 서비스에 대한 트래픽을 통제할 수 있습니다.

UI 또는 CLI를 통해 dashboard-vmcounting에 접근할 수 없도록 정의합니다.

$ consul intention create -deny -replace dashboard-vm counting
+Created: dashboard-vm => counting (deny)
+
Edit Intention - Consul 2020-11-16 00-27-50
Edit Intention - Consul 2020-11-16 00-27-50

접근할 수 없게 설정되었기 때문에 Sidecar에 주입된 설정으로 Dashboard에서는 Counting서비스에 접근할 수 없다는 메시지를 출력합니다.

Dashboard-intention
Dashboard-intention
+ + + diff --git a/04-HashiCorp/04-Consul/03-UseCase/Consul Health Check.html b/04-HashiCorp/04-Consul/03-UseCase/Consul Health Check.html new file mode 100644 index 0000000000..8ce86bfcfd --- /dev/null +++ b/04-HashiCorp/04-Consul/03-UseCase/Consul Health Check.html @@ -0,0 +1,57 @@ + + + + + + + + + + Consul Health Check on VMs | docmoa + + + + + +
본문으로 건너뛰기

Consul Health Check on VMs

euimokna1분 미만Consul

Consul Health Check on VMs

https://www.consul.io/docs/discovery/servicesopen in new window
https://learn.hashicorp.com/tutorials/consul/service-registration-health-checks?in=consul/developer-discovery#tuning-scripts-to-be-compatible-with-consulopen in new window

1. 스트립트 Health Check를 위한 설정

Consul config 디렉토리 하위에 monitor.hcl파일을 만듭니다.

services {
+  id = "web-service"
+  namd = "web-service"
+  address = "10.10.10.201"
+  port = 8080
+  checks = [
+    {
+      script = "/opt/consul/script/ps-check.sh"
+      interval = "180s"
+    }
+  ]
+}
+

2. 스크립트 Health Check시 Consul설정

Consul에서 스크립트 기반의 설정시 Config파일 내에 하기와 같은 옵션이 추가되어야 합니다.

(기존설정)
+.....
+enable_script_checks = "true" 또는 
+enable_local_script_checks = "true"
+
+

참고 Url : https://www.consul.io/docs/agent/options#_enable_script_checksopen in new window

+ + + diff --git a/04-HashiCorp/04-Consul/03-UseCase/index.html b/04-HashiCorp/04-Consul/03-UseCase/index.html new file mode 100644 index 0000000000..c2bde284ef --- /dev/null +++ b/04-HashiCorp/04-Consul/03-UseCase/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 03 Use Case | docmoa + + + + + +
본문으로 건너뛰기

03 Use Case

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Enterprise Feature.html b/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Enterprise Feature.html new file mode 100644 index 0000000000..8238584d9c --- /dev/null +++ b/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Enterprise Feature.html @@ -0,0 +1,54 @@ + + + + + + + + + + Identifying consul split-brain | docmoa + + + + + +
본문으로 건너뛰기

Identifying consul split-brain

1분 미만Consul

Identifying consul split-brain

https://support.hashicorp.com/hc/en-us/articles/360058026733-Identifying-and-Recovering-from-a-Consul-Split-Brainopen in new window

  1. 다음을 실행하여 각 서버에서 Consul 서버 로그를 확인
consul monitor -log-level=debug
+
  1. Server들이 투표하는 로그 라인을 확인합니다. 다음과 같이 보여야 합니다.:

==정상적인 경우

2020/10/19 16:21:23 [INFO] raft: Node at 10.90.168.42:8300 [Candidate] entering Candidate state in term 3732
+2020/10/19 16:21:23 [DEBUG] raft: Votes needed: 2
+2020/10/19 16:21:23 [DEBUG] raft: Vote granted from foobar in term 3732. Tally: 1
+

== 비 정상적인 경우

2020/10/19 16:28:53 [WARN] raft: Election timeout reached, restarting election
+2020/10/19 16:28:53 [INFO] raft: Node at 00.00.000.00:8300 [Candidate] entering Candidate state in term 992
+2020/10/19 16:28:53 [DEBUG] raft: Votes needed: 2
+2020/10/19 16:28:53 [DEBUG] raft: Vote granted from foobar2 in term 992. Tally: 1
+2020/10/19 16:28:53 [ERR] raft: Failed to make RequestVote RPC to {Voter <Voter ID>)
+
복구 시 정상화된 로그
+020/10/19 16:29:04 [WARN] raft: Election timeout reached, restarting election
+2020/10/19 16:29:04 [INFO] raft: Node at 00.00.000.00:8300 [Candidate] entering Candidate state in term 989
+2020/10/19 16:29:04 [DEBUG] raft: Votes needed: 2
+2020/10/19 16:29:04 [DEBUG] raft: Vote granted from <ID> in term 989. Tally: 1
+
+ + + diff --git a/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Install.html b/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Install.html new file mode 100644 index 0000000000..02ebe61678 --- /dev/null +++ b/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Install.html @@ -0,0 +1,44 @@ + + + + + + + + + + Consul yum install issue | docmoa + + + + + +
본문으로 건너뛰기

Consul yum install issue

1분 미만Consulinstall

Consul yum install issue

AmazonLinux 환경에서 하기와 같은 명령어로 consul 설치 후 systemd 를 통한 Consul 시작시 오류 발생

sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
+sudo yum -y install consul
+

Consul 시작시 오류로그

[ec2-user@ip-10-0-10-35 ~]$ sudo systemctl start consul
+Job for consul.service failed because a configured resource limit was exceeded. See "systemctl status consul.service" and "journalctl -xe" for details.
+

원인 및 해결방안

yum 명령어로 consul설치시 /etc/consul.d/ 경로에 기본적으로 consul.env 파일이 자동으로 생성되는데 해당 파일이 생성되지 않아 수동으로 생성함.

+ + + diff --git a/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Sidecar Inject not working on k8s.html b/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Sidecar Inject not working on k8s.html new file mode 100644 index 0000000000..935280f67e --- /dev/null +++ b/04-HashiCorp/04-Consul/04-TroubleShooting/Consul Sidecar Inject not working on k8s.html @@ -0,0 +1,88 @@ + + + + + + + + + + Consul Sidecar Inject not working on K8S | docmoa + + + + + +
본문으로 건너뛰기

Consul Sidecar Inject not working on K8S

1분 미만ConsulServiceMeshSideCarKubernetesK8S

Consul Sidecar Inject not working on K8S

Consul Version : 1.9.x
Helm Chart : 0.30.0

Consul을 쿠버네티스 상에 구성하게 되면 annotation 구성만으로도 쉽게 Sidecar를 애플리케이션과 함께 배포 가능하다.

참고 : Controlling Injection Via Annotationopen in new window

annotations:
+  'consul.hashicorp.com/connect-inject': 'true'
+

Consul Sidecar가 Pod 배포시 함께 구성되야 하는 것이 정상이나, Sidecar의 생성 실패나 이미지 가져오기 실패라는 언급도 없이 Sidecar의 injection이 동작하지 않는 경우가 있다.

Log 확인

쿠버네티스 상의 Consul을 구성하게 되면 injector가 Sidecar를 함께 배포하는 작업을 수행하므로 먼저 해당 컴포넌트의 로그를 확인한다.

kubectl logs -n consul -l component=connect-injector -f
+

Kubernetes API 확인

annotation의 동작은 쿠버네티스 컨트롤 플래인, 즉, 쿠버네티스의 API를 통해 요청되므로 해당 API를 통해 Consul에 접근이 가능한지 확인이 필요하다.
consul-inject에서 kubernetest api 접속이 불가하다면 500에러가 발생한다.

  1. api 접속을 위한 proxy를 활성화
    $ kubectl proxy
    +Starting to serve on 127.0.0.1:8001
    +
  2. 다른 터미널에서 API로 확인
    • 정상인 경우
    $ curl -vv localhost:8001/api/v1/namespaces/consul/services/https:consul-connect-injector-svc:443/proxy/health/ready
    +*   Trying 127.0.0.1...
    +* TCP_NODELAY set
    +* Connected to localhost (127.0.0.1) port 8001 (#0)
    +> GET /api/v1/namespaces/consul/services/https:consul-connect-injector-svc:443/proxy/health/ready HTTP/1.1
    +> Host: localhost:8001
    +> User-Agent: curl/7.61.1
    +> Accept: */*
    +>
    +< HTTP/1.1 204 No Content
    +< Audit-Id: 52947d1d-0c90-47eb-8dc2-6c2efa0193fa
    +< Cache-Control: no-cache, private
    +< Date: Fri, 06 Aug 2021 10:15:21 GMT
    +<
    +* Connection #0 to host localhost left intact
    +
    • 쿠버네티스 API 접근이 안되는 경우
    *   Trying 127.0.0.1...                                      
    +* TCP_NODELAY set                                            
    +* Connected to localhost (127.0.0.1) port 8001 (#0)          
    +> GET /api/v1/namespaces/consul/services/https:consul-connect
    +-injector-svc:443/proxy/health/ready HTTP/1.1                
    +> Host: localhost:8001                                       
    +> User-Agent: curl/7.61.1                                    
    +> Accept: */*                                                
    +>                                                            
    +< HTTP/1.1 500 Internal Server Error                         
    +< Audit-Id: acb30d91-d8db-463e-a91e-1e2a5382329e             
    +< Cache-Control: no-cache, private                           
    +< Content-Length: 178                                        
    +< Content-Type: application/json
    +< Date: Fri, 06 Aug 2021 11:04:38 GMT                        
    +<                                                            
    +{                                                            
    +  "kind": "Status",
    +  "apiVersion": "v1",                                        
    +  "metadata": {                                              
    +                                                            
    +  },                                                         
    +  "status": "Failure",                                       
    +  "message": "error trying to reach service: Address is not a
    +  llowed",                                                     
    +  "code": 500
    +}
    +* Connection #0 to host localhost left intact
    +
+ + + diff --git a/04-HashiCorp/04-Consul/04-TroubleShooting/connection-termination.html b/04-HashiCorp/04-Consul/04-TroubleShooting/connection-termination.html new file mode 100644 index 0000000000..1999d518d8 --- /dev/null +++ b/04-HashiCorp/04-Consul/04-TroubleShooting/connection-termination.html @@ -0,0 +1,42 @@ + + + + + + + + + + Connection termination | docmoa + + + + + +
본문으로 건너뛰기

Connection termination

1분 미만ConsulServiceMeshSideCarKubernetesK8S

Connection termination

로그

[debug] [router] upstream reset: reset reason: connection termination, transport failure reason.
+[debug] [http] Sending local reply with details upstream_reset_before_response_started(connection termination).
+

현상

  • upstream_reset_before_response_started : The upstream connection was reset before a response was started This may include further details about the cause of the disconnect.

connection termination

  • 원인 1: Envoy에서 TCP 연결(FIN)이 닫는 현상 보고됨 - Keepalive time 이슈

  • 해결 1 - 1: Keepalive time을 끄거나

  • 해결 1 - 2: max_requests_per_connection 을 1로 설정

  • 해결 1 - 3: Keepalive interval을 짧게 (10초)

  • 원인 2 : Kubernetes 내부 기본 TCP 계층 4 연결 부하 분산의 제한, (e.g. tomcat - maxKeepAliveRequests)

  • 해결 2 - 1: 애플리케이션의 KeepAlive를 끔

  • 원인 3 : 외부 서비스에 대해 요청시 Envoy Proxy는 애플리케이션이 연결을 닫으려 하지 않는 한 영구적으로 연결을 닫지 않으므로 신규요청 발생시 IPSET이 만료되는 (1시간) 시간이 지나 DNS 확인 없이 동일한 IP로 요청하는 경우

  • 해결 3 - 1: Keepalive time을 끄거나

  • 해결 3 - 2: 시간 초과 값을 늘임

  • 해결 3 - 3: dns_refresh_rate 간격을 짧게 (300초)

+ + + diff --git a/04-HashiCorp/04-Consul/04-TroubleShooting/index.html b/04-HashiCorp/04-Consul/04-TroubleShooting/index.html new file mode 100644 index 0000000000..45f1d012f7 --- /dev/null +++ b/04-HashiCorp/04-Consul/04-TroubleShooting/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 04 Trouble Shooting | docmoa + + + + + +
본문으로 건너뛰기

04 Trouble Shooting

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/05-Template_Sample/index.html b/04-HashiCorp/04-Consul/05-Template_Sample/index.html new file mode 100644 index 0000000000..698e002e84 --- /dev/null +++ b/04-HashiCorp/04-Consul/05-Template_Sample/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Template Sample | docmoa + + + + + +
본문으로 건너뛰기

05 Template Sample

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/05-Template_Sample/kv-sample.html b/04-HashiCorp/04-Consul/05-Template_Sample/kv-sample.html new file mode 100644 index 0000000000..bc2c2ca4e3 --- /dev/null +++ b/04-HashiCorp/04-Consul/05-Template_Sample/kv-sample.html @@ -0,0 +1,69 @@ + + + + + + + + + + KV Sample | docmoa + + + + + +
본문으로 건너뛰기

KV Sample

1분 미만ConsulConsul Template

KV Sample

참고 : https://learn.hashicorp.com/tutorials/consul/consul-templateopen in new window

템플릿 파일 변환 하기

템플릿 파일 작성

  • 대상 kv : apache/version
# apache_install.sh.ctmpl
+#!/bin/bash
+sudo apt-get remove -y apache2
+sudo apt-get install -y apache2={{ key "/apache/version" }}
+

consul에 KV추가

consul kv put apache/version 2.2.14-5ubuntu8.7

실행

$ consul-template -template="./apache_install.sh.ctmpl:./apache_install.sh" -once
+

파일 구조

.
+├── apache_install.sh.ctmpl
+└── `apache_install.sh`
+

내용 확인

#!/bin/bash
+sudo apt-get remove -y apache2
+sudo apt-get install -y apache2=2.2.14-5ubuntu8.7
+

Config 활용

CLI Inline의 옵션을 정의하는 config 작성

# consul-template-apache-install.hcl
+consul {
+  address = "localhost:8500"
+
+  retry {
+    enabled  = true
+    attempts = 12
+    backoff  = "250ms"
+  }
+}
+template {
+  source      = "./apache_install.sh.ctmpl"
+  destination = "./apache_install.sh"
+  perms       = 0644
+  command     = "echo './apache_install.sh'"
+}
+

실행

$ consul-template -config=consul-template-apache-install.hcl
+apache_install.sh
+
+ + + diff --git a/04-HashiCorp/04-Consul/05-Template_Sample/nginx.html b/04-HashiCorp/04-Consul/05-Template_Sample/nginx.html new file mode 100644 index 0000000000..3540b41ca7 --- /dev/null +++ b/04-HashiCorp/04-Consul/05-Template_Sample/nginx.html @@ -0,0 +1,81 @@ + + + + + + + + + + NGINX Sample | docmoa + + + + + +
본문으로 건너뛰기

NGINX Sample

1분 미만ConsulConsul TemplateNGINX

NGINX Sample

참고 : https://learn.hashicorp.com/tutorials/consul/load-balancing-nginxopen in new window

템플릿 파일 변환 하기

템플릿 파일 작성

  • 대상 서비스 : nginx-backend
# nginx.conf.ctmpl
+upstream backend {
+  {{- range service "nginx-backend" }}
+  server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
+  {{else}}server 127.0.0.1:65535; # force a 502
+  {{- end}}
+}
+
+server {
+  listen 80 default_server;
+
+  location /stub_status {
+    stub_status;
+  }
+
+  location / {
+    proxy_pass http://backend;
+  }
+}
+

실행

$ consul-template -template="./nginx.conf.ctmpl:./nginx.conf"
+

파일 구조

.
+├── nginx.conf.ctmpl
+└── `nginx.conf`
+

Config 활용

CLI Inline의 옵션을 정의하는 config 작성

# consul-template-nginx.hcl
+consul {
+  address = "localhost:8500"
+
+  retry {
+    enabled  = true
+    attempts = 12
+    backoff  = "250ms"
+  }
+}
+template {
+  source      = "./nginx.conf.ctmpl"
+  destination = "./nginx.conf"
+  perms       = 0644
+  command     = "echo 'service nginx reload'"
+}
+

실행

$ consul-template -config=consul-template-nginx.hcl
+service nginx reload
+
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.html new file mode 100644 index 0000000000..0b6dfcccdd --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.html @@ -0,0 +1,86 @@ + + + + + + + + + + 01. Install | docmoa + + + + + +
본문으로 건너뛰기

01. Install

1분 미만ConsulServiceMeshK8sKubernetes

01. Install

실습을 위한 조건은 다음과 같습니다.

참고 : https://learn.hashicorp.com/collections/consul/kubernetes-productionopen in new window

Consul Gossip

다운받은 Consul 바이너리를 통해 Gossip 암호화 키를 생성합니다

kubectl create secret generic consul-gossip-encryption-key --from-literal=key=$(consul keygen)
+

License (Option)

발급받은 라이선스 파일을 저장(e.g. consul.hclic)하고 Kubernetes의 secret으로 적용합니다.

kubectl create secret generic license --from-file='key=./consul.hclic'
+

Helm

Helm repo add & update

helm repo add hashicorp https://helm.releases.hashicorp.com && \
+helm repo update
+

Helm Chart

github : https://github.com/hashicorp/consul-helm/blob/master/values.yamlopen in new window

Helm 설치를 위한 파일(e.g. value.yaml) 을 작성합니다.

global:
+  enabled: true
+  name: consul
+  image: hashicorp/consul-enterprise:1.11.3-ent
+  enableConsulNamespaces: true
+  adminPartitions:
+    enabled: false
+  datacenter: dc1
+  # enterpriseLicense:
+  #   secretName: license
+  #   secretKey: key
+  gossipEncryption:
+    secretName: consul-gossip-encryption-key
+    secretKey: key
+  tls:
+    enabled: false
+    enableAutoEncrypt: true
+  enableConsulNamespaces: true
+
+client:
+  enabled: true
+  grpc: true
+
+connectInject:
+  enabled: true
+  replicas: 2
+
+dns:
+  enabled: true
+
+controller:
+  enabled: true
+
+syncCatalog:
+  enabled: true
+  toConsul: false
+  consulNamespaces:
+    mirroringK8S: true
+
+

설치

kubectl config use-context $(grep gs-cluster-0 KCONFIG.txt)
+helm install consul -f ./values.yaml hashicorp/consul --version v0.40.0 --debug
+

UI 확인

kubectl port-forward service/consul-server 8500:8500
+

http://localhost:8500/uiopen in new window

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.html new file mode 100644 index 0000000000..da61c3f64e --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.html @@ -0,0 +1,215 @@ + + + + + + + + + + 02. SideCar | docmoa + + + + + +
본문으로 건너뛰기

02. SideCar

약 2 분ConsulServiceMeshK8sKubernetes

02. SideCar

실습을 위한 조건은 다음과 같습니다.

  • Consul 이 구성된 Kubernetes 환경
  • 설치 구성 시 connectInject 이 활성화 되어있어야 합니다.

참고 : https://learn.hashicorp.com/tutorials/consul/service-mesh-application-secure-networking?in=consul/kubernetesopen in new window

Consul 서비스 메시를 사용하면 애플리케이션을 제로 트러스트 네트워크에 배포할 수 있습니다. 제로 트러스트 네트워크는 아무 것도 자동으로 신뢰되지 않는 네트워크입니다. 모든 연결은 인증과 승인을 모두 받아야 합니다. 이 패러다임은 동일한 네트워크에서 다수의 서비스가 실행될 수 있는 마이크로서비스 및 멀티 클라우드 환경에서 중요합니다. Consul 서비스 메시를 사용하면 mTLS를 사용하여 서비스 ID를 인증하고 의도를 사용하여 서비스 작업을 승인하거나 차단할 수 있습니다.

이 튜토리얼에서는 두 개의 서비스 webapi를 Kubernetes 클러스터에서 실행되는 Consul의 서비스 메시에 배포합니다. 두 서비스는 Consul을 사용하여 서로를 검색하고 사이드카 프록시를 사용하여 mTLS를 통해 통신합니다. 두 서비스는 웹UI와 백엔드 서비스로 구성된 간단한 2-tier 애플리케이션으로 HTTP를 통해 서비스와 통신합니다.

Sidecar Proxy

Sidecar Proxy는 애플리케이션 컨테이너와 함께 동일 Pod상에 배포됨으로 localhost 로 통신할 수 있습니다. 사용자가 다른서비스에 대한 요청을 Sidecar Proxy에 지정하면 해당 요청을 맵핑된 다른 서비스로 전달합니다. 이 방식은 기존 개발자가 로컬에서 개발하는 환경과 동일하게 동작합니다. UI웹을 로컬 9090포트로 실행하고 백엔드 앱을 8080 포트로 실행한경우 UI웹은 백엔드 앱을 localhost:8080 으로 호출합니다. Consul Sidecar Proxy는 localhost 로 요청되는 포트를 지정한 Upstream 서비스로 전달하는 규칙을 처리하며, 여기에는 mTLS가 자동으로 구성됩니다.

Intention 규칙 추가

Sidecar Proxy의 서비스 접근 제어를 위해 모든 서비스에 대한 Deny 구성을 수행합니다. UI의 좌측 메뉴의 Intention을 클릭하고 우측의 Create 버튼을 클릭하여 모든 서비스 (엔터프라이즈의 경우 모든 Namespace 포함) 간에 Deny 규칙을 생성합니다.

Service 구성

테스트 구성을 저장하기 위한 디렉토리를 생성합니다.

mkdir ./k8s_config
+

백엔드 서비스

cat > ./k8s_config/api.yaml <<EOF
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: api-v1
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: api-v1
+spec:
+  selector:
+    app: api-v1
+  ports:
+    - port: 9091
+      targetPort: 9091
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: api-v1
+  labels:
+    app: api-v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: api-v1
+  template:
+    metadata:
+      labels:
+        app: api-v1
+      annotations:
+        consul.hashicorp.com/connect-inject: 'true'
+    spec:
+      serviceAccountName: api-v1
+      containers:
+        - name: api
+          image: nicholasjackson/fake-service:v0.7.8
+          ports:
+            - containerPort: 9091
+          env:
+            - name: 'LISTEN_ADDR'
+              value: '127.0.0.1:9091'
+            - name: 'NAME'
+              value: 'api-v1'
+            - name: 'MESSAGE'
+              value: 'Response from API v1'
+EOF
+

프론트엔드 서비스

cat > ./k8s_config/web.yaml <<EOF
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: web
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: web
+spec:
+  selector:
+    app: web
+  ports:
+    - port: 9090
+      targetPort: 9090
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-deployment
+  labels:
+    app: web
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: web
+  template:
+    metadata:
+      labels:
+        app: web
+      annotations:
+        consul.hashicorp.com/connect-inject: 'true'
+        consul.hashicorp.com/connect-service-upstreams: 'api-v1:9091'
+    spec:
+      serviceAccountName: web
+      containers:
+        - name: web
+          image: nicholasjackson/fake-service:v0.7.8
+          ports:
+            - containerPort: 9090
+          env:
+            - name: 'LISTEN_ADDR'
+              value: '0.0.0.0:9090'
+            - name: 'UPSTREAM_URIS'
+              value: 'http://localhost:9091'
+            - name: 'NAME'
+              value: 'web'
+            - name: 'MESSAGE'
+              value: 'Hello World'
+EOF
+

프론트엔드 서비스에 Deployment 구성 내용의 annotation 을 확인하세요. 이 형식은 9091로 요청된 localhost로의 요청을 sidecar가 api-v1서비스로 전달하는 것을 의미합니다.

  • consul.hashicorp.com/connect-service-upstreams: 'api-v1:9091'

서비스 배포 및 등록 확인

kubectl apply 명령을 통해 배포를 확인하고 Consul UI에서 확인합니다.

kubectl apply -f ./k8s_config/api.yaml
+
# 출력
+serviceaccount/api-v1 created
+service/api-v1 created
+deployment.apps/api-v1 created
+
kubectl apply -f ./k8s_config/web.yaml
+
# 출력
+serviceaccount/web created
+service/web created
+deployment.apps/web-deployment created
+

UI에 접속하고 좌측 Namespace에서 사용중인 Namespace를 확인합니다. 서비스 목록에 api-v1, web 이 등록되고 상태가 정상임을 확인합니다.

Services - Consul 2022-02-19 19-21-21
Services - Consul 2022-02-19 19-21-21

port-forward를 통해 로컬에서 web 앱을 확인합니다.

kubectl port-forward service/web 9090:9090 --address 0.0.0.0
+
# 출력
+Forwarding from 0.0.0.0:9090 -> 9090
+

http://localhost:9090/uiopen in new window 에 브라우저로 접속하여 상태를 확인합니다.

image-20220219192259588
image-20220219192259588

500 에러가 발생하였습니다. Consul Service Mesh는 서비스간 의도적으로 접속 가능여부를 동적으로 통제합니다. 이 기능을 Intention 이라고 부릅니다. Consul UI에 접속하여 web 서비스 이름을 클릭하면 다음과 같이 요청이 거부되어 있는것을 확인할 수 있습니다.

Intention 수정을 위해서는 권한이 필요합니다. 현재 환경에서는 전달받은 token (3108cbb3-005c-a3e4-9a42-6f13d1f5e4e6) 을 우측 상단 로그인에서 입력합니다.

web - Consul 2022-02-19 19-24-22
web - Consul 2022-02-19 19-24-22

x 표시를 클릭하여 Create 버튼을 클릭하여 Intention 규칙을 생성합니다. 이후에 연결이 허용된것을 확인할 수 있습니다.

web - Consul 2022-02-19 19-26-03
web - Consul 2022-02-19 19-26-03

다시 http://localhost:9090/uiopen in new window 에 브라우저로 접속하여 상태를 확인합니다.

image-20220219192915371
image-20220219192915371

다음 과정을 진행하기 위해 기존 적용된 구성을 삭제합니다.

kubectl delete -f ./k8s_config
+
# 출력
+serviceaccount "api-v1" deleted
+service "api-v1" deleted
+deployment.apps "api-v1" deleted
+serviceaccount "web" deleted
+service "web" deleted
+deployment.apps "web-deployment" deleted
+

With Namespace (Ent)

백엔드 서비스를 다른 Namespace에 배포하고, 프론트엔드가 해당 백엔드에 접근할 수 있도록 수정합니다.

프론트엔드 서비스

cat > ./k8s_config/web.yaml <<EOF
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: web
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: web
+spec:
+  selector:
+    app: web
+  ports:
+    - port: 9090
+      targetPort: 9090
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-deployment
+  labels:
+    app: web
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: web
+  template:
+    metadata:
+      labels:
+        app: web
+      annotations:
+        consul.hashicorp.com/connect-inject: 'true'
+        consul.hashicorp.com/connect-service-upstreams: 'api-v1.<namespace>:9091'
+    spec:
+      serviceAccountName: web
+      containers:
+        - name: web
+          image: nicholasjackson/fake-service:v0.7.8
+          ports:
+            - containerPort: 9090
+          env:
+            - name: 'LISTEN_ADDR'
+              value: '0.0.0.0:9090'
+            - name: 'UPSTREAM_URIS'
+              value: 'http://localhost:9091'
+            - name: 'NAME'
+              value: 'web'
+            - name: 'MESSAGE'
+              value: 'Hello World'
+EOF
+

프론트엔드 서비스에 Deployment 구성 내용의 annotation 을 확인하세요. 이 형식은 9091로 요청된 localhost로의 요청을 sidecar가 <namespace> Namespace의 api-v1서비스로 전달하는 것을 의미합니다.

  • consul.hashicorp.com/connect-service-upstreams: 'api-v1.<namespace>:9091'

또한 Namespace 간 Intention을 작성하여 적용해야합니다.

정리

  • 이 단계에서는 두개의 앱을 이용하여 Service Mesh를 구성하기위한 Sidecar Proxy를 통해 통신하는 방식을 확인하였습니다.
  • Sidecar Proxy에는 Consul이 정책을 주입할 수 있으므로, 기존 Firewall 방식이 아닌 서비스간 의도적인 연결의 정의를 통해 허용/비허용 됨을 확인하였습니다.

참고 - Intention 순위

범위가 좁을수록 우선순위가 높습니다.

Source NamespaceSource NameDestination NamespaceDestination Name높을수록 서열 높음
ExactExactExactExact9
Exact*ExactExact8
**ExactExact7
ExactExactExact*6
Exact*Exact*5
**Exact*4
ExactExact**3
Exact***2
****1
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.html new file mode 100644 index 0000000000..0f01191150 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.html @@ -0,0 +1,139 @@ + + + + + + + + + + 03. CRD로 Consul Serive Mesh 관리 | docmoa + + + + + +
본문으로 건너뛰기

03. CRD로 Consul Serive Mesh 관리

약 2 분ConsulServiceMeshK8sKubernetes

03. CRD로 Consul Serive Mesh 관리

참고 : https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetesopen in new window

참고 : https://learn.hashicorp.com/tutorials/consul/service-mesh-zero-trust-network?in=consul/gs-consul-service-meshopen in new window

Consul 1.9 이전에는 Kubernetes에서 Consul과 함께 구성 항목을 사용할 때 운영자가 실행 중인 컨테이너에 들어가거나 로컬 Consul 바이너리를 사용하여 구성해야 했습니다. 1.9 이전 버전에서는 구성 항목을 Consul CLI, HTTP API로 관리하거나 시작하는 동안 구성 파일로 에이전트에 제공해야 합니다.

Consul 1.9부터 대부분의 구성 항목은 Kubernetes 사용자 지정 리소스 정의(CRD)로 관리할 수 있습니다. 이제 대부분의 구성 항목을 YAML로 정의하고 익숙한 kubectl apply명령을 사용하여 Consul에 등록할 수 있습니다.

현재 Kubernetes에서 Consul의 CRD로 사용할 수 있는 구성 항목은 다음과 같습니다.

샘플 애플리케이션 준비

필요한 애플리케이션을 다운로드 받습니다. git clone 또는 https://github.com/hashicorp/learn-consul-kubernetesopen in new window 로 접속하여 Code를 다운로드 받습니다.

  • git clone

    git clone https://github.com/hashicorp/learn-consul-kubernetes.git
    +
  • Code download

    image-20220219195033888
    image-20220219195033888

다운로드 후 learn-consul-kubernetes/service-mesh/deploy 경로로 이동하고 샘플 구성을 반영합니다.

cd learn-consul-kubernetes/service-mesh/deploy
+kubectl apply -f hashicups/
+
# 출력
+service/frontend created
+serviceaccount/frontend created
+servicedefaults.consul.hashicorp.com/frontend created
+configmap/nginx-configmap created
+deployment.apps/frontend created
+service/postgres created
+serviceaccount/postgres created
+servicedefaults.consul.hashicorp.com/postgres created
+deployment.apps/postgres created
+service/product-api created
+serviceaccount/product-api created
+servicedefaults.consul.hashicorp.com/product-api created
+configmap/db-configmap created
+deployment.apps/product-api created
+service/public-api created
+serviceaccount/public-api created
+servicedefaults.consul.hashicorp.com/public-api created
+deployment.apps/public-api created
+

서비스는 Consul이 각 서비스에 대한 프록시를 자동으로 삽입할 수 있도록 하는 annotation 을 사용합니다. 프록시 는 Consul의 구성을 기반으로 서비스 간의 요청을 처리하기 위해 데이터 플레인을 생성합니다. Consul이 주입되는 label을 선택하여 프록시가 있는 응용 프로그램을 확인할 수 있습니다.

kubectl get pods --selector consul.hashicorp.com/connect-inject-status=injected
+
# 출력
+NAME                           READY   STATUS    RESTARTS   AGE
+frontend-98cb6859b-6ndvk       2/2     Running   0          3m10s
+postgres-6ccb6d9968-hkbgz      2/2     Running   0          3m9s
+product-api-6798bc4b4d-9ddv4   2/2     Running   2          3m9s
+public-api-5bdf986897-tlxj2    2/2     Running   0          3m9s
+

배포된 앱에 접근하기 위해 port-forward를 구성합니다.

kubectl port-forward service/frontend 18080:80 --address 0.0.0.0
+
# 출력
+Forwarding from 0.0.0.0:18080 -> 80
+

브라우저에서 http://localhost:18080open in new window 로 접근합니다.

image-20220219202023073
image-20220219202023073

현재 Intention 규칙이 모두 deny로 구성되어있다면 에러 화면을 확인하게 됩니다.

CRD 적용해보기

UI에서가 아닌 CRD를 통해 Intention 을 정의하기위해 아래와 같이 구성합니다.

cat > ./service-to-service.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: frontend-to-public-api
+spec:
+  destination:
+    name: public-api
+  sources:
+    - name: frontend
+      action: allow
+---
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: public-api-to-product-api
+spec:
+  destination:
+    name: product-api
+  sources:
+    - name: public-api
+      action: allow
+---
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: product-api-to-postgres
+spec:
+  destination:
+    name: postgres
+  sources:
+    - name: product-api
+      action: allow
+EOF
+

규칙의 내용은 다음과 같습니다.

  • frontend -> public-api
  • public-api -> product-api
  • product-api -> postgres

규칙을 적용합니다.

kubectl apply -f service-to-service.yaml
+
# 출력
+serviceintentions.consul.hashicorp.com/frontend-to-public-api created
+serviceintentions.consul.hashicorp.com/public-api-to-product-api created
+serviceintentions.consul.hashicorp.com/product-api-to-postgres created
+

Consul UI에서 확인해보면 해당 Intention 규칙은 CRD로 적용되었기 때문에 Managed by CRD 표시가 붙는것을 확인할 수 있습니다.

배포된 앱에 접근하기 위해 port-forward를 구성합니다.

kubectl port-forward service/frontend 18080:80 --address 0.0.0.0
+
# 출력
+Forwarding from 0.0.0.0:18080 -> 80
+

브라우저에서 http://localhost:18080open in new window 로 접근합니다.

image-20220219202943498
image-20220219202943498

서비스 간 연결이 허용되었으므로 페이지가 잘 표시됩니다.

다음 과정을 위해 배포된 리소스를 정리합니다.

kubectl delete -f service-to-service.yaml
+
# 출력
+serviceintentions.consul.hashicorp.com "frontend-to-public-api" deleted
+serviceintentions.consul.hashicorp.com "public-api-to-product-api" deleted
+serviceintentions.consul.hashicorp.com "product-api-to-postgres" deleted
+
kubectl delete -f hashicups/
+
# 출력
+service "frontend" deleted
+serviceaccount "frontend" deleted
+servicedefaults.consul.hashicorp.com "frontend" deleted
+configmap "nginx-configmap" deleted
+deployment.apps "frontend" deleted
+service "postgres" deleted
+serviceaccount "postgres" deleted
+servicedefaults.consul.hashicorp.com "postgres" deleted
+deployment.apps "postgres" deleted
+service "product-api" deleted
+serviceaccount "product-api" deleted
+servicedefaults.consul.hashicorp.com "product-api" deleted
+configmap "db-configmap" deleted
+deployment.apps "product-api" deleted
+service "public-api" deleted
+serviceaccount "public-api" deleted
+servicedefaults.consul.hashicorp.com "public-api" deleted
+deployment.apps "public-api" deleted
+
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.html new file mode 100644 index 0000000000..ec218b871e --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.html @@ -0,0 +1,309 @@ + + + + + + + + + + 04. 트래픽 관리 | docmoa + + + + + +
본문으로 건너뛰기

04. 트래픽 관리

약 2 분ConsulServiceMeshK8sKubernetes

04. 트래픽 관리

실습을 진행하기 위한 디렉토리를 생성합니다.

mkdir ./traffic
+

Service Mesh는 HTTP 프로토콜 상에서 L7으로 동작하게 됩니다. 따라서 기본 프로토콜을 http로 변경합니다.

cat > ./traffic/service-to-service.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ProxyDefaults
+metadata:
+  name: global
+spec:
+  config:
+    protocol: http
+EOF
+
kubectl apply -f ./traffic/service-to-service.yaml
+
# 출력
+proxydefaults.consul.hashicorp.com/global created
+

샘플 앱 준비

프론트엔드 서비스

cat > ./traffic/gs-frontend.yaml <<EOF
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: gs-frontend
+spec:
+  selector:
+    app: gs-frontend
+  ports:
+    - protocol: TCP
+      port: 3000
+      targetPort: 3000
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: gs-frontend
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gs-frontend
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gs-frontend
+  template:
+    metadata:
+      labels:
+        app: gs-frontend
+      annotations:
+        prometheus.io/scrape: "true"
+        prometheus.io/port: "9901"
+        consul.hashicorp.com/connect-inject: "true"
+        consul.hashicorp.com/transparent-proxy: true
+        consul.hashicorp.com/connect-service-upstreams: "gs-backend:8080"
+    spec:
+      serviceAccountName: gs-frontend
+      containers:
+        - name: gs-frontend
+          image: hahohh/consul-frontend-nodejs:v1.5
+          env:
+            - name: PORT
+              value: "3000"
+            - name: UPSTREAM_URL
+              value: "http://localhost:8080
+          ports:
+            - containerPort: 3000
+EOF
+

적용하기

kubectl apply -f ./traffic/gs-frontend.yaml
+

백엔드 서비스

cat > ./traffic/gs-backend.yaml <<EOF
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: gs-backend
+spec:
+  selector:
+    app: gs-backend
+  ports:
+    - protocol: TCP
+      port: 8080
+      targetPort: 8080
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: gs-backend
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gs-backend-v1
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gs-backend
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: gs-backend
+        version: v1
+      annotations:
+        consul.hashicorp.com/connect-inject: "true"
+        consul.hashicorp.com/service-meta-version: v1
+        consul.hashicorp.com/service-tags: v1
+    spec:
+      serviceAccountName: gs-backend
+      containers:
+        - name: gs-backend
+          image: hahohh/consul-backend-go:v1.2
+          env:
+            - name: PORT
+              value: "8080"
+            - name: COLOR
+              value: "green"
+            - name: VERSION
+              value: "v1"
+          ports:
+            - containerPort: 8080
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gs-backend-v2
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gs-backend
+      version: v2
+  template:
+    metadata:
+      labels:
+        app: gs-backend
+        version: v2
+      annotations:
+        consul.hashicorp.com/connect-inject: "true"
+        consul.hashicorp.com/service-meta-version: v2
+        consul.hashicorp.com/service-tags: v2
+    spec:
+      serviceAccountName: gs-backend
+      containers:
+        - name: gs-backend
+          image: hahohh/consul-backend-go:v1.2
+          env:
+            - name: PORT
+              value: "8080"
+            - name: COLOR
+              value: "blue"
+            - name: VERSION
+              value: "v2"
+            # - name: ISFAIL
+            #   value: "yyyy"
+          ports:
+            - containerPort: 8080
+EOF
+

적용하기

kubectl apply -f ./traffic/gs-backend.yaml
+

서비스 Intention

cat > ./traffic/service-to-service.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: gs-backend
+spec:
+  destination:
+    name: gs-backend
+  sources:
+    - name: gs-frontend
+      action: allow
+EOF
+

적용하기

kubectl apply -f ./traffic/service-to-service.yaml
+

port-forward를 통해 로컬에서 web 앱을 확인합니다.

kubect l port-forward service/gs-frontend 3000:3000 --address 0.0.0.0
+
# 출력
+Forwarding from 0.0.0.0:3000 -> 3000
+

http://localhost:3000open in new window 에 브라우저로 접속하여 상태를 확인합니다.

두개의 버전의 백엔드가 서로다른 갑을 리턴하여 때에 따라 v1, v2가 번갈아 나타납니다.

v1
v1
v2
v2

트래픽 제어 요소

traffic-method
traffic-method
  • Resolver : 동일한 서비스 이름이 있더라도 조건에 따라 각 서비스를 독립적으로 정의합니다. (e.g. v1, v2, canary)
  • Splitter : 정의된 서비스로 가중치에 따라 트래픽을 분산합니다. 10000분율이 적용됩니다.
  • Router : HTTP, gRPC 속성 기반으로 정의된 서비르소 트래픽을 분산합니다.
    • pathPrefix / PathExact / PathRegex
    • Header
    • QueryParam

Resolve

cat > ./traffic/service-resolver.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceResolver
+metadata:
+  name: gs-backend
+spec:
+  defaultSubset: v1
+  subsets:
+    v1:
+      filter: "Service.Meta.version == v1"
+    v2:
+      filter: "Service.Meta.version == v2"
+EOF
+

적용하기

kubectl apply -f ./traffic/service-resolver.yaml
+

앞서 배포된 gs-backendDeployment 에 선언된 annotation 내용을 확인하면 consul.hashicorp.com/service-meta-version: v2 을 확인할 수 있습니다. Consul UI에서도 해당 Meta 정보를 확인할 수 있습니다. 선언된 정보를 기반으로 서비스의 subset 을 정의합니다.

service-meta
service-meta

Splitter

cat > ./traffic/service-splitter.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceSplitter
+metadata:
+  name: gs-backend
+spec:
+  splits:
+    - weight: 50
+      serviceSubset: v1
+    - weight: 50
+      serviceSubset: v2
+EOF
+

적용하기

kubectl apply -f ./traffic/service-splitter.yaml
+

weight에 지정된 비율로 Resolve된 서비스 대상 subset 에 트래픽을 분산합니다. weight값을 0:100, 100:0 등으로 변경하여 요청의 결과가 어떻게 변화하는지 확인해 봅니다.

Router

cat > ./traffic/service-router.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceRouter
+metadata:
+  name: gs-backend
+spec:
+  routes:
+    - match:
+        http:
+          pathPrefix: '/v1'
+      destination:
+        service: gs-backend
+        serviceSubset: v1
+    - match:
+        http:
+          pathPrefix: '/v2'
+      destination:
+        service: gs-backend
+        serviceSubset: v2
+    - match:
+        http:
+          queryParam:
+            - name: version
+              exact: 'v1'
+      destination:
+        service: gs-backend
+        serviceSubset: v1
+    - match:
+        http:
+          queryParam:
+            - name: version
+              exact: 'v2'
+      destination:
+        service: gs-backend
+        serviceSubset: v2
+EOF
+

적용하기

kubectl apply -f ./traffic/service-router.yaml
+

예제에서는 url의 path, queryParam을 예로 트래픽을 컨트롤 합니다. 다음과같이 요청하여 트래픽이 조정되는 것을 확인합니다.

서비스 Intention (L7)

service-to-service 허용 방식에도 Meshod, Path 등을 지정할 수 있습니다. 다음과 같이 변경하고 POST만 넣은 상태에서는 어떻게 동작하는지 확인합니다.

cat > ./traffic/service-to-service.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: gs-backend
+spec:
+  destination:
+    name: gs-backend
+  sources:
+    - name: gs-frontend
+      permissions:
+        - action: allow
+          http:
+            pathPrefix: /
+            # methods: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']
+            methods: ['POST']
+

적용하기

kubectl apply -f ./traffic/service-to-service.yaml
+

다시 앱간의 요청인 GET으로 변경하고 트래픽 허용여부를 확인해봅니다.

cat > ./traffic/service-to-service.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceIntentions
+metadata:
+  name: gs-backend
+spec:
+  destination:
+    name: gs-backend
+  sources:
+    - name: gs-frontend
+      permissions:
+        - action: allow
+          http:
+            pathPrefix: /
+            # methods: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']
+            methods: ['GET']
+

적용하기

kubectl apply -f ./traffic/service-to-service.yaml
+

Consul UI Routing table

Consul UI에 접속하여 gs-backendRouting 탭을 클릭, 구성된 Resolver, Splitter, Router가 어떻게 구성되었는지, 각 서비스에는 어떤 조건으로 요청할 수 있는지 확인합니다.

ui
ui
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/index.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/index.html new file mode 100644 index 0000000000..d98939a7d6 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Service Mesh101 | docmoa + + + + + +
본문으로 건너뛰기

Service Mesh101

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/index.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/index.html new file mode 100644 index 0000000000..f7fd6edc73 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Annotation | docmoa + + + + + +
본문으로 건너뛰기

Annotation

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/ingress-and-serviceroute.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/ingress-and-serviceroute.html new file mode 100644 index 0000000000..93a6863e6e --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/ingress-and-serviceroute.html @@ -0,0 +1,67 @@ + + + + + + + + + + Ingress & ServiceRoute | docmoa + + + + + +
본문으로 건너뛰기

Ingress & ServiceRoute

1분 미만ConsulServiceMeshK8sKubernetesingress

Ingress & ServiceRoute

Ingress gateway가 8080을 Listen하도록 구성되어있으면, 아래와 같이 해당 포트의 요청을 받을 대상 서비스를 지정합니다.

apiVersion: consul.hashicorp.com/v1alpha1
+kind: IngressGateway
+metadata:
+  name: ingress-gateway
+spec:
+  listeners:
+    - port: 8080
+      protocol: http
+      services:
+        - name: hashicups
+          hosts: ["*"]
+

여기서 지정된 hashicups는 가상의 서비스 입니다. 해당 서비스에 대한 Service Router 설정을 다음과 같이 구성합니다.

apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceRouter
+metadata:
+  name: hashicups
+spec:
+  routes:
+    - match:
+        http:
+          pathPrefix: '/api'
+      destination:
+        service: public-api
+    - match:
+        http:
+          pathPrefix: '/'
+      destination:
+        service: frontend
+
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/multiport.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/multiport.html new file mode 100644 index 0000000000..ce461b6be9 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/multiport.html @@ -0,0 +1,45 @@ + + + + + + + + + + Multiport | docmoa + + + + + +
본문으로 건너뛰기

Multiport

1분 미만ConsulServiceMeshK8sKubernetesannotation

Multiport

Consul Doc : https://www.consul.io/docs/k8s/connect#kubernetes-pods-with-multiple-portsopen in new window

annotation 에 다음과 같이 서비스 이름과 대상 포트를 리스트로 지정합니다.

consul.hashicorp.com/connect-inject: true
+consul.hashicorp.com/transparent-proxy: false
+consul.hashicorp.com/connect-service: web,web-admin
+consul.hashicorp.com/connect-service-port: 8080,9090
+

포트가 서비스 이름과 동일한 순서로 나열되는 순서에 입니다. 즉, 첫 번째 서비스 이름 web은 첫 번째 포트인 8080에 해당합니다. 두 번째 서비스 이름 web-admin은 두 번째 포트인 9090에 해당합니다.

이 서비스를 호출하는 타 서비스의 Upstream은 다음과 같을 수 있습니다.

consul.hashicorp.com/connect-service-upstreams: "web:1234,web-admin:2234"
+
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/envoy_timeout.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/envoy_timeout.html new file mode 100644 index 0000000000..200fff8700 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/envoy_timeout.html @@ -0,0 +1,249 @@ + + + + + + + + + + Envoy Timeout | docmoa + + + + + +
본문으로 건너뛰기

Envoy Timeout

약 1 분ConsulServiceMeshK8sKubernetestimeout

Envoy Timeout

Consul API : https://www.consul.io/api-docs/configopen in new window
Proxy Default : https://www.consul.io/docs/connect/config-entries/proxy-defaultsopen in new window
Envoy Integration : https://www.consul.io/docs/connect/proxies/envoyopen in new window

단계 1. Deployment Sample

cat > ./gs-frontend.yaml <<EOF
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: gs-frontend
+spec:
+  selector:
+    app: gs-frontend
+  ports:
+    - protocol: TCP
+      port: 3000
+      targetPort: 3000
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: gs-frontend
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: gs-frontend
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: gs-frontend
+  template:
+    metadata:
+      annotations:
+        consul.hashicorp.com/connect-inject: "true"
+        consul.hashicorp.com/transparent-proxy: "false"
+        consul.hashicorp.com/connect-service-upstreams: "gs-backend:8080"
+      labels:
+        app: gs-frontend
+    spec:
+      serviceAccountName: gs-frontend
+      containers:
+        - name: gs-frontend
+          image: hahohh/consul-frontend-nodejs:v1.5
+          env:
+            - name: PORT
+              value: "3000"
+            - name: UPSTREAM_URL
+              value: "http://localhost:8080"
+          ports:
+            - containerPort: 3000
+EOF
+
+kubectl apply -f ./gs-frontend.yaml
+

단계 2. config_dump 생성

kubectl exec pod/<POD_ID> -c envoy-sidecar -- wget -qO- http://localhost:19000/config_dump
+
{
+ "configs": [
+  {
+   "@type": "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump",
+   "bootstrap": {
+     <생략>
+    "static_resources": {
+     "clusters": [
+      {
+       "name": "local_agent",
+       "type": "STATIC",
+       "connect_timeout": "1s",
+  <생략>
+  {
+   "@type": "type.googleapis.com/envoy.admin.v3.ClustersConfigDump",
+   "static_clusters": [
+    {
+     "cluster": {
+      "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
+      "name": "local_agent",
+      "type": "STATIC",
+      "connect_timeout": "1s",
+      "http2_protocol_options": {},
+      
+  <생략>
+   "dynamic_active_clusters": [
+    {
+     "version_info": "eb3fa9f7104047dd6420d0eb13fd556995d2c6e7d687c4db07b759408ecf0345",
+     "cluster": {
+      "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
+      "name": "gs-backend.default.dc1.internal.a3568e3d-e611-5f3e-01b9-24ec787ce275.consul",
+      "type": "EDS",
+      "eds_cluster_config": {
+       "eds_config": {
+        "ads": {},
+        "resource_api_version": "V3"
+       }
+      },
+      "connect_timeout": "5s",
+      "circuit_breakers": {},
+  <생략>
+
  • 일부 기본 timeout 값은 기본값
    • static_resources.clusters.*.connect_timeout : "1s"
    • dynamic_active_clusters.*.connect_timeout : "5s"

단계 3. ProxyDefaults 설정

cat > ./proxydefaults.yaml <<EOF
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ProxyDefaults
+metadata:
+  name: global
+spec:
+  config:
+    protocol: http
+    local_connect_timeout_ms: 60000
+    local_request_timeout_ms: 60000
+    upstreamConfig:
+      defaults:
+        protocol: http
+        connectTimeoutMs : 60000
+EOF
+
+kubectl apply -f ./proxydefaults.yaml
+

단계 4. Consul API 에서 proxy-default 값 변경 확인

kubectl exec pod/consul-server-0 -- wget -qO- http://localhost:8500/v1/config/proxy-defaults/global | jq .
+{
+  "Kind": "proxy-defaults",
+  "Name": "global",
+  "Config": {
+    "local_connect_timeout_ms": 60000,
+    "local_request_timeout_ms": 60000,
+    "protocol": "http",
+    "upstreamConfig": {
+      "defaults": {
+        "connectTimeoutMs": 60000,
+        "protocol": "http"
+      }
+    }
+  },
+  "TransparentProxy": {},
+  "MeshGateway": {},
+  "Expose": {},
+  "Meta": {
+    "consul.hashicorp.com/source-datacenter": "dc1",
+    "external-source": "kubernetes"
+  },
+  "Partition": "default",
+  "Namespace": "default",
+  "CreateIndex": 354,
+  "ModifyIndex": 354
+}
+

단계 5. config_dump 재생성

{
+ "configs": [
+  {
+   "@type": "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump",
+   "bootstrap": {
+  <생략>
+    {
+     "version_info": "c80b97864daee80ecc0335fdd5b67437b946ea76e77f848d0cd69d6d1ad330ea",
+     "cluster": {
+      "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
+      "name": "local_app",
+      "type": "STATIC",
+      "connect_timeout": "60s",
+      "load_assignment": {
+  <생략>
+    {
+     "name": "public_listener:10.0.1.162:20000",
+     "active_state": {
+      "version_info": "4f8654a4ce70f346dd63c479b3b42b41c5a92eda52f6b2d38dab8ff4536923f3",
+      "listener": {
+       "@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
+       "name": "public_listener:10.0.1.162:20000",
+       "address": {
+        "socket_address": {
+         "address": "10.0.1.162",
+         "port_value": 20000
+        }
+       },
+       "filter_chains": [
+        {
+         "filters": [
+          {
+           "name": "envoy.filters.network.http_connection_manager",
+           "typed_config": {
+            "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
+            "stat_prefix": "public_listener",
+            "route_config": {
+             "name": "public_listener",
+             "virtual_hosts": [
+              {
+               "name": "public_listener",
+               "domains": [
+                "*"
+               ],
+               "routes": [
+                {
+                 "match": {
+                  "prefix": "/"
+                 },
+                 "route": {
+                  "cluster": "local_app",
+                  "timeout": "60s"
+  <생략>
+    {
+     "route_config": {
+      "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
+      "name": "public_listener",
+      "virtual_hosts": [
+       {
+        "name": "public_listener",
+        "domains": [
+         "*"
+        ],
+        "routes": [
+         {
+          "match": {
+           "prefix": "/"
+          },
+          "route": {
+           "cluster": "local_app",
+           "timeout": "60s"
+  <생략>
+
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/index.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/index.html new file mode 100644 index 0000000000..5a12f2e9e2 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Configuration | docmoa + + + + + +
본문으로 건너뛰기

Configuration

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/index.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/index.html new file mode 100644 index 0000000000..66ea4886d2 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 06 on Kubernetes | docmoa + + + + + +
본문으로 건너뛰기

06 on Kubernetes

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/consul-istio.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/consul-istio.html new file mode 100644 index 0000000000..b3da97b4d7 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/consul-istio.html @@ -0,0 +1,453 @@ + + + + + + + + + + Consul vs Istio - Performance Test | docmoa + + + + + +
본문으로 건너뛰기

Consul vs Istio - Performance Test

약 5 분ConsulIstioKubetenetesk8sPerformance

Consul vs Istio - Performance Test

1. 성능 테스트 수행 결과 요약

Case 2-1

  • Consul Ingress Gateway의 resources.limits 와 resources.requests 의 cpu, memory 를 각각 250m / 500Mi 로 수정
  • Istio Default 1527 Requests/sec 대비 1860 Requests/sec 로 약 22% 빠름 (Case 2-1)

Case 2-2

  • Consul Ingress Gateway resource allocation을 Istio와 동률 구성 시,
  • Istio Default 1527 Requests/sec 대비 3002 Requests/sec로 약 196% 빠름 (Case 2-2)

Response Time Chart

  • concurrent user: 100
  • total request: 15000
A Line Chart
  • 상세 내용 확인 - "4.성능 테스트 수행"
CaseResources: CPUResources: MemoryLimits: CPULimits: MemoryPerformance
1-1. Consul Default100 m100 Mi100 m100 Mi896 reqs / sec
1-2. Istio Default10 m40 Mi2 C1 Gi1527 reqs / sec
2-1. Consul w/ alloc250 m500 Mi250 m500 Mi1860 reqs / sec
2-2. Consul resources like Istio10 m40 Mi2 C1 Gi3002 reqs / sec

2. 시나리오

  • Load Generator -> Ingress Gateway -> Sidecar -> Test App 의 흐름으로 외부에서 유입되는 요청에 대한 응답 성능 측정

3. 환경 구성

kubectl create namespace consul	#consul namespace
+kubectl create namespace istio-system #istio namespace
+

3-1. Consul 구성

  • Test 에서는 Sever / Client 모두 AKS 내 설치
  • Performance 관점에서 Server 의 위치에 따른 영향도 없음 (Data Plane 을 이용한 Traffic 운반이므로)
  • values.yaml
global:
+  enabled: true
+  datacenter: dc1
+  logLevel: "debug"
+  logJSON: false
+  image: hashicorp/consul-enterprise:1.12.3-ent
+  gossipEncryption:
+    secretKey: key
+    secretName: consul-gossip-encryption-key
+  tls:
+    enabled: false
+    enableConsulNamespaces: true
+  imageEnvoy: envoyproxy/envoy:v1.22-latest
+  enterpriseLicense:
+    secretName: license
+    secretKey: key
+server:
+  enabled: true
+  replicas: 1
+  bootstrapExpect: 1
+  exposeGossipAndRPCPorts: true
+client:
+  enabled: true
+  extraConfig: |
+    {
+      "log_level": "DEBUG"
+    }
+ui:
+  enabled: true
+  service:
+    type: LoadBalancer
+connectInject:
+  enabled: true
+  default: true
+controller:
+  enabled: true
+ingressGateways:
+  enabled: true
+  defaults:
+    replicas: 1
+    service:
+      type: LoadBalancer
+      ports:
+      - port: 80
+
  • 설치
helm install consul hashicorp/consul --namespace consul --values values.yaml
+
  • Test App 배포: Fake Service
# web-consul.yaml
+# kubectl apply -f web-consul.yaml -nconsul
+
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: web-consul
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: web-consul
+spec:
+  selector:
+    app: web-consul
+  ports:
+    - port: 9090
+      targetPort: 9090
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-deploy-consul
+  labels:
+    app: web-consul
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: web-consul
+  template:
+    metadata:
+      labels:
+        app: web-consul
+      annotations:
+        consul.hashicorp.com/connect-inject: 'true'
+    spec:
+      serviceAccountName: web-consul
+      containers:
+        - name: web-consul
+          image: nicholasjackson/fake-service:v0.23.1
+          ports:
+            - containerPort: 9090
+          env:
+            - name: 'LISTEN_ADDR'
+              value: '0.0.0.0:9090'
+            - name: 'NAME'
+              value: 'web'
+            - name: 'MESSAGE'
+              value: 'Hello World'
+
  • Service Mesh Config 설정: proxy-defaults
# proxy-defaults.yaml
+# kubectl apply -f proxy-defaults.yaml -nconsul
+
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ProxyDefaults
+metadata:
+  name: global
+spec:
+  config:
+    protocol: http
+
  • Service Mesh Config 설정: service-defaults
# service-defaults.yaml
+# kubectl apply -f service-defaults.yaml -nconsul
+
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceDefaults
+metadata:
+  name: web-consul
+spec:
+  protocol: http
+
  • Service Mesh Config 설정: ingress-gateway
# ingress-gateway.yaml
+# kubectl apply -f ingresss-gateway.yaml -nconsul
+
+apiVersion: consul.hashicorp.com/v1alpha1
+kind: IngressGateway
+metadata:
+  name: ingress-gateway
+spec:
+  listeners:
+    - port: 80
+      protocol: http
+      services:
+        - name: web-consul
+

3-2. Istio 구성

curl -sL https://istio.io/downloadIstioctl | sh -
+export PATH=$HOME/.istioctl/bin:$PATH
+
istioctl install
+
  • Sidecar Injection 설정: namespace 'istio-system' 한정
kubectl label namespace istio-system istio-injection=enabled
+
  • Test App 배포: Fake Service
# web-istio.yaml
+# kubectl apply -f web-istio.yaml -nistio-system
+
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: web-istio
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: web-istio
+spec:
+  selector:
+    app: web-istio
+  ports:
+    - port: 9090
+      targetPort: 9090
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: web-deploy-istio
+  labels:
+    app: web-istio
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: web-istio
+  template:
+    metadata:
+      labels:
+        app: web-istio
+    spec:
+      serviceAccountName: web-istio
+      containers:
+        - name: web-istio
+          image: nicholasjackson/fake-service:v0.23.1
+          ports:
+            - containerPort: 9090
+          env:
+            - name: 'LISTEN_ADDR'
+              value: '0.0.0.0:9090'
+            - name: 'NAME'
+              value: 'web-istio'
+            - name: 'MESSAGE'
+              value: 'Hello World'
+
  • Service Mesh Config: Ingress Gateway
# ingress-gateway.yaml
+# kubectl apply -f ingress-gateway.yaml
+
+apiVersion: networking.istio.io/v1alpha3
+kind: Gateway
+metadata:
+  name: fakeservice-gateway
+  namespace: istio-system
+spec:
+  selector:
+    istio: ingressgateway
+  servers:
+  - port:
+      number: 80
+      name: http
+      protocol: HTTP
+    hosts:
+    - "*"
+
  • Service Mesh Config: Virtual Service
# virtual-service.yaml
+# kubectl apply -f virtual-service.yaml
+
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: web-istio
+  namespace: istio-system
+spec:
+  hosts:
+  - "*"
+  gateways:
+  - fakeservice-gateway
+  http:
+  - match:
+    - uri:
+        exact: "/"
+    route:
+    - destination:
+        host: web-istio
+        port:
+          number: 9090
+

4. 성능 테스트 수행

테스트 결과 요약

CaseResources: CPUResources: MemoryLimits: CPULimits: MemoryPerformance
1-1. Consul Default100 m100 Mi100 m100 Mi896 reqs / sec
1-2. Istio Default10 m40 Mi2 C1 Gi1527 reqs / sec
2-1. Consul w/ alloc250 m500 Mi250 m500 Mi1860 reqs / sec
2-2. same resources10 m40 Mi2 C1 Gi3002 reqs / sec

Case 1-1. Consul Default

  • Consul Default (w/o any tuning): 896 Requests / sec
# concurrent user: 100
+# total request: 15000
+
+hyungwook@MacBook-Pro  ~  hey -n 15000 -c 100 http://20.200.225.63:8080/
+Summary:
+  Total:  16.7374 secs
+  Slowest:  0.2477 secs
+  Fastest:  0.0060 secs
+  Average:  0.1092 secs
+  Requests/sec: 896.1988
+  Total data: 3865847 bytes
+  Size/request: 257 bytes
+Response time histogram: 0.006 [1] |
+0.030 [828] |■■■■ 0.054 [100] |■
+0.079 [144] |■
+0.103 [7519] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.127 [4063] |■■■■■■■■■■■■■■■■■■■■■■
+0.151 [75] |
+0.175 [199] |■
+0.199 [1756] |■■■■■■■■■
+0.224 [230] |■
+0.248 [85] |
+Latency distribution:
+  10% in 0.0855 secs
+  25% in 0.0958 secs
+  50% in 0.1014 secs
+  75% in 0.1091 secs
+  90% in 0.1866 secs
+  95% in 0.1946 secs
+  99% in 0.2027 secs
+Details (average, fastest, slowest):
+  DNS+dialup: 0.0001 secs, 0.0060 secs, 0.2477 secs
+  DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
+  req write:  0.0000 secs, 0.0000 secs, 0.0052 secs
+  resp wait:  0.1090 secs, 0.0058 secs, 0.2282 secs
+  resp read:  0.0001 secs, 0.0000 secs, 0.0062 secs
+Status code distribution:
+  [200] 15000 responses
+

Case 1-2. Istio (w/o any tuning)

  • Istio (w/o any tuning): 1527 Requests / sec
# concurrent user: 100
+# total request: 15000
+
+hyungwook@MacBook-Pro  ~  hey -n 15000 -c 100 http://20.196.248.118/
+Summary:
+  Total:  9.8192 secs
+  Slowest:  0.2723 secs
+  Fastest:  0.0055 secs
+  Average:  0.0639 secs
+  Requests/sec: 1527.6172
+  Total data: 3804546 bytes
+  Size/request: 253 bytes
+Response time histogram:
+0.006 [1] |
+0.032 [1005] |■■■■■■■
+0.059 [6144] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.086 [5457] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.112 [1887] |■■■■■■■■■■■■
+0.139 [337] |■■ 0.166 [55] | 0.192 [5] | 0.219 [34] | 0.246 [60] | 0.272 [15] |
+Latency distribution:
+  10% in 0.0360 secs
+  25% in 0.0467 secs
+  50% in 0.0603 secs
+  75% in 0.0772 secs
+  90% in 0.0940 secs
+  95% in 0.1051 secs
+  99% in 0.1447 secs
+Details (average, fastest, slowest):
+  DNS+dialup: 0.0002 secs, 0.0055 secs, 0.2723 secs
+  DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
+   req write:  0.0000 secs, 0.0000 secs, 0.0063 secs
+  resp wait:  0.0636 secs, 0.0055 secs, 0.2723 secs
+  resp read:  0.0001 secs, 0.0000 secs, 0.0081 secs
+Status code distribution:
+  [200] 15000 responses
+

Case 2-1. Consul w/ resource allocation

  • Consul w/ resource allocation: 1860 Requests / sec (Case 1-2. Istio 1527 Requests / sec 대비 약 22% 빠름 )

    • Ingress Gateway: resources.limits 와 resources.requests 의 cpu / memory 를 각각 250m / 500Mi 로 수정
    # kubectl edit deployments consul-client-ingress-gateway -nconsul
    +# from 120th line..
    +
    +        name: ingress-gateway
    +        ports:
    +        - containerPort: 21000
    +          name: gateway-health
    +          protocol: TCP
    +        - containerPort: 8080
    +          name: gateway-0
    +          protocol: TCP
    +        - containerPort: 9090
    +          name: gateway-1
    +          protocol: TCP
    +        readinessProbe:
    +          failureThreshold: 3
    +          initialDelaySeconds: 10
    +          periodSeconds: 10
    +          successThreshold: 1
    +          tcpSocket:
    +            port: 21000
    +          timeoutSeconds: 5
    +        resources:
    +          limits:
    +            cpu: 250m
    +            memory: 500Mi
    +          requests:
    +            cpu: 250m
    +            memory: 500Mi
    +
    • Sidecar: resources.limits 와 resources.requests 의 cpu / memory 를 각각 250m / 500Mi 로 수정
    # from 177th line..
    +
    +        name: consul-sidecar
    +        resources:
    +          limits:
    +            cpu: 25m
    +            memory: 500Mi
    +          requests:
    +            cpu: 250m
    +            memory: 250Mi
    +
    • Test
    hyungwook@MacBook-Pro  ~  hey -n 15000 -c 100 http://20.200.225.63:8080/
    +Summary:
    +  Total:  8.0605 secs
    +  Slowest:  0.2963 secs
    +  Fastest:  0.0049 secs
    +  Average:  0.0525 secs
    +  Requests/sec: 1860.9347
    +  Total data: 3867565 bytes
    +  Size/request: 257 bytes
    +Response time histogram:
    +0.005 [1] |
    +0.034 [3890] |■■■■■■■■■■■■■■■■■■■■■■■■
    +0.063 [6524] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.092 [3937] |■■■■■■■■■■■■■■■■■■■■■■■■
    +0.121 [496] |■■■
    +0.151 [52] |
    +0.180 [0] |
    +0.209 [10] |
    +0.238 [50] |
    +0.267 [0] |
    +0.296 [40] |
    +Latency distribution:
    +  10% in 0.0237 secs
    +  25% in 0.0333 secs
    +  50% in 0.0506 secs
    +  75% in 0.0668 secs
    +  90% in 0.0803 secs
    +  95% in 0.0902 secs
    +  99% in 0.1224 secs
    +Details (average, fastest, slowest):
    +  DNS+dialup: 0.0001 secs, 0.0049 secs, 0.2963 secs
    +  DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
    +  req write:  0.0000 secs, 0.0000 secs, 0.0165 secs
    +  resp wait:  0.0521 secs, 0.0049 secs, 0.2771 secs
    +  resp read:  0.0002 secs, 0.0000 secs, 0.0105 secs
    +Status code distribution:
    +  [200] 15000 responses
    +

Case 2-2. Consul w/ Istio Resource Allocation 과 동률 구성

  • Istio 의 Resource Allocation 과 동률 구성: 3002 Requests / sec (Case 1-2. Istio 1527 Requests / sec 대비 약 2배 빠름 )

    • Istio Ingress Gateway resource allocation 확인 후 Consul 에 동일하게 적용
    # kubectl edit deployment istio-ingressgateway -nistio-system
    +# from 149th line..
    +
    +        resources:
    +          limits:
    +            cpu: "2"
    +            memory: 1Gi
    +          requests:
    +            cpu: 10m
    +            memory: 40Mi
    +
    • Test
     
    +hyungwook@MacBook-Pro  ~  hey -n 15000 -c 100 http://20.200.225.63:8080/
    +Summary:
    +  Total:  4.9955 secs
    +  Slowest:  0.2424 secs
    +  Fastest:  0.0059 secs
    +  Average:  0.0322 secs
    +  Requests/sec: 3002.6970
    +  Total data: 3864684 bytes
    +  Size/request: 257 bytes
    +Response time histogram:
    +0.006 [1] |
    +0.030 [8452] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.053 [5382] |■■■■■■■■■■■■■■■■■■■■■■■■■
    +0.077 [772] |■■■■
    +0.100 [80] |
    +0.124 [112] |■
    +0.148 [86] |
    +0.171 [111] |■
    +0.195 [3] |
    +0.219 [0] |
    +0.242 [1] |
    +Latency distribution:
    +  10% in 0.0170 secs
    +  25% in 0.0216 secs
    +  50% in 0.0278 secs
    +  75% in 0.0361 secs
    +  90% in 0.0490 secs
    +  95% in 0.0614 secs
    +  99% in 0.1331 secs
    +Details (average, fastest, slowest):
    +  DNS+dialup: 0.0001 secs, 0.0059 secs, 0.2424 secs
    +  DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
    +  req write:  0.0000 secs, 0.0000 secs, 0.0068 secs
    +  resp wait:  0.0319 secs, 0.0058 secs, 0.2424 secs
    +  resp read:  0.0001 secs, 0.0000 secs, 0.0064 secs
    +Status code distribution:
    +  [200] 15000 responses
    +
+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/index.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/index.html new file mode 100644 index 0000000000..04e4271585 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Performance | docmoa + + + + + +
본문으로 건너뛰기

Performance

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/index.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/index.html new file mode 100644 index 0000000000..d4be58c1ee --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Tracing | docmoa + + + + + +
본문으로 건너뛰기

Tracing

1분 미만

+ + + diff --git a/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/jaeger_tracing.html b/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/jaeger_tracing.html new file mode 100644 index 0000000000..4a3a5bac38 --- /dev/null +++ b/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/jaeger_tracing.html @@ -0,0 +1,269 @@ + + + + + + + + + + Jaeger를 활용한 Consul Service Mesh Tracing | docmoa + + + + + +
본문으로 건너뛰기

Jaeger를 활용한 Consul Service Mesh Tracing

유형욱약 3 분ConsulJaegerTracingOpenTelemetryIstioIngressGatewayKubetenetesK8s

Jaeger를 활용한 Consul Service Mesh Tracing

0. 사전 요구사항

1) Consul Install

Jaeger 연동을 위해 Consul on K8s 환경을 구성합니다. 해당 가이드의 경우에는 여기를 참고하세요.

(1) 시크릿 생성 - 라이센스

  • 라이센스 파일 생성 및 시크릿 생성
# license파일 생성
+vi consul.lic
+
+# 생성한 license파일로 secret 생성
+kubectl create secret generic license --from-file='key=./consul.lic'
+

(2) 시크릿 생성 - Gossip Key

  • consul-gossip-encryption-key 시크릿 생성
GOSSIP_KEY="VeQ8CHV3sDY/bHCseXC7PGXNTSXtWWvOzQKAaFFo9oE="
+kubectl patch secret consul-gossip-encryption-key -n consul --patch='{"stringData":{"key": "$GOSSIP_KEY"}}'
+

(3) Consul Helm 배포

values.yaml 파일 수정 및 배포합니다.

  • values.yaml 파일 예시
global:
+  name: consul
+  datacenter: dc1
+  logLevel: "debug"
+  logJSON: false
+  image: hashicorp/consul-enterprise:1.12.3-ent
+  gossipEncryption:
+    autoGenerate: true
+  tls:
+    enabled: false
+    enableAutoEncrypt: false
+    verify: false
+    httpsOnly: false
+  imageEnvoy: envoyproxy/envoy:v1.22-latest
+  enterpriseLicense:
+    secretName: license
+    secretKey: key
+server:
+  replicas: 3
+client:
+  enabled: true
+  exposeGossipPorts: true
+  extraConfig: |
+    {
+      "log_level": "debug"
+    }
+  grpc: true
+ui:
+  enabled: true
+  service:
+    type: LoadBalancer
+connectInject:
+  enabled: true
+controller:
+  enabled: true
+  #terminatingGateways:
+  #enabled: true
+  #apiGateway:
+  #enabled: true
+  #image: "hashicorp/consul-api-gateway:latest"
+ingressGateways:
+  enabled: true
+  gateways:
+  - name: ingress-gateway
+    service:
+      type: LoadBalancer
+      ports:
+      - port: 5000
+

1. Cert-Manager 설치open in new window

Jaeger를 설치할 때 cert-manager 설치가 필수적으로 요구됩니다.

Since version 1.31 the Jaeger Operator uses webhooks to validate Jaeger custom resources (CRs). This requires an installed version of the cert-manager.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml
+
  • cert-manager 파드 배포확인
kubectl get pods -n cert-manager
+NAME                                       READY   STATUS    RESTARTS   AGE
+cert-manager-6544c44c6b-z76nd              1/1     Running   0          25s
+cert-manager-cainjector-5687864d5f-pdzbn   1/1     Running   0          25s
+cert-manager-webhook-785bb86798-v6phx      1/1     Running   0          25s
+

2. Jaeger 설치open in new window

Tracing을 위해 Jaeger 공식 문서를 참고하여 K8s 환경에 Jaeger Operator를 설치합니다.

(1) Role Binding 배포(선택)

💡참고 : 동일 네임스페이스 배포할 경우 해당 과정은 생략!

  • YAML 생성 : consul-jaeger RoleBinding 생성
# role-binding.yaml
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: jaeger-operator-in-myproject
+  namespace: consul-jaeger
+subjects:
+- kind: ServiceAccount
+  name: jaeger-operator
+  namespace: observability
+roleRef:
+  kind: Role
+  name: jaeger-operator
+  apiGroup: rbac.authorization.k8s.io
+
  • YAML 배포
kubectl apply -f role-binding.yaml
+

(2) Jaeger Operator 배포(CRD 설치)

Jaeger Operator를 배포하기 위한 observbility 네임스페이스를 생성합니다. 이때, 별도의 네임스페이스에 배포할 경우에는 다운받은 .yaml에 설저된 네임스페이스명을 변경하셔야 합니다. 참고open in new window

  • 네임스페이스 생성 및 각종 리소스 배포
kubectl create namespace observability
+kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.37.0/jaeger-operator.yaml -n observability
+
  • 배포된 jaeger-operator 확인
kubectl get deployment jaeger-operator -n observability 
+NAME              READY   UP-TO-DATE   AVAILABLE   AGE
+jaeger-operator   1/1     1            1           2m30s
+

(3) Jaeger CR 배포 - AllInOne

실제 K8s 환경에서 Jaeger리소스 생성을 위해 다음 .yaml 파일을 배포합니다. 본 문서에서는 편의상 AllInOne 이미지를 사용하여 배포합니다.

AllInOne 이미지는 프로덕션 환경에서 사용하기에는 적합하지 않으며, Dev 또는 Test 목적으로 사용해야 합니다. (배포전략 참고)open in new window

The simplest possible way to create a Jaeger instance is by creating a YAML file like the following example. This will install the default AllInOne strategy, which deploys the “all-in-one” image (agent, collector, query, ingester, Jaeger UI) in a single pod, using in-memory storage by default.

  • YAML 생성
# simplest.yaml
+apiVersion: jaegertracing.io/v1
+kind: Jaeger
+metadata:
+  name: simplest
+  namespace: observability
+
# simplest-debug.yaml
+apiVersion: jaegertracing.io/v1
+kind: Jaeger
+metadata:
+  name: simplest
+  namespace: observability
+spec:
+  strategy: allInOne
+  allInOne:
+    image: jaegertracing/all-in-one:latest
+    options:
+      log-level: debug
+
  • YAML 배포
kubectl apply -f simplest.yaml
+
  • 배포 로그 확인
    • {"level":"info","ts":1661997111.1498919,"caller":"healthcheck/handler.go:129","msg":"Health Check state change","status":"ready"} 로그를 통해서 정상적인 상태 확인됨
kubectl logs -l app.kubernetes.io/instance=simplest -n consul-jaeger
+{"level":"info","ts":1661997111.149404,"caller":"channelz/funcs.go:340","msg":"[core][Channel #10] Channel Connectivity change to TRANSIENT_FAILURE","system":"grpc","grpc_log":true}
+{"level":"info","ts":1661997111.1495373,"caller":"app/static_handler.go:181","msg":"UI config path not provided, config file will not be watched"}
+{"level":"info","ts":1661997111.149864,"caller":"app/server.go:217","msg":"Query server started","http_addr":"[::]:16686","grpc_addr":"[::]:16685"}
+{"level":"info","ts":1661997111.1498919,"caller":"healthcheck/handler.go:129","msg":"Health Check state change","status":"ready"}
+{"level":"info","ts":1661997111.149912,"caller":"app/server.go:300","msg":"Starting GRPC server","port":16685,"addr":":16685"}
+{"level":"info","ts":1661997111.1499252,"caller":"channelz/funcs.go:340","msg":"[core][Server #9 ListenSocket #12] ListenSocket created","system":"grpc","grpc_log":true}
+{"level":"info","ts":1661997111.1499453,"caller":"app/server.go:281","msg":"Starting HTTP server","port":16686,"addr":":16686"}
+{"level":"info","ts":1661997112.150468,"caller":"channelz/funcs.go:340","msg":"[core][Channel #10 SubChannel #11] Subchannel Connectivity change to IDLE","system":"grpc","grpc_log":true}
+{"level":"info","ts":1661997112.1505697,"caller":"grpclog/component.go:71","msg":"[core]pickfirstBalancer: UpdateSubConnState: 0xc00082a700, {IDLE connection error: desc = \"transport: Error while dialing dial tcp :16685: connect: connection refused\"}","system":"grpc","grpc_log":true}
+{"level":"info","ts":1661997112.1505857,"caller":"channelz/funcs.go:340","msg":"[core][Channel #10] Channel Connectivity change to IDLE","system":"grpc","grpc_log":true}
+

(4) (선택) Jaeger Sidecar 배포 방식

Jaeger Auto Injection 및 Manaul Injection 활용방안을 가이드합니다. (3)에서 Jaeger리소스를 직접 배포했다면 생략하셔도 됩니다.

방안1. CRD 배포

해당 방안은 3)-(3)에서 작성한 방식으로, 관리되는 네임스페이스에 있는 애플리케이션의 tracing을 수행합니다.

방안2. Auto Injection - annotation 활용

해당 방안은 annotation 절에 "sidecar.jaegertracing.io/inject": "true" 를 기입하여 tracing 하고자 하는 애플리케이션에 sidecar auto-injection을 수행합니다.

  • tracing 할 애플리케이션 deployment yaml의 annotation 위치
metadata:
+  name: web-deployment
+  labels:
+    app: web
+  namespace: observability
+  annotations:
+    "sidecar.jaegertracing.io/inject": 'true'
+

방안3. Manual Injectio

tracing 하고자 하는 애플리케이션에 직접 sidecar를 붙혀 tracing 합니다.

      - name: jaeger-agent
+        image: jaegertracing/jaeger-agent:latest
+        imagePullPolicy: IfNotPresent
+        ports:
+          - containerPort: 5775
+            name: zk-compact-trft
+            protocol: UDP
+          - containerPort: 5778
+            name: config-rest
+            protocol: TCP
+          - containerPort: 6831
+            name: jg-compact-trft
+            protocol: UDP
+          - containerPort: 6832
+            name: jg-binary-trft
+            protocol: UDP
+          - containerPort: 14271
+            name: admin-http
+            protocol: TCP
+        args:
+          - --reporter.grpc.host-port=dns:///simplest-collector-headless.observability:14250
+          - --reporter.type=grpc
+

(5) Jaeger UI 접속

기본적을 Jaeger UI는 ClusterIP로 배포됩니다. 외부에서 접속하기 위해 다음 몇 가지 방안을 제시합니다.

참고 : 본 문서에서는 편의상 LoadBalancer 타입으로 변경하는 샘플을 제공합니다.

  • Ingress
  • LoadBalancer
  • Port-Forwarding

기본적으로 Jaeager UI는 16686 Port를 사용합니다. 필자는 편읜상 simplest-query 서비스를 LoadBalancer타입으로 변경하여 조회합니다.

spec:
+(중략)
+  ports:
+  - name: http-query
+    nodePort: 32731
+    port: 16686
+    protocol: TCP
+    targetPort: 16686
+  - name: grpc-query
+    nodePort: 31322
+    port: 16685
+    protocol: TCP
+    targetPort: 16685
+  selector:
+    app: jaeger
+    app.kubernetes.io/component: all-in-one
+    app.kubernetes.io/instance: simplest
+    app.kubernetes.io/managed-by: jaeger-operator
+    app.kubernetes.io/name: simplest
+    app.kubernetes.io/part-of: jaeger
+  sessionAffinity: None
+  type: LoadBalancer
+
  • Jaeger UI 예제
image-20221110190509279
image-20221110190509279

(참고) Jaeger와 다른 네임스페이스에 애플리케이션 사용할 경우

3. Consul Config 설정open in new window

(1) Ingress Gateway

apiVersion: consul.hashicorp.com/v1alpha1
+kind: IngressGateway
+metadata:
+  name: ingress-gateway
+  namespace: consul
+spec:
+  listeners:
+    - port: 5000
+      protocol: http
+      services:
+        - name: web
+          hosts: ['*']
+

(2) Service Defaults

apiVersion: consul.hashicorp.com/v1alpha1
+kind: ServiceDefaults
+metadata:
+  name: web
+spec:
+  protocol: "http"
+

(3) Proxy Defaults

ProxyDefaults 설정을 통해 Collector 서버에 대한 주소 및 Clustesr Name에 대해 선언합니다.

apiVersion: consul.hashicorp.com/v1alpha1
+kind: ProxyDefaults
+metadata:
+  name: global
+  namespace: consul
+spec:
+  config:
+    protocol: http
+    envoy_tracing_json: |
+      {
+        "http":{
+          "name":"envoy.tracers.zipkin",
+          "typedConfig":{
+            "@type":"type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
+            "collector_cluster":"simplest-collector",
+            "collector_endpoint_version":"HTTP_JSON",
+            "collector_endpoint":"/api/v2/spans",
+            "shared_span_context":false
+          }
+        }
+      }
+    envoy_extra_static_clusters_json: |
+      {
+        "connect_timeout":"3.000s",
+        "dns_lookup_family":"V4_ONLY",
+        "lb_policy":"ROUND_ROBIN",
+        "load_assignment":{
+          "cluster_name":"simplest-collector",
+          "endpoints":[
+            {
+              "lb_endpoints":[
+                {
+                  "endpoint":{
+                    "address":{
+                      "socket_address":{
+                        "address":"simplest-collector",
+                        "port_value":9411,
+                        "protocol":"TCP"
+                      }
+                    }
+                  }
+                }
+              ]
+            }
+          ]
+        },
+        "name":"simplest-collector",
+        "type":"STRICT_DNS"
+      }
+

99) 참고문서

+ + + diff --git a/04-HashiCorp/04-Consul/index.html b/04-HashiCorp/04-Consul/index.html new file mode 100644 index 0000000000..9759e4c10d --- /dev/null +++ b/04-HashiCorp/04-Consul/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 04 Consul | docmoa + + + + + +
본문으로 건너뛰기

04 Consul

1분 미만

+ + + diff --git a/04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.html b/04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.html new file mode 100644 index 0000000000..772d215d67 --- /dev/null +++ b/04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.html @@ -0,0 +1,423 @@ + + + + + + + + + + Boundary Install on Consul-Nomad | docmoa + + + + + +
본문으로 건너뛰기

Boundary Install on Consul-Nomad

약 2 분BoundaryInstall

Boundary Install on Consul-Nomad

1. Nomad namespace create

nomad namespace apply -description "Boundary" boundary
+

2. Postgresql setup

2.1 Postgresql job run

postgresql.nomad
job "postgresql" {
+  type = "service"
+  datacenters = ["hashistack"]
+  namespace = "boundary"
+
+  group "postgres" {
+    count = 1
+
+    volume "postgres-vol" {
+      type = "host"
+      read_only = false
+      source = "postgres-vol"
+    }
+
+    task "db" {
+      driver = "docker"
+
+      volume_mount {
+        volume = "postgres-vol"
+        destination = "/var/lib/postgresql/data"
+        read_only = false
+      }
+
+      config {
+        image = "postgres:13.2"
+        port_map {
+          pg = 5432
+        }
+      }
+      
+      env {
+        POSTGRES_PASSWORD = "postgres"
+        POSTGRES_USER = "postgres"
+        PGDATA = "/var/lib/postgresql/data/pgdata"
+      }
+
+      resources {
+        memory = 1024
+
+        network {
+          port "pg" {
+            static = 5432
+          }
+        }
+      }
+
+      service {
+        name = "postgresql"
+        tags = ["db", "boundary"]
+
+        port = "pg"
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "pg"
+        }
+      }
+    }
+  }
+}
+
nomad job run -namespace="boundary" postgresql.nomad
+

2.2 Posgresql init

# Login
+psql -h 172.28.128.11 -U postgres postgres
+
# <enter the password> postgres
+CREATE ROLE boundary WITH LOGIN PASSWORD 'PASSWORD';
+CREATE DATABASE boundary OWNER boundary;
+GRANT ALL PRIVILEGES ON DATABASE boundary TO boundary;
+ALTER USER boundary PASSWORD 'boundary';
+

3. Boundary database init

3.1 Create config file

</tmp/config.hcl>

disable_mlock = true
+
+controller {
+  name = "controller-0"
+  database {
+    url = "postgresql://boundary:boundary@172.28.128.11:5432/boundary?sslmode=disable"
+  }
+}
+
+kms "aead" {
+  purpose = "root"
+  aead_type = "aes-gcm"
+  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
+  key_id = "global_root"
+}
+
+kms "aead" {
+  purpose = "worker-auth"
+  aead_type = "aes-gcm"
+  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+  key_id = "global_worker-auth"
+}
+
+kms "aead" {
+    purpose   = "recovery"
+    aead_type = "aes-gcm"
+    key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+    key_id    = "global_recovery"
+}
+

3.2 Init database

boundary database init -config=/tmp/config.hcl
+

4. Boundary Job

4.1 Boundary Controller Job

boundary-controller.nomad
locals {
+  version = "0.6.2"
+  postgre_ip = "172.28.128.11"
+  postgre_port = "5432"
+}
+
+job "boundary-controller" {
+  type = "service"
+  datacenters = ["hashistack"]
+  namespace = "boundary"
+
+  group "controller" {
+    count = 1
+
+    network {
+      mode = "host"
+    }
+
+    task "migration" {
+      driver = "raw_exec"
+
+      env {
+        BOUNDARY_POSTGRES_URL = "postgresql://boundary:boundary@${local.postgre_ip}:${local.postgre_port}/boundary?sslmode=disable"
+      }
+      artifact {
+        source = "https://releases.hashicorp.com/boundary/${local.version}/boundary_${local.version}_linux_amd64.zip"
+      }
+      template {
+        data = <<EOH
+disable_mlock = true
+
+{{ range service "postgresql" }}
+controller {
+  name = "controller-0"
+  database {
+    url = "postgresql://boundary:boundary@{{ .Address }}:{{ .Port }}/boundary?sslmode=disable"
+  }
+}
+{{ end }}
+
+listener "tcp" {
+  address = "0.0.0.0:9200"
+  purpose = "api"
+  tls_disable = true
+}
+listener "tcp" {
+  address = "0.0.0.0:9201"
+  purpose = "cluster"
+  tls_disable = true
+}
+
+kms "aead" {
+  purpose = "root"
+  aead_type = "aes-gcm"
+  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
+  key_id = "global_root"
+}
+
+kms "aead" {
+  purpose = "worker-auth"
+  aead_type = "aes-gcm"
+  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+  key_id = "global_worker-auth"
+}
+
+kms "aead" {
+    purpose   = "recovery"
+    aead_type = "aes-gcm"
+    key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+    key_id    = "global_recovery"
+}
+EOH
+        destination = "local/config/config.hcl"
+      }
+      config {
+        command = "local/boundary"
+        args = ["database", "migrate", "-config", "local/config/config.hcl"]
+      }
+      lifecycle {
+        hook    = "prestart"
+        sidecar = false
+      }
+    }
+
+    task "controller" {
+      driver = "docker"
+
+      config {
+        image = "hashicorp/boundary:${local.version}"
+        port_map {
+          controller = 9200
+          cluster = 9201
+        }
+        mount {
+          type   = "bind"
+          source = "local/config"
+          target = "/boundary"
+        }
+        // network_mode = "boundary-net"
+        // network_aliases = [
+        //   "boundary-controller"
+        // ]
+      }
+      
+      template {
+        data = <<EOH
+disable_mlock = true
+
+{{ range service "postgresql" }}
+controller {
+  name = "controller-0"
+  database {
+    url = "postgresql://boundary:boundary@{{ .Address }}:{{ .Port }}/boundary?sslmode=disable"
+  }
+  public_cluster_addr = "{{ env "NOMAD_ADDR_cluster" }}"
+}
+{{ end }}
+
+listener "tcp" {
+  address = "0.0.0.0:9200"
+  purpose = "api"
+  tls_disable = true
+}
+listener "tcp" {
+  address = "0.0.0.0:9201"
+  purpose = "cluster"
+  tls_disable = true
+}
+
+kms "aead" {
+  purpose = "root"
+  aead_type = "aes-gcm"
+  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
+  key_id = "global_root"
+}
+
+kms "aead" {
+  purpose = "worker-auth"
+  aead_type = "aes-gcm"
+  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+  key_id = "global_worker-auth"
+}
+
+kms "aead" {
+    purpose   = "recovery"
+    aead_type = "aes-gcm"
+    key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+    key_id    = "global_recovery"
+}
+EOH
+        destination = "local/config/config.hcl"
+      }
+      
+      env {
+        // BOUNDARY_POSTGRES_URL = "postgresql://boundary:boundary@${local.postgre_ip}:${local.postgre_port}/boundary?sslmode=disable"
+        SKIP_SETCAP = true
+      }
+
+      resources {
+        cpu    = 300
+        memory = 500
+        network {
+          port "controller" {
+            static = 9200
+          }
+          port "cluster" {
+            static = 9201
+          }
+        }
+      }
+
+      service {
+        name = "boundary"
+        tags = ["cluster"]
+
+        port = "cluster"
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "cluster"
+        }
+      }
+    }
+  }
+}
+
nomad job run -namespace="boundary" boundary-controller.nomad
+

4.2 Boundary worker Job

boundary-controller.nomad
locals {
+  version = "0.6.2"
+}
+
+job "boundary-worker" {
+  type = "service"
+  datacenters = ["hashistack"]
+  namespace = "boundary"
+  
+  group "worker" {
+    count = 1
+
+    scaling {
+      enabled = true
+      min = 1
+      max = 3
+    }
+
+    network {
+      mode = "host"
+    }
+    
+    task "worker" {
+      driver = "docker"
+
+      config {
+        image = "hashicorp/boundary:${local.version}"
+        port_map {
+          proxy = 9202
+        }
+        volumes = [
+          "local/boundary:/boundary/",
+        ]
+        // network_mode = "boundary-net"
+      }
+      
+      template {
+        data = <<EOH
+disable_mlock = true
+
+listener "tcp" {
+  address = "0.0.0.0:9202"
+  purpose = "proxy"
+  tls_disable = true
+}
+
+worker {
+  name = "worker-0"
+  controllers = [
+{{ range service "boundary" }}
+		"{{ .Address }}",
+{{ end }}
+  ]
+  public_addr = "{{ env "NOMAD_ADDR_proxy" }}"
+}
+
+kms "aead" {
+  purpose = "root"
+  aead_type = "aes-gcm"
+  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
+  key_id = "global_root"
+}
+
+kms "aead" {
+  purpose = "worker-auth"
+  aead_type = "aes-gcm"
+  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+  key_id = "global_worker-auth"
+}
+
+kms "aead" {
+  purpose   = "recovery"
+  aead_type = "aes-gcm"
+  key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+  key_id    = "global_recovery"
+}
+EOH
+        destination = "/local/boundary/config.hcl"
+      }
+      
+      env {
+        // BOUNDARY_POSTGRES_URL = "postgresql://boundary:boundary@172.28.128.11:5432/boundary?sslmode=disable"
+        SKIP_SETCAP = true
+      }
+
+      resources {
+        network {
+          port "proxy" {}
+        }
+      }
+    }
+  }
+}
+
nomad job run -namespace="boundary" boundary-worker.nomad
+
+ + + diff --git a/04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.html b/04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.html new file mode 100644 index 0000000000..d9db915053 --- /dev/null +++ b/04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.html @@ -0,0 +1,118 @@ + + + + + + + + + + Boundary Run Dev Mode on Nomad Job | docmoa + + + + + +
본문으로 건너뛰기

Boundary Run Dev Mode on Nomad Job

1분 미만BoundaryInstall

Boundary Run Dev Mode on Nomad Job

1. Job sample

locals {
+  version = "0.8.1"
+  private_ip = "192.168.0.27"
+  public_ip = "11.129.13.30"
+}
+
+job "boundary-dev" {
+  type = "service"
+  datacenters = ["home"]
+  namespace = "boundary"
+
+  constraint {
+    attribute = "${attr.os.name}"
+    value     = "raspbian"
+  }
+
+  group "dev" {
+    count = 1
+
+    ephemeral_disk { sticky  = true }
+
+    network {
+      mode = "host"
+      port "api" {
+        static = 9200
+        to = 9200
+      }
+      port "cluster" {
+        static = 9201
+        to = 9201
+      }
+      port "worker" {
+        static = 9202
+        to = 9202
+      }
+    }
+
+    task "dev" {
+      driver = "raw_exec"
+
+      env {
+        BOUNDARY_DEV_CONTROLLER_API_LISTEN_ADDRESS = local.private_ip
+        BOUNDARY_DEV_CONTROLLER_CLUSTER_LISTEN_ADDRESS = "0.0.0.0"
+        BOUNDARY_DEV_WORKER_PUBLIC_ADDRESS = local.public_ip
+        BOUNDARY_DEV_WORKER_PROXY_LISTEN_ADDRESS = local.private_ip
+        BOUNDARY_DEV_PASSWORD = "password"
+      }
+
+      // artifact {
+      //   source = "https://releases.hashicorp.com/boundary/${local.version}/boundary_${local.version}_linux_arm.zip"
+      // }
+
+      config {
+        command = "boundary"
+        args = ["dev"]
+      }
+
+      resources {
+        cpu    = 500
+        memory = 500
+      }
+
+      service {
+        name = "boundary"
+        tags = ["cluster"]
+
+        port = "cluster"
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "api"
+        }
+      }
+    }
+  }
+}
+

2. ENV

  • BOUNDARY_DEV_WORKER_PUBLIC_ADDRESS : Worker public ip config, (or -worker-public-addr flag)
+ + + diff --git a/04-HashiCorp/05-Boundary/01-Install/index.html b/04-HashiCorp/05-Boundary/01-Install/index.html new file mode 100644 index 0000000000..86b17ca34e --- /dev/null +++ b/04-HashiCorp/05-Boundary/01-Install/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Install | docmoa + + + + + +
본문으로 건너뛰기

01 Install

1분 미만

+ + + diff --git a/04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.html b/04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.html new file mode 100644 index 0000000000..9d39da298d --- /dev/null +++ b/04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.html @@ -0,0 +1,275 @@ + + + + + + + + + + Configure Boudary using Terraform | docmoa + + + + + +
본문으로 건너뛰기

Configure Boudary using Terraform

약 2 분BoundaryTerraformConfig

Configure Boudary using Terraform

main.tfopen in new window

provider "boundary" {
+  addr             = "http://172.28.128.11:9200"
+//   recovery_kms_hcl = <<EOT
+// kms "aead" {
+//     purpose   = "recovery"
+//     aead_type = "aes-gcm"
+//     key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
+//     key_id    = "global_recovery"
+// }
+// EOT
+    auth_method_id = "ampw_U6FXouWRDK"
+    password_auth_method_login_name = "admin"
+    password_auth_method_password = "POByMKtvabYS1wtRHLgZ"
+}
+
+resource "boundary_scope" "global" {
+  global_scope = true
+  scope_id     = "global"
+  description  = "Global scope"
+}
+
+// Scope HashiStack
+resource "boundary_scope" "corp" {
+  name                     = "hashistack"
+  description              = "hashistack scope"
+  scope_id                 = boundary_scope.global.id
+  auto_create_admin_role   = true
+  auto_create_default_role = true
+}
+
+resource "boundary_auth_method" "corp_password" {
+  name        = "corp_password_auth_method"
+  description = "Password auth method"
+  type        = "password"
+  scope_id    = boundary_scope.corp.id
+}
+
+resource "boundary_account" "user" {
+  for_each       = var.users
+  name           = each.key
+  description    = "User account for my user"
+  type           = "password"
+  login_name     = lower(each.key)
+  password       = "password"
+  auth_method_id = boundary_auth_method.corp_password.id
+}
+
+resource "boundary_user" "users" {
+  for_each    = var.users
+  name        = each.key
+  description = "User resource for ${each.key}"
+  account_ids = ["${boundary_account.user[each.key].id}"]
+  scope_id = boundary_scope.corp.id
+}
+
+resource "boundary_group" "admin" {
+  name        = "admin"
+  description = "Organization group for readonly users"
+  member_ids  = [for user in boundary_user.users : user.id]
+  scope_id    = boundary_scope.corp.id
+}
+
+resource "boundary_user" "readonly_users" {
+  for_each    = var.readonly_users
+  name        = each.key
+  description = "User resource for ${each.key}"
+  scope_id    = boundary_scope.corp.id
+}
+
+resource "boundary_group" "readonly" {
+  name        = "read-only"
+  description = "Organization group for readonly users"
+  member_ids  = [for user in boundary_user.readonly_users : user.id]
+  scope_id    = boundary_scope.corp.id
+}
+
+resource "boundary_role" "corp_admin" {
+  name        = "corp_admin"
+  description = "Corp Administrator role"
+  principal_ids = concat(
+    [for user in boundary_user.users: user.id]
+  )
+  grant_strings   = ["id=*;type=*;actions=create,read,update,delete"]
+  scope_id = boundary_scope.corp.id
+}
+
+resource "boundary_role" "organization_readonly" {
+  name        = "Read-only"
+  description = "Read-only role"
+  principal_ids = [boundary_group.readonly.id]
+  grant_strings = ["id=*;type=*;actions=read"]
+  scope_id    = boundary_scope.corp.id
+}
+
+resource "boundary_scope" "core_infra" {
+  name                   = "core_infra"
+  description            = "My first project!"
+  scope_id               = boundary_scope.corp.id
+  auto_create_admin_role = true
+}
+
+resource "boundary_host_catalog" "backend_servers" {
+  name        = "backend_servers"
+  description = "Backend servers host catalog"
+  type        = "static"
+  scope_id    = boundary_scope.core_infra.id
+}
+
+resource "boundary_host" "ssh_servers" {
+  for_each        = var.ssh_server_ips
+  type            = "static"
+  name            = "ssh_server_service_${each.value}"
+  description     = "ssh server host"
+  address         = each.key
+  host_catalog_id = boundary_host_catalog.backend_servers.id
+}
+
+resource "boundary_host" "backend_servers" {
+  for_each        = var.backend_server_ips
+  type            = "static"
+  name            = "backend_server_service_${each.value}"
+  description     = "Backend server host"
+  address         = each.key
+  host_catalog_id = boundary_host_catalog.backend_servers.id
+}
+
+resource "boundary_host_set" "ssh_servers" {
+  type            = "static"
+  name            = "ssh_servers"
+  description     = "Host set for ssh servers"
+  host_catalog_id = boundary_host_catalog.backend_servers.id
+  host_ids        = [for host in boundary_host.ssh_servers : host.id]
+}
+
+resource "boundary_host_set" "backend_servers" {
+  type            = "static"
+  name            = "backend_servers"
+  description     = "Host set for backend servers"
+  host_catalog_id = boundary_host_catalog.backend_servers.id
+  host_ids        = [for host in boundary_host.backend_servers : host.id]
+}
+
+# create target for accessing backend servers on port :8000
+resource "boundary_target" "backend_servers_service" {
+  type         = "tcp"
+  name         = "backend_server"
+  description  = "Backend service target"
+  scope_id     = boundary_scope.core_infra.id
+  default_port = "8080"
+
+  host_set_ids = [
+    boundary_host_set.backend_servers .id
+  ]
+}
+
+# create target for accessing backend servers on port :22
+resource "boundary_target" "backend_servers_ssh" {
+  type         = "tcp"
+  name         = "ssh_server"
+  description  = "Backend SSH target"
+  scope_id     = boundary_scope.core_infra.id
+  // default_port = "22"
+
+  host_set_ids = [
+    boundary_host_set.ssh_servers.id
+  ]
+}
+
+// anonymous
+resource "boundary_role" "global_anon_listing" {
+  scope_id = boundary_scope.global.id
+  grant_strings = [
+    "id=*;type=auth-method;actions=list,authenticate",
+    "type=scope;actions=list",
+    "id={{account.id}};actions=read,change-password"
+  ]
+  principal_ids = ["u_anon"]
+}
+
+resource "boundary_role" "org_anon_listing" {
+  scope_id = boundary_scope.corp.id
+  grant_strings = [
+    "id=*;type=auth-method;actions=list,authenticate",
+    "type=scope;actions=list",
+    "id={{account.id}};actions=read,change-password"
+  ]
+  principal_ids = ["u_anon"]
+}
+
+output "corp_auth_method_id" {
+    value = "boundary authenticate password -auth-method-id ${boundary_auth_method.corp_password.id} -login-name ${boundary_account.user["gslee"].login_name} -password ${boundary_account.user["gslee"].password}"
+}
+

variable.tfopen in new window

variable "addr" {
+  default = "http://172.28.128.11:9200"
+}
+
+variable "users" {
+  type = set(string)
+  default = [
+    "gslee",
+    "Jim",
+    "Mike",
+    "Todd",
+    "Jeff",
+    "Randy",
+    "Susmitha"
+  ]
+}
+
+variable "readonly_users" {
+  type = set(string)
+  default = [
+    "Chris",
+    "Pete",
+    "Justin"
+  ]
+}
+
+variable "ssh_server_ips" {
+  type = set(string)
+  default = [
+    "172.28.128.11"
+  ]
+}
+
+variable "backend_server_ips" {
+  type = set(string)
+  default = [
+    "172.28.128.11",
+    "172.28.128.50",
+    "172.28.128.60",
+    "172.28.128.61",
+    "172.28.128.70",
+  ]
+}
+
+ + + diff --git a/04-HashiCorp/05-Boundary/02-Config/index.html b/04-HashiCorp/05-Boundary/02-Config/index.html new file mode 100644 index 0000000000..ab5109c7e9 --- /dev/null +++ b/04-HashiCorp/05-Boundary/02-Config/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Config | docmoa + + + + + +
본문으로 건너뛰기

02 Config

1분 미만

+ + + diff --git a/04-HashiCorp/05-Boundary/index.html b/04-HashiCorp/05-Boundary/index.html new file mode 100644 index 0000000000..958b18747b --- /dev/null +++ b/04-HashiCorp/05-Boundary/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Boundary | docmoa + + + + + +
본문으로 건너뛰기

05 Boundary

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/index.html b/04-HashiCorp/06-Vault/01-Information/index.html new file mode 100644 index 0000000000..25b4b8ea8b --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Information | docmoa + + + + + +
본문으로 건너뛰기

01 Information

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/kmip-faq.html b/04-HashiCorp/06-Vault/01-Information/kmip-faq.html new file mode 100644 index 0000000000..c540d1ce3d --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/kmip-faq.html @@ -0,0 +1,73 @@ + + + + + + + + + + Vault KMIP FAQ | docmoa + + + + + +
본문으로 건너뛰기

Vault KMIP FAQ

hashicat(MZC), chadness12(MZC)약 2 분vaultkmip

Vault KMIP FAQ

KMIP 적용 흐름도
KMIP 적용 흐름도

Client 인증서의 유효 기간

기본 설정시 1,209,600초(2주)의 유효 기간을 갖게 되며, 설정에 따라 긴 유효시간의 적용이 가능합니다. (옵션 : deault_tls_client_ttl)
설정은 상기 도식화한 절차 중 "2. kmip 기본 config" 단계에 적용 가능하며. 이는 KMIP 적용 흐름도의 "4. kmip scope, role 정의" 단계에서 override 할 수 있습니다.

# 예시 - 1년의 유효기간을 설정
+vault write kmip/config \
+  listen_addrs=0.0.0.0:5696 \
+  tls_ca_key_type="rsa" \
+  tls_ca_key_bits=2048 \
+  server_ips=192.168.1.101,192.168.1.102 \
+  default_tls_client_ttl=31536000
+

KMIP 시크릿 엔진의 config 변경시 CA의 변경 여부

일반적인 설정은 CA가 변경되지 않으나, CA에 직접적인 옵션 즉, 키 강도, 키 길이, 알고리즘 등을 변경하시면 CA가 재발급되므로 이 경우 영향이 있습니다.
server_ip, server_hostnames를 추가하는 것으로는 CA는 변경되지 않습니다. 이는 SAN 인증서 발급에 영향이지만, vault에서 server인증서를 외부로 export 하는 과정이 없기 때문입니다.

#키강도 변경
+vault write kmip/config \
+  listen_addrs=0.0.0.0:5696 \
+  tls_ca_key_type="rsa" \
+  tls_ca_key_bits=4096 
+

SAN 발급시 server_hostnames 기입 정보

https 로 호출이 가능한 명칭이면 상관없습니다. dns로 호출시에는 네임서버의 zone의 설정에 따라 다를터 이나 fqdn으로 기입, 혹은 https 프로토콜로 호출만 가능하다면 호스트네임이어도 됩니다.

주의할 점은 http(s) 프로토콜로 호출시에 request/response 헤더에 요청될 host 명과 이것이 인증서의 CN(혹은 SAN 확장 필드)와 매칭되는지의 여부 입니다.

CA 발급시 servername_hostnames에 기입되어야하는 서버의 리스트 정보

Vault 서버 정보를 기입해주시면 됩니다. (e.g. vault.mysite.com)

주의

vault의 KMIP 통신에 앞서 server-client의 상호 인증인 two way ssl 과정을 거치므로 각자의 cn을 판단하기 위해 개별 대상의 기입이 필요할 것으로 보여지나, operation role 부여시에 해당 권한에 대한 제어가 수행되므로 Client 의 값을 기입할 필요는 없습니다.

인증서 재발급시 이전 인증서의 만료 여부

기존 인증서를 강제로 revoke 시키지 않으면 유효기간(ttl)동안 문제 없습니다.

Secrets 별 port 바인딩

KMIP이 활성화된 secrets engine이 다르다면, port는 활성화된 엔진 마다 개별 바인딩이 필요합니다.

KMIP 프로토콜로 교환되는 key의 라이프사이클

Vault는 KMIP 으로 발급되는 키의 라이프사이클에 대한 제어권을 클라이언트에 포괄적으로 위임하고 있습니다.

scope 내의 Role을 부여하는 과정 중 Role에 operation_ 접두사로 시작되는 권한을 부여하여, 해당 role에서 발급한 credential을 통해 해당 role에 부여된 KMIP에 대한 권한을 행사합니다. 요약하자면, Vault서버에서 KMIP자체의 라이프사이클이 아닌 KMIP의 권한을 부여하는 우회적인 관리책을 제공하고 있습니다.

따라서, 해당 role에서 KMIP 키에 대해 별다른 작업을 요청하지 않는다면, vault 내에서 키를 만료 혹은 수정(rotation)하는 작업을 수행하지 않으며,
해당 키는 지속적으로 사용가능합니다.

현 버전 vault의 KMIP에 대한 지원 정보는 현재 KMIP profile 1.4 표준을 따르고 있음이 퍼블릭 문서로 오픈되어있습니다.

(참고 - vault 가 구현중인 KMIP profile 정보 : https://developer.hashicorp.com/vault/docs/secrets/kmip-profilesopen in new window)

Vault 로그에서 KMIP에 대한 액세스 로그도 남길 수 있는지

Vault의 로그는 Vault 플랫폼 자체에 대한 이벤트 기록 만을 수행하여, 세밀한 키 처리에 대한 부분은 audit log 라는 기능을 enable 함으로써 이벤트 처리를 모니터링 할수 있습니다.

Vault 로그 파일 분리해서 로테이션 설정할 수 있는지

어떠한 추가 설정이 없다면, 기본적으로 시스템 로그에 기록되도록 되어있습니다. 말씀하신대로 해당 로그 파일을 별도 분리가능하며, 로테이션에 대한 설정도 용량/시간/갯수의 제한을 설정가능합니다.

(참고 : https://developer.hashicorp.com/vault/docs/configuration#log_rotate_durationopen in new window)

KMIP 통신 시 클러스터의 모든 요청이 리더로 포워딩 되는지, 아니면 요청 종류에 따라 다른지

해당 동작은 licence 정책에 따라 분화됩니다. 현재 라이선스로는 Read/Write 모두를 리더 노드가 처리하도록 되어있고. 요청을 처리받은 follwer 노드는 처리를 리더 노드로 포워딩합니다.

Read/Write를 모든 Vault에서 처리하는 동작은 Read Replica라는 동작으로 Vault Enterprise의 Performance 라이선스에 해당됩니다.

만약 KMIP 사용이 가능한 라이선스인 ADP-KM 만 적용 된 경우, R/W가 빈번하지 않지만, 높은 비용의 라이선스 정책에서만 사용가능하던 ADP(Advanced Data Protection)라이선스의 KMIP 기능을 좀 더 비용합리적으로 사용가능하게끔, 해당 기능을 제거하고 좀 더 낮은 비용으로 책정된 라이선스입니다.

(현재 라이선스 정책에 대한 상세 참조 링크 : https://developer.hashicorp.com/vault/docs/enterprise/license/faq#q-how-can-the-new-adp-modules-be-purchased-and-what-features-are-customer-entitled-to-as-part-of-that-purchaseopen in new window)

매뉴얼하게 Vault 리더를 변경할 수 있는 방법이 있는지

vault에서 step-down 이라는 작업으로 칭합니다. 이때, 리더를 확인하여 다른 노드로 리더 권한을 이전하는 작업이 필요합니다.

해당 작업이 없이 리더를 shutdown하게 되면 자체적인 리더 선출과정이 발생하여 약 5~10초정도의 순단이 발생가능하기 때문에. maintanance 작업전엔 leader를 확인 후 작업의 선행이 꼭 필요하겠습니다.

Vault 업그레이드, 라이선스 변경(수동) 같은 작업 절차

이미 사용중인 경우, 다음과 같이 진행됩니다.

  1. 오토백업 설정 추가
  2. 스텝다운
  3. 키 마이그레이션
  4. 정상화 모니터링

다음은 구성된 Vault 클러스터 예시 이며, health check config는 이해를 돕기위한 설정 입니다.

Sample Vault Cluster
Sample Vault Cluster

작업간 rolling update 형태로 노드를 순환적 재기동하게되는데, LB의 health check 인터벌 때문에 해당노드가 다운되었음에도 노드를 신뢰하여 포워딩 하는 구간이 발생하게 됩니다.

  1. vault follower node의 확인

  2. 작업 대상인 follower node를 GSLB 타겟에서 제거
    2.1 제거된 follower node를 셧다운 후 키 마이그레이션 작업 후 GSLB의 타겟에 추가

  3. 다음 follower node에 대해 "2~3"작업 반복
    3.1. follower node 기동 후 정상적인 follower로서 join 되었는지 확인

  4. leader node를 GSLB 타겟에서 제거
    4-1. 제거된 leader node를 "step-down"하여 leader role을 follower로 마이그레이션
    4-2. "3"의 과정을 현재 node에서 수행

  5. 가능한 경우 KMIP 클라이언트 (e.g.MongoDB)를 재기동 하여 정상적으로 구동됨 확인

4의 과정에서 taget을 먼저 제거하는 이유는, step-down시 ready 상태가 되는 leader노드로 LB가 요청을 보낼시 역시 50X대 서버 에러를 리턴받기 때문입니다.

follower를 통해 유입된 reqeust는 response또한 follower를 통해 회신되므로, 요청처리에는 문제가 없습니다

Linux 서비스로 등록된 경우 로그 파일 외부 구성

systemd 상에 등록되어 있는 vault service는 "vault server" command를 사용합니다.

systemd로 등록된 서비스는 따로 설정해 주지 않는다면 Standart Out,Error 모두 syslog에 저장하므로, Vault Service Script에서 vault log가 저장되지 않도록 Standard Error를 null로 처리하고, vault server command에 옵션을 주어 vault log를 외부로 지정할 수 있습니다.

# /lib/systemd/system/vault.service 수정
+[Unit]
+...
+
+[Service]
+...
+ExecStart=/usr/local/bin/vault server -config=/etc/vault.d/vault.hcl -log-file={ Log_path }
+StandardError=null
+
+[Install]
+...
+
  • ExecStart 항목에 -log-file 항목 추가
  • StandardError=null 항모고 추가

Auto Unseal 설정을 하지 않은 상태에서 pkill -HUP vault 커맨드실행하는 경우에 vault 프로세스가 재시작되면서 Seal(봉인) 상태가 될까요?

해당 pkill -HUP vault 명령어는 vault 가 재기동되는 명령어가 아닌 설정을 reload하는 명령어로 Vault seal에는 영향이 없습니다.

Vault service 상에서도 명시적으로 Reload 기능이 있으며, Systemctl reload vault.service 명령으로 같은 기능을 수행할 수 있습니다.

# /lib/systemd/system/vault.service 수정
+[Unit]
+...
+
+[Service]
+...
+ExecReload=/bin/kill --signal HUP $MAINPID
+
+[Install]
+...
+

KMIP 엔진을 설정하면 KMIP 서비스 포트(5696/TCP)에 서버측 SSL 인증서가 자동으로 세팅되는데, 이 자체 서명된 인증서의 만료일이 1년으로 잡혀있는데 자동으로 갱신되고 서비스에 영향이 없는지

Vault KMIP Secret Engine에서의 서버측 SSL 인증서의 경우 하단 3가지 상황에서 갱신됩니다

  • Vault 의 리더 변경 시
  • KMIP Config 변경 시
  • Vault 재기동 시
검증 내용
검증 내용

(참고 : https://support.hashicorp.com/hc/en-us/articles/4411836765075-KMIP-Secrets-Engine-server-TLS-certificate-Guideopen in new window)

PR 기능이 있는 License 에서 PR 기능이 없는 License로 이관 시 이슈

PR 기능을 포함된 라이선스 사용 시 PR이 없는 (e.g. ADP-KM) Vault License로 교체하면 Performance Standby 상태가 종료되면서 vault 리더 노드의 프로세스 리로드 시 팔로워 노드 프로세스가 재시작되어 seal 상태가 되는 상황이 발생합니다.

이 경우 하단 링크와 같이 vault config 파일에 disable_performance_standby=true를 명시적으로 기입함으로써 해당 문제를 방지할 수 있습니다.

(링크 : https://support.hashicorp.com/hc/en-us/articles/21909597784211-Vault-Disabling-Performance-Standby-mode-when-not-licensed-for-this-featureopen in new window)

MongoDB - Vault KMIP 연동시 --kmipKeyStatePollingSeconds 옵션 동작

MongoDB의 --kmipKeyStatePollingSeconds 옵션은 MongoDB 에서 Vault Kmip 으로 암호화 Key가 Active 상태인지 확인하는 주기 입니다.

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/port-info.html b/04-HashiCorp/06-Vault/01-Information/port-info.html new file mode 100644 index 0000000000..4bd5c1c421 --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/port-info.html @@ -0,0 +1,63 @@ + + + + + + + + + + Vault Listen Address & Port | docmoa + + + + + +
본문으로 건너뛰기

Vault Listen Address & Port

1분 미만vaultportrequirement

Vault Listen Address & Port

https://learn.hashicorp.com/tutorials/vault/reference-architecture#network-connectivityopen in new window

Vault 포트

TCP

SourceDestinationportprotocolDirectionPurpose
외부 호출지점에서Vault 서버로8200tcp인바운드Vault API
Vault 서버 에서Vault 서버로8200tcp양방향Cluster bootstrapping
Vault 서버 에서Vault 서버로8201tcp양방향Raft, replication, request forwarding

Listening on Multiple Interfaces

listener "tcp" {
+  address = "127.0.0.1:8200"
+}
+
+listener "tcp" {
+  address = "10.0.0.5:8200"
+}
+
+# Advertise the non-loopback interface
+api_addr = "https://10.0.0.5:8200"
+cluster_addr = "https://10.0.0.5:8201"
+

Listening on all IPv6 & IPv4 Interfaces

listener "tcp" {
+  address         = "[::]:8200"
+  cluster_address = "[::]:8201"
+}
+

Listening to specific IPv6 address

listener "tcp" {
+  address         = "[2001:1c04:90d:1c00:a00:27ff:fefa:58ec]:8200"
+  cluster_address = "[2001:1c04:90d:1c00:a00:27ff:fefa:58ec]:8201"
+}
+
+# Advertise the non-loopback interface
+api_addr = "https://[2001:1c04:90d:1c00:a00:27ff:fefa:58ec]:8200"
+cluster_addr = "https://[2001:1c04:90d:1c00:a00:27ff:fefa:58ec]:8201"
+
+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-audit.html b/04-HashiCorp/06-Vault/01-Information/vault-audit.html new file mode 100644 index 0000000000..e9851bde00 --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-audit.html @@ -0,0 +1,46 @@ + + + + + + + + + + Vault Audit | docmoa + + + + + +
본문으로 건너뛰기

Vault Audit

1분 미만vaultaudit

Vault Audit

Vault Audit은 -path를 달리하여 여러 Audit 메커니즘을 중복해서 구성 가능

File

$ vault audit enable file file_path=/var/log/vault/vault_audit.log
+$ vault audit enable -path=file2 file file_path=/var/log/vault/vault_audit2.log
+

Syslog

$ vault audit enable syslog tag="vault" facility="AUTH"
+

Socket

$ vault audit enable socket address=127.0.0.1:9090 socket_type=tcp
+
  • Socket TEST - TCP listener sample - netcat
    sudo apt install -y netcat
    +nc -l 9090
    +
+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-dev-mode-option.html b/04-HashiCorp/06-Vault/01-Information/vault-dev-mode-option.html new file mode 100644 index 0000000000..b0a554ec4a --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-dev-mode-option.html @@ -0,0 +1,118 @@ + + + + + + + + + + Vault Development Mode Options | docmoa + + + + + +
본문으로 건너뛰기

Vault Development Mode Options

약 1 분vaultoptinos

Vault Development Mode Options

볼트 개발 모드 서버를 시작하는 기초적인 커맨드와 실행 후 안내 메시지는 다음과 같다.

$ vault server -dev
+
+==> Vault server configuration:
+
+             Api Address: http://127.0.0.1:8200
+                     Cgo: disabled
+         Cluster Address: https://127.0.0.1:8201
+   Environment Variables: HOME, ITERM_PROFILE, ...
+              Go Version: go1.19.4
+              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
+               Log Level: info
+                   Mlock: supported: false, enabled: false
+           Recovery Mode: false
+                 Storage: inmem
+                 Version: Vault v1.12.3, built 2023-02-02T09:07:27Z
+             Version Sha: 209b3dd99fe8ca320340d08c70cff5f620261f9b
+
+==> Vault server started! Log data will stream in below:
+
+...
+

개발 모드에서 제공하는 옵션을 확인하기 위해 vault server -h 커맨드를 실행하면 하단에서 Dev Opstions를 확인할 수 있다.

$ vault server -h
+
+...
+Dev Options:
+
+  -dev
+      Enable development mode. In this mode, Vault runs in-memory and starts
+      unsealed. As the name implies, do not run "dev" mode in production. The
+      default is false.
+
+  -dev-listen-address=<string>
+      Address to bind to in "dev" mode. The default is 127.0.0.1:8200. This
+      can also be specified via the VAULT_DEV_LISTEN_ADDRESS environment
+      variable.
+
+  -dev-no-store-token
+      Do not persist the dev root token to the token helper (usually the local
+      filesystem) for use in future requests. The token will only be displayed
+      in the command output. The default is false.
+
+  -dev-root-token-id=<string>
+      Initial root token. This only applies when running in "dev" mode.
+      This can also be specified via the VAULT_DEV_ROOT_TOKEN_ID environment
+      variable.
+
+  -dev-tls
+      Enable TLS development mode. In this mode, Vault runs in-memory and
+      starts unsealed, with a generated TLS CA, certificate and key. As the
+      name implies, do not run "dev-tls" mode in production. The default is
+      false.
+
+  -dev-tls-cert-dir=<string>
+      Directory where generated TLS files are created if `-dev-tls` is
+      specified. If left unset, files are generated in a temporary directory.
+

개발 모드로 실행하는 -dev 옵션 외 다른 옵션에 대한 설명은 다음과 같다.

  • -dev-listen-address : 기본은 127.0.0.1:8200이며, 변경을 원하는 경우 여기 입력한다. VAULT_DEV_LISTEN_ADDRESS 환경변수로도 대체 가능하다.
  • -dev-no-store-token : 개발 모드시 사용자 디렉토리에 저장되는 .vault-token을 생성하지 않는 경우 true로 설정한다.
  • -dev-root-token-id: Root Token은 임의의 값으로 생성되는데, 개발 모드 한정으로 사용자가 지정한 문자열로 정의할 수 있다.
  • -dev-tls : 개발 모드를 위한 임시 TLS 인증서를 적용하고 Api AddressHTTPS로 설정한다. 실행 후 출력에 VAULT_CACERT 로 임의 생성된 인증서 위치와 설정을 출력한다.
  • -dev-tls-cert-dir: -dev-tls가 적용된 경우 사용자가 보유한 인증서를 적용하려는 경우 대상 디렉토리를 지정한다.

다음과 같이 옵션을 적용한 개발 모드 볼트를 실행하면, Api Address가 지정된 주소로 설정되고, .vault-token은 생성되지 않고, Root Tokenroot로 설정되고, 생성된 인증서 위치와 커맨드 실행에 필요한 VAULT_CACERT가 출력되는 것을 확인할 수 있다.

$ vault server -dev -dev-listen-address=0.0.0.0:8200 -dev-no-store-token=true -dev-root-token-id=root -dev-tls
+
+==> Vault server configuration:
+
+             Api Address: https://0.0.0.0:8200
+                       ...
+              Listener 1: tcp (addr: "0.0.0.0:8200", cluster address: "0.0.0.0:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "enabled")
+
+==> Vault server started! Log data will stream in below:
+
+...
+
+You may need to set the following environment variables:
+
+    $ export VAULT_ADDR='https://0.0.0.0:8200'
+    $ export VAULT_CACERT='/var/folders/5r/8y6t82xd1h183tq1l_whv8yw0000gn/T/vault-tls3194599057/vault-ca.pem'
+
+The unseal key and root token are displayed below in case you want to
+seal/unseal the Vault or re-authenticate.
+
+Unseal Key: DfFexcExFfbN3jRu7cRuAvMeLMpP9J9yNTgzvvcO8Gw=
+Root Token: root
+
+Development mode should NOT be used in production installations!
+
+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html new file mode 100644 index 0000000000..ed3156698c --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html @@ -0,0 +1,142 @@ + + + + + + + + + + Vault Secrets Operator 개요 | docmoa + + + + + +
본문으로 건너뛰기

Vault Secrets Operator 개요

약 1 분vaultoperator

Vault Secrets Operator 개요

참고:
현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *hereopen in new window*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

img
img

Vault Secrets Operator(이하, VSO)를 사용하면 파드가 쿠버네티스 시크릿에서 기본적으로 볼트 시크릿을 사용할 수 있다.

개요

VSO는 지원되는 Custom Resource Definitions (CRD) 집합의 변경 사항을 감시하여 작동한다. 각 CRD는 오퍼레이터가 Vault Secrets을 Kubernetes Secret에 동기화할 수 있도록 하는 데 필요한 사양(specification)을 제공한다.

오퍼레이터는 소스(source) 볼트 시크릿 데이터를 대상(destination) 쿠버네티스 시크릿에 직접 쓰며, 소스에 대한 변경 사항이 수명 기간 동안 대상에 복제되도록 한다. 이러한 방식으로 애플리케이션은 대상 시크릿에 대한 접근 권한만 있으면 그 안에 포함된 시크릿 데이터를 사용할 수 있다.

특징

VSO가 지원하는 기능은 다음과 같다:

  • 모든 Vault 비밀 엔진 지원.
  • Vault와의 TLS/mTLS 통신.
  • Kubernetes Auth Methodopen in new window을 통해 요청하는 PodServiceAccount를 사용한 인증.
  • Vault Secrets을 Kubernetes Secrets에 동기화.
  • Deployment, ReplicaSet, StatefulSet 쿠버네티스 리소스 유형에 대한 시크릿 로테이션.
  • 운영자 모니터링을 위한 Prometheus 계측(instrumentation) 제공.
  • 지원되는 설치 방법: Helm, Kustomize
  • 자세한 내용은 installationopen in new window 문서를 참조.

지원되는 쿠버네티스 버전

현재 지원되는 Kubernetes minor releasesopen in new window는 다음과 같다. 최신 버전은 각 쿠버네티스 버전에 대해 테스트되며, 다른 버전의 Kubernetes에서도 작동할 수 있지만 지원되지는 않는다.

  • 1.26
  • 1.25
  • 1.24
  • 1.23
  • 1.22

Vault 접근 및 사용자 지정 리소스 정의

참고:
현재, 오퍼레이터는 쿠버네티스 인증 방법만 지원한다. 시간이 지남에 따라 더 많은 Vault 인증 방식에 대한 지원을 추가할 예정이다.

Vault 연결 및 인증 구성은 VaultConnectionVaultAuth CRD에서 제공한다. 이는 모든 비밀 복제 유형 리소스가 참조하는 기본 사용자 지정 리소스로 간주할 수 있다.

VaultConnection 커스텀 리소스

오퍼레이터가 단일 Vault 서버 인스턴스에 연결하는 데 필요한 구성을 제공한다.

---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultConnection
+metadata:
+  namespace: vso-example
+  name: example
+spec:
+  # 필수적인 구성정보
+  # Vault 서버 주소
+  address: http://vault.vault.svc.cluster.local:8200
+
+  # 선택적인 구성정보
+  # 모든 Vault 요청에 포함될 HTTP headers
+  # headers: []
+  
+  # TLS 연결의 SNI 호스트로 사용할 TLS 서버 이름
+  # tlsServerName: ""
+  
+  # Vault에 대한 TLS 연결에 대한 TLS verification을 건너뜀
+  # skipTLSVerify: false
+ 
+  # Kubernetes Secret에 저장된 신뢰할 수 있는 PEM 인코딩된 CA 인증서 체인
+  # caCertSecretRef: ""
+

VaultAuth 커스텀 리소스

오퍼레이터가 VaultConnection 사용자 지정 리소스에 지정된 대로 단일 Vault 서버 인스턴스에 인증하는 데 필요한 구성을 제공한다.

---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultAuth
+metadata:
+  namespace: vso-example
+  name: example
+spec:
+  # 필수적인 구성정보
+  # 해당 VaultConnection 커스텀 리소스의 VaultConnectionRef
+  # 값을 지정하지 않으면 오퍼레이터는 자체 쿠버네티스 네임스페이스에 구성된 
+  # 'default' VaultConnection을 기본값으로 사용
+  vaultConnectionRef: example
+
+  # Vault에 인증할 때 사용하는 방법.
+  method: kubernetes
+  # Auth methods로 인증할 때 사용할 마운트.
+  mount: kubernetes
+  # 쿠버네티스용 인증 구성을 사용하려면, Method를 쿠버네티스로 설정해야 함
+  kubernetes:
+    # Vault에 인증할 때 사용할 역할
+    role: example
+    # Vault에 인증할 때 사용할 서비스어카운트 파드/애플리케이션당 항상 고유한 서비스어카운트를 제공을 권장
+    serviceAccount: default
+
+  # 선택적인 구성정보
+  # 인증 백엔드가 마운트되는 Vault 네임스페이스(Vault Enterprise 전용기능)
+  # namespace: ""
+
+  # Vault에 인증할 때 사용할 매개변수
+  # params: []
+
+  # 모든 Vault 인증 요청에 포함할 HTTP 헤더
+  # headers: []
+

볼트 시크릿 커스텀 리소스 정의

오퍼레이터가 단일 볼트 시크릿을 단일 쿠버네티스 시크릿으로 복제하는 데 필요한 구성을 제공한다. 지원되는 각 CRD는 아래 문서에 설명된 Vault Secret의 Class에 특화되어 있다.

VaultStaticSecret 사용자 지정 리소스

오퍼레이터가 단일 볼트 정적 시크릿을 단일 Kubernetes Secret에 동기화하는 데 필요한 구성을 제공한다.

---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultStaticSecret
+metadata:
+  namespace: vso-example
+  name: example
+spec:
+  vaultAuthRef: example
+  mount: kvv2
+  type: kv-v2
+  name: secret
+  refreshAfter: 60s
+  destination:
+    create: true
+    name: static-secret1
+

VaultPKISecret 사용자 지정 리소스

운영자가 단일 볼트 PKI 시크릿을 단일 쿠버네티스 시크릿에 동기화하는 데 필요한 구성을 제공한다.

  • 지원되는 비밀 엔진: pki
---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultPKISecret
+metadata:
+  namespace: vso-example
+  name: example
+spec:
+  vaultAuthRef: example
+  mount: pki
+  name: default
+  commonName: example.com
+  format: pem
+  expiryOffset: 1s
+  ttl: 60s
+  namespace: tenant-1
+  destination:
+    create: true
+    name: pki1
+

VaultDynamicSecret Custom Resource

오퍼레이터가 단일 볼트 동적 시크릿을 단일 쿠버네티스 시크릿에 동기화하는 데 필요한 구성을 제공한다.

---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultDynamicSecret
+metadata:
+  namespace: vso-example
+  name: example
+spec:
+  vaultAuthRef: example
+  mount: db
+  role: postgres
+  destination:
+    create: true
+    name: dynamic1
+
+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html new file mode 100644 index 0000000000..a2cf09e3f2 --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html @@ -0,0 +1,47 @@ + + + + + + + + + + Vault Secrets Operator 설치 | docmoa + + + + + +
본문으로 건너뛰기

Vault Secrets Operator 설치

1분 미만vaultoperator

Vault Secrets Operator 설치

참고:
현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *hereopen in new window*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

사전 요구사항

  • Kubernetes 1.22+
  • Vault OSS/Enterprise 1.11+

Helm을 활용한 설치

Vault Secrets Operator Helm chartopen in new window는 Vault Secrets Operator(이하, VSO)를 설치하고 구성하는 권고 방안이다.

VSO의 새 인스턴스를 설치하려면 먼저 HashiCorp Helm Repo를 추가하고 Chart에 액세스할 수 있는지 확인한다:

$helm repo add hashicorp https://helm.releases.hashicorp.com
+"hashicorp" has been added to your repositories
+
+$ helm search repo hashicorp/vault-secrets-operator --devel
+NAME            CHART VERSION   APP VERSION DESCRIPTION
+hashicorp/vault-secrets-operator    0.1.0-beta          0.1.0-beta      Official HashiCorp Vault Secrets Operator Chart
+

그런다음 Operator를 설치한다:

$ helm install --create-namespace --namespace vault-secrets-operator vault-secrets-operator hashicorp/vault-secrets-operator --version 0.1.0-beta
+

Helm을 사용한 업그레이드

업그레이드는 기존 설치에서 helm upgrade로 수행할 수 있다. 설치 또는 업그레이드 전에 항상 --dry-run으로 헬름을 실행하여 변경 사항을 확인한다.

Helm Chart Values

지원되는 모든 헬름 차트 값은 hereopen in new window에서 확인할 수 있다.

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html new file mode 100644 index 0000000000..b334df5b9c --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html @@ -0,0 +1,504 @@ + + + + + + + + + + Vault Secrets Operator 예제실습 | docmoa + + + + + +
본문으로 건너뛰기

Vault Secrets Operator 예제실습

약 7 분vaultoperator

Vault Secrets Operator 예제실습

📌 참고:
현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *hereopen in new window*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

본 문서는 HashiCorp 공식 GitHub의 Vault Secret Operator 저장소 에서 제공하는 코드를 활용하여 환경구성 및 샘플 애플리케이션 배포/연동에 대한 상세 분석을 제공한다.

img
img

0. 사전 요구사항

1) 패키지 및 바이너리 정보

  • go(1.20.2 이상)
  • make
  • KinD
  • Docker
  • Kubectl
  • unzip

2) 저장소 복제

실습을 위해 vault-secrets-operatoropen in new window 저장소를 복제한다.

# 저장소 복제
+$ git clone https://github.com/hashicorp/vault-secrets-operator.git
+
+# 작업 디렉토리 이동
+$ cd vault-secrets-operator
+

1. K8s 환경구성 및 샘플배포

📌 참고:
실행결과 : vso-demo-1.shopen in new window

  • Start a KinD Cluster
$ make setup-kind
+
  • setup-kind 수행 후 생성된 KinD 클러스터 및 파드정보 확인

vault-secrets-operator-control-plane 가 단일노드로 배포된 것을 확인할 수 있다.

$ kubectl get nodes -o wide
+NAME                                   STATUS   ROLES           AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
+vault-secrets-operator-control-plane   Ready    control-plane   3m18s   v1.25.3   172.18.0.2    <none>        Ubuntu 22.04.1 LTS   5.15.49-linuxkit   containerd://1.6.9
+
+$ kubectl get pods -A
+NAMESPACE            NAME                                                           READY   STATUS    RESTARTS   AGE
+kube-system          coredns-565d847f94-42vpm                                       1/1     Running   0          3m5s
+kube-system          coredns-565d847f94-6fsv9                                       1/1     Running   0          3m5s
+kube-system          etcd-vault-secrets-operator-control-plane                      1/1     Running   0          3m18s
+kube-system          kindnet-9j486                                                  1/1     Running   0          3m6s
+kube-system          kube-apiserver-vault-secrets-operator-control-plane            1/1     Running   0          3m18s
+kube-system          kube-controller-manager-vault-secrets-operator-control-plane   1/1     Running   0          3m18s
+kube-system          kube-proxy-tfqc8                                               1/1     Running   0          3m6s
+kube-system          kube-scheduler-vault-secrets-operator-control-plane            1/1     Running   0          3m17s
+local-path-storage   local-path-provisioner-684f458cdd-2dzfn                        1/1     Running   0          3m5s
+

2. Vault 클러스터 배포

📌 참고
실행결과 : vso-demo-2.shopen in new window

앞서 생성된 KinD 클러스터 내부에 Vault 클러스터를 배포한다. 이때, 필요한 사전 환경을 Terraform 코드를 통해 자동으로 구성한다.

make setup-integration-test
+
  • 배포된 Vaulat Server Pod 확인
# Pod 확인
+$ kubectl get pods -n vault
+NAME      READY   STATUS    RESTARTS   AGE
+vault-0   1/1     Running   0          73s
+
+# vault 상태확인
+$ kubectl exec -n vault -it vault-0 -- vault status
+Key             Value
+---             -----
+Seal Type       shamir
+Initialized     true
+Sealed          false
+Total Shares    1
+Threshold       1
+Version         1.13.2
+Build Date      2023-04-25T13:02:50Z
+Storage Type    inmem
+Cluster Name    vault-cluster-199af322
+Cluster ID      23b647d5-f067-ba94-b359-2fca26af9ff9
+HA Enabled      false
+

Terraform의 kubernetes, helm 프로바이더를 사용하여 다음과 같은 리소스를 자동으로 배포한다.

📌 참고 :
원본코드 : main.tfopen in new window

  • kubernetes
    • kubernetes_namespace : demo
    • kubernetes_secret : vault_license
    • kubernetes_cluster_role_binding : reviewer
  • helm(helm_release)
    • server.dev.enabled
    • server.image.repository
    • server.image.tag
    • server.logLevel
    • injector.enabled

3. Vault 설정

📌 참고:
원본소스 : setup.shopen in new window
실행결과 : vso-demo-3.shopen in new window

setup.sh 스크립트를 실행하여 다음 3가지 시크릿 엔진에 대한 실습 환경을 구성한다.

  • Secret Engine
  • ACL Policy
  • Auth Methods
$ ./config/samples/setup.sh
+
img
img

1) Secret Engine

(1) KV 시크릿엔진 활성화 : kvv1, kvv2

KV 시크릿엔진 Version 1, Version2를 활성화 하고 샘플 데이터를 주입한다.

vault secrets disable kvv2/
+vault secrets enable -path=kvv2 kv-v2
+vault kv put kvv2/secret username="db-readonly-username" password="db-secret-password"
+
+vault secrets disable kvv1/
+vault secrets enable -path=kvv1 -version=1 kv
+vault kv put kvv1/secret username="v1-user" password="v1-password"
+
  • KV 시크릿 엔진 : kvv1
img
img
  • KV 시크릿 엔진 : kvv2
img
img

(2) PKI 시크릿 엔진 활성화 : pki

PKI 시크릿 엔진을 활성화하고 다음 설정을 진행한다.

  • PKI 인증서 생성
  • CA, CRL 설정
  • Role 설정
# PKI Secret 엔진 활성화
+vault secrets disable pki
+vault secrets enable pki
+
+# PKI 인증서 생성
+vault write pki/root/generate/internal \
+    common_name=example.com \
+    ttl=768h
+
+# 설정
+vault write pki/config/urls \
+    issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \
+    crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
+
+# 역할구성
+vault write pki/roles/default \
+    allowed_domains=example.com \
+    allowed_domains=localhost \
+    allow_subdomains=true \
+    max_ttl=72h
+
  • PKI 시크릿 엔진 구성확인
img
img

2) ACL Policy

(1) 정책 정의 : demo

각 시크릿 엔진에 대한 ACL Policy를 정의하기 위해 다음 hcl 을 작성하고 적용한다.

# policy.hcl 작성
+cat <<EOT > /tmp/policy.hcl
+path "kvv2/*" {
+  capabilities = ["read"]
+}
+path "kvv1/*" {
+  capabilities = ["read"]
+}
+path "pki/*" {
+  capabilities = ["read", "create", "update"]
+}
+EOT
+
+# demo 정책 생성
+vault policy write demo /tmp/policy.hcl
+

vault policy write 명령으로 정책을 생성하고 확인한다.

img
img
  • demo policy 확인
    • kvv2 : read
    • kvv1 : read
    • pki : read, create, update
img
img

3) Auth Methods

(1) 인증방식 정의: kubernetes

Vault와 연동을 위해 kubernetes 인증방식을 설정한다.

참고:
Beta 버전에서는 Kubernetes 인증 방식만 제공

# Kubernetes 인증방식 활성화
+vault auth disable kubernetes
+vault auth enable kubernetes
+
+vault write auth/kubernetes/config \
+    kubernetes_host=https://kubernetes.default.svc
+
+vault write auth/kubernetes/role/demo \
+    bound_service_account_names=default \
+    bound_service_account_namespaces=tenant-1,tenant-2 \
+    policies=demo \
+    ttl=1h
+

VSO에서는 현재 Kubernetes 인증 방식만을 제공하고 있으므로 Kubernetes 인증 방식을 통해 실습을 진행한다.

  • 생성된 kubernetes 인증방식 확인
img
img

kubernetes 인증방식 구성을 위해 Roles, Config를 정의한다.

img
img
  • Roles 확인
    • bound_service_account_names=default
    • bound_service_account_namespaces=tenant-1,tenant-2
    • policies=demo
    • ttl=1h (3600s)
img
img
  • Config 확인
img
img
  • Config 확인(상세)
    • kubernetes_host=https://kubernetes.default.svc
img
img

(참고) Entity 확인

img
img

4) Kubernetes 네임스페이스 생성

K8s 인증방식의 역할(Role)에서 사용할 네임스페이스 확인

kubectl get ns | grep tenant
+tenant-1                        Active   5h2m
+tenant-2                        Active   5h2m
+

4. 오퍼레이터 빌드 및 배포

참고
vso-demo-4.shopen in new window

Vault 설정이 완료되었으므로 실제 Kubernetes Cluster에서 Operator를 배포한다.

$ make build docker-build deploy-kind
+

1) 배포된 리소스 확인

$ kubectl get pods -n vault-secrets-operator-system
+NAME                                                         READY   STATUS    RESTARTS   AGE
+vault-secrets-operator-controller-manager-6f8b6b8f49-5lt97   2/2     Running   0          3h59m
+
+$ k get crd -A
+NAME                                        CREATED AT
+vaultauths.secrets.hashicorp.com            2023-05-12T08:37:15Z
+vaultconnections.secrets.hashicorp.com      2023-05-12T08:37:15Z
+vaultdynamicsecrets.secrets.hashicorp.com   2023-05-12T08:37:15Z
+vaultpkisecrets.secrets.hashicorp.com       2023-05-12T08:37:15Z
+vaultstaticsecrets.secrets.hashicorp.com    2023-05-12T08:37:15Z
+

5. 샘플 K8s 리소스 배포

$ kubectl apply -k config/samples
+
+secret/pki1 created
+secret/secret1 created
+secret/secret1 created
+service/tls-app-service created
+ingress.networking.k8s.io/tls-example-ingress created
+vaultauth.secrets.hashicorp.com/vaultauth-sample created
+vaultauth.secrets.hashicorp.com/vaultauth-sample created
+vaultconnection.secrets.hashicorp.com/vaultconnection-sample created
+vaultconnection.secrets.hashicorp.com/vaultconnection-sample created
+vaultdynamicsecret.secrets.hashicorp.com/vaultdynamicsecret-sample created
+vaultpkisecret.secrets.hashicorp.com/vaultpkisecret-sample-tenant-1 created
+vaultpkisecret.secrets.hashicorp.com/vaultpkisecret-tls created
+vaultstaticsecret.secrets.hashicorp.com/vaultstaticsecret-sample-tenant-1 created
+vaultstaticsecret.secrets.hashicorp.com/vaultstaticsecret-sample-tenant-2 created
+pod/app1 created
+pod/tls-app created
+pod/app1 created
+
  • 생성된 Secret 확인:
$ kubectl get secrets -n tenant-1 secret1 -o yaml
+$ kubectl get secrets -n tenant-1 pki1 -o yaml
+$ kubectl get secrets -n tenant-2 secret1 -o yaml
+

1) 연결 및 인증방식 설정

설명추가

(1) VaultConnection 커스텀 리소스

Vault Operator가 연결할 Vault Cluster 정보를 구성한다.

---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultConnection
+metadata:
+  labels:
+    app.kubernetes.io/name: vaultconnection
+    app.kubernetes.io/instance: vaultconnection-sample
+    app.kubernetes.io/part-of: vault-secrets-operator
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/created-by: vault-secrets-operator
+  name: vaultconnection-sample
+  namespace: tenant-1
+spec:
+  address: http://vault.vault.svc.cluster.local:8200
+---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultConnection
+metadata:
+  labels:
+    app.kubernetes.io/name: vaultconnection
+    app.kubernetes.io/instance: vaultconnection-sample
+    app.kubernetes.io/part-of: vault-secrets-operator
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/created-by: vault-secrets-operator
+  name: vaultconnection-sample
+  namespace: tenant-2
+spec:
+  address: http://vault.vault.svc.cluster.local:8200
+

(2) VaultAuth 커스텀 리소스

사전에 정의된 VaultConnection 을 통해 Operator가 Vault Server와 연결할 때, 어떤 인증방식을 사용할지 구성한다.

참고 : Beta 버전에서는 K8s 인증방식만 제공

  • .spec.vaultConnectionRef
  • .spec.method
  • .spec.mount
  • .spec.kubernetes.role
  • .spec.kubernetes.serviceAccount
---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultAuth
+metadata:
+  labels:
+    app.kubernetes.io/name: vaultauth
+    app.kubernetes.io/instance: vaultauth-sample
+    app.kubernetes.io/part-of: vault-secrets-operator
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/created-by: vault-secrets-operator
+  name: vaultauth-sample
+  namespace: tenant-1
+spec:
+  vaultConnectionRef: vaultconnection-sample
+  method: kubernetes
+  mount: kubernetes
+  kubernetes:
+    role: demo
+    serviceAccount: default
+---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultAuth
+metadata:
+  labels:
+    app.kubernetes.io/name: vaultauth
+    app.kubernetes.io/instance: vaultauth-sample
+    app.kubernetes.io/part-of: vault-secrets-operator
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/created-by: vault-secrets-operator
+  name: vaultauth-sample
+  namespace: tenant-2
+spec:
+  vaultConnectionRef: vaultconnection-sample
+  method: kubernetes
+  mount: kubernetes
+  kubernetes:
+    role: demo
+    serviceAccount: default
+

2) Vault CRD 예제

VSO에서 제공하는 3가지 CRD를 사용하여 Kubernetes 오브젝트와 연동하여 사용하는 방법을 알아본다.

  • VaultPKISecret
  • VaultStaticSecret
  • VaultDynamicSecret

(1) VaultPKISecret : Pod + PKI Secret

다음은 PKI 인증서를 생성하고 Nginx 웹 서버에 적용하는 실습 예제이다. Nginx 파드를 생성할 때 secret 타입의 볼륨을 마운트한다.

  • VaultPKISecret
---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: pki1
+  namespace: tenant-1
+type: Opaque
+---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultPKISecret
+metadata:
+  namespace: tenant-1
+  name: vaultpkisecret-sample-tenant-1
+spec:
+  vaultAuthRef: vaultauth-sample
+  namespace: tenant-1
+  mount: pki
+  name: default
+  destination:
+    name: pki1
+  commonName: consul.example.com
+  format: pem
+  revoke: true
+  clear: true
+  expiryOffset: 5s
+  ttl: 15s
+
  • Pod 샘플
---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app1
+  namespace: tenant-1
+spec:
+  containers:
+    - name: nginx
+      image: nginx
+      volumeMounts:
+        - name: secrets
+          mountPath: "/etc/secrets"
+          readOnly: true
+  volumes:
+    - name: secrets
+      secret:
+        # created in Terraform
+        secretName: pki1
+        optional: false # default setting; "mysecret" must exist
+

실제 PKI 인증서가 정상적으로 생성되는 확인해본다.

  • /etc/secrets 디렉토에서 파일목록 확인
$ ls -lrt /etc/secrets
+
+total 0
+lrwxrwxrwx 1 root root 20 May 14 08:33 serial_number -> ..data/serial_number
+lrwxrwxrwx 1 root root 23 May 14 08:33 private_key_type -> ..data/private_key_type
+lrwxrwxrwx 1 root root 18 May 14 08:33 private_key -> ..data/private_key
+lrwxrwxrwx 1 root root 17 May 14 08:33 issuing_ca -> ..data/issuing_ca
+lrwxrwxrwx 1 root root 17 May 14 08:33 expiration -> ..data/expiration
+lrwxrwxrwx 1 root root 18 May 14 08:33 certificate -> ..data/certificate
+lrwxrwxrwx 1 root root 15 May 14 08:33 ca_chain -> ..data/ca_chain
+lrwxrwxrwx 1 root root 11 May 14 08:33 _raw -> ..data/_raw
+

본 실습에서는 실제 nginx 파드의 구성파일에 PKI 인증서를 적용하는 시나리오가 아닌 단순 파일생성 및 갱신해보았다.

(2) VaultPKISecret 예제2 : Ingress + Pod + PKI Secret

이번 실습에서는 앞서 확인한 PKI 인증서를 활용하여 K8s Ingress 오브젝트에 적용하고 주기적으로 교체되는 시나리오를 확인해본다.

참고 : Ingress 실습을 위해서는 Nginx Ingress Controller를 설치 후 진행해야 한다. [참고open in new window]

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
+
+kubectl wait --namespace ingress-nginx \
+  --for=condition=ready pod \
+  --selector=app.kubernetes.io/component=controller \
+  --timeout=90s
+
  • Ingress에서 PKI 인증서 연동을 위한 샘플예제
---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultPKISecret
+metadata:
+  name: vaultpkisecret-tls
+  namespace: tenant-1
+spec:
+  vaultAuthRef: vaultauth-sample
+  namespace: tenant-1
+  mount: pki
+  name: default
+  destination:
+    create: true
+    name: pki-tls
+    type: kubernetes.io/tls
+  commonName: localhost
+  format: pem
+  revoke: true
+  clear: true
+  expiryOffset: 15s
+  ttl: 1m
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: tls-app
+  namespace: tenant-1
+  labels:
+    app: tls-app
+spec:
+  containers:
+  - command:
+    - /agnhost
+    - netexec
+    - --http-port
+    - "8080"
+    image: registry.k8s.io/e2e-test-images/agnhost:2.39
+    name: tls-app
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tls-app-service
+  namespace: tenant-1
+spec:
+  selector:
+    app: tls-app
+  ports:
+    - port: 443
+      targetPort: 8080
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: tls-example-ingress
+  namespace: tenant-1
+  annotations:
+    nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+  tls:
+  - hosts:
+    - localhost
+    secretName: pki-tls
+  rules:
+  - host: localhost
+    http:
+      paths:
+      - path: /tls-app(/|$)(.*)
+        pathType: Prefix
+        backend:
+          service:
+            name: tls-app-service
+            port:
+              number: 443
+
  • 인증서 확인(명령어)
$ curl -k https://localhost:38443/tls-app/hostname
+tls-app%
+$ curl -kvI https://localhost:38443/tls-app/hostname
+*   Trying 127.0.0.1:38443...
+* Connected to localhost (127.0.0.1) port 38443 (#0)
+# 중략
+* Server certificate:
+*  subject: CN=localhost
+*  start date: May 14 08:04:00 2023 GMT
+*  expire date: May 14 08:05:30 2023 GMT
+*  issuer: CN=example.com
+
  • nginx 컨트롤러 로그를 확인하여 교체되는 TLS 시크릿을 확인:
kubectl logs -f -n ingress-nginx -l app.kubernetes.io/instance=ingress-nginx
+W0514 07:51:58.673604       1 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
+{"level":"info","msg":"patching webhook configurations 'ingress-nginx-admission' mutating=false, validating=true, failurePolicy=Fail","source":"k8s/k8s.go:118","time":"2023-05-14T07:51:58Z"}
+{"level":"info","msg":"Patched hook(s)","source":"k8s/k8s.go:138","time":"2023-05-14T07:51:58Z"}
+I0514 08:19:30.110926       9 store.go:619] "secret was updated and it is used in ingress annotations. Parsing" secret="tenant-1/pki-tls"
+I0514 08:19:30.113988       9 backend_ssl.go:59] "Updating secret in local store" name="tenant-1/pki-tls"
+W0514 08:19:30.114178       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:20:30 +0000 UTC)
+I0514 08:20:15.208102       9 store.go:619] "secret was updated and it is used in ingress annotations. Parsing" secret="tenant-1/pki-tls"
+I0514 08:20:15.208539       9 backend_ssl.go:59] "Updating secret in local store" name="tenant-1/pki-tls"
+W0514 08:20:15.208801       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:21:15 +0000 UTC)
+W0514 08:20:18.543113       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:21:15 +0000 UTC)
+I0514 08:21:00.107794       9 store.go:619] "secret was updated and it is used in ingress annotations. Parsing" secret="tenant-1/pki-tls"
+I0514 08:21:00.108127       9 backend_ssl.go:59] "Updating secret in local store" name="tenant-1/pki-tls"
+W0514 08:21:00.108295       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:22:00 +0000 UTC)
+W0514 07:51:58.418022       1 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
+{"err":"secrets \"ingress-nginx-admission\" not found","level":"info","msg":"no secret found","source":"k8s/k8s.go:229","time":"2023-05-14T07:51:58Z"}
+{"level":"info","msg":"creating new secret","source":"cmd/create.go:28","time":"2023-05-14T07:51:58Z"}
+
  • 인증서 생성/만료 시간 확인
img
img
  • 인증서 생성/만료 시간 변경 확인
img
img
  • 인증서 생성/만료 시간 확인(브라우저)
img
img
  • 인증서 생성/만료 변경 확인(브라우저)
img
img

(3) VaultStaticSecret 예제 :

---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: secret1
+  namespace: tenant-1
+type: Opaque
+---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultStaticSecret
+metadata:
+  namespace: tenant-1
+  name: vaultstaticsecret-sample-tenant-1
+spec:
+  # namespace: cluster1/tenant-1
+  vaultAuthRef: vaultauth-sample
+  mount: kvv2
+  type: kv-v2
+  name: secret
+  refreshAfter: 5s
+  destination:
+    name: secret1
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: secret1
+  namespace: tenant-2
+type: Opaque
+---
+apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultStaticSecret
+metadata:
+  namespace: tenant-2
+  name: vaultstaticsecret-sample-tenant-2
+spec:
+  # namespace: cluster1/tenant-2
+  vaultAuthRef: vaultauth-sample
+  mount: kvv1
+  type: kv-v1
+  name: secret
+  refreshAfter: 5s
+  destination:
+    name: secret1
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app1
+  namespace: tenant-1
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    volumeMounts:
+    - name: secrets
+      mountPath: "/etc/secrets"
+      readOnly: true
+  volumes:
+  - name: secrets
+    secret:
+      secretName: secret1
+      optional: false # default setting; "mysecret" must exist
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app1
+  namespace: tenant-2
+spec:
+  containers:
+  - name: nginx
+    image: nginx
+    volumeMounts:
+    - name: secrets
+      mountPath: "/etc/secrets"
+      readOnly: true
+  volumes:
+  - name: secrets
+    secret:
+      secretName: secret1
+      optional: false # default setting; "mysecret" must exist
+

(5) VaultDynamicSecret

🔥 업데이트 예정

apiVersion: secrets.hashicorp.com/v1alpha1
+kind: VaultDynamicSecret
+metadata:
+  labels:
+    app.kubernetes.io/name: vaultdynamicsecret
+    app.kubernetes.io/instance: vaultdynamicsecret-sample
+    app.kubernetes.io/part-of: vault-secrets-operator
+    app.kubernetes.io/managed-by: kustomize
+    app.kubernetes.io/created-by: vault-secrets-operator
+  name: vaultdynamicsecret-sample
+spec:
+  # TODO(user): Add fields here
+

6. 리소스 삭제

샘플 삭제:

# K8s 리소스 삭제
+$ kubectl delete -k config/samples
+
+# kind 클러스터 삭제
+$ kind delete clusters vault-secrets-operator
+

2. [테라폼 기반 데모](

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/index.html b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/index.html new file mode 100644 index 0000000000..bd3897c28c --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Vault Secret Operator | docmoa + + + + + +
본문으로 건너뛰기

Vault Secret Operator

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-server-configuration-info.html b/04-HashiCorp/06-Vault/01-Information/vault-server-configuration-info.html new file mode 100644 index 0000000000..627a922a64 --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-server-configuration-info.html @@ -0,0 +1,60 @@ + + + + + + + + + + Vault Server Configuration - Info | docmoa + + + + + +
본문으로 건너뛰기

Vault Server Configuration - Info

1분 미만vaultconfiguration

Vault Server Configuration - Info

볼트 서버를 시작하는 기초적인 커맨드와 실행 후 안내 메시지는 다음과 같다.

$ vault server -dev
+
+==> Vault server configuration:
+
+             Api Address: http://127.0.0.1:8200
+                     Cgo: disabled
+         Cluster Address: https://127.0.0.1:8201
+   Environment Variables: HOME, ITERM_PROFILE, ...
+              Go Version: go1.19.4
+              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
+               Log Level: info
+                   Mlock: supported: false, enabled: false
+           Recovery Mode: false
+                 Storage: inmem
+                 Version: Vault v1.12.3, built 2023-02-02T09:07:27Z
+             Version Sha: 209b3dd99fe8ca320340d08c70cff5f620261f9b
+
+==> Vault server started! Log data will stream in below:
+
+...
+

기동 후 서버가 실행된 주요 설정 정보의 설명인 Vault server configuration의 내용의 설명은 다음과 같다.

  • Api Address: http://127.0.0.1:8200
    : 볼트와의 인터페이스를 위한 API 주소로, RestAPI, CLI, UI 모두 해당 주소와 통신한다.

  • Cgo: disabled
    : 볼트는 golang으로 이루어져 있는데, go 컴파일 시 CGO_ENABLED가 활성화되어있다면 dynamic library로 빌드된다. 이 경우 실행 환경에 컴파일 시 사용되는 링크 모듈이 존재하지 않으면 에러가 발생하므로, 실행된 볼트 바이너리는 빌드시 static library로 빌드되었다는 의미다.

  • Cluster Address: https://127.0.0.1:8201
    : 볼트 서버를 고가용성(HA)을 위해 다수의 서버로 구성한 경우 서버 간에 통신하는데 사용되는 주소로, TLS1.2로 상호간 인증한다.

  • Environment Variables: HOME, ITERM_PROFILE, ...
    : 볼트 서버가 실행된 환경의 환경변수 값의 나열로, 볼트 실행에 영향을 주는 환경변수가 있는지 확인할 수 있다.

  • Go Version: go1.19.4
    : 볼트 서버가 빌드 된 golang 버전이다.

  • Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
    : 볼트의 리스너 정보로, 이 정보를 기반으로 Api Address, Cluster Address 가 설정 된다. 숫자가 붙어있다는 의미는 리스터를 여러개 설정 할 수 있다는 의미다.

  • Log Level: info
    : 볼트에서 기록하는 로그 수준이다.

  • Mlock: supported: false, enabled: false
    : 볼트의 기본값은 디스크로의 스와핑을 비활성화하기 위해 메모리상의 가상 주소 공간을 RAM에서 잠근다. 이를 위해서는 mlock()을 지원하는 시스템, 루트 권한, 설정의 활성화가 필요하다.

  • Recovery Mode: false
    : 볼트 시작시 복구모드로 실행되었는지의 여부이다.

  • Storage: inmem
    : 볼트 저장소 타입이다.

  • Version: Vault v1.12.3, built 2023-02-02T09:07:27Z
    : 볼트 바이너리 버정 정보를 나타낸다.

  • Version Sha: 209b3dd99fe8ca320340d08c70cff5f620261f9b
    : 각 볼트 버전과 관련된 고유 식별자입니다. 실행 중인 코드의 버전을 식별하는 데 사용되며, 볼트 오픈소스의 GitHub Repository의 commit id와 같다.

+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-sizing.html b/04-HashiCorp/06-Vault/01-Information/vault-sizing.html new file mode 100644 index 0000000000..f483b88f20 --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-sizing.html @@ -0,0 +1,40 @@ + + + + + + + + + + Vault Sizing | docmoa + + + + + +
본문으로 건너뛰기

Vault Sizing

1분 미만vaultsizing

Vault Sizing

https://learn.hashicorp.com/tutorials/vault/reference-architecture#deployment-system-requirementsopen in new window

Vault의 Backend-Storage 사용 여부에 따라 구성에 차이가 발생

Vault (Raft - self hosted storage)

Vault reaches a milestone as HashiCorp releases Vault 1.4 - Amazic World
Vault reaches a milestone as HashiCorp releases Vault 1.4 - Amazic World
SizeCPUMemoryDiskTypical Cloud Instance Types
최소2 core4-8 GB RAM50 GB2 vCPU, 8 GB Mem, 10Gbps Network
권장4-8 core16-32 GB RAM200 GB4 vCPU, 16 GB Mem, 10Gbps Network
  • HA 구성을 위해 3대 이상의 홀수 구성

Vault - Consul(Backend Storage)

Vault High Availability with Consul | Vault - HashiCorp Learn
Vault High Availability with Consul | Vault - HashiCorp Learn

Vault

SizeCPUMemoryDiskTypical Cloud Instance Types
최소2 core4-8 GB RAM20 GB2 vCPU, 8 GB Mem, 10Gbps Network
권장4-8 core16-32 GB RAM100 GB4 vCPU, 16 GB Mem, 10Gbps Network
  • HA 구성을 위해 2대 이상 구성

Consul

SizeCPUMemoryDiskTypical Cloud Instance Types
Small2 core4-8 GB RAM25 GB2 vCPU, 8 GB Mem, 10Gbps Network
Large4-8 core16-32 GB RAM100 GB4 vCPU, 16 GB Mem, 10Gbps Network
  • Vault Backend Storage로 사용
  • HA 구성을 위해 3대 이상 홀수 구성
+ + + diff --git a/04-HashiCorp/06-Vault/01-Information/vault-token.html b/04-HashiCorp/06-Vault/01-Information/vault-token.html new file mode 100644 index 0000000000..33be83fba3 --- /dev/null +++ b/04-HashiCorp/06-Vault/01-Information/vault-token.html @@ -0,0 +1,231 @@ + + + + + + + + + + Token의 이해 | docmoa + + + + + +
본문으로 건너뛰기

Token의 이해

약 3 분vaulttoken

Token의 이해

https://developer.hashicorp.com/vault/docs/concepts/tokensopen in new window

image-20230304125220402
image-20230304125220402

1. Token의 구성과 내용

다음은 수동으로 Token을 생성하는 방법으로 Token을 생성할 수 있는 권한의 사용자가 CLI를 사용하여 default Policy를 갖는 Token을 생성하는 경우의 예이다.

$ vault token create -policy=default
+
+Key                  Value
+---                  -----
+token                hvs.CAESIO7WUHJ15SkEOgtqzcVuF8pTZdBQmI
+token_accessor       yK2enofb1NExLrLFqg136mw5
+token_duration       768h
+token_renewable      true
+token_policies       ["default"]
+identity_policies    []
+policies             ["default"]
+

간단한 응답과 달리 API로 요청하면 더 상세한 응답 결과를 확인할 수 있다.

# payload.json
+{
+  "policies": ["default"],
+  "meta": {
+    "user": "armon"
+  },
+  "ttl": "1h",
+  "renewable": true
+}
+
+# API 요청
+curl \
+    --header "X-Vault-Token: root" \
+    --request POST \
+    --data @payload.json \
+		http://127.0.0.1:8200/v1/auth/token/create | jq .
+
+# API 응답
+{
+  "request_id": "a0a87aea-3627-a2a6-ab3c-8c08285fdc7d",
+  "lease_id": "",
+  "renewable": false,
+  "lease_duration": 0,
+  "data": null,
+  "wrap_info": null,
+  "warnings": [
+    "Endpoint ignored these unrecognized parameters: [meta]"
+  ],
+  "auth": {
+    "client_token": "hvs.CAESIO-LeVOy1mOLSz1f-yDC22cFqOXQ2u5a3hmLVxeZ1V07Gh4KHGh2cy5mTmJZbERwZ0xLeldqeFgwYWRyc3Z4a0g",
+    "accessor": "ArOmYq9MuDyo1wZkLisad6Ml",
+    "policies": [
+      "default"
+    ],
+    "token_policies": [
+      "default"
+    ],
+    "metadata": {
+      "user": "armon"
+    },
+    "lease_duration": 3600,
+    "renewable": true,
+    "entity_id": "",
+    "token_type": "service",
+    "orphan": false,
+    "mfa_requirement": null,
+    "num_uses": 0
+  }
+}
+

다음은 userpass 인증방식을 CLI를 사용하여 default Policy를 갖는 Token을 발급받는 경우의 예이다. 이경우 발급된 Token은 시스템 홈디렉터리의 .vault-token 파일에 Token이 저장된다.

$ vault login -method=userpass username=admin
+Password (will be hidden): ********
+
+Success! You are now authenticated. The token information displayed below
+is already stored in the token helper. You do NOT need to run "vault login"
+again. Future Vault requests will automatically use this token.
+
+Key                    Value
+---                    -----
+token                  hvs.CAESIFyNxoV1I-_nFeBh9LBxDB9oGNghX
+token_accessor         80nJPKtpaPbMUyQ715VkRGig
+token_duration         768h
+token_renewable        true
+token_policies         ["default"]
+identity_policies      []
+policies               ["default"]
+token_meta_username    admin
+

CLI 출력을 기준으로 출력된 Key의 설명은 다음과 같다.

CLI 결과API 결과설명
tokenclient_token생성된 Token 문자열 값
token_accessoraccessorToken과 쌍으로 생성된 참조 값
token_durationlease_duration생성된 Token의 사용 기간
token_renewablerenewableRenewal 가능 여부
token_policiestoken_policies생성시 부여된 Policy
identity_policies(entity_id로 조회 필요)Token이 속한 Identity(Entity)에 부여된 Policy 목록
policespoliciesToken에 부여된 전체 Policy 목록
token_meta_[key]metadatametatada 출력

Token이 생성되면 종류에 따라 붙는 Prefix를 보고 유형을 유추할 수 있다.

Token 유형<1.9>=1.10
Service Tokens.hvs.
Batch Tokenb.hvb.
Recovery Tokenr.hvr.

2. Token 계층(종속성)

Token의 여러 속성 중 종속성의 이해가 필요하다. 종속성으로 인해 상위 Token이 취소되거나 만료되면 하위 Token도 함께 취소된다. 동작을 확인하기 위해 독립된 Orphan Token을 생성하고 하위 Token을 생성 한 뒤 상위 Token을 취소했을 때 현상을 확인하여 종속성의 결과를 확인 가능하다.

Root Token이 VAULT_TOKEN 환경변수에 정의된 상태에서 모든 권한을 갖는 Policy를 생성한다.

# super-user.hcl 
+path "*" {
+    capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+# Policy 적용
+$ vault policy write super-user super-user.hcl
+

독립된 Token을 생성한다. 이 Token의 TTL은 60초로 짧게 부여한다.

$ vault token create -policy=super-user -ttl=60s -orphan
+

새로 생성된 Token을 VAULT_TOKEN환경변수로 지정한 상태에서 새로운 하위 Token을 생성한다. 이 Token의 TTL은 768시간으로 길게 부여한다.

$ vault token create -policy=default -ttl=768h
+

-orphan 옵션이 붙지 않은 Token은 상위 Token에 종속되므로 768시간의 TTL을 갖고 있지만 상위 Token이 60초가 지나 만료되면 해당 하위 토큰도 함께 만료됨을 확인할 수 있다.

$ vault token <CHILD_TOKEN>
+Error looking up token: Error making API request.
+
+URL: POST http://127.0.0.1:8200/v1/auth/token/lookup
+Code: 403. Errors:
+
+* bad token
+

3. Orphan Token

Token 종속성으로인한 하위 Token의 의도하지 않은 취소를 피하려면 Orphan(고아) Token으로의 지정이 필요하다. Token을 생성한 Parent(부모/상위) Token과 생성된 Child(자식/하위) Token의 관계가 형성되며, Orphan Token의 경우 Parent에서 독립되어 Token Tree의 루트가 된다. Orphan Token을 생성하는 방안은 다음과 같다.

방안 1. auth/token/create-orphan 엔드포인트

auth/token/create-orphan 엔드포인트에 write 권한이 필요하다.

path "auth/token/create-orphan" {
+    capabilities = ["create", "update"]
+}
+

Token 생성 시 해당 엔드포인트로 요청하여 Orphan Token을 생성한다.

 $ vault write -force auth/token/create-orphan policies=default
+

방안 2. auth/token/create 요청 시 no_parent 옵션 사용

[방안 1]과 같이 Orphan을 위한 엔드포인트 외에 일반적인 Token 생성 엔드포인트를 요청하는 경우 no_parent 옵션을 true로 설정하여 요청한다. auth/token/create 엔드포인트에 write 권한 필요하다.

path "auth/token/create" {
+    capabilities = ["create", "update"]
+}
+

Token 생성 시 해당 엔드포인트로 요청하여 Orphan Token을 생성한다.

$ vault write -force auth/token/create policies=default no_parent=true
+

vault write 요청과 함께, Token을 위한 전용 CLI 인 vault token create를 사용할 수 있다.

$ vault token create -policy="default" -orphan
+

방안 3. Token Role에 Orphan 정의

Token을 필요한 시점마다 만드는 것이 아닌 미리 정의된 Token Role을 기반으로 Token을 생성할 때, 해당 Role에 Orphan으로 생성한다는 정의가 된 경우 생성되는 Token은 Orphan Token이 된다. Token Role을 CLI로 생성하는 방법은 다음과 같다. 간단히 허용하는 Policy와 Orphan 여부만 설정하였다.

$ vault write auth/token/roles/my-orphan allowed_policies="default" orphan=true
+

Token Role을 기반으로 Token 생성을 요청하면 정의된 설정에 의해 Token이 생성된다.

$ vault token create -policy=default -role=my-orphan
+

방안 4. Token 직접 생성을 제외한 Auth Methods로 로그인

Auth Methods를 통해 인증 후 받게되는 Token은 Orphan Token으로 생성되어 반환된다.

위 [방안 1~4]에서 생성된 Orphan Token은 Token을 조회하면 Orphan 여부를 확인할 수 있다.

$ vault token lookup <VAULT_TOKEN>
+
+Key                 Value
+---                 -----
+...
+orphan              true # Orphan 여부
+path                auth/userpass/login/admin # Token이 생성된 API 엔드포인트
+policies            [default super-user]
+type                service
+

4. Token 유형

Token의 유형은 Service 타입과 Batch 타입으로 나뉘며, 각각은 Orphan 여부에 따라 Token을 생성한 Parent Token과의 종속성을 정의할 수 있다.

  • Service Token :
    • Orphan Token이 아니라면 이를 생성한 Token과 연결되어 함께 추적
    • Parent Token이 만료되면 함께 취소됨
  • Batch Token :
    • Batch Token으로 생성된 Lease는 Batch Token의 남은 TTL 기간으로 제한
    • Batch Token이 Orphan이 아닌 경우 이를 생성한 상위 Token에 의해 추적되며, Parent Token 만료시 사용 중지
기능Service TokenBatch Token
Root Token 역할⛔️
Chile Token 생성⛔️
Renewable(기간 늘림)⛔️
Manually Revocable(수동 취소)⛔️
Periodic 형태⛔️
Explicit Max TTL 설정⛔️ (고정)
Accessor 여부⛔️
Cubbyhole 사용 여부⛔️
Revoke with parent (부모 Token이 취소될 때 같이 취소)Revoke는 아니나 사용 중지
Dynamic Secrets lease assignment자체 할당부모로 동작
Performance Replication Cluster 전체에서 사용⛔️
Cost무거움 : 백엔드 스토리지에 건당 저장가벼움 : 백엔드 스토리지 저장되지 않음

5. Token Accessor

Token이 생성되면 Accessor도 함께 생성되는데, 이 Accessor는 Token을 참조하는 값임과 동시에 Token을 직접 알지 못하더라도 Token에 대한 기본 속성이나 Renew(갱신), Revoke(취소) 작업을 수행할 수 있다. Accessor에 대한 작업은 일반적인 read, write 작업과 더불어 vault token CLI로도 사용 가능하다.

# Token lookup
+$ vault write auth/token/lookup-accessor accessor=<ACCESSOR>
+$ vault token lookup -accessor <ACCESSOR>
+
+# Token Renew
+$ vault write auth/token/renew-accessor accessor=<ACCESSOR>
+$ vault token renew -accessor <ACCESSOR>
+
+# Token Revoke
+$ vault write auth/token/revoke-accessor accessor=<ACCESSOR>
+$ vault token revoke -accessor <ACCESSOR>
+

이미 발급된 Token을 알수는 없지만 몇몇 동작은 Accessor로 수행이 가능하므로, 이런 Accessor 값을 노출시키는 것은 임의로 Revoke하는 작업이 수반되는 경우 위험할 수 있어 각별히 조심해야 한다.

6. TTL과 Period

Root Token을 제외한 모든 Token은 TTL이 부여된다. TTL은 token의 생성 시간 또는 마지막 갱신 시간 중 가장 최근 시간 기준으로 이후의 유효한 기간까지의 시간이다.

  • Token의 TTL이 갱신이 가능한 경우 vault token renew 명령으로 갱신 가능
  • Token에는 Explicit Max TTL를 설정할 수 있으며, Explicit Max TTL 시간 까지는 TTL을 갱신 할 수 있으며, 모든 경우에서 Max TTL을 넘어서 사용 불가

다음과 같이 TTL과 Explicit Max TTL을 설정하여 Token을 생성해본다.

$ vault token create -policy=default -ttl=60s -explicit-max-ttl=90s
+

생성된 Token의 정보를 확인해보면 적용된 값이 확인된다.

$ vault token lookup <TOKEN>
+Key                 Value
+---                 -----
+creation_ttl        1m
+expire_time         2023-03-04T16:44:40.187494+09:00
+explicit_max_ttl    1m30s
+issue_time          2023-03-04T16:43:40.187497+09:00
+ttl                 54s
+

TTL과 관련된 내용을 확인해보면 다음과 같이 해석할 수 있다.

  • issue_time : 해당 시간에 Token이 발급됨
  • creation_ttl : 최초 요청된 TTL 시간
  • expire_time : 현재 부여된 TTL을 기준으로 만료되는 시간
  • ttl : 현재 남은 TTL
  • explicit_max_ttl : 최대로 부여받을 수 있는 총 TTL

Renew를 수행하면 최초 부여한 60초 만큼을 더하여 TTL을 증가시키는데, Explicit Max TTL이 1분 30초 이므로, 30초가 넘어간 시점에 Renew를 요청하면 추가 60초 만큼을 남은 총량 대비 부여하지 못한다는 메시지를 확인할 수 있다.

$ vault token renew <TOKEN>
+
+WARNING! The following warnings were returned from Vault:
+
+  * TTL of "1m" exceeded the effective max_ttl of "56s"; TTL value is capped
+  accordingly
+

TTL은 특성상 max TTL이 있으므로 영구적으로 사용은 불가능한 속성이다. Token이 만료되는 것이 문제가 될 수 있고, 오랜 기간동안 유지해야 하는 경우 Periodic Token을 사용한다.

  • periodic token에는 TTL은 있지만 max TTL은 없음
  • TTL이 지나면 자동으로 취소되고, 만약 계속 사용한다면 vault token renew 로 TTL을 계속 갱신해서 사용

Periodic Token을 생성하는 방법은 ttl 대신 period 에 기간을 주어 생성한다.

$ vault token create -policy=default -period=60s
+

생성된 Token의 정보를 확인해보면 period 값이 있는 것이 확인된다.

$ vault token lookup <TOKEN>
+
+Key                 Value
+---                 -----
+explicit_max_ttl    0s
+period              1m
+

explicit_max_ttl이 0초라는 의미는 무제한으로 풀이할 수 있다. 이후 Renew를 수행하면 지속적으로 TTL이 갱신되는 것을 확인할 수 있다.

$ vault token renew <TOKEN>
+
+Key                  Value
+---                  -----
+token_duration       1m
+
+$ vault token renew <TOKEN>
+
+Key                  Value
+---                  -----
+token_duration       1m
+
+...계속...
+

7. Root Token

볼트를 Init하면 최초 발급되는 Token이 Root Token이다. 볼트에서 유일하게 만료되지 않는 Token으로 모든 권한(root Policy)을 갖고 있다. 일반적으로 Init 후 root 권한을 갖는 일반 인증을 생성한 뒤 파기하는 것을 권장한다.

# Root에 준하는 Policy
+path "*" {
+	capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+

Root Token을 분실했거나 파기 후 필요한 경우 다음 순서에 따라 새로운 Root Token을 발급한다.

https://developer.hashicorp.com/vault/tutorials/operations/generate-rootopen in new window

단계 1. Root Token 생성 초기화

$ vault operator generate-root -init
+
+A One-Time-Password has been generated for you and is shown in the OTP field.
+You will need this value to decode the resulting root token, so keep it safe.
+Nonce         1a6294ff-1f09-cccf-6434-e49279aec4df
+Started       true
+Progress      0/1
+Complete      false
+OTP           vxa9sXQjPCb91C1rS1yPJYcMw90f
+OTP Length    28
+

생성된 NonceOTP가 Unseal 및 인코딩된 Root Token 복호화에 사용된다.

단계 2. Root Token 생성

동일한 터미널 상에서 수행했다면 Nonce 값이 자동으로 입력되지만 아닌경우에는 -nonce 속석으로 지정이 필요하다.

$ vault operator generate-root
+or
+$ vault operator generate-root -nonce=1a6294ff-1f09-cccf-6434-e49279aec4df
+

단계 3. Unseal key를 활용한 인코딩된 Root Token 발급

vault operator generate-root를 수행하면 Unseal 키를 입력해야 한다. Init에서 발생된 Unseal Key 값을 입력하여 인코딩된 Root Token을 발급 받는다.

$ vault operator generate-root
+
+Root generation operation nonce: 1a6294ff-1f09-cccf-6434-e49279aec4df
+Unseal Key (will be hidden):
+
+Nonce            1a6294ff-1f09-cccf-6434-e49279aec4df
+Started          true
+Progress         5/5
+Complete         true
+Encoded Token    Hg4SFzwRZCAoAQFheHRLHmZiFicabzZ7D0pEJw
+

단계 4. Root Token 복호화

초기화시 발급된 OTP를 이용하여 인코딩된 Root Token을 복호화 한다.

$ vault operator generate-root \
+	-decode=Hg4SFzwRZCAoAQFheHRLHmZiFicabzZ7D0pEJw \
+	-otp=vxa9sXQjPCb91C1rS1yPJYcMw90f
+	
+hvs.OI5JxBcXI7zl5SowP6U6xstA
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/index.html b/04-HashiCorp/06-Vault/02-Secret_Engine/index.html new file mode 100644 index 0000000000..004e8ea121 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Secret Engine | docmoa + + + + + +
본문으로 건너뛰기

02 Secret Engine

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/keymgmt.html b/04-HashiCorp/06-Vault/02-Secret_Engine/keymgmt.html new file mode 100644 index 0000000000..6496c17bc2 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/keymgmt.html @@ -0,0 +1,735 @@ + + + + + + + + + + Key Management | docmoa + + + + + +
본문으로 건너뛰기

Key Management

약 14 분vaultVault Enterprisekeymgmt

Key Management

Key Management Secret Engine을 활성화 하기 위해서는 ADP 수준의 라이선스가 필요하다.

Key Management 시크릿 엔진은 KMS(Key Management Service)를 공급하는 대상의 암호화 키의 배포 및 수명 주기 관리를 위한 워크플로를 제공한다. KMS 공급자 고유의 암호화 기능을 기존처럼 사용하면서도, 볼트에서 키를 중앙 집중식으로 제어할 수 있다.

볼트는 KMS의 구성에 사용되는 Key Meterial 원본을 생성하여 보유한다. 관리가능한 KMS에 대해 키 수명주기를 설정 및 관리하면 Key Meterial의 복사본이 대상에 배포된다. 이 방식으로 볼트는 KMS 서비스의 전체 수명 주기 관리 및 키 복구 수단을 제공한다. 지원되는 KMS는 다음과 같다.

  • AWS KMS
  • Azure Key Vault
  • GCP Cloud KMS
  • PKCS#11 호환

Key Management의 구성 및 동작의 순서는 다음과 같다.

  1. Key Management 시크릿 엔진인 keymgmt를 활성화

  2. 키 생성

    • type : 키 유형
      • AWS KMS 지원 유형: aes256-gcm96
      • Azure Key Vault 지원 유형 : rsa-2048, rsa-3072, rsa-4096
      • GCP Cloud KMS 지원 유형 : aes256-gcm96, rsa-2048, rsa-3072, rsa-4096, ecdsa-p256, ecdsa-p384
  3. 지원되는 KMS 서비스 지정 및 공급자 별 인증 정보 등록

    • awskms : AWS KMS
    • azurekeyvault : Azure Key Vault
    • gcpckms : GCP Cloud KMS
  4. KMS 서비스에 키 생성

    • purpose : 목적
      • enctypt
      • dectypt
      • sign
      • verify
      • wrap
      • unwrap
    • protection : 키 보호 지정
      • hsm
      • software
  5. 관리를 위한 키 회전 (선택)

  6. 키 버전 활성/비활성 (선택)

  7. KMS 서비스의 키 제거

    • KMS 서비스의 키가 삭제되지만 키는 볼트에 저장
    • 영구 제거를 위해서는 별도 키 삭제 호출

keymgmt Secret Engine 활성화

keymgmt 시크릿 엔진을 활성화하여, 해당 엔드포인트에서 시크릿에 대한 관리를 수행한다. 관리 목적에 따라 별도의 엔드포인트를 path로 지정한다.

$ vault secrets enable keymgmt
+Success! Enabled the keymgmt secrets engine at: keymgmt/
+

1. AWS KMS

1.1 구성 요소

  • AWS KMS 관리를 위한 권한
    • kms:CreateKey
    • kms:GetParametersForImport
    • kms:ImportKeyMaterial
    • kms:EnableKey
    • kms:DisableKey
    • kms:ScheduleKeyDeletion
    • kms:CreateAlias
    • kms:UpdateAlias
    • kms:DeleteAlias
    • kms:ListAliases
    • kms:TagResource
  • keymgmt Secret Engine

1.2 Vault 가 대상 AWS KMS 관리 위해 사용할 자격증명

Secret Engine 에 대한 활성화는 Secret Engine 에 접근하기 위해 사용되는 엔드포인트 정보만 생성되었을 뿐, 연동하고자 하는 대상 AWS 환경에 대한 정보에 대해서는 세부 설정이 필요하다. 이를 위해 Vault 가 대상 AWS 환경에 대해 접근, 자격증명 발급 그리고 생명주기 관리 작업을 수행할 수 있는 권한이 부여된 자격증명 발급이 필요하다.

  1. 자격증명 발급 및 생명주기 관리 권한을 위한 정책 생성

    AWS IAM 정책 생성: https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/access_policies_create-console.htmlopen in new window

    1. https://console.aws.amazon.com/iam/에서open in new window IAM 콘솔에 접속.

    2. 왼쪽의 탐색 창에서 정책(Policies) 을 선택.

    3. 정책 생성(Create policy) 을 선택.

    4. JSON 탭을 선택.

    5. JSON 정책 문서 입력

      • Action 란에 권한 부여를 포함한 자격 증명 발급 및 관리를 위한 IAM 서비스 관련 권한 작성
      • Resource 란의 ACCOUNT-ID-WITHOUT-HYPHENS 는 AWS 콘솔 우상단에서 확인 가능한 숫자 12자리로 구성된 계정 고유 ID 정보 입력
      {
      +  "Version": "2012-10-17",
      +  "Statement": [
      +    {
      +      "Effect": "Allow",
      +      "Action": [
      +        "kms:CreateKey",
      +        "kms:GetParametersForImport",
      +        "kms:ImportKeyMaterial",
      +        "kms:EnableKey",
      +        "kms:DisableKey",
      +        "kms:ScheduleKeyDeletion",
      +        "kms:CreateAlias",
      +        "kms:UpdateAlias",
      +        "kms:DeleteAlias",
      +        "kms:ListAliases",
      +        "kms:TagResource"
      +      ],
      +      "Resource": ["*"]
      +    }
      +  ]
      +}
      +
    6. 보안 경고, 오류 또는 일반 경고를 해결한 다음 정책 검토(Review policy) 선택.

    7. 정책 이름 정의 후 정책 생성(Create Policy) 선택 하여 생성 완료

  2. 자격증명 발급

    AWS 계정 및 액세스 키 : https://docs.aws.amazon.com/ko_kr/powershell/latest/userguide/pstools-appendix-sign-up.htmlopen in new window

    1. https://console.aws.amazon.com/iam/open in new window 에서 IAM 콘솔에 접속.
    2. 탐색 메뉴에서 사용자 선택.
    3. 사용자 추가(Add User) 선택 하여 사용자 이름 지정
    4. 직접 정책 연결(Attach policies directly) 선택하여 이전 단계에서 생성한 정책 선택
    5. 사용자 생성(Create User) 선택하여 사용자 생성 완료
    6. Security credentials(보안 자격 증명) 탭을 연 다음 **Create access key(액세스 키 생성)**를 선택.
    7. 새 액세스 키를 보려면 [Show]를 선택. 키 정보는 아래와 같이 액세스 키 / 보안 액세스 키로 구성.
      • 액세스 키 ID: AKIAIOSFODNN7EXAMPLE
      • 보안 액세스 키: wJalrXUt******************XAMPLEKEY
    8. 키 페어 파일을 다운로드하려면 [Download .csv file]을 선택

1.3 Vault의 AWS KMS 구성

AWS KMS에서 호환되는 암호화 키를 생성한다.

 vault secrets enable keymgmt
+
+$ vault write -f keymgmt/key/aes256-gcm96 type="aes256-gcm96"
+Success! Data written to: keymgmt/key/aes256-gcm96
+

생성된 암호화 키의 정보를 확인한다.

$ vault read keymgmt/key/aes256-gcm96
+Key                    Value
+---                    -----
+deletion_allowed       false
+latest_version         1
+min_enabled_version    1
+name                   aes256-gcm96
+type                   aes256-gcm96
+versions               map[1:map[creation_time:2023-07-05T12:04:48.099141545Z]]
+

AWS KMS 공급자 리소스를 구성한다.

$ vault write keymgmt/kms/aws-kms-anea2 \
+    provider="awskms" \
+    key_collection="ap-northeast-2" \
+    credentials=access_key="ASIADJO3WTX6WPLJM42V" \
+    credentials=secret_key="bCiYmNroLxLmPNQ47VIvjlm8mQu5oktZcQdq195w"
+    
+Success! Data written to: keymgmt/kms/aws-kms
+

AWS KMS 공급자 리소스를 구성하는 매개변수의 설명은 다음과 같다.

매개변수설명
providerAWS KMS를 구성하는 경우 awskms를 사용
key_collectionAWS 리전을 지정
credentials=access_keyAWS Access Key
credentials=secret_keyAWS Secret Key
credentials=session_tokenAWS Session Token
credentials=endpointAWS KMS API endpoint

AWS KMS 공급자 리소스에 암호화 키를 배포

 vault write keymgmt/kms/:name/key/:key_name
+$ vault write keymgmt/kms/aws-kms-anea2/key/aes256-gcm96 \
+    purpose="encrypt,decrypt" \
+    protection="hsm"
+    
+Success! Data written to: keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+
  • AWS KMS의 키 사용 목적(purpose)로는 encryptdecrypt를 함께 사용한다.
  • AWS KMS에서는 보호 수준(protection)을 hsm만 사용 가능하다.

등록이 완료되면 AWS Console의 Key Management Service(KMS)에서 고객 관리형 키로 등록된다.

KMS Console 2023-07-05 21-15-20
KMS Console 2023-07-05 21-15-20

생성된 키정보를 확인하여 현재 버전에 명시된 ID가 AWS 상의 KMS의 키 ID와 같은지 확인한다.

$ vault read keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+Key                  Value
+---                  -----
+distribution_time    2023-07-05T12:14:52.173163449Z
+name                 aes256-gcm96-1688559292
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:503e44bf-7629-47c3-8c22-a5337b3aab3a]
+
KMS Console 2023-07-05 21-20-08
KMS Console 2023-07-05 21-20-08

AWS KMS사용자의 경우 vault로의 API 요청으로 키ID를 확인할 수 있다.

curl -H "X-Vault-Token: token" -X GET http://<vault_hostname>:<port>/v1/keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+{
+  "request_id": "e8147c9e-a3fd-71b6-075e-8f2d67393127",
+  "lease_id": "",
+  "lease_duration": 0,
+  "renewable": false,
+  "data": {
+    "distribution_time": "2023-07-05T12:14:52.173163449Z",
+    "name": "aes256-gcm96-1688602383",
+    "protection": "hsm",
+    "purpose": "decrypt,encrypt",
+    "versions": {
+      "1": "503e44bf-7629-47c3-8c22-a5337b3aab3a"
+    }
+  },
+  "warnings": null
+}
+

AWS KMS에 적용된 키를 순환시킨다.

$ vault write -f keymgmt/key/aes256-gcm96/rotate
+

순환되어 새로 추가된 키 버전을 확인한다.

$ vault read keymgmt/key/aes256-gcm96
+Key                    Value
+---                    -----
+deletion_allowed       false
+latest_version         2
+min_enabled_version    1
+name                   aes256-gcm96
+type                   aes256-gcm96
+versions               map[1:map[creation_time:2023-07-05T12:04:48.099141545Z] 2:map[creation_time:2023-07-05T12:23:01.870942633Z]]
+

키가 적용된 AWS KMS에도 순환된 키 버전이 적용됨을 확인한다.

$ vault read keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+Key                  Value
+---                  -----
+distribution_time    2023-07-05T12:14:52.173163449Z
+name                 aes256-gcm96-1688559292
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:503e44bf-7629-47c3-8c22-a5337b3aab3a 2:c2bb1927-76b2-4fce-8b32-73569489a70c]
+

추가된 마지막 키는 별칭(Alias)로 지정되어 앱에서는 alias/hashicorp/<name>으로 호출할 수 있다.

(e.g. alias/hashicorp/aes256-gcm96-1688602383)

AWS Console에서 확인하면, 신규 키 ID가 적용된 항목이 새로 추가됨을 확인할 수 있다.

KMS Console 2023-07-05 21-25-12
KMS Console 2023-07-05 21-25-12

적용된 키의 최소 버전을 업데이트 한다.

$ vault write keymgmt/key/aes256-gcm96 min_enabled_version=2 deletion_allowed=true
+

키의 최소 버전에 따라 그 이하의 키가 삭제되었음을 확인한다. (비활성 처리)

$ vault read keymgmt/key/aes256-gcm96
+Key                    Value
+---                    -----
+deletion_allowed       true
+latest_version         2
+min_enabled_version    2
+name                   aes256-gcm96
+type                   aes256-gcm96
+versions               map[2:map[creation_time:2023-07-05T12:23:01.870942633Z]]
+

AWS KMS에 적용된 버전은 기존 버전은 존재하나, AWS Console에서 확인하면 비활성 처리됨을 확인할 수 있다.

$ vault read keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+Key                  Value
+---                  -----
+distribution_time    2023-07-05T12:14:52.173163449Z
+name                 aes256-gcm96-1688559292
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:503e44bf-7629-47c3-8c22-a5337b3aab3a 2:c2bb1927-76b2-4fce-8b32-73569489a70c]
+
KMS Console 2023-07-05 21-31-05
KMS Console 2023-07-05 21-31-05

최소 버전을 이전 버전을 포함하여 업데이트 하면 이전 버전의 키가 복구된다.

$ vault write keymgmt/key/aes256-gcm96 min_enabled_version=1 deletion_allowed=true
+Success! Data written to: keymgmt/key/aes256-gcm96
+
+$ vault read keymgmt/key/aes256-gcm96
+Key                    Value
+---                    -----
+deletion_allowed       true
+latest_version         2
+min_enabled_version    1
+name                   aes256-gcm96
+type                   aes256-gcm96
+versions               map[1:map[creation_time:2023-07-05T12:04:48.099141545Z] 2:map[creation_time:2023-07-05T12:23:01.870942633Z]]
+
KMS Console 2023-07-05 21-33-07
KMS Console 2023-07-05 21-33-07

AWS KMS 구성에서 키를 삭제하는 경우 적용된 키가 일괄 삭제 대기 상태로 변경되며, 해당 키는 삭제 가능하다. (AWS 정책상 7~30일 유예)

$ vault delete keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+Success! Data deleted (if it existed) at: keymgmt/kms/aws-kms-anea2/key/aes256-gcm96
+
KMS Console 2023-07-05 21-59-17
KMS Console 2023-07-05 21-59-17

주의 사항 1.

AWS KMS에서 생성된 암호화 키는 기본적으로 해당 리전에 한정되어 사용된다. 암호화 키는 생성된 리전 내에서만 사용 가능하며, 다른 리전에 직접 이동시키는 것은 불가능하다. 이는 AWS KMS 서비스의 설계와 보안 모델에 기인한 제약 사항이다.

AWS KMS는 키 관리와 관련된 강력한 보안 제어를 제공합니다. 이를 위해 암호화 키는 해당 리전의 KMS 서비스에 의해 엄격하게 관리되며, 다른 리전에 암호화 키를 이동시키는 것은 보안상의 이슈를 야기할 수 있어 기본 구성은 아니다.

따라서 암호화 키를 다른 AWS 리전의 KMS에 적용하려면, 해당 리전에서 새로운 암호화 키를 생성해야 합니다. 이는 암호화 키의 보안성과 범위를 유지하기 위해 필요한 조치이다.

만약 여러 리전에서 동일한 암호화 키를 사용해야 하는 경우에는 AWS KMS의 Cross-Region Replication 기능을 사용해야 한다.

주의 사항 2.

키 순환을 위해서는 데이터 암호화 시 복호화 가능한 AWS KMS의 Id 또는 arn을 함께 기록해야 복호화 시 사용할 키를 지정할 수 있다. 볼트의 transit과는 달리 새로 생성된 키는 기존 암호화된 데이터를 복호화 할 수 없다.

1.5 테스트를 위한 예제 - Python

  • python 3.x
  • boto3pycryptodome 패키
$ python --version
+Python 3.9.12
+
+$ pip install boto3
+$ pip install pycryptodome
+

Encryption

import base64
+import json
+import logging
+import boto3
+from botocore.exceptions import ClientError
+AWS_REGION = 'ap-northeast-2'
+
+logger = logging.getLogger()
+logging.basicConfig(level=logging.INFO, format='%(asctime)s: %(levelname)s: %(message)s')
+kms_client = boto3.client("kms", region_name=AWS_REGION)
+
+def encrypt(secret, alias):
+    """
+    Encrypts plaintext into ciphertext by using a KMS key.
+    """
+    try:
+        cipher_text = kms_client.encrypt(
+            KeyId=alias,
+            Plaintext=bytes(secret, encoding='utf8'),
+        )
+    except ClientError:
+        logger.exception('Could not encrypt the string.')
+        raise
+    else:
+        return base64.b64encode(cipher_text["CiphertextBlob"])
+
+if __name__ == '__main__':
+     Constants
+    SECRET = 'hands-on-vault-key-management'
+    KEY_ALIAS = 'alias/hashicorp/aes256-gcm96-1688605574'
+    logger.info('Encrypting...')
+    kms = encrypt(SECRET, KEY_ALIAS)
+    logger.info(f'Encrypted string: {kms}.')
+

Decryption

  • 위 encryption 결과로 생성된 BLOB 데이터를 코드의 CIPHER_BLOB에 붙여넣어 테스트
import base64
+import json
+import logging
+import boto3
+from botocore.exceptions import ClientError
+AWS_REGION = 'ap-northeast-2'
+
+logger = logging.getLogger()
+logging.basicConfig(level=logging.INFO, format='%(asctime)s: %(levelname)s: %(message)s')
+kms_client = boto3.client("kms", region_name=AWS_REGION)
+
+def decrypt(cipher_text, alias):
+    """
+    Decrypts ciphertext that was encrypted by a KMS key.
+    """
+    try:
+        plain_text = kms_client.decrypt(KeyId=alias, CiphertextBlob=bytes(base64.b64decode(cipher_text)))
+    except ClientError:
+        logger.exception('Could not decrypt the string.')
+        raise
+    else:
+        return plain_text['Plaintext']
+
+if __name__ == '__main__':
+     Constants
+    CIPHER_BLOB = 'AQICAHgGLc+TNQuAGEYhHYwf5zxQz9XvN0uXI2N4YU+dPYN0fgFmLVCFrkLNP+EJWytolEIfAAAAezB5BgkqhkiG9w0BBwagbDBqAgEAMGUGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMn0LDunt5nrftC18BAgEQgDgPSUhp2iLAGjEFUuSOSxDdYj1m9o4KetZJjmKfX4pvvZMJGkozLEnZpQ0KMET5NjjyGOzax7H84g=='
+    KEY_ALIAS = 'alias/hashicorp/aes256-gcm96-1688605574'
+    logger.info('Decrypting...')
+    kms = decrypt(CIPHER_BLOB, KEY_ALIAS)
+    logger.info(f"Decrypted string: {kms.decode('utf8')}.")
+

2. Azure key Vault

https://developer.hashicorp.com/vault/tutorials/adp/key-management-secrets-engine-azure-key-vaultopen in new window

2.1 Azure Key Vault 구성

  • Azure Key Vault

    • 기존 생성된 Azure Key Vault를 관리
    • 키 자격 증명 모음 에서 새 키 생성
  • Azure Key Vault 관리를 위한 권한

    • create
    • delete
    • get
    • import
    • update
  • Terraform Example

    terraform {
    +  required_providers {
    +    azurerm = {
    +      source  = "hashicorp/azurerm"
    +      version = "~> 3.65.0"
    +    }
    +  }
    +}
    +
    +provider "azurerm" {
    +  features {
    +    key_vault {
    +      purge_soft_delete_on_destroy    = true
    +      recover_soft_deleted_key_vaults = true
    +    }
    +  }
    +}
    +
    +resource "random_id" "app_rg_name" {
    +  byte_length = 3
    +}
    +
    +data "azurerm_client_config" "current" {}
    +
    +resource "azurerm_resource_group" "key_vault_rg" {
    +  name     = "gs-rg-${random_id.app_rg_name.hex}"
    +  location = "Korea Central"
    +}
    +
    +resource "azurerm_key_vault" "example" {
    +  name                       = "gs-keyvault-${random_id.app_rg_name.hex}-vault"
    +  location                   = azurerm_resource_group.key_vault_rg.location
    +  resource_group_name        = azurerm_resource_group.key_vault_rg.name
    +  sku_name                   = "premium"
    +  soft_delete_retention_days = 7
    +  tenant_id                  = data.azurerm_client_config.current.tenant_id
    +
    +  access_policy {
    +    tenant_id = data.azurerm_client_config.current.tenant_id
    +    object_id = data.azurerm_client_config.current.object_id
    +
    +    // "Create", "Delete", "Get", "Import", "Update"
    +    key_permissions = [
    +      "Backup", "Create", "Decrypt", "Delete", "Encrypt", "Get", "Import", "List",
    +      "Purge", "Recover", "Restore", "Sign", "UnwrapKey", "Update", "Verify", "WrapKey",
    +      "Release", "Rotate", "GetRotationPolicy"
    +    ]
    +  }
    +}
    +
    +output "key_vault_name" {
    +  value = azurerm_key_vault.example.name
    +}
    +

2.2 Vault의 Azure Key Vault 구성

Azure Key Vault에서 호환되는 암호화 키를 생성한다.

  • Azure Key Vault 지원 유형 : rsa-2048, rsa-3072, rsa-4096
$ vault write -f keymgmt/key/rsa-2048-key type="rsa-2048"
+Success! Data written to: keymgmt/key/rsa-2048-key
+

생성된 암호화 키의 정보를 확인한다.

$ vault read keymgmt/key/rsa-2048-key
+
+Key                    Value
+---                    -----
+deletion_allowed       false
+latest_version         1
+min_enabled_version    1
+name                   rsa-2048-key
+type                   rsa-2048
+versions               map[1:map[creation_time:2023-07-14T19:18:28.45274+09:00 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qK54OiinWQFdyupvkg0
+HqBPpp/5H29fhcByipoEMpCMHpqNwgea2r6I3sTWX/0YdLZ6w/1L4Fc+B/yABu66
+vXq31OXvnIkvkT73jn9qEQsnYIhqdnElngT+4DOD5nuxPd4e8Ov5OOCIAjKA36YI
+VRiTJtR36qUFFVxxByGnvgSZ3Q090bRRLZx0WidqUilDAjh9CFucAcl3ybl5F80U
+H3aA9HiakGm+hTV1PLZPOT9mhmZk92NFSRVEuEddNb7Rndg3RrZ2/Sgrlbmxc28R
+SJnQswhA9Qbb9HmjCEmfo3rXpvEzJy8YCY24nk5GsyOwOA9z5uQwEJidBxmpsvdy
+QQIDAQAB
+-----END PUBLIC KEY-----
+]]
+

Azure Key Vault 공급자 리소스를 구성한다.

 기본 Location은 "West US"
+$ export AZURE_LOCATION='koreacentral'
+
+$ vault write keymgmt/kms/gs-keyvault-mgmt \
+    key_collection="gs-keyvault-ee81ec-vault" \
+    provider="azurekeyvault" \
+    credentials=client_id=$AZURE_CLIENT_ID \
+    credentials=client_secret=$AZURE_CLIENT_SECRET \
+    credentials=tenant_id=$AZURE_TENANT_ID
+    
+Success! Data written to: keymgmt/kms/gs-keyvault-mgmt
+

Azure Key Vault 공급자 리소스를 구성하는 매개변수의 설명은 다음과 같다. credentials은 Managed Service Identity (MSI)가 구성된 Azure 상에서 Vault가 실행되거나 환경변수로 지정된 경우 생략 가능하다.

매개변수설명
providerAWS KMS를 구성하는 경우 awskms를 사용
key_collection기존 Azure Key Vault 인스턴스의 이름을 나타냅니다. 생성 후에는 변경할 수 없습니다.
credentials=client_idAzure API를 호출하기 위한 자격 증명을 위한 클라이언트 ID (AZURE_CLIENT_ID)
credentials=client_secretAzure API를 호출하기 위한 자격 증명을 위한 클라이언트 암호 (AZURE_CLIENT_SECRET)
credentials=tenant_idAzure Active Directory 조직의 테넌트 ID (AZURE_TENANT_ID)

Azure Key Vault 공급자 리소스에 암호화 키를 배포

 vault write keymgmt/kms/:name/key/:key_name
+$ vault write keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key \
+    purpose="encrypt,decrypt" \
+    protection="hsm"
+    
+Success! Data written to: keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key
+
+$ vault write keymgmt/kms/gs-keyvault-mgmt/key/rsa-4096-sign \
+    purpose="sign" \
+    protection="hsm"
+    
+Success! Data written to: keymgmt/kms/gs-keyvault-mgmt/key/rsa-4096-sign
+
gs-keyvault-ee81ec-vault - Microsoft Azure 2023-07-14 20-24-36
gs-keyvault-ee81ec-vault - Microsoft Azure 2023-07-14 20-24-36

현재 키 버전을 확인한다.

$ vault read keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key
+Key                  Value
+---                  -----
+distribution_time    2023-07-14T19:19:47.100453+09:00
+name                 rsa-2048-key-1689329987
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:80bb514c42f14422b3d3405d3b2fa1fd]
+
image-20230714204157598
image-20230714204157598

Azure Key Vault에 적용된 키를 순환시킨다.

$ vault write -f keymgmt/key/rsa-2048-key/rotate
+

순환되어 새로 추가된 키 버전을 확인한다.

$ vault read keymgmt/key/rsa-2048-key
+
+Key                    Value
+---                    -----
+deletion_allowed       false
+latest_version         2
+min_enabled_version    1
+name                   rsa-2048-key
+type                   rsa-2048
+versions               map[1:map[creation_time:2023-07-14T19:18:28.45274+09:00 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qK54OiinWQFdyupvkg0
+HqBPpp/5H29fhcByipoEMpCMHpqNwgea2r6I3sTWX/0YdLZ6w/1L4Fc+B/yABu66
+vXq31OXvnIkvkT73jn9qEQsnYIhqdnElngT+4DOD5nuxPd4e8Ov5OOCIAjKA36YI
+VRiTJtR36qUFFVxxByGnvgSZ3Q090bRRLZx0WidqUilDAjh9CFucAcl3ybl5F80U
+H3aA9HiakGm+hTV1PLZPOT9mhmZk92NFSRVEuEddNb7Rndg3RrZ2/Sgrlbmxc28R
+SJnQswhA9Qbb9HmjCEmfo3rXpvEzJy8YCY24nk5GsyOwOA9z5uQwEJidBxmpsvdy
+QQIDAQAB
+-----END PUBLIC KEY-----
+] 2:map[creation_time:2023-07-14T20:14:02.507171+09:00 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+5ziHlHjaKN+YqfZX70
+5pxjVZqT4rq2ZkFAK+HRNbSW9QQltBpnn1hmyDEhZX8FAxTiaEpF01ZVptmrNY3Q
+KkHMduqUReA1jjcLbQ2E6DYCp3B/RUDLD7vNuXHvgGqTQr7aeEs0JHKYTERXt0MQ
+KUeFCBRi6zyAiTrcGU2o2/PRNs3Lmxjf88IFziDbcCj4Alqj1+0ruD0n1/HG6yXI
+1F5wYzziimJ+J4A3Sw2xQC/1tOxOR2onjMDT4Fd1xIsp3N7wKWFgGmQoZKn1ETtX
+e4m1ZLZEmrQnpz0aoiG1IXvwfa3ncjPhrhXM2f53p0r9Zuwuq4SZpg/ZRM1zd9No
+BQIDAQAB
+-----END PUBLIC KEY-----
+]]
+

키가 적용된 AWS KMS에도 순환된 키 버전이 적용됨을 확인한다.

$ vault read keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key
+Key                  Value
+---                  -----
+distribution_time    2023-07-14T19:19:47.100453+09:00
+name                 rsa-2048-key-1689329987
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:80bb514c42f14422b3d3405d3b2fa1fd 2:cb4765bae58b40e8bc1d77a96f0c0079]
+

추가된 마지막 키는 key_id로 지정되어 앱에서는 https://<kms이름>.vault.azure.net/keys/<key이름>/<적용된 key 버전>으로 호출할 수 있다.

(e.g. https://gs-keyvault-ee81ec-vault.vault.azure.net/keys/rsa-2048-key-1689329987/80bb514c42f14422b3d3405d3b2fa1fd)

Azure Console에서 확인하면, 신규 키 ID가 적용된 항목이 새로 추가됨을 확인할 수 있다.

rsa-2048-key-1689329987 - Microsoft Azure 2023-07-14 20-14-19
rsa-2048-key-1689329987 - Microsoft Azure 2023-07-14 20-14-19

적용된 키의 최소 버전을 업데이트 한다.

$ vault write keymgmt/key/rsa-2048-key min_enabled_version=2 deletion_allowed=true
+

키의 최소 버전에 따라 그 이하의 키가 삭제되었음을 확인한다. (비활성 처리)

$ vault read keymgmt/key/rsa-2048-key
+Key                    Value
+---                    -----
+deletion_allowed       true
+latest_version         2
+min_enabled_version    2
+name                   rsa-2048-key
+type                   rsa-2048
+versions               map[2:map[creation_time:2023-07-14T20:14:02.507171+09:00 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+5ziHlHjaKN+YqfZX70
+5pxjVZqT4rq2ZkFAK+HRNbSW9QQltBpnn1hmyDEhZX8FAxTiaEpF01ZVptmrNY3Q
+KkHMduqUReA1jjcLbQ2E6DYCp3B/RUDLD7vNuXHvgGqTQr7aeEs0JHKYTERXt0MQ
+KUeFCBRi6zyAiTrcGU2o2/PRNs3Lmxjf88IFziDbcCj4Alqj1+0ruD0n1/HG6yXI
+1F5wYzziimJ+J4A3Sw2xQC/1tOxOR2onjMDT4Fd1xIsp3N7wKWFgGmQoZKn1ETtX
+e4m1ZLZEmrQnpz0aoiG1IXvwfa3ncjPhrhXM2f53p0r9Zuwuq4SZpg/ZRM1zd9No
+BQIDAQAB
+-----END PUBLIC KEY-----
+]]
+

Azure Key Vault에 적용된 버전은 기존 버전은 존재하나, AWS Console에서 확인하면 비활성 처리됨을 확인할 수 있다.

$ vault read keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key
+Key                  Value
+---                  -----
+distribution_time    2023-07-14T19:19:47.100453+09:00
+name                 rsa-2048-key-1689329987
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:80bb514c42f14422b3d3405d3b2fa1fd 2:cb4765bae58b40e8bc1d77a96f0c0079]
+
rsa-2048-key-1689329987 - Microsoft Azure 2023-07-14 20-18-38
rsa-2048-key-1689329987 - Microsoft Azure 2023-07-14 20-18-38

최소 버전을 이전 버전을 포함하여 업데이트 하면 이전 버전의 키가 복구된다.

$ vault write keymgmt/key/rsa-2048-key min_enabled_version=1 deletion_allowed=true
+Success! Data written to: keymgmt/key/aes256-gcm96
+
+$ vault read keymgmt/key/rsa-2048-key
+Key                    Value
+---                    -----
+deletion_allowed       true
+latest_version         2
+min_enabled_version    1
+name                   rsa-2048-key
+type                   rsa-2048
+versions               map[1:map[creation_time:2023-07-14T19:18:28.45274+09:00 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qK54OiinWQFdyupvkg0
+HqBPpp/5H29fhcByipoEMpCMHpqNwgea2r6I3sTWX/0YdLZ6w/1L4Fc+B/yABu66
+vXq31OXvnIkvkT73jn9qEQsnYIhqdnElngT+4DOD5nuxPd4e8Ov5OOCIAjKA36YI
+VRiTJtR36qUFFVxxByGnvgSZ3Q090bRRLZx0WidqUilDAjh9CFucAcl3ybl5F80U
+H3aA9HiakGm+hTV1PLZPOT9mhmZk92NFSRVEuEddNb7Rndg3RrZ2/Sgrlbmxc28R
+SJnQswhA9Qbb9HmjCEmfo3rXpvEzJy8YCY24nk5GsyOwOA9z5uQwEJidBxmpsvdy
+QQIDAQAB
+-----END PUBLIC KEY-----
+] 2:map[creation_time:2023-07-14T20:14:02.507171+09:00 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+5ziHlHjaKN+YqfZX70
+5pxjVZqT4rq2ZkFAK+HRNbSW9QQltBpnn1hmyDEhZX8FAxTiaEpF01ZVptmrNY3Q
+KkHMduqUReA1jjcLbQ2E6DYCp3B/RUDLD7vNuXHvgGqTQr7aeEs0JHKYTERXt0MQ
+KUeFCBRi6zyAiTrcGU2o2/PRNs3Lmxjf88IFziDbcCj4Alqj1+0ruD0n1/HG6yXI
+1F5wYzziimJ+J4A3Sw2xQC/1tOxOR2onjMDT4Fd1xIsp3N7wKWFgGmQoZKn1ETtX
+e4m1ZLZEmrQnpz0aoiG1IXvwfa3ncjPhrhXM2f53p0r9Zuwuq4SZpg/ZRM1zd9No
+BQIDAQAB
+-----END PUBLIC KEY-----
+]]
+
rsa-2048-key-1689329987 - Microsoft Azure 2023-07-14 20-19-34
rsa-2048-key-1689329987 - Microsoft Azure 2023-07-14 20-19-34

Azure Key Vault 구성에서 키를 삭제하는 경우 적용된 키가 일괄 삭제된다.

$ vault delete keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key
+Success! Data deleted (if it existed) at: keymgmt/kms/gs-keyvault-mgmt/key/rsa-2048-key
+
찾을 수 없음 - Microsoft Azure 2023-07-14 20-20-51
찾을 수 없음 - Microsoft Azure 2023-07-14 20-20-51

주의 사항.

키 순환을 위해서는 데이터 암호화 시 복호화 가능한 AWS KMS의 Id 또는 arn을 함께 기록해야 복호화 시 사용할 키를 지정할 수 있다. 볼트의 transit과는 달리 새로 생성된 키는 기존 암호화된 데이터를 복호화 할 수 없다.

2.3 테스트를 위한 예제 - Python

https://learn.microsoft.com/en-us/python/api/azure-keyvault-keys/azure.keyvault.keys.crypto?view=azure-pythonopen in new window

  • python 3.x
  • azure-keyvault-keys, azure-keyvault-secrets, azure-identity 패키지
  • aiohttp 패키지
$ python --version
+Python 3.9.12
+
+$ pip install azure-keyvault-keys azure-keyvault-secrets azure-identity aiohttp
+

List

from azure.identity import DefaultAzureCredential
+from azure.keyvault.keys import KeyClient
+
+credential = DefaultAzureCredential()
+key_client = KeyClient(vault_url="https://gs-keyvault-ee81ec-vault.vault.azure.net/", credential=credential)
+keys = key_client.list_properties_of_keys()
+
+for key in keys:
+    print(key.name)
+
$ python vault_list.py
+
+rsa-2048-key-1689329987
+rsa-4096-sign-1689330068
+

Encryption

from azure.identity import DefaultAzureCredential
+from azure.keyvault.keys.crypto import CryptographyClient
+
+ Azure Key Vault 관련 설정
+key_vault_name = "gs-keyvault-ee81ec-vault"
+key_id = "https://gs-keyvault-ee81ec-vault.vault.azure.net/keys/rsa-2048-key-1689329987/80bb514c42f14422b3d3405d3b2fa1fd"
+
+ 인증 및 액세스 토큰 가져오기
+credential = DefaultAzureCredential()
+
+ Key Vault 클라이언트 및 암호화 클라이언트 생성
+cryptography_client = CryptographyClient(key=key_id, credential=credential)
+
+ 암호화할 데이터
+data_to_encrypt = b"Hello, Azure Key Vault!"
+
+ 데이터 암호화
+result = cryptography_client.encrypt(algorithm="RSA-OAEP", plaintext=data_to_encrypt)
+encrypted_data = result.ciphertext
+
+print("암호화된 데이터:", encrypted_data)
+
$ python encrypt.py
+Local encrypt operation failed: 'str' object has no attribute 'value'
+암호화된 데이터: b"Q\x8e\x94\xe0R\xd7\xc5\x87\xa4M\x9cMx\xccM\xc2S\xd5C\xe0\xef7\xf5\x1afJ8A\x81\xef\xfcA\x8b\x82\xb4\x8d\x93\x17\xa7\xc5\x0b?x\x9b\xa6\xfd\xc2qe<_\x99@yC\x16\xa6\xcbSn\x10Z\xa9y\xa6\xf5V\xdd\xdc\x9c\xe7\xf2\x0fs\x9b\x06j\r+z\x11D|lu\xce\xccV\x9b\xef\xb8\x9c\xc2\x9b7A>\xff\xf8\x806\x98\x00o.|):\xea\x9a\xbcI\x92b\x81DE|\xc1\x80\xae\xbb\x7f\xc9\x8e5\xc5\t|\xe8\xc8\xac\x1d\x98\xc7\xc0\xca\x00b\n\x13\xe4\xd1j\xe6]L\xff'\xb7\xbd^g\xb4\x9eAZq#\x9c\x10A\x83\x82\x9d\x1bXR\xba\xb6\x17\xc3&\xaa\x95l\x83\xfcs\x89)\xb1\xde^\x07\xb3s\x87\x90\xfd\x83\xf0\xfc\x15\x82\x1a\x02\xb1\x93\x8e\x1d\x88u!K\xc9y\xdfL^\x97\xe5\xb5\x05\x83\xe4!E1\x83k\x11\xceC}\xb0C{Td\xa1\x8a\x0f=\xbeE'\x0c7\x14\xbfKm\xd0I}\xb9\xb9P\x93\xb3$\xa33\xfdn"
+

Decryption

  • 위 encryption 결과로 생성된 BLOB 데이터를 코드의 data_to_decrypt에 붙여넣어 테스트
from azure.identity import DefaultAzureCredential
+from azure.keyvault.keys.crypto import CryptographyClient
+
+ Azure Key Vault 관련 설정
+key_vault_name = "gs-keyvault-ee81ec-vault"
+key_id = "https://gs-keyvault-ee81ec-vault.vault.azure.net/keys/rsa-2048-key-1689329987/80bb514c42f14422b3d3405d3b2fa1fd"
+
+ 인증 및 액세스 토큰 가져오기
+credential = DefaultAzureCredential()
+
+ Key Vault 클라이언트 및 암호화 클라이언트 생성
+cryptography_client = CryptographyClient(key=key_id, credential=credential)
+
+ 복호화할 데이터 (b"Hello, Azure Key Vault!")
+data_to_decrypt = b"Q\x8e\x94\xe0R\xd7\xc5\x87\xa4M\x9cMx\xccM\xc2S\xd5C\xe0\xef7\xf5\x1afJ8A\x81\xef\xfcA\x8b\x82\xb4\x8d\x93\x17\xa7\xc5\x0b?x\x9b\xa6\xfd\xc2qe<_\x99@yC\x16\xa6\xcbSn\x10Z\xa9y\xa6\xf5V\xdd\xdc\x9c\xe7\xf2\x0fs\x9b\x06j\r+z\x11D|lu\xce\xccV\x9b\xef\xb8\x9c\xc2\x9b7A>\xff\xf8\x806\x98\x00o.|):\xea\x9a\xbcI\x92b\x81DE|\xc1\x80\xae\xbb\x7f\xc9\x8e5\xc5\t|\xe8\xc8\xac\x1d\x98\xc7\xc0\xca\x00b\n\x13\xe4\xd1j\xe6]L\xff'\xb7\xbd^g\xb4\x9eAZq#\x9c\x10A\x83\x82\x9d\x1bXR\xba\xb6\x17\xc3&\xaa\x95l\x83\xfcs\x89)\xb1\xde^\x07\xb3s\x87\x90\xfd\x83\xf0\xfc\x15\x82\x1a\x02\xb1\x93\x8e\x1d\x88u!K\xc9y\xdfL^\x97\xe5\xb5\x05\x83\xe4!E1\x83k\x11\xceC}\xb0C{Td\xa1\x8a\x0f=\xbeE'\x0c7\x14\xbfKm\xd0I}\xb9\xb9P\x93\xb3$\xa33\xfdn"
+
+ 데이터 암호화
+result = cryptography_client.decrypt(algorithm="RSA-OAEP", ciphertext=data_to_decrypt)
+decrypted_data = result.plaintext
+
+print("복호화된 데이터:", decrypted_data)
+
$ python decrypt.py 
+복호화된 데이터: b'Hello, Azure Key Vault!'
+

3. GCP Cloud KMS

3.1 Vault 가 대상 GCP Cloud KMS 관리 위해 사용할 역할 (권한)

  1. https://console.cloud.google.comopen in new window 을 통해 GCP 포탈에 접속 한다.

  2. 상단 프로젝트 선택에서 프로젝트 이름을 선택합니다.

  3. 좌측 메뉴확장 또는 검색을 통해 IAM 및 관리자 > 역할을 선택한다.

  4. + 역할 만들기 선택하여 Vault Key Management를 위한 역할 생성

    • 제목 : e.g. vault-key-management

    • Vault Key Management를 위해 할당된 권한에 다음을 추가

      • cloudkms.cryptoKeys.create

      • cloudkms.cryptoKeys.update

      • cloudkms.importJobs.create

      • cloudkms.importJobs.get

      • cloudkms.cryptoKeyVersions.list

      • cloudkms.cryptoKeyVersions.destroy

      • cloudkms.cryptoKeyVersions.update

      • cloudkms.cryptoKeyVersions.create

      • cloudkms.importJobs.useToImport

    • KeyRing을 생성하기 위해서는 할당된 권한에 다음을 추가

      • cloudkms.keyRings.create
      • cloudkms.keyRings.get
      • cloudkms.cryptoKeys.get
    • Encrypt/Decrypt를 테스트하기 위해서는 할당된 권한에 다음을 추가

      • cloudkms.cryptoKeyVersions.useToEncrypt
      • cloudkms.cryptoKeyVersions.useToDecrypt
      • cloudkms.cryptoKeyVersions.viewPublicKey

3.2 Vault 가 대상 GCP Cloud KMS 관리 위해 사용할 자격증명

Vault가 Cloud KMS 인스턴스에 연결하고 관리하는 데 사용할 이 서비스 계정에 대한 JSON 기반 자격 증명 파일을 생성해야한다.

  1. https://console.cloud.google.comopen in new window 을 통해 GCP 포탈에 접속 한다.
  2. 상단 프로젝트 선택에서 프로젝트 이름을 선택합니다.
  3. 좌측 메뉴확장 또는 검색을 통해 IAM 및 관리자 > 서비스 계정을 선택한다.
  4. 상단의 + 서비스 계정 만들기를 선택하여 신규 계정을 추가한 뒤 속성을 부여 하고 완료 합니다.
    • 서비스 계정 이름 : e.g. vault-keymgmt-test
    • 액세스 권한 부여 : 앞서 생성한 vault-key-management
  5. 선택한 계정 상단의 탭을 선택합니다.
  6. 키 추가 드롭다운 메뉴를 클릭하고 새 키 만들기를 선택합니다.
  7. 키 유형은 JSON 을 선택하여 생성합니다.

3.3 GCP Cloud KMS 구성

Vault의 Key Management에서 관리할 GCP Cloud KMS를 생성한다.

  1. 좌측 메뉴확장 또는 검색을 통해 `보안 > Key Management를 선택한다.

    • Cloud KMS가 비활성인경우 사용 버튼으로 활성화 한다.

      Cloud Key Management Service (KMS) API – Marketplace – gs-keymgmt-test – Google Cloud Console 2023-07-17 13-58-07
      Cloud Key Management Service (KMS) API – Marketplace – gs-keymgmt-test – Google Cloud Console 2023-07-17 13-58-07
  2. GCP 안내에 따라 새로운 키링을 생성 한다. (https://cloud.google.com/kms/docs/create-encryption-keys?hl=koopen in new window)

  3. 또는 Terraform 으로 새로운 키링을 생성 한다.

    terraform {
    +  required_providers {
    +    google = {
    +      source  = "hashicorp/google"
    +      version = "~> 4.73.1"
    +    }
    +  }
    +}
    +
    +locals {
    +  region = "asia-northeast3"
    +}
    +
    +provider "google" {
    +  project     = "hc-f5e09ac82cca41c78e99aac5ea3"
    +  credentials = file("kms.json")
    +  region      = local.region
    +}
    +
    +resource "google_kms_key_ring" "keyring" {
    +  name     = "vault-keyring"
    +  location = local.region
    +}
    +
  4. 생성된 키링의 리소스 이름 복사를 클릭하면 Vault 구성에서 사용할 key_collection에 할당하는 키링의 이름을 복사할 수 있다.

키 관리 – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-41-39
키 관리 – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-41-39

3.4 Vault의 GCP Cloud KMS 구성

GCP KMS에서 호환되는 암호화 키를 생성한다.

$ vault write -f keymgmt/key/gcp-aes256-gcm96 type="aes256-gcm96"
+Success! Data written to: keymgmt/key/gcp-aes256-gcm96
+

생성된 암호화 키의 정보를 확인한다.

$ vault read keymgmt/key/gcp-aes256-gcm96
+
+Key                    Value
+---                    -----
+deletion_allowed       false
+latest_version         1
+min_enabled_version    1
+name                   gcp-aes256-gcm96
+type                   aes256-gcm96
+versions               map[1:map[creation_time:2023-07-17T13:46:58.17194+09:00]]
+

GCP Cloud KMS 공급자 리소스를 구성한다.

$ vault write keymgmt/kms/gcpckms-korea \
+    provider="gcpckms" \
+    key_collection="projects/hc-f5e09ac82cca41c78e99aac5ea3/locations/asia-northeast3/keyRings/vault-keyring" \
+    credentials=service_account_file="$FULL_PATH/kms.json"
+    
+Success! Data written to: keymgmt/kms/gcpckms-korea
+

AWS KMS 공급자 리소스를 구성하는 매개변수의 설명은 다음과 같다.

매개변수설명
providerGCP Cloud KMS를 구성하는 경우 gcpckms를 사용
key_collectionGCP Cloud KMS의 키링 리소스 이름을 지정
credentials=service_account_file자격증명 파일로 GCP Cloud KMS 인증에 사용할 자격증명 파일을 지정한다. GOOGLE_CREDENTIALS 로 지정된 경우 생략할 수 있다.

AWS KMS 공급자 리소스에 암호화 키를 배포

 vault write keymgmt/kms/:name/key/:key_name
+$ vault write keymgmt/kms/gcpckms-korea/key/gcp-aes256-gcm96 \
+    purpose="encrypt,decrypt" \
+    protection="hsm"
+    
+Success! Data written to: keymgmt/kms/gcpckms-korea/key/gcp-aes256-gcm96
+

등록이 완료되면 GCP Console의 대상 키링에 키가 추가된 것을 확인할 수 있다.

키 관리 – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-49-10
키 관리 – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-49-10

생성된 키정보를 확인하여 현재 버전에 명시된 ID가 GCP 상의 KMS의 키 ID와 같은지 확인한다.

$ vault read keymgmt/kms/gcpckms-korea/key/gcp-aes256-gcm96
+
+Key                  Value
+---                  -----
+distribution_time    2023-07-17T14:48:10.777969+09:00
+name                 gcp-aes256-gcm96-1689572890
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:1]
+
키링 세부정보 – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-51-44
키링 세부정보 – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-51-44

GCP Cloud KMS사용자의 경우 vault로의 API 요청으로 키ID를 확인할 수 있다.

curl -H "X-Vault-Token: token" -X GET http://<vault_hostname>:<port>/v1/keymgmt/kms/gcpckms-korea/key/gcp-aes256-gcm96
+{
+  "request_id": "6f3a9711-2c6c-d894-55a9-74897d735759",
+  "lease_id": "",
+  "lease_duration": 0,
+  "renewable": false,
+  "data": {
+    "distribution_time": "2023-07-17T14:48:10.777969+09:00",
+    "name": "gcp-aes256-gcm96-1689572890",
+    "protection": "hsm",
+    "purpose": "decrypt,encrypt",
+    "versions": {
+      "1": "1"
+    }
+  },
+  "warnings": null
+}
+

GCP Cloud KMS에 적용된 키를 순환시킨다.

$ vault write -f keymgmt/key/gcp-aes256-gcm96/rotate
+

순환되어 새로 추가된 키 버전을 확인한다.

$ vault read keymgmt/key/gcp-aes256-gcm96
+Key                    Value
+---                    -----
+deletion_allowed       false
+latest_version         2
+min_enabled_version    1
+name                   gcp-aes256-gcm96
+type                   aes256-gcm96
+versions               map[1:map[creation_time:2023-07-17T13:46:58.17194+09:00] 2:map[creation_time:2023-07-17T14:54:38.978298+09:00]]
+

키가 적용된 AWS KMS에도 순환된 키 버전이 적용됨을 확인한다.

$ vault read keymgmt/kms/gcpckms-korea/key/gcp-aes256-gcm96
+Key                  Value
+---                  -----
+distribution_time    2023-07-17T14:48:10.777969+09:00
+name                 gcp-aes256-gcm96-1689572890
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:1 2:2]
+

GCP Console에서 확인하면, 신규 대상 키에 신규 버전의 키 항목이 새로 추가됨을 확인할 수 있다.

키: “gcp-aes256-gcm96-1689572890” – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-56-06
키: “gcp-aes256-gcm96-1689572890” – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 14-56-06

적용된 키의 최소 버전을 업데이트 한다.

$ vault write keymgmt/key/gcp-aes256-gcm96 min_enabled_version=2 deletion_allowed=true
+
+Success! Data written to: keymgmt/key/gcp-aes256-gcm96
+

키의 최소 버전에 따라 그 이하의 키가 삭제되었음을 확인한다. (비활성 처리)

$ vault read keymgmt/key/gcp-aes256-gcm96
+
+Key                    Value
+---                    -----
+deletion_allowed       true
+latest_version         2
+min_enabled_version    2
+name                   gcp-aes256-gcm96
+type                   aes256-gcm96
+versions               map[2:map[creation_time:2023-07-17T14:54:38.978298+09:00]]
+

GCP Cloud KMS에 적용된 버전은 기존 버전은 존재하나, GCP Console에서 확인하면 비활성 처리됨을 확인할 수 있다.

$ vault read keymgmt/kms/gcpckms-korea/key/gcp-aes256-gcm96
+Key                  Value
+---                  -----
+distribution_time    2023-07-17T14:48:10.777969+09:00
+name                 gcp-aes256-gcm96-1689572890
+protection           hsm
+purpose              decrypt,encrypt
+versions             map[1:1 2:2]
+
Google Cloud console 2023-07-17 14-58-47
Google Cloud console 2023-07-17 14-58-47

최소 버전을 이전 버전을 포함하여 업데이트 하면 이전 버전의 키가 복구된다.

$ vault write keymgmt/key/gcp-aes256-gcm96 min_enabled_version=1 deletion_allowed=true
+
+Success! Data written to: keymgmt/key/gcp-aes256-gcm96
+
+$ vault read keymgmt/key/gcp-aes256-gcm96
+Key                    Value
+---                    -----
+deletion_allowed       true
+latest_version         2
+min_enabled_version    1
+name                   gcp-aes256-gcm96
+type                   aes256-gcm96
+versions               map[1:map[creation_time:2023-07-17T13:46:58.17194+09:00] 2:map[creation_time:2023-07-17T14:54:38.978298+09:00]]
+
키: “gcp-aes256-gcm96-1689572890” – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 15-00-06
키: “gcp-aes256-gcm96-1689572890” – 보안 – gs-keymgmt-test – Google Cloud Console 2023-07-17 15-00-06

3.5 테스트를 위한 예제 - Python

https://github.com/GoogleCloudPlatform/python-docs-samples/blob/HEAD/kms/snippets/encrypt_symmetric.pyopen in new window

https://github.com/GoogleCloudPlatform/python-docs-samples/blob/174c3032a1faea4ceb3b93385eac71b80d87e6e1/kms/snippets/decrypt_symmetric.pyopen in new window

  • python 3.x
  • 필요 패키지
    • google-cloud-kms
    • cryptography
    • crcmod
    • jwcrypto
$ python --version
+Python 3.9.12
+ 
+$ pip install google-cloud-kms cryptography crcmod jwcrypto
+
+$ export GOOGLE_APPLICATION_CREDENTIALS="$FULL_PATH/kms.json"
+

google-cloud-kms 설치시 grpcio 설치 실패하여 테스트 하지 못함

3.6 테스트를 위한 예제 - NodeJS

https://github.com/googleapis/nodejs-kms/tree/aad6cc451952f42b96d752f31399a2c364f07610/samplesopen in new window

  • nodejs v14.20.0
  • 필요 패키지
    • @google-cloud/kms
    • fast-crc32c
$ node --version
+v14.20.0
+
+$ npm install --save @google-cloud/kms
+$ npm install --save fast-crc32c
+
+$ export GOOGLE_APPLICATION_CREDENTIALS="$FULL_PATH/kms.json"
+

package.json

{
+  "name": "gcpckms-test",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "@google-cloud/kms": "^3.7.0",
+    "fast-crc32c": "^2.0.0"
+  }
+}
+

encryptSymmetric.js

'use strict';
+
+const projectId = 'hc-f5e09ac82cca41c78e99aac5ea3'
+const locationId = 'asia-northeast3'
+const keyRingId = 'vault-keyring'
+const keyId = 'gcp-aes256-gcm96-1689572890'
+const versionId = '1'
+const plaintextBuffer = Buffer.from('Vault GCP Cloud KMS test')
+
+const {KeyManagementServiceClient} = require('@google-cloud/kms');
+
+const crc32c = require('fast-crc32c');
+
+// Instantiates a client
+const client = new KeyManagementServiceClient();
+
+// Build the key name
+const keyName = client.cryptoKeyPath(projectId, locationId, keyRingId, keyId, versionId);
+
+// Optional, but recommended: compute plaintext's CRC32C.
+async function encryptSymmetric() {
+  const plaintextCrc32c = crc32c.calculate(plaintextBuffer);
+  console.log(`Plaintext crc32c: ${plaintextCrc32c}`);
+  const [encryptResponse] = await client.encrypt({
+    name: keyName,
+    plaintext: plaintextBuffer,
+    plaintextCrc32c: {
+      value: plaintextCrc32c,
+    },
+  });
+
+  const ciphertext = encryptResponse.ciphertext;
+
+  // Optional, but recommended: perform integrity verification on encryptResponse.
+  // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
+  // https://cloud.google.com/kms/docs/data-integrity-guidelines
+  if (!encryptResponse.verifiedPlaintextCrc32c) {
+    throw new Error('Encrypt: request corrupted in-transit');
+  }
+  if (
+    crc32c.calculate(ciphertext) !==
+    Number(encryptResponse.ciphertextCrc32c.value)
+  ) {
+    throw new Error('Encrypt: response corrupted in-transit');
+  }
+
+  console.log(`Ciphertext: ${ciphertext.toString('base64')}`);
+  console.log(`Ciphertext crc32c: ${encryptResponse.ciphertextCrc32c.value}`)
+  return ciphertext;
+}
+
+async function decryptSymmetric(ciphertext) {
+  const cipherTextBuf = Buffer.from(await ciphertext);
+  const ciphertextCrc32c = crc32c.calculate(cipherTextBuf);
+  console.log(`Ciphertext crc32c: ${ciphertextCrc32c}`);
+  const [decryptResponse] = await client.decrypt({
+    name: keyName,
+    ciphertext: cipherTextBuf,
+    ciphertextCrc32c: {
+      value: ciphertextCrc32c,
+    },
+  });
+
+  // Optional, but recommended: perform integrity verification on decryptResponse.
+  // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
+  // https://cloud.google.com/kms/docs/data-integrity-guidelines
+  if (
+    crc32c.calculate(decryptResponse.plaintext) !==
+    Number(decryptResponse.plaintextCrc32c.value)
+  ) {
+    throw new Error('Decrypt: response corrupted in-transit');
+  }
+
+  const plaintext = decryptResponse.plaintext.toString();
+
+  console.log(`Plaintext: ${plaintext}`);
+  console.log(`Plaintext crc32c: ${decryptResponse.plaintextCrc32c.value}`)
+  return plaintext;
+}
+
+decryptSymmetric(encryptSymmetric());
+
$ node encrypt_decrypt.js
+Ciphertext: CiQADXWVuwUXBHPL+a8tqce4HfUe3YDMujDZebUWGn4wajmCflcSRypFChQKDJ2A64fX3MUmUfJ8fxDiwuqVBhITCgtm+dZClP/tuRw0CxDE64XfDRoYChChQEcfHsoXhHFXpkpaaTvMENuLg60G
+Plaintext: Vault GCP Cloud KMS test
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/kmip-mongo.html b/04-HashiCorp/06-Vault/02-Secret_Engine/kmip-mongo.html new file mode 100644 index 0000000000..d01e751726 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/kmip-mongo.html @@ -0,0 +1,107 @@ + + + + + + + + + + KMIP - MongoDB | docmoa + + + + + +
본문으로 건너뛰기

KMIP - MongoDB

1분 미만vaultVault EnterpriseKMIPMongoDB

KMIP - MongoDB

Enterprise 기능

Vault - dev mode run (Option)

VAULT_UI=true vault server -dev-root-token-id=root -dev -log-level=trace
+
+export VAULT_ADDR="http://127.0.0.1:8200"
+echo "export VAULT_ADDR=$VAULT_ADDR" >> /root/.bashrc
+vault status
+vault login root
+

KMIP 구성

KMIP 활성화

$ vault secrets enable kmip
+
+Success! Enabled the kmip secrets engine at: kmip/
+

KMIP Listner 구성 (5696은 표준 기본 포트 입니다.)

$ vault write kmip/config listen_addrs=0.0.0.0:5696 \
+  tls_ca_key_type="rsa" \
+  tls_ca_key_bits=2048
+  
+Success! Data written to: kmip/config
+

MongoDB에 전달할 KMIP CA 인증서를 저장

$ vault read -format json kmip/ca | jq -r .data.ca_pem > ca.pem
+

샘플로 "HashiCup" 앱의 관리 개체에 대한 범위를 생성

  • 범위는 KMIP 관리 객체를 여러 명명된 버킷으로 분할합니다. (여기서는 "HashiCup")
  • 버킷 하위의 Role(역할)은 버킷 내에서 관리되며 다양한 허용 KMIP 작업을 할당할 수 있습니다. 여기서는 MongoDB가 수행할 수 있는 허용된 KMIP 작업을 지정하는 "payment" 역할도 생성합니다.
$ vault write -f kmip/scope/hashicups
+Success! Data written to: kmip/scope/hashicups
+
+$ vault write kmip/scope/hashicups/role/payments operation_all=true
+Success! Data written to: kmip/scope/hashicups/role/payments
+

리프 인증서와 개인 키 생성

$ vault write -format=json \
+  kmip/scope/hashicups/role/payments/credential/generate \
+  format=pem > credential.json
+$ jq -r .data.certificate < credential.json > cert.pem
+$ jq -r .data.private_key < credential.json > key.pem
+$ cat cert.pem key.pem > client.pem
+

MongoDB 구성

KMIP을 사용하기 위한 옵션과 함께 mongoDB를 시작

$ mongod --dbpath /var/lib/mongodb \
+  --logpath /var/log/mongodb/mongo.log \
+  --enableEncryption \
+  --kmipServerName localhost \
+  --kmipPort 5696 \
+  --kmipServerCAFile ca.pem \
+  --kmipClientCertificateFile client.pem
+

KMIP 적용 확인

$ cat /var/log/mongodb/mongo.log  | grep KMIP | jq
+{
+  "t": {
+    "$date": "2021-07-20T02:03:34.031+00:00"
+  },
+  "s": "I",
+  "c": "STORAGE",
+  "id": 24199,
+  "ctx": "initandlisten",
+  "msg": "Created KMIP key",
+  "attr": {
+    "keyId": "agZTSeeJyQjVOKJgn3xeGJ6Va8sXfRXP"
+  }
+}
+

TEST

샘플 데이터 삽입

$ mongo
+
+MongoDB Enterprise > db.examples.insertOne(
+  {
+    name: "sue",
+    age: 26
+  }
+)
+
+MongoDB Enterprise > exit
+

결과 확인

# Collection WiredTiger 파일에 기록된 정보
+# KMIP 적용 전
+$ cat /var/lib/mongodb/collection-7*
+A�#�\�D���1_id`�1�g�~R=��namesueage:@4�D��8�����D��2
+
+# KMIP 적용 후
+$ cat /var/lib/mongodb/collection-7*
+A�#�\
+     ��$�|��H�}l�����(ں?����s�ɛocD��\K�>J������m��N��#����_�������К
+�X���ϩ}_�z6��L�nQ���pQ�sO�]�0���h_�     #�Ȟ�߳2
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/pki-nginx.html b/04-HashiCorp/06-Vault/02-Secret_Engine/pki-nginx.html new file mode 100644 index 0000000000..cfdd90ff83 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/pki-nginx.html @@ -0,0 +1,317 @@ + + + + + + + + + + PKI - nginx 샘플 | docmoa + + + + + +
본문으로 건너뛰기

PKI - nginx 샘플

약 3 분vaultPKI

PKI - nginx 샘플

https://learn.hashicorp.com/tutorials/vault/pki-engineopen in new window

Vault 구성

환경 변수

export VAULT_SKIP_VERIFY=True
+export VAULT_ADDR='http://172.28.128.11:8200'
+export VAULT_TOKEN=s.8YXFI825TZxnwLtYHsLc9Fnb
+

정책 및 사용자 구성

. ./<pki-policy.hcl>

$ vault policy write pki - << EOF
+# Enable secrets engine
+path "sys/mounts/*" {
+  capabilities = [ "create", "read", "update", "delete", "list" ]
+}
+
+# List enabled secrets engine
+path "sys/mounts" {
+  capabilities = [ "read", "list" ]
+}
+
+# Work with pki secrets engine
+path "pki*" {
+  capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
+}
+EOF
+

인증 생성

user/pass 생성

$ vault auth enable userpass
+
+$ vault write auth/userpass/users/pki \
+    password=pki \
+    policies=pki
+    
+$ vault login -method userpass username=pki password=pki
+Success! You are now authenticated. The token information displayed below
+is already stored in the token helper. You do NOT need to run "vault login"
+again. Future Vault requests will automatically use this token.
+
+Key                    Value
+---                    -----
+token                  s.ldJApybiqGBmq3CuBAaqsKXZ
+token_accessor         Maek0IMLkOLmFVkpG4DoGUdY
+token_duration         768h
+token_renewable        true
+token_policies         ["pki"]
+identity_policies      []
+policies               ["pki"]
+token_meta_username    db
+
+$ export VAULT_TOKEN=s.7mN7t6hd1a1m97j2ptytfCqf
+

approle 생성

$ vault auth enable approle
+Success! Enabled approle auth method at: approle/
+
+$ vault write auth/approle/role/pki-agent \
+    secret_id_ttl=120m \
+    token_ttl=60m \
+    token_max_tll=120m \
+    policies="pki"
+Success! Data written to: auth/approle/role/pki-agent
+
+$ vault read auth/approle/role/pki-agent/role-id
+Key        Value
+---        -----
+role_id    dfa2a248-1e1b-e2e9-200c-69c63b9ca447
+
+$ vault write -f auth/approle/role/pki-agent/secret-id
+Key                   Value
+---                   -----
+secret_id             864360c1-c79f-ea7c-727b-7752361fe1ba
+secret_id_accessor    3cc068e2-a172-2bb1-c097-b777c3525ba6
+
+#Tip
+$ echo $(vault write -f -format=json auth/approle/role/pki-agent/secret-id | jq -r '.data.secret_id') > secretid
+
+$ vault write auth/approle/login role_id=dfa2a248-1e1b-e2e9-200c-69c63b9ca447 secret_id=864360c1-c79f-ea7c-727b-7752361fe1ba
+Key                     Value
+---                     -----
+token                   s.uGtTFun8zSNcczBrtEJrSx5y
+token_accessor          eLjxnLYqfVTWFbOCXDVqwb3S
+token_duration          1h
+token_renewable         true
+token_policies          ["default" "pki"]
+identity_policies       []
+policies                ["default" "pki"]
+token_meta_role_name    pki-agent
+

PKI Engin

1단계 : Root CA 생성 (없는 경우)

  • pki secret engine 활성화

    $ vault secrets enable -path pki pki
    +Success! Enabled the database secrets engine at: pki/
    +
  • 최대 TTL (Time-to-Live)이 87600 시간(10년) 인 인증서를 발급

    $ vault secrets tune -max-lease-ttl 87600h pki
    +Success! Tuned the secrets engine at: pki/
    +
  • 루트 인증서 생성 CA_cert.crt

    $ vault write -f -field=certificate pki/root/generate/internal \
    +        common_name="example.com" \
    +        ttl=87600h > CA_cert.crt
    +

    이렇게하면 새로운 자체 서명 된 CA 인증서와 개인 키가 생성됩니다. Vault는 임대 기간 (TTL)이 끝나면 생성 된 루트를 자동으로 취소합니다. CA 인증서는 자체 인증서 해지 목록 (CRL)에 서명합니다.

  • CA 와 CRL URL 구성

    $ vault write pki/config/urls \
    +        issuing_certificates="http://172.28.128.11:8200/v1/pki/ca" \
    +        crl_distribution_points="http://172.28.128.11:8200/v1/pki/crl"
    +

2단계 : Intermediate CA 구성

이전 단계에서 생성한 Root CA를 사용하여 Intermediate CA 생성

  • pki secret engine 활성화

    $ vault secrets enable -path=pki_int pki
    +Success! Enabled the pki secrets engine at: pki_int/
    +
  • 최대 TTL (Time-to-Live)이 43800 시간(5년) 인 인증서를 발급 하도록 비밀 엔진을 조정

    $ vault secrets tune -max-lease-ttl=43800h pki_int
    +Success! Tuned the secrets engine at: pki_int/
    +
  • Intermediate CSR 생성 <pki_intermediate.csr>

    $ vault write -format=json pki_int/intermediate/generate/internal \
    +        common_name="example.com Intermediate Authority" \
    +        | jq -r '.data.csr' > pki_intermediate.csr
    +
  • Root 인증서로 Intermediate 인증서에 서명 <intermediate.cert.pem>

    $ vault write -format=json pki/root/sign-intermediate csr=@pki_intermediate.csr \
    +        format=pem_bundle ttl="43800h" \
    +        | jq -r '.data.certificate' > intermediate.cert.pem
    +
  • CSR이 서명되고 Root CA가 인증서를 반환하면 다시 Vault에서 가져옴

    $ vault write -f pki_int/intermediate/set-signed certificate=@intermediate.cert.pem
    +Success! Data written to: pki_int/intermediate/set-signed
    +
  • URL 구성

    $ vault write pki_int/config/urls \
    +        issuing_certificates="http://172.28.128.11:8200/v1/pki_int/ca" \
    +        crl_distribution_points="http://172.28.128.11:8200/v1/pki_int/crl"
    +

3단계 : Role 생성

Role은 자격 증명을 생성하는데 사용되는 정책에 매핑되는 논리적 이름으로, 구성 매개변수가 인증서 일반 이름, 대체 이름, 유효한 키 사용등을 제어 가능

[주요 매개 변수]
ParamDescription
allowed_domains역할의 도메인을 지정합니다 (allow_bare_domainsallow-subdomains 옵션과 함께 사용).
allow_bare_domains클라이언트가 실제 도메인 자체의 값과 일치하는 인증서를 요청할 수 있는지 여부를 지정합니다.
allow_subdomains클라이언트가 다른 역할 옵션에서 허용하는 CN의 하위 도메인 인 CN을 사용하여 인증서를 요청할 수 있는지 여부를 지정합니다 (참고 : 여기에는 와일드 카드 하위 도메인이 포함됨).
allow_glob_domainsallowed_domains에 지정된 이름에 glob 패턴 (예 : ftp * .example.com)을 포함 할 수 있습니다.

여기서는 example-dot-com 이라는 Role 을 생성

  • 하위 도메인을 허용하는 example-dot.com Role 생성

    $ vault write pki_int/roles/example-dot-com \
    +        allowed_domains="example.com" \
    +        allow_subdomains=true \
    +        max_ttl="720h"
    +Success! Data written to: pki_int/roles/example-dot-com
    +

4단계 : 인증서 요청

Vault의 단기 비밀관리의 철학은 인증서 수명을 짧게 유지하는 것입니다.

  • example-dot-com Role에 따라 test.example.com 도메인에 대한 새 인증서 요청
    (응답에는 PEM으로 인코딩 된 개인 키, 키 유형 및 인증서 일련 번호가 포함됩니다.)

    $ vault write pki_int/issue/example-dot-com common_name="test.example.com" ttl="2m" #format="pem_bundle"
    +# vault write pki_int/issue/example-dot-com common_name="tfe.example.com" ttl="700h" 
    +
    +Key                 Value
    +---                 -----
    +certificate         -----BEGIN CERTIFICATE-----
    +MIIDwzCCAqugAwIBAgIUTQABMCAsXjG6ExFTX8201xKVH4IwDQYJKoZIhvcNAQEL
    +BQAwGjEYMBYGA1UEAxMPd3d3LmV4YW1wbGUuY29tMB4XDTE4MDcyNDIxMTMxOVoX
    +             ...
    +
    +-----END CERTIFICATE-----
    +issuing_ca          -----BEGIN CERTIFICATE-----
    +MIIDQTCCAimgAwIBAgIUbMYp39mdj7dKX033ZjK18rx05x8wDQYJKoZIhvcNAQEL
    +             ...
    +
    +-----END CERTIFICATE-----
    +private_key         -----BEGIN RSA PRIVATE KEY-----
    +MIIEowIBAAKCAQEAte1fqy2Ekj+EFqKV6N5QJlBgMo/U4IIxwLZI6a87yAC/rDhm
    +W58liadXrwjzRgWeqVOoCRr/B5JnRLbyIKBVp6MMFwZVkynEPzDmy0ynuomSfJkM
    +             ...
    +
    +-----END RSA PRIVATE KEY-----
    +private_key_type    rsa
    +serial_number       4d:00:01:30:20:2c:5e:31:ba:13:11:53:5f:cd:b4:d7:12:95:1f:82
    +

Vault Agent

생성되는 PKI인증서를 자동으로 갱신하기 위해 Vault Agent 구성

secretid 생성의 예

echo $(vault write -f -format=json auth/approle/role/pki-agent/secret-id | jq -r '.data.secret_id') > secretid
+

[vault_agent.hcl]

pid_file = "/root/vault_agent/pidfile"
+
+auto_auth {
+  method  {
+    type = "approle"
+    config = {
+      role_id_file_path = "/root/vault_agent/roleid"
+      secret_id_file_path = "/root/vault_agent/secretid"
+    }
+  }
+
+  sink {
+    type = "file"
+    config = {
+      path = "/tmp/vault_agent"
+    }
+  }
+}
+
+vault {
+  address = "http://172.28.128.11:8200"
+}
+
+template {
+  source      = "/root/vault_agent/cert.tpl"
+  destination = "/root/cert/my-app.crt"
+}
+
+template {
+  source      = "/root/vault_agent/ca.tpl"
+  destination = "/root/cert/ca.crt"
+}
+
+template {
+  source      = "/root/vault_agent/key.tpl"
+  destination = "/root/cert/my-app.key"
+}
+

[인증 정보]

role_id_file_path, secret_id_file_path에는 앞서 생성한 approle의 role id와 secret id를 대상 파일에 저장

[template - default pem]

  • cert.tpl

    {{- /* /tmp/ca.tpl */ -}}
    +{{ with secret "pki_int/issue/example-dot-com" "common_name=test.example.com" }}
    +{{ .Data.issuing_ca }}{{ end }}
    +
  • ca.tpl

    {{- /* /tmp/cert.tpl */ -}}
    +{{ with secret "pki_int/issue/example-dot-com" "common_name=test.example.com" }}
    +{{ .Data.certificate }}{{ end }}
    +
  • Key.tpl

    {{- /* /tmp/key.tpl */ -}}
    +{{ with secret "pki_int/issue/example-dot-com" "common_name=test.example.com" }}
    +{{ .Data.private_key }}{{ end }}
    +

실행은 스크립트 혹은 스크립트를 서비스로 구성

  • script (e.g. start.shopen in new window)

    vault agent -config=/root/vault_agent/vault_agent.hcl -log-level=debug
    +
  • service (e.g. vault-agent.service)

    [Unit]
    +Description=Vault Service Discovery Agent
    +Documentation=https://www.vaultproject.io/
    +After=network-online.target
    +Wants=network-online.target
    +
    +[Service]
    +Type=simple
    +User=vault
    +Group=vault
    +ExecStart=/usr/local/bin/vault agent -config=/root/vault_agent/vault_agent.hcl
    +
    +ExecReload=/bin/kill -HUP $MAINPID
    +KillSignal=SIGINT
    +TimeoutStopSec=5
    +Restart=on-failure
    +SyslogIdentifier=vault
    +
    +[Install]
    +WantedBy=multi-user.target
    +

Appendix

PKI 관리

  • pki revoke

    $ vault write pki_int/revoke serial_number="56:ac:c0:f3:b4:1e:87:69:ec:dd:7d:27:54:f6:1c:14:91:3d:11:2d"
    +Key                        Value
    +---                        -----
    +revocation_time            1611557908
    +revocation_time_rfc3339    2021-01-25T06:58:28.592511981Z
    +
  • pki rotate

    $ vault read pki_int/crl/rotate
    +Key        Value
    +---        -----
    +success    true
    +

Nginx 활용

테스트 대상 시스템에 CA, Intermediated 인증서를 신뢰할 수 있는 인증서로 등록

[vault_agent.hcl]

pid_file = "/root/vault_agent/pidfile"
+
+auto_auth {
+  method  {
+    type = "approle"
+    config = {
+      role_id_file_path = "/root/vault_agent/roleid"
+      secret_id_file_path = "/root/vault_agent/secretid"
+    }
+  }
+
+  sink {
+    type = "file"
+    config = {
+      path = "/tmp/vault_agent"
+    }
+  }
+}
+
+vault {
+  address = "http://172.28.128.11:8200"
+}
+
+template {
+  source      = "/root/vault_agent/cert.tpl"
+  destination = "/root/cert/test.cert.pem"
+  perms       = "0600"
+}
+
+template {
+  source      = "/root/vault_agent/key.tpl"
+  destination = "/root/cert/test.key.pem"
+  perms       = "0600"
+}
+

[template - pem_bundle]

  • cert.tpl

    {{- /* /tmp/cert.tpl */ -}}
    +{{ with secret "pki_int/issue/example-dot-com" "common_name=test.example.com"  "ttl=2m"}}
    +{{ .Data.certificate }}
    +{{ .Data.issuing_ca }}{{ end }}
    +
  • Key.tpl

    {{- /* /tmp/key.tpl */ -}}
    +{{ with secret "pki_int/issue/example-dot-com" "common_name=test.example.com" "ttl=2m"}}
    +{{ .Data.private_key }}{{ end }}
    +

[/etc/nginx/sites-enabled/default]

rotation 되면 systemctl reload nginx

server {
+       listen 80;
+       server_name text.example.com;
+       return 301 HTTPS://$server_name$request_uri;
+}
+
+server {
+	listen 443 ssl default_server;
+	listen [::]:443 ssl default_server;
+
+  ssl on;
+  server_name test.example.com;
+  ssl_certificate /root/cert/test.cert.pem;
+  ssl_certificate_key /root/cert/test.key.pem;
+
+	root /var/www/html;
+
+	# Add index.php to the list if you are using PHP
+	index index.html index.htm index.nginx-debian.html;
+
+	server_name _;
+
+	location / {
+		# First attempt to serve request as file, then
+		# as directory, then fall back to displaying a 404.
+		try_files $uri $uri/ =404;
+	}
+}
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-debian.html b/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-debian.html new file mode 100644 index 0000000000..7dc21035c2 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-debian.html @@ -0,0 +1,92 @@ + + + + + + + + + + SSH OTP - Debian 계열 | docmoa + + + + + +
본문으로 건너뛰기

SSH OTP - Debian 계열

약 1 분vaultSSHOTPDebianUbuntu

SSH OTP - Debian 계열

https://learn.hashicorp.com/tutorials/vault/pki-engineopen in new window

Vault설정

시크릿 엔진 활성화

$ vault secrets enable -path ssh ssh
+

롤 생성

$ vault write ssh/roles/otp_key_role \
+    key_type=otp \
+    default_user=test \
+    allowed_users=test \
+    key_bits=2048 \
+    cidr_list=172.28.128.0/24
+Success! Data written to: ssh/roles/otp_key_role
+

e.g. cidr_list=127.0.0.1/32,172.28.128.1/32 or 0.0.0.0/0

대상 서버 설정

접속할 사용자 생성

$ sudo adduser test
+

vault-ssh-helper 구성 <config.hcl>

https://github.com/hashicorp/vault-ssh-helperopen in new window

vault_addr = "http://172.28.128.21:8200"
+ssh_mount_point = "ssh"
+namespace = ""
+tls_skip_verify = true
+allowed_roles = "*"
+allowed_cidr_list = "0.0.0.0/0"
+

vault-ssh-helper 다운로드

https://releases.hashicorp.com/vault-ssh-helper/open in new window
tls를 사용하지 않는 경우. -dev

아래와 같이 검증

vault-ssh-helper -verify-only -config=config.hcl -dev
+

pam.d 설정

기존 PW 방식을 대체

/etc/pam.d/sshd 파일의 @include common-auth 부분을 다음과 같이 변경 추가

#@include common-auth
+auth requisite pam_exec.so quiet expose_authtok log=/tmp/vaultssh.log /usr/local/bin/vault-ssh-helper -config=/etc/vault-ssh-helper.d/config.hcl -dev
+auth optional pam_unix.so not_set_pass use_first_pass nodelay
+

ssh 설정

/etc/ssh/sshd_config 파일의 ChallengeResponseAuthentication 부분을 수정

이미 있는 옵션은 값 수정, 없는 옵션은 추가

ChallengeResponseAuthentication yes
+UsePAM yes
+PasswordAuthentication no
+

ssh 서비스 재시작

$ systemctl restart ssh
+

테스트

otp 발급

$ vault write ssh/creds/otp_key_role ip=172.28.128.31
+Key                Value
+---                -----
+lease_id           ssh/creds/otp_key_role/r2SFjhwt3brVT0msL1KEq2Dv
+lease_duration     768h
+lease_renewable    false
+ip                 172.28.128.31
+key                f8a32d6c-beec-383c-62d6-3718b367f88d
+key_type           otp
+port               22
+username           test
+

접속 방법 1. ssh

Password: 에 앞서 요청한 롤의 credential 값의 key 를 넣어준다.

$ ssh test@172.28.128.31
+Password:
+

접속 방법 2. vault ssh

Vault로 해당 ssh otp에 권한이 있는 사용자인 경우 sshpass 가 설치되어있으면 자동 입력

$ vault ssh -role otp_key_role -mode otp test@172.28.128.31
+or
+$ vault ssh -role otp_key_role -mode otp -strict-host-key-checking=no test@172.28.128.31
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-redhat.html b/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-redhat.html new file mode 100644 index 0000000000..e5ee7ddc12 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-redhat.html @@ -0,0 +1,98 @@ + + + + + + + + + + SSH OTP - RedHat 계열 | docmoa + + + + + +
본문으로 건너뛰기

SSH OTP - RedHat 계열

약 1 분vaultSSHOTPRockyRHELCentOS

SSH OTP - RedHat 계열

https://learn.hashicorp.com/tutorials/vault/pki-engineopen in new window

Vault설정

시크릿 엔진 활성화

$ vault secrets enable -path ssh ssh
+

롤 생성

$ vault write ssh/roles/otp_key_role \
+    key_type=otp \
+    default_user=test \
+    allowed_users=test \
+    key_bits=2048 \
+    cidr_list=172.28.128.0/24
+Success! Data written to: ssh/roles/otp_key_role
+

e.g. cidr_list=127.0.0.1/32,172.28.128.1/32 or 0.0.0.0/0

대상 서버 설정

접속할 사용자 생성

$ sudo adduser test
+

vault-ssh-helper 구성 <config.hcl>

https://github.com/hashicorp/vault-ssh-helperopen in new window

vault_addr = "http://172.28.128.21:8200"
+ssh_mount_point = "ssh"
+namespace = ""
+tls_skip_verify = true
+allowed_roles = "*"
+allowed_cidr_list = "0.0.0.0/0"
+

vault-ssh-helper 다운로드

https://releases.hashicorp.com/vault-ssh-helper/open in new window

vault-ssh-helper verify

tls를 사용하지 않는 경우. -dev

vault-ssh-helper -verify-only -config=config.hcl -dev
+

Pam 설정

/etc/pam.d/sshd 파일의 @include common-auth 부분을 다음과 같이 변경 추가

#%PAM-1.0
+auth        required    pam_sepermit.so
+#auth       substack    password-auth # COMMENT OUT FOR SSH-HELPER
+auth        include     postlogin
+auth        requisite   pam_exec.so quiet expose_authtok log=/var/log/vaultssh.log /usr/local/bin/vault-ssh-helper -config=/etc/vault-ssh-helper.d/config.hcl -dev
+auth        optional    pam_unix.so not_set_pass use_first_pass nodelay
+# Used with polkit to reauthorize users in remote sessions
+-auth       optional    pam_reauthorize.so prepare
+account     required    pam_nologin.so
+account     include     password-auth
+#password   include     password-auth # COMMENT OUT FOR SSH-HELPER
+# pam_selinux.so close should be the first session rule
+session     required    pam_selinux.so close
+session     required    pam_loginuid.so
+# pam_selinux.so open should only be followed by sessions to be executed in the user context
+session     required    pam_selinux.so open env_params
+session     required    pam_namespace.so
+session     optional    pam_keyinit.so force revoke
+session     include     password-auth
+session     include     postlogin
+# Used with polkit to reauthorize users in remote sessions
+-session   optional     pam_reauthorize.so prepare
+

ssh 설정

/etc/ssh/sshd_config 파일의 다음 항목을 수정

이미 있는 옵션은 값 수정, 없는 옵션은 추가

ChallengeResponseAuthentication yes
+UsePAM yes
+PasswordAuthentication no
+

ssh 서비스 재시작

$ systemctl restart sshd
+

테스트

otp 발급

$ vault write ssh/creds/otp_key_role ip=172.28.128.31
+Key                Value
+---                -----
+lease_id           ssh/creds/otp_key_role/r2SFjhwt3brVT0msL1KEq2Dv
+lease_duration     768h
+lease_renewable    false
+ip                 172.28.128.31
+key                f8a32d6c-beec-383c-62d6-3718b367f88d
+key_type           otp
+port               22
+username           test
+

접속 방법 1. ssh

Password: 에 앞서 요청한 롤의 credential 값의 key 를 넣어준다.

$ ssh test@172.28.128.31
+Password:
+

접속 방법 2. vault ssh

Vault로 해당 ssh otp에 권한이 있는 사용자인 경우 sshpass 가 설치되어있으면 자동 입력

$ vault ssh -role otp_key_role -mode otp test@172.28.128.31
+or
+$ vault ssh -role otp_key_role -mode otp -strict-host-key-checking=no test@172.28.128.31
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-signed-certificates.html b/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-signed-certificates.html new file mode 100644 index 0000000000..31747ffc22 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-signed-certificates.html @@ -0,0 +1,85 @@ + + + + + + + + + + SSH - Signed Certificates | docmoa + + + + + +
본문으로 건너뛰기

SSH - Signed Certificates

1분 미만vaultSSH

SSH - Signed Certificates

Vault설정

시크릿 엔진 활성화

$ vault secrets enable -path=ssh-client-signer ssh
+

구성 생성

$ vault write ssh-client-signer/config/ca generate_signing_key=true
+Key           Value
+---           -----
+public_key    ...
+

keypair가 이미 있는 경우

$ vault write ssh-client-signer/config/ca \
+    private_key="..." \
+    public_key="..."
+

구성에서 public key 가져오기

API

$ curl -o /etc/ssh/trusted-user-ca-keys.pem http://127.0.0.1:8200/v1/ssh-client-signer/public_key
+

CLI

$ vault read -field=public_key ssh-client-signer/config/ca > /tmp/trusted-user-ca-keys.pem
+$ /tmp/trusted-user-ca-keys.pem
+/etc/ssh/trusted-user-ca-keys.pem로 복사
+

SSH 설정

/etc/ssh/sshd_config 파일의 TrustedUserCAKeys 부분을 수정

이미 있는 옵션은 값 수정, 없는 옵션은 추가

TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
+

ssh 서비스 재시작

$ systemctl restart sshd
+

키 서명을 위한 Role 생성

TTL 2분

$ vault write ssh-client-signer/roles/my-role - <<EOF
+{
+  "allow_user_certificates": true,
+  "allowed_users": "*",
+  "allowed_extensions": "permit-pty,permit-port-forwarding",
+  "default_extensions": [
+    {
+      "permit-pty": ""
+    }
+  ],
+  "key_type": "ca",
+  "default_user": "test",
+  "ttl": "0m20s"
+}
+EOF
+

Client SSH Authentication

  1. 클라이언트에서 SSH에서 사용할 Keypair를 생성

    $ ssh-keygen -t rsa -C "test@rocky"
    +Generating public/private rsa key pair.
    +Enter file in which to save the key (/Users/gs/.ssh/id_rsa): /Users/gs/.ssh/vault_rsa
    +
    • -C : 코맨트 옵션
  2. Vault에 생성한 키 중 공개키 (.pub)에 대한 서명 요청

    $ vault write ssh-client-signer/sign/my-role \
    +    public_key=@$HOME/.ssh/vault_rsa.pub
    +    
    +Key             Value
    +---             -----
    +serial_number   c73f26d2340276aa
    +signed_key      ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1...
    +
    • signed_key는 또다른 public key

    위와 같은 방식으로 생성되는 추가 public key인 signed_key를 저장하고자 한다면 다음과 같은 방식으로 가능

    $ vault write -field=signed_key ssh-client-signer/sign/my-role public_key=@$HOME/.ssh/vault_rsa.pub > /tmp/signed-cert.pub
    +    
    +$ vault write -field=signed_key ssh-client-signer/sign/my-role \
    +    public_key=@$HOME/.ssh/vault_rsa.pub > $HOME/.ssh/vault_rsa-cert.pub
    +
  3. 접속 하기

    $ ssh -i /tmp/signed-cert.pub -i ~/.ssh/vault_rsa test@172.28.128.61
    +$ ssh -i ~/.ssh/id_rsa test@172.28.128.61
    +
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/transform-fpe.html b/04-HashiCorp/06-Vault/02-Secret_Engine/transform-fpe.html new file mode 100644 index 0000000000..d47c0e961d --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/transform-fpe.html @@ -0,0 +1,94 @@ + + + + + + + + + + Transform FPE (Ent) | docmoa + + + + + +
본문으로 건너뛰기

Transform FPE (Ent)

1분 미만vaulttransformfpe

Transform FPE (Ent)

Transform secrets 엔진은 제공된 입력 값에 대해 안전한 데이터 변환 및 토큰화를 처리합니다. 변환 방법은 FF3-1 을 통한 형태 보존 암호화(FPE) 와 같은 NIST 검증된 암호화 표준을 포함 할 수 있지만 마스킹과 같은 다른 수단을 통한 데이터의 익명 변환일 수도 있습니다.

아래 영상에서는 UI로 진행하는 방식을 설명합니다.

Enterprise 라이선스가 필요하기 때문에 라이선스가 필요한 경우 Trial 을 발급 받을 수 있습니다.
: https://www.hashicorp.com/products/vault/trialopen in new window

Vault구성

Vault Dev Mode 실행 (Option)

Transform은 엔터프라이즈 기능으로, 테스트를 위해서는 엔터프라이즈 바이너리가 필요합니다. 디렉토리 뒤에 +ent로 표기되어있습니다.

-dev 옵션을 추가하여 테스트를 위한 개발모드 실행을 위해서는 라이선스파일에 대한 경로 설정이 필요합니다.

export VAULT_LICENSE_PATH=<license_file_path>
+vault server -dev
+

Vault CLI 테스트 (Option)

CLI 테스트 시 필요한 환경변수는 다음과 같습니다.

export VAULT_SKIP_VERIFY=True
+export VAULT_ADDR='http://127.0.0.1:8200'
+export VAULT_TOKEN=<mytoken>
+

Transform 구성

  1. 우선 transform 시크릿 엔진을 활성화 합니다.
vault secrets enable transform
+
  1. 사용할 템플릿을 구성합니다.
vault write transform/template/phone-number-tmpl \
+  type=regex \
+  pattern='\d{3}-(\d{4})-(\d{4})' \
+  alphabet=builtin/numeric
+
  • pattern에서 괄호()에 해당하는 위치가 암호화 됩니다.
  • 최소, 최대 암호화 필요 자리수는 Input Limitsopen in new window 링크를 참고 합니다. 예를들어 숫자는 10개의 캐릭터로 구성되므로 최소 6자리는 암호화 필드로 지정되어야 합니다.
  1. transform을 생성합니다. 앞서 생성한 템플릿을 지정합니다.
vault write transform/transformations/fpe/phone-number \
+  template="phone-number-tmpl" \
+  tweak_source=internal \
+  allowed_roles=customer
+
  1. role과 role에서 사용할 transform을 지정합니다.
vault write transform/role/customer transformations=phone-number
+

Transform 동작 확인

  • 암호화

    • 요청
      vault write transform/encode/customer value=010-1234-5678 \
      +    transformation=phone-number
      +
    • 결과
      Key             Value
      +---              -----
      +encoded_value    010-7494-8066
      +
  • 복호화

    • 요청
      vault write transform/decode/customer value=010-7494-8066 \
      +    transformation=phone-number
      +
    • 결과
      Key             Value
      +---              -----
      +decoded_value    010-1234-5678
      +
한글 FPE
vault secrets enable transform
+
+vault write transform/alphabet/hangul alphabet="가각간갇갈감갑갓강개객갠갣갤갬갭갯갱갸갹갼갿걀걈걉걋걍걔걕걘걛걜걤걥걧걩거걱건걷걸검겁것겅게겍겐겓겔겜겝겟겡겨격견겯결겸겹겻경계곅곈곋곌곔곕곗곙고곡곤곧골곰곱곳공과곽관괃괄괌괍괏광괘괙괜괟괠괨괩괫괭괴괵괸괻괼굄굅굇굉교굑굔굗굘굠굡굣굥구국군굳굴굼굽굿궁궈궉권궏궐궘궙궛궝궤궥궨궫궬궴궵궷궹귀귁귄귇귈귐귑귓귕규귝균귣귤귬귭귯귱그극근귿글금급긋긍긔긕긘긛긜긤긥긧긩기긱긴긷길김깁깃깅까깍깐깓깔깜깝깟깡깨깩깬깯깰깸깹깻깽꺄꺅꺈꺋꺌꺔꺕꺗꺙꺠꺡꺤꺧꺨꺰꺱꺳꺵꺼꺽껀껃껄껌껍껏껑께껙껜껟껠껨껩껫껭껴껵껸껻껼꼄꼅꼇꼉꼐꼑꼔꼗꼘꼠꼡꼣꼥꼬꼭꼰꼳꼴꼼꼽꼿꽁꽈꽉꽌꽏꽐꽘꽙꽛꽝꽤꽥꽨꽫꽬꽴꽵꽷꽹꾀꾁꾄꾇꾈꾐꾑꾓꾕꾜꾝꾠꾣꾤꾬꾭꾯꾱꾸꾹꾼꾿꿀꿈꿉꿋꿍꿔꿕꿘꿛꿜꿤꿥꿧꿩꿰꿱꿴꿷꿸뀀뀁뀃뀅뀌뀍뀐뀓뀔뀜뀝뀟뀡뀨뀩뀬뀯뀰뀸뀹뀻뀽끄끅끈끋끌끔끕끗끙끠끡끤끧끨끰끱끳끵끼끽낀낃낄낌낍낏낑나낙난낟날남납낫낭내낵낸낻낼냄냅냇냉냐냑냔냗냘냠냡냣냥냬냭냰냳냴냼냽냿넁너넉넌넏널넘넙넛넝네넥넨넫넬넴넵넷넹녀녁년녇녈념녑녓녕녜녝녠녣녤녬녭녯녱노녹논녿놀놈놉놋농놔놕놘놛놜놤놥놧놩놰놱놴놷놸뇀뇁뇃뇅뇌뇍뇐뇓뇔뇜뇝뇟뇡뇨뇩뇬뇯뇰뇸뇹뇻뇽누눅눈눋눌눔눕눗눙눠눡눤눧눨눰눱눳눵눼눽뉀뉃뉄뉌뉍뉏뉑뉘뉙뉜뉟뉠뉨뉩뉫뉭뉴뉵뉸뉻뉼늄늅늇늉느늑는늗늘늠늡늣능늬늭늰늳늴늼늽늿닁니닉닌닏닐님닙닛닝다닥단닫달담답닷당대댁댄댇댈댐댑댓댕댜댝댠댣댤댬댭댯댱댸댹댼댿덀덈덉덋덍더덕던덛덜덤덥덧덩데덱덴덷델뎀뎁뎃뎅뎌뎍뎐뎓뎔뎜뎝뎟뎡뎨뎩뎬뎯뎰뎸뎹뎻뎽도독돈돋돌돔돕돗동돠돡돤돧돨돰돱돳돵돼돽됀됃됄됌됍됏됑되됙된됟될됨됩됫됭됴됵됸됻됼둄둅둇둉두둑둔둗둘둠둡둣둥둬둭둰둳둴둼둽둿뒁뒈뒉뒌뒏뒐뒘뒙뒛뒝뒤뒥뒨뒫뒬뒴뒵뒷뒹듀듁듄듇듈듐듑듓듕드득든듣들듬듭듯등듸듹듼듿딀딈딉딋딍디딕딘딛딜딤딥딧딩따딱딴딷딸땀땁땃땅때땍땐땓땔땜땝땟땡땨땩땬땯땰땸땹땻땽떄떅떈떋떌떔떕떗떙떠떡떤떧떨떰떱떳떵떼떽뗀뗃뗄뗌뗍뗏뗑뗘뗙뗜뗟뗠뗨뗩뗫뗭뗴뗵뗸뗻뗼똄똅똇똉또똑똔똗똘똠똡똣똥똬똭똰똳똴똼똽똿뙁뙈뙉뙌뙏뙐뙘뙙뙛뙝뙤뙥뙨뙫뙬뙴뙵뙷뙹뚀뚁뚄뚇뚈뚐뚑뚓뚕뚜뚝뚠뚣뚤뚬뚭뚯뚱뚸뚹뚼뚿뛀뛈뛉뛋뛍뛔뛕뛘뛛뛜뛤뛥뛧뛩뛰뛱뛴뛷뛸뜀뜁뜃뜅뜌뜍뜐뜓뜔뜜뜝뜟뜡뜨뜩뜬뜯뜰뜸뜹뜻뜽띄띅띈띋띌띔띕띗띙띠띡띤띧띨띰띱띳띵라락란랃랄람랍랏랑래랙랜랟랠램랩랫랭랴략랸랻랼럄럅럇량럐럑럔럗럘럠럡럣럥러럭런럳럴럼럽럿렁레렉렌렏렐렘렙렛렝려력련렫렬렴렵렷령례롁롄롇롈롐롑롓롕로록론롣롤롬롭롯롱롸롹롼롿뢀뢈뢉뢋뢍뢔뢕뢘뢛뢜뢤뢥뢧뢩뢰뢱뢴뢷뢸룀룁룃룅료룍룐룓룔룜룝룟룡루룩룬룯룰룸룹룻룽뤄뤅뤈뤋뤌뤔뤕뤗뤙뤠뤡뤤뤧뤨뤰뤱뤳뤵뤼뤽륀륃륄륌륍륏륑류륙륜륟률륨륩륫륭르륵른륻를름릅릇릉릐릑릔릗릘릠릡릣릥리릭린릳릴림립릿링마막만맏말맘맙맛망매맥맨맫맬맴맵맷맹먀먁먄먇먈먐먑먓먕먜먝먠먣먤먬먭먯먱머먹먼먿멀멈멉멋멍메멕멘멛멜멤멥멧멩며멱면멷멸몀몁몃명몌몍몐몓몔몜몝몟몡모목몬몯몰몸몹못몽뫄뫅뫈뫋뫌뫔뫕뫗뫙뫠뫡뫤뫧뫨뫰뫱뫳뫵뫼뫽묀묃묄묌묍묏묑묘묙묜묟묠묨묩묫묭무묵문묻물뭄뭅뭇뭉뭐뭑뭔뭗뭘뭠뭡뭣뭥뭬뭭뭰뭳뭴뭼뭽뭿뮁뮈뮉뮌뮏뮐뮘뮙뮛뮝뮤뮥뮨뮫뮬뮴뮵뮷뮹므믁믄믇믈믐믑믓믕믜믝믠믣믤믬믭믯믱미믹민믿밀밈밉밋밍바박반받발밤밥밧방배백밴밷밸뱀뱁뱃뱅뱌뱍뱐뱓뱔뱜뱝뱟뱡뱨뱩뱬뱯뱰뱸뱹뱻뱽버벅번벋벌범법벗벙베벡벤벧벨벰벱벳벵벼벽변볃별볌볍볏병볘볙볜볟볠볨볩볫볭보복본볻볼봄봅봇봉봐봑봔봗봘봠봡봣봥봬봭봰봳봴봼봽봿뵁뵈뵉뵌뵏뵐뵘뵙뵛뵝뵤뵥뵨뵫뵬뵴뵵뵷뵹부북분붇불붐붑붓붕붜붝붠붣붤붬붭붯붱붸붹붼붿뷀뷈뷉뷋뷍뷔뷕뷘뷛뷜뷤뷥뷧뷩뷰뷱뷴뷷뷸븀븁븃븅브븍븐븓블븜븝븟븡븨븩븬븯븰븸븹븻븽비빅빈빋빌빔빕빗빙빠빡빤빧빨빰빱빳빵빼빽뺀뺃뺄뺌뺍뺏뺑뺘뺙뺜뺟뺠뺨뺩뺫뺭뺴뺵뺸뺻뺼뻄뻅뻇뻉뻐뻑뻔뻗뻘뻠뻡뻣뻥뻬뻭뻰뻳뻴뻼뻽뻿뼁뼈뼉뼌뼏뼐뼘뼙뼛뼝뼤뼥뼨뼫뼬뼴뼵뼷뼹뽀뽁뽄뽇뽈뽐뽑뽓뽕뽜뽝뽠뽣뽤뽬뽭뽯뽱뽸뽹뽼뽿뾀뾈뾉뾋뾍뾔뾕뾘뾛뾜뾤뾥뾧뾩뾰뾱뾴뾷뾸뿀뿁뿃뿅뿌뿍뿐뿓뿔뿜뿝뿟뿡뿨뿩뿬뿯뿰뿸뿹뿻뿽쀄쀅쀈쀋쀌쀔쀕쀗쀙쀠쀡쀤쀧쀨쀰쀱쀳쀵쀼쀽쁀쁃쁄쁌쁍쁏쁑쁘쁙쁜쁟쁠쁨쁩쁫쁭쁴쁵쁸쁻쁼삄삅삇삉삐삑삔삗삘삠삡삣삥사삭산삳살삼삽삿상새색샌샏샐샘샙샛생샤샥샨샫샬샴샵샷샹섀섁섄섇섈섐섑섓섕서석선섣설섬섭섯성세섹센섿셀셈셉셋셍셔셕션셛셜셤셥셧셩셰셱셴셷셸솀솁솃솅소속손솓솔솜솝솟송솨솩솬솯솰솸솹솻솽쇄쇅쇈쇋쇌쇔쇕쇗쇙쇠쇡쇤쇧쇨쇰쇱쇳쇵쇼쇽숀숃숄숌숍숏숑수숙순숟술숨숩숫숭숴숵숸숻숼쉄쉅쉇쉉쉐쉑쉔쉗쉘쉠쉡쉣쉥쉬쉭쉰쉳쉴쉼쉽쉿슁슈슉슌슏슐슘슙슛슝스슥슨슫슬슴습슷승싀싁싄싇싈싐싑싓싕시식신싣실심십싯싱싸싹싼싿쌀쌈쌉쌋쌍쌔쌕쌘쌛쌜쌤쌥쌧쌩쌰쌱쌴쌷쌸썀썁썃썅썌썍썐썓썔썜썝썟썡써썩썬썯썰썸썹썻썽쎄쎅쎈쎋쎌쎔쎕쎗쎙쎠쎡쎤쎧쎨쎰쎱쎳쎵쎼쎽쏀쏃쏄쏌쏍쏏쏑쏘쏙쏜쏟쏠쏨쏩쏫쏭쏴쏵쏸쏻쏼쐄쐅쐇쐉쐐쐑쐔쐗쐘쐠쐡쐣쐥쐬쐭쐰쐳쐴쐼쐽쐿쑁쑈쑉쑌쑏쑐쑘쑙쑛쑝쑤쑥쑨쑫쑬쑴쑵쑷쑹쒀쒁쒄쒇쒈쒐쒑쒓쒕쒜쒝쒠쒣쒤쒬쒭쒯쒱쒸쒹쒼쒿쓀쓈쓉쓋쓍쓔쓕쓘쓛쓜쓤쓥쓧쓩쓰쓱쓴쓷쓸씀씁씃씅씌씍씐씓씔씜씝씟씡씨씩씬씯씰씸씹씻씽아악안앋알암압앗앙애액앤앧앨앰앱앳앵야약얀얃얄얌얍얏양얘얙얜얟얠얨얩얫얭어억언얻얼엄업엇엉에엑엔엗엘엠엡엣엥여역연엳열염엽엿영예옉옌옏옐옘옙옛옝오옥온옫올옴옵옷옹와왁완왇왈왐왑왓왕왜왝왠왣왤왬왭왯왱외왹왼왿욀욈욉욋욍요욕욘욛욜욤욥욧용우욱운욷울움웁웃웅워웍원웓월웜웝웟웡웨웩웬웯웰웸웹웻웽위윅윈윋윌윔윕윗윙유육윤윧율윰윱윳융으윽은읃을음읍읏응의읙읜읟읠읨읩읫읭이익인읻일임입잇잉자작잔잗잘잠잡잣장재잭잰잳잴잼잽잿쟁쟈쟉쟌쟏쟐쟘쟙쟛쟝쟤쟥쟨쟫쟬쟴쟵쟷쟹저적전젇절점접젓정제젝젠젣젤젬젭젯젱져젹젼젿졀졈졉졋졍졔졕졘졛졜졤졥졧졩조족존졷졸좀좁좃종좌좍좐좓좔좜좝좟좡좨좩좬좯좰좸좹좻좽죄죅죈죋죌죔죕죗죙죠죡죤죧죨죰죱죳죵주죽준줃줄줌줍줏중줘줙줜줟줠줨줩줫줭줴줵줸줻줼쥄쥅쥇쥉쥐쥑쥔쥗쥘쥠쥡쥣쥥쥬쥭쥰쥳쥴쥼쥽쥿즁즈즉즌즏즐즘즙즛증즤즥즨즫즬즴즵즷즹지직진짇질짐집짓징짜짝짠짣짤짬짭짯짱째짹짼짿쨀쨈쨉쨋쨍쨔쨕쨘쨛쨜쨤쨥쨧쨩쨰쨱쨴쨷쨸쩀쩁쩃쩅쩌쩍쩐쩓쩔쩜쩝쩟쩡쩨쩩쩬쩯쩰쩸쩹쩻쩽쪄쪅쪈쪋쪌쪔쪕쪗쪙쪠쪡쪤쪧쪨쪰쪱쪳쪵쪼쪽쫀쫃쫄쫌쫍쫏쫑쫘쫙쫜쫟쫠쫨쫩쫫쫭쫴쫵쫸쫻쫼쬄쬅쬇쬉쬐쬑쬔쬗쬘쬠쬡쬣쬥쬬쬭쬰쬳쬴쬼쬽쬿쭁쭈쭉쭌쭏쭐쭘쭙쭛쭝쭤쭥쭨쭫쭬쭴쭵쭷쭹쮀쮁쮄쮇쮈쮐쮑쮓쮕쮜쮝쮠쮣쮤쮬쮭쮯쮱쮸쮹쮼쮿쯀쯈쯉쯋쯍쯔쯕쯘쯛쯜쯤쯥쯧쯩쯰쯱쯴쯷쯸찀찁찃찅찌찍찐찓찔찜찝찟찡차착찬찯찰참찹찻창채책챈챋챌챔챕챗챙챠챡챤챧챨챰챱챳챵챼챽첀첃첄첌첍첏첑처척천첟철첨첩첫청체첵첸첻첼쳄쳅쳇쳉쳐쳑쳔쳗쳘쳠쳡쳣쳥쳬쳭쳰쳳쳴쳼쳽쳿촁초촉촌촏촐촘촙촛총촤촥촨촫촬촴촵촷촹쵀쵁쵄쵇쵈쵐쵑쵓쵕최쵝쵠쵣쵤쵬쵭쵯쵱쵸쵹쵼쵿춀춈춉춋춍추축춘춛출춤춥춧충춰춱춴춷춸췀췁췃췅췌췍췐췓췔췜췝췟췡취췩췬췯췰췸췹췻췽츄츅츈츋츌츔츕츗츙츠측츤츧츨츰츱츳층츼츽칀칃칄칌칍칏칑치칙친칟칠침칩칫칭카칵칸칻칼캄캅캇캉캐캑캔캗캘캠캡캣캥캬캭캰캳캴캼캽캿컁컈컉컌컏컐컘컙컛컝커컥컨컫컬컴컵컷컹케켁켄켇켈켐켑켓켕켜켝켠켣켤켬켭켯켱켸켹켼켿콀콈콉콋콍코콕콘콛콜콤콥콧콩콰콱콴콷콸쾀쾁쾃쾅쾌쾍쾐쾓쾔쾜쾝쾟쾡쾨쾩쾬쾯쾰쾸쾹쾻쾽쿄쿅쿈쿋쿌쿔쿕쿗쿙쿠쿡쿤쿧쿨쿰쿱쿳쿵쿼쿽퀀퀃퀄퀌퀍퀏퀑퀘퀙퀜퀟퀠퀨퀩퀫퀭퀴퀵퀸퀻퀼큄큅큇큉큐큑큔큗큘큠큡큣큥크큭큰큳클큼큽큿킁킈킉킌킏킐킘킙킛킝키킥킨킫킬킴킵킷킹타탁탄탇탈탐탑탓탕태택탠탣탤탬탭탯탱탸탹탼탿턀턈턉턋턍턔턕턘턛턜턤턥턧턩터턱턴턷털텀텁텃텅테텍텐텓텔템텝텟텡텨텩텬텯텰텸텹텻텽톄톅톈톋톌톔톕톗톙토톡톤톧톨톰톱톳통톼톽퇀퇃퇄퇌퇍퇏퇑퇘퇙퇜퇟퇠퇨퇩퇫퇭퇴퇵퇸퇻퇼툄툅툇툉툐툑툔툗툘툠툡툣툥투툭툰툳툴툼툽툿퉁퉈퉉퉌퉏퉐퉘퉙퉛퉝퉤퉥퉨퉫퉬퉴퉵퉷퉹튀튁튄튇튈튐튑튓튕튜튝튠튣튤튬튭튯튱트특튼튿틀틈틉틋틍틔틕틘틛틜틤틥틧틩티틱틴틷틸팀팁팃팅파팍판팓팔팜팝팟팡패팩팬팯팰팸팹팻팽퍄퍅퍈퍋퍌퍔퍕퍗퍙퍠퍡퍤퍧퍨퍰퍱퍳퍵퍼퍽펀펃펄펌펍펏펑페펙펜펟펠펨펩펫펭펴펵편펻펼폄폅폇평폐폑폔폗폘폠폡폣폥포폭폰폳폴폼폽폿퐁퐈퐉퐌퐏퐐퐘퐙퐛퐝퐤퐥퐨퐫퐬퐴퐵퐷퐹푀푁푄푇푈푐푑푓푕표푝푠푣푤푬푭푯푱푸푹푼푿풀품풉풋풍풔풕풘풛풜풤풥풧풩풰풱풴풷풸퓀퓁퓃퓅퓌퓍퓐퓓퓔퓜퓝퓟퓡퓨퓩퓬퓯퓰퓸퓹퓻퓽프픅픈픋플픔픕픗픙픠픡픤픧픨픰픱픳픵피픽핀핃필핌핍핏핑하학한핟할함합핫항해핵핸핻핼햄햅햇행햐햑햔햗햘햠햡햣향햬햭햰햳햴햼햽햿헁허헉헌헏헐험헙헛헝헤헥헨헫헬헴헵헷헹혀혁현혇혈혐협혓형혜혝혠혣혤혬혭혯혱호혹혼혿홀홈홉홋홍화확환홛활홤홥홧황홰홱홴홷홸횀횁횃횅회획횐횓횔횜횝횟횡효횩횬횯횰횸횹횻횽후훅훈훋훌훔훕훗훙훠훡훤훧훨훰훱훳훵훼훽휀휃휄휌휍휏휑휘휙휜휟휠휨휩휫휭휴휵휸휻휼흄흅흇흉흐흑흔흗흘흠흡흣흥희흭흰흳흴흼흽흿힁히힉힌힏힐힘힙힛힝"
+
+vault write transform/template/kr-name-tmpl \
+     type=regex \
+     pattern='([가-힣]{2,4})' \
+     alphabet=hangul
+    
+vault write transform/transformations/fpe/kr-name \
+template="kr-name-tmpl" \
+tweak_source=internal \
+allowed_roles="*"
+
+vault write transform/role/customer transformations=kr-name
+
+vault write transform/encode/customer value="한볼트" \
+    transformation=kr-name
+    
+Key              Value
+---              -----
+encoded_value    붏숁삂
+    
+vault write transform/decode/customer value="붏숁삂" \
+    transformation=kr-name
+    
+Key              Value
+---              -----
+decoded_value    한볼트
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/transit-import.html b/04-HashiCorp/06-Vault/02-Secret_Engine/transit-import.html new file mode 100644 index 0000000000..38a0d2ed2e --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/transit-import.html @@ -0,0 +1,309 @@ + + + + + + + + + + Transit (Import) | docmoa + + + + + +
본문으로 건너뛰기

Transit (Import)

약 5 분vaulttransit

Transit (Import)

키 가져오기(Import) 기능은 HSM, 사용자 정의 키, 기타 외부 시스템에서 기존 키를 가져와야 하는 경우를 지원한다. 공개키(Public Key)만을 가져올 수도 있다.

1. CLI

transit importtransit import-versionBYOK 메커니즘open in new window을 통해 사용자가 생성한 키를 Transit 엔진에서 사용할 키로 가져온다.

  • transit import : 키를 새 키로 가져오며 이미 존재하는 경우 실패
  • transit import-version : 기존 키를 새 버전의 키로 업데이트

2. OpenSSL을 사용하여 암복호화

openssl을 이용하여 RSA 키를 생성해보고, 데이터를 암호화 및 복호화

2.1 RSA 키 생성

먼저 openssl을 이용하여 RSA 키쌍을 생성한다. 이 예에서는 2048비트 키를 생성한다.

# Private 키
+openssl genrsa -out private_key.pem 2048
+# Public 키
+openssl rsa -pubout -in private_key.pem -out public_key.pem
+

private_key.pem에 개인 키가 저장되고 public_key.pem에 공개 키가 저장된다.

생성된 PEM 예제

private_key.pem
-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxAqZGiw7/Et52
+TRHgLFVYi3HbnMB5m/ZBMu/CZxk6H5zFrCIXcBFh+K58P/rMydFh2rveTd6CT1+s
+zrphe1MPS9mRjvvgy3Bk1XEEUBYOlmIk/eD3lLJEoTjY1E5bpuxirTgV7rR47XEy
+ZsdT6g8Z6s6/M4bjCnJ4ZuBu1VZ3e/5pHrYFSEudy7Xag1Uby1T0Txr/OPPPjVYO
+A9jgGAO+0wr4elPHyYua1IDnQGfOVuGMKKei+PybKdUvErtp6Z5PJxl7Ylj0Uq8V
+ym2iXyF7deryXj4vwDZg4UdbX6TcNVSCSM5rvKtAf3S7AsSwflG9WO5Kt1t7QGy2
+EWVLTrnNAgMBAAECggEASfZJdAB2663+tn/NkFX182GQ2arN4gKBCw01kY1yxQ6g
+exhJxnFVUhKPFevF5/wMOU8kYOc4qkpwN8zJpCHbuvB+oIuWQ3++HuPwrVSpYr8D
+k2FhtxGyy2pyTmentjQxYanvXXq4fi74tY6siyup07KBYPMu0X90BUs3TBhoYNQE
+KlcZXAR20Y+8NCsKa3QmX9yXUOmDUz5i0zWo7Ojwlig96GpJXq8au3NJcarzFZsw
+YFkGelNIMCDcH05ao8ujOoKecmusMEOGoue1DOzduFAvFRGoo7Cx1C+O2ORR0uwC
+jyO2H7qrIckoBlnjSzJ5GOY2UNyLAGs5cNEy6Na9IQKBgQDmoSCfx/DSC00lFY7H
+Z0fsJrQWzhFnj5hyP3bti4GdtCtYxf1jgM+ZPt8SNU1fqWl2+JbDMXM0C65z39bA
+YcHYeWYHVXGMVU/6vDyeD3l7ohuNi7GwvnejZmN0QWoIpKQ9OeVZWQ243bKg7UTR
+SDWrOj30RJoS6CzfoKUIM+yCLwKBgQDEe4dJ452/0RsLFhuICQmP9GSUCHZCnSBv
+RZvWlw6IJ0qL+Ww+fyNKld0BdUFKZsHVamxcEr/e0MMjFYyoq4JGkBJbUZjcg6QI
+bSn4ENKNWEnEfCGeEf2o2IZSdiTGtC8kV1zAgIoy9he/imosMufovjHLXV2QsuDA
+plaQwD1wwwKBgQCf0nkxQPV6GarUUCQpu0D0Pb3/L76P7crPIXvhEhQ4nWqMkmgO
+VG2I3TDpBVchO92CPLL9gX88SfwTAMNpflU/FqHF40hU36oVL+0x+7dMHgLKDEyP
+Fu8BpSq2nb5FTxMh+sUdLcF8ouXu734JKelHR403gXLkN1Ehh8nV7WWwsQKBgEfz
+9NdaQ6q7KOwmbG6k4JuXJD4R2z0JzZbyJt+u8eNqgCJCdSFt7b6iowylpANbHiDJ
+mGUfeKRgTxXKDni2Vj8BA7ftac1XZ/qt/3CYuIKKknkh/C2m6P2sTYRlP5KE6b6l
+P5I/gFypQokiZz9IZSUWgaW3y0vyNdxXDdx0iguBAoGBAOR4r7I8WcOM9i/uTSJm
+oAlI9FqxCmQg6Yly6alMF5jjC2/F2/7byYB1FcZ0EnTomYb4dEePCMron3pBpFIk
+gx2rcjODvK/hU2Uodpy1XF47mx7dGzuTZYLPijRzl3R/5nW05xnDLBRRAQXaBKLj
+CYIKBHwiRAEvHioLiDIostz1
+-----END PRIVATE KEY-----
+

2.2 RSA 키로 암호화

공개 키(public_key.pem)를 사용하여 "This is my data"라는 문자열을 암호화한다.

echo "This is my data" | openssl rsautl -encrypt -pubin -inkey public_key.pem -out encrypted_data.bin
+
암호화된 데이터

encrypted_data.bin

�W�U�F�B��	�����F	��u8-�U>���j��;"`Z�+�A��)�6u����9��H�W��t)h��,�m��
+
+                                                                          *h��(UL;ZC�l�K��8���*��Y�k?��`�?�
+              �%eܓ�O^
+                     K�]���'8�QI�H��2�d���2�Nv$��)F���z���Ձd�B��"�na���x��v/�J-�^�
+  ��ΕJ���̳*
+          
+

2.3 RSA 키로 복호화

암호화된 데이터를 복호화하려면 개인 키(private_key.pem)를 사용해야 한다.

openssl rsautl -decrypt -inkey private_key.pem -in encrypted_data.bin
+
복호화 실행 결과
$ openssl rsautl -decrypt -inkey private_key.pem -in encrypted_data.bin
+
+This is my data
+

3. Vault Transit로 키 가져오기

Import 주의 사항

  • 대칭 키(예: AES 또는 ChaCha20 키)를 래핑할 때는 키의 원시 바이트를 래핑해야 한다. 예를 들어 AES 128비트 키의 경우 16자 길이의 바이트 배열이 되는데, 이 바이트 배열은 Base64나 다른 인코딩 없이 바로 래핑된다.

  • 비대칭 키(예: RSA 또는 ECDSA 키)를 래핑할 때는 이 키의 PKCS8 인코딩된 형식을 원시 DER/바이너리 형식으로 래핑해야 한다.

2. OpenSSL을 사용하여 암복호화에서 생성한 개인키를 사용하여 진행한다.

3.1 PEM 형식의 키를 DER로 변환

OpenSSL로 생성된 비대칭 키는 DER 형식으로 변경이 필요하다.

openssl pkcs8 -topk8 -nocrypt -inform PEM -outform DER -in private_key.pem -out private_key.der
+
DER 내용
$ cat private_key.der
+
+��0�����F���vM�,UX�qۜ�y��A2��g:�Ŭ"pa�|?����aڻ�MނO_�κa{SKّ����pd�qP�b$���D�8��N[��b�8�x�q2f�S��ο3��
+rxf�n�Vw{�i�HK�˵ڃUT�O�8�ύV����
+�zS�ɋ�Ԁ�@g�V�(����)�/�i�O'{bX�R��m�_!{u��^>/�6`�G[_��5T�H�k��@t�İ~Q�X�J�[{@l�eKN���I�Itv���͐U��a�٪���
+5��r��{I�qUR�����
+                 9O$`�8�Jp7�ɤ!ۺ�~���C���T�b��aa���jrNg��41a��]z�~.�����+�Ӳ�`�.�tK7Lh`�*W\vя�4+
+kt&_ܗP�S>b�5����(=�jI^��sIq���0`YzSH0 �NZ�ˣ:��rk�0C���
+                                                      �ݸP/�����/���Q���#���!�(Y�K2y�6P܋k9p�2�ֽ!��� ����
+                      M%��gG�&��g��r?v틁��+X��c�ϙ>�5M_�iv��1s4
+                                                              �s���a��yfUq�UO�<��ݲ��D�H5�:=�D��,ߠ3�/���{�I㝿�		��dvB� oE�֗�'J��l>#J��uAJf��jl\����#����F�[Q�܃m)�ҍXI�|!��؆Rv$ƴ/$W\���2���j,2��1�]]�����V��=p�����y1@�z��P$)�@�=��/�����!{�8�j��hTm��0�W!;݂<��<I��i~U?���HTߪ/�1�L�
+                                         L���*���EO!��-�|����~	)�GG�7�r�7Q!����e����G���ZC��(&ln����$>=     	͖�&߮��j�"Bu!m����
+                                               ��[ ɘex�`O�x�V?��i�Wg��p�����y!�-�t�M�e?��龥?�?�\�B�"g?He%����K�5�W
+   ����x��<Y�/�M"f�	H�Z�
+d �r�L��
+        o����ɀu�tt虆�tG��zA�R$��r3����Se(v��\^;���e�ϊ4s�t�u���,Q���	�
+|"D/*
+     �2(���%
+

그런 다음 가져오기 도구와 함께 사용하려면 키를 줄 바꿈 없이 base64로 인코딩해야 한다.

BASE64_KEY=$(base64 -i private_key.der)
+
BASE64 인코딩 된 DER 내용
$ echo $BASE64_KEY
+
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxAqZGiw7/Et52TRHgLFVYi3HbnMB5m/ZBMu/CZxk6H5zFrCIXcBFh+K58P/rMydFh2rveTd6CT1+szrphe1MPS9mRjvvgy3Bk1XEEUBYOlmIk/eD3lLJEoTjY1E5bpuxirTgV7rR47XEyZsdT6g8Z6s6/M4bjCnJ4ZuBu1VZ3e/5pHrYFSEudy7Xag1Uby1T0Txr/OPPPjVYOA9jgGAO+0wr4elPHyYua1IDnQGfOVuGMKKei+PybKdUvErtp6Z5PJxl7Ylj0Uq8Vym2iXyF7deryXj4vwDZg4UdbX6TcNVSCSM5rvKtAf3S7AsSwflG9WO5Kt1t7QGy2EWVLTrnNAgMBAAECggEASfZJdAB2663+tn/NkFX182GQ2arN4gKBCw01kY1yxQ6gexhJxnFVUhKPFevF5/wMOU8kYOc4qkpwN8zJpCHbuvB+oIuWQ3++HuPwrVSpYr8Dk2FhtxGyy2pyTmentjQxYanvXXq4fi74tY6siyup07KBYPMu0X90BUs3TBhoYNQEKlcZXAR20Y+8NCsKa3QmX9yXUOmDUz5i0zWo7Ojwlig96GpJXq8au3NJcarzFZswYFkGelNIMCDcH05ao8ujOoKecmusMEOGoue1DOzduFAvFRGoo7Cx1C+O2ORR0uwCjyO2H7qrIckoBlnjSzJ5GOY2UNyLAGs5cNEy6Na9IQKBgQDmoSCfx/DSC00lFY7HZ0fsJrQWzhFnj5hyP3bti4GdtCtYxf1jgM+ZPt8SNU1fqWl2+JbDMXM0C65z39bAYcHYeWYHVXGMVU/6vDyeD3l7ohuNi7GwvnejZmN0QWoIpKQ9OeVZWQ243bKg7UTRSDWrOj30RJoS6CzfoKUIM+yCLwKBgQDEe4dJ452/0RsLFhuICQmP9GSUCHZCnSBvRZvWlw6IJ0qL+Ww+fyNKld0BdUFKZsHVamxcEr/e0MMjFYyoq4JGkBJbUZjcg6QIbSn4ENKNWEnEfCGeEf2o2IZSdiTGtC8kV1zAgIoy9he/imosMufovjHLXV2QsuDAplaQwD1wwwKBgQCf0nkxQPV6GarUUCQpu0D0Pb3/L76P7crPIXvhEhQ4nWqMkmgOVG2I3TDpBVchO92CPLL9gX88SfwTAMNpflU/FqHF40hU36oVL+0x+7dMHgLKDEyPFu8BpSq2nb5FTxMh+sUdLcF8ouXu734JKelHR403gXLkN1Ehh8nV7WWwsQKBgEfz9NdaQ6q7KOwmbG6k4JuXJD4R2z0JzZbyJt+u8eNqgCJCdSFt7b6iowylpANbHiDJmGUfeKRgTxXKDni2Vj8BA7ftac1XZ/qt/3CYuIKKknkh/C2m6P2sTYRlP5KE6b6lP5I/gFypQokiZz9IZSUWgaW3y0vyNdxXDdx0iguBAoGBAOR4r7I8WcOM9i/uTSJmoAlI9FqxCmQg6Yly6alMF5jjC2/F2/7byYB1FcZ0EnTomYb4dEePCMron3pBpFIkgx2rcjODvK/hU2Uodpy1XF47mx7dGzuTZYLPijRzl3R/5nW05xnDLBRRAQXaBKLjCYIKBHwiRAEvHioLiDIostz1
+

3.2 Run Vault dev mode & Transit Enable (Option)

다음 명령어로 Vault를 개발 모드로 실행한다.

vault server -dev -dev-root-token-id=root
+

다른 명령창에서 아래 환경변수를 입력한다.

export VAULT_ADDR=http://127.0.0.1:8200
+export VAULT_TOKEN=root
+

환경변수가 적용된 명령창에서 transit을 활성화 한다.

vault secrets enable transit
+

3.3 Transit Import

적절한 키 유형으로 기존 키를 가져온다.

vault transit import transit/keys/my-key $BASE64_KEY type=rsa-2048
+
키 가져오기 결과 출력 예제
$ vault transit import transit/keys/my-key $BASE64_KEY type=rsa-2048
+
+Retrieving wrapping key.
+Wrapping source key with ephemeral key.
+Encrypting ephemeral key with wrapping key.
+Submitting wrapped key.
+Success!
+

적용된 키 정보는 기존 transit의 키 정보를 확인하는 방식과 같다. 키를 가져오면 개인키를 기반으로 공개키가 자동 생성된다.

$ vault read transit/keys/my-key
+
+Key                            Value
+---                            -----
+allow_plaintext_backup         false
+auto_rotate_period             0s
+deletion_allowed               false
+derived                        false
+exportable                     false
+imported_key                   true
+imported_key_allow_rotation    false
+keys                           map[1:map[creation_time:2023-09-21T17:21:37.857532+09:00 name:rsa-2048 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQKmRosO/xLedk0R4CxV
+WItx25zAeZv2QTLvwmcZOh+cxawiF3ARYfiufD/6zMnRYdq73k3egk9frM66YXtT
+D0vZkY774MtwZNVxBFAWDpZiJP3g95SyRKE42NROW6bsYq04Fe60eO1xMmbHU+oP
+GerOvzOG4wpyeGbgbtVWd3v+aR62BUhLncu12oNVG8tU9E8a/zjzz41WDgPY4BgD
+vtMK+HpTx8mLmtSA50BnzlbhjCinovj8mynVLxK7aemeTycZe2JY9FKvFcptol8h
+e3Xq8l4+L8A2YOFHW1+k3DVUgkjOa7yrQH90uwLEsH5RvVjuSrdbe0BsthFlS065
+zQIDAQAB
+-----END PUBLIC KEY-----
+]]
+latest_version                 1
+min_available_version          0
+min_decryption_version         1
+min_encryption_version         0
+name                           my-key
+supports_decryption            true
+supports_derivation            false
+supports_encryption            true
+supports_signing               true
+type                           rsa-2048
+

3.4 Transit으로 암호화/복호화

기존 transit의 암호화/복호화 방식과 동일하게 사용한다.

$ vault write transit/encrypt/my-key plaintext=$(echo "This is my data" | base64)
+
+Key            Value
+---            -----
+ciphertext     vault:v1:Kbiudy2+vK+IRIWnMKPUOwRXPn1eh3KfvvU+59YSPJgidndodgno+7naujQvxpe8T4+ThI01pqw2SeAB6KST8Uh/WVfM91vJ5kWV2NAXJXy+gqe0K3WxzhMQT2DTkxa2mkcUj4WM9blwFW46P9z5SYuphj7ripfiPu1mclolFFD2CUU0WgdW5IzLugWWOOeUlBTh8zQMpdVVVC9xXH8WtPFErXZu1zbo1quDkoR+lLCoyt0ONfcUB24R9oVvP2RjY63Taeu5Phi8DmHDAkAa4T1xB8DbH0wGKBZoK3s2e+GFTfH5XWlxiY832Ds10IuvtbW/TZhkd2Vq1r1bYj3q9w==
+key_version    1
+
+$ echo $(vault write -field=plaintext transit/decrypt/my-key ciphertext=vault:v1:Kbiudy2+vK+IRIWnMKPUOwRXPn1eh3KfvvU+59YSPJgidndodgno+7naujQvxpe8T4+ThI01pqw2SeAB6KST8Uh/WVfM91vJ5kWV2NAXJXy+gqe0K3WxzhMQT2DTkxa2mkcUj4WM9blwFW46P9z5SYuphj7ripfiPu1mclolFFD2CUU0WgdW5IzLugWWOOeUlBTh8zQMpdVVVC9xXH8WtPFErXZu1zbo1quDkoR+lLCoyt0ONfcUB24R9oVvP2RjY63Taeu5Phi8DmHDAkAa4T1xB8DbH0wGKBZoK3s2e+GFTfH5XWlxiY832Ds10IuvtbW/TZhkd2Vq1r1bYj3q9w==) | base64 -d
+
+This is my data
+

4. 키 버전 추가

이전과 동일한 키를 transit_import로 추가

$ vault transit import-version transit/keys/my-key $BASE64_KEY type=rsa-2048
+
+Retrieving wrapping key.
+Wrapping source key with ephemeral key.
+Encrypting ephemeral key with wrapping key.
+Submitting wrapped key.
+Success!
+

신규 추가된 키 확인

$ vault read transit/keys/my-key
+Key                            Value
+---                            -----
+allow_plaintext_backup         false
+auto_rotate_period             0s
+deletion_allowed               false
+derived                        false
+exportable                     false
+imported_key                   true
+imported_key_allow_rotation    false
+keys                           map[1:map[creation_time:2023-09-21T17:21:37.857532+09:00 name:rsa-2048 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQKmRosO/xLedk0R4CxV
+WItx25zAeZv2QTLvwmcZOh+cxawiF3ARYfiufD/6zMnRYdq73k3egk9frM66YXtT
+D0vZkY774MtwZNVxBFAWDpZiJP3g95SyRKE42NROW6bsYq04Fe60eO1xMmbHU+oP
+GerOvzOG4wpyeGbgbtVWd3v+aR62BUhLncu12oNVG8tU9E8a/zjzz41WDgPY4BgD
+vtMK+HpTx8mLmtSA50BnzlbhjCinovj8mynVLxK7aemeTycZe2JY9FKvFcptol8h
+e3Xq8l4+L8A2YOFHW1+k3DVUgkjOa7yrQH90uwLEsH5RvVjuSrdbe0BsthFlS065
+zQIDAQAB
+-----END PUBLIC KEY-----
+] 2:map[creation_time:2023-09-21T17:41:44.047857+09:00 name:rsa-2048 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7H/FBtt07km/w4z/qAK
+sJZumWinnmn5V/5f8TMfQcOjiyF2J4RTTIjUBEGsXF+/bg2w88f39f+r7Ws4wFHa
+91UCgc9MpyQOil42UYN+Rm+kc6hWN26+ZmxqEU/HL1iLPwtu/HGU38MCeS5552M6
+VY7BB7vIhheFyEy8+GDwrjZ3bo+f6Vaya6hyMZ7psS7N5OVaN3z7PsN57lzYaxZ6
+0vVFJeUeeUq371nl7f0cN3eC8PTI8XgQW7Yy8B4lWKHzjpbA2w1hivh6cuXgE2+c
+5MUqkxEKmE8BOMsgm7C+DQ9umQ4q1DkHIWub4oLUg4Tr/VgzECUj+D9tcGLZXFHd
+dwIDAQAB
+-----END PUBLIC KEY-----
+]]
+latest_version                 2
+min_available_version          0
+min_decryption_version         1
+min_encryption_version         0
+name                           my-key
+supports_decryption            true
+supports_derivation            false
+supports_encryption            true
+supports_signing               true
+type                           rsa-2048
+

주의 사항

Vault의 기본 Transit에서 제공하는 키의 순환(rotate)을 사용하지 않고 사용자가 임의로 새로운 키 버전을 추가하기 때문에 동일한 키가 추가될 수 있다. 이 경우, 동일한 개인 키를 Transit으로 가져오면 신규 버전으로 생성된 키(또는 이전 버전의 키)의 암호화 키 또한 동일한 공개키가 생성되므로, 키 버전과 관계 없이 복호화 된다.

동일한 개인키를 신규 버전 추가하여 기존 공개키와 신규 공개키가 같음을 확인하여 재현할 수 있다.

$ vault read transit/keys/my-key
+Key                            Value
+---                            -----
+allow_plaintext_backup         false
+auto_rotate_period             0s
+deletion_allowed               false
+derived                        false
+exportable                     false
+imported_key                   true
+imported_key_allow_rotation    false
+keys                           map[1:map[creation_time:2023-09-21T17:21:37.857532+09:00 name:rsa-2048 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQKmRosO/xLedk0R4CxV
+WItx25zAeZv2QTLvwmcZOh+cxawiF3ARYfiufD/6zMnRYdq73k3egk9frM66YXtT
+D0vZkY774MtwZNVxBFAWDpZiJP3g95SyRKE42NROW6bsYq04Fe60eO1xMmbHU+oP
+GerOvzOG4wpyeGbgbtVWd3v+aR62BUhLncu12oNVG8tU9E8a/zjzz41WDgPY4BgD
+vtMK+HpTx8mLmtSA50BnzlbhjCinovj8mynVLxK7aemeTycZe2JY9FKvFcptol8h
+e3Xq8l4+L8A2YOFHW1+k3DVUgkjOa7yrQH90uwLEsH5RvVjuSrdbe0BsthFlS065
+zQIDAQAB
+-----END PUBLIC KEY-----
+] 2:map[creation_time:2023-09-21T17:41:44.047857+09:00 name:rsa-2048 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQKmRosO/xLedk0R4CxV
+WItx25zAeZv2QTLvwmcZOh+cxawiF3ARYfiufD/6zMnRYdq73k3egk9frM66YXtT
+D0vZkY774MtwZNVxBFAWDpZiJP3g95SyRKE42NROW6bsYq04Fe60eO1xMmbHU+oP
+GerOvzOG4wpyeGbgbtVWd3v+aR62BUhLncu12oNVG8tU9E8a/zjzz41WDgPY4BgD
+vtMK+HpTx8mLmtSA50BnzlbhjCinovj8mynVLxK7aemeTycZe2JY9FKvFcptol8h
+e3Xq8l4+L8A2YOFHW1+k3DVUgkjOa7yrQH90uwLEsH5RvVjuSrdbe0BsthFlS065
+zQIDAQAB
+-----END PUBLIC KEY-----
+]]
+latest_version                 2
+min_available_version          0
+min_decryption_version         1
+min_encryption_version         0
+name                           my-key
+supports_decryption            true
+supports_derivation            false
+supports_encryption            true
+supports_signing               true
+type                           rsa-2048
+











 
 
 
 
 
 
 


 
 
 
 
 
 
 












동일한 개인키가 Transit 키로 가져오기되고, 따라서 같은 공개키가 생성된다. 앞서 복호화 했던 기존 버전(vault:v1:)의 ciphertext의 버전을 신규 버전(vault:v2:)으로 변경하여 복호화 요청을 하면 복호화 된다.

echo $(vault write -field=plaintext transit/decrypt/my-key ciphertext=vault:v2:Kbiudy2+vK+IRIWnMKPUOwRXPn1eh3KfvvU+59YSPJgidndodgno+7naujQvxpe8T4+ThI01pqw2SeAB6KST8Uh/WVfM91vJ5kWV2NAXJXy+gqe0K3WxzhMQT2DTkxa2mkcUj4WM9blwFW46P9z5SYuphj7ripfiPu1mclolFFD2CUU0WgdW5IzLugWWOOeUlBTh8zQMpdVVVC9xXH8WtPFErXZu1zbo1quDkoR+lLCoyt0ONfcUB24R9oVvP2RjY63Taeu5Phi8DmHDAkAa4T1xB8DbH0wGKBZoK3s2e+GFTfH5XWlxiY832Ds10IuvtbW/TZhkd2Vq1r1bYj3q9w==) | base64 -d
+
+This is my data
+

5. 주의 사항

Rotate 불가

가져온 키는 rotate 기능을 사용할 수 없다.

$ vault write -f transit/keys/my-key/rotate
+
+Error writing data to transit/keys/my-key/rotate: Error making API request.
+
+URL: PUT http://127.0.0.1:8200/v1/transit/keys/my-key/rotate
+Code: 500. Errors:
+
+* 1 error occurred:
+	* imported key my-key does not allow rotation within Vault
+

최소 버전 설정

추가된 키 버전에 대해 최소 버전을 지정하는 것은 가능하다.

$ vault write -f transit/keys/my-key/config min_decryption_version=2
+
+Key                            Value
+---                            -----
+allow_plaintext_backup         false
+auto_rotate_period             0s
+deletion_allowed               false
+derived                        false
+exportable                     false
+imported_key                   true
+imported_key_allow_rotation    false
+keys                           map[2:map[creation_time:2023-09-21T17:41:44.047857+09:00 name:rsa-2048 public_key:-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsQKmRosO/xLedk0R4CxV
+WItx25zAeZv2QTLvwmcZOh+cxawiF3ARYfiufD/6zMnRYdq73k3egk9frM66YXtT
+D0vZkY774MtwZNVxBFAWDpZiJP3g95SyRKE42NROW6bsYq04Fe60eO1xMmbHU+oP
+GerOvzOG4wpyeGbgbtVWd3v+aR62BUhLncu12oNVG8tU9E8a/zjzz41WDgPY4BgD
+vtMK+HpTx8mLmtSA50BnzlbhjCinovj8mynVLxK7aemeTycZe2JY9FKvFcptol8h
+e3Xq8l4+L8A2YOFHW1+k3DVUgkjOa7yrQH90uwLEsH5RvVjuSrdbe0BsthFlS065
+zQIDAQAB
+-----END PUBLIC KEY-----
+]]
+latest_version                 2
+min_available_version          0
+min_decryption_version         2
+min_encryption_version         0
+name                           my-key
+supports_decryption            true
+supports_derivation            false
+supports_encryption            true
+supports_signing               true
+type                           rsa-2048
+

min_decryption_version2 인 경우, 이보다 낮은 버전의 키 버전의 암호화 된 키는 복호화 할 수 없다.

$ vault write -field=plaintext transit/decrypt/my-key ciphertext=vault:v1:Kbiudy2+vK+IRIWnMKPUOwRXPn1eh3KfvvU+59YSPJgidndodgno+7naujQvxpe8T4+ThI01pqw2SeAB6KST8Uh/WVfM91vJ5kWV2NAXJXy+gqe0K3WxzhMQT2DTkxa2mkcUj4WM9blwFW46P9z5SYuphj7ripfiPu1mclolFFD2CUU0WgdW5IzLugWWOOeUlBTh8zQMpdVVVC9xXH8WtPFErXZu1zbo1quDkoR+lLCoyt0ONfcUB24R9oVvP2RjY63Taeu5Phi8DmHDAkAa4T1xB8DbH0wGKBZoK3s2e+GFTfH5XWlxiY832Ds10IuvtbW/TZhkd2Vq1r1bYj3q9w==
+Error writing data to transit/decrypt/my-key: Error making API request.
+
+URL: PUT http://127.0.0.1:8200/v1/transit/decrypt/my-key
+Code: 400. Errors:
+
+* ciphertext or signature version is disallowed by policy (too old)
+

Transit으로 암호화된 정보는 Vault에서만 복호화

Vault Transit의 암호화된 ciphertext는 가져오기 전의 개인 키로 복호화 할 수 없다.
(vault:v#:을 포함하거나 제외한 ciphertext의 base64 디코드된 데이터)

$  openssl rsautl -decrypt -inkey private_key.pem -in encrypted_data.bin
+
+RSA operation error
+80A095F101000000:error:0200009F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error:crypto/rsa/rsa_pk1.c:269:
+80A095F101000000:error:02000072:rsa routines:rsa_ossl_private_decrypt:padding check failed:crypto/rsa/rsa_ossl.c:499:
+
+ + + diff --git a/04-HashiCorp/06-Vault/02-Secret_Engine/transit.html b/04-HashiCorp/06-Vault/02-Secret_Engine/transit.html new file mode 100644 index 0000000000..f137e7b009 --- /dev/null +++ b/04-HashiCorp/06-Vault/02-Secret_Engine/transit.html @@ -0,0 +1,158 @@ + + + + + + + + + + Transit | docmoa + + + + + +
본문으로 건너뛰기

Transit

약 1 분vaulttransit

Transit

Vault구성 (Option)

시크릿 엔진 활성화

export VAULT_SKIP_VERIFY=True
+export VAULT_ADDR='http://172.28.128.21:8200'
+export VAULT_TOKEN=<mytoken>
+

정책 및 사용자 구성

transit-admin

$ vault policy write transit-admin - << EOF
+# Enable transit secrets engine
+path "sys/mounts/transit" {
+  capabilities = [ "create", "read", "update", "delete", "list" ]
+}
+
+# To read enabled secrets engines
+path "sys/mounts" {
+  capabilities = [ "read" ]
+}
+
+# Manage the transit secrets engine
+path "transit/*" {
+  capabilities = [ "create", "read", "update", "delete", "list" ]
+}
+EOF
+

transit-message

$ vault policy write transit-message -<<EOF
+path "transit/encrypt/message" {
+   capabilities = [ "update" ]
+}
+path "transit/decrypt/message" {
+   capabilities = [ "update" ]
+}
+EOF
+

user/pass 생성

$ vault auth enable userpass
+
+$ vault write auth/userpass/users/transit-admin \
+    password=transit-admin \
+    policies=transit-admin
+    
+$ vault write auth/userpass/users/transit-message \
+    password=transit-message \
+    policies=transit-message
+

transit-admin 로그인

$ vault login -method userpass username=transit-admin password=transit-admin
+Success! You are now authenticated. The token information displayed below
+is already stored in the token helper. You do NOT need to run "vault login"
+again. Future Vault requests will automatically use this token.
+
+Key                    Value
+---                    -----
+token                  s.ldJApybiqGBmq3CuBAaqsKXZ
+token_accessor         Maek0IMLkOLmFVkpG4DoGUdY
+token_duration         768h
+token_renewable        true
+token_policies         ["transit-admin"]
+identity_policies      []
+policies               ["transit-admin"]
+token_meta_username    transit-admin
+

Transit Secret Engine

Transit Secret Engine 활성화

$ vault secrets enable -path=transit transit
+

암호화 키링 생성

# vault write -f transit/keys/<key_name>
+$ vault write -f transit/keys/message
+

암호화 (Transit-message User)

$ VAULT_TOKEN=<client_token> vault write transit/encrypt/message \
+    plaintext=$(base64 <<< "4111 1111 1111 1111")
+    
+Key            Value
+---            -----
+ciphertext     vault:v1:IKfJjYkwv1NWAaw+7O8F0QKcWxu5J98/Wvf0d8yHeBX8AoRajI6BLmS7iniCvkyp
+key_version    1
+

해독 (Transit-message User)

$ VAULT_TOKEN=<client_token> vault write transit/decrypt/message \
+    ciphertext="vault:v1:cZNHVx+sxdMErXRSuDa1q/pz49fXTn1PScKfhf+PIZPvy8xKfkytpwKcbC0fF2U="
+
+Key          Value
+---          -----
+plaintext    Y3JlZGl0LWNhcmQtbnVtYmVyCg==
+
+$ base64 --decode <<< "Y3JlZGl0LWNhcmQtbnVtYmVyCg=="
+4111 1111 1111 1111
+

또는

$ vault write -field=plaintext transit/decrypt/message ciphertext="vault:v1:cZNHVx+sxdMErXRSuDa1q/pz49fXTn1PScKfhf+PIZPvy8xKfkytpwKcbC0fF2U=" | base64 --decode
+4111 1111 1111 1111
+

암호화 키링 순환

$ vault write -f transit/keys/message/rotate
+$ vault read transit/keys/message
+Key                       Value
+---                       -----
+allow_plaintext_backup    false
+deletion_allowed          false
+derived                   false
+exportable                false
+keys                      map[1:1617699577 2:1617705005]
+latest_version            2
+min_available_version     0
+min_decryption_version    1
+min_encryption_version    0
+name                      message
+supports_decryption       true
+supports_derivation       true
+supports_encryption       true
+supports_signing          false
+type                      aes256-gcm96
+

업데이트된 키링으로 암호화

$ vault write transit/encrypt/message \
+    plaintext=$(base64 <<< "4111 1111 1111 1111")
+Key            Value
+---            -----
+ciphertext     vault:v2:wdEpTdasoqY0I9SWfj0r93fDevsIl2cX2aAdfDqAPmvCMAf2w/2blU+k86MVscgW
+key_version    2
+

업데이트된 키링으로 재 암호화

$ vault write transit/rewrap/message \
+    ciphertext="vault:v1:+msBmr5zjE7ZOaA1h9/kV7ZWGZlZX+YEzgco70wTT+lvlfxUDLIgdFGFVOYN777X"
+
+Key           Value
+---           -----
+ciphertext    vault:v2:kChHZ9w4ILRfw+DzO53IZ8m5PyB2yp2/tKbub34uB+iDqtDRB+NLCPrpzTtJHJ4=
+

기존 키링 파기

$ vault write transit/keys/message/config min_decryption_version=2
+$ vault read transit/keys/message
+Key                       Value
+---                       -----
+...
+keys                      map[2:1617705005]
+...
+

기존 암호화 데이터 복호화 → 실패

$ vault write -field=plaintext transit/decrypt/message ciphertext="vault:v1:KBhy3R8Po4J7tRtkJzZId7DZIpugxMFpTkwPwq3JOy60t1sq149PB8mmPqhKBVLT" | base64 --decode
+
+Error writing data to transit/decrypt/message: Error making API request.
+
+URL: PUT http://172.28.128.21:8200/v1/transit/decrypt/message
+Code: 400. Errors:
+
+* ciphertext or signature version is disallowed by policy (too old)
+
+ + + diff --git a/04-HashiCorp/06-Vault/03-Auth_Method/aws-auth.html b/04-HashiCorp/06-Vault/03-Auth_Method/aws-auth.html new file mode 100644 index 0000000000..cc7b452c2e --- /dev/null +++ b/04-HashiCorp/06-Vault/03-Auth_Method/aws-auth.html @@ -0,0 +1,512 @@ + + + + + + + + + + AWS Auth Method | docmoa + + + + + +
본문으로 건너뛰기

AWS Auth Method

약 4 분vault authAWS

AWS Auth Method

https://developer.hashicorp.com/vault/docs/auth/awsopen in new window

https://developer.hashicorp.com/vault/api-docs/auth/awsopen in new window

https://blog.gruntwork.io/a-guide-to-automating-hashicorp-vault-3-authenticating-with-an-iam-user-or-role-a3203a3ee088open in new window

AWS auth method는 IAM 계정 또는 EC2 인스턴스에 대한 Vault 토큰을 검색하는 자동화된 메커니즘을 제공한다. 이 방식은 다양한 상황에서 운영자가 보안에 민감한 자격증명(토큰, 사용자 이름/비밀번호, 클라이언트 인증서 등)을 수동으로 먼저 생성할 필요가 없다.

1. 인증 워크플로

AWS auth method는 iamec2 방식을 지원한다.

iam 인증 방식

AWS IAM 자격 증명으로 서명된 AWS 요청이 인증에 사용된다. 거의 모든 AWS 리소스는 AWS 보안 토큰 서비스(STS)를 호출하여 자신의 신원을 조회할 수 있다. Vault의 AWS iam 인증 방식은 사용자가 직접 요청을 보내는 대신, 서명된 요청 데이터를 Vault로 전송하여 STS에 서명된 요청을 생성할 수 있도록 함으로써 이러한 이점을 활용한다. Vault는 요청을 실행하고 AWS(다시 말해서 신뢰할 수 있는 제3자)로부터 사용자의 실제 신원을 확인한다. iam방식이 좀더 최신의 방식이며, 기존 ec2방식의 한계였던 람다 또는 ECS 같은 다양한 유형의 서비스에서 작동한다.

AWS STS API에는 클라이언트의 신원을 확인할 수 있는 메서드인 sts:GetCallerIdentity가 포함되어 있다. 클라이언트는 AWS 서명 v4 알고리즘을 사용하여 GetCallerIdentity 쿼리에 서명하고 이를 Vault 서버로 보낸다. GetCallerIdentity 요청에 서명하는 데 사용되는 자격 증명은 EC2 인스턴스에 대한 EC2 인스턴스 메타데이터 서비스 또는 AWS Lambda 함수 실행의 AWS 환경 변수에서 가져올 수 있으므로 운영자가 먼저 신원 자료를 수동으로 프로비저닝할 필요가 없다. 그리고 사용되는 자격 증명은 원칙적으로 AWS내부의 Role이부여된 리소스 뿐만 아니라 Access Key를 사용하여 어디에서나 가져올 수 있다.

GetCallerIdentity 쿼리는 Request URL, Request Body, Request Header, Request Method의 네 가지 정보로 구성되며, AWS 서명은 이러한 필드를 통해 계산된다. Vault 서버는 이 정보를 사용해 쿼리를 재구성하고 AWS STS 서비스로 전달한다. STS 서비스의 응답에 따라 서버는 클라이언트를 인증한다.

클라이언트가 AWS STS API 엔드포인트와 통신하기 위한 네트워크 접근이 필요하지 않으며, 요청에 서명하기 위한 자격 증명에 대한 액세스만 있으면 된다. 그러나 Vault 서버가 STS 엔드포인트로 요청을 전송하려면 네트워크 수준 액세스가 필요하다.

서명된 각 AWS 요청에는 현재 타임스탬프가 포함되어 리플레이 공격의 위험을 완화합니다. 또한, Vault에서는 다양한 유형의 리플레이 공격(예: 개발 Vault 인스턴스에서 서명된 GetCallerIdentity 요청을 도용하여 프로덕션 Vault 인스턴스에 인증하는 데 사용하는 공격)을 완화하기 위해 추가 헤더인 X-Vault-AWS-IAM-Server-ID를 요구할 수 있다. 또한 Vault는 이 헤더가 AWS 서명에 포함된 헤더 중 하나이어야 하며, 해당 서명을 인증하기 위해 AWS에 의존한다.

AWS API 엔드포인트는 서명된 GET 요청과 POST 요청을 모두 지원하지만, 간단하게 하기 위해 aws auth 메서드는 POST 요청만 지원합니다. 또한 사전 서명된 요청, 즉 인증 정보가 포함된 X-Amz-Credential, X-Amz-Signature, X-Amz-SignedHeaders GET 쿼리 매개 변수가 있는 요청은 지원하지 않는다.

또한 GetCallerIdentity 호출과 관련하여 어떤 종류의 권한 부여도 포함하지 않는다. 예를 들어, 자격 증명에 대해 모든 액세스가 MFA 인증을 받아야 하는 IAM 정책이 있는 경우, MFA 인증을 받지 않은 자격 증명(즉, GetSessionToken을 호출하고 MFA 코드를 제공하여 검색한 자격 증명이 아닌 원시 자격 증명)은 이 방법을 사용하여 여전히 Vault에 인증할 수 있다. Vault에 인증하는 동안 IAM 주체가 MFA 인증을 받도록 강제하는 것은 불가능하다.

ec2 인증 방식

ec2 방식에서는 AWS가 신뢰할 수 있는 제3자로 취급되며, 각 EC2 인스턴스를 고유하게 나타내는 암호화 서명된 동적 메타데이터 정보가 인증에 사용된다. 이 메타데이터 정보는 AWS가 모든 EC2 인스턴스에 자동으로 제공한다. 특정 AMI, 특정 인스턴스 프로파일의 EC2 인스턴스 또는 특수 태그 값을 가진 EC2 인스턴스(role_tag 기능을 통해)에서 EC2 인스턴스에 대한 액세스를 제한하는 등 EC2 인스턴스를 처리하는 데 특화되어 있다.

Amazon EC2 인스턴스는 인스턴스를 설명하는 메타데이터에 액세스할 수 있습니다. Vault EC2 인증 메서드는 이 메타데이터의 구성 요소를 활용하여 초기 Vault 토큰을 인증하고 EC2 인스턴스에 배포한다. 데이터 흐름(아래 그래픽에도 표시됨)은 다음과 같다:

assets (1368×998) 2023-06-30 20-34-15
assets (1368×998) 2023-06-30 20-34-15
  1. AWS EC2 인스턴스는 EC2 메타데이터 서비스에서 AWS 인스턴스 ID 문서를 가져옵니다. AWS는 데이터 자체 외에도 데이터의 PKCS#7 서명을 제공하고, 서명을 확인하는 데 사용할 수 있는 공개 키(지역별로)를 게시한다.
  2. AWS EC2 인스턴스는 PKCS#7 서명을 사용하여 Vault에 요청합니다. PKCS#7 서명에는 인스턴스 ID 문서가 포함된다.
  3. Vault는 PKCS#7 문서의 서명을 확인하여 정보가 AWS에 의해 정확하게 인증되었는지 확인합니다. 이 프로세스는 문서 데이터의 유효성과 무결성을 모두 검증합니다. 추가 보안 조치로, Vault는 인스턴스가 현재 퍼블릭 EC2 API 엔드포인트를 사용하여 실행 중인지 확인한다.
  4. 모든 단계가 성공적으로 완료되면 Vault는 초기 Vault 토큰을 EC2 인스턴스에 반환합니다. 이 토큰은 인스턴스 메타데이터를 기반으로 구성된 모든 정책에 매핑된다.

2. Vault의 AWS auth method 인증에 필요한 권장 IAM Policy

{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "ec2:DescribeInstances",
+        "iam:GetInstanceProfile",
+        "iam:GetUser",
+        "iam:GetRole"
+      ],
+      "Resource": "*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": ["sts:AssumeRole"],
+      "Resource": ["arn:aws:iam::<AccountId>:role/<VaultRole>"]
+    },
+    {
+      "Sid": "ManageOwnAccessKeys",
+      "Effect": "Allow",
+      "Action": [
+        "iam:CreateAccessKey",
+        "iam:DeleteAccessKey",
+        "iam:GetAccessKeyLastUsed",
+        "iam:GetUser",
+        "iam:ListAccessKeys",
+        "iam:UpdateAccessKey"
+      ],
+      "Resource": "arn:aws:iam::*:user/${aws:username}"
+    },
+    {
+      "Sid": "IAM_Method",
+      "Effect": "Allow",
+      "Action": [
+        "sts:GetCallerIdentity"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+
+
  • arn:aws:iam::\<AccountId\>:*: 로 구성하면 AWS 계정의 모든 주체가 이 계정에 로그인
  • arn:aws:iam::\<AccountId\>:role/* 로 구성하면 AWS 계정의 모든 IAM 역할이 해당 계정에 로그인
  • 와일드카드를 지정하려면 전체 사용자 경로를 올바르게 확인할 수 있도록 Vault에 iam:GetUseriam:GetRole 권한을 부여
  • ARN이 arn:aws:iam::123456789012:role/MyRole인 역할이 있는 경우, 해당 역할에서 AssumeRole을 호출하여 반환되는 자격 증명은 arn:aws:sts::123456789012:assumed-role/MyRole/RoleSessionName이며, 여기서 RoleSessionName은 AssumeRole API 호출의 세션 이름
  • ec2:DescribeInstances는 ec2 인증 메서드를 사용하거나 EC2 인스턴스가 role binding 요구 사항을 충족하는지 확인하기 위해 ec2_instance엔티티 유형을 추론할 때 필요
  • iam:GetInstanceProfile은 ec2 인증 메서드에 bound_iam_role_arn이 있을 때 사용
  • 계정간 접근이 필요한 경우 sts:AssumeRole 구문이 필요(지정된 리소스는 계정 간 액세스를 구성한 모든 역할의 목록이어야 하며, 각 역할에는 이 IAM 정책이 첨부되어 있어야 하며 sts:AssumeRole 문은 제외)
  • 정적 자격 증명으로 Vault를 구성한 후 Rotate Root Credentials API 호출을 통해 이러한 자격 증명을 회전하려는 경우 ManageOwnAccessKeys 구절이 필요

3. AWS 외부에서 구성

AWS외부에서 구성하는 경우 AWS 인증을 위한 Policy가 구성된 Access Key/Secret Key가 필요하다.

aws auth method 활성화

$ vault auth enable aws
+

활성화된 auth method의 구성

  • access_key
  • secret_key
$ vault write auth/aws/config/client secret_key=vCtSM8ZUEQ3mOFVlYPBQkf2sO6F/W7a5TVzrl3Oj access_key=VKIAJBRHKH6EVTTNXDHA
+

ec2 role 구성 및 로그인

$ vault write auth/aws/role/dev-role auth_type=ec2 bound_ami_id=ami-fce3c696 policies=prod,dev max_ttl=500h
+
+$ SIGNATURE=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/rsa2048 | tr -d '\n')
+
+$ vault write auth/aws/login role=dev-role pkcs7=$SIGNATURE
+

iam role 구성 및 로그인

$ vault write auth/aws/role/dev-role-iam auth_type=iam  bound_iam_principal_arn=arn:aws:iam::123456789012:role/MyRole policies=prod,dev max_ttl=500h
+
+$ vault login -method=aws header_value=vault.example.com role=dev-role-iam \
+        aws_access_key_id=<access_key> \
+        aws_secret_access_key=<secret_key> \
+        aws_security_token=<security_token>
+
+# AWS SDK가 지원하는 인증 방식이 설정되어있는 경우 aws_access_key_id 생략 가능
+# ~/.aws/credentials
+# IAM 인스턴스 프로파일
+# ECS task role 등
+$ vault login -method=aws header_value=vault.example.com role=dev-role-iam
+
+# 리전 지정이 필요한 경우 대상 지정
+$ vault login -method=aws region=us-west-2 role=dev-role-iam
+
+# 요청 매개변수를 생성하여 로그인 메서드에 전달
+vault write auth/aws/login role=dev-role-iam \
+        iam_http_request_method=POST \
+        iam_request_url=aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8= \
+        iam_request_body=QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ== \
+        iam_request_headers=eyJDb250ZW50LUxlbmd0aCI6IFsiNDMiXSwgIlVzZXItQWdlbnQiOiBbImF3cy1zZGstZ28vMS40LjEyIChnbzEuNy4xOyBsaW51eDsgYW1kNjQpIl0sICJYLVZhdWx0LUFXU0lBTS1TZXJ2ZXItSWQiOiBbInZhdWx0LmV4YW1wbGUuY29tIl0sICJYLUFtei1EYXRlIjogWyIyMDE2MDkzMFQwNDMxMjFaIl0sICJDb250ZW50LVR5cGUiOiBbImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZDsgY2hhcnNldD11dGYtOCJdLCAiQXV0aG9yaXphdGlvbiI6IFsiQVdTNC1ITUFDLVNIQTI1NiBDcmVkZW50aWFsPWZvby8yMDE2MDkzMC91cy1lYXN0LTEvc3RzL2F3czRfcmVxdWVzdCwgU2lnbmVkSGVhZGVycz1jb250ZW50LWxlbmd0aDtjb250ZW50LXR5cGU7aG9zdDt4LWFtei1kYXRlO3gtdmF1bHQtc2VydmVyLCBTaWduYXR1cmU9YTY5ZmQ3NTBhMzQ0NWM0ZTU1M2UxYjNlNzlkM2RhOTBlZWY1NDA0N2YxZWI0ZWZlOGZmYmM5YzQyOGMyNjU1YiJdfQ==
+
  • iam_request_url 값 예시 : https://sts.amazonaws.com/

  • iam_request_body 값 예시 : Action=GetCallerIdentity&Version=2011-06-15

  • iam_request_headers 값 예시 :

    {
    +  "Content-Length": [
    +    "43"
    +  ],
    +  "User-Agent": [
    +    "aws-sdk-go/1.4.12 (go1.7.1; linux; amd64)"
    +  ],
    +  "X-Vault-AWSIAM-Server-Id": [
    +    "vault.example.com"
    +  ],
    +  "X-Amz-Date": [
    +    "20160930T043121Z"
    +  ],
    +  "Content-Type": [
    +    "application/x-www-form-urlencoded; charset=utf-8"
    +  ],
    +  "Authorization": [
    +    "AWS4-HMAC-SHA256 Credential=foo/20160930/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-server, Signature=a69fd750a3445c4e553e1b3e79d3da90eef54047f1eb4efe8ffbc9c428c2655b"
    +  ]
    +}
    +

4. AWS 내부에서 구성

AWS 내부에서 구성된 Vault 서버 인스턴스 및 클라이언트에 프로파일을 구성하여 Access Key를 생략하고 인증을 구현할 수 있다.

Terraform Sample

terraform {
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "5.5.0"
+    }
+    random = {
+      source  = "hashicorp/random"
+      version = "3.5.1"
+    }
+    tls = {
+      source  = "hashicorp/tls"
+      version = "3.0.0"
+    }
+  }
+}
+
+provider "aws" {
+  region = "ap-northeast-2"
+
+  default_tags {
+    tags = {
+      Environment = "Demo"
+      Owner       = "gs@hashicorp.com"
+      Project     = "example"
+    }
+  }
+}
+
+data "aws_caller_identity" "current" {}
+
+resource "aws_vpc" "example" {
+  cidr_block           = "10.0.0.0/16"
+  enable_dns_support   = true
+  enable_dns_hostnames = true
+}
+
+data "aws_availability_zones" "available" {
+  state = "available"
+}
+
+// public subnet
+resource "aws_subnet" "public" {
+  vpc_id                  = aws_vpc.example.id
+  availability_zone       = data.aws_availability_zones.available.names.0
+  cidr_block              = cidrsubnet(aws_vpc.example.cidr_block, 8, 0) // "10.0.0.0/24" & "10.0.1.0/24"
+  map_public_ip_on_launch = true
+}
+
+resource "aws_internet_gateway" "public" {
+  vpc_id = aws_vpc.example.id
+}
+
+resource "aws_route_table" "public" {
+  vpc_id = aws_vpc.example.id
+
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.public.id
+  }
+}
+
+resource "aws_route_table_association" "public" {
+  subnet_id      = aws_subnet.public.id
+  route_table_id = aws_route_table.public.id
+}
+
+resource "aws_eip" "public" {
+  domain = "vpc"
+}
+
+resource "aws_nat_gateway" "public" {
+  allocation_id = aws_eip.public.id
+  subnet_id     = aws_subnet.public.id
+}
+
+// SG
+resource "aws_security_group" "example" {
+  name   = "example"
+  vpc_id = aws_vpc.example.id
+
+  egress {
+    from_port        = 0
+    to_port          = 0
+    protocol         = "-1"
+    cidr_blocks      = ["0.0.0.0/0"]
+    ipv6_cidr_blocks = ["::/0"]
+  }
+}
+
+resource "aws_security_group_rule" "example_ssh" {
+  type              = "ingress"
+  from_port         = 22
+  to_port           = 22
+  protocol          = "tcp"
+  cidr_blocks       = ["0.0.0.0/0"]
+  security_group_id = aws_security_group.example.id
+}
+
+resource "aws_security_group_rule" "example_vault" {
+  type              = "ingress"
+  from_port         = 8200
+  to_port           = 8200
+  protocol          = "tcp"
+  cidr_blocks       = ["0.0.0.0/0"]
+  security_group_id = aws_security_group.example.id
+}
+
+// key pair
+resource "tls_private_key" "ssh" {
+  algorithm = "RSA"
+  rsa_bits  = 4096
+}
+
+resource "local_sensitive_file" "ssh_private" {
+  content  = tls_private_key.ssh.private_key_pem
+  filename = "${path.module}/ssh_private"
+}
+
+resource "random_id" "key_id" {
+  keepers = {
+    ami_id = tls_private_key.ssh.public_key_openssh
+  }
+
+  byte_length = 8
+}
+
+resource "aws_key_pair" "ssh" {
+  key_name   = "key-${random_id.key_id.hex}"
+  public_key = tls_private_key.ssh.public_key_openssh
+}
+
+// EC2
+data "aws_ami" "ubuntu" {
+  most_recent = true
+
+  filter {
+    name   = "name"
+    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
+  }
+
+  filter {
+    name   = "virtualization-type"
+    values = ["hvm"]
+  }
+
+  owners = ["099720109477"] # Canonical
+}
+
+data "aws_iam_policy_document" "example_instance_role_client" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ec2.amazonaws.com"]
+    }
+  }
+}
+
+resource "aws_iam_role" "example_client_instance_role_client" {
+  name_prefix        = "auth-example-iam-role-client"
+  assume_role_policy = data.aws_iam_policy_document.example_instance_role_client.json
+}
+
+resource "aws_iam_instance_profile" "example_instance_profile_client" {
+  path = "/"
+  role = aws_iam_role.example_client_instance_role_client.name
+}
+
+resource "aws_instance" "client" {
+  ami                         = data.aws_ami.ubuntu.id
+  iam_instance_profile        = aws_iam_instance_profile.example_instance_profile_client.name
+  instance_type               = "t3.micro"
+  key_name                    = aws_key_pair.ssh.key_name
+  subnet_id                   = aws_subnet.public.id
+  associate_public_ip_address = true
+  vpc_security_group_ids      = [aws_security_group.example.id]
+
+  user_data = <<EOF
+		#! /bin/bash
+    sudo apt update && sudo apt install gpg
+    wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
+    gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
+    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
+    sudo apt update
+    sudo apt install vault
+  EOF
+
+  tags = {
+    Name = "client"
+  }
+}
+
+data "aws_iam_policy_document" "assume_role" {
+  statement {
+    effect = "Allow"
+
+    principals {
+      type        = "Service"
+      identifiers = ["ec2.amazonaws.com"]
+    }
+
+    actions = ["sts:AssumeRole"]
+  }
+}
+
+resource "aws_iam_role" "vault_role" {
+  name               = "vault-role"
+  assume_role_policy = data.aws_iam_policy_document.assume_role.json
+}
+
+data "aws_iam_policy_document" "example_policy_vault" {
+  statement {
+    effect    = "Allow"
+    actions = [
+      "ec2:DescribeInstances",
+      "iam:GetInstanceProfile",
+      "iam:GetUser",
+      "iam:GetRole",
+    ]
+    resources = [
+      "*"
+      // "arn:aws:iam::*:user/*",
+      // "arn:aws:iam::*:role/*",
+    ]
+  }
+  statement {
+    effect    = "Allow"
+    actions = [
+      "sts:AssumeRole",
+    ]
+    resources = [
+      "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${aws_iam_role.vault_role.name}"
+    ]
+  }
+  statement {
+    effect    = "Allow"
+    actions   = [
+      "sts:GetCallerIdentity"
+    ]
+    resources = ["*"]
+  }
+  statement {
+    sid = "ManageOwnAccessKeys"
+    actions = [
+      "iam:CreateAccessKey",
+      "iam:DeleteAccessKey",
+      "iam:GetAccessKeyLastUsed",
+      "iam:GetUser",
+      "iam:ListAccessKeys",
+      "iam:UpdateAccessKey",
+    ]
+    resources = ["arn:aws:iam::*:user/$${aws:username}"]
+  }
+}
+
+resource "aws_iam_policy" "policy" {
+  name        = "vault-policy"
+  description = "A test policy"
+  policy      = data.aws_iam_policy_document.example_policy_vault.json
+}
+
+resource "aws_iam_role_policy_attachment" "vault-attach" {
+  role       = aws_iam_role.vault_role.name
+  policy_arn = aws_iam_policy.policy.arn
+}
+
+resource "aws_iam_instance_profile" "vault_profile" {
+  name = "vault_profile"
+  role = aws_iam_role.vault_role.name
+}
+
+resource "aws_instance" "vault" {
+  ami                         = data.aws_ami.ubuntu.id
+  iam_instance_profile        = aws_iam_instance_profile.vault_profile.name
+  instance_type               = "t3.micro"
+  key_name                    = aws_key_pair.ssh.key_name
+  subnet_id                   = aws_subnet.public.id
+  associate_public_ip_address = true
+  vpc_security_group_ids      = [aws_security_group.example.id]
+
+  user_data = <<EOF
+		#! /bin/bash
+    sudo apt update && sudo apt install gpg
+    wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
+    gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
+    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
+    sudo apt update
+    sudo apt install vault
+  EOF
+
+  tags = {
+    Name = "vault"
+  }
+}
+
+output "info" {
+  value = {
+    client_ip       = aws_instance.client.public_ip
+    vault_ip        = aws_instance.vault.public_ip
+    client_role_arn = aws_iam_role.example_client_instance_role_client.arn
+    ami_id          = aws_instance.client.ami
+    ec2_id          = aws_instance.client.id
+    private_key     = nonsensitive(tls_private_key.ssh.private_key_pem)
+  }
+}
+
  • 서버 ssh 접속을 위한 private key는 ssh_private 파일로 자동 생성
    $ ssh -i ./ssh_private $(terraform output -raw vault_ip)
    +

Vault Server 실행

# Server Instance
+$ vault server -dev -dev-root-token-id=root -log-level=trace
+

Client에서 Auth 구성 및 테스트

# Client Instance
+$ export VAULT_ADDR=http://10.0.0.8:8200
+$ export VAULT_TOKEN=root
+
+$ vault policy write "example-policy" -<<EOF
+path "secret/data/example_*" {
+  capabilities = ["create", "read"]
+}
+EOF
+
+$ vault kv put secret/example_test foo=bar
+
+$ vault auth enable aws
+
+# auth 구성에 access key 선언이 생략
+$ vault write auth/aws/config/client sts_region="ap-northeast-2"
+
+# 만약 Vault Server의 AWS Auth에 권한이 없는 경우
+$ vault write \
+   auth/aws/role/example-role-name \
+   auth_type=iam \
+   policies=example-policy \
+   max_ttl=500h \
+   bound_iam_principal_arn=$client_instance_role_arn \
+   inferred_entity_type="ec2_instance" \
+   inferred_aws_region="ap-northeast-2"
+
+Error writing data to auth/aws/role/example-role-name: Error making API request.
+
+URL: PUT http://10.0.0.86:8200/v1/auth/aws/role/example-role-name
+Code: 400. Errors:
+
+* unable to resolve ARN "arn:aws:iam::467567795630:role/auth-example-iam-role20230630045547898800000001" to internal ID: unable to fetch current caller: NoCredentialProviders: no valid providers in chain. Deprecated.
+	For verbose messaging see aws.Config.CredentialsChainVerboseErrors
+	
+
+# Policy 추가 후 login 수행
+$ vault login -method=aws role=example-role-name
+
+Key                      Value
+---                      -----
+token                    hvs.CAESIGe7HuhqFefKHDkE_M_leja0bRDEnwPZs7CeztZQXuVCGh4KHGh2cy41S2VienFmbU5scFZBcmFpWkZNemtrdmE
+token_accessor           VAD61CRZHhLp7VN6Uf6qRHbh
+token_duration           500h
+token_renewable          true
+token_policies           ["default" "example-policy"]
+identity_policies        []
+policies                 ["default" "example-policy"]
+token_meta_account_id    467567795630
+token_meta_auth_type     iam
+token_meta_role_id       c1de423d-0751-a879-7b43-1047f1c43a42
+
+
+$ VAULT_TOKEN=hvs.CAESIGe7HuhqFefKHDkE_M_leja0bRDEnwPZs7CeztZQXuVCGh4KHGh2cy41S2VienFmbU5scFZBcmFpWkZNemtrdmE vault kv get secret/example_test
+
+====== Secret Path ======
+secret/data/example_test
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-06-30T06:21:09.409042961Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            1
+
+=== Data ===
+Key    Value
+---    -----
+foo    bar
+
+ + + diff --git a/04-HashiCorp/06-Vault/03-Auth_Method/index.html b/04-HashiCorp/06-Vault/03-Auth_Method/index.html new file mode 100644 index 0000000000..0a439d256a --- /dev/null +++ b/04-HashiCorp/06-Vault/03-Auth_Method/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 03 Auth Method | docmoa + + + + + +
본문으로 건너뛰기

03 Auth Method

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/03-Auth_Method/mfa-login.html b/04-HashiCorp/06-Vault/03-Auth_Method/mfa-login.html new file mode 100644 index 0000000000..55267da316 --- /dev/null +++ b/04-HashiCorp/06-Vault/03-Auth_Method/mfa-login.html @@ -0,0 +1,87 @@ + + + + + + + + + + MFA Login with Vault TOTP | docmoa + + + + + +
본문으로 건너뛰기

MFA Login with Vault TOTP

1분 미만vault authMFA

MFA Login with Vault TOTP

HashiCorp Learn - Login MFA : https://learn.hashicorp.com/tutorials/vault/multi-factor-authenticationopen in new window
Configure TOTP MFA Method : https://www.vaultproject.io/api-docs/secret/identity/mfa/totpopen in new window
Vault Login MFA Overview : https://www.vaultproject.io/docs/auth/login-mfaopen in new window
1.10.3+ recommend : https://discuss.hashicorp.com/t/vault-1-10-3-released/39394open in new window

ENV Setup

$ ROOT_TOKEN=hvs...
+$ VAULT_ADDR=https://<your-vault-addr>:8200
+$ MY_PASSWORD=password
+
+# If you have NAMESPACE with Enterprise
+$ export VAULT_NAMESPACE=admin
+

Enable username and password auth method

$ VAULT_TOKEN=$ROOT_TOKEN vault auth enable userpass
+
+$ USERPASS_ACCESSOR=$(VAULT_TOKEN=$ROOT_TOKEN vault auth list | grep userpass | awk '{print $3}')
+
+$ VAULT_TOKEN=$ROOT_TOKEN vault write auth/userpass/users/admin password=$MY_PASSWORD
+

Create an entity and alias

$ ENTITY_ID=$(VAULT_TOKEN=$ROOT_TOKEN vault write -field=id identity/entity name="admin")
+
+echo $ENTITY_ID
+
+$ VAULT_TOKEN=$ROOT_TOKEN vault write identity/entity-alias \
+    name="admin" \
+    canonical_id="$ENTITY_ID" \
+    mount_accessor="$USERPASS_ACCESSOR"
+

Enable MFA method (TOTP)

https://www.vaultproject.io/api-docs/secret/identity/mfa/totp#parametersopen in new window

  • identity/mfa/method/totp/generate : for current entity
  • identity/mfa/method/totp/admin-generate : manage to other entity
$ METHOD_ID=$(vault write -field=method_id identity/mfa/method/totp issuer=HCP-Vault period=30 key_size=30 qr_size=200 algorithm=SHA256 digits=6 name=admin)
+
+$ echo $METHOD_ID
+
+$ vault read identity/mfa/method/totp/$METHOD_ID
+
+# vault write identity/mfa/method/totp/generate method_id=$METHOD_ID
+$ vault write identity/mfa/method/totp/admin-generate method_id=$METHOD_ID entity_id=$ENTITY_ID
+
+Key        Value
+---        -----
+barcode    iVBORw0KGgoAAAANSUhEUgAAAM...
+url        otpauth://totp/Vault:307d6c16-6f5c...
+

Create login enforcement

$ VAULT_TOKEN=$ROOT_TOKEN vault write identity/mfa/login-enforcement/mylogin \
+   mfa_method_ids="$METHOD_ID" \
+   auth_method_accessors="$USERPASS_ACCESSOR"
+

Vault OTP Test (Option)

That's able to use online QR generator

$ vault secrets enable totp
+
+$ vault write totp/keys/hcp-vault url="otpauth://totp/HCP-Vault:0d0cf6f5-62e6-6914-5070-47e997e2aa..."
+
+$ vault read totp/code/hcp-vault
+Key     Value
+---     -----
+code    714908
+

Vault Login Userpass + totp

CLI

$ vault login -method userpass username=admin password=$MY_PASSWORD
+
+Enter the passphrase for methodID "0b9d2229-5d64-dc5d-87cc-0fd22775b918" of
+type "totp": <enter_totp>
+

UI

+ + + diff --git a/04-HashiCorp/06-Vault/03-Auth_Method/super-user-create.html b/04-HashiCorp/06-Vault/03-Auth_Method/super-user-create.html new file mode 100644 index 0000000000..3016317374 --- /dev/null +++ b/04-HashiCorp/06-Vault/03-Auth_Method/super-user-create.html @@ -0,0 +1,79 @@ + + + + + + + + + + Vault SuperUser 생성 | docmoa + + + + + +
본문으로 건너뛰기

Vault SuperUser 생성

1분 미만vault auth

Vault SuperUser 생성

주의

해당 방법은 username/password 방식의 Admin권한의 사용자를 생성하나,
보안상 실 구성에는 권장하지 않습니다.

  1. userpass 활성화
vault auth enable userpass
+
  1. 권한 추가 (e.g. super-user)
Linux/MacOS
vault policy write super-user - << EOF
+path "*" {
+capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+EOF
+
  1. 계정 생성
vault write auth/userpass/users/admin password=password policies=super-user
+
  1. 로그인 또는 토큰 생성
Login
vault login -method userpass username=admin password=password
+Success! You are now authenticated. The token information displayed below
+is already stored in the token helper. You do NOT need to run "vault login"
+again. Future Vault requests will automatically use this token.
+
+Key                    Value
+---                    -----
+token                  s.vIRSNJYiVMtRFuOwq4pKbYGK
+token_accessor         EQz974TUJCy0iV7ALZ8xzGe4
+token_duration         768h
+token_renewable        true
+token_policies         ["default" "super-user"]
+identity_policies      []
+policies               ["default" "super-user"]
+token_meta_username    admin
+
+ + + diff --git a/04-HashiCorp/06-Vault/03-Auth_Method/token_role.html b/04-HashiCorp/06-Vault/03-Auth_Method/token_role.html new file mode 100644 index 0000000000..d327b5a3d1 --- /dev/null +++ b/04-HashiCorp/06-Vault/03-Auth_Method/token_role.html @@ -0,0 +1,137 @@ + + + + + + + + + + Token Role | docmoa + + + + + +
본문으로 건너뛰기

Token Role

약 1 분vault auth

Token Role

별도 Auth Method를 사용하지 않고 Token으로만 사용하는 경우 Token에 대한 role을 생성하여 해당 role의 정의된 설정에 종속된 Token을 생성할 수 있음

  • Entity가 발생하므로 Vault Client Count 절약 가능
  • 일관된 Token 생성 가능
  • Token에 대한 별도 Tune(TTL 조정 등) 가능

절차

  1. UI > Access > Entities > [create entity] : 100y-entity

  2. entity에서 aliases 생성 : 100y-alias

  3. role 생성 (payload.json)

    {
    +  "allowed_policies": [
    +    "my-policy"
    +  ],
    +  "name": "100y",
    +  "orphan": false,
    +  "bound_cidrs": ["127.0.0.1/32", "128.252.0.0/16"],
    +  "renewable": true,
    +  "allowed_entity_aliases": ["100y-alias"]
    +}
    +
  4. role 적용

    curl -H "X-Vault-Token: hvs.QKRiVmCedA06dCSc2TptmSk1" -X POST --data @payload.json http://127.0.0.1:8200/v1/auth/token/roles/100y
    +
  5. role에 대한 사용자 정의 tune 적용(옵션)

    vault auth tune -max-lease-ttl=876000h token/role/100y
    +vault auth tune -default-lease-ttl=876000h token/role/100y
    +
  6. tune 적용된 role 확인

    $ vault read auth/token/roles/100y
    +
    +Key                         Value
    +---                         -----
    +allowed_entity_aliases      [100y-alias]
    +allowed_policies            [default]
    +allowed_policies_glob       []
    +bound_cidrs                 [127.0.0.1 128.252.0.0/16]
    +disallowed_policies         []
    +disallowed_policies_glob    []
    +explicit_max_ttl            0s
    +name                        100y
    +orphan                      false
    +path_suffix                 n/a
    +period                      0s
    +renewable                   true
    +token_bound_cidrs           [127.0.0.1 128.252.0.0/16]
    +token_explicit_max_ttl      0s
    +token_no_default_policy     false
    +token_period                0s
    +token_type                  default-service
    +
  7. token 생성

    $ vault token create -entity-alias=100y-alias -role=100y
    +Key                  Value
    +---                  -----
    +token                hvs.CAESIIveQyE34VOowkCXj4InopxsQHWXu2iW00UQDDCTb-pIGh4KHGh2cy5UZGJ4MjJic1RjY1BlVGRWVHhzNFgwWW4
    +token_accessor       Cx6qjyUGwqPmqoPNe9tmkCiN
    +token_duration       876000h
    +token_renewable      true
    +token_policies       ["default"]
    +identity_policies    ["default"]
    +policies             ["default"]
    +
  8. token이 role의 구성이 반영되었는지 확인

    $ vault token lookup hvs.CAESIIveQyE34VOowkCXj4InopxsQHWXu2iW00UQDDCTb-pIGh4KHGh2cy5UZGJ4MjJic1RjY1BlVGRWVHhzNFgwWW4
    +
    +Key                            Value
    +---                            -----
    +accessor                       Cx6qjyUGwqPmqoPNe9tmkCiN
    +bound_cidrs                    [127.0.0.1 128.252.0.0/16]
    +creation_time                  1651059486
    +creation_ttl                   876000h
    +display_name                   token
    +entity_id                      53fc4716-fc0d-db34-14b8-ab4258f89fb1
    +expire_time                    2122-04-03T20:38:06.73198+09:00
    +explicit_max_ttl               0s
    +external_namespace_policies    map[]
    +id                             hvs.CAESIIveQyE34VOowkCXj4InopxsQHWXu2iW00UQDDCTb-pIGh4KHGh2cy5UZGJ4MjJic1RjY1BlVGRWVHhzNFgwWW4
    +identity_policies              [default]
    +issue_time                     2022-04-27T20:38:06.731984+09:00
    +meta                           <nil>
    +num_uses                       0
    +orphan                         false
    +path                           auth/token/create/100y
    +policies                       [default]
    +renewable                      true
    +role                           100y
    +ttl                            875999h59m3s
    +type                           service
    +

Entity 구성 CLI 예제 (옵션)

vault auth list -format=json | jq -r '.["token/"].accessor' > accessor_token.txt
+
+vault write -format=json identity/entity name="100y-entity" policies="default" \
+     metadata=organization="HC" \
+     metadata=team="QA" \
+     | jq -r ".data.id" > entity_id.txt
+     
+vault write identity/entity-alias name="100y-alias" \
+     canonical_id=$(cat entity_id.txt) \
+     mount_accessor=$(cat accessor_token.txt) \
+     custom_metadata=account="QA Account"
+     
+vault write auth/token/roles/100y allowed_policies="super-user" orphan=false bound_cidrs="127.0.0.1/32,128.252.0.0/16" renewable=true allowed_entity_aliases="100y-alias" token_period="876000h"
+
+vault auth tune -max-lease-ttl=876000h token/role/100y
+
+vault auth tune -default-lease-ttl=876000h token/role/100y
+
+vault token create -entity-alias=100y-alias -role=100y
+Key                  Value
+---                  -----
+token                hvs.CAESIDv-SKwwf3MS-CAutW7aQgAZRBjh01lYLeriuSYzYIwfGiEKHGh2cy50cXFIYVhneDBVYU1OT1hXbWc3WHdtbzUQsgU
+token_accessor       TAAPfxaUX1nx64ZqrLPa1VHx
+token_duration       876000h
+token_renewable      true
+token_policies       ["default" "super-user"]
+identity_policies    ["default"]
+policies             ["default" "super-user"]
+
+ + + diff --git a/04-HashiCorp/06-Vault/03-Auth_Method/vault-kv-v2-ui-policy.html b/04-HashiCorp/06-Vault/03-Auth_Method/vault-kv-v2-ui-policy.html new file mode 100644 index 0000000000..985a2f9344 --- /dev/null +++ b/04-HashiCorp/06-Vault/03-Auth_Method/vault-kv-v2-ui-policy.html @@ -0,0 +1,121 @@ + + + + + + + + + + kv-v2 UI Policy | docmoa + + + + + +
본문으로 건너뛰기

kv-v2 UI Policy

1분 미만vaultkvpolicy

kv-v2 UI Policy

사용자별 UI 접근에 대한 설정을 Kv-v2를 예로 확인

Policy 구성

UI 접근을 위해서는 metadata에 대한 권한 추가가 필요함

$ vault policy write ui-kv-policy - << EOF
+
+path "kv-v2/data/path/" {
+  capabilities = ["create", "update", "read", "delete", "list"]
+}
+path "kv-v2/delete/path/" {
+  capabilities = ["update"]
+}
+path "kv-v2/metadata/path/" {
+  capabilities = ["list", "read", "delete"]
+}
+path "kv-v2/destroy/path/" {
+  capabilities = ["update"]
+}
+
+path "kv-v2/data/path/userid/*" {
+  capabilities = ["create", "update", "read", "delete", "list"]
+}
+path "kv-v2/delete/path/userid/*" {
+  capabilities = ["update"]
+}
+path "kv-v2/metadata/path/userid/*" {
+  capabilities = ["list", "read", "delete"]
+}
+path "kv-v2/destroy/path/userid/*" {
+  capabilities = ["update"]
+}
+
+# Additional access for UI
+path "kv-v2/metadata" {
+  capabilities = ["list"]
+}
+EOF
+
+##### or #####
+
+vault policy write ui-kv-policy - << EOF
+
+path "kv-v2/data/path/userid" {
+  capabilities = ["create", "update", "read", "delete", "list"]
+}
+path "kv-v2/delete/path/userid" {
+  capabilities = ["update"]
+}
+path "kv-v2/metadata/path/userid" {
+  capabilities = ["list", "read", "delete"]
+}
+path "kv-v2/destroy/path/userid" {
+  capabilities = ["update"]
+}
+
+# Additional access for UI
+path "kv-v2/metadata/*" {
+  capabilities = ["list"]
+}
+EOF
+
+

인증 생성

생성한 Policy 기반으로 인증 생성

userpass
$ vault write auth/userpass/users/userid password=password policies=ui-kv-policy
+

조회 확인

생성한 인증의 Token을 사용하여 데이터가 조회됨을 확인

$ curl  --request GET --header "X-Vault-Token: s.FqFMzKL8ExjJeBrq79Jjh1eB" http://172.28.128.11:8200/v1/kv-v2/data/path/userid | jq .
+{
+  "request_id": "d3db0633-2e13-ba98-4d79-ca48f2307d5e",
+  "lease_id": "",
+  "renewable": false,
+  "lease_duration": 0,
+  "data": {
+    "data": {
+      "access_key": "1234",
+      "secret_key": "1234"
+    },
+    "metadata": {
+      "created_time": "2021-07-16T06:35:51.496079412Z",
+      "deletion_time": "",
+      "destroyed": false,
+      "version": 1
+    }
+  },
+  "wrap_info": null,
+  "warnings": null,
+  "auth": null
+}
+

UI에서 접근가능한지 확인
Vault 2021-07-16 15-35-48

+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/argocd-vault-plugin.html b/04-HashiCorp/06-Vault/04-UseCase/argocd-vault-plugin.html new file mode 100644 index 0000000000..8a87268bad --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/argocd-vault-plugin.html @@ -0,0 +1,579 @@ + + + + + + + + + + ArgoCD Vault Plugin | docmoa + + + + + +
본문으로 건너뛰기

ArgoCD Vault Plugin

약 7 분vaultargocdgitopsdevsescopspipelinegithubgitlabsecretkubernetesk8seks

ArgoCD Vault Plugin

참고 : 본 글은 AEWS 스터디 7주차 내용중 일부로 작성된 내용입니다.

1. ArgoCD

img

1) 개요 및 소개

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.

  • 지속적인 배포란(Continuous Delivery, CD) 개발자가 소스코드를 변경해서 깃 저장소에 푸시하면 해당 변경 사항이 고객이 사용하는 실제 운영환경의 시스템까지 자동으로 반영함
    • 개발자의 코드가 원격 저장소에 업로드됐을 때 아르고시디가 자동으로 해당 코드를 클러스터 운영환경에 배포합니다.
    • 아르고시티로 배포한 헬름 애플리케이션의 리소스 목록, 각 리소스 간 관계 및 에러 유무를 UI로 보여줍니다.
  • 단일 진실 원천(SSOT, Single Source Of Truth)이란 어떠한 진실(결과)의 원인이 하나의 이유(원천)에서 비롯되는 것을 의미합니다.
    • 쿠버네티스 환경에서 깃옵스의 의미는 실제 운영 중인 클러스터의 상태를 개발자의 로컬 PC혹은 아무런 기록을 남기지 않고 클러스터에서 임의로 수정하게 하지 않고 공용으로 관리하는 깃 저장소에서만 유일하게 변경을 허용함으로써 단일 진실 원천(SSOT)를 구현합니다.
    • 아르고시디를 사용하면 쿠버네티스 매니페스트 소스 파일을 여러 개발자의 개인 PC에 보관하지 않고 중앙의 통합된 깃 저장소에 반드시 업로드하고 동기화하도록 정책 관리 가능함
  • GitOps - 출처(Automating Amazon EKS with GitOps)open in new window
imgimg

2) 설치

# 설치
+helm repo add argo https://argoproj.github.io/argo-helm
+helm repo update
+helm install argocd argo/argo-cd --set server.service.type=LoadBalancer --namespace argocd --create-namespace --version 5.42.3
+
+# External IP 확인
+EXTERNAL_IP=$(k get svc -n argocd argocd-server -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
+echo $EXTERNAL_IP
+
+# admin 계정의 암호 확인
+ARGOPW=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
+echo $ARGOPW
+mf8bOtNEq7iHMqq1
+

(1) UI 접속 확인

img

(2) CLI 도구설치 및 연동

# 최신버전 설치
+curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
+install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
+chmod +x /usr/local/bin/argocd
+
+# 버전 확인
+argocd version --short
+
+# argocd 서버 로그인
+argocd login $EXTERNAL_IP --username admin --password $ARGOPW
+WARNING: server certificate had error: tls: failed to verify certificate: x509: certificate signed by unknown authority. Proceed insecurely (y/n)? y
+'admin:login' logged in successfully
+Context 'k8s-argocd-argocdse-789cd00c72-b0b60b99b16f1fc7.elb.ap-northeast-2.amazonaws.com' updated
+
+# 기 설치한 깃랩의 프로젝트 URL 을 argocd 깃 리포지토리(argocd repo)로 등록. 깃랩은 프로젝트 단위로 소스 코드를 보관.
+argocd repo add <저장소 주소> --username <계정명> --password <암호>
+ 
+# 등록 확인 : 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
+argocd repo list
+TYPE  NAME  REPO                                            INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
+git         https://github.com/hyungwook0221/argo-demo.git  false     false  false  true   Successful
+
+# 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
+argocd cluster list
+SERVER                          NAME        VERSION  STATUS   MESSAGE                                                  PROJECT
+https://kubernetes.default.svc  in-cluster           Unknown  Cluster has no applications and is not being monitored.
+

3) 샘플 앱 배포

(1) Git 저장소 생성 및 다운

해당 저장소는 개인이 생성한 Git 저장소로 대체하셔도 됩니다.
필자가 만든 저장소를 그대로 사용한다면, "ArgoCD Application CRD" 챕터로 넘어가시면 됩ㄴ디ㅏ.

# Git 저장소 설정
+git clone https://github.com/hyungwook0221/argo-demo.git
+cd argo-demo
+
+# 깃 원격 오리진 주소 확인
+git config -l | grep remote.origin.url
+remote.origin.url=https://github.com/hyungwook0221/argo-demo.git
+

(2) PostgreSQL 배포

참고 : https://artifacthub.io/packages/helm/bitnami/postgresqlopen in new window

  • PostgreSQL 헬름차트 준비 및 Github 저장소
# PostgreSQL 헬름차트 추가 및 다운로드
+helm repo add bitnami https://charts.bitnami.com/bitnami
+helm fetch bitnami/postgresql --untar
+cd postgresql/
+
+# audit.logConnections=false에서 true로 변경
+cat <<EOF > override-values.yaml
+audit:
+  logConnections: true
+EOF
+
+# 헬름 차트를 깃랩 저장소에 업로드
+git add . && git commit -m "add postgresql helm"
+git push
+
  • ArgoCD Application CRD 준비 : postgresql-helm-argo-application.yml
# postgresql-helm-argo-application.yml
+---
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+  name: postgresql-helm
+  namespace: argocd
+spec:
+  destination:
+    namespace: postgresql
+    server: https://kubernetes.default.svc
+  project: default
+  source:
+    repoURL: https://github.com/hyungwook0221/argo-demo.git
+    path: postgresql
+    targetRevision: main
+    helm:
+      valueFiles:
+      - override-values.yaml
+  syncPolicy:
+    syncOptions:
+    - CreateNamespace=true
+    automated:
+      selfHeal: true
+      prune: true
+
  • CRD 배포 및 확인
# 모니터링 : argocd 웹 화면 보고 있기!
+echo -e "Argocd Web URL = $EXTERNAL_IP"
+
+# 배포
+kubectl apply -f postgresql-helm-argo-application.yml
+
+# YAML 파일을 적용(apply)하여 아르고시디 ‘Application’ CRD를 생성
+kubectl get applications.argoproj.io -n argocd
+NAME              SYNC STATUS   HEALTH STATUS
+postgresql-helm   Synced        Healthy
+
+# argocd app 배포 확인
+argocd app get postgresql-helm
+Name:               argocd/postgresql-helm
+Project:            default
+Server:             https://kubernetes.default.svc
+Namespace:          postgresql
+URL:                https://k8s-argocd-argocdse-789cd00c72-b0b60b99b16f1fc7.elb.ap-northeast-2.amazonaws.com/applications/postgresql-helm
+Repo:               https://github.com/hyungwook0221/argo-demo.git
+Target:             main
+Path:               postgresql
+Helm Values:        override-values.yaml
+SyncWindow:         Sync Allowed
+Sync Policy:        Automated (Prune)
+Sync Status:        Synced to main (cf8a47a)
+Health Status:      Healthy
+
+GROUP  KIND         NAMESPACE   NAME                STATUS     HEALTH   HOOK  MESSAGE
+       Namespace                postgresql          Succeeded  Synced         namespace/postgresql created
+       Secret       postgresql  postgresql-helm     Synced                    secret/postgresql-helm created
+       Service      postgresql  postgresql-helm-hl  Synced     Healthy        service/postgresql-helm-hl created
+       Service      postgresql  postgresql-helm     Synced     Healthy        service/postgresql-helm created
+apps   StatefulSet  postgresql  postgresql-helm     Synced     Healthy        statefulset.apps/postgresql-helm created
+
  • AargoCD GitHub/GitLab Deploy 아키텍처(참고 : 가시다 스터디)
img

2. Argo CD Vault Plugin

Argo CD에는 다양한 시크릿 관리 도구(HashiCorp Vault, IBM Cloud Secrets Manager, AWS Secrets Manager 등)플러그인을 통해 Kubernetes 리소스에 주입할 수 있도록 지원합니다.

플러그인을 통해 Operator 또는 CRD(Custom Resource Definition)에 의존하지 않고 GitOps와 Argo CD로 시크릿 관리 문제를 해결할 수 있습니다.특히 Secret 뿐만 아니라, deployment, configMap 또는 기타 Kubernetes 리소스에도 사용할 수 있습니다.

필자는 그 중에서 가장 대표적인 시크릿 관리 도구인 HashiCorp Vault 플러그인을 연동하는 방법을 알아보겠습니다.

img

1) Vault 환경 준비

(1) Vault 설치

# 저장소 추가
+helm repo add hashicorp https://helm.releases.hashicorp.com
+
+# 저장소 업데이트
+helm repo update
+
+# 저장소 추가확인
+helm search repo hashicorp/vault
+
+# vault-server-values.yaml
+---
+server:
+  dev:
+    enabled: true
+    devRootToken: "root"
+  logLevel: debug
+
+injector:
+  enabled: "false"
+
+# vault 헬름차트 배포
+helm install vault hashicorp/vault -n vault --create-namespace --values vault-server-values.yaml
+

(2) Vault 구성

  • 시크릿 엔진 설정
# shell 접속
+kubectl exec -n vault vault-0 -it -- sh
+
+# enable kv-v2 engine in Vault
+vault secrets enable kv-v2
+
+# create kv-v2 secret with two keys
+vault kv put kv-v2/demo user="secret_user" password="secret_password"
+
+# create policy to enable reading above secret
+vault policy write demo - <<EOF
+path "kv-v2/data/demo" {
+  capabilities = ["read"]
+}
+EOF
+
  • 인증방식 활성화
# enable Kubernetes Auth Method
+vault auth enable kubernetes
+
+# get Kubernetes host address
+# K8S_HOST="https://kubernetes.default.svc"
+# K8S_HOST="https://$(env | grep KUBERNETES_PORT_443_TCP_ADDR| cut -f2 -d'='):443"
+# K8S_HOST="https://$( kubectl exec -n vault vault-0 -- env | grep KUBERNETES_PORT_443_TCP_ADDR| cut -f2 -d'='):443"
+
+# get Service Account token from Vault Pod
+#SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
+# SA_TOKEN=$(kubectl exec -n vault vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
+
+# get Service Account CA certificate from Vault Pod
+#SA_CERT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)
+#SA_CERT=$(kubectl exec -n vault vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)
+
+# configure Kubernetes Auth Method
+# kubectl exec -n vault vault-0 -- vault write auth/kubernetes/config \
+#     token_reviewer_jwt=$SA_TOKEN \
+#     kubernetes_host=$K8S_HOST \
+#     kubernetes_ca_cert=$SA_CERT
+
+# 인증방식 업데이트
+vault write auth/kubernetes/config \
+  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
+  kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
+  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+
+# create authenticate Role for ArgoCD
+vault write auth/kubernetes/role/argocd \
+  bound_service_account_names=argocd-repo-server \
+  bound_service_account_namespaces=argocd \
+  policies=demo \
+  ttl=48h
+
+exit
+

(3) ArgoCD Vault Plugin Credentials 생성

💡 참고

kind: Secret
+apiVersion: v1
+metadata:
+  name: argocd-vault-plugin-credentials
+  namespace: argocd
+type: Opaque
+stringData:
+  AVP_AUTH_TYPE: "k8s"
+  AVP_K8S_ROLE: "argocd"
+  AVP_TYPE: "vault"
+  VAULT_ADDR: "http://vault.vault:8200"
+

2) Vault Plugin 설치

공식문서를 통해 Argo CD에 Vault Plugin을 설치하는 방법은 크게 4가지 방법 있으며, 크게는 **2가지 방법**으로 구분해서 소개하고 있습니다. 참고open in new window

(0) 설치방안 2 가지


방안1-1) Installaion via a sidecar(with Manual)

필자는 v2.4.0부터 제공되는 사이드카 방식을 통해 구성하는 방법을 채택했습니다.

a. InitContainer and configuration via sidecar

사이드카 컨테이너에 마운트할 컨피그맵에서 플러그인을 정의

💡 참고 :

apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cmp-plugin
+  namespace: argocd
+data:
+  avp.yaml: |
+    apiVersion: argoproj.io/v1alpha1
+    kind: ConfigManagementPlugin
+    metadata:
+      name: argocd-vault-plugin
+    spec:
+      allowConcurrency: true
+      discover:
+        find:
+          command:
+            - sh
+            - "-c"
+            - "find . -name '*.yaml' | xargs -I {} grep \"<path\\|avp\\.kubernetes\\.io\" {} | grep ."
+      generate:
+        command:
+          - argocd-vault-plugin
+          - generate
+          - "."
+      lockRepo: false
+  avp-helm.yaml: |
+    ---
+    apiVersion: argoproj.io/v1alpha1
+    kind: ConfigManagementPlugin
+    metadata:
+      name: argocd-vault-plugin-helm
+    spec:
+      allowConcurrency: true
+
+      # Note: this command is run _before_ any Helm templating is done, therefore the logic is to check
+      # if this looks like a Helm chart
+      discover:
+        find:
+          command:
+            - sh
+            - "-c"
+            - "find . -name 'Chart.yaml' && find . -name 'values.yaml'"
+      generate:
+        # **IMPORTANT**: passing `${ARGOCD_ENV_helm_args}` effectively allows users to run arbitrary code in the Argo CD 
+        # repo-server (or, if using a sidecar, in the plugin sidecar). Only use this when the users are completely trusted. If
+        # possible, determine which Helm arguments are needed by your users and explicitly pass only those arguments.
+        command:
+          - sh
+          - "-c"
+          - |
+            helm template $ARGOCD_APP_NAME -n $ARGOCD_APP_NAMESPACE ${ARGOCD_ENV_HELM_ARGS} . |
+            argocd-vault-plugin generate -s argocd:argocd-vault-plugin-credentials -
+      lockRepo: false
+  avp-kustomize.yaml: |
+    ---
+    apiVersion: argoproj.io/v1alpha1
+    kind: ConfigManagementPlugin
+    metadata:
+      name: argocd-vault-plugin-kustomize
+    spec:
+      allowConcurrency: true
+
+      # Note: this command is run _before_ anything is done, therefore the logic is to check
+      # if this looks like a Kustomize bundle
+      discover:
+        find:
+          command:
+            - find
+            - "."
+            - -name
+            - kustomization.yaml
+      generate:
+        command:
+          - sh
+          - "-c"
+          - "kustomize build . | argocd-vault-plugin generate -"
+      lockRepo: false
+---
+

argocd-repo-server를 패치하여 argocd-vault-plugin을 다운로드하고 사이드카를 정의하기 위한 initContainer를 추가합니다.

💡 참고 :

apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: argocd-repo-server
+spec:
+  template:
+    spec:
+      automountServiceAccountToken: true
+      volumes:
+      # volumes절 아래 추가
+      - configMap:
+          name: cmp-plugin
+        name: cmp-plugin
+      - name: custom-tools
+        emptyDir: {}
+      initContainers:
+      # initContainers 절 아래 추가   
+      # 필자는 편의상 alpine/curl 이미지 사용하여 바이너리 다운로드
+      - name: download-tools
+        image: alpine/curl
+        env:
+          - name: AVP_VERSION
+            value: 1.15.0
+        command: [sh, -c]
+        args:
+          - >-
+            curl -L https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v$(AVP_VERSION)/argocd-vault-plugin_$(AVP_VERSION)_linux_amd64 -o argocd-vault-plugin &&
+            chmod +x argocd-vault-plugin &&
+            mv argocd-vault-plugin /custom-tools/
+        volumeMounts:
+          - mountPath: /custom-tools
+            name: custom-tools
+      # argocd-vault-plugin 배포방안(3가지 중 선택)
+      containers:
+      # AVP : argocd-vault-plugin with plain YAML
+      - name: avp
+        command: [/var/run/argocd/argocd-cmp-server]
+        image: quay.io/argoproj/argocd:v2.7.4
+        securityContext:
+          runAsNonRoot: true
+          runAsUser: 999
+        volumeMounts:
+          - mountPath: /var/run/argocd
+            name: var-files
+          - mountPath: /home/argocd/cmp-server/plugins
+            name: plugins
+          - mountPath: /tmp
+            name: tmp
+
+          # Register plugins into sidecar
+          - mountPath: /home/argocd/cmp-server/config/plugin.yaml
+            subPath: avp.yaml
+            name: cmp-plugin
+
+          # Important: Mount tools into $PATH
+          - name: custom-tools
+            subPath: argocd-vault-plugin
+            mountPath: /usr/local/bin/argocd-vault-plugin
+            
+      # AVP-Helm : argocd-vault-plugin with Helm
+      - name: avp-helm
+        command: [/var/run/argocd/argocd-cmp-server]
+        image: quay.io/argoproj/argocd:v2.7.4
+        securityContext:
+          runAsNonRoot: true
+          runAsUser: 999
+        volumeMounts:
+          - mountPath: /var/run/argocd
+            name: var-files
+          - mountPath: /home/argocd/cmp-server/plugins
+            name: plugins
+          - mountPath: /tmp
+            name: tmp
+
+          # Register plugins into sidecar
+          - mountPath: /home/argocd/cmp-server/config/plugin.yaml
+            subPath: avp-helm.yaml
+            name: cmp-plugin
+
+          # Important: Mount tools into $PATH
+          - name: custom-tools
+            subPath: argocd-vault-plugin
+            mountPath: /usr/local/bin/argocd-vault-plugin
+            
+      # AVP-Kustomize : argocd-vault-plugin with Kustomize
+      - name: avp-kustomize
+        command: [/var/run/argocd/argocd-cmp-server]
+        image: quay.io/argoproj/argocd:v2.4.0
+        securityContext:
+          runAsNonRoot: true
+          runAsUser: 999
+        volumeMounts:
+          - mountPath: /var/run/argocd
+            name: var-files
+          - mountPath: /home/argocd/cmp-server/plugins
+            name: plugins
+          - mountPath: /tmp
+            name: tmp
+
+          # Register plugins into sidecar
+          - mountPath: /home/argocd/cmp-server/config/plugin.yaml
+            subPath: avp-kustomize.yaml
+            name: cmp-plugin
+
+          # Important: Mount tools into $PATH
+          - name: custom-tools
+            subPath: argocd-vault-plugin
+            mountPath: /usr/local/bin/argocd-vault-plugin
+
  • 편집 후 재기동 로그 확인
img
img

방안1-2) Installaion via a sidecar(with Helm)

💡 참고

a. ConfigManagementPlugin 설정을 위한 configMap 생성 - 링크open in new window
apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: cmp-plugin
+  namespace: argocd
+data:
+  avp-helm.yaml: |
+    apiVersion: argoproj.io/v1alpha1
+    kind: ConfigManagementPlugin
+    metadata:
+      name: argocd-vault-plugin-helm
+    spec:
+      allowConcurrency: true
+      discover:
+        find:
+          command:
+            - sh
+            - "-c"
+            - "find . -name 'Chart.yaml' && find . -name 'values.yaml'"
+      generate:
+        command:
+          - bash
+          - "-c"
+          - |
+            helm template $ARGOCD_APP_NAME --include-crds -n $ARGOCD_APP_NAMESPACE -f ${ARGOCD_ENV_HELM_VALUES} . |
+            argocd-vault-plugin generate -s argocd:argocd-vault-plugin-credentials -
+      lockRepo: false
+

💡 참고

b. ArgoCD wi5th Vault Plugin Helm Chart 작성
  • argocd-helm-values.yaml
repoServer:
+  rbac:
+    - verbs:
+        - get
+        - list
+        - watch
+      apiGroups:
+        - ''
+      resources:
+        - secrets
+        - configmaps
+  initContainers:
+    - name: download-tools
+      image: alpine/curl
+      env:
+        - name: AVP_VERSION
+          value: 1.14.0
+      command: [sh, -c]
+      args:
+        - >-
+          curl -L https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v$(AVP_VERSION)/argocd-vault-plugin_$(AVP_VERSION)_linux_amd64 -o argocd-vault-plugin &&
+          chmod +x argocd-vault-plugin &&
+          mv argocd-vault-plugin /custom-tools/
+      volumeMounts:
+        - mountPath: /custom-tools
+          name: custom-tools
+  extraContainers:
+    - name: avp-helm
+      command: [/var/run/argocd/argocd-cmp-server]
+      image: quay.io/argoproj/argocd:v2.7.4
+      securityContext:
+        runAsNonRoot: true
+        runAsUser: 999
+      volumeMounts:
+        - mountPath: /var/run/argocd
+          name: var-files
+        - mountPath: /home/argocd/cmp-server/plugins
+          name: plugins
+        - mountPath: /tmp
+          name: tmp-dir
+        - mountPath: /home/argocd/cmp-server/config
+          name: cmp-plugin
+        - name: custom-tools
+          subPath: argocd-vault-plugin
+          mountPath: /usr/local/bin/argocd-vault-plugin
+  volumes:
+    - configMap:
+        name: cmp-plugin
+      name: cmp-plugin
+    - name: custom-tools
+      emptyDir: {}
+    - name: tmp-dir
+      emptyDir: {}
+
+# If you face issue with ArgoCD CRDs installation, then uncomment below section to disable it
+#crds:
+#  install: false
+

방안2) Installation via arocd-cm ConfigMap

해당 방안의 경우에는 argocd-cm configMap을 수정하여 적용하는 방안입니다.

💡 참고 : 2.6.0에서 Deprecated 될 예정

      containers:
+      - name: argocd-repo-server
+        # volumeMounts절에 custom-tools 추가
+        volumeMounts:
+        - name: custom-tools
+          mountPath: /usr/local/bin/argocd-vault-plugin
+          subPath: argocd-vault-plugin
+  	  # volume절에 custom-tools 추가
+      volumes:
+      - name: custom-tools
+        emptyDir: {}
+      # init Container 추가  
+      initContainers:
+      - name: download-tools
+        image: alpine:3.8
+        command: [sh, -c]
+        # Don't forget to update this to whatever the stable release version is
+        # Note the lack of the `v` prefix unlike the git tag
+        env:
+          - name: AVP_VERSION
+            value: "1.14.0"
+        args:
+          - >-
+            wget -O argocd-vault-plugin
+            https://github.com/argoproj-labs/argocd-vault-plugin/releases/download/v${AVP_VERSION}/argocd-vault-plugin_${AVP_VERSION}_linux_amd64 &&
+            chmod +x argocd-vault-plugin &&
+            mv argocd-vault-plugin /custom-tools/
+        volumeMounts:
+          - mountPath: /custom-tools
+            name: custom-tools
+

3) 샘플 애플리케이션 배포

(1) 저장소 추가

💡 참고 : Git 저장소에 대한 Fork 후 진행

# 샘플 애플리케이션 배포를 위한 저장소 추가
+# argocd repo add <저장소 주소> --username <계정명> --password <암호>
+argocd repo add https://github.com/hyungwook0221/spring-boot-debug-app --username <계정명> --password <암호>
+ 
+# 등록 확인 : 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
+argocd repo list
+TYPE  NAME  REPO                                                    INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
+git         https://github.com/hyungwook0221/argo-demo.git          false     false  false  true   Successful
+git         https://github.com/hyungwook0221/spring-boot-debug-app  false     false  false  true   Successful
+

(2) Helm Chart에 포함된 시크릿 데이터 배포

  • Applicaton YAML 샘플
apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+  name: demo
+  namespace: argocd
+spec:
+  destination:
+    namespace: argocd
+    server: https://kubernetes.default.svc
+  project: default
+  source:
+    path: infra/helm
+    repoURL: https://github.com/hyungwook0221/spring-boot-debug-app
+    targetRevision: main
+    plugin:
+      env:
+        - name: HELM_ARGS
+          value: '-f override-values.yaml'
+  syncPolicy:
+    automated:
+      prune: true
+      selfHeal: true
+
  • 배포된 아키텍처
img
img

위 Application 배포시 사용될 override-values.yaml 파일의 코드 중 Vault를 통해서 받아올 부분은 다음과 같습니다.

#(생략)
+envs:
+  - name: VAULT_SECRET_USER
+    value: <path:kv-v2/data/demo#user>
+  - name: VAULT_SECRET_PASSWORD
+    value: <path:kv-v2/data/demo#password>
+

해당 Values 파일에 등록된 VAULT_SECRET_USER, VAULT_SECRET_PASSWORD 값은 Vault의 KV-V2에 저장된 값을 호출하여 실제 매니페스트로 저장되어 배포될 때에는 다음과 같이 파싱된 후 기입됩니다.

img

이 외에 추가 데모 시나리오는 다음 글에서 이어서 업로드 하겠습니다!🔥

+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/index.html b/04-HashiCorp/06-Vault/04-UseCase/index.html new file mode 100644 index 0000000000..5198fd70da --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 04 Use Case | docmoa + + + + + +
본문으로 건너뛰기

04 Use Case

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/jenkins-pipeilne-vault-approle.html b/04-HashiCorp/06-Vault/04-UseCase/jenkins-pipeilne-vault-approle.html new file mode 100644 index 0000000000..e90e4d7f36 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/jenkins-pipeilne-vault-approle.html @@ -0,0 +1,353 @@ + + + + + + + + + + Jenkins Pipeline Vault Approle (with Nomad) | docmoa + + + + + +
본문으로 건너뛰기

Jenkins Pipeline Vault Approle (with Nomad)

약 5 분vaultjenkinsapprole

Jenkins Pipeline Vault Approle (with Nomad)

Vault의 AppRole 인증 방식은 Vault Token을 얻기위한 단기 자격증명을 사용하는 장점이 있지만 자동화된 환경에 어울리는(반대로 사람에게 불편한)방식으로 Vault를 이용하는 애플리케이션/스크립트의 배포 파이프라인을 구성하는 방식을 추천합니다.

TEST ENV
$ sw_vers
+ProductName:	macOS
+ProductVersion:	12.4
+
+$ brew --version
+Homebrew 3.5.2
+
+$ git version
+git version 2.27.0
+
+$ java -version
+openjdk version "11.0.14.1" 2022-02-08
+
+$ gradle --version
+Welcome to Gradle 7.4.2!
+
+$ docker version
+Client:
+ Version:           20.10.9
+
+Server:
+ Engine:
+  Version:          20.10.14
+
+$ vault version
+Vault v1.11.0
+
+$ nomad version
+Nomad v1.3.1
+
+$ curl --version
+curl 7.79.1 (x86_64-apple-darwin21.0)
+

1. Vault & Nomad Integration (dev mode)

  • 테스트를 위한 Vault를 실행합니다. 개인을 위한 개발모드로 실행하기 때문에 실환경 적용을 위해서는 별도 구성이 필요합니다.
  • Nomad는 배포/실행을 위한 용도로 사용되었습니다. 별도 VM, K8S 환경이 있다면 PIPELINE 구성 마지막의 배포 구성에 변경이 필요합니다.

1.1 Vault Setup

경고

개발모드로 실행하면 데이터가 메모리에만 저장되어 종료시 삭제 됩니다.

Vault Start Dev mode

vault server -dev -dev-root-token-id=root
+
  • -dev-root-token-id : 개발모드에서는 root 토큰을 지정가능

Vault Env

Another terminal

export VAULT_ADDR=http://127.0.0.1:8200   #Vault 주소
+export VAULT_TOKEN=root                   #Vault Root Token
+export JENKINS_POLICY=jenkins-policy      #테스트용 Jenkins Policy 이름
+

1.2 Dynamic Secret Sample (AWS)

정보

테스트를 위한 Secret Engine으로 AWS를 활성화 합니다.
AWS Secret Engine을 사용하기 위해서는 AWS Credential 정보가 필요합니다.
발급 안내 : https://docs.aws.amazon.com/ko_kr/powershell/latest/userguide/pstools-appendix-sign-up.htmlopen in new window

Setup AWS Env

export AWS_ACCESS_KEY=AKIAU3NXXXXX
+export AWS_SECRET_KEY=Rex3GPUKO3++123
+export AWS_REGION=ap-northeast-2
+

Enable AWS Secret Engine

vault secrets enable -path=aws aws
+
  • -path 인수로 aws Secret Engine이 마운트되는 경로를 설정할 수 있고, 기본 endpoint는 aws

AWS Secret Engine Configuration

vault write aws/config/root \
+  access_key=$AWS_ACCESS_KEY \
+  secret_key=$AWS_SECRET_KEY \
+  region=$AWS_REGION
+
  • 마운트 된 aws Secret Engine의 기본 설정

AWS Secret Engine Lease change (option)

vault write /aws/config/lease lease=1m lease_max=1m
+
  • aws Secret Engine에서 iam_user 형태의 role을 생성하는 경우의 ttl
  • lease는 해당 aws Secret Engine에서 생성되는 계정의 기본 유지 기간을 설정
  • 기본 값은 768h (32일)

Role setup (e.g. s3)

vault write aws/roles/sts-s3 \
+    credential_type=federation_token \
+    policy_document=-<<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+     		"s3:PutObject",
+      	"s3:PutObjectAcl"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+
  • aws로 생성할 federation_token(STS) 타입의 role 정의

Test AWS Secret

$ vault write aws/sts/sts-s3 ttl=900
+Key                Value
+---                -----
+lease_id           aws/sts/sts-s3/Qxfoy2plVAfS57tDQ99B5vPM
+lease_duration     14m59s
+lease_renewable    false
+access_key         ASIAU3NXDWRUE3FCRWBM
+secret_key         TX76EXmadilWw3TySTscB1XGAPI4kNyhdQIdKKtS
+security_token     IQoJb3JpZ2luX2VjENb//////////wEaDmFwLW5vcnRoZWFzdC0yIkcwRQIhAM
+
  • iam_user 형태인 경와 다르게 중간에 sts 경로를 넣음
  • ttlfederation_token인 role에서는 15분(900초)가 최소 값

1.3 Approle Setup

앞서 생성한 aws Secret Engine의 federation_token 을 획득하기 위한 Vault 인증을 추가합니다. AppRole은 기계친화적인 인증방식으로 Username/Password 방식과 빗대어 Username역할을 하는 role_id와 Password 역할을 하는 secret_id 페어로 인증하게 됩니다. secret_id는 영구적이지 않으므로 필요할 때 발급받아 사용합니다. 필요할 때 발급받게 되는 것으로 보안성은 높으나 발급받기위한 자동화 구성이 요구됩니다.

Vault Policy for AWS

cat <<EOF | vault policy write aws_policy -
+path "aws/sts/sts-s3" {
+  capabilities = ["read","update"]
+}
+EOF
+
  • 생성할 AppRole 계정에서 사용할 정책을 추가
  • aws/sts/sts-s3 에 대한 읽기(발급)와 갱신 권한

Approle Create

aws_policy 정책을 갖는 AppRole 생성

$ vault auth enable approle
+
+Success! Enabled approle auth method at: approle/
+
+$ vault write auth/approle/role/aws-cred \
+    secret_id_ttl=1m \
+    token_ttl=60m \
+    token_max_ttl=120m \
+    policies="aws_policy"
+    
+Success! Data written to: auth/approle/role/aws-cred
+
+$ vault read auth/approle/role/aws-cred/role-id
+
+Key        Value
+---        -----
+role_id    430111ee-5955-aa83-a53d-924b7e11ac36
+
+$ vault write -f auth/approle/role/aws-cred/secret-id
+
+Key                   Value
+---                   -----
+secret_id             7f86b671-2f47-f841-18a4-c36ca34ab8d8
+secret_id_accessor    9ad4256a-acc6-e8c0-f7fe-7633e66b1318
+secret_id_ttl         1m
+

생성한 role_idsecret_id로 Vault에 인증을 수행합니다. secret_id의 경우 ttl 지정이 가능 합니다.

# Test
+$ vault read -field role_id auth/approle/role/aws-cred/role-id > role_id.txt
+$ vault write -f -field secret_id auth/approle/role/aws-cred/secret-id > secret_id.txt
+$ unset VAULT_TOKEN
+$ vault write auth/approle/login role_id=$(cat ./role_id.txt) secret_id=$(cat ./secret_id.txt)
+
+Key                     Value
+---                     -----
+token                   hvs.CAESIDoJqJmtgUQXj_DSHaSLwkdZFQQpjr7_x-r_bmy6ZbpyGh4KHGh2cy5SeEVpWGpmcXlJSE95WEpKUVdKSW8zMXI
+token_accessor          SpMtPdNUXaF3GAOATtWD0Qdi
+token_duration          1h
+token_renewable         true
+token_policies          ["aws_policy" "default"]
+identity_policies       []
+policies                ["aws_policy" "default"]
+token_meta_role_name    aws-cred
+
+$ vault read /aws/creds/s3
+
+Key                Value
+---                -----
+lease_id           aws/creds/s3/1iaII6N6DaUULD27w91ZpePP
+lease_duration     1m
+lease_renewable    true
+access_key         AKIAU3NXDWRUEXSGRTNL
+secret_key         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
+security_token     <nil>
+
  • role_id는 Pipeline 작성에 사용

1.4 Jenkins Token

Jenkins에서는 생성된 AppRole의 role_id에 대한 secret_id를 발급받을 권한이 있어야 합니다.

Vault Policy for AppRole Secret

cat <<EOF | vault policy write approle_policy -
+path "auth/approle/role/aws-cred/role-id" {
+  capabilities = ["read"]
+}
+path "auth/approle/role/aws-cred/secret-id" {
+  capabilities = ["create", "update"]
+}
+EOF
+
  • role-id를 읽기 가능
  • secret-id를 생성 가능

Create Token Role for Jenkins

Jenkins는 Token을 넣어줄 것이므로 Token Role을 생성하여 Entity를 하나로 지정합니다.
Role을 생성하지 않는 경우 일반적인 vault token create -policy=<policy_name> -orphan=true -period=700h 같은 명령어로도 생성할 수 있습니다.

# Get token accessor id
+$ vault auth list -format=json | jq -r '.["token/"].accessor' > accessor_token.txt
+
+# Create entity for Jenkins
+$ vault write -format=json identity/entity name="jenkins-entity" policies="default" \
+     metadata=organization="Company" \
+     metadata=team="Security" \
+     | jq -r ".data.id" > entity_id.txt
+
+# Create alias for Jenkins
+$ vault write identity/entity-alias name="jenkins-alias" \
+     canonical_id=$(cat entity_id.txt) \
+     mount_accessor=$(cat accessor_token.txt) \
+     custom_metadata=account="Security Account"
+
+Key             Value
+---             -----
+canonical_id    4a950f94-3c35-dc0f-cdf3-4f5bc931ae0b
+id              66528d3b-1561-283b-d7d2-33c8b683b49f
+
+# Create role for Jenkins
+$ vault write auth/token/roles/jenkins allowed_policies="approle_policy" orphan=false bound_cidrs="127.0.0.1/32,128.252.0.0/16" renewable=true allowed_entity_aliases="jenkins-alias" token_period="720h"
+
+Success! Data written to: auth/token/roles/jenkins
+
+# Create Token
+$ vault token create -field=token -entity-alias=jenkins-alias -role=jenkins
+hvs.CAESIGkayX80rrAdHR-LychPp6GITGM_DG8Af8VpOY36hdHQGh4KHGh2cy5pNXprSDdOMEVMTEtia0QxYW1YRHF4dmo
+
  • token 은 Jenkins에 저장하는 값
# Test
+$ vault token create -field=token -entity-alias=jenkins-alias -role=jenkins > token.txt
+$ VAULT_TOKEN=$(cat ./token.txt) vault write -f -field secret_id auth/approle/role/aws-cred/secret-id
+
+aae55515-f9ed-a171-dd56-53710ab29018
+

1.2 Nomad Setup

Nomad는 CI/CD 파이프라인 구조 상 배포를 위한 대상을 생성하기위해 구성되었습니다.

Nomad Start Dev mode

nomad agent -dev -alloc-dir=/tmp/nomad/alloc -state-dir=/tmp/nomad/state
+

Nomad Env

Another terminal

export NOMAD_ADDR=http://127.0.0.1:4646
+

Jar file Up/Download Nexus Job

java 드라이버를 사용한 배포에서 빌드된 jar파일을 원격에서 불러와야 하므로 임시 파일 서버를 구성합니다.

cat <<EOF | nomad job run -
+job "fileserver" {
+  datacenters = ["dc1"]
+
+  group "fileserver" {
+    count = 1
+
+    network {
+      port "http" {
+        to = 3000
+        static = 3000
+      }
+    }
+
+    task "fileserver" {
+      driver = "docker"
+
+      config {
+        image = "julienmeerschart/simple-file-upload-download-server"
+        ports = ["http"]
+      }
+    }
+  }
+}
+EOF
+

Upload Test

$ curl -F file=@/tmp/dynamic.properties http://localhost:3000
+{"downloadLink":"http://localhost:3000/file?file=dynamic.properties","curl":"curl http://localhost:3000/file?file=dynamic.properties > dynamic.properties"}
+

2. Jenkins Setup

Download Guide : https://www.jenkins.io/download/open in new window
macOS Install Guide : https://www.jenkins.io/download/lts/macos/open in new window

2.1 Jenkins Install (macOS)

macOS guide : https://www.jenkins.io/download/lts/macos/open in new window

brew install jenkins-lts
+
brew services start jenkins-lts
+

2.2 Unlock Jenkins

Home 디렉토리의 Jenkins 활성화를 위한 패스워드를 다음 경로에서 복사하여 http://localhost:8080open in new window 페이지의 Unlock Jenkins 에 입력

image-20220627095332250
image-20220627095332250

2.3 Jenkins Setup

Customize Jenkins

빠른 시작을 위해 기본 값인 Install suggested plugins 를 클릭

Create First Admin User

계정명, 암호, 이름, 이메일 주소를 기입하고 Save and Continue 버튼 클릭

Instance Configuration

올바른 Jenkins URL을 확인하고 Save and Finish 버튼 클릭

Jenkins is ready!

Start using Jenkins 버튼 클릭

2.4 Jenkins github plugin

GitHub

  1. GitHub 로그인

  2. 우측 상단 사용자 메뉴 클릭 후 Settings 클릭

    GitHub 2022-06-27 12-36-30
    GitHub 2022-06-27 12-36-30
  3. 좌측 메뉴 최하단 Developer settings 클릭

  4. 좌측 메뉴 Personal access tokens 클릭

  5. Generate new token 버튼 클릭

    Personal access tokens
    Personal access tokens
  6. Token 옵션 선택 후 Generate token 클릭

    • Note : 토큰 목적 입력 (e.g. Jenkins Token)
    • Expiration : 기간을 설정 (e.g. No expiration)
    • Select scopes
      • repo
      • admin:org
      • admin:repo_hook
  7. 생성된 토큰을 기록/보관

    Personal access tokens 2022-06-27 12-42-58
    Personal access tokens 2022-06-27 12-42-58

Jenkins

  1. Jenkins 관리 > 시스템 설정 으로 이동

  2. JDK 항목에서 Add JDK 클릭

    • Name : 이름 입력 (e.g. jdk11)
    • JAVA_HOME : 자바 홈 경로 (e.g. /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home)
    image-20220627143641019
    image-20220627143641019
  3. Git 항목에서 Add Git을 클릭

    • Name : 이름 입력 (e.g. local)
    • Path to Git executable : git 실행파일 경로 입력 (e.g. /usr/local/bin/git)
  4. GitHub 항목에서 Add GitHub Server 드롭박스의 GitHub Server를 클릭

    • Name : 이름 입력 (e.g. jenkins_github)
    • API URL : 기본 값 (https://api.github.comopen in new window)
    • Credentials : 아래 +Add 버튼 클릭하여 Jenkins선택 후 새로운 크리덴셜 생성 후 생성된 항목 지정
    • Kind : Secret Test 선택
    • Secret : GitHub에서 생성한 토큰 입력
    • ID : 사용자 지정 (e.g. jenkins_github)
    • Test Connection 버튼으로 연결 확인
    스크린샷 2022-06-27 12.48.18
    스크린샷 2022-06-27 12.48.18
  5. Gradle 항목에서 Add Gradle 클릭

    • Name : 이름 입력 (e.g. gradle)

    • GRADLE_HOME : Gradle 홈 디렉토리 입력 (e.g. /usr/local/Cellar/gradle/7.4.2/libexec)

Vault Credential

  1. Jenkins 관리 > Manage Credentials 로 이동
  2. Domains의 (global) 항목 선택하여 Add credentials 클릭
image-20220627220603230
image-20220627220603230
  • Kind : Secret text

  • Scope : Global

  • ID : 이름 (e.g. vault)

  • Secret: jenkins role 의 토큰

    $ vault token create -field=token -entity-alias=jenkins-alias -role=jenkins
    +hvs.CAESIGkayX80rrAdHR-LychPp6GITGM_DG8Af8VpOY36hdHQGh4KHGh2cy5pNXprSDdOMEVMTEtia0QxYW1YRHF4dmo
    +

3. Sample Java

3.1 App Setting

Spring-boot Initializr

https://start.spring.io/open in new window

image-20220628201916786
image-20220628201916786
  • Project : Gradle Project
  • Language : Java
  • Spring Boot : 2.7.2
  • Packageing : Jar
  • Java : 11
  • Dependencies
    • Spring Web
    • Vault Configuration

App Setup

github : https://github.com/Great-Stone/jenkins-gradle-vault-pipelineopen in new window

build.gradle

<생략> 
+dependencies {
+	implementation 'org.springframework.boot:spring-boot-starter-web'
+	implementation 'org.springframework.cloud:spring-cloud-starter-vault-config'
+	implementation 'org.springframework.cloud:spring-cloud-vault-config-aws'
+	testImplementation 'org.springframework.boot:spring-boot-starter-test'
+}
+<생략>
+

demo>src>main>resources>application.yml

server:
+  port: ${NOMAD_HOST_PORT_http:8080}
+spring:
+  config:
+    import: vault://
+  cloud.vault:
+    enabled: true
+    host: ${VAULT_HOST:127.0.0.1}
+    port: ${VAULT_PORT:8200}
+    scheme: http
+    uri: ${VAULT_URI:http://127.0.0.1:8200/}
+    config:
+      lifecycle:
+        min-renewal: 14m
+        expiry-threshold: 15m
+    authentication: APPROLE
+    app-role:
+      role-id: ${VAULT_ROLE_ID:430111ee-5955-aa83-a53d-924b7e11ac36}
+      secret-id: ${VAULT_SECRET_ID:6db07578-b019-95b4-6741-de4c79cbde39}
+      role: aws-cred
+      app-role-path: approle
+    kv:
+      enabled: false
+    aws:
+      enabled: true
+      role: sts-s3
+      backend: aws
+      credential-type: federation_token
+      access-key-property: cloud.aws.credentials.accessKey
+      secret-key-property: cloud.aws.credentials.secretKey
+      session-token-key-property: cloud.aws.credentials.sessionToken
+      ttl: 900s
+

Set dummy properties & Test

$ export VAULT_TOKEN=root
+$ export VAULT_ROLE_ID=$(vault read -field role_id auth/approle/role/aws-cred/role-id)
+$ export VAULT_SECRET_ID=$(write -f -field secret_id auth/approle/role/aws-cred/secret-id)
+$ gradle bootRun
+...
+2022-06-28 23:23:22.721  INFO 35144 --- [   scheduling-1] com.example.demo.VaultAWSConfiguration   : AwsConfigurationProperties [accessKey=ASIAU3NXDWRUABR7V6UM, secretKey=IBXXM3FXq8Q7qQE2XVUnjPN5lcN8bvsf5bw3TwXX, sessionToken=tokentoken]
+2022-06-28 23:23:24.721  INFO 35144 --- [   scheduling-1] com.example.demo.VaultAWSConfiguration   : AwsConfigurationProperties [accessKey=ASIAU3NXDWRUABR7V6UM, secretKey=IBXXM3FXq8Q7qQE2XVUnjPN5lcN8bvsf5bw3TwXX, sessionToken=tokentoken]
+<==========---> 80% EXECUTING [5s]
+> :bootRun
+

4. Jenkins Pipeline 생성

Pipeline 구성

  1. GitHub checkout
  2. Gradle build
  3. jar upload
  4. Nomad Job Start

  1. 좌측 + 새로운 Item 버튼 클릭

  2. 이름 입력 (e.g. Nomad Job - Vault approve)

  3. Pipeline 선택 후 OK

  4. 성성된 Jenkins Job의 Pipeline에 스크립트 구성

    pipeline {
    +    agent any
    +    triggers {
    +        cron('H */8 * * *') //regular builds
    +        pollSCM('* * * * *') //polling for changes, here once a minute
    +    }
    +    tools { 
    +        git('local')
    +        gradle('gradle')
    +        jdk("jdk11")
    +    }
    +    environment {
    +        NOMAD_ADDR = 'http://localhost:4646'
    +        VAULT_HOST = '127.0.0.1'
    +        VAULT_PORT = '8200'
    +    }
    +    stages {
    +        stage('Clone') {
    +            steps {
    +                git branch: 'main',
    +                    credentialsId: 'jenkins_github',
    +                    url: 'https://github.com/Great-Stone/jenkins-gradle-vault-pipeline'
    +                sh "ls -lat"
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh './gradlew test'
    +            }
    +        }
    +        stage('Build') {
    +            steps {
    +                sh './gradlew build'
    +            }
    +        }
    +        stage('Upload') {
    +            steps {
    +                sh 'mv ./build/libs/demo-0.0.1-SNAPSHOT.jar ./demo-vault-${BUILD_NUMBER}.jar'
    +                sh 'curl -F file=@./demo-vault-${BUILD_NUMBER}.jar http://localhost:3000'
    +            }
    +        }
    +        stage('Nomad Download') {
    +            steps {
    +                sh 'curl -C - --output nomad_1.3.1_darwin_amd64.zip https://releases.hashicorp.com/nomad/1.3.1/nomad_1.3.1_darwin_amd64.zip'
    +                sh 'unzip -o nomad_1.3.1_darwin_amd64.zip'
    +            }
    +        }
    +        stage('Deploy To Nomad') {
    +            steps {
    +                script {
    +                    withCredentials([string(credentialsId: 'vault', variable: 'TOKEN')]) {
    +                        sh '''
    +                        curl -H "X-Vault-Token: ${TOKEN}" -X GET http://${VAULT_HOST}:${VAULT_PORT}/v1/auth/approle/role/aws-cred/role-id | /usr/local/bin/jq -r '.data.role_id' > role_id.txt
    +                        curl -H "X-Vault-Token: ${TOKEN}" -X POST http://${VAULT_HOST}:${VAULT_PORT}/v1/auth/approle/role/aws-cred/secret-id | /usr/local/bin/jq -r '.data.secret_id' > secret_id.txt
    +                        ./nomad job run -var version=${BUILD_NUMBER} -var vault_host=${VAULT_HOST} -var vault_port=${VAULT_PORT} -var role_id=$(cat ./role_id.txt) -var secret_id=$(cat ./secret_id.txt) ./nomad-java.hcl
    +                        '''
    +                    }
    +                }
    +            }
    +        }
    +    }
    +}
    +
  5. 지금 빌드를 클릭하여 빌드를 진행합니다.

    image-20220629001337731
    image-20220629001337731
    image-20220629001415515
    image-20220629001415515
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault-otp.html b/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault-otp.html new file mode 100644 index 0000000000..a3f266a024 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault-otp.html @@ -0,0 +1,89 @@ + + + + + + + + + + jenkins with vault otp | docmoa + + + + + +
본문으로 건너뛰기

jenkins with vault otp

1분 미만vaultjenkinsotptoken

jenkins with vault otp

jenkins와 vault otp를 연동하여 pipe line에서 ssh/scp test

otp 설정은 docmoa의 ssh-otp 참고

vault token 설정

# ssh 권한을 사용 할 policy 생성
+$ tee ssh-policy.hcl <<EOF
+# To list SSH secrets paths
+path "ssh/*" {
+  capabilities = [ "list" ]
+}
+# To use the configured SSH secrets engine otp_key_role role
+path "ssh/creds/otp_key_role" {
+  capabilities = ["create", "read", "update"]
+}
+EOF
+
+#ssh(otp) 정책 생성
+$ vault policy write ssh ssh-policy.hcl
+
+#rest api에서 사용 할 token 생성
+$ vault token create -policy=ssh
+

otp token을 활용하여 jenkins credentials 생성

otp를 사용한 ssh, scp pipe line

// jenkins pipe line v1
+pipeline {
+    agent any
+    environment {
+      // 위에서 생성한 credential id
+      ssh_token = credentials('vault_ssh_token')
+    }
+    options {
+        buildDiscarder(logRotator(numToKeepStr: '20'))
+        disableConcurrentBuilds()
+    }
+    stages{   
+      stage('SSH') {
+          steps{
+            // 1. curl로 받아 온 password를 변수에 담음
+            // 2. ssh에 자동으로 패스워드를 입력하기 위해 sshpass 명령어 추가 사용 
+            // -o StrictHostKeyChecking=no는 최초 로그인에 known_hosts에 등록하는 문구 무시
+            // scp도 동일하게 사용 가능
+            // 주의할점은 다음라인은 jenkins 서버로 돌아온다.
+            sh '''
+            ssh_passwd=$(curl --header "X-Vault-Token: $ssh_token" --request POST --data '{"ip": "172.21.2.56"}' http://172.21.2.50:8200/v1/ssh/creds/otp_key_role  | jq ".data.key" | tr -d '""')
+            sshpass -p $ssh_passwd ssh ubuntu@172.21.2.56 -o StrictHostKeyChecking=no "cd /usr/local \
+            && ls -la \
+            && pwd"
+
+            ssh_passwd=$(curl --header "X-Vault-Token: $ssh_token" --request POST --data '{"ip": "172.21.2.56"}' http://172.21.2.50:8200/v1/ssh/creds/otp_key_role  | jq ".data.key" | tr -d '""')
+            sshpass -p $ssh_passwd scp -o StrictHostKeyChecking=no ~/a ubuntu@172.21.2.56:~/test 
+            '''
+          }
+      }
+    }
+}
+
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault.html b/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault.html new file mode 100644 index 0000000000..d440eada06 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault.html @@ -0,0 +1,142 @@ + + + + + + + + + + jenkins with vault | docmoa + + + + + +
본문으로 건너뛰기

jenkins with vault

1분 미만vaultjenkinsscrectkv

jenkins with vault

jenkins와 vault를 연동하여 pipe line에서 kv 사용하기
이 예제는 진짜 kv까지만 테스트함

# approle 엔진 생성
+$ vault auth enable approle
+# kv2 enable
+$ vault secrets enable kv-v2
+# kv enable
+$ vault secrets enable -path=kv kv
+
+# jenkins 정책으로 될 파일 생성 v1, v2
+$ tee jenkins-policy.hcl <<EOF
+path "kv/secret/data/jenkins/*" {
+  capabilities = [ "read" ]
+}
+path "kv-v2/data/jenkins/*" {
+  capabilities = [ "read" ]
+}
+EOF
+
+#jenkins 정책 생성
+vault policy write jenkins jenkins-policy.hcl
+
+#approle 생성 및 정책 jenkins에 연결
+vault write auth/approle/role/jenkins token_policies="jenkins" \
+token_ttl=1h token_max_ttl=4h
+ 
+#Role id, secret id 가져오기
+
+vault read auth/approle/role/jenkins/role-id
+vault write -f auth/approle/role/jenkins/secret-id
+
+
+vault secrets enable -path=kv kv
+$ tee gitlab.json <<EOF
+{
+  "gitlabIP": "172.21.2.52",
+  "api-key": "RjLAbbWsSAzXoyBvo2qL"
+}
+EOF
+
+tee gitlab-v2.json <<EOF
+{
+  "gitlabIP": "172.21.2.52",
+  "api-key": "RjLAbbWsSAzXoyBvo2qL",
+  "version": "v2"
+}
+EOF
+
+vault kv put kv/secret/data/jenkins/gitlab @gitlab.json
+vault kv put kv-v2/jenkins/gitlab @gitlab-v2.json
+

v1 pipe line

# jenkins pipe line v1
+def secrets = [
+  [path: 'kv%2Fsecret/data/jenkins/gitlab', engineVersion:1, secretValues: [
+    [envVar: 'gitlabIP', vaultKey: 'gitlabIP'],
+    [envVar: 'API_KEY', vaultKey: 'api-key']]],
+]
+def configuration = [vaultUrl: 'http://172.21.2.50:8200',  vaultCredentialId: 'vault-approle', engineVersion: 1]
+
+pipeline {
+    agent any
+    options {
+        buildDiscarder(logRotator(numToKeepStr: '20'))
+        disableConcurrentBuilds()
+    }
+    stages{   
+      stage('Vault') {
+        steps {
+          withVault([configuration: configuration, vaultSecrets: secrets]) {
+            sh "echo $gitlabIP"
+            sh "echo ${env.API_KEY}"
+            sh "curl -v $gitlabIP"
+          }
+        }  
+      }
+    }
+}
+

v2 pipe line

# jenkins pipe line v2
+def secrets = [
+  [path: 'kv-v2/jenkins/gitlab', engineVersion:2, secretValues: [
+    [envVar: 'gitlabIP', vaultKey: 'gitlabIP'],
+    [envVar: 'API_KEY', vaultKey: 'api-key'],
+    [envVar: 'version2', vaultKey: 'version']]]
+]
+
+def configuration = [vaultUrl: 'http://172.21.2.50:8200',  vaultCredentialId: 'vault-approle', engineVersion: 2]
+                      
+pipeline {
+    agent any
+    options {
+        buildDiscarder(logRotator(numToKeepStr: '20'))
+        disableConcurrentBuilds()
+    }
+    stages{   
+      stage('Vault') {
+        steps {
+          withVault([configuration: configuration, vaultSecrets: secrets]) {
+            sh "echo ${env.API_KEY}"
+            sh "echo ${env.version2}"
+            sh "curl -v ${env.gitlabIP}"
+          }
+        }  
+      }
+    }
+}
+
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/mtls.html b/04-HashiCorp/06-Vault/04-UseCase/mtls.html new file mode 100644 index 0000000000..eddb63f56a --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/mtls.html @@ -0,0 +1,451 @@ + + + + + + + + + + Vault PKI - mTLS demo | docmoa + + + + + +
본문으로 건너뛰기

Vault PKI - mTLS demo

약 9 분vaultpkimTLS

Vault PKI - mTLS demo

Demo App Github : https://github.com/Great-Stone/vault-mtls-demoopen in new window

1. mTLS 설명

1.1 SSL과 TLS

SSL(Secure Sokets Layer, 보안 소캣 계층)는 클라이언트와 서버 사이에 전송된 데이터를 암호화 하고 인터넷 연결에 보안을 유지하는 표준 기술이다. 악의적 외부인이 클라이언트와 서버 사이에 전송되는 정보를 확인 및 탈취하는 것을 방지한다.

TLS(Transport Layer Security, 전송 계층 보안)는 현재 더이상 사용되지 않는 SSL을 계승하는 보다 진보된 보안 기술이다. SSL 3.0을 기반으로 만들어졌지만 호환되지는 않는다. 최신 버전은 TLS 1.3이다.

  • TLS 1.2의 경우 암호화 방식과 키 교환 통신이 handshake 과정에 포한되어있어 2회 정도의 추가 요청이 있었다.
  • TLS 1.3에서는 handshake과정을 최소화해 암호화 통신하는 방안이 추가되어 HTTPS 통신에 속도와 보안이 개선되었다.
    • handshake에 0-RTT 모드 추가
    • 정적인 RSA와 Diffie-Hellman Cipher Suite 제거
    • handshake 최대한 암호화
    • 키 교환과 암호화 방식을 Cipher Suite를 통해 묶어서 정하지 않고 개별적 지정

SSL 기술이 TLS로 대체되었다고 하지만 여전히 브라우저 인증서는 SSL 인증서라고 불린다.

1.2 TLS Handshake

TLS에서는 서버에만 TLS 인증서 및 공개/개인 키 쌍이 있고 클라이언트에는 없다. TLS 프로세스는 다음과 같다.

image-20230320091036089
image-20230320091036089

1.3 mutualTLS(mTLS)

mTLS에서는 클라이언트와 서버 모두에 인증서가 있고 양쪽에서 공개/개인 키 쌍을 사용하여 인증한다. TLS 대비 mTLS는 양쪽을 확인하기 위한 추가 단계가 있다.

image-20230320091236622
image-20230320091236622

1.4 mTLS 의 장단점

먼저 mTLS의 장점을 살펴보면,

  • 서버와 클라이언트 간의 상호 인증을 가능하게 하므로, 서버와 클라이언트 모두 신뢰할 수 있는 대상인지 확인할 수 있다. 이를 통해 중간자 공격 및 위조된 인증서와 같은 보안 문제를 방지할 수 있다.
  • mTLS는 암호화된 연결을 통해 전송되는 데이터의 안전성을 보장한다. TLS 프로토콜을 사용하므로, 데이터는 암호화되어 전송되며, 중간자 공격 및 도청과 같은 공격으로부터 안전하게 보호된다.

mTLS의 단점은 다음과 같다.

  • 연결을 설정하는 과정에서 추가적인 CPU 리소스와 대역폭이 필요할 수 있다. 이는 특히 고사양의 서버에서 큰 부담이 될 수 있다.
  • 서버와 클라이언트 모두가 인증서를 발급하고 관리해야 한다는 점이 있다. 인증서를 발급하는 과정은 복잡할 수 있으며, 이를 관리하는 것도 일정한 노력과 비용이 필요합니다.
  • mTLS를 구현하는 것은 애플리케이션과 서버 모두에게 추가적인 복잡성을 요구할 수 있다. 이를 위해 애플리케이션과 서버 모두에 대한 추가적인 설정 및 관리가 필요할 수 있다.

1.5 구성의 예

Python - Flask

from flask import Flask, render_template, request, make_response
+import ssl
+
+app = Flask(__name__)
+
+### APIs ###
+
+if __name__ == "__main__":
+    app.debug = True
+    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile='ca.crt')
+    ssl_context.load_cert_chain(certfile=f'site.crt', keyfile=f'site.key', password='')
+    ssl_context.verify_mode = ssl.CERT_REQUIRED
+    app.run(host="0.0.0.0", port=src_port, ssl_context=ssl_context, use_reloader=True)
+









 
 
 

nginx

# default.conf
+server {
+    listen                  443 ssl;
+
+    access_log /var/log/nginx/access.log;
+    error_log /var/log/nginx/error.log;
+
+    ssl_certificate         /etc/ssl/server.crt;
+    ssl_certificate_key     /etc/ssl/server.key;
+    ssl_protocols           TLSv1.2 TLSv1.3;
+    ssl_client_certificate  /etc/nginx/client_certs/ca.crt;
+    ssl_verify_client       on;
+    ssl_verify_depth        2;
+
+    location / {
+        if ($ssl_client_verify != SUCCESS) { return 403; }
+
+        ### 구성 ###
+    }
+}
+







 
 
 
 
 
 







Apache HTTPD 2.4

<VirtualHost *:80>
+    ServerName {DOMAIN}
+    Redirect permanent / https://{DOMAIN}
+</VirtualHost>
+
+<IfModule mod_ssl.c>
+    <VirtualHost *:443>
+        ServerAdmin info@{DOMAIN}
+        ServerName {DOMAIN}
+
+        Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains;"
+
+        SSLEngine       on
+        SSLCompression      Off
+        SSLProtocol         ALL -SSLv2 -SSLv3
+        SSLHonorCipherOrder     On
+        SSLCipherSuite      EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
+        SSLCertificateFile  {SSL}/fullchain.pem
+        SSLCertificateKeyFile   {SSL}/privkey.pem
+        SSLCACertificateFile    {PATH}/ca.crt
+        SSLStrictSNIVHostCheck  on
+
+        <Location / >
+            SSLVerifyClient     require 
+            SSLVerifyDepth      1
+
+            Options FollowSymLinks
+            AllowOverride None
+        </Location>     
+
+        <Location /health>
+            SSLVerifyClient none
+        </Location>
+
+        ProxyPreserveHost On
+        ProxyRequests off
+        ProxyPass / http://localhost/
+        ProxyPassReverse / http://localhost/
+    </VirtualHost>
+</IfModule>
+

















 
 
 



 

















볼트가 제공하는 PKI 기능과 Agent의 자동 교체 기능을 활용하여 인증서 관리와 발급을 자동화하여 애플리케이션과 서버에 대한 부담을 줄이고 mTLS의 장점을 취할 수 있다.

2. use OpenSSL

OpenSSL을 활용하여 볼트를 사용하지 않고 mTLS를 구현하는 과정을 설명한다.

2.1 Root Key 생성

root ca 생성을 위한 root key를 생성한다.

cd cert
+openssl genrsa -out root.key 2048
+

OS에 따라(Linux/MacOS) 권한 변경이 권장된다.

chmod 600 root.key
+

2.2 Root CA 요청서(CSR) 생성

생성된 root.key 기반의 root ca 인증서 생성을 위한 요청서를 생성한다.

$ openssl req -config ca.conf -extensions usr_cert -new -key root.key -out ca.csr
+
  • -config : 미리 구성해 놓은 ca용 구성 정보를 읽는다.

openssl-xxx.conf sample

구분작성 예
Country Name (국가코드)KR
State or Province Name (시/도의 전체이름)Seoul
Locality Name (시/군/구 등의 이름)Songpa-gu
Organization (회사이름)XXXX
Organization Unit (부서명)Server
Common Name (SSL 인증서를 설치할 서버의 Full Domain)www.xxxx.comopen in new window

Check

$ openssl req -text -in ca.csr
+Certificate Request:
+    Data:
+        Version: 0 (0x0)
+        Subject: C=KR, ST=Seoul, L=Seoul, O=COMPANY, OU=DEV/emailAddress=example@example.com, CN=example root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    <...생략...>
+                Exponent: 65537 (0x10001)
+        Attributes:
+        Requested Extensions:
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+         <...생략...>
+-----BEGIN CERTIFICATE REQUEST-----
+<...생략...>
+-----END CERTIFICATE REQUEST-----
+

2.3 Root CA 인증서 생성

생성된 요청서에 대해 자체 서명(self-signning)한다.

openssl x509 -req -days 3650 -in ca.csr -signkey root.key -extfile ca.ext -out ca.crt
+
  • -days : 인증서 기간은 10년으로 하였다.
  • -extfile : 서명시 추가 정보에 대한 내용을 읽는다.

Check

$ openssl x509 -text -noout -in ca.crt
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            ee:38:a2:de:5e:b2:11:c8
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C=KR, ST=Seoul, L=Seoul, O=COMPANY, OU=DEV/emailAddress=example@example.com, CN=example root
+        Validity
+            Not Before: Mar 15 03:04:58 2023 GMT
+            Not After : Mar 12 03:04:58 2033 GMT
+        Subject: C=KR, ST=Seoul, L=Seoul, O=COMPANY, OU=DEV/emailAddress=example@example.com, CN=example root
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    <...생략...>
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha256WithRSAEncryption
+         <...생략...>
+

생성된 root ca 파일을 시스템에 신뢰할 수 있는 인증서로 등록하면, 브라우저 호출시 신뢰할 수 없는 인증서로 인한 경고 창이 뜨지 않는다.

  • MacOS의 경우 ca.crt를 더블클릭하여 키체인 접근 앱에 인증서 탭에 등록하고, 등록된 example.com인증서를 더블클릭하여 신뢰 항목에서 이 인증서 사용 시항상 신뢰로 변경한다.
  • RedHat 계열 OS의 경우 /etc/pki/ca-trust/source/anchors/ 에 인증서를 복사 한 후, update-ca-trust 명령을 실행한다.
  • Windows의 경우 ca.crt를 더블클릭하여 인증서 창의 인증서 설치...를 클릭, 인증서 가져오기 마법사로 신뢰할 수 있는 인증서로 등록한다.

2.4 서비스 A 용 Key 생성

데모 서비스 A용 인증서를 생성하기 위해 해당 인증서를 위한 key를 생성한다. 생성 시 패스워드를 넣어주며, 패스워드 없는 key를 생성하려는 경우 한번더 풀어주는 과정이 필요하다.

# 패스워드 4자리 이상 입력
+openssl genrsa -aes256 -out service-a-with-pw.key 2048
+# 패스워드 없는 key
+openssl rsa -in service-a-with-pw.key -out service-a.key
+

2.5 서비스 A 용 인증서 요청서(CSR) 생성

서비스 A용 인증서를 위한 요청서를 생성한다.

openssl req -new -key service-a.key -config service-a.conf -out service-a.csr
+
  • -config : 미리 구성해 놓은 서비스 A용 구성 정보를 읽는다.

2.6 서비스용 인증서 생성

자체 서명과정에서 앞서 생성한 root ca 인증서와 key를 넣어 서비스 A인증서가 root ca에 종속되도록 구성한다.

openssl x509 -req -days 365 -in service-a.csr -extfile service-a.ext -CA ca.crt -CAkey root.key -CAcreateserial -out service-a.crt
+$ openssl x509 -text -in service-a.crt
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            ec:71:b0:dd:72:c2:a2:4a
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C=KR, ST=Seoul, L=Seoul, O=COMPANY, OU=DEV/emailAddress=example@example.com, CN=example root
+        Validity
+            Not Before: Mar 15 03:36:06 2023 GMT
+            Not After : Mar 14 03:36:06 2024 GMT
+        Subject: C=KR, ST=Seoul, L=Seoul, O=COMPANY, OU=DEV/emailAddress=example@example.com, CN=service-a.example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                RSA Public-Key: (2048 bit)
+                Modulus:
+                    <..생략..>
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Alternative Name: 
+                DNS:service-a.example.com
+    Signature Algorithm: sha256WithRSAEncryption
+         <..생략..>
+-----BEGIN CERTIFICATE-----
+<..생략..>
+-----END CERTIFICATE-----
+
  • -days : 인증서 기간을 1년으로 하였다.
  • -CA : root ca 인증서를 지정한다.
  • -CAkey : root ca의 key를 지정한다.
  • -CAcreateserial : 서명 작업에 root ca가 인증서에 대한 일련번호 생성
  • -extfile : 서비스 A를 위한 추가 정보

2.7 Service B용 생성

서비스 B에 대한 인증서도 생성한다. 앞서 설명된 내용을 생략하고 아래 커맨드만 나열한다.

openssl genrsa -aes256 -out service-b-with-pw.key 2048
+
+openssl rsa -in service-b-with-pw.key -out service-b.key
+
+openssl req -new -key service-b.key -config service-b.conf -out service-b.csr
+
+openssl x509 -req -days 365 -in service-b.csr -extfile service-b.ext -CA ca.crt -CAkey root.key -CAcreateserial -out service-b.crt
+

3. Demo App (Python)

데모 앱은 Python으로 구성되었다.

3.1 preparation

Python

$ python --version
+Python 3.10.5
+
+$ pip --version
+pip 23.0.1
+
+$ pip install requests flask
+

System : hosts

127.0.0.1   service-a.example.com   service-b.example.com
+

3.2 Run services

Service A

cd python_service_a
+python main.py
+

Service B

cd python_service_b
+python main.py
+

3.3 Test API

Check curl - Service A

Python으로 작성된 flask api server 구성은 다음과 같다.

# main.py
+
+### 생략 ###
+if __name__ == "__main__":
+    app.debug = True
+    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile='../cert/ca.crt')
+    ssl_context.load_cert_chain(certfile=f'../cert/{src}.crt', keyfile=f'../cert/{src}.key', password='')
+    # ssl_context.verify_mode = ssl.CERT_REQUIRED
+    app.run(host="0.0.0.0", port=src_port, ssl_context=ssl_context, use_reloader=True, extra_files=[f'../cert/{src}.crt'])
+
  • ssl.create_default_context : flask에서 사용할 ssl context를 정의한다. 여기 cafile에 root ca 파일을 지정한다.
  • ssl_context.load_cert_chain : cert와 key를 지정하여 인증서 체인을 설정한다.
  • ssl_context.verify_mode : service A는 인증서 검증을 무시할 수 있도록 해당 옵션에 주석처리 한다.
  • app.run(..., extra_files=[f'../cert/{src}.crt']) : 인증서가 변경되면 flask를 다시 시작하도록 구성한다.

서비스 A의 경우 https로 접근할 수 있고, ssl.CERT_REQUIRED 옵션이 활성화 되어있지 않아 신뢰할 수 없는 인증서라도 curl로 --insecure 옵션을 추가하여 응답을 확인할 수 있다. 브라우저에서도 별도의 신뢰 확인을 통해 접근가능하다.

$ curl https://service-a.example.com:7443
+
+curl: (60) SSL certificate problem: self signed certificate in certificate chain
+More details here: https://curl.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.
+$ curl --insecure https://service-a.example.com:7443
+
+Hello from "service-a"% 
+

Check Curl - Service B

Python으로 작성된 flask api server 구성은 다음과 같다.

# main.py
+
+### 생략 ###
+if __name__ == "__main__":
+    app.debug = True
+    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile='../cert/ca.crt')
+    ssl_context.load_cert_chain(certfile=f'../cert/{src}.crt', keyfile=f'../cert/{src}.key', password='')
+    ssl_context.verify_mode = ssl.CERT_REQUIRED
+    app.run(host="0.0.0.0", port=src_port, ssl_context=ssl_context, use_reloader=True, extra_files=[f'../cert/{src}.crt'])
+
  • ssl_context.verify_mode = ssl.CERT_REQUIRED 설정으로 인해 인증서 검증이 반드시 필요하도록 설정한다.

--insecure 옵션을 추가하더라도 서비스 B는 인증서를 요구한다.

$ curl --insecure https://service-b.example.com:8443
+curl: (56) LibreSSL SSL_read: error:1404C45C:SSL routines:ST_OK:reason(1116), errno 0
+

따라서 요청시 root ca, cert(인증서), key를 함께 사용해야 한다.

$ curl --cacert ca.crt --key service-b.key --cert service-b.crt https://service-b.example.com:8443 
+

Normal mTLS Check

서비스 A에서 B로 요청할 때 인증서 모두를 설정한 경우이다. 응답이 정상적으로 오는지 확인한다.

https://service-a.example.com:7443/w-mtlsopen in new window

Without Cert

서비스 A에서 B로 요청할 때 A의 인증정보를 담지 않은 경우이다. 서비스 B에서 인증서를 요구하는 메시지가 출력된다.

https://service-a.example.com:7443/wo-cert-mtlsopen in new window

# 응답
+SSLError(1, '[SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required')
+

Without CA

서비스 A에서 B로 요청할 때 root ca를 포함하지 않는 경우이다. 인증을 위한 자체 서명 인증서를 요구한다.

https://service-a.example.com:7443/wo-ca-mtlsopen in new window

# 응답
+SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain')
+

With 'Expired' Cert - Service A

faketime : https://github.com/wolfcw/libfaketimeopen in new window

faketime을 사용하여 서비스 A의 인증서 만료 기간을 현재시간 이전으로 만든다.

faketime '2023-01-01 00:00:00' /bin/bash -c 'openssl x509 -req -days 30 -in service-a.csr -extfile service-a.ext -CA ca.crt -CAkey root.key -CAcreateserial -out service-a.crt'
+

서비스 A가 보유한 인증서가 만료된 경우 인증서 만료됨을 표기한다. (서비스 B 인증서는 정상)

https://service-a.example.com:7443/w-mtlsopen in new window

# 응답
+SSLError(SSLError(1, '[SSL: SSLV3_ALERT_CERTIFICATE_EXPIRED] sslv3 alert certificate expired')
+

With 'Expired' Cert - Service B

faketime : https://github.com/wolfcw/libfaketimeopen in new window

faketime을 사용하여 서비스 B의 인증서 만료 기간을 현재시간 이전으로 만든다.

faketime '2023-01-01 00:00:00' /bin/bash -c 'openssl x509 -req -days 30 -in service-b.csr -extfile service-b.ext -CA ca.crt -CAkey root.key -CAcreateserial -out service-b.crt'
+

서비스 B가 보유한 인증서가 만료된 경우 인증서 만료됨을 표기한다. (서비스 A 인증서는 정상)

https://service-a.example.com:7443/w-mtlsopen in new window

SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired')
+

With 'Different' Cert - Service A & B

A와 B의 인증서 Root CA가 다른 경우 인증서 서명이 다르므로 요청 실패한다. 아래와 같이 서비스 B를 위한 인증서를 root ca부터 새로 생성한다.

cd cert
+
+openssl genrsa -out root-b.key 2048
+
+chmod 600 root-b.key
+
+openssl req -config ca.conf -extensions usr_cert -new -key root-b.key -out ca-b.csr
+
+openssl x509 -req -days 3650 -in ca-b.csr -signkey root-b.key -extfile ca-b.ext -out ca-b.crt
+
+openssl genrsa -aes256 -out service-b-with-pw.key 2048
+
+openssl rsa -in service-b-with-pw.key -out service-b.key
+
+openssl req -new -key service-b.key -config service-b.conf -out service-b.csr
+
+openssl x509 -req -days 365 -in service-b.csr -extfile service-b.ext -CA ca-b.crt -CAkey root-b.key -CAcreateserial -out service-b.crt
+

python_service_bmain.py에 서 ca 파일 이름을 변경한다.

if __name__ == "__main__":
+    app.debug = True
+    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH, cafile='../cert/ca-b.crt')
+    ssl_context.load_cert_chain(certfile=f'../cert/{src}.crt', keyfile=f'../cert/{src}.key', password='')
+    ssl_context.verify_mode = ssl.CERT_REQUIRED
+    app.run(host="0.0.0.0", port=src_port, ssl_context=ssl_context, use_reloader=True, extra_files=[f'../cert/{src}.crt'])
+

요청 시 서비스 A와 B의 서명이 달라 오류가 발생함을 확인한다.

https://service-a.example.com:7443/w-mtlsopen in new window

SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate signature failure')
+

테스트가 끝났으면 다시 root ca 파일을 원래의 같은 ca.crt 파일로 지정한다.

4. Vault PKI Setup

Vault Download : https://releases.hashicorp.com/vault/open in new window

Vault의 인증서 관리 및 자동화 관리 방안을 설명한다.

4.1 Run Vault

vault server -dev -dev-root-token-id=root
+

4.2 Set env for Vault

export VAULT_ADDR='http://127.0.0.1:8200'
+$ vault login
+
+Token (will be hidden): root
+

4.3 Enable PKI & Setup

PKI 엔진 활성화

vault secrets enable pki
+

PKI 엔진 TTL tuning

Vault 기본 Max TTL은 32일(786h) 이므로 원하는 TTL로 변경한다.

vault secrets tune -max-lease-ttl=87600h pki
+

root CA 생성

vault write pki/root/generate/internal \
+    key_bits=2048 \
+    private_key_format=pem \
+    signature_bits=256 \
+    country=KR \
+    province=Seoul \
+    locality=KR \
+    organization=COMPANY \
+    ou=DEV \
+    common_name=example.com \
+    ttl=87600h
+

CRL 생성

Certificate Revocation List(인증서 해지 목록) 엔드포인트 작성

vault write pki/config/urls \
+    issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \
+    crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
+

Role 생성

미리 Role을 구성해 놓으면 사용자 및 앱은 지정된 규칙에 따라 인증서를 발급받을 수 있다.

vault write pki/roles/example-dot-com \
+    allowed_domains=example.com \
+    allow_subdomains=true \
+    max_ttl=72h
+

발급 테스트

vault write pki/issue/example-dot-com \
+    common_name=service-a.example.com
+

4.4 Vault Agent Setup

vault_agent 디렉토리에서 작업한다.

Vault Agent는 볼트가 가지고 있는 시크릿 정보를 발급 및 TTL 만료 시 자동 갱신해주는 역할을 수행한다.

Policy 추가

Vault Agent가 취득할 정책을 추가한다. 앞서 생성한 PKI 시크릿 엔진에 대한 권한이 설정되어있다.

$ vault policy write pki pki_policy.hcl
+

Vault Agent를 위한 approle인증 추가

$ vault auth enable approle
+Success! Enabled approle auth method at: approle/
+
+$ vault write auth/approle/role/pki-agent \
+    secret_id_ttl=120m \
+    token_ttl=60m \
+    token_max_tll=120m \
+    policies="pki"
+Success! Data written to: auth/approle/role/pki-agent
+
+$ vault read auth/approle/role/pki-agent/role-id
+Key        Value
+---        -----
+role_id    dfa2a248-1e1b-e2e9-200c-69c63b9ca447
+
+$ vault write -f auth/approle/role/pki-agent/secret-id
+Key                   Value
+---                   -----
+secret_id             864360c1-c79f-ea7c-727b-7752361fe1ba
+secret_id_accessor    3cc068e2-a172-2bb1-c097-b777c3525ba6
+

Vault Agent가 사용할 RoleID, SecretID 저장

Vault Agent 실행 시 approle 인증방식을 사용하도록 구성하는 예제로, role_idsecret_id가 필요하다. Vault Agent 재기동시에는 secret_id를 재발급 해야 한다.

$ vault read -field=role_id auth/approle/role/pki-agent/role-id > roleid
+
+$ vault write -f -field=secret_id auth/approle/role/pki-agent/secret-id > secretid
+

Template 확인

Vault Agent는 Template에 따라 시크릿을 특정 파일로 랜더링하는 기능을 갖고 있다.

# ca-a.tpl
+{{- /* ca-a.tpl */ -}}
+{{ with secret "pki/issue/example-dot-com" "common_name=service-a.example.com" "ttl=2m" }}
+{{ .Data.issuing_ca }}{{ end }}
+

위 구문은 pki/issue/example-dot-com 에서 common_name=service-a.example.com 인 인증서를 발급하는 것으로, 테스트를 위해 ttl=2m로 짧게 설정하였다. 볼트로 부터 받는 결과 중에서 issuing_ca 값을 랜더링한다.

vault_agent.hcl에서는 위 Template에 대한 랜더링 결과를 특정 파일로 저장하도록 명시한다.

template {
+  source      = "ca-a.tpl"
+  destination = "../cert/ca.crt"
+}
+

Vault Agent 실행

vault agent -config=vault_agent.hcl -log-level=debug
+

지정된 TTL 간격마다 템플릿 랜더링 로그 확인한다.

...
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) rendering "ca-a.tpl" => "../cert/ca.crt"
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) checking template a04612e63b9a03a45ef968a8984a23db
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) rendering "cert-a.tpl" => "../cert/service-a.crt"
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) checking template 850589d81f7afe64c7c5a0a8440c8569
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) rendering "key-a.tpl" => "../cert/service-a.key"
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) checking template 60e7f2683d2c76a501eb54879bf89ad2
+2023-03-18T22:29:09.312+0900 [DEBUG] (runner) rendering "cert-b.tpl" => "../cert/service-b.crt"
+2023-03-18T22:29:09.333+0900 [INFO] (runner) rendered "cert-b.tpl" => "../cert/service-b.crt"
+2023-03-18T22:29:09.333+0900 [DEBUG] (runner) checking template 1fb22b9f15857b7eeb0b68a3c9ac6d20
+2023-03-18T22:29:09.334+0900 [DEBUG] (runner) rendering "key-b.tpl" => "../cert/service-b.key"
+2023-03-18T22:29:09.354+0900 [INFO] (runner) rendered "key-b.tpl" => "../cert/service-b.key"
+

랜더링이 완료되고, 파일이 갱신되면 Python의 Flask 설정의 extra_files 항목이 변경되므로 재시작되어 인증서를 다시 읽어온다.

 * Detected change in '/vault-examples/mtls-pki/cert/130906523', reloading
+ * Detected change in '/vault-examples/mtls-pki/cert/service-a.crt', reloading
+ * Restarting with watchdog (fsevents)
+ * Debugger is active!
+ * Debugger PIN: 136-647-438
+

변경된 인증서를 확인해보면 갱신된 유효기간을 확인할 수 있고, 브라우저에서도 인증서 보기를 통해 변경된 인증서의 유효기간을 확인할 수 있다.

Monosnap 2023-03-19 14-44-08
Monosnap 2023-03-19 14-44-08

5. Nomad 연계

인증서 같은 시크릿은 파일 형태로 관리되는데, 이런 파일이 변경되면 애플리케이션 또는 웹서버나 솔루션에서 감지하는 구성이 필요하다. 데모 앱인 Python의 Flask에서는 Debug모드에 extra_files에 인증서를 지정하여 변경되는 인증서를 감지하도록 하였으나 이는 운영에서는 권장되지 않는 방식이며 인증서 교체와 함께 watch, reload, restart에 대한 동작이 요구된다.

애플리케이션에서 내부적으로 코드 구현을 통해 이를 교체하는 방법도 있으나, mTLS가 적용되는 코드 전반에 변경이 필요하므로 HasihCorp Nomad같은 Vault 연계된 애플리케이션 오케스트레이터를 활용할 수 있다.

Vault의 인증서 관리 및 자동화 관리 방안을 Nomad와 연계하여 설명한다.

Nomad Download : https://releases.hashicorp.com/nomad/open in new window

  • Vault 서버는 그대로 두고, PKI를 기존것을 사용한다.
  • 서비스 A와 B는 종료한다.
  • Vault Agent는 종료한다.

준비된 Policy 및 Job은 nomad 디렉토리에 있다.

4.1 Nomad Policy를 Vault에 생성 및 Nomad 실행

Nomad 에 부여할 Vault의 정책을 생성한다.

vault policy write nomad-server nomad_policy.hcl
+

Nomad 에서 사용할 Token Role을 생성한다. Nomad에서 허용되는 정책은 앞서 생성한 pki 이다.

vault write auth/token/roles/nomad-cluster allowed_policies="pki" disallowed_policies=nomad-server token_explicit_max_ttl=0 orphan=true token_period="259200" renewable=true
+

생성한 Token Role 기반으로 Nomad와의 설정에 사용할 Token을 하나 발급한다.

vault token create -field token -policy nomad-server -period 72h -orphan > /tmp/token.txt
+

Nomad를 실행한다.

$ nomad agent -dev -vault-enabled=true -vault-address=http://127.0.0.1:8200 -vault-token=$(cat /tmp/token.txt) -vault-tls-skip-verify=true -vault-create-from-role=nomad-cluster
+
+==> No configuration files loaded
+==> Starting Nomad agent...
+==> Nomad agent configuration:
+
+       Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
+            Bind Addrs: HTTP: [127.0.0.1:4646]; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
+                Client: true
+             Log Level: DEBUG
+                Region: global (DC: dc1)
+                Server: true
+               Version: 1.5.1
+
+==> Nomad agent started! Log data will stream in below:
+...
+    2023-03-19T15:34:30.081+0900 [DEBUG] nomad.vault: starting renewal loop: creation_ttl=72h0m0s
+    2023-03-19T15:34:30.082+0900 [DEBUG] nomad.vault: successfully renewed server token
+    2023-03-19T15:34:30.082+0900 [INFO]  nomad.vault: successfully renewed token: next_renewal=35h59m59.999944054s
+...
+

4.2 Set env for Nomad

export NOMAD_ADDR='http://127.0.0.1:4646'
+

4.3 Job spec 설명

Nomad Job을 해석하면 다음과 같다.

job "mtls-service-a" {
+  datacenters = ["dc1"]
+
+  type = "service"
+
+  group "service" {
+    count = 1
+
+    network {
+      port "https" {
+        static = 7433
+      }
+    }
+    
+    # vault에서 할당받을 Polocy를 명시 한다.
+    # 해당 Policy로 생성되는 Token의 변경시 동작은 change_mode에서 지정한다.
+    vault {
+      namespace = ""
+      policies = ["pki"]
+      change_mode = "noop"
+    }
+
+    task "python-task" {
+      driver = "raw_exec"
+
+      config {
+        command = "local/start.sh"
+      }
+      template {
+        data = <<EOH
+#!/bin/bash
+cp -R /Users/gs/workspaces/hashicorp_example/vault-examples/mtls-pki/python_service_a python_service_a
+cd python_service_a
+pip install requests flask
+python main.py
+      EOH
+        destination = "local/start.sh"
+      }
+      
+      # Vault Agent에서 구성했던 Template이 Job내에 정의된다.
+      template {
+        data = <<EOH
+{{- /* ca-a.tpl */ -}}
+{{ with secret "pki/issue/example-dot-com" "common_name=service-a.example.com" "ttl=2m" }}
+{{ .Data.issuing_ca }}{{ end }}
+      EOH
+        destination = "/cert/ca.crt"
+        change_mode = "noop"
+      }
+      # 인증서가 변경되는 경우 change_mode에 지정된 restart를 통해 Job을 재시작한다.
+      template {
+        data = <<EOH
+{{- /* cert-a.tpl */ -}}
+{{ with secret "pki/issue/example-dot-com" "common_name=service-a.example.com" "ttl=2m" }}
+{{ .Data.certificate }}{{ end }}
+      EOH
+        destination = "/cert/service-a.crt"
+        change_mode = "restart"
+      }
+      template {
+        data = <<EOH
+{{- /* key-a.tpl */ -}}
+{{ with secret "pki/issue/example-dot-com" "common_name=service-a.example.com" "ttl=2m" }}
+{{ .Data.private_key }}{{ end }}
+      EOH
+        destination = "/cert/service-a.key"
+        change_mode = "noop"
+      }
+    }
+  }
+}
+

change_mode 의 경우 인증서 변경후 동작을 정의하는데,

  • noop은 아무 동작도 수행하지 않음을 의미한다.
  • restart는 Job을 재시작한다.
  • signal은 system signal을 호출하며, systemctl로 실행되는 프로세스의 경우 SIGHUP을 지정하면 reload 동작이 발생한다.

4.4 Job 실행

앞서 Python을 직접 실행했던것과 같이 Nomad 를 통해 Python을 실행하며, 조건은 동일하다. Flask에서 파일 체크를 위해 추가했던 extra_files는 삭제해도 된다.

nomad job run service_a_job.hcl
+nomad job run service_b_job.hcl
+
Jobs - Nomad 2023-03-19
Jobs - Nomad 2023-03-19

Vault 에서 가져온 인증서가 변경되면 change_mode에 정의된 restart 에 의해 애플리케이션을 자동 재시작 한다.

Task python-task logs - Nomad 2023-03-19 16-55-40
Task python-task logs - Nomad 2023-03-19 16-55-40

5. Consul의 mTLS

Consul에서는 mTLS를 위한 인증서를 각 애플리케이션에서 분리하여 envoy로 구현된 proxy에서 이를 대체한다. 따라서 애플리케이션에는 별도 mTLS 구현이 불필요하며, 인증서 교체를 Consul이 제공하는 proxy가 담당하게 된다.

Consul Service Mesh에서 기본 제공하는 mTLS를 사용하는 경우 장점은

  • 애플리케이션 개발에 mTLS 및 인증서 관리가 불필요하다.
  • Consul 내에서 인증서가 자동 교체된다.
  • mTLS의 서비스 간 인증 외에 Intention과 같은 서비스 요청에 대한 방향성을 지정 가능하다.

단점은 Consul의 Control Plane과 Data Plane을 구분하는 동작으로 인해 추가적인 리소스가 발생한다는 점이다.

Service Mesh Certificate Authority - Overview | Consul | HashiCorp Developer 2023-03-19 17-23-03
Service Mesh Certificate Authority - Overview | Consul | HashiCorp Developer 2023-03-19 17-23-03
Load Balancing Services in Consul Service Mesh with Envoy | Consul | HashiCorp Developer 2023-03-19 17-25-21
Load Balancing Services in Consul Service Mesh with Envoy | Consul | HashiCorp Developer 2023-03-19 17-25-21
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/nomad-integration.html b/04-HashiCorp/06-Vault/04-UseCase/nomad-integration.html new file mode 100644 index 0000000000..5111566c87 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/nomad-integration.html @@ -0,0 +1,432 @@ + + + + + + + + + + Vault & Nomad Integration Test | docmoa + + + + + +
본문으로 건너뛰기

Vault & Nomad Integration Test

약 4 분nomadvaultawsdb

Vault & Nomad Integration Test

Dev Mode 를 활용한 테스트

1. Vault

1.1 Vault Dev Run

vault server -dev -dev-root-token-id=root
+

1.2 Vault Setup

Another terminal

Vault Env

export VAULT_ADDR=http://127.0.0.1:8200
+export VAULT_TOKEN=root
+export NOMAD_POLICY=nomad-server
+

Vault Policy for Nomad

cat <<EOF | vault policy write $NOMAD_POLICY -
+# Allow creating tokens under "nomad-cluster" token role. The token role name
+# should be updated if "nomad-cluster" is not used.
+path "auth/token/create/nomad-cluster" {
+  capabilities = ["update"]
+}
+
+# Allow looking up "nomad-cluster" token role. The token role name should be
+# updated if "nomad-cluster" is not used.
+path "auth/token/roles/nomad-cluster" {
+  capabilities = ["read"]
+}
+
+# Allow looking up the token passed to Nomad to validate # the token has the
+# proper capabilities. This is provided by the "default" policy.
+path "auth/token/lookup-self" {
+  capabilities = ["read"]
+}
+
+# Allow looking up incoming tokens to validate they have permissions to access
+# the tokens they are requesting. This is only required if
+# `allow_unauthenticated` is set to false.
+path "auth/token/lookup" {
+  capabilities = ["update"]
+}
+
+# Allow revoking tokens that should no longer exist. This allows revoking
+# tokens for dead tasks.
+path "auth/token/revoke-accessor" {
+  capabilities = ["update"]
+}
+
+# Allow checking the capabilities of our own token. This is used to validate the
+# token upon startup.
+path "sys/capabilities-self" {
+  capabilities = ["update"]
+}
+
+# Allow our own token to be renewed.
+path "auth/token/renew-self" {
+  capabilities = ["update"]
+}
+EOF
+

Vault Policy for AWS & DB

cat <<EOF | vault policy write aws_policy -
+path "aws/sts/s3" {
+  capabilities = ["read","update"]
+}
+EOF
+
+cat <<EOF | vault policy write db_policy -
+path "db/creds/mysql" {
+  capabilities = ["read","update"]
+}
+EOF
+

Create Token Role

vault write auth/token/roles/nomad-cluster allowed_policies="aws_policy,db_policy" disallowed_policies="$NOMAD_POLICY" token_explicit_max_ttl=0 orphan=true token_period="259200" renewable=true
+

Create Token

vault token create -field token -policy $NOMAD_POLICY -period 72h -orphan > /tmp/token.txt
+# vault token create -field token -role nomad-cluster -period 72h -orphan > /tmp/token.txt
+

2. Nomad

  • Docker 이미지 실행을 위해서는 Nomad 실행 환경에 Docker가 설치되어야 합니다.

  • Java 실행을 위해서는 Nomad 실행 환경에 Java가 설치되어야 합니다.

    $ docker version
    +Client:
    + Version:           20.10.9
    + API version:       1.41
    + ...
    +
    +Server:
    + Engine:
    +  Version:          20.10.14
    +  API version:      1.41 (minimum version 1.12)
    +  ...
    +  
    +$ java -version
    +openjdk version "11.0.14.1" 2022-02-08
    +OpenJDK Runtime Environment Temurin-11.0.14.1+1 (build 11.0.14.1+1)
    +OpenJDK 64-Bit Server VM Temurin-11.0.14.1+1 (build 11.0.14.1+1, mixed mode)
    +

2.1 Nomad Dev Run (Vault Integrated)

nomad agent -dev -vault-enabled=true -vault-address=http://127.0.0.1:8200 -vault-token=$(cat /tmp/token.txt) -vault-tls-skip-verify=true -vault-create-from-role=nomad-cluster
+

2.2. Nomad Env

Another terminal

export NOMAD_ADDR=http://127.0.0.1:4646
+

2.2 Mysql

cat <<EOF | nomad job run -
+job "mysql" {
+  datacenters = ["dc1"]
+
+  type = "service"
+
+  group "mysql-group" {
+    count = 1
+
+    network {
+	    port "db" {
+	      to = 3306
+      	static = 3306
+      }
+    }
+
+    task "mysql-task" {
+      driver = "docker"
+
+      config {
+        image = "mysql:5"
+        ports = ["db"]
+      }
+      
+      env {
+        MYSQL_ROOT_PASSWORD = "rooooot"
+      }
+    }
+  }
+}
+EOF
+

3. Dynamic Secret

3.1 AWS

$ export AWS_ACCESS_KEY=AKIAU3NXDWRUFZSXYRNX
+$ export AWS_SECRET_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+$ export AWS_REGION=ap-northeast-2
+
+$ vault secrets enable aws
+
+$ vault write aws/config/root \
+    access_key=$AWS_ACCESS_KEY \
+    secret_key=$AWS_SECRET_KEY \
+    region=$AWS_REGION
+
+$ vault write aws/roles/s3 \
+    credential_type=federation_token \
+    policy_document=-<<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+     		"s3:PutObject",
+      	"s3:PutObjectAcl"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+
+$ vault write aws/sts/s3 ttl=15m
+
+Key                Value
+---                -----
+lease_id           aws/sts/s3/lasSraK69Ii19tUIzI9yXLnR
+lease_duration     14m59s
+lease_renewable    false
+access_key         ASIAU3NXDWRUOZPCWIGY
+secret_key         FXWXK2xHlBbsHhepuZN2yuN5C8kd7qi2PKyMVf+t
+security_token     IQoJb3JpZ2luX2VjEND//////////wEaDmFwLW5vcnRoZWFzdC0y
+

3.2 DB

$ vault secrets enable -path=db database
+
+$ vault write db/config/my-mysql-database \
+    plugin_name=mysql-database-plugin \
+    connection_url="{{username}}:{{password}}@tcp(127.0.0.1:3306)/" \
+    allowed_roles="mysql" \
+    username="root" \
+    password="rooooot"
+
+$ vault write db/roles/mysql \
+    db_name=my-mysql-database \
+    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON *.* TO '{{name}}'@'%';" \
+    default_ttl="5s" \
+    max_ttl="10s"
+
+$ vault read db/creds/mysql
+
+Key                Value
+---                -----
+lease_id           db/creds/mysql/VuufZZP1NO9thZj4pPnNtPdU
+lease_duration     10s
+lease_renewable    true
+password           WkFTPwWPrCe3yeWQoS--
+username           v-token-mysql-Cy7p0vP6uOYnW7csKz
+

4. Sample Spring-boot

https://start.spring.io/open in new window

Spring Initializr 2022-05-27 22-22-15
Spring Initializr 2022-05-27 22-22-15
  • Spring Web
CLI
curl -G https://start.spring.io/starter.zip \
+    -d type=maven-build
+    -d dependencies=web \
+    -d javaVersion=11 \
+    -o demo.zip
+

4.1 demo app

demo>src>main>resources>application.yml

dynamic:
+  path: ${DYNAMIC_PROPERTIES_PATH:/tmp/dynamic.properties}
+server:
+  port: ${NOMAD_HOST_PORT_http:8080}
+

demo>src>main>java>com>example>demo>DemoApplication.java

package com.example.demo;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+@RestController
+@SpringBootApplication
+@EnableScheduling
+public class DemoApplication {
+
+	private static String FILE_PATH;
+
+	@Value("${dynamic.path}")
+	public void setKey(String value) {
+		FILE_PATH = value;
+	}
+
+	private boolean flag = true;
+
+	public static void main(String[] args) {
+		SpringApplication.run(DemoApplication.class, args);
+	}
+
+	@Scheduled(fixedRate=1000)
+	public void filecheck() throws IOException {
+		List<String> str = Files.readAllLines(Paths.get(FILE_PATH));
+		System.out.println(str);
+	}
+
+	@RequestMapping(method = RequestMethod.GET, path = "/")
+	public String index() throws IOException {
+		List<String> str = Files.readAllLines(Paths.get(FILE_PATH));
+		System.out.println(str);
+
+		return "<h1>AWS</h1>"
+		.concat("<h2>" + str.get(0) + "</h2>")
+		.concat("<h2>" + str.get(1) + "</h2>")
+		.concat("<h2>" + str.get(2) + "</h2>")
+		.concat("<br>")
+		.concat("<h1>MySQL</h1>")
+		.concat("<h2>" + str.get(3) + "</h2>")
+		.concat("<h2>" + str.get(4) + "</h2>");
+	}
+}
+

4.2 Set dummy properties & Test

cat <<EOF> /tmp/dynamic.properties
+aws_access_key=abc
+aws_secret_key=def
+aws_secret_token=ghi
+db_username=user
+db_password=pw
+EOF
+
$ mvn spring-boot:run
+...
+[aws_access_key=abc, aws_secret_key=def, aws_secret_token=ghi, db_username=user, db_password=pw]
+

http://localhost:8080open in new window

image-20220527225739417
image-20220527225739417
cat <<EOF> /tmp/dynamic.properties
+aws_access_key=123
+aws_secret_key=456
+aws_secret_token=789
+db_username=user1
+db_password=pw2
+EOF
+
image-20220527225811346
image-20220527225811346

4.3 build jar

$ mvn install
+...
+[INFO] Building jar: /Users/gs/Downloads/demo/target/demo-0.0.1-SNAPSHOT.jar
+...
+

4.4 build container

$ cat <<EOF> Dockerfile
+FROM amazoncorretto:11
+ARG JAR_FILE=target/demo-0.0.1-SNAPSHOT.jar
+COPY ${JAR_FILE} app.jar
+ENV JAVA_OPTS=""
+CMD java $JAVA_OPTS -server -jar app.jar
+EOF
+
$ docker build -t java/vault .
+Step 1/5 : FROM amazoncorretto:11
+11: Pulling from library/amazoncorretto
+8de5b65bd171: Pull complete 
+6d24904f7237: Pull complete 
+Digest: sha256:34810d3d08456f7e658747d47aec5afc052fcfb2dcadf25db80a51f63086532d
+Status: Downloaded newer image for amazoncorretto:11
+ ---> 299f114f2f6b
+Step 2/5 : ARG JAR_FILE=target/demo-0.0.1-SNAPSHOT.jar
+ ---> Running in 5a0662c5b4a5
+Removing intermediate container 5a0662c5b4a5
+ ---> 608c348e23ac
+Step 3/5 : COPY ${JAR_FILE} app.jar
+ ---> 36d147070bd3
+Step 4/5 : ENV JAVA_OPTS=""
+ ---> Running in 58cb66bb0eab
+Removing intermediate container 58cb66bb0eab
+ ---> f92b3ffeac4d
+Step 5/5 : CMD java $JAVA_OPTS -server -jar app.jar
+ ---> Running in a5d4d1071697
+Removing intermediate container a5d4d1071697
+ ---> 67ae9829dc07
+Successfully built 67ae9829dc07
+Successfully tagged java/vault:latest
+

5. Nomad Job with dynamic secret

  • Nomad Job 명세의 template을 활용하여 Nomad와 연계된 Vault의 시크릿을 작성 할 수 있음

    • File (파일)

    • Env (환경변수) : env 설정이 true인경우

  • Nomad Job에서는 앞서 Vault에서 선언한 nomad-cluster token role에서 정의한 Policy만을 사용할 수 있음

  • change_mode 값이 기본 restart이므로 aws와 db 크리덴셜 같이 ttl 이 적용되는 경우 만료시 자동 갱신되기 때문에 파일과 환경변구 갱신만을 하기 위해서는 noop으로 설정 필요

5.1 Nomad Job Sample Run (Java Driver)

$ cat <<EOF | nomad job run -
+job "java-test" {
+  datacenters = ["dc1"]
+
+  type = "service"
+
+  group "java" {
+    count = 1
+
+    network {
+	    port "http" {} # random port
+    }
+    
+    vault {
+      namespace = ""
+      policies = ["aws_policy","db_policy"]
+      change_mode = "noop"
+    }
+
+    task "java-task" {
+      driver = "java"
+
+      config {
+        jar_path = "/demo/target/demo-0.0.1-SNAPSHOT.jar"
+      }
+      env {
+        DYNAMIC_PROPERTIES_PATH = "local/dynamic.properties"
+      }
+      template {
+        data = <<EOH
+{{- with secret "aws/sts/s3" "ttl=15m" }}
+aws_access_key={{ .Data.access_key | toJSON }}
+aws_secret_key={{ .Data.secret_key | toJSON }}
+aws_secret_token={{ .Data.security_token | toJSON }}
+{{- end }}
+{{- with secret "db/creds/mysql" }}
+db_username={{ .Data.username | toJSON }}
+db_password={{ .Data.password | toJSON }}
+{{- end }}
+      EOH
+				env = true
+				destination = "local/dynamic.properties"
+				change_mode = "noop"
+      }
+    }
+  }
+}
+EOF
+

5.2 Nomad Job Sample Run (Docker Driver)

경고

Nomad Dev 모드에서는 파일시스템 접근권한이 없으므로 Prod 모드 구성 필요

$ cat <<EOF | nomad job run -
+job "docker-test" {
+  datacenters = ["dc1"]
+
+  type = "service"
+
+  group "docker" {
+    count = 1
+
+    network {
+	    port "http" {}
+    }
+    
+    vault {
+      namespace = ""
+      policies = ["aws_policy","db_policy"]
+      change_mode = "noop"
+    }
+
+    task "docker-task" {
+      driver = "docker"
+
+      config {
+        image = "hahohh/java-vault-nomad-demo:0.0.1"
+        ports = ["http"]
+        volumes = [
+          "local:/tmp",
+        ]
+        # auth {
+        #   username = "registry username"
+        #   password = "registry password"
+        # }
+      }
+      env {
+        DYNAMIC_PROPERTIES_PATH = "/local/dynamic.txt"
+      }
+      template {
+        data = <<EOH
+{{- with secret "aws/sts/s3" "ttl=15m" }}
+aws_access_key={{ .Data.access_key | toJSON }}
+aws_secret_key={{ .Data.secret_key | toJSON }}
+aws_secret_token={{ .Data.security_token | toJSON }}
+{{- end }}
+{{- with secret "db/creds/mysql" }}
+db_username={{ .Data.username | toJSON }}
+db_password={{ .Data.password | toJSON }}
+{{- end }}
+      EOH
+				env = true
+				destination = "local/dynamic.txt"
+				change_mode = "noop"
+      }
+    }
+  }
+}
+EOF
+
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/sentinel-check-identity-cidr.html b/04-HashiCorp/06-Vault/04-UseCase/sentinel-check-identity-cidr.html new file mode 100644 index 0000000000..39cf26c3d5 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/sentinel-check-identity-cidr.html @@ -0,0 +1,141 @@ + + + + + + + + + + Sentinel - (Identity & CIDR) | docmoa + + + + + +
본문으로 건너뛰기

Sentinel - (Identity & CIDR)

약 1 분vaultsentinelcidrenterprise

Sentinel - (Identity & CIDR)

Enterprise 기능

Token Role에 bound_cidr을 적용하거나 여타 인증(AppRole, Userpass 등)에 허용하는 cidr을 적용하는 경우 다시 Token을 발급하거나 인증받지 않는한은 cidr을 기반으로한 차단을 동적으로 적용할 수 없다.

이경우 Sentinel을 사용하여 동적인 정책을 적용할 수 있다. Sentinel은 ACL방식의 기존 Policy와는 달리 Path가 아닌 다른 검증 조건을 추가할 수 있다.

테스트 사용자 생성

Sentinel 적용을 확인하기 위해 모든 권한이 있는 기존 Policy 방식의 정책을 생성한다.

vault policy write super-user - << EOF
+path "*" {
+capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+EOF
+

생성한 정책고 앞으로 생성할 Sentinel 정책이 포함된 사용자를 생성한다.

vault write auth/userpass/users/admin password=password policies="super-user, test-rgp"
+vault write auth/userpass/users/rgp password=password policies="super-user, test-rgp"
+

adminrgp 사용자 모두 동일한 정책을 부여 받았다. Sentinel에서는 identity 정보를 기반으로 조건을 부여할 수 있으며, 동일한 정책이 부여되었더라도 어떤 identity 인가에 따라 적용 여부를 선택적으로 검증할 수 있다.

각 사용자로 로그인하여 Token 정보를 확인하면 entity_id 값을 확인할 수 있다.

$ TOKEN=$(vault login -field=token -method userpass username=admin password=password)
+
+$ vault token lookup $TOKEN
+
+Key                 Value
+---                 -----
+display_name        userpass-admin
+entity_id           17230158-d0ad-dd6d-b749-3c7de9e2b4cf
+policies            [default super-user test-rgp]
+renewable           true
+ttl                 768h
+

Sentinel 생성

다음과 같이 적용할 identity-cidr-check.sentinel 파일을 생성한다. (확장자는 다른 확장자를 사용해도 무방하다. e.g. hcl)

import "sockaddr"
+import "strings"
+
+print(identity.entity.id)
+print(request.connection.remote_addr)
+
+precond = rule {
+    # admin user
+    # identity.entity.id is "17230158-d0ad-dd6d-b749-3c7de9e2b4cf" or
+    # rgp user
+    identity.entity.id is "31cc28c0-9fd0-82b3-a70d-0eef741c5349"
+}
+
+cidrcheck = rule {
+    ## Loopback
+    # sockaddr.is_contained("127.0.0.0/8", request.connection.remote_addr) or 
+    sockaddr.is_contained("22.32.4.0/24", request.connection.remote_addr)
+}
+
+main = rule when precond {
+    cidrcheck
+}
+
  • precond : main 규칙에서 조건으로 부여할 규칙을 정의한다.
    • identity.entity.id로 검증할 아이디 내용에는 앞서 확인한 adminrgp 사용자의 entity_id를 조건에 넣는다.
    • admin 사용자의 경우 우선 주석처리하여 진행한다.
  • cidrcheck : cidr을 검증할 규칙을 정의한다.

적용하는 방식은 다음과 같다.

POLICY=$(base64 identity-cidr-check.sentinel)
+
+vault write sys/policies/rgp/test-rgp \
+      policy="${POLICY}" \
+      enforcement_level="hard-mandatory"
+

Sentinel 검증

admin 사용자로 로그인하여 kv를 생성하고 값을 넣는다.

$ vault login -method userpass username=admin password=password
+$ vault secrets enable kv
+$ vault kv put kv/hello foo=bar
+$ vault kv get kv/hello
+=== Data ===
+Key    Value
+---    -----
+foo    bar
+

rgp 사용자로 로그인하여 kv를 조회해본다.

$ vault login -method userpass username=rgp password=password
+$ vault kv get kv/hello
+Error making API request.
+
+URL: GET http://127.0.0.1:8200/v1/sys/internal/ui/mounts/kv/hello
+Code: 400. Errors:
+
+* 2 errors occurred:
+	* rgp standard policy "root/test-rgp" evaluation resulted in denial.
+
+The specific error was:
+<nil>
+
+A trace of the execution for policy "root/test-rgp" is available:
+
+Result: false
+
+Description: <none>
+
+print() output:
+
+31cc28c0-9fd0-82b3-a70d-0eef741c5349
+127.0.0.1
+
+
+Rule "main" (root/test-rgp:19:1) = false
+Rule "cidrcheck" (root/test-rgp:14:1) = false
+Rule "precond" (root/test-rgp:7:1) = true
+	* permission denied
+

cidrcheck 에서 검증하는 cidr에 속하지 못하면 요청 단계에서 권한이 없음을 표기한다.

identity 동적으로 변경하기

앞서 작성한 sentinel 규칙에서 admin 사용자의 identity.id의 주석을 해제하여 다시 적용해 본다.

...생략...
+
+precond = rule {
+    # admin user
+    identity.entity.id is "17230158-d0ad-dd6d-b749-3c7de9e2b4cf" or
+    # rgp user
+    identity.entity.id is "31cc28c0-9fd0-82b3-a70d-0eef741c5349"
+}
+
+...생략...
+




 





admin 사용자로 로그인하여 kv를 조회해도 cidr 조건에 맞지 않으면 동일한 오류가 발생한다.

cidr 동적으로 변경하기

허용하는 cidr을 추가해본다. 로컬에서 테스트하는 경우 127.0.0.1이 해당 ip가 될 수 있다.

...생략...
+
+cidrcheck = rule {
+    ## Loopback
+    sockaddr.is_contained("127.0.0.0/8", request.connection.remote_addr) or 
+    sockaddr.is_contained("22.32.4.0/24", request.connection.remote_addr)
+}
+
+...생략...
+



 





적용 후 admin 사용자와 rgp 사용자 모두 정상적으로 kv의 값을 확인할 수 있다.

+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/spring-boot.html b/04-HashiCorp/06-Vault/04-UseCase/spring-boot.html new file mode 100644 index 0000000000..1bdccc5f63 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/spring-boot.html @@ -0,0 +1,395 @@ + + + + + + + + + + Vault로 Spring Boot 구성관리 | docmoa + + + + + +
본문으로 건너뛰기

Vault로 Spring Boot 구성관리

약 5 분vaultjavaspring

Vault로 Spring Boot 구성관리

Example Source : https://github.com/Great-Stone/vault_springboot_exampleopen in new window

볼트는 애플리케이션(앱)의 구성관리, 특히 사용자 ID, 패스워드, Token, 인증서, 엔드포인트, AWS 자격증명 등과 같은 민감한 정보를 안전하게 저장하는 중앙 집중식 인프라를 제공한다. 서비스의 성장과 더불어, 이를 구성하는 앱은 확장과 분리 요구 사항이 발생하면 구성 관리가 어려워 진다. 특히, 시크릿 정보가 포함되는 구성 관리는 수동으로 관리하는 경우 로컬 환경을 포함한 여러 시스템에 노출되는 위험성을 갖고, 환경마다 다른 시크릿을 관리하기위한 유지 관리의 노력과 비용이 증가한다. 볼트에서 이야기하는 앱과 관련한 "시크릿 스프롤(퍼짐)" 현상은 다음과 같다.

  • 시트릿의 위치가 파일 서버, Git 저장소, 로컬 환경, 앱 실행 환경등 다양한 곳에 존재하고 추적이 어려움
  • 분산 서비스 및 Scale out/in 되는 앱 환경에 구성 설정의 변경 시 개별적 관리 필요
  • 스크릿 사용의 위반 추적이 어렵고 거버넌스와 규제 요구사항에 대한 통제

본질적으로 시크릿 스프롤은 가시성과 통제력의 저하를 야기한다.

구성관리 개념

앱과 구성 관계에서 구성관리의 원칙은 다음과 같다.

  • 구성은 앱과 분리되어야 한다.
  • 앱은 한번 빌드되면 로직의 변화가 없는 경우 그대로 배포되어야 한다.
  • 구성의 변경 사항은 앱의 런타임시 주입되어야 한다.
  • 구성은 중앙집중화 되어 강력한 감사와 접근제어가 동반되어야 한다.
  • 민감한 구성은 암호화 되어야 한다.
  • 12 factor 앱 모범 사례에 따라, 중앙 저장소의 구성 데이터를 사용할 수 있도록(부트스트래핑) 앱을 설계해야 한다.

볼트의 구성관리 저장소 및 관리

볼트는 구성 요소에 대해 중앙 저장소를 제공하며 다음과 같은 주요 이점이 있다.

  • 중앙 집중식 구성 저장소
  • 저장되는 데이터의 암호화 저장
  • KV 형태의 구성 저장 및 버전 관리
  • 정책 기반 접근관리
  • 감사 기능
  • 저장 및 인증을 위한 플러그인 기반
  • 동적 시크릿 발급 및 수명주기 관리
  • 고가용성(HA) 아키텍처 제공
  • 정적 구성 요소 관리를 위한 템플릿팅 지원 (xml, json 등)
  • 다중 클러스터 간 복제

[Example 1. Spring Boot Application]

앱을 위한 볼트 구성을 위해 다음과 같이 볼트를 실행한다.

$ vault server -dev -dev-root-token-id=root -log-level=trace
+
+...
+You may need to set the following environment variables:
+
+    $ export VAULT_ADDR='http://127.0.0.1:8200'
+
+The unseal key and root token are displayed below in case you want to
+seal/unseal the Vault or re-authenticate.
+
+Unseal Key: UTZ7HoZCu8dtWa/eSMKcwq1klhC/qFoDxHXmhRn4qnE=
+Root Token: root
+

root 토큰은 구성관리 관리자의 권한으로 가정한다.

$ export VAULT_ADDR='http://127.0.0.1:8200'
+$ vault login
+Token (will be hidden): root
+
+Success! You are now authenticated. The token information displayed below
+is already stored in the token helper. You do NOT need to run "vault login"
+again. Future Vault requests will automatically use this token.
+
+Key                  Value
+---                  -----
+token                root
+token_accessor       w5LvrjTvDDcfjPHrnOj6ib7E
+token_duration       ∞
+token_renewable      false
+token_policies       ["root"]
+identity_policies    []
+policies             ["root"]
+

Spring Boot 앱에서 사용할 KV를 활성화 한다.

$ vault secrets enable -path=demo-app -version=2 kv
+
+Success! Enabled the kv secrets engine at: demo-app/
+

예제에서는 구성관리에서 MySQL 정보를 관리한다고 가정합니다. 관련 Spring Boot 앱은 spring initializropen in new window를 통해 생성한다.

image-20230406150032627
image-20230406150032627

테스트를 위한 종속성 목록은 다음과 같다.

Dependencies설명
Spring WebSpring MVC를 사용하여 RESTful을 포함한 웹 애플리케이션 구축에 사용
MySQL DriverMySQL을 사용하기위한 드라이버 (MySQL 없는 경우 생략)
Spring Data JPAJPA를 사용하기 편하도록 만들어놓은 모듈 (MySQL 없는 경우 생략)
Vault Configuration분산 시스템에서 외부화된 볼트 구성에 대한 클라이언트 측 지원을 제공
Lombok기계적인 코드들을 어노테이션을 기반으로 코드를 자동화하여 작성해주는 Java의 라이브러리

MySQL의 경우 다음과 같이 구성한다.

CREATE DATABASE java_dev_db;
+CREATE USER 'dev-user'@'%' IDENTIFIED BY 'dev-password';
+GRANT ALL PRIVILEGES ON java_dev_db.* TO 'dev-user'@'%';
+

앱에서 사용할 구성을 볼트의 demo-app/java_and_vault/dev에 추가한다. 엔드포인트 정보의 조합은 <kv_endpoint>/<app_name>/<profile> 이다. 다음과 같이 CLI를 사용하여 구성 정보를 추가한다.

$ vault kv put demo-app/java_and_vault/dev \
+		app.config.auth.token=MY-AUTH-TOKEN-DEV-0000 \
+		app.config.auth.username=dev-user \
+		spring.datasource.database=java_dev_db \
+		spring.datasource.password=dev-password \
+		spring.datasource.username=dev-user
+

UI에서 확인해보면 결과는 다음과 같다.

image-20230407093720360
image-20230407093720360

앱과 볼트 연동 구성을 위해 다음을 추가한다. 기존 application.properties 대신 application.yml로 변경하여 구성한다.

spring:
+  application:
+    name: java_and_vault
+  cloud.vault:
+      host: 127.0.0.1
+      port: 8200
+      scheme: http
+      config:
+        lifecycle:
+          enabled: false
+      authentication: TOKEN
+      token: root
+      kv:
+        enabled: true
+        backend: demo-app
+        profile-separator: '/'
+      generic:
+        enabled: false
+  config:
+    import: vault://
+  datasource:
+    url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.database}
+
  • spring.cloud.vault 에 볼트 관련 설정이 추가된다.
    • host : 볼트 서버의 호스트이름 또는 IP를 설정한다.
    • port : 볼트 서버의 포트를 설정한다.
    • scheme : 볼트 서버와의 통신에 사용할 프로토콜을 설정한다.
    • config.lifecycle.enabled의 경우 동적인 시크릿에 대한 생명주기 관리 동작 여부를 설정한다. 여기서는 정적인 구성을 사용하므로 false로 설정한다.
  • spring.cloud.vault.authentication은 관리자 테스트를 위해 TOKEN으로 입력한다.
  • spring.cloud.vault.token은 관리자용 인증인 root를 입력한다.
  • spring.cloud.vault.kv는 활성화한 KV 의 선언을 위한 계층이다.
    • enalbed : 활성화 여부를 boolean 값으로 설정한다.
    • backend : KV가 활성화된 엔드포인트 경로 이름을 입력한다. 기본 값은 secret이다.
  • spring.cloud.vault.generic은 v1 타입의 KV 선언을 위한 계층이다.
    • enalbed : 활성화 여부를 boolean 값으로 설정한다. 사용되지 않으므로 false로 설정한다.
  • spring.config.importvault://를 지정하여 볼트를 PropertySource로 마운트한다.
  • spring.datasource에서 MySQL 연동관련 정의를 설정한다.
    • url : DB Connection Url을 명시한다.
    • database : DB의 이름을 정의한다. 여기서는 볼트에서 해당 값을 가져온다.
    • username : DB 계정 사용자 이름을 정의한다. 여기서는 볼트에서 해당 값을 가져오므로 생략되었다.
    • password : DB 계정 사용자 패스워드를 정의한다. 여기서는 볼트에서 해당 값을 가져오므로 생략되었다.

기본 패키지 경로(e.g. src/main/java/com/example/demo)에 다음의 Java 파일을 추가한다.

| AppConfiguration.java

package com.example.demo;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import lombok.Getter;
+import lombok.Setter;
+@Getter
+@Setter
+@Configuration
+@ConfigurationProperties("app.config.auth")
+public class AppConfiguration {
+    private String username;
+    private String token;
+}
+
  • @ConfigurationProperties에 정의한 app.config.auth로 마운팅된 볼트의 내용을 주입한다.
  • AppConfiguration 클래스는 어노테이션 정의에 따라 볼트로부터 내부에 정의되는 변수 usernametoken 값이 할당된다.

| AppService.java

package com.example.demo;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import javax.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AppService {
+    private final AppConfiguration appConfiguration;
+    @PostConstruct
+    public void readConfigs() {
+        log.info("Reading configuration {} - {}", appConfiguration.getToken(), appConfiguration.getUsername());
+    }
+}
+
  • readConfigs() 메소드에 로그 출력에서 볼트로부터 할당된 변수 값을 확인한다.

| DemoApplication.java

package com.example.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import javax.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@SpringBootApplication
+public class DemoApplication {
+
+	@Value("${spring.datasource.username}")
+	private String ds_name;
+
+	@Value("${spring.datasource.password}")
+	private String ds_pw;
+
+	public static void main(String[] args) {
+		SpringApplication.run(DemoApplication.class, args);
+	}
+
+	@PostConstruct
+	public void readDBconfigs() {
+			log.info("Reading datasource config {} - {}", ds_name, ds_pw);
+	}
+}
+
  • @Value 로 볼트에서 가져오는 구성정보가 application.yml에 정의되어야 하는 구성 정보에 주입된 값을 받아온다.
  • readDBconfigs() 메소드에 로그 출력에서 볼트로부터 할당된 구성 값을 확인한다.

앱을 실행하여 구성을 가져오는지 확인한다.

$ gradle bootRun --args='--spring.profiles.active=dev'
+
+> Task :bootRun
+
+  .   ____          _            __ _ _
+ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
+( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
+ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
+  '  |____| .__|_| |_|_| |_\__, | / / / /
+ =========|_|==============|___/=/_/_/_/
+ :: Spring Boot ::                (v3.0.5)
+
+# dev profile이 사용됨을 표기
+2023-04-06T17:15:58.395+09:00  INFO 48275 --- [           main] 
+com.example.demo.DemoApplication         : The following 1 profile is active: "dev"
+
+# 앱 구성의 spring.datasource 에서 정의하는 정보가 볼트에서 가져와서 실행되어 Connection Pool이 생성되고, 가져온 계정 정보가 출력됨을 확인
+2023-04-06T17:16:00.359+09:00  INFO 48275 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
+2023-04-06T17:16:00.614+09:00  INFO 48275 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@57416e49
+2023-04-06T17:16:00.616+09:00  INFO 48275 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
+...
+2023-04-07T08:57:39.888+09:00  INFO 52598 --- [           main] com.example.demo.DemoApplication         : Reading datasource config dev-user - dev-password
+
+# 앱 구성 app.config.auth 항목을 볼트에서 가져와서 출력됨을 확인
+2023-04-06T17:16:01.363+09:00  INFO 48275 --- [           main] com.example.demo.AppService              : Reading configuration MY-AUTH-TOKEN-DEV-0000 - dev-user
+

[Example 2. Spring Boot Application + RBAC]

Example 1에서는 볼트의 루트 사용자를 사용하여 모든 구성 값을 확인할 수 있지만 앱과 이를 배포하는 사람, 파이프라인은 특정 구성에 대한 정보만 확인할 수 있어야 한다. 여기서는 prd 프로파일을 위한 구성과 정책 정의에 대해 확인한다.

MySQL의 경우 다음과 같이 구성한다.

CREATE DATABASE java_prd_db;
+CREATE USER 'prd-user'@'%' IDENTIFIED BY 'prd-password';
+GRANT ALL PRIVILEGES ON java_prd_db.* TO 'prd-user'@'%';
+

prd를 위한 구성정보를 볼트에 추가한다.

$ vault kv put demo-app/java_and_vault/prd \
+		app.config.auth.token=MY-AUTH-TOKEN-prd-1111 \
+		app.config.auth.username=prd-user \
+		spring.datasource.database=java_prd_db \
+		spring.datasource.password=prd-password \
+		spring.datasource.username=prd-user
+
image-20230407094227005
image-20230407094227005

구성 관리자를 위한 Policy java-and-vault-prd-admin.hcl파일 내용 및 적용은 다음과 같다.

$ cat java-and-vault-prd-admin.hcl
+
+path "demo-app/data/java_and_vault/prd" {
+  capabilities = ["create", "update", "read"]
+}
+
+$ vault policy write java-and-vault-prd-admin java-and-vault-prd-admin.hcl
+
+Success! Uploaded policy: java-and-vault-prd-admin
+

구성을 읽을수만 있는 Policy java-and-vault-prd-read.hcl파일 내용 및 적용은 다음과 같다.

$ cat java-and-vault-prd-read.hcl
+
+path "demo-app/data/java_and_vault/prd" {
+  capabilities = ["read"]
+}
+
+$ vault policy write java-and-vault-prd-read java-and-vault-prd-read.hcl
+
+Success! Uploaded policy: java-and-vault-prd-read
+

앱을 위한 계정을 발급하기위한 Policy인 java-and-vault-prd-approle.hcl 파일 내용은 다음과 같다.

$ cat java-and-vault-prd-approle.hcl
+
+path "auth/approle/role/java-vault-prd/role-id" {
+  capabilities = ["read"]
+}
+
+path "auth/approle/role/java-vault-prd/secret-id" {
+  capabilities = ["create", "update"]
+}
+
+$ vault policy write java-and-vault-prd-approle java-and-vault-prd-approle.hcl
+
+Success! Uploaded policy: java-and-vault-prd-approle
+

관리자에게 java-and-vault-prd-admin, java-and-vault-prd-approle 를 부여하여 구성에 대한 관리와 앱을위한 계정 발급 권한을 준다.

# 활성화 되어있지 않다면 userpass Auth Method 활성화
+$ vault auth enable userpass
+
+Success! Enabled userpass auth method at: userpass/
+
+$ vault write auth/userpass/users/app-prd-admin password=password policies=java-and-vault-prd-admin,java-and-vault-prd-approle
+
+Success! Data written to: auth/userpass/users/app-prd-admin
+

앱을 위한 AppRole인증에 java-and-vault-prd-read를 추가한다.

# 활성화 되어있지 않다면 approle Auth Method 활성화
+$ vault auth enable approle
+
+Success! Enabled approle auth method at: approle/
+
+$ vault write auth/approle/role/java-vault-prd \
+    secret_id_ttl=10m \
+    token_period=24h \
+    policies="java-and-vault-prd-read"
+    
+Success! Data written to: auth/approle/role/java-vault-prd
+

생성한 관리자 계정으로 로그인 하면 demo-app/java_and_vault/prd 의 구성 변경과 AppRole 계정의 secret-id 발급이 가능한지 확인한다. (별도의 터미널)

$ export VAULT_ADDR=http://127.0.0.1:8200
+$ vault login -method userpass username=app-prd-admin password=password
+
+Success! You are now authenticated. The token information displayed below
+is already stored in the token helper. You do NOT need to run "vault login"
+again. Future Vault requests will automatically use this token.
+
+Key                    Value
+---                    -----
+token                  hvs.CAESIAE31Vrf91UbPhV5O0eh8KM0Tky_7MGk5ThyRu4tJbhUGh4KHGh2cy50ZDdZZ09BdDRnRmpqdkVRcUJYOWR5YUI
+token_accessor         9XuvRw1jKWt99iwlZ146652v
+token_duration         768h
+token_renewable        true
+token_policies         ["default" "java-and-vault-prd-admin" "java-and-vault-prd-approle"]
+identity_policies      []
+policies               ["default" "java-and-vault-prd-admin" "java-and-vault-prd-approle"]
+token_meta_username    app-prd-admin
+
+$ vault kv put demo-app/java_and_vault/prd \
+    app.config.auth.token=MY-AUTH-TOKEN-prd-1111 \
+    app.config.auth.username=prd-user \
+    spring.datasource.database=java_prd_db \
+    spring.datasource.password=prd-password \
+    spring.datasource.username=prd-user
+    
+========== Secret Path ==========
+demo-app/data/java_and_vault/prd
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-04-07T01:54:45.464698Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            2
+
+$ vault read auth/approle/role/java-vault-prd/role-id
+
+Key        Value
+---        -----
+role_id    53b96749-1234-fec1-05b8-760c29991d89
+
+$ vault write -f auth/approle/role/java-vault-prd/secret-id
+
+Key                   Value
+---                   -----
+secret_id             69b144ae-543a-81e3-9afa-8b290d8efd75
+secret_id_accessor    d9338290-f1ff-ca09-fbaf-742071afeaa6
+secret_id_num_uses    0
+secret_id_ttl         10m
+

앱에서 사용할 AppRole 계정으로 로그인 하면 demo-app/java_and_vault/prd 의 구성 변경을 읽을수는 있고 업데이트는 안되는 여부를 확인한다. (별도의 터미널)

$ export VAULT_ADDR=http://127.0.0.1:8200
+$ vault write auth/approle/login \
+    role_id=53b96749-1234-fec1-05b8-760c29991d89 \
+    secret_id=aebbc4ac-79e4-c529-8751-c52f2f31a3d7
+
+Key                     Value
+---                     -----
+token                   hvs.CAESIC7bpDI_cDGLCpKl6rZ
+token_accessor          guDRqHNpnJtpmFXqkqsahc2e
+token_duration          24h
+token_renewable         true
+token_policies          ["default" "java-and-vault-prd-read"]
+identity_policies       []
+policies                ["default" "java-and-vault-prd-read"]
+token_meta_role_name    java-vault-prd
+
+# 앱용 계정은 부여된 권한에 읽기 권한이 있으므로 정보 확인
+$ VAULT_TOKEN=hvs.CAESIC7bpDI_cDGLCpKl6rZ vault kv get demo-app/java_and_vault/prd
+
+========== Secret Path ==========
+demo-app/data/java_and_vault/prd
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-04-07T01:54:45.464698Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            2
+
+=============== Data ===============
+Key                           Value
+---                           -----
+app.config.auth.token         MY-AUTH-TOKEN-prd-1111
+app.config.auth.username      prd-user
+spring.datasource.database    java_prd_db
+spring.datasource.password    prd-password
+spring.datasource.username    prd-user
+
+# 앱용 계정은 부여된 권한에 쓰기 권한이 없으므로 관련 요청시 권한 거부
+$ VAULT_TOKEN=hvs.CAESIC7bpDI_cDGLCpKl6rZ vault kv put demo-app/java_and_vault/prd \
+    app.config.auth.token=MY-AUTH-TOKEN-prd-2222
+
+Error writing data to demo-app/data/java_and_vault/prd: Error making API request.
+
+URL: PUT http://127.0.0.1:8200/v1/demo-app/data/java_and_vault/prd
+Code: 403. Errors:
+
+* 1 error occurred:
+	* permission denied
+

앱과 정책이 적용된 볼트 연동 구성을 위해 application.yml를 수정한다.

spring:
+  application:
+    name: java_and_vault
+  cloud.vault:
+      host: 127.0.0.1
+      port: 8200
+      scheme: http
+      config:
+        lifecycle:
+          enabled: false
+      # authentication: TOKEN
+      # token: root
+      authentication: APPROLE
+      app-role:
+        role-id: 53b96749-1234-fec1-05b8-760c29991d89
+        secret-id: aebbc4ac-79e4-c529-8751-c52f2f31a3d7
+        role: db-kv-reader
+        app-role-path: approle
+      kv:
+        enabled: true
+        backend: demo-app
+        profile-separator: '/'
+      generic:
+        enabled: false
+  config:
+    import: vault://
+  datasource:
+    url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.database}
+
  • spring.cloud.vault.authentication은 앱용 인증으로 생성한 방식인 APPROLE을 설정한다.
  • spring.cloud.vault.authentication.app-roleAPPROLE인증에 대한 선언을 위한 계층이다.
    • role-id : 발급한 role-id를 설정한다.
    • secret-id : 발급한 secret-id를 설정한다. secret-id는 제한시간이 10m이였으므로, 배포시마다 교체해주어 계정을 보호한다.
    • role : role-id가 포함된 Approle의 이름을 설정한다.
    • app-role-path : 활성화된 Approle의 엔드포인트 경로 이름을 입력한다.

앱을 실행하여 구성을 가져오는지 확인한다. prd 프로파일을 지정한다.

$ gradle bootRun --args='--spring.profiles.active=prd'                                                                      
+
+> Task :bootRun
+
+  .   ____          _            __ _ _
+ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
+( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
+ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
+  '  |____| .__|_| |_|_| |_\__, | / / / /
+ =========|_|==============|___/=/_/_/_/
+ :: Spring Boot ::                (v3.0.5)
+
+# prd profile이 사용됨을 표기
+2023-04-07T14:05:03.395+09:00  INFO 67782 --- [           main] com.example.demo.DemoApplication         : The following 1 profile is active: "prd"
+
+# 앱 구성의 spring.datasource 에서 정의하는 정보가 볼트에서 가져온 계정 정보가 출력됨을 확인
+2023-04-07T14:05:05.099+09:00  INFO 67782 --- [           main] com.example.demo.DemoApplication         : Reading datasource config prd-user - prd-password
+
+# 앱 구성 app.config.auth 항목을 볼트에서 가져와서 출력됨을 확인
+2023-04-07T14:05:05.103+09:00  INFO 67782 --- [           main] com.example.demo.AppService              : Reading configuration MY-AUTH-TOKEN-prd-1111 - prd-user
+

권한이 없는 dev 프로파일을 지정하는 경우 구성 값을 가져오지 못하므로 앱이 실행될 때 에러가 발생한다.

+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/terraform-with-aws-secret-engine.html b/04-HashiCorp/06-Vault/04-UseCase/terraform-with-aws-secret-engine.html new file mode 100644 index 0000000000..22574af43e --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/terraform-with-aws-secret-engine.html @@ -0,0 +1,159 @@ + + + + + + + + + + Terraform 코드 상에서 Vault 연동하기 | docmoa + + + + + +
본문으로 건너뛰기

Terraform 코드 상에서 Vault 연동하기

powhapki약 1 분terraformvaultaws

Terraform 코드 상에서 Vault 연동하기

Terraform Enterprise/Terraform Cloud를 사용할 때 Workspace의 변수(Variable)를 Vault를 사용하여 설정하는 것은 Terraform의 TFE 프로바이더와 Vault Provider를 사용하여 가능하다.

이번 예제는 Terraform Configuration Template에서 Vault를 사용하는 예제이다. Vault 인증 시 AppRole인증을 사용하였으나 기타 지원되는 인증 방법을 사용할 수 있다.

AWS Provider 설정 시 필요한 access_key와 secret_key를 환경 변수 설정이 아니라 코드 실행 시 Vault AWS 시크릿 엔진을 사용하도록 구성된 예제로, 코드는 다음과 같이 4개의 파일로 구성된다.

❯ tree
+.
+├── ec2.tf
+├── provider.tf
+├── terraform.tfvars
+└── variables.tf
+
+0 directories, 4 files
+

경고

위 예제를 사용하기 위해서는 Vault 상의 AWS 시크릿 엔진이 구성되어 있어야 하고, 인증을 위한 AppRole 구성 그리고 정책이 사전에 설정되어 있어야 한다.

1. Provider 설정 (provider.tfopen in new window)

사용할 프로바이더로 aws(자원 배포 대상)와 vault를 지정.

terraform {
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "3.23.0"
+    }
+    vault = {
+      source  = "hashicorp/vault"
+      version = "2.17.0"
+    }
+  }
+}
+
+provider "vault" {
+  # It is strongly recommended to configure this provider through the environment variables described above, so that each user can have
+  # separate credentials set in the environment.
+  #
+  # This will default to using $VAULT_ADDR
+  # But can be set explicitly
+  # address = "https://vault.example.net:8200"
+  address = var.vault_addr
+
+  
+  auth_login {
+  path = "auth/approle/login"
+  parameters = {
+    role_id   = var.login_approle_role_id
+    secret_id = var.login_approle_secret_id
+  }
+ }
+}
+
+# 코드 실행 시 Vault AWS 시크릿 엔진을 사용하여, data 값으로 access_key와 secret_key 생성하여 사용
+provider "aws" {
+  region     = var.region
+  access_key = data.vault_aws_access_credentials.creds.access_key
+  secret_key = data.vault_aws_access_credentials.creds.secret_key
+  # STS Token을 사용하지 않는 경우 주석 처리
+  token      = data.vault_aws_access_credentials.creds.security_token
+}
+
+

2. 자원 생성 시 사용 설정 (ec2.tfopen in new window)

data 소스를 이용하여 Vault에 설정된 AWS 시크릿 엔진을 읽어서 access_key와 secret_key를 생성하고, 해당 정보를 provider에서 사용하게 된다.

data "vault_aws_access_credentials" "creds" {
+  # AWS 시크릿 엔진 경로 : 기본은 AWS
+  backend = var.aws_sec_path
+  # AWS 시크릿 엔진 구성 시 사용한  Role 이름
+  role    = var.aws_sec_role
+  #STS Token으로 발급받아 설정. 아닌 경우, 다음 코드를 주석 처리 후 실행할 것.
+  type ="sts"
+}
+
+# AMI 정보 조회
+data "aws_ami" "ubuntu" {
+  most_recent = true
+
+  filter {
+    name   = "name"
+    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
+  }
+
+  filter {
+    name   = "virtualization-type"
+    values = ["hvm"]
+  }
+
+  owners = ["099720109477"] # Canonical
+}
+
+# Create AWS EC2 Instance
+resource "aws_instance" "main" {
+  ami           = data.aws_ami.ubuntu.id
+  instance_type = "t2.nano"
+
+  tags = {
+    Name  = var.name
+    TTL   = var.ttl
+    owner = "${var.name}-guide"
+  }
+}
+

3. 변수 선언 및 변수 값 지정

3.1 변수 선언 (variables.tfopen in new window)

variable region {
+  default="ap-northeast-2"
+}
+
+variable "name" { default = "vault-dynamic-creds"}
+
+variable ttl { default = "24h"}
+
+variable "vault_addr" {
+  description = "Vault Server address format : http://IP_ADDRES:8200"
+  default     = "http://127.0.0.1:8200"
+}
+
+variable login_approle_role_id {
+  description = "AppRole의 Role ID값 설정"
+}
+variable login_approle_secret_id {
+  description = "AppRole의 Secret ID값 설정"
+}
+# 
+variable aws_sec_path {
+  description = "AWS 시크릿 엔진 경로, 마지막은 반드시 '/'로 끝나게 설정."
+  default = "aws/"
+}
+
+variable aws_sec_role {
+  description = "AWS 시크릿 엔진 상의 Role 이름"
+  default ="VAULT상에 생성된 AWS시크릿 엔진의 Role이름"
+}
+

3.2 변수 값 지정 (terraform.tfvars)

vault_addr="http://127.0.0.1:8200" 
+login_approle_role_id="AppRole의 Role_ID값"
+login_approle_secret_id="AppRole의 Secret_ID값" 
+
+

개선 아이디어

  • terraform.tfvars 상의 Role_ID, Secret_ID값은 Terraform Cloud/Enterprise를 사용하는 경우, Workspace 상의 변수로 설정할 수 있다. --> 해당 작업을 수행하는 별도의 Admin Workspace가 있는 경우, Run Trigger를 활용할 수 있다.
  • terraform.tfvars 파일을 Vault Agent, ConsulTemplate 또는 envConsul을 이용하여 설정 후 사용하는 것도 가능.
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/transit-stress-test.html b/04-HashiCorp/06-Vault/04-UseCase/transit-stress-test.html new file mode 100644 index 0000000000..bfc6ee4f04 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/transit-stress-test.html @@ -0,0 +1,64 @@ + + + + + + + + + + Vault Stress Test | docmoa + + + + + +
본문으로 건너뛰기

Vault Stress Test

1분 미만vaultperformancetransit

Vault Stress Test

wrk github : https://github.com/wg/wrkopen in new window
transit : https://www.vaultproject.io/docs/secrets/transitopen in new window

Enable Transit

  1. Transit 시크릿 활성화
$ vault secrets enable transit
+Success! Enabled the transit secrets engine at: transit/
+
  1. 암호화 키 생성
$ vault write -f transit/keys/my-key
+Success! Data written to: transit/keys/my-key
+
  1. Test
$ vault write transit/encrypt/my-key plaintext=$(base64 <<< "my secret data")
+
+Key           Value
+---           -----
+ciphertext    vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM13ok481zoCmHnSeDX9vyf7w==
+

API Check

경고

  • 헤더에 X-Vault-Token 필요
  • plaintext 데이터의 값은 base64 인코딩 필요
curl \
+    -H "X-Vault-Token: s.HeeRXjkW1KJhF8ofQsglI9yw" \
+    -X POST \
+    -d "{\"plaintext\":\"dGhlIHF1aWNrIGJyb3duIGZveAo=\"}" \
+    http://192.168.60.103:8200/v1/transit/encrypt/my-key
+

부하테스트

wrk 사용시 Vault Transit은 POST를 사용하므로 스크립트 작성이 필요

1. 암호화 테스트

  • 스크립트 작성

    -- enc.lua
    +wrk.method = "POST"
    +wrk.body   = "{\"plaintext\":\"dGhlIHF1aWNrIGJyb3duIGZveAo=\"}"
    +wrk.headers["X-Vault-Token"] = "s.HeeRXjkW1KJhF8ofQsglI9yw"
    +
  • 실행

    wrk -c 240 -t 8 -d 10s -s enc.lua http://192.168.60.103:8200/v1/transit/encrypt/my-key
    +

2. 복호화 테스트

  • 스크립트 작성

    -- dec.lua
    +wrk.method = "POST"
    +wrk.body   = "{\"ciphertext\":\"vault:v1:I2JoSCrTduIDSI7BVIsFppwUop+YHFHejUbaHGeC7sb19CVZaYHEwicuJaXHxP/4\"}"
    +wrk.headers["X-Vault-Token"] = "s.HeeRXjkW1KJhF8ofQsglI9yw"
    +
  • 실행

    wrk -c 360 -t 12 -d 60s -s ./dec.lua http://192.168.60.103:8200/v1/transit/decrypt/my-key
    +
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-integration-three-methods.html b/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-integration-three-methods.html new file mode 100644 index 0000000000..4baacdc41e --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-integration-three-methods.html @@ -0,0 +1,40 @@ + + + + + + + + + + Kubernetes Vault 통합방안 3가지 비교 | docmoa + + + + + +
본문으로 건너뛰기

Kubernetes Vault 통합방안 3가지 비교

약 3 분vaultkubernetessecretVSO

Kubernetes Vault 통합방안 3가지 비교

  • Sidecar Agent Injector
  • CSI provider
  • Vault Secrets Operator

개요

본 글에서는 HashiCorp Vault 및 Kubernetes 통합을 위해 HashiCorp가 지원하는 세 가지 방법을 자세히 비교한다:

  1. 볼트 사이드카 에이전트 인젝터(Sidecar Agent Injector)
  2. 볼트 컨테이너 스토리지 인터페이스 공급자(Container Storage Interface (CSI) provider)
  3. 볼트 시크릿 오퍼레이터(Secrets Operator)

각 방법에 대한 실용적인 지침(guidance)을 제공하여 사용 사례에 가장 적합한 방법을 이해하고 선택할 수 있도록 안내한다.

참고

본 포스트는 제품 설명서나 단계별(step-by-step) 구현 가이드가 아니며, HashiCorp Vault 및 Kubernetes에 익숙하고 시크릿 관리 개념에 대한 기본적인 이해가 있는 데브옵스 실무자를 위한 문서이다.

1. Vault Sidecar Agent Injector

Vault 사이드카 에이전트 인젝터(Vault Sidecar Agent Injectoropen in new window)는 사이드카 패턴(sidecar patternopen in new window)을 활용하여 공유 메모리 볼륨에 볼트 시크릿을 렌더링하는 Vault 에이전트 컨테이너를 포함하도록 파드 사양(spec)을 변경한다. 공유 볼륨에 시크릿을 렌더링함으로써, 파드 내의 컨테이너는 Vault를 인식(Vault-aware)하지 않고도 Vault 시크릿을 사용할 수 있다.
인젝터는 Kubernetes Mutating Webhook Controller이다. Controller는 파드 이벤트를 가로채고(intercepts) 요청 내에 어노테이션이 있는 경우 파드에 변형(mutations)을 적용한다. 이 기능은 vault-k8sopen in new window 프로젝트에 의해 제공되며, Vault Helm 차트를 사용하여 자동으로 설치 및 구성할 수 있다.

img
img

2. Vault CSI provider

파드가 임시(ephemeral) CSI Secrets Storeopen in new window 볼륨을 사용하여 볼트 시크릿을 사용할 수 있도록 하는 것이 Vault CSI provideropen in new window이다.
높은 수준(high level)에서, CSI Secretes Store 드라이버는 사용자가 SecretProviderClass 오브젝트를 생성할 수 있게 해준다. 이 오브젝트는 사용할 시크릿 프로바이더와 검색할 시크릿을 정의한다. CSI 볼륨을 요청하는 파드가 생성되면, CSI Secretes Store 드라이버는 프로바이더가 vault인 경우 요청을 Vault CSI Provider에게 보낸다. 그러면 Vault CSI Provider는 지정된 SecretProviderClass와 파드의 서비스 어카운트(SA)을 사용하여 볼트에서 시크릿을 검색하고 파드의 CSI 볼륨에 마운트한다. 시크릿은 Vault에서 검색되어 ContainerCreation 단계에서 CSI 시크릿 스토어 볼륨에 채워진다. 즉, Vault에서 시크릿을 읽고 볼륨에 쓰기 전까지는 파드가 시작되지 않도록 차단된다.

img
img

3. Vault Secrets Operator

Vault Secrets Operatoropen in new window는 기본적으로 Vault secrets을 Kubernetes Secrets에 동기화할 책임이 있는(responsible) CRD 집합으로 쿠버네티스 시크릿 오퍼레이터(Kubernetes Secrets Operator)를 구현하는 새로운 통합 방법이다.

오퍼레이터는 하나 이상의 볼트 서버 인스턴스에서 정적(static), 동적(dynamic) 및 PKI 기반(PKI-based) 시크릿을 포함한 시크릿 관리의 전체 라이프사이클 동기화를 지원한다. 또한 오퍼레이터는 시크릿 로테이션(secret rotation)을 관리하고 Deploymentopen in new windowrolling updateopen in new window를 통해 애플리케이션에 직접 알리거나(notifying) 롤링 업데이트를 트리거(triggering)하는 등 로테이션 후 작업을 수행할 수 있다.

참고 :

4. 설계 고려사항

두 솔루션 간에는 몇 가지 유사점과 차이점이 있으며, Kubernetes 환경에서 시크릿 관리 전략을 설계하고 구현할 때 고려해야 할 사항이다.

고려사항설명
Secret projections모든 애플리케이션은 특정 방식으로 시크릿을 제공해야 한다. 일반적으로, 애플리케이션은 환경 변수로 내보내거나(exported) 애플리케이션 시작(startup) 시 애플리케이션이 읽을 수 있는 파일에 시크릿을 기록한다. 사용할 올바른 방법을 결정할 때 이 점을 염두에 두자.
Secret scope일부 애플리케이션은 데이터센터, 엣지 또는 퍼블릭 클라우드의 여러 Kubernetes 환경(예: dev, qa, producton)에 배포된다. 일부 서비스는 가상 머신, 서버리스 또는 기타 클라우드 관리형(cloud-managed) 서비스에 배포된 외부 Kubernetes 환경에서 실행되기도 한다.
이러한 애플리케이션이 다양한 이기종 환경 전반에서 일련의 시크릿을 공유해야 하는 시나리오에 직면할 수 있다. 키의 범위(Scoping)를 올바르게 설정하여 Kubernetes 환경에 로컬 또는 여러 환경에 걸쳐 전역(global)으로 설정하면 각 애플리케이션이 배포된 환경 내에서 자체 키 집합에 쉽고 안전하게 액세스할 수 있다.
Secret types시크릿은 텍스트 파일, 바이너리 파일, 토큰 또는 인증서 등이 대표적이다. 정적으로 생성하거나 동적으로 생성할 수도 있다. 영구적으로 유효하거나 시간 제한적(time-scoped)으로 유효할 수 있다. 또한 크기도 다양하다. 애플리케이션에 필요한 시크릿 유형과 애플리케이션에 투영(projected)되는 방식을 고려해야 한다.
Secret definition또한 각 비밀이 정의, 생성, 업데이트 및 제거되는 방법과 해당 프로세스와 관련된 도구도 고려해야 한다.
Encryption미사용(at rest) 시크릿과 전송(transit) 중인 시크릿을 모두 암호화하는 것은 많은 기업 조직에서 중요한 요구 사항이다.
Governance애플리케이션과 비밀은 다대다(many-to-many) 관계를 가질 수 있으므로 애플리케이션이 각각의 비밀을 검색할 수 있도록 액세스 권한을 부여할 때 신중한 고려가 필요하다. 애플리케이션과 암호의 수(scale)가 증가함에 따라 액세스 정책 관리의 어려움도 커진다.
Secrets updates and rotation시크릿은 임대(leased), 시간 범위(time-scoped) 지정 또는 자동으로 순환(rotated)될 수 있으며, 각 시나리오는 새 시크릿이 애플리케이션 파드에 올바르게 전파되도록 프로그래밍(programmatic) 프로세스를 거쳐야 한다.
Secret caching특정 쿠버네티스 환경(예: edge 또는 retail)에서는 환경과 시크릿 스토리지 간의 통신 또는 네트워크 장애가 발생할 경우 시크릿 캐싱이 필요할 수 있다.
Auditability모든 시크릿 액세스 정보를 자세히 설명하는 시크릿 액세스 감사 로그를 보관하는 것은 시크릿 액세스(secret-access) 이벤트의 추적성(traceability)을 보장(Keeping)하는 데 중요하다.

이러한 설계 고려 사항을 염두에 두고, 두 통합 솔루션의 유사점(similarities)과 차이점(differences)을 살펴본다.

5. Similarities

Vault Operator, CSISidecar 솔루션:

  • Vault에 저장된 다양한 유형의 시크릿 검색을 간소화(Simplify)하고 사소하지 않은(not-so-trivial) Vault 프로세스를 인식하지 않고도 Kubernetes에서 실행 중인 대상 파드에 시크릿을 노출한다. 중요한 점은 이러한 솔루션을 사용하기 위해 애플리케이션 로직이나 코드를 변경할 필요가 없기 때문에 브라운필드(brownfield) 애플리케이션을 Kubernetes로 더 쉽게 마이그레이션할 수 있다는 것이다. 그린필드(greenfield) 애플리케이션을 작업하는 개발자는 Vault SDKsopen in new window를 활용하여 Vault와 직접 통합할 수 있다.

  • 모든 유형의 Vault secrets enginesopen in new window을 지원한다. 즉, 정적 키-값(key-value) 시크릿부터 동적으로 생성된 데이터베이스 자격 증명(credentials), 사용자 정의 TTL이 포함된 TLS 인증서까지 광범위한(extensive) 시크릿 유형 세트를 활용할 수 있다.

  • 애플리케이션의 Kubernetes 포드 서비스 어카운트(SA) 토큰을 “Secret Zero”open in new window로 활용하여 Kubernetes Auth Methodopen in new window를 통해 Vault에 인증한다.
    즉, Vault에 인증할 때 애플리케이션 파드를 식별하기 위해 또 다른 별도의 ID를 관리할 필요가 없다.

img
img

6. Vault’s Kubernetes auth workflow

  • 애플리케이션을 배포하기 전에 원하는 시크릿이 Vault 내에 존재해야 한다.
  • 원하는 시크릿에 액세스할 수 있는 정책으로 파드의 서비스 어카운트가 Vault 역할에 바인딩(bound)되어야 한다.
    (즉, 시크릿에 대한 액세스 권한을 부여하는 데 Kubernetes RBAC이 사용되지 않아야 함).
  • Helm을 통해 배포 가능
  • 파드가 시작되기 전에 볼트에서 시크릿을 성공적으로 검색(retrieving)해야 한다.
  • 사용자 정의(user-defined) 파드 어노테이션을 사용하여 Vault에서 필요한 시크릿을 검색(retrieve)한다.
  • Sidecar Injector Serviceopen in new windowCSI Driveropen in new window는 모두 자동으로 시크릿/토큰을 갱신(renew), 회전(rotate) 및 가져올(fetch) 수 있다.

7. Differences

세 가지 솔루션의 차이점은 다음과 같다:

  • 사이드카 에이전트 인젝터 솔루션은 두 가지 요소로 구성된다:
    • Sidecar Service Injector는 클러스터 서비스로 배포되며, Kubernetes API Server 파드 이벤트를 가로채고 필요한 사이드카 컨테이너를 추가하기 위해 파드 스펙을 변경하는 역할을 한다.
    • Vault Sidecar Container는 각 애플리케이션 파드와 함께 배포되며 Vault에 인증하고, Vault에서 시크릿을 검색하고, 애플리케이션이 사용할 시크릿을 렌더링하는 것을 담당한다.
  • 이와 대조적으로, Vault CSI Driver는 쿠버네티스 클러스터의 모든 노드에 데몬셋으로 배포되며, 지정된 시크릿 프로바이더 클래스와 파드의 서비스 계정을 사용하여 Vault에서 시크릿을 검색하고 파드의 CSI 볼륨에 마운트한다.
  • 또한 Vault Operator는 시크릿 로테이션을 관리하고 디플로이먼트의 롤링 업데이트를 통해(또는 트리거를 통해) 애플리케이션에 직접 알리는 등 로테이션 후 작업을 수행할 수 있다.
  • 지원되는 인증 방법의 차이는 다음과 같다:
  • 모든 애플리케이션 파드와 함께 실행되는 사이드카 컨테이너는 자동 인증, 템플릿, 캐싱과 같은 강력한 기능 세트를 제공하는 Vault Agentopen in new window를 사용한다. CSI Driver는 Vault Agent를 사용하지 않으므로 이러한 기능을 제공하지 않는다.
  • Vault Operator에는 거버넌스 보고(governance reporting)를 위한 Promethus 오퍼레이터에 대한 지원이 포함되어 있다.
  • Vault CSI Driver는 Vault 시크릿을 쿠버네티스 시크릿과 환경 변수로 렌더링하는 것을 지원한다. Sidecar Injector Service는 시크릿을 쿠버네티스 시크릿으로 렌더링하는 것을 지원하지 않지만, 에이전트 템플릿(agent templatingopen in new window)을 사용하여 시크릿을 환경 변수로 렌더링하는 방법이 있다.
  • CSI Driver는 hostPath를 사용하여 임시 볼륨을 파드에 마운트하는데, 일부 컨테이너 플랫폼(예: OpenShift)은 기본적으로 이 기능을 비활성화한다. 반면, Sidecar Agent Service는 인메모리(in-memory) tmpfs 볼륨을 사용한다.
  • Sidecar Injector Service는 자동으로(automaticallyopen in new window) 시크릿/토큰을 갱신, 회전 및 패치(fetches)를 지원하지만, CSI Driver는 이를 지원하지 않는다.

8. Comparison Chart

아래 표는 두 솔루션을 개략적(high-level)으로 비교한 것이다:

SidecarCSIVault Operator
Secret projection공유 메모리 볼륨
환경 변수*
임시 디스크
환경변수
쿠버네티스 시크릿
쿠버네티스 시크릿
쿠버네티스 시크릿 볼륨
환경변수
Secret scope전역(Global)전역(Global)전역(Global)
Secret types모든 볼트 시크릿 엔진
(정적 & 동적)
모든 볼트 시크릿 엔진
(정적 & 동적)
모든 볼트 시크릿 엔진
(정적 & 동적)
Secret templating✅(+1.15)
Secret size limitVault w/Consul
Backend: 512 KB(기본)
제한없음

Vault w/Integrated Storage
Backend: 1MiB(기본)
제한없음
Vault w/Consul
Backend: 512 KB(기본)
제한없음

Vault w/Integrated Storage
Backend: 1MiB(기본)
제한없음
Vault w/Consul
Backend: 512 KB(기본)
제한없음

Vault w/Integrated Storage
Backend: 1MiB(기본)
제한없음
Secret definitionsVault CLI / API / UIVault CLI / API / UIVault CLI / API / UI
Encryption지원(at rest & in-transit)지원(at rest & in-transit)at-rest : etcd 저장소 암호화 시
in-transit : TLS 사용 시
Secret rotation
Secret caching
Auditability
Deployment method1개의 공유된 K8s 클러스터 서비스
+
1개의 사이드카 컨테이너
데몬셋디플로이먼트
Vault agent support
Helm support
Custom Support
K8s Secrets
drift detection
and automation
remediation

***** achieved through Agent templatingopen in new window

9. Going Beyond the Native Kubernetes Secrets

겉으로 보기에 Kubernetes native secrets은 위에 제시된 두 가지 접근 방식과 비슷해 보일 수 있지만, 두 접근 방식에는 큰 차이점이 있다:

  • Kubernetes 시크릿 관리 솔루션이 아닙니다. 기본적으로 시크릿을 지원하지만, 이는 엔터프라이즈 시크릿 관리 솔루션과는 상당히 다르다. Kubernetes 시크릿은 클러스터로만 범위가 제한되며, 많은 애플리케이션은 일부 서비스를 Kubernetes 외부 또는 다른 Kubernetes 클러스터에서 실행한다. 따라서 설계 프로세스의 일부로 시크릿 범위를 고려하는 것이 중요하다. 이러한 애플리케이션이 Kubernetes 환경 외부에서 Kubernetes 시크릿을 사용하도록 하면 번거롭고 인증 및 권한 부여 문제가 발생할 수 있다.

  • Kubernetes 시크릿은 본질적으로(in nature) 정적(static)이다. 시크릿은 kubectl 또는 Kubernetes API를 사용하여 정의할 수 있지만, 일단 정의되면 etcd에 저장되고 파드를 생성하는 동안에만 파드에 제공된다. 이로 인해 시크릿이 오래되거나, 구식이거나(outdated), 만료되는 시나리오가 발생할 수 있으며, 시크릿을 업데이트하고 회전하기 위해 추가 워크플로우가 필요하고 새 버전의 시크릿을 사용하기 위해 애플리케이션을 다시 배포(re-deploying)해야 한다. 이로 인해 복잡성(complexity)이 가중되고 시간이 낭비될 수 있다. 따라서 디자인 프로세스의 일부로 시크릿의 최신성(freshness), 업데이트 및 순환에 대한 요구 사항을 고려해야 한다.

  • 시크릿 액세스 관리의 보안 모델은 Kubernetes RBAC 모델과 연결되어 있다. 이 모델은 Kubernetes에 익숙하지 않은 사용자에게는 채택하기 어려울 수 있다. 플랫폼에 구애받지 않는(platform-agnostic) 보안 거버넌스 모델을 채택하면 애플리케이션이 실행되는 방식과 위치에 관계없이 애플리케이션에 대한 워크플로우를 채택할 수 있다.

10. Summary

Kubernetes에서 시크릿 관리를 위한 설계는 쉬운 일이 아니다. 각각 장단점이 있는 여러 가지 접근 방식이 있다. 이 게시물에 제시된 옵션을 살펴보고 내부를 이해하여 사용 사례에 가장 적합한 옵션을 결정하시길 적극 권장한다.

10. Additional Resources

+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-manually-using-the-sidecar.html b/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-manually-using-the-sidecar.html new file mode 100644 index 0000000000..677d9ca546 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-manually-using-the-sidecar.html @@ -0,0 +1,290 @@ + + + + + + + + + + Kubernetes에 Vault Agent(Sidecar) 수동 구성 | docmoa + + + + + +
본문으로 건너뛰기

Kubernetes에 Vault Agent(Sidecar) 수동 구성

약 3 분vaultkubernetes

Kubernetes에 Vault Agent(Sidecar) 수동 구성

Kubernetes(K8s)환경에서 외부 Vault(External Vault Server)와 연계하는 경우 일반적으로 kubernetes 인증방식을 활용하여 Vault와 K8s 간 플랫폼 수준에서의 인증을 처리하나, K8s로의 Cluster API에 대한 inbound가 막혀있는 경우 이같은 방식은 사용할 수 없다. 따라서 helm, vso 같은 방식의 사용이 불가능하므로 Vault Agent를 Sidecar로 함께 배포하는 경우 수동으로 구성해주어야 한다.

구성 과정은 Vault Agent를 BM/VM 환경에 구성하는 방식과 유사하며, 관련 구성 파일과 인증을 위한 정보를 Kubernetes 리소스를 활용한다는 차이가 있다.

테스트를 위한 Secret Engine은 kv-v2 이며, /secret 경로에 할당하였다.

Secret Example
vault secrets enable -version=2 -path=secret kv
+
+vault kv put secret/my-k8s-secret foo=my-k8s-secret-data
+
+vault policy write my-secret - <<EOF
+path "secret/data/my-k8s-secret" {
+  capabilities = ["read"]
+}
+EOF
+

1. Approle 인증 방식

AppRole 인증방식을 활성화 한다.

vault auth enable approle
+

AppRole의 role을 생성한다.

vault write auth/approle/role/k8s-role \
+    secret_id_ttl=10m \
+    token_ttl=60m \
+    token_max_ttl=120m \
+    policies=my-secret
+

role_id를 확인한다.

vault read auth/approle/role/k8s-role/role-id
+

role_idsecret_id를 K8s의 Secret에 저장한다.

# kubectl create secret generic vault-approle --from-literal=role_id=<role-id-1234> --from-literal=secret_id=<s.1234567890abcdef>
+kubectl create secret generic vault-approle \
+  --from-literal=role_id=$(vault read -field=role_id auth/approle/role/k8s-role/role-id) \
+  --from-literal=secret_id=$(vault write -force -field=secret_id auth/approle/role/k8s-role/secret-id)
+

AppRole로 인증하는 Vault Agent를 위한 구성 파일을 vault-agent-config.hcl에 설정한다. ConfigMap에 저장한다.

cat <<EOF | kubectl create configmap vault-agent-config --from-file=agent-config.hcl=/dev/stdin
+vault {
+  address = "http://10.100.11.233:8200"
+}
+
+auto_auth {
+  method "approle" {
+    config = {
+      role_id_file_path = "/etc/vault/approle/role_id"
+      secret_id_file_path = "/etc/vault/approle/secret_id"
+    }
+  }
+
+  sink "file" {
+    config = {
+      path = "/etc/vault-agent-token/token"
+    }
+  }
+}
+
+template_config {
+	static_secret_render_interval = "20s"
+}
+
+template {
+  destination = "/etc/secrets/index.html"
+  contents = <<EOH
+  <html>
+  <body>
+  <p>Secret Value: {{ with secret "secret/data/my-k8s-secret" }}{{ .Data.data.foo }}{{ end }}</p>
+  </body>
+  </html>
+  EOH
+}
+EOF
+

AppRole ID과 SecretID, Vault Agent Config 를 사용하는 샘플 앱을 실행한다. 다음은 Nginx를 사용한 Deployment Yaml의 예이다.

kubectl apply -f - <<EOF
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx-vault-demo
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: nginx-vault-demo
+  template:
+    metadata:
+      labels:
+        app: nginx-vault-demo
+    spec:
+      containers:
+      - name: nginx
+        image: nginx:latest
+        ports:
+        - containerPort: 80
+        volumeMounts:
+        - name: html-volume
+          mountPath: /usr/share/nginx/html
+      - name: vault-agent-sidecar
+        image: hashicorp/vault:latest
+        args:
+          - "agent"
+          - "-config=/etc/vault/agent-config.hcl"
+        volumeMounts:
+        - name: vault-agent-config
+          mountPath: /etc/vault
+        - name: vault-approle
+          mountPath: /etc/vault/approle
+        - name: vault-token
+          mountPath: /etc/vault-agent-token
+        - name: html-volume
+          mountPath: /etc/secrets
+      volumes:
+      - name: vault-agent-config
+        configMap:
+          name: vault-agent-config
+      - name: vault-approle
+        secret:
+          secretName: vault-approle
+      - name: vault-token
+        emptyDir: {}
+      - name: html-volume
+        emptyDir: {}
+EOF
+

Nginx의 Service를 등록한다.

kubectl apply -f - <<EOF
+apiVersion: v1
+kind: Service
+metadata:
+  name: nginx-service
+spec:
+  selector:
+    app: nginx-vault-demo
+  ports:
+    - protocol: TCP
+      port: 80
+      targetPort: 80
+EOF
+

port-forward를 이용하여 Nginx에서 정상적으로 랜더링된 vault의 시크릿을 포함한 페이지가 나타나는지 확인한다.

kubectl port-forward $(kubectl get pods -l app=nginx-vault-demo -o jsonpath='{.items[0].metadata.name}') 8080:80
+

Vault Agent 구성파일에서 static_secret_render_interval에 대한 정의가 있으므로, 20s 간격마다 변경된 KV 값으로 랜더링하는지 확인해본다.

vault kv put secret/my-k8s-secret foo=my-k8s-secret-data-v2
+

Pod 내의 vault-agent-sidecar 로그에 rendered 로그가 기록된다.

2023-12-04T02:01:51.992Z [INFO] (runner) rendered "(dynamic)" => "/etc/secrets/index.html"
+

2. Cloud Provider 인증 방식(e.g. AWS Auth)

범용적인 AppRole 대신 Cloud Provider와의 인증 방식(여기서는 AWS 인증 방식)을 사용하여 Vault와 통신하는 구성을 적용할 수 있다.

EKS의 경우, EKS에 배포되는 Vault Agent는 AWS Role을 확인 가능하므로, AWS 인증 방식은 Vault가 AWS의 IAM 자격증명을 사용하여 인증을 수행하게 된다.

Vault AWS 인증 방식을 사용하기 위해서는 사전에 Vault AWS 인증에 사용할 Role이 필요하다.(아래는 Terraform으로의 구성 예제이다.)

AWS Role create - Terraform Example
provider "aws" {
+  region = "ap-northeast-2"
+}
+
+resource "aws_iam_role" "eks_vault_auth_role" {
+  name = "eks-vault-auth-role"
+
+  assume_role_policy = jsonencode({
+    Version = "2012-10-17",
+    Statement = [
+      {
+        Action = "sts:AssumeRole",
+        Effect = "Allow",
+        Principal = {
+          Service = "ec2.amazonaws.com"
+        }
+      }
+    ]
+  })
+}
+
+# Vault에 접근할 수 있는 역할에 대한 정책 (필요에 따라 수정)
+resource "aws_iam_role_policy" "vault_access" {
+  name = "VaultAccess"
+  role = aws_iam_role.eks_vault_auth_role.id
+
+  policy = jsonencode({
+    Version = "2012-10-17",
+    Statement = [
+      {
+        Action = [
+          "ec2:DescribeInstances",
+          "ec2:DescribeTags"
+        ],
+        Effect   = "Allow",
+        Resource = "*"
+      }
+    ]
+  })
+}
+
+output "role_arn" {
+  value = aws_iam_role.eks_vault_auth_role.arn
+}
+

Vault 서버에서 AWS 인증 방식을 활성화한다.

vault auth enable aws
+

AWS 역할을 생성하고, 해당 역할에 적절한 정책을 할당한다. 이 역할은 EKS에서 실행되는 서비스나 애플리케이션이 Vault에 인증할 때 사용된다. (terraform으로 생성한 경우 role_arn output에 출력된 결과를 bound_iam_principal_arn에 입력해준다.)

vault write auth/aws/role/k8s-role \
+    auth_type=iam \
+    bound_iam_principal_arn="arn:aws:iam::<AWS_ACCOUNT_ID>:role/<EKS_ROLE_NAME>" \
+    policies=my-secret \
+    ttl=1h
+

AWS로 인증하는 Vault Agent를 위한 구성 파일을 vault-agent-config-aws.hcl에 설정한다. ConfigMap에 저장한다.

cat <<EOF | kubectl create configmap vault-agent-config-aws --from-file=agent-config.hcl=/dev/stdin
+vault {
+  address = "http://10.100.11.233:8200"
+}
+
+auto_auth {
+  method "aws" {
+    mount_path = "auth/aws"
+    config = {
+      type = "iam"
+      role = "k8s-role"
+    }
+  }
+
+  sink "file" {
+    config = {
+      path = "/etc/vault-agent-token/token"
+    }
+  }
+}
+
+template_config {
+	static_secret_render_interval = "20s"
+}
+
+template {
+  destination = "/etc/secrets/index.html"
+  contents = <<EOH
+  <html>
+  <body>
+  <p>Secret Value: {{ with secret "secret/data/my-k8s-secret" }}{{ .Data.data.foo }}{{ end }}</p>
+  </body>
+  </html>
+  EOH
+}
+EOF
+

Deployment는 다음과 같이 수정하여 적용한다. AppRole 구성에서의 관련 설정들이 제외된다.

kubectl apply -f - <<EOF
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx-vault-demo
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: nginx-vault-demo
+  template:
+    metadata:
+      labels:
+        app: nginx-vault-demo
+    spec:
+      containers:
+      - name: nginx
+        image: nginx:latest
+        ports:
+        - containerPort: 80
+        volumeMounts:
+        - name: html-volume
+          mountPath: /usr/share/nginx/html
+      - name: vault-agent-sidecar
+        image: hashicorp/vault:latest
+        args:
+          - "agent"
+          - "-config=/etc/vault/agent-config.hcl"
+        volumeMounts:
+        - name: vault-agent-config-aws
+          mountPath: /etc/vault
+        - name: vault-token
+          mountPath: /etc/vault-agent-token
+        - name: html-volume
+          mountPath: /etc/secrets
+      volumes:
+      - name: vault-agent-config
+        configMap:
+          name: vault-agent-config
+      - name: vault-token
+        emptyDir: {}
+      - name: html-volume
+        emptyDir: {}
+EOF
+
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-usecase-csi-injection.html b/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-usecase-csi-injection.html new file mode 100644 index 0000000000..d4f06a0fa0 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-usecase-csi-injection.html @@ -0,0 +1,911 @@ + + + + + + + + + + How to integrate Vault with K8s (CSI & Injection & VSO) | docmoa + + + + + +
본문으로 건너뛰기

How to integrate Vault with K8s (CSI & Injection & VSO)

약 13 분vaultkubernetes

How to integrate Vault with K8s (CSI & Injection & VSO)

Vault에 저장된 시크릿 또는 발행되는(Dynamic) 시크릿을 획득하기 위해서는, 시크릿을 요청하는 클라이언트(사람/앱/장비)가 다음의 과정을 수행해야 합니다.

  1. 클라이언트가 Vault 토큰을 획득하기 위한 인증 절차
  2. 획득한 Vault 토큰의 수명주기 관리 (갱신과 재요청)
  3. Vault의 특정 시크릿 경로를 저장하고 해당 시크릿 요청
  4. 동적(Dynamic) 시크릿인 경우 해당 임대(Lease)정보 확인 및 갱신과 재요청

Vault는 위의 과정을 클라이언트 대신 플랫폼 수준에서 대행할 수 있는 방안을 제공하고 있습니다. 여기서는 Kubernetes 상에서의 Vault와의 통합 구성을 활용하여 위 과정을 대체하고 Kubernetes 플랫폼 자체(Kuberetes Native)의 기능을 사용하듯 Vault의 시크릿을 사용하게 만드는 방식에 대해 설명합니다.

Kubernetes에 배포되는 컨테이너 애플리케이션이 Vault의 시크릿 데이터를 얻기위해 사용되는 플랫폼 수준(Kubernetes)에서의 통합을 설명합니다. CSI, Sidecar Injection, Vault Secret Operator VSO에 대한 설명은 다음 글을 확인해 보세요.

Vault와 Kuberentes간의 통합의 세가지 방식은 중복으로 적용 가능합니다.

준비사항

구성을 위한 사전 필요 사항은 다음과 같습니다.

  • Vault CLI를 위한 바이너리
  • Kubectl CLI 도구 및 대상 Kubernetes에 대한 구성 완료
  • Helm CLI 도구

1. CSI

참고 : https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-secret-store-driveropen in new window

CSI 방식에서는 SecretProviderClass 가 Vault의 정보를 구성하는 역할을 수행하고, 이후 deployment에서 볼륨 형태로 호출하는 방식으로 구성됩니다.

1.1 Kubernetes에 CSI 드라이버 설치

Container Storage Interface(CSI) 드라이버를 설치하면 SecretProviderClass CRD 구성을 사용하여 Kubernets에 외부 시크릿 저장소의 값을 Pod에 마운트 할 수 있습니다.

먼저 CSI 드라이버 Helm 차트를 등록합니다.

helm repo add secrets-store-csi-driver \
+    https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
+

다음으로 CSI 드라이버를 설치 합니다.

helm install csi secrets-store-csi-driver/secrets-store-csi-driver \
+    --set syncSecret.enabled=true
+

설치가 정상적으로 완료되면 다음의 Pod를 확인할 수 있습니다.

$ kubectl get pods
+
+NAME                                 READY   STATUS    RESTARTS   AGE
+csi-secrets-store-csi-driver-vkppq   3/3     Running   0          20s
+

1.2 CSI를 위한 Vault 구성 (Helm)

CSI 드라이버에서 vault 프로바이더를 사용하기 위한 구성을 설치해야 합니다. 이 구성이 설치되면 SecretProviderClass 정의 시 프로바이더 대상으로 vault를 지정할 수 있습니다.

먼저 Vault Helm 차트를 등록합니다.

helm repo add hashicorp https://helm.releases.hashicorp.com
+

Vault Helm 차트를 사용하여 1) Kubernetes에 Vault를 설치하는 구성 또는 2) 외부 Vault와 연계하는 구성으로 설치 할 수 있습니다.

with Vault
helm install vault hashicorp/vault \
+    --set "server.dev.enabled=true" \
+    --set "injector.enabled=false" \
+    --set "csi.enabled=true"
+
  • server.dev.enabled: 개발 모드로 Vault 서버를 구성합니다. 운영 환경 구성시에는 사용하지 않습니다.
  • injector.enabled: Sidecar Injection 방식이 기본 활성화되므로 비활성으로 정의합니다.
  • csi.enabled: CSI 프로바이더 구성 설치를 위해 활성화 합니다.

설치가 정상적으로 완료되면 다음의 Pod를 확인할 수 있습니다. (vault-0는 Vault 서버를 설치한 경우 확인되고, 외부 Vault 서버를 사용하는 경우에는 확인되지 않습니다.)

$ kubectl get pods
+
+NAME                                 READY   STATUS    RESTARTS   AGE
+vault-0                    1/1     Running   0          58s
+vault-csi-provider-t874l   1/1     Running   0          58s
+

1.3 CSI에서 사용할 Vault 시크릿 정의

간단한 예로 Vault KV 시크릿 엔진을 사용합니다.

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+
/secret 경로에 KV 시크릿 엔진 활성화 (Optional)

Vault가 개발 모드로 실행된 경우 기본적으로 Secret이라는 경로에 KV version2 시크릿 엔진이 활성화되어있습니다. 만약 개발 모드가 아닌경우 다음과 같이 활성화 합니다. (Optional)

vault secrets enable -path secret -version=2 kv
+

secret/db-pass 경로에 password 값을 저장 합니다.

vault kv put secret/db-pass password="db-secret-password-v1"
+

다음과 같이 저장된 값을 확인할 수 있습니다.

$ vault kv get secret/db-pass
+
+=== Secret Path ===
+secret/data/db-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-25T11:49:15.6993Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            1
+
+====== Data ======
+Key         Value
+---         -----
+password    db-secret-password-v1
+

1.4 Vault에 인증받기 위한 Kubernetes 인증 방식 구성

Vault는 Kubernetes의 Service Account 토큰으로 인증할 수 있는 Kubernetes 인증 방식을 제공합니다. CSI 드라이버가 Vault에 저장된 시크릿 정보에 접근하여 시크릿을 획득하는 과정에서 Vault에 대한 인증/인가가 요구되므로 Kubernetes상의 리소스에서는 Kubernetes 인증 방식을 통해 Kubernetes의 방식으로 인증 받는 워크플로를 구성합니다.

Vault에 Kubernetes 인증 방식을 활성화 합니다.

vault auth enable kubernetes
+

Kubernetes API 주소를 Kubernetes 인증 방식 구성에 설정 합니다. 이 경우 자동으로 Vault Pod를 위한 자체 Service Account를 사용합니다.

Internal Vault
vault write auth/kubernetes/config \
+    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
+

생성할 Kubernetes 인증 방식의 롤 정의에서 사용되는 정책을 구성합니다. Vault의 secret/data/db-pass 경로에 저장된 시크릿을 읽을 수 있는 정책 입니다.

Linux/MacOS
vault policy write internal-app - <<EOF
+path "secret/data/db-pass" {
+  capabilities = ["read"]
+}
+EOF
+

예제의 롤 정의에서는 허용할 Service Account와 Kubernetes Namespace, 부여하는 정책으로 앞서 생성한 internal-app 정책을 할당합니다. 인증된 이후 유효 기간은 20분으로 설정 합니다.

vault write auth/kubernetes/role/database \
+    bound_service_account_names=webapp-sa \
+    bound_service_account_namespaces=default \
+    policies=internal-app \
+    ttl=20m
+

1.5 SecretProviderClass 구성

SecretProviderClass를 사용하여 리소스 정의를 합니다. 정의를 할 뿐 시크릿을 읽는 동작을 수행하지는 않습니다. 다음 예제 리소스 spc-vault-database.yaml파일에 설정한 정의는 vault 프로바이더를 사용하는 경우의 파라미터를 설명합니다.

apiVersion: secrets-store.csi.x-k8s.io/v1
+kind: SecretProviderClass
+metadata:
+  name: vault-database # CSI Provider로 호출될 이름
+spec:
+  provider: vault # CSI Provider 유형
+  parameters:
+    vaultAddress: "http://vault.default:8200"
+    # Vault에 구성한 Kubernetes 인증의 Role 이름
+    roleName: "database"
+    # Vault 주소 - 기본은 vault.default로 서비스 이름을 참조하나,
+    # 외부 Vault인경우 해당 주소를 지정해야 합니다.
+    vaultAddress: "https://vault.default:8200"
+    # Vault에 저장된 시크릿 경로와 대상을 지정합니다.
+    objects: |
+      - objectName: "db-password"
+        secretPath: "secret/data/db-pass"
+        secretKey: "password"
+

objects 항목은 리스트 구성으로 다수개의 시크릿을 정의할 수 있습니다.

  • objectName : 해당 시크릿을 가리키는 이름으로, 최종적으로 이 이름으로 파일이 생성됨
  • secretPath : Vault에 정의된 시크릿 경로 (KV version2의 경우 API 구조적으로 활성화된 경로 뒤에 data가 붙음)
  • secretKey : Vault의 시크릿 경로 호출시 반환되는 값의 키 이름

설정한 spc-vault-database.yaml를 적용합니다.

kubectl apply -f spc-vault-database.yaml
+

앞서 1) CSI에 사용될 Vault 프로바이더가 설치되고, 2) 인증이 구성되고, 3) 인증을 위한 롤이 정의되고, 4) Vault에 시크릿 값이 저장되고, 5) SecretProviderClass가 정의되었습니다.

롤에서 정의한 허용하는 Service Account를 생성합니다.

kubectl create serviceaccount webapp-sa
+

앞서 생성된 SecretProviderClassVolume으로 정의하여 Pod 정의를 webapp-pod.yaml에 저장합니다.

kind: Pod
+apiVersion: v1
+metadata:
+  name: webapp
+spec:
+  # 롤에서 허용하는 Service Account
+  serviceAccountName: webapp-sa
+  containers:
+  - image: jweissig/app:0.0.1
+    name: webapp
+    volumeMounts:
+    	# 아래 volumes에서 정의한 csi 이름
+    - name: secrets-store-inline
+    	# Pod에 마운트할 경로 지정
+    	# 해당 경로 상에 SecretProviderClass에서 정의한 objectName으로 파일이 생성됨
+      mountPath: "/mnt/secrets-store"
+      # 마운트된 파일의 읽기/쓰기 여부
+      readOnly: true
+  volumes:
+  	  # volumeMounts에서 정의될 이름
+    - name: secrets-store-inline
+      csi:
+        driver: secrets-store.csi.k8s.io
+        # 마운트된 파일의 읽기/쓰기 여부
+        readOnly: true
+        volumeAttributes:
+        	# SecretProviderClass로 정의한 이름
+          secretProviderClass: "vault-database"
+

webapp-pod.yaml 정의를 사용하여 Pod를 실행합니다. Pod가 실행되는 시점에 정의한 SecretProviderClass에 의해 지정한 위치에 Vault에 저장된 시크릿이 마운트 됩니다.

kubectl apply -f webapp-pod.yaml
+

실행된 Pod를 확인합니다.

$ kubectl get pods
+
+NAME                                     READY   STATUS    RESTARTS   AGE
+webapp                                   1/1     Running   0          5m
+

Pod 내에 마운트된 시크릿 정보를 확인합니다.

$ kubectl exec webapp -- cat /mnt/secrets-store/db-password
+
+db-secret-password-v1
+


 

1.7 시크릿 갱신

새로운 내용의 시크릿을 동일한 secret/db-pass 경로에 다시 저장합니다.

$ vault kv put secret/db-pass password="db-secret-password-v2"
+
+=== Secret Path ===
+secret/data/db-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-27T00:06:52.910923Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            2
+












 

Vault의 시크릿이 변경되었지만 이전의 시크릿 정보로 마운트 된 기존 Pod에는 변경된 시크릿으로의 갱신이 발생하지 않습니다.

$ kubectl exec webapp -- cat /mnt/secrets-store/db-password
+
+db-secret-password-v1
+

이번 예제에서는 Pod를 실행하였으므로, 이미 실행된 Pod를 종료시키고 다시 실행해야 변경된 시크릿을 다시 CSI 드라이버로 요청하여 마운트 됩니다.

기존 pod를 삭제 합니다.

kubectl delete pod webapp
+

이전의 정의를 다시 사용하여 Pod를 실행합니다.

kubectl apply -f webapp-pod.yaml
+

Vault의 변경된 시크릿이 적용된 것을 확인 합니다.

$ kubectl exec webapp -- cat /mnt/secrets-store/db-password
+
+db-secret-password-v2
+


 

2. Injecting

참고 1 : https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecaropen in new window

참고 2 : https://www.hashicorp.com/blog/injecting-vault-secrets-into-kubernetes-pods-via-a-sidecaropen in new window

참고 3 : https://developer.hashicorp.com/vault/docs/platform/k8s/injector/annotationsopen in new window

참고 4 : https://devopscube.com/vault-agent-injector-tutorial/open in new window

BM/VM 환경에서는 Vault의 시크릿을 획득하고 갱신하는 과정을 지원하기 위해 Vault Agent를 활용할수 있습니다. Kubernetes에서는 애플리케이션 배포 시 Vault Agent를 사이트카로 구성하여 자동화된 구성과 해당 애플리케이션 만을 위한 Vault Agent를 제공할 수 있습니다.

사이드카 방식이 적용되도록 Kubernetes에 설치되면 Sidecar Injector 서비스가 실행되고, 이 서비스는 annotation이 정의된 배포를 후킹하여 Vault Agent 컨테이너를 주입(Injection) 합니다.

2.1 Injection을 위한 Vault 구성 (Helm)

Kubernetes에 Sidecar Injector 서비스를 구성을 설치해야 합니다. 이 구성이 설치되면 annotation에 정의된 내용이 vault-k8s webhook을 호출하여 Pod를 재정의하여 Vault Agent를 사이드카로 주입(Injection)합니다.

먼저 Vault Helm 차트를 등록합니다.

helm repo add hashicorp https://helm.releases.hashicorp.com
+

Vault Helm 차트를 사용하여 1) Kubernetes에 Vault를 설치하는 구성 또는 2) 외부 Vault와 연계하는 구성으로 설치 할 수 있습니다.

with Vault
helm install vault hashicorp/vault \
+    --set "server.dev.enabled=true" \
+    --set "injector.enabled=true"
+
  • server.dev.enabled: 개발 모드로 Vault 서버를 구성합니다. 운영 환경 구성시에는 사용하지 않습니다.
  • injector.enabled: Sidecar Injection 방식이 기본 값이 true이나, 명시적으로 선언합니다.

설치가 정상적으로 완료되면 다음의 Pod를 확인할 수 있습니다.

  • vault-0는 Vault 서버를 설치한 경우 확인되고, 외부 Vault 서버를 사용하는 경우에는 확인되지 않습니다.
  • vault-agent-injector-* Pod는 annotation 기반으로 사이드카를 주입하는 역할을 담당합니다.
$ kubectl get pods
+
+NAME                                    READY   STATUS    RESTARTS   AGE
+vault-0                                 1/1     Running   0          80s
+vault-agent-injector-5945fb98b5-tpglz   1/1     Running   0          80s
+

2.2 Injection에서 사용할 Vault 정적 시크릿 정의

Injection을 사용하여 Vault Agent를 사용할 수 있는 환경에서는 시크릿 업데이트를 자동으로 수행할 수 있고, KV 같은 정적(Static)인 시크릿의 경우 해당 시크릿의 생명주기 정보는 별도로 없기 때문에 이후 Injection 구성에서 변경을 확인할 시간 간격을 지정하게 됩니다.

예제에서는 KV를 활용합니다. Injection에서 사용할 KV 시크릿 엔진을 활성화합니다.

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+
vault secrets enable -path for-injection -version=2 kv
+

for-injection/my-pass 경로에 password 값을 저장 합니다.

$ vault kv put for-injection/my-pass password="my-secret-password-v1"
+
+======= Secret Path =======
+for-injection/data/my-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-27T00:41:16.656713Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            1
+
저장된 시크릿 값을 확인
CLI
$ vault kv get for-injection/my-pass
+
+======= Secret Path =======
+for-injection/data/my-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-27T00:41:16.656713Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            1
+
+====== Data ======
+Key         Value
+---         -----
+password    my-secret-password-v1
+

2.3 Injection에서 사용할 Vault 동적 시크릿 정의

Injection을 사용하여 Vault Agent를 사용할 수 있는 환경에서는 시크릿 업데이트를 자동으로 수행할 수 있고, Database, PKI, Cloud Credential(AWS,Azure,GCP,Ali) 등 동적(Dynamic)인 시크릿의 경우 Vault Agent의 기존 방식 처럼 4/5 지점에서 갱신 작업을 수행 합니다.

예제에서는 PKI를 활용합니다. Injection에서 사용할 PKI 시크릿 엔진을 활성화 합니다.

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+
vault secrets enable -path=pki pki
+vault secrets tune -max-lease-ttl=86400s -default-lease-ttl=3600s pki
+

루트 인증서를 생성합니다.

vault write -field=certificate pki/root/generate/internal \
+  common_name="test" \
+  ttl="86400h"
+

생성된 루트 인증서에 기반한 PKI 롤을 생성합니다. 예제에서는 동적 시크릿의 교체를 확인하기 위해 주기(ttl, max_ttl)를 짧게 구성합니다.

vault write pki/roles/my-role \
+  key_bits=4096 \
+  ttl="60s" \
+  max_ttl="60s" \
+  allow_ip_sans=true \
+  allowed_domains="example.com,my.domain" \
+  allow_subdomains=true
+
PKI 인증서 발급 확인
CLI
$ vault write pki/issue/my-role common_name=my-test.example.com
+
+Key                 Value
+---                 -----
+ca_chain            [-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIUR6Auk4MVpeis2oLq0StUwce/v/kwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAxMEdGVzdDAeFw0yMzEwMjYyMzUyNDlaFw0yMzEwMjcyMzUz
+MTlaMA8xDTALBgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDXmlaX2Qu/rF+AFgWqJBCaNPziJrwsBB8nEUQh2S2XRMD9osoliWpaS33i
+iFAxc++Mec/FzKIsB7TskYWyFlv/GPmFG5gKdYfMuEMAgHrxM3OYWibQq0hDajJn
+oOcT1DwCx0mZqYdGoFVcw2TdW1vqgKRMx1vWBskaJHoGGpRvEPe7cYLz8itwqQfR
+7zkcVw3vdK6U50I7NnV/1wC+WOuwZ6IL5DKC1v3DtE5CrYKf/sBwDZfcdwFEjLpQ
+3hSXlVtv6t9E7QABcYqFkP5iebisNVP71L1Qk7oCuk4zqKpkbFytD6Nlf1LMRSFj
+SDt+aPuoqlmKrNtGsNcTqlW8k39HAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTwEbHemyl86vBdxfMICjaKOJIoJzAf
+BgNVHSMEGDAWgBTwEbHemyl86vBdxfMICjaKOJIoJzAPBgNVHREECDAGggR0ZXN0
+MA0GCSqGSIb3DQEBCwUAA4IBAQB2y9QDCSNlr+j4v5H/7s4aZR8EWqbSdGc6F9w2
+FrR/bwo9eIxWiABFn/SH+bqHSK7fw4TMPJ0rEnJxBEvIPpA2kvGIxsBzPAdPzQ+A
+4F6tSJtiXB5A/7IZn9SQLUrcmcA5SuBGN9GjmPLpYSQg2ykJsTlExkYdg4co2sYV
+0F1gE5SXFGEmNTwFlpPSmKY6Zs8fKJZrzf+feCXFRlD/u+I4vftJqu7pwxZvPifR
+gPWi3kuzj71b4rEkZW3zNCP9XOtkCO/pNW2hJnc0QiTgQGvWXl/A8rIohsc+by2N
+MVr8w8iw1OdwbxI0LyC5siVgn+aER5qryYlpdeKR0/F2LuWX
+-----END CERTIFICATE-----]
+certificate         -----BEGIN CERTIFICATE-----
+MIIETjCCAzagAwIBAgIUbrsk5aFaFZ5MB9aeS9DjXlcEvFswDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAxMEdGVzdDAeFw0yMzEwMjcwMDU1MDlaFw0yMzEwMjcwMDU2
+MDJaMB4xHDAaBgNVBAMTE215LXRlc3QuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQDBSDy7gpekQv6Ro8p+4Szm8iavHv3KRyOoMYOv
+UdRlT+2KT6UcZGc9c0RLYS1yvT2QuYm6CeFLs+msYU/mVdLG/ih8YlCiOG9uDyZi
+CNqA+MOkxkwgChTfNgeOWQr8uo2J9CaV3bjProtE7weGaK/J5UYDTHxsZxMTom+t
+dMCAHol8d888cqVUvHXOth07/OKO5orKBcsxFhq0IAwERNT3kGxIcfOFvhWJfNUn
+ihdZMjq8u/CBaD3MhKU2Sn5e40FGLKuIoF0pMxhvPnJARiz53sAMMujoQxVgiIsQ
+9DT8phhNKXqufjOYEUUJ0hy/quy+/i4B00SPNsOOcD8vOsz96mhZC9ik4Avz0xdB
+KY0UaeULPmztdJW08dEaY1DSJB/k8rPMu4VZAFgxeFgj4byA9UwQ14aMJCZWHZYH
+cGbkJjcdFEC1ZhICKIHOO0KSoXpxD9xIQ1UWYvoegqSBSqvecaYf6y52kg7hb4rg
+jVFdBKWhBCGJ1RaqnbnBBp+Qk5AAkCyYfUpXXNmpYB7akIXLe3iTL50MkaiTd+GE
+xBXhfCYvwbpIZu35bAurwp3+nSTTeJw4d2O7s1L4iqdQ24fERYwEL8euLzzmxsjv
+qsmN1cHzbMulrCjVT3ZNBPFiMltoDJXyJDssKTM4nOpxr+FxBiCpbufcy2tDJ4eb
+svMxiQIDAQABo4GSMIGPMA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEF
+BQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFGO3lOOstANAUseQaJmGMnCVQkw8MB8G
+A1UdIwQYMBaAFPARsd6bKXzq8F3F8wgKNoo4kignMB4GA1UdEQQXMBWCE215LXRl
+c3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBADT1aqab6RhLGuAvUgIS
+3lZ+B/ltWFQroFRgnfQArlMrVnCE1/7LAH+i7n8Ev7ixK0xP2CYRLwm8McLBEIjm
+qWB8ZXJJq4gXqZ6i5kIFvuRILkesSGJbs49TdeAMz6lyJd/BQmzM/uAhnqMrhlRt
+H6ZWnC5Z7dRGWT/yIlKL6kMcmxqEZCTt7j76V/8CRRUtxHtEgt4B4R/0lykWM8Ed
+HMok6crNYk94Jg/S8MWZlUHtCjDeXMd3mhDVQKaBNeLGjyugDF8KLVpcIMjEjglk
+UDG/bqxqwS2/jVUnDFvejbrOkJ/e3NefZa52/fZlXwqnwAlumtHOgEk3j00rHQSA
+/04=
+-----END CERTIFICATE-----
+expiration          1698368162
+issuing_ca          -----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIUR6Auk4MVpeis2oLq0StUwce/v/kwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAxMEdGVzdDAeFw0yMzEwMjYyMzUyNDlaFw0yMzEwMjcyMzUz
+MTlaMA8xDTALBgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDXmlaX2Qu/rF+AFgWqJBCaNPziJrwsBB8nEUQh2S2XRMD9osoliWpaS33i
+iFAxc++Mec/FzKIsB7TskYWyFlv/GPmFG5gKdYfMuEMAgHrxM3OYWibQq0hDajJn
+oOcT1DwCx0mZqYdGoFVcw2TdW1vqgKRMx1vWBskaJHoGGpRvEPe7cYLz8itwqQfR
+7zkcVw3vdK6U50I7NnV/1wC+WOuwZ6IL5DKC1v3DtE5CrYKf/sBwDZfcdwFEjLpQ
+3hSXlVtv6t9E7QABcYqFkP5iebisNVP71L1Qk7oCuk4zqKpkbFytD6Nlf1LMRSFj
+SDt+aPuoqlmKrNtGsNcTqlW8k39HAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTwEbHemyl86vBdxfMICjaKOJIoJzAf
+BgNVHSMEGDAWgBTwEbHemyl86vBdxfMICjaKOJIoJzAPBgNVHREECDAGggR0ZXN0
+MA0GCSqGSIb3DQEBCwUAA4IBAQB2y9QDCSNlr+j4v5H/7s4aZR8EWqbSdGc6F9w2
+FrR/bwo9eIxWiABFn/SH+bqHSK7fw4TMPJ0rEnJxBEvIPpA2kvGIxsBzPAdPzQ+A
+4F6tSJtiXB5A/7IZn9SQLUrcmcA5SuBGN9GjmPLpYSQg2ykJsTlExkYdg4co2sYV
+0F1gE5SXFGEmNTwFlpPSmKY6Zs8fKJZrzf+feCXFRlD/u+I4vftJqu7pwxZvPifR
+gPWi3kuzj71b4rEkZW3zNCP9XOtkCO/pNW2hJnc0QiTgQGvWXl/A8rIohsc+by2N
+MVr8w8iw1OdwbxI0LyC5siVgn+aER5qryYlpdeKR0/F2LuWX
+-----END CERTIFICATE-----
+private_key         -----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAwUg8u4KXpEL+kaPKfuEs5vImrx79ykcjqDGDr1HUZU/tik+l
+HGRnPXNES2Etcr09kLmJugnhS7PprGFP5lXSxv4ofGJQojhvbg8mYgjagPjDpMZM
+IAoU3zYHjlkK/LqNifQmld24z66LRO8HhmivyeVGA0x8bGcTE6JvrXTAgB6JfHfP
+PHKlVLx1zrYdO/zijuaKygXLMRYatCAMBETU95BsSHHzhb4ViXzVJ4oXWTI6vLvw
+gWg9zISlNkp+XuNBRiyriKBdKTMYbz5yQEYs+d7ADDLo6EMVYIiLEPQ0/KYYTSl6
+rn4zmBFFCdIcv6rsvv4uAdNEjzbDjnA/LzrM/epoWQvYpOAL89MXQSmNFGnlCz5s
+7XSVtPHRGmNQ0iQf5PKzzLuFWQBYMXhYI+G8gPVMENeGjCQmVh2WB3Bm5CY3HRRA
+tWYSAiiBzjtCkqF6cQ/cSENVFmL6HoKkgUqr3nGmH+sudpIO4W+K4I1RXQSloQQh
+idUWqp25wQafkJOQAJAsmH1KV1zZqWAe2pCFy3t4ky+dDJGok3fhhMQV4XwmL8G6
+SGbt+WwLq8Kd/p0k03icOHdju7NS+IqnUNuHxEWMBC/Hri885sbI76rJjdXB82zL
+pawo1U92TQTxYjJbaAyV8iQ7LCkzOJzqca/hcQYgqW7n3MtrQyeHm7LzMYkCAwEA
+AQKCAgBL3AhKKBVQWSMFEl4VslcnRX89WFKPo6AxEU3374wHP3mhwWSyYg3LJoR1
+eWyXDgMt3ERcCiisx649A+ySILkbdQF64DN5l+DUN4n/DC6GVBylfVa/dHWArfoF
+Opl/W9DVhkfmpiE1EfKDWbWAYXItMZlrDgf/m+z21dgzIhGzt0iK25MwzGZrfZRX
+T07mDnj1UTLD28ZGO8C7VaChxEo56Cs3u9GyekqFrcMTQ7WqQnafQLxCbiFjNeSK
+DG7Q2yzxV/LzKs2lr/I1JzM8Ws6oO27w2sJi9oFbY/wA6XgqeR4sms0V0154nr6T
+/i1eZL2KsMRp3vuXogzayN9jsBZoG3gXBE83nNK9/rXv7ExFXtlKTtzNPpJxQKYb
+YZ2LJf93vmmTYJLagTQxXHJKc2BXJJj3f09/0bXztr/gJDohTYZSuYA/c3H+ISl+
+AUZq4YI4hGOZi5e1iZYP1mUD9U42q107fXrb8HkVihaTptT2IPhYQtf+cRUpg84K
+yvAOp0VQm4xA+/NmKbV5buXYSsYh7ASTTc1LfwhsBNlsc5OUA8+EQ9GooJvS80wk
+xvsTeJ0Mml9KleY6Hw69JmZQEjbsQmLajJy4kvMQmT2NusJH/pbKrcDsazMjKqY8
+OMy+lsjUOp67mGvU7dxJC6ItkJfIpEWkJIjRUy/mF8gqSeI96QKCAQEA5YXZgsU/
+osFQZLY+qPe1tzUD/JYnwEmd4mTD/imNr+O0ZWAL8zqR8VtQsaeeQm9ktsUx3yyT
+GcXxwUiP1v4iFi9WryImD25rlGCbSNryUbf21XJec9DGptVYMxU5T1WDbfnVkX/7
+reWc8wnmhRDJ8/9rhjtlE9jUuJvs+rZt5n8Uz5t3cJqvsGTA1KcW4iU5l3nziAWj
+ZJebuZWrFgkL30cU4Py1Z1xS4tuNHeln0pF7IzKESSWFdoDB/8WBNQ1RqpscFGFn
+kPU/HirRbyT/S57v192lEHrKn0OoXketQqFqkc/xfRkVwD7bRske8/WCWcroUJl+
+dsuKGEVH3USD8wKCAQEA15Qlj2xVGJAjlf2oK6BcYLtIfBwzSc7PEojmq5u+ougS
+tyeV7NXsdbD+d95f3ZOW2b77jRe3nsWJKH5dgKoXZj5F1FbxE2KVDenAZ7OaQtml
+k1QtdNEI1v/qg4DtEmLBtYvbQK1fAsPe4PvajFYukI4SWO6/7LLKzFbKdl/0C4Qs
+QZVdFNfsBFJSYHCqkxpbhzY5t3hEK0uoVD9MEJSNPmgIHSxcnWRuMXSDVREBDEwS
+kmc+96KX4SEnn0pJ3NRQje6RhmWbb/bYNEpeFecNaAL0P9bUwYEIV3Je6bvOf9Nb
+71kouvbhRC17u36vrvMvdr9d7eg8kkch3QQVWhsfkwKCAQEAlouGsZmDNdOqUYSf
+8OAZFoP1i3VJuXwPzPDfBRRoVNf7+QpYjD78ftywPvZ8fYLnAmKxZXqtOZh2C5r2
+jcO+w+Jk7xZs9G4urfH3qH/DtQn/It2TSk/EHKWO5mKjZn/mZvoZtQfHIraajWcP
+BnSOojYEZtUKZUwxqqzLcV67ExaDpfCJFRjA5+gN+u1luwtDjTF2JN/d3hr7D201
+/IwOd3L+JNxcd+E8lIQBOX9gk+LMa7e0wO2VbrbhiEwZhZyo1khK0Kta7N+PeNAI
+8ufHc+hZ1LMSk46W3IPaKYzF/hA2AFHuSWlstN4FoZZFcSq1RwQqAMPNCUpT17uJ
+eX55NQKCAQEAuIX2IG156Sx3SUt1RuJcL/Aeex0oSWTbmeHUj88fvhEm897OVYpG
+e/aj2bZeGCrcVEVEy+AhK6WpYR/IqPjuTnW/D6Hbd9xJ+T67kggJYm8papIC1pqW
+FnG3KhiQ08v0QpETeqjrSlKd07W/u5+I+/Kfgb/aR6BCNeWUJv66xaC8wOY4Zj7r
+pkdQe3v0hTVqYrHndUNcFjMMQhBr60U8IM6rI011eMMeDvbL82Q6oWv7+ZSmMRDb
+L7hRUeckkgCpctNhfMg74/pF1XxSTC0ZLI5awsoAEiGAIlmjJC2882zWpGiMlHv9
+FX5ZCoPFnNpLJjlnDNxb/FkmgyebnyTYQQKCAQB/ef3oo/zb7OzESaIJrig52s2w
+4nyyvq6CcLZVMZ/8jN/3yU/SlzHpjdjTzS0ZFNCNPZyQKF5K9HXQAReTPBcobugc
+hlJc/EKAFxw2CZlH58qB2GMgUO0ZetHLiM+KU+AIhI/Hd+6iDUtFEduQSiWHzQth
+0F5bVH1MywUJIAXMvW4DOJetEqwHzYZ42PpJv8maWuqtaGsgv9wbDSdNy+ln5tya
+ubm4S+tIzeia5ucXFmy2xwWEOBATllxvNlBrDrwBCTgNDpJw1clo5Zz2tH1LGm/5
+G3bLC5clv3E3T/EXkst3LhcUIbRrsoQTIPeDQIyYqAurzECNCgfmyK5arNU4
+-----END RSA PRIVATE KEY-----
+private_key_type    rsa
+serial_number       6e:bb:24:e5:a1:5a:15:9e:4c:07:d6:9e:4b:d0:e3:5e:57:04:bc:5b
+

2.4 Vault에 인증받기 위한 Kubernetes 인증 방식 구성

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+

Vault는 Kubernetes의 Service Account 토큰으로 인증할 수 있는 Kubernetes 인증 방식을 제공합니다. CSI 드라이버가 Vault에 저장된 시크릿 정보에 접근하여 시크릿을 획득하는 과정에서 Vault에 대한 인증/인가가 요구되므로 Kubernetes상의 리소스에서는 Kubernetes 인증 방식을 통해 Kubernetes의 방식으로 인증 받는 워크플로를 구성합니다.

Vault에 Kubernetes 인증 방식을 활성화 합니다. (이미 구성된 경우 실패합니다.)

vault auth enable kubernetes
+

Kubernetes API 주소를 Kubernetes 인증 방식 구성에 설정 합니다. 이 경우 자동으로 Vault Pod를 위한 자체 Service Account를 사용합니다.

Internal Vault
vault write auth/kubernetes/config \
+    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
+

생성할 Kubernetes 인증 방식의 롤 정의에서 사용되는 정책을 구성합니다. 생서한 Vault KV, PKI의 경로에 저장된 시크릿을 읽고 발행할 수 있는 정책 입니다.

Linux/MacOS
vault policy write injection-app - <<EOF
+path "for-injection/data/my-pass" {
+  capabilities = ["read"]
+}
+
+path "pki/issue/my-role" {
+  capabilities = ["create", "update"]
+}
+EOF
+

예제의 롤 정의에서는 허용할 Service Account와 Kubernetes Namespace, 부여하는 정책으로 앞서 생성한 injection-app 정책을 할당합니다. 인증된 이후 유효 기간은 20분으로 설정 합니다.

vault write auth/kubernetes/role/injection \
+    bound_service_account_names=webapp-vault \
+    bound_service_account_namespaces=default \
+    policies=injection-app \
+    ttl=20m
+

2.5 Sidecar 적용된 애플리케이션 적용

롤에서 정의한 허용하는 Service Account를 생성합니다.

kubectl create sa webapp-vault
+

다음과 같이 정의된 Deploymentvault-sidecar-deployment.yml 로 정의합니다.

apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: webapp-injection
+  labels:
+    app: issues
+spec:
+  selector:
+    matchLabels:
+      app: issues
+  replicas: 1
+  template:
+    metadata:
+      annotations:
+        vault.hashicorp.com/agent-inject: "true"
+        vault.hashicorp.com/agent-inject-status: "update"
+        # KV의 정적 시크릿 저장 대상 파일과 텔플릿 정의
+        vault.hashicorp.com/agent-inject-secret-my-config.txt: "for-injection/data/my-pass"
+        vault.hashicorp.com/agent-inject-template-my-config.txt: |
+          {{- with secret "for-injection/data/my-pass" -}}
+          password={{ .Data.data.password }}
+          {{- end }}
+        # PKI의 동적 시크릿 저장 대상 파일과 템플릿 정의 - Cert
+        vault.hashicorp.com/agent-inject-secret-cert.pem: "pki/issue/my-role"
+        vault.hashicorp.com/agent-inject-template-cert.pem: |
+          {{- with secret "pki/issue/my-role" "common_name=test.example.com" "ttl=30s" -}}
+          {{ .Data.certificate }}
+          {{ .Data.issuing_ca }}
+          {{- end }}
+        # PKI의 동적 시크릿 저장 대상 파일과 템플릿 정의 - Key
+        vault.hashicorp.com/agent-inject-secret-key.pem: "pki/issue/my-role"
+        vault.hashicorp.com/agent-inject-template-key.pem: |
+          {{- with secret "pki/issue/my-role" "common_name=test.example.com" "ttl=30s" -}}
+          {{ .Data.private_key }}
+          {{- end }}
+        # Vault의 Kubernetes인증으로 등록되어있는 Role 이름
+        vault.hashicorp.com/role: "injection"
+        vault.hashicorp.com/template-static-secret-render-interval: "10s"
+      labels:
+        app: issues
+    spec:
+      serviceAccountName: webapp-vault
+      containers:
+      - name: webapp
+        image: jweissig/app:0.0.1
+

spec.template.metadata.annotations에 정의된 vault.hashicorp.com의 설명은 다음과 같습니다.

  • vault.hashicorp.com/agent-inject : Vault Agent의 사이드카 구성 여부를 지정
  • vault.hashicorp.com/agent-inject-secret-\<filename> : filename영역에 문자 값으로 자동 랜더링하여 저장
  • vault.hashicorp.com/agent-inject-template-\<filename> : filename영역의 문자값으로 파일을 생성할 때 사용자 정의 방식이 필요한 경우 사용
  • vault.hashicorp.com/role : kubernetes 인증에 정의한 롤 이름 지정
  • vault.hashicorp.com/template-static-secret-render-interval : 정적인 시크릿에 대한 검사 주기 설정

정의한 vault-sidecar-deployment.yml를 적용합니다.

kubectl apply -f vault-sidecar-deployment.yml
+

생성된 Pod를 확인 합니다.

$ kubectl get pods
+
+NAME                                   READY   STATUS    RESTARTS       AGE
+webapp-injection-7768d64fd9-89kqn      2/2     Running   0              9m18s
+

Pod 내에 애플리케이션 컨테이너 webapp과 Vault Agent인 vault-agent 사이드카 컨테이너가 생성됨을 확인합니다.

$ kubectl get pods \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -o jsonpath='{.spec.containers[*].name}'
+
+webapp vault-agent
+

Pod 내 생성된 작용된 시크릿을 확인 합니다.

$ kubectl exec \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -c webapp -- cat /vault/secrets/my-config.txt
+
+password=my-secret-password-v1
+
$ kubectl exec \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -c webapp -- cat /vault/secrets/cert.pem
+
+-----BEGIN CERTIFICATE-----
+MIIESDCCAzCgAwIBAgIUKJqdIZIXXjE021NJzxnhP3Lihu0wDQYJKoZIhvcNAQEL
+...생략...
+Mk7y7BOjoZzXiqioAtk61FrjRwrc4vgJk9ESVeMnuJA8SorCAp3iNyVZJvU=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIUR6Auk4MVpeis2oLq0StUwce/v/kwDQYJKoZIhvcNAQEL
+...생략...
+MVr8w8iw1OdwbxI0LyC5siVgn+aER5qryYlpdeKR0/F2LuWX
+-----END CERTIFICATE-----
+
$ kubectl exec \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -c webapp -- cat /vault/secrets/key.pem
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAwSWRRLQ17lpKGaWGCOVaFIafd4NCe8u3LLMKSflutwMh3e7l
+...생략...
+cpxBcJwE+orZbH2TJAjVvqyfRrYflOLA6gqkbZd5W6zQClN3orb/E8UzEmg=
+-----END RSA PRIVATE KEY-----
+

기본 마운트 경로가 /vault/secrets/<file-name> 이므로, 변경이 필요한경우 다음의 annotation 정의를 추가할 수 있습니다.

## 생략 ##
+spec:
+	template:
+    metadata:
+      annotations:
+      	# 생략
+        vault.hashicorp.com/agent-inject-file-database-config: "/some/secret/here.txt"
+        vault.hashicorp.com/secret-volume-path-database-config: "/app"
+

결과 : /app/some/secret/here.txt

2.6 정적 시크릿 자동 갱신

Vault Agent가 시크릿 데이터가 변경됨을 감지하고 대상 Pod에 저장되는 시크릿을 자동으로 갱신합니다.

정적 시크릿인 KV의 값을 변경해봅니다.

vault kv put for-injection/my-pass password="my-secret-password-v2"
+

다시 Pod내의 저장된 KV 시크릿 값을 확인합니다. 확인 간격을 10초로 정의했으므로, 갱신이 안된경우 다시 확인해봅니다.

$ kubectl exec \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -c webapp -- cat /vault/secrets/my-config.txt
+
+password=my-secret-password-v2
+

2.7 동적 시크릿 자동 갱신

Vault가 생성하는 동적 시크릿은 lease에서 생성 후 수명주기를 관리합니다. Vault Agent는 생성한 동적 시크릿의 수명주기를 확인하여 만료되기 전 자동 연장(Renewal) 및 자동 갱신 합니다.

예제의 인증서의 수명을 60s로 정의하였으므로, 4/5 지점인 약 48초가 지나면 자동갱신됩니다. 앞서 랜더링된 인증서 확인 커맨드를 사용하여 변경되는 인증서를 확인합니다.

$ kubectl exec \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -c webapp -- cat /vault/secrets/cert.pem
+
+-----BEGIN CERTIFICATE-----
+...생략...
+Mk7y7BOjoZzXiqioAtk61FrjRwrc4vgJk9ESVeMnuJA8SorCAp3iNyVZJvU=
+-----END CERTIFICATE-----
+...생략...
+






 


$ kubectl exec \
+  $(kubectl get pod -l app=issues -o jsonpath="{.items[0].metadata.name}") \
+  -c webapp -- cat /vault/secrets/cert.pem
+
+-----BEGIN CERTIFICATE-----
+...생략...
+uDtXjqrZYtEI47dZjsVLxnBDLBoTRmzyxtywRezmvL2aMA5r9Z6WhhmFY2o=
+-----END CERTIFICATE-----
+...생략...
+






 


3. VSO Vault Secret Operator

참고 1 : https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operatoropen in new window

참고 2 : https://developer.hashicorp.com/vault/docs/platform/k8s/vsoopen in new window

VSO를 사용하면 Pod가 기존 Kubernetes Secrets을 활용하여 시크릿을 사용하던 방식으로 Vault의 시크릿을 사용하게 됩니다. VSO는 CRD(Custom Resource Definitions)를 구성하여 Vault의 시크릿을 Kubernetes Secrets으로 동기화하는 동작을 수행합니다.

애플리케이션 수준, 또는 CSI와 사이드카 인젝션 방식의 구성 또한 Vault 시크릿을 사용하기 위한 다양한 방안을 제공했지만 애플리케이션 또는 기존 배포의 정의에 새로운 구성이 필요합니다. VSO는 Kubernetes의 Secret을 활용하여 기존 사용 경험을 유지하지만 Vault의 시크릿을 사용할 수 있는 방안을 제공합니다.

현재 지원되는 버전은 다음과 같습니다. 변경 사항은 링크를 참고하세요. (지원되는 Kubernetes 버전open in new window)

1.281.271.261.251.24

3.1 VSO를 위한 Vault 구성 (Helm)

Kubernetes에 Sidecar Injector 서비스를 구성을 설치해야 합니다. 이 구성이 설치되면 annotation에 정의된 내용이 vault-k8s webhook을 호출하여 Pod를 재정의하여 Vault Agent를 사이드카로 주입(Injection)합니다.

먼저 Vault Helm 차트를 등록합니다.

helm repo add hashicorp https://helm.releases.hashicorp.com
+

Vault Helm 차트를 사용하여 1) Kubernetes에 Vault를 설치하는 구성 또는 2) 외부 Vault와 연계하는 구성으로 설치 할 수 있습니다.

Vault를 Kubernetes에 설치 (Option)

Vault가 별도 구성되어있지 않은 경우 해당 Kubernetes에 Vault 서버를 구성합니다.

helm install vault hashicorp/vault \
+    --set "server.dev.enabled=true" \
+    --set "injector.enabled=false" \
+    --set "csi.enabled=false"
+

아래와 같이 helm 설치 시 값을 정의한 vault-operator-values.yaml 파일을 생성합니다.

internal Vault
defaultVaultConnection:
+  enabled: true
+  address: http://vault.default.svc.cluster.local:8200
+  skipTLSVerify: false
+  spec:
+  template:
+    spec:
+      containers:
+      - name: manager
+        args:
+        - "--client-cache-persistence-model=direct-encrypted"
+

다음을 실행하여 정의한 helm의 값으로 설치를 진행합니다.

helm install vault-secrets-operator \ hashicorp/vault-secrets-operator \
+    -n vault-secrets-operator-system \
+    --create-namespace \
+    --values vault-operator-values.yaml
+

설치가 완료되면 다음의 Pod를 확인할 수 있습니다.

$ kubectl get pods -n vault-secrets-operator-system
+
+NAME                                                         READY   STATUS    RESTARTS   AGE
+vault-secrets-operator-controller-manager-67879cb4d4-wzs6c   2/2     Running   0          4h22m
+

3.2 VSO에서 사용할 Vault 정적 시크릿 정의

VSO에서 사용할 KV 시크릿 엔진을 활성화합니다.

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+
vault secrets enable -path for-vso -version=2 kv
+

for-vso/my-pass 경로에 password 값을 저장 합니다.

$ vault kv put for-vso/my-pass password="my-secret-password-v1"
+
+==== Secret Path ====
+for-vso/data/my-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-27T04:24:18.430362Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            1
+
저장된 시크릿 값을 확인
CLI
$ vault kv get for-vso/my-pass
+
+==== Secret Path ====
+for-vso/data/my-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-27T04:24:18.430362Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            1
+
+====== Data ======
+Key         Value
+---         -----
+password    my-secret-password-v1
+

3.3 VSO에서 사용할 Vault PKI 시크릿 정의

PKI 시크릿의 경우 동적 시크릿으로, 발급 후 만료되기 전 Kubernetes Secret의 내용을 갱신 합니다.

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+
vault secrets enable -path=pki pki
+vault secrets tune -max-lease-ttl=86400s -default-lease-ttl=3600s pki
+

루트 인증서를 생성합니다.

vault write -field=certificate pki/root/generate/internal \
+  common_name="test" \
+  ttl="86400h"
+

생성된 루트 인증서에 기반한 PKI 롤을 생성합니다. 예제에서는 동적 시크릿의 교체를 확인하기 위해 주기(ttl, max_ttl)를 짧게 구성합니다.

vault write pki/roles/my-role \
+  key_bits=4096 \
+  ttl="60s" \
+  max_ttl="60s" \
+  allow_ip_sans=true \
+  allowed_domains="example.com,my.domain" \
+  allow_subdomains=true
+
PKI 인증서 발급 확인
CLI
$ vault write pki/issue/my-role common_name=my-test.example.com
+
+Key                 Value
+---                 -----
+ca_chain            [-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIUR6Auk4MVpeis2oLq0StUwce/v/kwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAxMEdGVzdDAeFw0yMzEwMjYyMzUyNDlaFw0yMzEwMjcyMzUz
+MTlaMA8xDTALBgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDXmlaX2Qu/rF+AFgWqJBCaNPziJrwsBB8nEUQh2S2XRMD9osoliWpaS33i
+iFAxc++Mec/FzKIsB7TskYWyFlv/GPmFG5gKdYfMuEMAgHrxM3OYWibQq0hDajJn
+oOcT1DwCx0mZqYdGoFVcw2TdW1vqgKRMx1vWBskaJHoGGpRvEPe7cYLz8itwqQfR
+7zkcVw3vdK6U50I7NnV/1wC+WOuwZ6IL5DKC1v3DtE5CrYKf/sBwDZfcdwFEjLpQ
+3hSXlVtv6t9E7QABcYqFkP5iebisNVP71L1Qk7oCuk4zqKpkbFytD6Nlf1LMRSFj
+SDt+aPuoqlmKrNtGsNcTqlW8k39HAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTwEbHemyl86vBdxfMICjaKOJIoJzAf
+BgNVHSMEGDAWgBTwEbHemyl86vBdxfMICjaKOJIoJzAPBgNVHREECDAGggR0ZXN0
+MA0GCSqGSIb3DQEBCwUAA4IBAQB2y9QDCSNlr+j4v5H/7s4aZR8EWqbSdGc6F9w2
+FrR/bwo9eIxWiABFn/SH+bqHSK7fw4TMPJ0rEnJxBEvIPpA2kvGIxsBzPAdPzQ+A
+4F6tSJtiXB5A/7IZn9SQLUrcmcA5SuBGN9GjmPLpYSQg2ykJsTlExkYdg4co2sYV
+0F1gE5SXFGEmNTwFlpPSmKY6Zs8fKJZrzf+feCXFRlD/u+I4vftJqu7pwxZvPifR
+gPWi3kuzj71b4rEkZW3zNCP9XOtkCO/pNW2hJnc0QiTgQGvWXl/A8rIohsc+by2N
+MVr8w8iw1OdwbxI0LyC5siVgn+aER5qryYlpdeKR0/F2LuWX
+-----END CERTIFICATE-----]
+certificate         -----BEGIN CERTIFICATE-----
+MIIETjCCAzagAwIBAgIUbrsk5aFaFZ5MB9aeS9DjXlcEvFswDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAxMEdGVzdDAeFw0yMzEwMjcwMDU1MDlaFw0yMzEwMjcwMDU2
+MDJaMB4xHDAaBgNVBAMTE215LXRlc3QuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQDBSDy7gpekQv6Ro8p+4Szm8iavHv3KRyOoMYOv
+UdRlT+2KT6UcZGc9c0RLYS1yvT2QuYm6CeFLs+msYU/mVdLG/ih8YlCiOG9uDyZi
+CNqA+MOkxkwgChTfNgeOWQr8uo2J9CaV3bjProtE7weGaK/J5UYDTHxsZxMTom+t
+dMCAHol8d888cqVUvHXOth07/OKO5orKBcsxFhq0IAwERNT3kGxIcfOFvhWJfNUn
+ihdZMjq8u/CBaD3MhKU2Sn5e40FGLKuIoF0pMxhvPnJARiz53sAMMujoQxVgiIsQ
+9DT8phhNKXqufjOYEUUJ0hy/quy+/i4B00SPNsOOcD8vOsz96mhZC9ik4Avz0xdB
+KY0UaeULPmztdJW08dEaY1DSJB/k8rPMu4VZAFgxeFgj4byA9UwQ14aMJCZWHZYH
+cGbkJjcdFEC1ZhICKIHOO0KSoXpxD9xIQ1UWYvoegqSBSqvecaYf6y52kg7hb4rg
+jVFdBKWhBCGJ1RaqnbnBBp+Qk5AAkCyYfUpXXNmpYB7akIXLe3iTL50MkaiTd+GE
+xBXhfCYvwbpIZu35bAurwp3+nSTTeJw4d2O7s1L4iqdQ24fERYwEL8euLzzmxsjv
+qsmN1cHzbMulrCjVT3ZNBPFiMltoDJXyJDssKTM4nOpxr+FxBiCpbufcy2tDJ4eb
+svMxiQIDAQABo4GSMIGPMA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEF
+BQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFGO3lOOstANAUseQaJmGMnCVQkw8MB8G
+A1UdIwQYMBaAFPARsd6bKXzq8F3F8wgKNoo4kignMB4GA1UdEQQXMBWCE215LXRl
+c3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBADT1aqab6RhLGuAvUgIS
+3lZ+B/ltWFQroFRgnfQArlMrVnCE1/7LAH+i7n8Ev7ixK0xP2CYRLwm8McLBEIjm
+qWB8ZXJJq4gXqZ6i5kIFvuRILkesSGJbs49TdeAMz6lyJd/BQmzM/uAhnqMrhlRt
+H6ZWnC5Z7dRGWT/yIlKL6kMcmxqEZCTt7j76V/8CRRUtxHtEgt4B4R/0lykWM8Ed
+HMok6crNYk94Jg/S8MWZlUHtCjDeXMd3mhDVQKaBNeLGjyugDF8KLVpcIMjEjglk
+UDG/bqxqwS2/jVUnDFvejbrOkJ/e3NefZa52/fZlXwqnwAlumtHOgEk3j00rHQSA
+/04=
+-----END CERTIFICATE-----
+expiration          1698368162
+issuing_ca          -----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIUR6Auk4MVpeis2oLq0StUwce/v/kwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAxMEdGVzdDAeFw0yMzEwMjYyMzUyNDlaFw0yMzEwMjcyMzUz
+MTlaMA8xDTALBgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDXmlaX2Qu/rF+AFgWqJBCaNPziJrwsBB8nEUQh2S2XRMD9osoliWpaS33i
+iFAxc++Mec/FzKIsB7TskYWyFlv/GPmFG5gKdYfMuEMAgHrxM3OYWibQq0hDajJn
+oOcT1DwCx0mZqYdGoFVcw2TdW1vqgKRMx1vWBskaJHoGGpRvEPe7cYLz8itwqQfR
+7zkcVw3vdK6U50I7NnV/1wC+WOuwZ6IL5DKC1v3DtE5CrYKf/sBwDZfcdwFEjLpQ
+3hSXlVtv6t9E7QABcYqFkP5iebisNVP71L1Qk7oCuk4zqKpkbFytD6Nlf1LMRSFj
+SDt+aPuoqlmKrNtGsNcTqlW8k39HAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTwEbHemyl86vBdxfMICjaKOJIoJzAf
+BgNVHSMEGDAWgBTwEbHemyl86vBdxfMICjaKOJIoJzAPBgNVHREECDAGggR0ZXN0
+MA0GCSqGSIb3DQEBCwUAA4IBAQB2y9QDCSNlr+j4v5H/7s4aZR8EWqbSdGc6F9w2
+FrR/bwo9eIxWiABFn/SH+bqHSK7fw4TMPJ0rEnJxBEvIPpA2kvGIxsBzPAdPzQ+A
+4F6tSJtiXB5A/7IZn9SQLUrcmcA5SuBGN9GjmPLpYSQg2ykJsTlExkYdg4co2sYV
+0F1gE5SXFGEmNTwFlpPSmKY6Zs8fKJZrzf+feCXFRlD/u+I4vftJqu7pwxZvPifR
+gPWi3kuzj71b4rEkZW3zNCP9XOtkCO/pNW2hJnc0QiTgQGvWXl/A8rIohsc+by2N
+MVr8w8iw1OdwbxI0LyC5siVgn+aER5qryYlpdeKR0/F2LuWX
+-----END CERTIFICATE-----
+private_key         -----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAwUg8u4KXpEL+kaPKfuEs5vImrx79ykcjqDGDr1HUZU/tik+l
+HGRnPXNES2Etcr09kLmJugnhS7PprGFP5lXSxv4ofGJQojhvbg8mYgjagPjDpMZM
+IAoU3zYHjlkK/LqNifQmld24z66LRO8HhmivyeVGA0x8bGcTE6JvrXTAgB6JfHfP
+PHKlVLx1zrYdO/zijuaKygXLMRYatCAMBETU95BsSHHzhb4ViXzVJ4oXWTI6vLvw
+gWg9zISlNkp+XuNBRiyriKBdKTMYbz5yQEYs+d7ADDLo6EMVYIiLEPQ0/KYYTSl6
+rn4zmBFFCdIcv6rsvv4uAdNEjzbDjnA/LzrM/epoWQvYpOAL89MXQSmNFGnlCz5s
+7XSVtPHRGmNQ0iQf5PKzzLuFWQBYMXhYI+G8gPVMENeGjCQmVh2WB3Bm5CY3HRRA
+tWYSAiiBzjtCkqF6cQ/cSENVFmL6HoKkgUqr3nGmH+sudpIO4W+K4I1RXQSloQQh
+idUWqp25wQafkJOQAJAsmH1KV1zZqWAe2pCFy3t4ky+dDJGok3fhhMQV4XwmL8G6
+SGbt+WwLq8Kd/p0k03icOHdju7NS+IqnUNuHxEWMBC/Hri885sbI76rJjdXB82zL
+pawo1U92TQTxYjJbaAyV8iQ7LCkzOJzqca/hcQYgqW7n3MtrQyeHm7LzMYkCAwEA
+AQKCAgBL3AhKKBVQWSMFEl4VslcnRX89WFKPo6AxEU3374wHP3mhwWSyYg3LJoR1
+eWyXDgMt3ERcCiisx649A+ySILkbdQF64DN5l+DUN4n/DC6GVBylfVa/dHWArfoF
+Opl/W9DVhkfmpiE1EfKDWbWAYXItMZlrDgf/m+z21dgzIhGzt0iK25MwzGZrfZRX
+T07mDnj1UTLD28ZGO8C7VaChxEo56Cs3u9GyekqFrcMTQ7WqQnafQLxCbiFjNeSK
+DG7Q2yzxV/LzKs2lr/I1JzM8Ws6oO27w2sJi9oFbY/wA6XgqeR4sms0V0154nr6T
+/i1eZL2KsMRp3vuXogzayN9jsBZoG3gXBE83nNK9/rXv7ExFXtlKTtzNPpJxQKYb
+YZ2LJf93vmmTYJLagTQxXHJKc2BXJJj3f09/0bXztr/gJDohTYZSuYA/c3H+ISl+
+AUZq4YI4hGOZi5e1iZYP1mUD9U42q107fXrb8HkVihaTptT2IPhYQtf+cRUpg84K
+yvAOp0VQm4xA+/NmKbV5buXYSsYh7ASTTc1LfwhsBNlsc5OUA8+EQ9GooJvS80wk
+xvsTeJ0Mml9KleY6Hw69JmZQEjbsQmLajJy4kvMQmT2NusJH/pbKrcDsazMjKqY8
+OMy+lsjUOp67mGvU7dxJC6ItkJfIpEWkJIjRUy/mF8gqSeI96QKCAQEA5YXZgsU/
+osFQZLY+qPe1tzUD/JYnwEmd4mTD/imNr+O0ZWAL8zqR8VtQsaeeQm9ktsUx3yyT
+GcXxwUiP1v4iFi9WryImD25rlGCbSNryUbf21XJec9DGptVYMxU5T1WDbfnVkX/7
+reWc8wnmhRDJ8/9rhjtlE9jUuJvs+rZt5n8Uz5t3cJqvsGTA1KcW4iU5l3nziAWj
+ZJebuZWrFgkL30cU4Py1Z1xS4tuNHeln0pF7IzKESSWFdoDB/8WBNQ1RqpscFGFn
+kPU/HirRbyT/S57v192lEHrKn0OoXketQqFqkc/xfRkVwD7bRske8/WCWcroUJl+
+dsuKGEVH3USD8wKCAQEA15Qlj2xVGJAjlf2oK6BcYLtIfBwzSc7PEojmq5u+ougS
+tyeV7NXsdbD+d95f3ZOW2b77jRe3nsWJKH5dgKoXZj5F1FbxE2KVDenAZ7OaQtml
+k1QtdNEI1v/qg4DtEmLBtYvbQK1fAsPe4PvajFYukI4SWO6/7LLKzFbKdl/0C4Qs
+QZVdFNfsBFJSYHCqkxpbhzY5t3hEK0uoVD9MEJSNPmgIHSxcnWRuMXSDVREBDEwS
+kmc+96KX4SEnn0pJ3NRQje6RhmWbb/bYNEpeFecNaAL0P9bUwYEIV3Je6bvOf9Nb
+71kouvbhRC17u36vrvMvdr9d7eg8kkch3QQVWhsfkwKCAQEAlouGsZmDNdOqUYSf
+8OAZFoP1i3VJuXwPzPDfBRRoVNf7+QpYjD78ftywPvZ8fYLnAmKxZXqtOZh2C5r2
+jcO+w+Jk7xZs9G4urfH3qH/DtQn/It2TSk/EHKWO5mKjZn/mZvoZtQfHIraajWcP
+BnSOojYEZtUKZUwxqqzLcV67ExaDpfCJFRjA5+gN+u1luwtDjTF2JN/d3hr7D201
+/IwOd3L+JNxcd+E8lIQBOX9gk+LMa7e0wO2VbrbhiEwZhZyo1khK0Kta7N+PeNAI
+8ufHc+hZ1LMSk46W3IPaKYzF/hA2AFHuSWlstN4FoZZFcSq1RwQqAMPNCUpT17uJ
+eX55NQKCAQEAuIX2IG156Sx3SUt1RuJcL/Aeex0oSWTbmeHUj88fvhEm897OVYpG
+e/aj2bZeGCrcVEVEy+AhK6WpYR/IqPjuTnW/D6Hbd9xJ+T67kggJYm8papIC1pqW
+FnG3KhiQ08v0QpETeqjrSlKd07W/u5+I+/Kfgb/aR6BCNeWUJv66xaC8wOY4Zj7r
+pkdQe3v0hTVqYrHndUNcFjMMQhBr60U8IM6rI011eMMeDvbL82Q6oWv7+ZSmMRDb
+L7hRUeckkgCpctNhfMg74/pF1XxSTC0ZLI5awsoAEiGAIlmjJC2882zWpGiMlHv9
+FX5ZCoPFnNpLJjlnDNxb/FkmgyebnyTYQQKCAQB/ef3oo/zb7OzESaIJrig52s2w
+4nyyvq6CcLZVMZ/8jN/3yU/SlzHpjdjTzS0ZFNCNPZyQKF5K9HXQAReTPBcobugc
+hlJc/EKAFxw2CZlH58qB2GMgUO0ZetHLiM+KU+AIhI/Hd+6iDUtFEduQSiWHzQth
+0F5bVH1MywUJIAXMvW4DOJetEqwHzYZ42PpJv8maWuqtaGsgv9wbDSdNy+ln5tya
+ubm4S+tIzeia5ucXFmy2xwWEOBATllxvNlBrDrwBCTgNDpJw1clo5Zz2tH1LGm/5
+G3bLC5clv3E3T/EXkst3LhcUIbRrsoQTIPeDQIyYqAurzECNCgfmyK5arNU4
+-----END RSA PRIVATE KEY-----
+private_key_type    rsa
+serial_number       6e:bb:24:e5:a1:5a:15:9e:4c:07:d6:9e:4b:d0:e3:5e:57:04:bc:5b
+

3.4 VSO에서 사용할 Vault 동적 시크릿 정의

예제에서는 동적 시크릿으로 PostgreSQL 데이터베이스에 대한 Database 시크릿 엔진을 구성합니다. 구성에 앞서 PostgreSQL을 설치해야 합니다. 먼저 설치될 Kubernetes Namespace를 생성합니다.

kubectl create ns postgres
+

bitnami에서 제공하는 PostgreSQL Helm Chart를 설치 합니다.

helm repo add bitnami https://charts.bitnami.com/bitnami
+

PostgreSQL을 설치 합니다.

helm upgrade --install postgres bitnami/postgresql --namespace postgres --set auth.audit.logConnections=true  --set auth.postgresPassword=secret-pass
+

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+

Database 시크릿 엔진을 활성화 합니다.

vault secrets enable -path=demo-db database
+

Vault에 PostgreSQL의 동적 시크릿을 위한 구성을 합니다.

PostgreSQL을 위한 롤을 생성합니다. 갱신 테스트를 위해 시크릿 수명을 1분으로 정의합니다.

vault write demo-db/roles/dev-postgres \
+  db_name=demo-db \
+  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
+    GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
+  backend=demo-db \
+  name=dev-postgres \
+  default_ttl="1m" \
+  max_ttl="1m"
+

동적으로 생성되는 Database 계정을 확인합니다.

$ vault read demo-db/creds/dev-postgres
+
+Key                Value
+---                -----
+lease_id           demo-db/creds/dev-postgres/V3kcRCCQQC3loq3MCO84UnWT
+lease_duration     1m
+lease_renewable    true
+password           1hmvH1a4hu-2DBrTjfzt
+username           v-token-dev-post-xhRYa0CK6DFLVMqkgikF-1698388424
+







 
 

3.5 Vault에 인증받기 위한 Kubernetes 인증 방식 구성

Kubernetes내의 Vault에서 CLI 사용

Kubernetes내에 배포된 Vault인 경우 다음과 같이 쉘을 실행할 수 있도록 Pod에 접근합니다. (Optional)

kubectl exec -it vault-0 -- /bin/sh
+

Vault는 Kubernetes의 Service Account 토큰으로 인증할 수 있는 Kubernetes 인증 방식을 제공합니다. CSI 드라이버가 Vault에 저장된 시크릿 정보에 접근하여 시크릿을 획득하는 과정에서 Vault에 대한 인증/인가가 요구되므로 Kubernetes상의 리소스에서는 Kubernetes 인증 방식을 통해 Kubernetes의 방식으로 인증 받는 워크플로를 구성합니다.

Vault에 Kubernetes 인증 방식을 활성화 합니다. (이미 구성된 경우 실패합니다.)

vault auth enable kubernetes
+

Kubernetes API 주소를 Kubernetes 인증 방식 구성에 설정 합니다. 이 경우 자동으로 Vault Pod를 위한 자체 Service Account를 사용합니다.

Internal Vault
vault write auth/kubernetes/config \
+    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
+

생성할 Kubernetes 인증 방식의 롤 정의에서 사용되는 정책을 구성합니다. 생서한 Vault KV, PKI의 경로에 저장된 시크릿을 읽고 발행할 수 있는 정책 입니다.

Linux/MacOS
vault policy write vso-app - <<EOF
+path "for-vso/data/my-pass" {
+  capabilities = ["read"]
+}
+
+path "pki/issue/my-role" {
+  capabilities = ["create", "update"]
+}
+
+path "demo-db/creds/dev-postgres" {
+   capabilities = ["read"]
+}
+EOF
+

예제의 롤 정의에서는 허용할 Service Account와 Kubernetes Namespace, 부여하는 정책으로 앞서 생성한 vso-app 정책을 할당합니다. 인증된 이후 유효 기간은 20분으로 설정 합니다.

vault write auth/kubernetes/role/vso \
+    bound_service_account_names=webapp-vault \
+    bound_service_account_namespaces=default \
+    policies=vso-app \
+    ttl=20m
+

3.6 VSO CRD "VaultAuth" 정의

VSO의 Secret 동기화를 위한 구성에 필요한 인증을 위한 리소스로 VaultAuth를 정의해야 합니다. 다음과 같이 vault-auth-static.yaml 파일을 작성합니다.

apiVersion: secrets.hashicorp.com/v1beta1
+kind: VaultAuth
+metadata:
+  name: static-auth
+  namespace: default
+spec:
+  kubernetes:
+    audiences:
+    - vault
+    role: vso
+    serviceAccount: webapp-vault
+    tokenExpirationSeconds: 600
+  method: kubernetes
+  mount: kubernetes
+

VaultAuth를 적용합니다.

kubectl apply -f vault-auth-static.yaml
+

3.7 VSO CRD - 정적 시크릿

시크릿 정의 중 정적 시크릿인 KV version 1, KV version 2를 정의하기위해서는 VaultStaticSecret를 사용합니다.

VaultStaticSecretopen in new window에서 정의하는 값은 다음과 같습니다.

필드설명
vaultAuthRef string지정되지 않은 경우 동일 Kubernetes Namespace의 default VaultAuth를 사용합니다. VaultAuth 리소스에 대한 VaultAuthRef에 네임스페이스 접두사를 붙일 수 있습니다(예: namespaceA/vaultAuthRefB).
namespace stringVault의 Namespace를 정의합니다.
mount string마운트 된 KV 시크릿 엔진 Path를 정의합니다.
path string대상 KV 시크릿 엔진 내의 저장된 시크릿 경로를 정의 합니다.
version integerKV version 2 인 경우 저장된 시크릿 버전을 정의 합니다.
refreshAfter string갱신 주기를 서정합니다.
hmacSecretData booleanHMACSecretData는 오퍼레이터가 시크릿 데이터의 HMAC를 계산할지 여부를 결정합니다.
rolloutRestartTargets애플리케이션이 변경된 Secret의 마운트된 값을 인지하지 못하는 경우 강재로 Rollout-Restart를 수행할 대상을 정의합니다.
destination저장할 Secret을 지정합니다.

Vault의 KV version 2를 적용하는 VaultStaticSecret 정의하는 static-secret.yaml 파일 내용은 다음과 같습니다.

apiVersion: secrets.hashicorp.com/v1beta1
+kind: VaultStaticSecret
+metadata:
+  name: vault-kv-app
+  namespace: default
+spec:
+  # Static 시크릿 엔진인 KV의 유형
+  type: kv-v2
+  # KV 엔진의 마운트 Path
+  mount: for-vso
+  # KV의 시크릿 저장 경로
+  path: my-pass
+  # k8s secret 이름 정의
+  destination:
+    name: secretkv
+    create: true
+  # 정적 시크릿의 변경 확인 주기
+  refreshAfter: 30s
+  # 생성한 Vault 인증을 위한 VaultAuth 리소스 이름
+  vaultAuthRef: static-auth
+

VaultStaticSecret을 적용합니다.

kubectl apply -f static-secret.yaml
+

적용이 완료되면 지정한 secretkv 이름으로 Secret 리소스가 생성되고, 동기화된 내용을 확인할 수 있습니다.

$ kubectl get secret
+
+NAME        TYPE        DATA   AGE
+secretkv    Opaque      2      4h55m
+
+$ kubectl get secret secretkv -o jsonpath='{.data.password}' | base64 --decode
+
+my-secret-password-v1
+



 



 

다음과 같이 저장된 시크릿을 업데이트 합니다.

$ vault kv put for-vso/my-pass password="my-secret-password-v2"
+
+==== Secret Path ====
+for-vso/data/my-pass
+
+======= Metadata =======
+Key                Value
+---                -----
+created_time       2023-10-27T05:43:54.906895Z
+custom_metadata    <nil>
+deletion_time      n/a
+destroyed          false
+version            2
+

Secret 리소스에 변경된 값이 동기화 되었는지 확인합니다.

$ kubectl get secret secretkv -o jsonpath='{.data.password}' | base64 --decode
+
+my-secret-password-v2
+

3.8 VSO CRD - PKI 시크릿

시크릿 정의 중 동적 시크릿인 PKI를 정의하기위해서는 VaultPKISecret를 사용합니다.

VaultPKISecretopen in new window에서 정의하는 값은 다음과 같습니다.

필드설명
vaultAuthRef string지정되지 않은 경우 동일 Kubernetes Namespace의 default VaultAuth를 사용합니다. VaultAuth 리소스에 대한 VaultAuthRef에 네임스페이스 접두사를 붙일 수 있습니다(예: namespaceA/vaultAuthRefB).
namespace stringVault의 Namespace를 정의합니다.
mount string마운트 된 KV 시크릿 엔진 Path를 정의합니다.
role stringPKI 시크릿 엔진 구성에서 사용할 롤 이름을 정의합니다.
revoke boolean정의한 리소스가 삭제되면 생성한 인증서도 취소(Revoke)할지의 여부를 정의합니다.
clear boolean정의한 리소스가 삭제되면 동기화 된 Secret 리소스도 삭제할지 여부를 정의합니다.
version integerKV version 2 인 경우 저장된 시크릿 버전을 정의 합니다.
expiryOffset string인증서를 갱신해야 하는 시기를 계산하는 데 오프셋(만료되기 이전 몇 시간 전)입니다.
issuerRef stringPKI 발급자에 대한 참조를 정의합니다.
rolloutRestartTargets애플리케이션이 변경된 Secret의 마운트된 값을 인지하지 못하는 경우 강재로 Rollout-Restart를 수행할 대상을 정의합니다.
destination저장할 Secret을 지정합니다.
commonName stringPKI 인증서 생싱시 요청할 CN 입니다.
altNames string array요청에 포함할 대체 이름 DNS 이름과 이메일 주소를 나열합니다.
ipSans string arrayPKI 인증서 생싱시 요청할 IP SANs 입니다.
uriSans string arrayPKI 인증서 생싱시 요청할 URI SANs 입니다.
otherSans string arrayPKI 인증서 생싱시 요청할 oid;type:value SANs 입니다.
ttl string인증서의 유효기간을 지정합니다.
format string인증서 형태를 지정합니다. (pem, der, pem_bundle)
privateKeyFormat string기본 값은 DER이며, 반환된 개인 키에 base64로 인코딩된 pkcs8 또는 PEM으로 인코딩된 pkcs8이 포함되도록 하려면 이 매개변수를 "pkcs8"로 지정합니다.
notAfter stringNotAfter 필드에 날짜 값을 지정합니다. 값 형식은 UTC 형식인 YYYY-MM-ddTHH:MM:SSZ로 지정해야 합니다.
excludeCNFromSans booleanDNS 또는 이메일 제목 대체 이름에서 CNFromSans를 제외합니다.

Vault의 PKI를 적용하는 VaultPKISecret 정의하는 pki-secret.yaml 파일 내용은 다음과 같습니다.

apiVersion: secrets.hashicorp.com/v1beta1
+kind: VaultPKISecret
+metadata:
+  name: vault-pki-app
+  namespace: default
+spec:
+  # PKI 시크릿 엔진의 마운트 Path
+  mount: pki
+  # 인증서를 발급할 PKI 롤
+  role: my-role
+  # 옵션
+  commonName: test.example.com
+  # 인증서 형태
+  format: pem
+  # 갱신 트리거 시간
+  expiryOffset: 2s
+  # 발급되는 인증서의 TTL
+  ttl: 30s
+  # k8s secret 이름 정의
+  destination:
+    name: secretpki
+    create: true
+  # 교체 발생시 Restart할 대상 지정
+  rolloutRestartTargets:
+    - kind: Deployment
+      name: vso-pki-demo
+  # 생성한 Vault 인증을 위한 VaultAuth 리소스 이름
+  vaultAuthRef: static-auth
+

VaultPKISecret을 적용합니다.

kubectl apply -f pki-secret.yaml
+

적용이 완료되면 지정한 secretpki 이름으로 Secret 리소스가 생성되고, 동기화된 내용을 확인할 수 있습니다. 여기서는 UNIXTIMESTAMP로 기록되는 expiration으로 확인해 봅니다.

$ kubectl get secret
+
+NAME         TYPE        DATA   AGE
+secretpki    Opaque      2      4h55m
+
+$ kubectl get secret secretpki -o jsonpath='{.data.expiration}' | base64 --decode
+
+1698386116
+
+### 시간이 흐른 뒤 새로 발급된 인증서의 만료 시간을 확인합니다.
+
+$ kubectl get secret secretpki -o jsonpath='{.data.expiration}' | base64 --decode
+
+1698386172
+



 



 





 

3.9 VSO CRD - 동적 시크릿

시크릿 정의 중 동적 시크릿인 PKI를 정의하기위해서는 VaultDynamicSecret를 사용합니다.

VaultDynamicSecretopen in new window에서 정의하는 값은 다음과 같습니다.

필드설명
vaultAuthRef string지정되지 않은 경우 동일 Kubernetes Namespace의 default VaultAuth를 사용합니다. VaultAuth 리소스에 대한 VaultAuthRef에 네임스페이스 접두사를 붙일 수 있습니다(예: namespaceA/vaultAuthRefB).
namespace stringVault의 Namespace를 정의합니다.
mount string마운트 된 KV 시크릿 엔진 Path를 정의합니다.
requestHTTPMethod stringVault에서 시크릿을 동기화할 때 사용할 요청 HTTPMethod이며, 기본은 GET 이므로 필요시 다른 요청 HTTPMethod를 정의합니다.
path stringVault에서 자격 증명을 가져올 경로이며, 마운트에 상대적입니다.
clear boolean정의한 리소스가 삭제되면 동기화 된 Secret 리소스도 삭제할지 여부를 정의합니다.
params object요청할 때 전달하는 매개변수를 정의합니다.
renewalPercent integer갱신을 위한 지점을 정의하며, 기본 값은 67% 입니다.
revoke boolean정의한 리소스가 삭제되면 생성한 인증서도 취소(Revoke)할지의 여부를 정의합니다.
allowStaticCreds boolean요청 시 생성하는 것이 아니라 Vault 서버에서 주기적으로 회전하는 자격 증명을 동기화할 때 AllowStaticCreds를 설정해야 합니다.
rolloutRestartTargets애플리케이션이 변경된 Secret의 마운트된 값을 인지하지 못하는 경우 강재로 Rollout-Restart를 수행할 대상을 정의합니다.
destination저장할 Secret을 지정합니다.

Vault의 Database 시크릿 엔진의 동적 시크릿을 적용하는 VaultDynamicSecret 정의하는 dynamic-secret.yaml 파일 내용은 다음과 같습니다.

apiVersion: secrets.hashicorp.com/v1beta1
+kind: VaultDynamicSecret
+metadata:
+  name: vault-db-app
+  namespace: default
+spec:
+  # 활성화 된 시크릿 엔진 Path
+  mount: demo-db
+  # 시크릿 롤 경로
+  path: creds/dev-postgres
+  # k8s secret 이름 정의
+  destination:
+    create: true
+    name: secretdb
+  # 교체 발생시 Restart할 대상 지정
+  rolloutRestartTargets:
+  - kind: Deployment
+    name: vso-db-demo
+  # 생성한 Vault 인증을 위한 VaultAuth 리소스 이름
+  vaultAuthRef: static-auth
+
$ kubectl get secret
+
+NAME        TYPE        DATA   AGE
+secretdb    Opaque      3      2h55m
+
+$ kubectl get secret secretdb -o jsonpath='{.data.username}' | base64 --decode
+
+v-kubernet-dev-post-Pl4QC4UC6rQ8uThG4pR8-169838984
+
+### 시간이 흐른 뒤 새로 발급된 인증서의 만료 시간을 확인합니다.
+
+$ kubectl get secret secretdb -o jsonpath='{.data.username}' | base64 --decode
+
+v-kubernet-dev-post-Dvta1R6q5bV9zrlFb5Zn-1698389887
+



 



 





 
+ + + diff --git a/04-HashiCorp/06-Vault/04-UseCase/windows-password-rotation.html b/04-HashiCorp/06-Vault/04-UseCase/windows-password-rotation.html new file mode 100644 index 0000000000..32e05860a9 --- /dev/null +++ b/04-HashiCorp/06-Vault/04-UseCase/windows-password-rotation.html @@ -0,0 +1,202 @@ + + + + + + + + + + Windows Password rotation | docmoa + + + + + +
본문으로 건너뛰기

Windows Password rotation

약 2 분vaultwindowsnomadpassword

Windows Password rotation

https://scarolan.github.io/painless-password-rotation/#37open in new window

Kv 추가

$ vault secrets enable -version=2 -path=systemcreds/ kv
+

권한 추가

$ vault policy write rotate-windows - << EOF
+# Allows hosts to write new passwords
+path "systemcreds/data/windows/*" {
+  capabilities = ["create", "update"]
+}
+# Allow hosts to generate new passphrases
+path "gen/passphrase" {
+  capabilities = ["update"]
+}
+# Allow hosts to generate new passwords
+path "gen/password" {
+  capabilities = ["update"]
+}
+EOF
+
+$ vault policy write windowsadmin - << EOF
+# Allows admins to read passwords.
+path "systemcreds/*" {
+  capabilities = ["list"]
+}
+path "systemcreds/data/windows/*" {
+  capabilities = ["list", "read"]
+}
+EOF
+

토큰

$ vault token create -policy=rotate-windows -period=600h
+

사용자

$ vault auth enable userpass 
+$ vault write auth/userpass/users/pwadmin password=password policies=windowsadmin
+

PowerShell e.g.

$VAULT_ADDR = $env:USERNAME
+# Make sure the user exists on the local system.
+if (-not (Get-LocalUser $USERNAME)) {
+    throw '$USERNAME does not exist!'
+}
+
+# Use TLS
+# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+# Import some environment variables.
+$VAULT_ADDR = $env:VAULT_ADDR
+$VAULT_TOKEN = $env:VAULT_TOKEN
+$HOSTNAME = $env:computername
+
+# Renew our token before we do anything else.
+Invoke-RestMethod -Headers @{"X-Vault-Token" = ${VAULT_TOKEN}} -Method POST -Uri ${VAULT_ADDR}/v1/auth/token/renew-self
+if(-Not $?)
+{
+   Write-Output "Error renewing Vault token lease."
+}
+
+# Fetch a new passphrase from Vault. Adjust the options to fit your requirements.
+#$NEWPASS = (Invoke-RestMethod -Headers @{"X-Vault-Token" = ${VAULT_TOKEN}} -Method POST -Body "{`"words`":`"4`",`"separator`":`"-`"}" -Uri ${VAULT_ADDR}/v1/gen/passphrase).data.value
+
+# Fetch a new password from Vault. Adjust the options to fit your requirements.
+$NEWPASS = c:\hashicorp\nomad\nomad operator keygen
+
+# Convert into a SecureString
+$SECUREPASS = ConvertTo-SecureString $NEWPASS -AsPlainText -Force
+
+# Create the JSON payload to write to Vault's K/V store. Keep the last 12 versions of this credential.
+$JSON="{ `"options`": { `"max_versions`": 12 }, `"data`": { `"$USERNAME`": `"$NEWPASS`" } }"
+
+# First commit the new password to vault, then change it locally.
+Invoke-RestMethod -Headers @{"X-Vault-Token" = ${VAULT_TOKEN}} -Method POST -Body $JSON -Uri ${VAULT_ADDR}/v1/systemcreds/data/windows/${HOSTNAME}/${USERNAME}_creds
+if($?) {
+   Write-Output "Vault updated with new password."
+   $UserAccount = Get-LocalUser -name $USERNAME
+   $UserAccount | Set-LocalUser -Password $SECUREPASS
+   if($?) {
+       Write-Output "${USERNAME}'s password was stored in Vault and updated locally."
+   }
+   else {
+       Write-Output "Error: ${USERNAME}'s password was stored in Vault but *not* updated locally."
+   }
+}
+else {
+    Write-Output "Error saving new password to Vault. Local password will remain unchanged."
+}
+

Job e.g.

job "pw-update" {
+  datacenters = ["hashistack"]
+  type        = "batch"
+
+  constraint {
+    attribute = "${meta.target}"
+    value     = "windows2016"
+  }
+
+  periodic {
+    cron             = "0 */5 * * * * *"
+    prohibit_overlap = true
+    time_zone        = "Asia/Seoul"
+  }
+
+  group "pw-update" {
+    count = 1
+    task "powershell" {
+      driver = "raw_exec"
+      config {
+        command = "powershell.exe"
+        args = ["-noprofile", "-executionpolicy", "bypass", "-file", "local/pw-rotate.ps1"]
+      }
+      env {
+        VAULT_TOKEN = "s.EZFCRJhNmjSc9U5b4EX5gwyy"
+        VAULT_ADDR = "http://172.28.128.21:8200"
+        USERNAME = "testuser"
+      }
+      template {
+data = <<EOF
+$USERNAME = $env:USERNAME
+# Make sure the user exists on the local system.
+if (-not (Get-LocalUser $USERNAME)) {
+    throw '$USERNAME does not exist!'
+}
+
+# Use TLS
+# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+# Import some environment variables.
+$VAULT_ADDR = $env:VAULT_ADDR
+$VAULT_TOKEN = $env:VAULT_TOKEN
+$HOSTNAME = $env:computername
+
+# Renew our token before we do anything else.
+Invoke-RestMethod -Headers @{"X-Vault-Token" = ${VAULT_TOKEN}} -Method POST -Uri ${VAULT_ADDR}/v1/auth/token/renew-self
+if(-Not $?)
+{
+   Write-Output "Error renewing Vault token lease."
+}
+
+# Fetch a new password from Vault. Adjust the options to fit your requirements.
+$NEWPASS = c:\hashicorp\nomad\nomad operator keygen
+
+# Convert into a SecureString
+$SECUREPASS = ConvertTo-SecureString $NEWPASS -AsPlainText -Force
+
+# Create the JSON payload to write to Vault's K/V store. Keep the last 12 versions of this credential.
+$JSON="{ `"options`": { `"max_versions`": 12 }, `"data`": { `"$USERNAME`": `"$NEWPASS`" } }"
+
+# First commit the new password to vault, then change it locally.
+Invoke-RestMethod -Headers @{"X-Vault-Token" = ${VAULT_TOKEN}} -Method POST -Body $JSON -Uri ${VAULT_ADDR}/v1/systemcreds/data/windows/${HOSTNAME}/${USERNAME}_creds
+if($?) {
+   Write-Output "Vault updated with new password."
+   $UserAccount = Get-LocalUser -name $USERNAME
+   $UserAccount | Set-LocalUser -Password $SECUREPASS
+   if($?) {
+       Write-Output "${USERNAME}'s password was stored in Vault and updated locally."
+   }
+   else {
+       Write-Output "Error: ${USERNAME}'s password was stored in Vault but *not* updated locally."
+   }
+}
+else {
+    Write-Output "Error saving new password to Vault. Local password will remain unchanged."
+}
+EOF
+        destination = "local/pw-rotate.ps1"
+      }
+    }
+  }
+}
+
+
+
+
+ + + diff --git a/04-HashiCorp/06-Vault/05-TroubleShooting/400-error.html b/04-HashiCorp/06-Vault/05-TroubleShooting/400-error.html new file mode 100644 index 0000000000..764cdc46e0 --- /dev/null +++ b/04-HashiCorp/06-Vault/05-TroubleShooting/400-error.html @@ -0,0 +1,84 @@ + + + + + + + + + + Vault 400 Error | docmoa + + + + + +
본문으로 건너뛰기

Vault 400 Error

1분 미만vaulterror400

Vault 400 Error

Vault HTTP Status Codes : https://www.vaultproject.io/api#http-status-codesopen in new window

Vault에 API 요청시 400에러가 발생하는 경우 Vault로 전달된 데이터 형태가 올바른지 확인이 필요하다.

  • 400 : Invalid request, missing or invalid data.

예를들어 아래와 같이 Transit의 복호화 요청을 하는 경우 데이터가 비어있다면 응답과 Audit로그에서 400 에러관련 메세지를 확인할 수 있다.

  • 1 error occurred: * invalid request
curl \
+  -H "X-Vault-Token: s.HeeRXjkW1KJhF8ofQsglI9yw" \
+  -X POST \
+  -d "{}" \
+  http://192.168.60.103:8200/v1/transit/decrypt/my-key
+
{
+  "time": "2022-03-04T08:02:37.596190958Z",
+  "type": "response",
+  "auth": {
+    "client_token": "hmac-sha256:17bc16e3346dd6c398646cb7da8e0bd71ae720f608a8c447b8942b8283388600",
+    "accessor": "hmac-sha256:798bb09d10dc2ac18533acb3d049c4185af3328fbc88fedd23081f63caa13b44",
+    "display_name": "root",
+    "policies": [
+      "root"
+    ],
+    "token_policies": [
+      "root"
+    ],
+    "token_type": "service",
+    "token_issue_time": "2021-09-15T13:45:47+09:00"
+  },
+  "request": {
+    "id": "c39906c5-f48d-c177-37c2-4c1635db78e7",
+    "operation": "update",
+    "mount_type": "transit",
+    "client_token": "hmac-sha256:17bc16e3346dd6c398646cb7da8e0bd71ae720f608a8c447b8942b8283388600",
+    "client_token_accessor": "hmac-sha256:798bb09d10dc2ac18533acb3d049c4185af3328fbc88fedd23081f63caa13b44",
+    "namespace": {
+      "id": "root"
+    },
+    "path": "kbhealth-transit/prod/decrypt/aes256",
+    "data": {
+      "ciphertext": "hmac-sha256:34c2966e2ef36e2dcdb24f05fd4442b8f85c0d2fbf0887977636c7592e2cef3b"
+    },
+    "remote_address": "10.100.0.85"
+  },
+  "response": {
+    "mount_type": "transit",
+    "data": {
+      "error": "hmac-sha256:bf7d730e400653f79b134c3bdb593f8220f6f1588a26048a6e1272a01ad47384"
+    }
+  },
+  "error": "1 error occurred:\n\t* invalid request\n\n"
+}
+
+ + + diff --git a/04-HashiCorp/06-Vault/05-TroubleShooting/index.html b/04-HashiCorp/06-Vault/05-TroubleShooting/index.html new file mode 100644 index 0000000000..73248baba1 --- /dev/null +++ b/04-HashiCorp/06-Vault/05-TroubleShooting/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Trouble Shooting | docmoa + + + + + +
본문으로 건너뛰기

05 Trouble Shooting

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/05-TroubleShooting/vault-sizing.html b/04-HashiCorp/06-Vault/05-TroubleShooting/vault-sizing.html new file mode 100644 index 0000000000..e7e99e237b --- /dev/null +++ b/04-HashiCorp/06-Vault/05-TroubleShooting/vault-sizing.html @@ -0,0 +1,57 @@ + + + + + + + + + + Vault MariaDB5.5 Dynamic Secret | docmoa + + + + + +
본문으로 건너뛰기

Vault MariaDB5.5 Dynamic Secret

1분 미만vaultMiriaDB

Vault MariaDB5.5 Dynamic Secret

  • 현상 : $vault read mysql/creds/my-role 입력시 오류

  • 오류 내용 :

Error reading mysql/creds/my-role: Error making API request.
+URL: GET http://127.0.0.1:8200/v1/mysql/creds/my-role
+Code: 500. Errors:
+
+* 1 error occurred:
+      * Error 1470: String 'v-root-my-role-87BP93fheiaHKGelc' is too long for user name (should be no longer than 16)
+
  • 원인 : mysql 버전이 오래되어 mysql-database-plugin이 아닌 mysql-legacy-database-plugin 을 사용해야 함.
    https://github.com/hashicorp/vault/issues/4602open in new window

  • 해결방안 : vault에서 database에 접근하기 위한 plugin설정시 아래와 같이 설정을 변경해야 함.

    • 기존설정
    $ vault write mysql/config/mysql-database \
    +    plugin_name=mysql-database-plugin \
    +    connection_url="{{username}}:{{password}}@tcp(192.168.56.204:3306)/" \
    +    allowed_roles="my-role" \
    +    username="vault2" \
    +    password="vaultpass"
    +
    • 변경설정
    $ vault write mysql/config/mysql-database \
    +    plugin_name=mysql-legacy-database-plugin \
    +    connection_url="{{username}}:{{password}}@tcp(192.168.56.204:3306)/" \
    +    allowed_roles="my-role" \
    +    username="vault2" \
    +
+ + + diff --git a/04-HashiCorp/06-Vault/06-Config/index.html b/04-HashiCorp/06-Vault/06-Config/index.html new file mode 100644 index 0000000000..cde71ef242 --- /dev/null +++ b/04-HashiCorp/06-Vault/06-Config/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 06 Config | docmoa + + + + + +
본문으로 건너뛰기

06 Config

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/06-Config/tls-config.html b/04-HashiCorp/06-Vault/06-Config/tls-config.html new file mode 100644 index 0000000000..fd1c0d75fd --- /dev/null +++ b/04-HashiCorp/06-Vault/06-Config/tls-config.html @@ -0,0 +1,71 @@ + + + + + + + + + + Vault Server tls 설정 | docmoa + + + + + +
본문으로 건너뛰기

Vault Server tls 설정

1분 미만VaulthttpsConfigurationServer

Vault Server tls 설정

  • Consul tls create 명령어를 이용하여 인증서 생성, 그외에 사설인증서 만드는 방법으로는 더 테스트 해봐야 할듯
# consul tls create로 인증서 생성
+consul tls ca create -domain=vault -days 3650
+consul tls cert create -domain=vault -dc=global  -server -days 3650
+consul tls cert create -domain=vault -dc=global  -client -days 3650
+consul tls cert create -domain=vault -dc=global  -cli -days 3650
+
+# vault config는 아래와 같다.
+ui = true
+
+storage "consul" {
+  address = "127.0.0.1:8500"
+  path    = "vault/"
+}
+
+listener "tcp" {
+  address         = "0.0.0.0:8200"
+  #tls_disable = 1
+  tls_cert_file = "/root/temp/global-server-vault-0.pem"
+  tls_key_file  = "/root/temp/global-server-vault-0-key.pem"
+}
+
+disable_mlock = true
+default_lease_ttl = "768h"
+max_lease_ttl = "768h"
+
+api_addr =  "https://172.21.2.50:8200"
+
+# 명령어를 써야 할 경우 cli 인증서를 export 해줘야한다.
+export VAULT_CACERT="${HOME}/temp/vault-agent-ca.pem"
+export VAULT_CLIENT_CERT="${HOME}/temp/global-cli-vault-0.pem"
+export VAULT_CLIENT_KEY="${HOME}/temp/global-cli-vault-0-key.pem"
+
+ + + diff --git a/04-HashiCorp/06-Vault/06-Config/vault-agent.html b/04-HashiCorp/06-Vault/06-Config/vault-agent.html new file mode 100644 index 0000000000..6df3995a7e --- /dev/null +++ b/04-HashiCorp/06-Vault/06-Config/vault-agent.html @@ -0,0 +1,237 @@ + + + + + + + + + + Vault Agent (with aws secret) | docmoa + + + + + +
본문으로 건너뛰기

Vault Agent (with aws secret)

약 3 분VaultAWSConfigurationAgent

Vault Agent (with aws secret)

참고 URL : https://learn.hashicorp.com/tutorials/vault/agent-awsopen in new window

Test ENV
$ sw_vers
+ProductName:	macOS
+ProductVersion:	12.4
+
+$ vault version
+Vault v1.11.0
+

1. Vault Server (dev mode)

1.1 Vault Setup

Vault Start Dev mode

vault server -dev -dev-root-token-id=root
+

Vault Env

Another terminal

export VAULT_ADDR=http://127.0.0.1:8200
+export VAULT_TOKEN=root
+

1.2 Dynamic Sectet Sample (AWS)

Setup AWS Env

export AWS_ACCESS_KEY=AKIAU3NXXXXX
+export AWS_SECRET_KEY=Rex3GPUKO3++123
+export AWS_REGION=ap-northeast-2
+

Enable AWS Secret Engine

vault secrets enable aws
+

AWS Secret Engine Configuration

vault write aws/config/root \
+  access_key=$AWS_ACCESS_KEY \
+  secret_key=$AWS_SECRET_KEY \
+  region=$AWS_REGION
+

AWS Secret Engine Lease change

vault write /aws/config/lease lease=1m lease_max=1m
+

Role setup (e.g. s3)

vault write aws/roles/s3 \
+    credential_type=iam_user \
+    policy_document=-<<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+     		"s3:PutObject",
+      	"s3:PutObjectAcl"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+

Test AWS Secret

$ vault write -force aws/creds/s3
+Key                Value
+---                -----
+lease_id           aws/creds/s3/w5hPWazlpWu4jKHUyR0WBnbZ
+lease_duration     1m
+lease_renewable    true
+access_key         AKIAU3NXDWRUCYVDBEYM
+secret_key         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
+security_token     <nil>
+

1.3 Approle Setup

Vault Policy for AWS

cat <<EOF | vault policy write aws_policy -
+path "aws/creds/s3" {
+  capabilities = ["read","update"]
+}
+EOF
+

Approle Create

$ vault auth enable approle
+
+Success! Enabled approle auth method at: approle/
+
+$ vault write auth/approle/role/aws-cred \
+    secret_id_ttl=120m \
+    token_ttl=60m \
+    token_max_ttl=120m \
+    policies="aws_policy"
+    
+Success! Data written to: auth/approle/role/aws-cred
+
+$ vault read auth/approle/role/aws-cred/role-id
+
+Key        Value
+---        -----
+role_id    430111ee-5955-aa83-a53d-924b7e11ac36
+
+$ vault write -f auth/approle/role/aws-cred/secret-id
+
+Key                   Value
+---                   -----
+secret_id             7f86b671-2f47-f841-18a4-c36ca34ab8d8
+secret_id_accessor    9ad4256a-acc6-e8c0-f7fe-7633e66b1318
+secret_id_ttl         2h
+
  • role_id는 Pipeline 작성에 사용

2. Vault Agent

2.1 Vault Config

$ cat <<EOF > ./vault-agent-config.hcl
+pid_file = "./pidfile"
+
+listener "tcp" {
+	address = "127.0.0.1:9200"
+	tls_disable = true
+	# tls_cert_file = "/path/to/cert.pem"
+  # tls_key_file = "/path/to/key.pem"
+}
+
+cache {
+  use_auto_auth_token = true
+}
+
+auto_auth {
+  method  {
+    type = "approle"
+    config = {
+      role_id_file_path = "./role_id.txt"
+      secret_id_file_path = "./secret_id.txt"
+    }
+  }
+}
+
+vault {
+  address = "http://127.0.0.1:8200"
+}
+
+# template 은 여러개 설정 가능
+template {
+  source      = "./aws_cred.tpl"
+  destination = "./aws_cred.txt"
+  command     = "cat ./aws_cred.txt" # 생략 가능
+}
+EOF
+

2.2 Vault Template

$ cat <<EOF > ./aws_cred.tpl
+{{- with secret "aws/creds/s3" -}}
+aws_access_key={{ .Data.access_key | toJSON }}
+aws_secret_key={{ .Data.secret_key | toJSON }}
+{{- end }}
+EOF
+

3. Vault Agent

3.1 Run Vault Agent

$ vault read -field role_id auth/approle/role/aws-cred/role-id > ./role_id.txt
+
+$ vault write -f -field secret_id auth/approle/role/aws-cred/secret-id > ./secret_id.txt
+
+$ vault agent -config=./vault-agent-config.hcl
+==> Vault agent started! Log data will stream in below:
+
+==> Vault agent configuration:
+
+           Api Address 1: http://127.0.0.1:9200
+           Api Address 2: http://bufconn
+                     Cgo: disabled
+               Log Level: info
+                 Version: Vault v1.11.0, built 2022-06-17T15:48:44Z
+             Version Sha: ea296ccf58507b25051bc0597379c467046eb2f1
+
+2022-06-30T17:17:08.726+0900 [INFO]  sink.file: creating file sink
+2022-06-30T17:17:08.727+0900 [INFO]  sink.file: file sink configured: path=/tmp/vault_agent mode=-rw-r-----
+2022-06-30T17:17:08.728+0900 [INFO]  sink.server: starting sink server
+2022-06-30T17:17:08.730+0900 [INFO]  template.server: starting template server
+2022-06-30T17:17:08.730+0900 [INFO]  auth.handler: starting auth handler
+2022-06-30T17:17:08.730+0900 [INFO]  auth.handler: authenticating
+2022-06-30T17:17:08.736+0900 [INFO] (runner) creating new runner (dry: false, once: false)
+2022-06-30T17:17:08.739+0900 [INFO]  auth.handler: authentication successful, sending token to sinks
+2022-06-30T17:17:08.740+0900 [INFO]  auth.handler: starting renewal process
+2022-06-30T17:17:08.740+0900 [INFO]  sink.file: token written: path=/tmp/vault_agent
+2022-06-30T17:17:08.743+0900 [INFO] (runner) creating watcher
+2022-06-30T17:17:08.745+0900 [INFO]  auth.handler: renewed auth token
+2022-06-30T17:17:08.745+0900 [INFO]  template.server: template server received new token
+2022-06-30T17:17:08.745+0900 [INFO] (runner) stopping
+2022-06-30T17:17:08.745+0900 [INFO] (runner) creating new runner (dry: false, once: false)
+2022-06-30T17:17:08.745+0900 [INFO] (runner) creating watcher
+2022-06-30T17:17:08.747+0900 [INFO] (runner) starting
+2022-06-30T17:17:11.635+0900 [INFO] (runner) rendered "./aws_cred.tpl" => "./aws_cred.txt"
+2022-06-30T17:17:11.635+0900 [INFO] (runner) executing command "[\"cat ./aws_cred.txt\"]" from "./aws_cred.tpl" => "./aws_cred.txt"
+2022-06-30T17:17:11.637+0900 [INFO] (child) spawning: sh -c cat ./aws_cred.txt
+aws_access_key="AKIAU3NXDWRUHRF5SJDM"
+aws_secret_key="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"
+2022-06-30T17:17:54.978+0900 [WARN] vault.read(aws/creds/s3): TTL of "1m" exceeded the effective max_ttl of "17s"; TTL value is capped accordingly
+2022-06-30T17:17:54.978+0900 [WARN] vault.read(aws/creds/s3): renewer done (maybe the lease expired)
+2022-06-30T17:17:57.422+0900 [INFO] (runner) rendered "./aws_cred.tpl" => "./aws_cred.txt"
+2022-06-30T17:17:57.422+0900 [INFO] (runner) executing command "[\"cat ./aws_cred.txt\"]" from "./aws_cred.tpl" => "./aws_cred.txt"
+2022-06-30T17:17:57.422+0900 [INFO] (child) spawning: sh -c cat ./aws_cred.txt
+aws_access_key="AKIAU3NXDWRUETMPDDOH"
+aws_secret_key="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"
+
$ watch cat ./aws_cred.txt
+Every 2.0s: cat ./aws_cred.txt                             gs-C02CT3ZFML85: Thu Jun 30 17:19:01 2022
+
+aws_access_key="AKIAU3NXDWRUHRF5SJDM"
+aws_secret_key="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"
+
+<< expire 되면 다시 발급 됨 >>
+
+aws_access_key="AKIAU3NXDWRUOOG33CPC"
+aws_secret_key="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"
+

3.2 Vault Agent API Endpoint

Vault Agent로는 X-Vault-Token 없이 API 호출 가능

$ curl -X GET http://127.0.0.1:9200/v1/aws/creds/s3 | jq '.data'
+{
+  "access_key": "AKIAU3NXDWRUJJ4UW2P2",
+  "secret_key": "BeI7xebPR75nXSJ9mzSsyh8P3qiwhCx5mqlh76R0",
+  "security_token": null
+}
+

3.3 Vault Systemd Sample

[Unit]
+Description=Nomad Agent
+Requires=consul-online.target
+After=consul-online.target
+
+[Service]
+KillMode=process
+KillSignal=SIGINT
+Environment=VAULT_ADDR=http://active.vault.service.consul:8200
+Environment=VAULT_SKIP_VERIFY=true
+ExecStartPre=/usr/local/bin/vault agent -config /etc/vault-agent.d/vault-agent.hcl
+ExecStart=/usr/bin/nomad-vault.sh
+ExecReload=/bin/kill -HUP $MAINPID
+Restart=on-failure
+RestartSec=2
+StartLimitBurst=3
+StartLimitIntervalSec=10
+LimitNOFILE=65536
+
+[Install]
+WantedBy=multi-user.target
+
+ + + diff --git a/04-HashiCorp/06-Vault/06-Config/vault-entierprise-license.html b/04-HashiCorp/06-Vault/06-Config/vault-entierprise-license.html new file mode 100644 index 0000000000..64432e9dbe --- /dev/null +++ b/04-HashiCorp/06-Vault/06-Config/vault-entierprise-license.html @@ -0,0 +1,43 @@ + + + + + + + + + + Vault +1.12 라이선스 | docmoa + + + + + +
본문으로 건너뛰기

Vault +1.12 라이선스

1분 미만VaultEnterpriseLicense

Vault +1.12 라이선스

License 기한 영향도

라이선스 exprire(기간 만료)

  • License

    • Expiration : 2022-6-19
    • Termination : 2032-6-18
  • 측정 시점 : 2022-12-9

  • Case 1 : Vault 바이너리가 릴리즈된 시점이 expiration 기간보다 이전인 경우

    • 실행 가능

    • 사용은 불가

      2022-12-09T14:39:09.474+0900 [ERROR] core.licensing: core: licensing error: expiration_time="2022-06-19 00:00:00 +0000 UTC" time_left=-4157h39m9s
      +
  • Case 2 : Vault 바이너리가 릴리즈된 시점이 expiration 기간보다 이후인 경우

    • 실행 불가

      Error initializing core: licensing could not be initialized: license validation failed: 1 error occurred:
      +	* license expiration date is before version build date
      +
  • Case 3 : 평가 라이선스의 Termination 시점 이후

    • 실행 불가
  • Case 3 : 일반 라이선스의 Termination 시점 이후

    • 바이너리 릴리즈 기준으로 expiration 기간과 비교하여 Case1, 2와 같이 동작

라이선스 갱신

expiration되었지만 실행은 가능한 이유

+ + + diff --git a/04-HashiCorp/06-Vault/07-Sentinel-Sample/aws-secrets-credential-type-check.html b/04-HashiCorp/06-Vault/07-Sentinel-Sample/aws-secrets-credential-type-check.html new file mode 100644 index 0000000000..e8b845eee4 --- /dev/null +++ b/04-HashiCorp/06-Vault/07-Sentinel-Sample/aws-secrets-credential-type-check.html @@ -0,0 +1,120 @@ + + + + + + + + + + AWS Secrets Role Type Check | docmoa + + + + + +
본문으로 건너뛰기

AWS Secrets Role Type Check

1분 미만VaultSentinelPolicy

AWS Secrets Role Type Check

1. EGP용 정책 생성 egp_iam_user_deny.sentinel

import "strings"
+
+# print(request.data)
+credential_type = request.data.credential_type
+print("CREDENTIAL_TYPE: ", credential_type)
+
+allow_role_type = ["federation_token"]
+
+role_type_check = rule {
+  credential_type in allow_role_type
+}
+
+# Only check AWS Secret Engine
+# Only check create, update
+precond = rule {
+	request.operation in ["create", "update"]
+}
+
+main = rule when precond {
+    role_type_check
+}
+
  • precond : API 요청이 POST, UPDATE 인 경우
  • role_type_check : 요청의 Body에 credential_type의 값이 허용된 type 인지 확인 (e.g. federation_token 허용)

2. EGP에 정책 설정

EGP는 지정된 path에 대해 정책을 적용

$ vault write /sys/policies/egp/iam_user_deny \
+  policy=@egp_iam_user_deny.sentinel \
+  enforcement_level=hard-mandatory \
+  paths="aws/roles/*"
+
  • paths로 지정된 API 경로에 요청이 들어오면 동작

3. 테스트

EGP로 지정된 path로 credential_type 이 iam_user 인경우 허용된 타입이 아니므로 거부됨

$ vault write aws/roles/iam-role \
+    credential_type=iam_user \
+    policy_document=-<<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "ec2:*",
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+

에러 메시지

Error writing data to aws/roles/iam-role: Error making API request.
+
+URL: PUT http://127.0.0.1:8200/v1/aws/roles/iam-role
+Code: 403. Errors:
+
+* 2 errors occurred:
+	* egp standard policy "root/iam_user_deny" evaluation resulted in denial.
+
+The specific error was:
+<nil>
+
+A trace of the execution for policy "root/iam_user_deny" is available:
+
+Result: false
+
+Description: <none>
+
+print() output:
+
+CREDENTIAL_TYPE:  iam_user
+
+
+Rule "main" (root/iam_user_deny:19:1) = false
+Rule "precond" (root/iam_user_deny:15:1) = true
+Rule "role_type_check" (root/iam_user_deny:9:1) = false
+	* permission denied
+

federation_token은 생성됩니다.

$ vault write aws/roles/sts-role \
+    credential_type=federation_token \
+    policy_document=-<<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "ec2:*",
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+Success! Data written to: aws/roles/sts-role
+
+ + + diff --git a/04-HashiCorp/06-Vault/07-Sentinel-Sample/index.html b/04-HashiCorp/06-Vault/07-Sentinel-Sample/index.html new file mode 100644 index 0000000000..9c3c60118b --- /dev/null +++ b/04-HashiCorp/06-Vault/07-Sentinel-Sample/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 07 Sentinel Sample | docmoa + + + + + +
본문으로 건너뛰기

07 Sentinel Sample

1분 미만

+ + + diff --git a/04-HashiCorp/06-Vault/07-Sentinel-Sample/transit-secrets-key-exportable-deny.html b/04-HashiCorp/06-Vault/07-Sentinel-Sample/transit-secrets-key-exportable-deny.html new file mode 100644 index 0000000000..c981e1d241 --- /dev/null +++ b/04-HashiCorp/06-Vault/07-Sentinel-Sample/transit-secrets-key-exportable-deny.html @@ -0,0 +1,110 @@ + + + + + + + + + + Transit Key Exportable Deny | docmoa + + + + + +
본문으로 건너뛰기

Transit Key Exportable Deny

1분 미만VaultSentinelPolicy

Transit Key Exportable Deny

1. EGP용 정책 생성 exportable_deny.sentinel

import "strings"
+
+exportable = request.data.exportable
+
+exportable_check = rule {
+  exportable is "false"
+}
+
+main = rule {
+  exportable_check
+}
+
  • exportable_check : 요청의 Body에 exportable의 값이 false인 경우에 TRUE 반환

2. EGP에 정책 설정

EGP는 지정된 path에 대해 정책을 적용

$ vault write sys/policies/egp/exportable-check \
+  policy="$(base64 -i exportable-deny.sentinel)" \
+  paths="*" \
+  enforcement_level="hard-mandatory"
+
  • paths로 지정된 API 경로에 요청이 들어오면 동작

3. 테스트

3.1 Test Policy

transit 경로에 모든 권한이 있는 사용자

vault policy write transit-admin - << EOF
+path "transit" {
+capabilities = ["create", "read", "update", "delete", "list"]
+}
+
+path "transit/*" {
+capabilities = ["create", "read", "update", "delete", "list"]
+}
+EOF
+

3.2 Policy Token 생성

VAULT_TOKEN=$(vault token create -policy=transit-admin)
+

3.3 Sentinel 테스트

exportable 옵션이 false인 경우 정상적으로 수행

$ vault write -f transit/keys/my-key-name exportable=false
+
+Key                       Value
+---                       -----
+allow_plaintext_backup    false
+auto_rotate_period        0s
+deletion_allowed          false
+derived                   false
+exportable                false
+imported_key              false
+keys                      map[1:1702877441]
+latest_version            1
+min_available_version     0
+min_decryption_version    1
+min_encryption_version    0
+name                      my-key-name
+supports_decryption       true
+supports_derivation       true
+supports_encryption       true
+supports_signing          false
+type                      aes256-gcm96
+

exportable 옵션이 false인 경우 거부됨

$ vault write -f transit/keys/my-key-name exportable=true
+
+Error writing data to transit/keys/my-key-name: Error making API request.
+
+URL: PUT http://127.0.0.1:8200/v1/transit/keys/my-key-name
+Code: 403. Errors:
+
+* 2 errors occurred:
+	* egp standard policy "root/exportable-check" evaluation resulted in denial.
+
+The specific error was:
+<nil>
+
+A trace of the execution for policy "root/exportable-check" is available:
+
+Result: false
+
+Description: <none>
+
+Rule "main" (root/exportable-check:9:1) = false
+Rule "exportable_check" (root/exportable-check:5:1) = false
+	* permission denied
+
+
+
+ + + diff --git a/04-HashiCorp/06-Vault/index.html b/04-HashiCorp/06-Vault/index.html new file mode 100644 index 0000000000..e77b265f09 --- /dev/null +++ b/04-HashiCorp/06-Vault/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 06 Vault | docmoa + + + + + +
본문으로 건너뛰기

06 Vault

1분 미만

+ + + diff --git a/04-HashiCorp/07-Nomad/01-Information/index.html b/04-HashiCorp/07-Nomad/01-Information/index.html new file mode 100644 index 0000000000..bc496a6092 --- /dev/null +++ b/04-HashiCorp/07-Nomad/01-Information/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 01 Information | docmoa + + + + + +
본문으로 건너뛰기

01 Information

1분 미만

+ + + diff --git a/04-HashiCorp/07-Nomad/01-Information/nomad-sizing.html b/04-HashiCorp/07-Nomad/01-Information/nomad-sizing.html new file mode 100644 index 0000000000..ee26ba4c5e --- /dev/null +++ b/04-HashiCorp/07-Nomad/01-Information/nomad-sizing.html @@ -0,0 +1,40 @@ + + + + + + + + + + Nomad Sizing | docmoa + + + + + +
본문으로 건너뛰기

Nomad Sizing

1분 미만nomadsizing

Nomad Sizing

https://learn.hashicorp.com/tutorials/nomad/production-reference-architecture-vm-with-consulopen in new window

Nomad는 Server/Client 구조로 구성되며, Client의 경우 자원사용량이 매우 미미하므로 자원산정은 Server를 기준으로 산정

SizeCPUMemoryDisk CapacityTypical Cloud Instance Types
최소2-4 core8-16 GB RAM50 GBAWS: m5.large, m5.xlarge
Azure: Standard_D2_v3, Standard_D4_v3
GCP: n2-standard-2, n2-standard-4
권장8-16 core32-64 GB RAM100 GBAWS: m5.2xlarge, m5.4xlarge
Azure: Standard_D8_v3, Standard_D16_v3
GCP: n2-standard-8, n2-standard-16
  • HA 구성을 위해 3대 이상의 홀수 구성

기본 아키텍처 (with Consul)

Reference diagram
Reference diagram
+ + + diff --git a/04-HashiCorp/07-Nomad/01-Information/nomad_job_restart.html b/04-HashiCorp/07-Nomad/01-Information/nomad_job_restart.html new file mode 100644 index 0000000000..28ff0e6997 --- /dev/null +++ b/04-HashiCorp/07-Nomad/01-Information/nomad_job_restart.html @@ -0,0 +1,95 @@ + + + + + + + + + + task 복구 방식 | docmoa + + + + + +
본문으로 건너뛰기

task 복구 방식

약 1 분nomadsizing

task 복구 방식

원문 : https://www.hashicorp.com/blog/resilient-infrastructure-with-nomad-restarting-tasksopen in new window
Nomad가 종종 운영자 개입 없이 장애, 중단 상황, Nomad 클러스터 인프라의 유지 관리를 처리하는 방법 확인

1. Job과 task의 선언

Job 은 실행 드라이버(Docker, Java, Exec 등)에 의해 Nomad 클라이언트 노드 에서 실행되는 명령, 서비스, 애플리케이션, 배치와 같은 형태입니다. task에서는 웹 애플리케이션, 데이터베이스 서버 또는 API와 같은 단기 배치 작업 또는 장기적으로 실행되는 서비스의 실행 방식을 정의 합니다.

redis를 실행하는 Job의 예는 다음과 같습니다.

job "example" {
+  datacenters = ["dc1"]
+  type = "service"
+
+  constraint {
+    attribute = "${attr.kernel.name}"
+    value     = "linux"
+  }
+  group "cache" {
+    count = 1
+
+    task "redis" {
+      driver = "docker"
+
+      config {
+        image = "redis:3.2"
+      }
+      resources {
+        cpu    = 500 # 500 MHz
+        memory = 256 # 256MB
+      }
+    }
+  }
+}
+

Job을 작성할 때 작성자는 워크로드에 대한 리소스와 제약 조건을 정의할 수 있습니다. 제약 조건 은 커널 유형, 버전, OS와 같은 속성에 따라 노드의 워크로드 배치를 지정 및 제한합니다. 리소스 요구 사항에는 task 실행에 필요한 메모리, 네트워크, CPU 등이 포함됩니다.

Nomad는 Job 작성자가 실패하고 응답하지 않는 task 동작을 자동으로 다시 시작하고, 반복적으로 실패한 작업들을 다른 노드로 자동으로 재예약하는 전략을 지정할 수 있도록 하여 워크로드를 탄력적으로 만듭니다.

2. 실패한 task 재시작

Job의 실행 실패는 성공적으로 완료되지 않거나 오류 또는 리소스(CPU, Memory) 부족으로 인해 서비스가 실패하는 경우 발생할 수 있습니다.

Nomad는 Job 파일의 restart 절에 있는 지시문에 따라 동일한 노드에서 실패한 task를 다시 시작합니다. 운영자는 재시작 횟수인 attempts, 지연된 작업을 재시작하기 전에 Nomad가 기다려야 하는 시간인 delay, 시도된 재시작을 간격으로 제한하는 interval 시간을 지정합니다. failuremode를 사용하여 주어진 간격 내의 모든 재시작 시도가 소진된 후 task가 실행되지 않는 경우 Nomad가 수행해야 할 동작을 지정합니다.

기본 실패 모드는 Nomad가 Job을 다시 시작하지 않도록 지시하는 fail입니다. 이것은 몇 번의 실패 후에 성공할 가능성이 없는 멱등성이 아닌 실행 방식에 권장되는 값입니다. 다른 옵션은 Job을 다시 시작하기 전에 간격(interval)으로 지정된 시간만큼 기다리도록 Nomad에 지시하는 지연(delay)입니다.

다음 재시작 스탠자인 restart는 Nomad에게 30분 이내에 최대 2번의 재시작을 시도하도록 지시하고, 각 재시작 사이에 15초를 지연시키고 모두 소진된 후에는 더 이상 재시작을 시도하지 않습니다.

group "cache" {
+  ...
+  restart {
+    attempts = 2
+    interval = "30m"
+    delay = "15s"
+    mode = "fail"
+  }
+  task "redis" {
+    ...
+  }
+}
+

다시 restart 동작은 버그, 메모리 누수, 기타 일시적인 문제에 대해 작업을 복원할 수 있도록 설계되었습니다. 이는 Nomad 외부에서 systemd, upstart 또는 runit과 같은 프로세스 관리자를 사용하는 것과 유사합니다.

3. 응답없는 task 재시작

또 다른 일반적인 시나리오는 프로세스 실행은 실패하지 않았지만 응답하지 않거나 비정상이 된 task를 다시 시작해야 하는 것입니다. Nomad는 check_restart 스탠자의 지시에 따라 응답하지 않는 Job을 다시 시작합니다. 이 동작은 Consul Health Checkopen in new window와 함께 작동합니다. 상태 확인이 제한 시간(limit)에 실패하면 Nomad가 Job을 재시작합니다. 값이 1이면 첫 번째 실패 시 다시 시작됩니다. 기본값인 0은 상태 확인 기반 재시작을 비활성화합니다.

실패는 연속적으로 발생해야 합니다. 한번이라도 정상적이였다면 카운트를 재설정하므로 통과 상태와 실패 상태를 번갈아 가는 경우 서비스가 다시 시작되지 않을 수 있습니다. 다시 시작한 후 상태 확인을 재개하기 위해 대기 기간을 지정하려면 grace를 사용하십시오. Nomad가 경고 상태를 통과 상태로 처리하고 다시 시작을 트리거하지 않도록 하려면 ignore_warnings = true로 설정하십시오.

다음 check_restart 정책은 상태 확인이 연속 3회 실패한 후 Redis 작업을 다시 시작하도록 Nomad에 지시합니다. task를 다시 시작한 후 90초 동안 대기하여 상태 확인을 재개하고 경고 상태(실패 포함)에서 다시 시작합니다.

task "redis" {
+  ...
+  service {
+    check_restart {
+      limit = 3
+      grace = "90s"
+      ignore_warnings = false
+    }
+  }
+}
+

기존 데이터 센터 환경에서 실패한 task를 다시 시작하는 것은 종종 작업자가 구성해야 하는 프로세스 관리자가 처리합니다. 비정상 task를 자동으로 감지하고 다시 시작하는 것은 더 복잡하며 모니터링 시스템이나 운영자 개입을 통합하기 위한 사용자 지정 스크립트가 필요합니다. Nomad를 사용하면 운영자 개입 없이 자동으로 발생합니다.

4. 실패한 task 다시 예약

지정된 수의 다시 시작한 후에 성공적으로 실행되지 않는 task는 하드웨어 오류, 커널 교착 상태 또는 기타 복구할 수 없는 오류와 같이 실행 중인 노드의 문제로 인해 실패할 수 있습니다.

운영자는 reschedule 스탠자를 사용하여 어떤 상황에서 실패한 task를 다른 노드로 일정을 변경해야 하는지 Nomad에게 알려줍니다.

Nomad는 이전에 해당 task에 사용되지 않은 노드로 일정을 변경하는 것을 선호합니다. restart 절과 마찬가지로 Nomad가 시도해야 하는 재스케줄 시도(attempts) 횟수, Nomad가 지연된 재스케줄 시도 사이에 대기해야 하는 시간(delay)과 간격(interval)으로 시도된 재스케줄 시도를 제한하는 시간을 지정할 수 있습니다.

또한 delay_function을 사용하여 초기 지연(delay) 후 후속 일정 변경 시도를 계산하는 데 사용할 함수를 지정합니다. 옵션은 constant, exponential, fibonacci입니다. 서비스 Job의 경우 피보나치(fibonacci) 스케줄링은 초기에 짧은 수명 중단에서 복구하기 위해 빠르게 일정을 재조정하는 반면 더 긴 중단 동안 이탈을 피하기 위해 속도를 늦추는 좋은 속성이 있습니다. exponentialfibonacci 지연 함수를 사용할 때 max_delay를 사용하여 지연 시간의 상한선을 설정하고 그 이후에는 증가하지 않습니다. 무제한 재스케줄 시도를 활성화하거나 활성화하지 않으려면 unlimitedtrue 또는 false로 설정합니다.

다시 스케줄링(reschedule)하는 동작을 완전히 비활성화하려면 attempts = 0unlimited = false로 설정합니다.

다음 reschedule 스탠자는 Nomad에게 task group 일정을 무제한으로 다시 예약하고 후속 시도 사이의 지연을 기하급수적으로 늘리도록 지시합니다. 시작 지연은 30초에서 최대 1시간입니다.

group "cache" {
+  ...
+  reschedule {
+    delay          = "30s"
+    delay_function = "exponential"
+    max_delay      = "1hr"
+    unlimited      = true
+  }
+}
+

재스케줄 스탠자는 모든 노드에서 실행되기 때문에 시스템 작업에 적용되지 않습니다.

Nomad 버전 0.8.4부터는 배포 중에 reschedule 스탠자가 적용됩니다.

기존 데이터 센터에서 노드 오류는 모니터링 시스템에 의해 감지되고 운영자에게 경고를 트리거합니다. 그런 다음 운영자는 장애가 발생한 노드를 복구하거나 워크로드를 다른 노드로 마이그레이션하기 위해 수동으로 개입해야 합니다. reschedule 기능을 통해 운영자는 가장 일반적인 실패 시나리오에 대한 계획을 세울 수 있으며 Nomad는 수동 개입의 필요성을 피하면서 자동으로 응답합니다. Nomad는 합리적인 기본값을 적용하므로 대부분의 사용자는 다양한 재시작 매개변수에 대해 생각할 필요 없이 로컬 재시작 및 일정 변경을 얻을 수 있습니다.

정리

운영자는 restart 절을 사용하여 실패한 작업에 대한 Nomad의 동일 노드에 대한 로컬 재시작 전략을 지정합니다. Consul 및 check_restart 스탠자와 함께 사용하면 Nomad는 restart 매개변수에 따라 응답하지 않는 작업을 동일 노드에서 다시 시작합니다. 운영자는 reschedule 절을 사용하여 실패한 Job을 다른 노드에 다시 예약하기 위한 Nomad의 전략을 지정합니다.

+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.html b/04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.html new file mode 100644 index 0000000000..8e383e164f --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.html @@ -0,0 +1,133 @@ + + + + + + + + + + Docker log driver and Cloudwatch on Nomad | docmoa + + + + + +
본문으로 건너뛰기

Docker log driver and Cloudwatch on Nomad

1분 미만NomadAWSCloudwatchlog

Docker log driver and Cloudwatch on Nomad

docker 런타임에는 log driver로 "awslogs"를 지원합니다.
https://docs.docker.com/config/containers/logging/awslogs/open in new window

Nomad에서 docker 자체의 로깅을 사용하므로서, Nomad에서 실행되는 docker 기반 컨테이너의 로깅이 특정 환경에 락인되는것을 방지합니다.

경고

AWS 환경이 아닌 외부 구성 시, 해당 노드에 Cloudwath 기록을 위한 Policy를 갖는 IAM의 credential 정보가 환경변수 또는 ~/.aws/credential 구성이 필요합니다.

EC2 Instance Role 구성

Nomad 구성 시 Cloudwatch에 대한 EC2 Instance의 IAM 구성이 필요합니다. 아래 Terraform 구성의 예를 참고하세요.

## 생략 ##
+
+resource "aws_iam_instance_profile" "ec2_profile" {
+  name = "ec2_profile"
+  role = aws_iam_role.role.name
+}
+
+resource "aws_iam_role" "role" {
+  name = "my_role"
+
+  assume_role_policy = jsonencode({
+    Version = "2012-10-17"
+    Statement = [
+      {
+        Action = "sts:AssumeRole"
+        Effect = "Allow"
+        Sid    = ""
+        Principal = {
+          Service = "ec2.amazonaws.com"
+        }
+      },
+    ]
+  })
+}
+
+resource "aws_iam_role_policy" "cloudwatch_policy" {
+  name        = "cloudwatch_policy"
+  role        = aws_iam_role.role.id
+  
+  policy = jsonencode({
+    Version = "2012-10-17"
+    Statement = [
+      {
+        Action = [
+          "logs:CreateLogStream",
+          "logs:PutLogEvents",
+          "logs:CreateLogGroup"
+        ]
+        Effect   = "Allow"
+        Resource = "*"
+      },
+    ]
+  })
+}
+
+resource "aws_instance" "example" {
+  ami           = "ami-04e6fcf8cfe3b09ea"
+  instance_type = "t2.micro"
+  key_name      = aws_key_pair.web_admin.key_name
+  vpc_security_group_ids = [
+    aws_security_group.ssh.id
+  ]
+
+  iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
+}
+


































 
 
 
















 

Nomad Job의 Docker Driver에 Logging 설정

Nomad에서 docker 드라이버 사용시 적용되는 기본 log driver는 json-file 입니다. 추가 설정을 통해 docker가 지원하는 다양한 log driver를 사용할 수 있습니다. (FluentD 샘플open in new window)

  • 구성에 필요한 정보는 Docker의 공식 문서를 참고 합니다. : https://docs.docker.com/config/containers/logging/configure/open in new window
  • 기존 docker cli 상에 구성했던 --log-driver 같은 옵션의 정의가 HCL형태로 정의됩니다.
  • HCL 문법을 따르므로, 몇몇 상이한 표현방식이 있을 수 있습니다. 예를들어 로그 날짜 구성에 사용되는 "\[%Y-%m-%d\]" 에서 [ 같은 특수문자 표기를 위해 \를 한번만 넣었다면, "\\[%Y-%m-%d\\]" 같이 두번 넣어야 할수도 있습니다.

구성 예제는 아래와 같습니다.

job "api" {
+	datacenters = ["dc1"]
+  type = "service"
+
+  group "api" {
+    network {
+      mode = "bridge"
+      port "api" {
+        to = 9001
+      }
+    }
+
+    service {
+      name = "count-api"
+      port = "api"
+      connect {
+        sidecar_service {}
+      }
+    }
+
+    task "web" {
+      driver = "docker"
+      config {
+        image = "hashicorpnomad/counter-api:v1"
+        ports = ["api"]
+        logging {
+          type = "awslogs"
+          config {
+            awslogs-region = "ap-northeast-2"
+            awslogs-group = "myGroup"
+            awslogs-create-group = true
+            awslogs-datetime-format = "\\[%Y-%m-%dT%H:%M:%S\\+09:00\\]"
+          }
+        }
+      }
+    }
+  }
+}
+

























 
 
 
 
 
 
 
 
 




Log 확인

Nomad의 로그 출력을 확인합니다.
NomadLog

Cloudwatch에 로그 출력을 확인합니다.
NomadLog

+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/Namespace.html b/04-HashiCorp/07-Nomad/02-Config/Namespace.html new file mode 100644 index 0000000000..8dbfeb02d5 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/Namespace.html @@ -0,0 +1,69 @@ + + + + + + + + + + Nomad Namespace | docmoa + + + + + +
본문으로 건너뛰기

Nomad Namespace

1분 미만NomadNamespace

Nomad Namespace

Nomad Version : >= 1.0.0
Nomad Ent. Version : >= 0.7.0
https://learn.hashicorp.com/tutorials/nomad/namespacesopen in new window

Namespace 생성

$ nomad namespace apply -description "PoC Application" apps
+

Namespace 삭제

$ nomad namespace delete apps
+

Namespace 리스트 확인

$ nomad namespace list
+Name      Description
+default   Default shared namespace
+

Job에 Namesapce 지정

job "rails-www" {
+
+    ## Run in the QA environments
+    namespace = "web-qa"
+
+    ## Only run in one datacenter when QAing
+    datacenters = ["us-west1"]
+    # ...
+}
+

CLI 사용시 flag 추가하거나 ENV로 생략 가능

# flag 설정
+nomad job status -namespace=web-qa
+
+# ENV 설정
+export NOMAD_NAMESPACE=web-qa
+nomad job status
+

ACL 구성시의 예

# Allow read only access to the production namespace
+namespace "web-prod" {
+    policy = "read"
+}
+
+# Allow writing to the QA namespace
+namespace "web-qa" {
+    policy = "write"
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.html b/04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.html new file mode 100644 index 0000000000..ea5a16731e --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.html @@ -0,0 +1,62 @@ + + + + + + + + + + Nomad UI Token | docmoa + + + + + +
본문으로 건너뛰기

Nomad UI Token

1분 미만NomadACL

Nomad UI Token

해당 Token의 policy는 특정인이 원하여 만들었으며, 더 다양한 제약과 허용을 할 수 있습니다. 해당 policy는 아래와 같은 제약과 허용을 합니다.

  1. UI에서 exec(job에 접근) 제한
  2. 그 외에 job, node, volume, server등의 모든 화면 읽어오기

Nomad cli

#원하는 권한이 있는 policy file
+$ cat nomad-ui-policy.hcl
+namespace "*" {
+  policy       = "read"
+  capabilities = ["submit-job", "dispatch-job", "read-logs", "list-jobs", "parse-job", "read-job", "csi-list-volume", "csi-read-volume", "list-scaling-policies", "read-scaling-policy", "read-job-scaling", "read-fs"]
+}
+node {
+  policy = "read"
+}
+host_volume "*" {
+  policy = "write"
+}
+plugin {
+  policy = "read"
+}
+
+#위에서 만든 policy 파일을 nomad cluster에 적용
+$ nomad acl policy apply -description "Production UI policy" prod-ui nomad-ui-policy.hcl
+
+#해당 policy로 token생성(policy는 여러개를 넣을 수도 있음)
+$ nomad acl token create -name="prod ui token" -policy=prod-ui -type=client | tee ui-prod.token
+#웹 브라우저 로그인을 위해 Secret ID 복사
+

Nomad UI

아래는 위에서 만들어진 토큰으로 로그인한 화면입니다.
TokenLogin

아래 그림과 같이 exec버튼이 비활성화되어 있는 걸 볼 수 있습니다.
exec비활성화

+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.html b/04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.html new file mode 100644 index 0000000000..5a99665032 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.html @@ -0,0 +1,54 @@ + + + + + + + + + + Nomad 인증서 생성 | docmoa + + + + + +
본문으로 건너뛰기

Nomad 인증서 생성

1분 미만NomadSSL

Nomad 인증서 생성

공식 사이트에 consul 인증서 생성 가이드는 있는데 Nomad 인증서 생성가이드는
Show Terminal을 들어가야 볼 수 있기때문에 귀찮음을 해결하기 위해 공유합니다.

Nomad 인증서 생성

consul tls ca create -domain=nomad -days 3650
+
+consul tls cert create -domain=nomad -dc=global  -server -days 3650
+
+consul tls cert create -domain=nomad -dc=global  -client -days 3650
+
+consul tls cert create -domain=nomad -dc=global  -cli -days 3650
+

Nomad env 설정

export NOMAD_CACERT="${HOME}/tls/nomad-agent-ca.pem"
+
+export NOMAD_CLIENT_CERT="${HOME}/tls/global-cli-nomad-0.pem"
+
+export NOMAD_CLIENT_KEY="${HOME}/tls/global-cli-nomad-0-key.pem"
+
+export NOMAD_ADDR="https://127.0.0.1:4646"
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/Server.html b/04-HashiCorp/07-Nomad/02-Config/Server.html new file mode 100644 index 0000000000..cfa3008cde --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/Server.html @@ -0,0 +1,120 @@ + + + + + + + + + + Nomad 서버 설정 | docmoa + + + + + +
본문으로 건너뛰기

Nomad 서버 설정

1분 미만NomadEnterpriseConfigurationServer

Nomad 서버 설정

최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server설정 파일입니다.
네트워크는 프라이빗(온프레이머스) 환경입니다.

#nomad server 설정
+server {
+  enabled = true
+  bootstrap_expect = 3
+  license_path="/opt/nomad/license/nomad.license"
+  server_join {
+    retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
+  }
+  raft_protocol = 3
+  event_buffer_size = 100
+  non_voting_server = false
+  heartbeat_grace = "10s"
+}
+ 
+ 
+#tls 설정
+tls {
+  http = true
+  rpc  = true
+ 
+  ca_file   = "/opt/ssl/nomad/nomad-agent-ca.pem"
+  cert_file = "/opt/ssl/nomad/global-server-nomad-0.pem"
+  key_file  = "/opt/ssl/nomad/global-server-nomad-0-key.pem"
+ 
+  #UI오픈할 서버만 변경
+  verify_server_hostname = false
+  verify_https_client    = false
+  #일반서버는 아래와 같이 설정
+  verify_server_hostname = true
+  verify_https_client    = true
+}
+

Nomad 서버 최소 설정 (20220807기준)

data_dir = "/opt/consul"
+
+client_addr = "0.0.0.0"
+
+datacenter = "my-dc"
+
+#ui
+ui_config {
+  enabled = true
+}
+
+# server
+server = true
+
+# Bind addr
+bind_addr = "0.0.0.0" # Listen on all IPv4
+# Advertise addr - if you want to point clients to a different address than bind or LB.
+advertise_addr = "node ip"
+
+# Enterprise License
+license_path = "/opt/nomad/nomad.lic"
+
+# bootstrap_expect
+bootstrap_expect=1
+
+# encrypt
+encrypt = "7w+zkhqa+YD4GSKXjRWETBIT8hs53Sr/w95oiVxq5Qc="
+
+# retry_join
+retry_join = ["server ip"]
+
+key_file = "/opt/consul/my-dc-server-consul-0-key.pem"
+cert_file = "/opt/consul/my-dc-server-consul-0.pem"
+ca_file = "/opt/consul/consul-agent-ca.pem"
+auto_encrypt {
+  allow_tls = true
+}
+
+verify_incoming = false
+verify_incoming_rpc = false
+verify_outgoing = false
+verify_server_hostname = false
+
+ports {
+  http = 8500
+  dns = 8600
+  server = 8300
+}
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/client.html b/04-HashiCorp/07-Nomad/02-Config/client.html new file mode 100644 index 0000000000..cbd86bd6b5 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/client.html @@ -0,0 +1,123 @@ + + + + + + + + + + Nomad 클라이언트 설정 | docmoa + + + + + +
본문으로 건너뛰기

Nomad 클라이언트 설정

1분 미만NomadEnterpriseConfigurationClient

Nomad 클라이언트 설정

최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 Client설정 파일입니다.
네트워크는 프라이빗(온프레이머스) 환경입니다.

#nomad client 설정
+ 
+client {
+  enabled = true
+  servers = ["172.30.1.17","172.30.1.18","172.30.1.19"]
+  server_join {
+    retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
+    retry_max = 3
+    retry_interval = "15s"
+  }
+  #host에서 nomad에서 사용할 수 있는 volume 설정
+  host_volume "logs" {
+    path      = "/var/logs/elk/"
+    read_only = false
+  }
+  #각각의 client의 레이블 작성
+  #meta {
+  #   name = "moon"
+  #   zone = "web"
+  #}
+  #nomad에서 예약할 자원
+  reserved {
+    #Specifies the amount of CPU to reserve, in MHz.
+    cpu = 200
+    #Specifies the amount of memory to reserve, in MB.
+    memory = 8192
+    #Specifies the amount of disk to reserve, in MB.
+    disk = 102400
+  }
+  no_host_uuid = true
+  #bridge network interface name
+  bridge_network_name = "nomad"
+  bridge_network_subnet = "172.26.64.0/20"
+  cni_path = "/opt/cni/bin"
+  cni_config_dir = "/opt/cni/config"
+}
+#tls 설정
+tls {
+  http = true
+  rpc  = true
+ 
+  ca_file   = "/opt/ssl/nomad/nomad-agent-ca.pem"
+  cert_file = "/opt/ssl/nomad/global-client-nomad-0.pem"
+  key_file  = "/opt/ssl/nomad/global-client-nomad-0-key.pem"
+ 
+  verify_server_hostname = true
+  verify_https_client    = true
+}
+

Nomad 클라이언트 최소 설정 (20220807기준)

data_dir  = "/opt/nomad/data"
+bind_addr = "0.0.0.0"
+
+client {
+  enabled = true
+  servers = ["server ip"]
+  # sidecar image 고정
+  meta {
+   connect.sidecar_image = "envoyproxy/envoy:v1.21.3"
+  }
+}
+
+#consul 정보 입력
+consul {
+  address  = "127.0.0.1:8501"
+  grpc_address="127.0.0.1:8502"
+  server_service_name = "nomad"
+  client_service_name = "nomad-client"
+  auto_advertise  = true
+  server_auto_join  = true
+  client_auto_join  = true
+  ssl       = true
+  verify_ssl = false
+  ca_file   = "/opt/consul/consul-agent-ca.pem"
+  cert_file = "/opt/consul/my-dc-client-consul-0.pem"
+  key_file  = "/opt/consul/my-dc-client-consul-0-key.pem"
+}
+
+plugin "docker" {
+  config {
+    auth {
+    }
+  }
+}
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/common.html b/04-HashiCorp/07-Nomad/02-Config/common.html new file mode 100644 index 0000000000..061ff05019 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/common.html @@ -0,0 +1,105 @@ + + + + + + + + + + Nomad 공통 설정 | docmoa + + + + + +
본문으로 건너뛰기

Nomad 공통 설정

1분 미만NomadEnterpriseConfigurationCommon

Nomad 공통 설정

최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server, client의 공통설정 파일입니다.
저는 agent.hcl파일안에 다 넣고 실행하지만 나눠서 추후에는 기능별로 나눠서 사용할 예정입니다.

#nomad 공통 설정
+datacenter = "dc1"
+region = "global"
+data_dir = "/opt/nomad/nomad"
+bind_addr = "{{ GetInterfaceIP `ens192` }}"
+ 
+advertise {
+  # Defaults to the first private IP address.
+  #http = "{{ GetInterfaceIP `ens244` }}"
+  #rpc  = "{{ GetInterfaceIP `ens244` }}"
+  #serf = "{{ GetInterfaceIP `ens244` }}"
+  http = "{{ GetInterfaceIP `ens192` }}"
+  rpc = "{{ GetInterfaceIP `ens192` }}"
+  serf = "{{ GetInterfaceIP `ens192` }}"
+}
+ 
+consul {
+  address  = "127.0.0.1:8500"
+  server_service_name = "nomad"
+  client_service_name = "nomad-client"
+  auto_advertise  = true
+  server_auto_join  = true
+  client_auto_join  = true
+  #consul join용 token
+  token = "33ee4276-e1ef-8e5b-d212-1f94ca8cf81e"
+}
+enable_syslog = false
+enable_debug = false
+disable_update_check = false
+ 
+log_level = "DEBUG"
+log_file = "/var/log/nomad/nomad.log"
+log_rotate_duration = "24h"
+log_rotate_bytes = 104857600
+log_rotate_max_files = 100
+ 
+ports {
+  http = 4646
+  rpc = 4647
+  serf = 4648
+}
+ 
+#prometheus에서 nomad의 metrics값을 수집 해 갈 수 있게 해주는 설정
+telemetry {
+  collection_interval = "1s"
+  disable_hostname = true
+  prometheus_metrics = true
+  publish_allocation_metrics = true
+  publish_node_metrics = true
+}
+ 
+ 
+plugin "docker" {
+  config {
+    auth {
+      config = "/root/.docker/config.json"
+    }
+    #온프레이머스환경에서는 해당 이미지를 private repository에 ㅓㄶ고 변경
+    infra_image = "google-containers/pause-amd64:3.1"
+  }
+}
+ 
+acl {
+  enabled = true
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/consul-namespace.html b/04-HashiCorp/07-Nomad/02-Config/consul-namespace.html new file mode 100644 index 0000000000..3024decda7 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/consul-namespace.html @@ -0,0 +1,534 @@ + + + + + + + + + + Consul namespace 사용시 Nomad의 서비스 등록 | docmoa + + + + + +
본문으로 건너뛰기

Consul namespace 사용시 Nomad의 서비스 등록

약 3 분NomadEnterpriseConsul

Consul namespace 사용시 Nomad의 서비스 등록

Job의 Consul Namespace 정의

Consul Enterprise는 Namespace가 있어서 Nomad로 Consul에 서비스 등록 시 특정 Namespace를 지정할 수 있음

Job > Group > Consul

job "frontback_job" {
+  group "backend_group_v1" {
+
+    count = 1
+
+    consul {
+      namespace = "mynamespace"
+    }
+
+    service {
+      name = "backend"
+      port = "http"
+
+      connect {
+        sidecar_service {}
+      }
+
+      check {
+        type     = "http"
+        path     = "/"
+        interval = "5s"
+        timeout  = "3s"
+      }
+    }
+# 생략
+





 
 
 

















Service Mesh Upstream

해당 group에 대한 글로벌 설정이기 때문에 Consul과 관련해서 구성되는 모든 설정은 해당 Namespace를 기준으로 적용됨
예를 들어 upstream 구성을 하면

job "frontback_job" {
+  group "frontend_group" {
+    count = 1
+
+    consul {
+      namespace = "mesh"
+    }
+
+    service {
+      name = "frontend"
+      port = "http"
+
+      connect {
+        sidecar_service {
+          proxy {
+            upstreams {
+              destination_name = "backend"
+              local_bind_port  = 10000
+            }
+          }
+        }
+      }
+# 생략
+




 
 
 








 
 
 
 




sidecar의 로그에서도 적용된 namespace로 리스너가 등록되는 로그(namesapce/servicename) 확인 가능

[2021-09-01 01:31:10.490][1][info][upstream] [source/common/upstream/cds_api_helper.cc:28] cds: add 3 cluster(s), remove 0 cluster(s)
+[2021-09-01 01:31:10.572][1][info][upstream] [source/common/upstream/cds_api_helper.cc:65] cds: added/updated 3 cluster(s), skipped 0 unmodified cluster(s)
+[2021-09-01 01:31:10.572][1][info][upstream] [source/common/upstream/cluster_manager_impl.cc:168] cm init: initializing secondary clusters
+[2021-09-01 01:31:10.574][1][info][upstream] [source/common/upstream/cluster_manager_impl.cc:192] cm init: all clusters initialized
+[2021-09-01 01:31:10.574][1][info][main] [source/server/server.cc:745] all clusters initialized. initializing init manager
+[2021-09-01 01:31:10.578][1][info][upstream] [source/server/lds_api.cc:78] lds: add/update listener 'mesh/backend:127.0.0.1:10000'
+[2021-09-01 01:31:10.586][1][info][upstream] [source/server/lds_api.cc:78] lds: add/update listener 'public_listener:0.0.0.0:24945'
+[2021-09-01 01:31:10.587][1][info][config] [source/server/listener_manager_impl.cc:888] all dependencies initialized. starting workers
+[2021-09-01 01:46:10.592][1][info][main] [source/server/drain_manager_impl.cc:70] shutting down parent after drain
+





 



DNS 쿼리

경고

주의할점은 DNS를 사용하는 경우, 예를들어 template 작성시 namespace가 추가되면 경로상 datacenter도 정의해줘야 인식하는 것으로 보임

[tag.]<service>.service.<namespace>.<datacenter>.<domain>
+

참고 링크 : https://www.consul.io/docs/discovery/dns#namespaced-servicesopen in new window

기존 템플릿

template {
+  data = <<EOF
+defaults
+   mode http
+
+frontend http_front
+   bind *:28888
+   default_backend http_back
+
+backend http_back
+    balance roundrobin
+    server-template mywebapp 2 _frontend._tcp.service.consul resolvers consul resolve-opts allow-dup-ip resolve-prefer ipv4 check
+
+resolvers consul
+  nameserver consul 127.0.0.1:8600
+  accepted_payload_size 8192
+  hold valid 5s
+EOF
+
+  destination = "local/haproxy.cfg"
+}
+











 









Namespace 적용 템플릿

template {
+  data = <<EOF
+defaults
+   mode http
+
+frontend http_front
+   bind *:28888
+   default_backend http_back
+
+backend http_back
+    balance roundrobin
+    server-template mywebapp 2 _frontend._tcp.service.mesh.hashistack.consul resolvers consul resolve-opts allow-dup-ip resolve-prefer ipv4 check
+
+resolvers consul
+  nameserver consul 127.0.0.1:8600
+  accepted_payload_size 8192
+  hold valid 5s
+EOF
+
+  destination = "local/haproxy.cfg"
+}
+











 









Full Example (mesh.nomad)
# nomad namespace apply -description "ServiceMesh Sample" mesh
+
+locals {
+  mode     = "Legacy"
+  namespace = "mesh"
+  #artifact = "https://hashicorpjp.s3.ap-northeast-1.amazonaws.com/masa/Snapshots2021Jan_Nomad/frontback.tgz"
+  artifact = "https://github.com/Great-Stone/Snapshots_2021Jan_Pseudo-containerized/raw/main/frontback.tgz"
+  node = "https://github.com/Great-Stone/Snapshots_2021Jan_Pseudo-containerized/raw/main/nodejs-linux"
+  subject    = "snapshot"
+}
+
+variables {
+  frontend_port = 8080
+  upstream_port = 10000
+}
+
+variable "attrib_v1" {
+  type = object({
+    version    = string
+    task_count = number
+    text_color = string
+  })
+  default = {
+    version    = "v1"
+    task_count = 1
+    text_color = "green"
+  }
+}
+
+variable "attrib_v2" {
+  type = object({
+    version    = string
+    task_count = number
+    text_color = string
+  })
+  default = {
+    version    = "v2"
+    task_count = 1
+    text_color = "red"
+  }
+}
+
+job "frontback_job" {
+
+  region = "global"
+  datacenters = ["hashistack"]
+  namespace = local.namespace
+
+  type = "service"
+
+  constraint {
+    attribute = "${meta.subject}"
+    value     = local.subject
+  }
+
+  #######################
+  #                     #
+  #      Backend v1     #
+  #                     #
+  #######################
+
+  group "backend_group_v1" {
+
+    count = var.attrib_v1["task_count"]
+
+    consul {
+      namespace = local.namespace
+    }
+
+    network {
+      mode = "bridge"
+      port "http" {}
+    }
+
+    service {
+      name = "backend"
+      port = "http"
+
+      connect {
+        sidecar_service {}
+      }
+
+      meta {
+        version = var.attrib_v1["version"]
+      }
+
+      check {
+        type     = "http"
+        path     = "/"
+        interval = "5s"
+        timeout  = "3s"
+      }
+
+      tags = [
+        "Snapshots",
+        "Backend",
+        local.mode,
+        var.attrib_v1["version"]
+      ]
+    }
+
+    task "backend" {
+
+      driver = "exec"
+
+      artifact {
+        source = local.artifact
+      }
+
+      env {
+        COLOR   = var.attrib_v1["text_color"]
+        MODE    = local.mode
+        TASK_ID = NOMAD_ALLOC_INDEX
+        ADDR    = NOMAD_ADDR_http
+        PORT    = NOMAD_PORT_http
+        VERSION = var.attrib_v1["version"]
+        # IMG_SRC		= "${local.img_dir}${var.attrib_v1["version"]}.png"
+      }
+
+      config {
+        command = "backend"
+      }
+
+      resources {
+        memory = 32  # reserve 32 MB
+        cpu    = 100 # reserve 100 MHz
+      }
+
+    }
+
+    reschedule {
+      delay          = "10s"
+      delay_function = "constant"
+    }
+  }
+
+  #######################
+  #                     #
+  #      Backend v2     #
+  #                     #
+  #######################
+
+  group "backend_group_v2" {
+
+    count = var.attrib_v2["task_count"]
+
+    consul {
+      namespace = local.namespace
+    }
+
+    network {
+      mode = "bridge"
+      port "http" {}
+    }
+
+    service {
+      name = "backend"
+      port = "http"
+
+      connect {
+        sidecar_service {}
+      }
+
+      meta {
+        version = var.attrib_v2["version"]
+      }
+
+      check {
+        type     = "http"
+        path     = "/"
+        interval = "5s"
+        timeout  = "3s"
+      }
+
+      tags = [
+        "Snapshots",
+        "Backend",
+        local.mode,
+        var.attrib_v2["version"]
+      ]
+    }
+
+    task "backend" {
+
+      driver = "exec"
+
+      artifact {
+        source = local.artifact
+      }
+
+      env {
+        COLOR   = var.attrib_v2["text_color"]
+        MODE    = local.mode
+        TASK_ID = NOMAD_ALLOC_INDEX
+        ADDR    = NOMAD_ADDR_http
+        PORT    = NOMAD_PORT_http
+        VERSION = var.attrib_v2["version"]
+        # IMG_SRC		= "${local.img_dir}${var.attrib_v2["version"]}.png"
+      }
+
+      config {
+        command = "backend"
+      }
+
+      resources {
+        memory = 32  # reserve 32 MB
+        cpu    = 100 # reserve 100 MHz
+      }
+    }
+
+    reschedule {
+      delay          = "10s"
+      delay_function = "constant"
+    }
+  }
+
+  ######################
+  #                    #
+  #      Frontend      #
+  #                    #
+  ######################
+
+  group "frontend_group" {
+
+    count = 1
+
+    consul {
+      namespace = local.namespace
+    }
+
+    network {
+      mode = "bridge"
+      port "http" {
+        // static = var.frontend_port
+      }
+    }
+
+    service {
+      name = "frontend"
+      port = "http"
+
+      connect {
+        sidecar_service {
+          proxy {
+            upstreams {
+              destination_name = "backend"
+              local_bind_port  = var.upstream_port
+            }
+          }
+        }
+      }
+
+			// check {
+			// 	type     = "http"
+			// 	path     = "/"
+			// 	interval = "5s"
+			// 	timeout  = "3s"
+			// }
+
+      tags = [
+        local.mode,
+        "Snapshots",
+        "Frontend"
+      ]
+    }
+
+    task "frontend" {
+
+      driver = "exec"
+
+      artifact {
+        source = local.node
+      }
+
+      env {
+        PORT         = NOMAD_PORT_http
+        UPSTREAM_URL = "http://${NOMAD_UPSTREAM_ADDR_backend}"
+      }
+
+      config {
+        command = "nodejs-linux"
+      }
+
+      resources {
+        memory = 32  # reserve 32 MB
+        cpu    = 100 # reserve 100 MHz
+      }
+
+    }
+
+    reschedule {
+      delay          = "10s"
+      delay_function = "constant"
+    }
+  }
+
+  ######################
+  #                    #
+  #      haproxy      #
+  #                    #
+  ######################
+
+  group "haproxy" {
+    count = 1
+
+    consul {
+      namespace = local.namespace
+    }
+
+    network {
+      port "http" {
+        static = 28888
+      }
+
+      port "stats" {
+        static = 21936
+      }
+    }
+
+    task "haproxy" {
+      driver = "docker"
+
+      config {
+        image        = "haproxy:2.0"
+        network_mode = "host"
+
+        volumes = [
+          "local/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg",
+        ]
+
+        ports = ["http", "stats"]
+      }
+
+      template {
+        data = <<EOF
+defaults
+   mode http
+
+frontend stats
+   bind *:21936
+   stats uri /
+   stats show-legends
+   no log
+
+frontend http_front
+   bind *:28888
+   default_backend http_back
+
+backend http_back
+    balance roundrobin
+    server-template mywebapp 2 _frontend._tcp.service.mesh.hashistack.consul resolvers consul resolve-opts allow-dup-ip resolve-prefer ipv4 check
+
+resolvers consul
+  nameserver consul 127.0.0.1:8600
+  accepted_payload_size 8192
+  hold valid 5s
+EOF
+
+        destination = "local/haproxy.cfg"
+      }
+
+      service {
+        name = "haproxy"
+
+        check {
+          name     = "alive"
+          type     = "tcp"
+          port     = "http"
+          interval = "10s"
+          timeout  = "2s"
+        }
+      }
+
+      resources {
+        cpu    = 200
+        memory = 128
+
+        network {
+          mbits = 10
+
+          // port "http" {
+          //   static = 28888
+          //   to = 8888
+          // }
+
+          // port "stats" {
+          //   static = 21936
+          //   to = 1936
+          // }
+        }
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/csi-nfs.html b/04-HashiCorp/07-Nomad/02-Config/csi-nfs.html new file mode 100644 index 0000000000..d89467d487 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/csi-nfs.html @@ -0,0 +1,138 @@ + + + + + + + + + + nomad csi (nfs) | docmoa + + + + + +
본문으로 건너뛰기

nomad csi (nfs)

1분 미만Nomadconfigcsinfs

nomad csi (nfs)

  • nomad에서 외부 storage를 사용하기 위한 plugin
    • 그 중에서도 접근성이 좋은 nfs를 사용, public cloud에서 제공하는 storage와는 사용법이 다를 수 있음
  • 구성환경은 아래와 같다.(사실 nfs server정보만 보면 될 거 같음)
    • nfs-server 10.0.0.151:/mnt/data

controller

  • 하나이상의 node에 storage를 배포할 수 있게 해주는 중앙관리 기능
  • 어느 node(client)에 띄어져도 상관없다.
job "plugin-nfs-controller" {
+  datacenters = ["dc1"]
+
+  group "controller" {
+    task "plugin" {
+      driver = "docker"
+
+      config {
+        image = "mcr.microsoft.com/k8s/csi/nfs-csi:latest"
+
+        args = [
+          "--endpoint=unix://csi/csi.sock",
+          "--nodeid=${attr.unique.hostname}",
+          "--logtostderr",
+          "-v=5",
+        ]
+      }
+
+      csi_plugin {
+        id        = "nfs"
+        type      = "controller"
+        mount_dir = "/csi"
+      }
+
+      resources {
+        cpu    = 250
+        memory = 128
+      }
+    }
+  }
+}
+
+

node plugin

  • 컨테이너가 동작하는, 그리고 storage가 띄어져야 할 모든 node에 올라가야됨
job "plugin-nfs-nodes" {
+  datacenters = ["dc1"]
+
+  type = "system"
+
+  group "nodes" {
+    task "plugin" {
+      driver = "docker"
+
+      config {
+        image = "mcr.microsoft.com/k8s/csi/nfs-csi:latest"
+
+        args = [
+          "--endpoint=unix://csi/csi.sock",
+          "--nodeid=${attr.unique.hostname}",
+          "--logtostderr",
+          "--v=5",
+        ]
+
+        privileged = true
+      }
+
+      csi_plugin {
+        id        = "nfs"
+        type      = "node"
+        mount_dir = "/csi"
+      }
+
+      resources {
+        cpu    = 250
+        memory = 128
+      }
+    }
+  }
+}
+
+

nomad volume

  • csi를 이용하여 실제 사용할 volume을 만든다.
  • cli: nomad volume register [파일명]
    • 다른 건 nomad doc에 다 나오지만 nfs 마운트 명령어에 던져 줄 parameter 값을 넣어줘야 할 경우에는 context에 넣어줘야한다.
id           = "nfs-vol"
+name         = "nfs"
+type         = "csi"
+external_id  = "nfs"
+plugin_id    = "nfs"
+#snapshot_id  = "test" # or clone_id, see below
+capacity_max = "20G"
+capacity_min = "10G"
+
+capability {
+  access_mode     = "single-node-writer"
+  attachment_mode = "file-system"
+}
+
+mount_options {
+  fs_type     = "ext4"
+  mount_flags = ["noatime"]
+}
+
+context {
+  server  = "10.0.0.151"
+  share   = "/mnt/data"
+}
+

job에는 아래와 같이 추가하여 사용했다.

    volume "nfs-vol" {
+      type            = "csi"
+      source          = "nfs-vol"
+      read_only       = false
+      attachment_mode = "file-system"
+      access_mode     = "single-node-writer"
+    }
+

nomad volume페이지에서 확인

  • nomad ui에서 volume 페이지에서 alloc에서 사용 중인 volume을 볼 수 있다
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/custom-ui-link.html b/04-HashiCorp/07-Nomad/02-Config/custom-ui-link.html new file mode 100644 index 0000000000..465cb17a86 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/custom-ui-link.html @@ -0,0 +1,51 @@ + + + + + + + + + + Nomad UI에 Consul과 Vault 링크 추가 | docmoa + + + + + +
본문으로 건너뛰기

Nomad UI에 Consul과 Vault 링크 추가

1분 미만NomadUI

Nomad UI에 Consul과 Vault 링크 추가

https://www.nomadproject.io/docs/configuration/uiopen in new window

Nomad ui 설정에 다음과 같이 Consul과 Vault의 링크를 추가할 수 있습니다.

ui {
+  enabled =  true
+
+  consul {
+    ui_url = "https://consul.example.com:8500/ui"
+  }
+
+  vault {
+    ui_url = "https://vault.example.com:8200/ui"
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/index.html b/04-HashiCorp/07-Nomad/02-Config/index.html new file mode 100644 index 0000000000..3470f9f0f7 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 02 Config | docmoa + + + + + +
본문으로 건너뛰기

02 Config

1분 미만

+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/integrateVault.html b/04-HashiCorp/07-Nomad/02-Config/integrateVault.html new file mode 100644 index 0000000000..92a9452aed --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/integrateVault.html @@ -0,0 +1,128 @@ + + + + + + + + + + integrate Vault | docmoa + + + + + +
본문으로 건너뛰기

integrate Vault

1분 미만NomadNamespace

integrate Vault

아래 작업 전 Forward DNS for Consul Service Discovery을 진행해야함

예제를 위한 vault kv 설정

# 사용된 policy들
+$ cat nomad-cluster-role.json
+{
+    "allowed_policies": "admin",
+    "token_explicit_max_ttl": 0,
+    "name": "nomad-cluster",
+    "orphan": true,
+    "token_period": 259200,
+    "renewable": true
+}
+vault write /auth/token/roles/nomad-cluster @nomad-cluster-role.json
+
+$ cat admin-policy.hcl 
+# Read system health check
+path "sys/health"
+{
+  capabilities = ["read", "sudo"]
+}
+
+# Create and manage ACL policies broadly across Vault
+
+# List existing policies
+path "sys/policies/acl"
+{
+  capabilities = ["list"]
+}
+
+# Create and manage ACL policies
+path "sys/policies/acl/*"
+{
+  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+# Enable and manage authentication methods broadly across Vault
+
+# Manage auth methods broadly across Vault
+path "auth/*"
+{
+  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+# Create, update, and delete auth methods
+path "sys/auth/*"
+{
+  capabilities = ["create", "update", "delete", "sudo"]
+}
+
+# List auth methods
+path "sys/auth"
+{
+  capabilities = ["read"]
+}
+
+# Enable and manage the key/value secrets engine at `secret/` path
+
+# List, create, update, and delete key/value secrets
+path "secret/*"
+{
+  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+# Manage secrets engines
+path "sys/mounts/*"
+{
+  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
+}
+
+# List existing secrets engines.
+path "sys/mounts"
+{
+  capabilities = ["read"]
+}
+
+vault policy write admin admin-policy.hcl
+
+# token 생성
+vault token create -policy admin -period 72h -orphan
+

Server 구성 예시

storage를 consul을 사용하고 consul에 서비스가 등록되어 있는환경

vault {
+    enabled = true
+    address = "http://active.vault.service.consul:8200"
+    task_token_ttl = "1h"
+    create_from_role = "nomad-cluster"
+    token = "s.hQRpxLmyk6AgSKJWOc9Gmbj1"
+}
+

Client 구성 예시

vault {
+    enabled = true
+    address = "http://active.vault.service.consul:8200"
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/nomad-guide-basic.html b/04-HashiCorp/07-Nomad/02-Config/nomad-guide-basic.html new file mode 100644 index 0000000000..e5ec2703e8 --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/nomad-guide-basic.html @@ -0,0 +1,397 @@ + + + + + + + + + + Nomad Guide - Basic | docmoa + + + + + +
본문으로 건너뛰기

Nomad Guide - Basic

약 3 분NomadSample

Nomad Guide - Basic

Download

on Linux

구성 샘플

└── hashicorp
+    └── nomad
+         ├── config
+         └── data
+
  • Donwload 받은 압축을 해제하고 기존 $PATH 위치에 복사 하거나 등록
    • e.g. utuntu : /usr/local/bin
    • e.g. centos : /usr/bin

Server Config

</hashicorp/nomad/config/nomad.hcl>

datacenter = "dc1"
+data_dir = "/hashicorp/nomad/data"
+
+bind_addr = "0.0.0.0"
+
+advertise {
+  http = "{{ GetInterfaceIP \"eth1\" }}"
+  rpc  = "{{ GetInterfaceIP \"eth1\" }}"
+  serf = "{{ GetInterfaceIP \"eth1\" }}"
+}
+
+server {
+  enabled          = true
+  bootstrap_expect = 1
+}
+
  • advertise 에 지정된 http, rpc, serf 의 IP는 직접 IP를 입력하는 것도 가능
  • go-discover 형태인 구문을 활용하면 인터페이스 이름을 넣어서 자동 IP 입력 가능
    {{ GetInterfaceIP \"eth1\" }}
    +

Client Config

retry_join에 Server의 주소 꼭 넣기!

</hashicorp/nomad/config/nomad.hcl>

datacenter = "dc1"
+data_dir = "/hashicorp/nomad/data"
+
+bind_addr = "0.0.0.0"
+
+server {
+  enabled = false
+}
+
+server_join {
+  retry_join = ["<server_ip>:4647"]
+}
+
+client {
+  enabled = true
+  servers = ["<server_ip>:4647"]
+  meta {
+    "key1" = "value1"
+    "key2" = "value2"
+  }
+  options = {
+    "driver.raw_exec.enable" = "1"
+  }
+}
+

Systemctl 서비스 등록

https://learn.hashicorp.com/tutorials/nomad/production-deployment-guide-vm-with-consulopen in new window

sudo touch /etc/systemd/system/nomad.service
+
  • ExecStart의 nomad 바이너리 경로에 주의!!!
  • ExecStart-config 에 앞서 작성한 config 파일 디렉토리 경로 맞추기!!!
  • 서버의 경우 User/Group을 일반 사용자로 구성
  • 클라이언트(워커 노드)는 User/Group을 root로 구성
[Unit]
+Description=Nomad
+Documentation=https://www.nomadproject.io/docs/
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+
+# Nomad server should be run as the nomad user. Nomad clients
+# should be run as root
+User=nomad
+Group=nomad
+
+ExecReload=/bin/kill -HUP $MAINPID
+ExecStart=/usr/local/bin/nomad agent -config /etc/nomad.d
+KillMode=process
+KillSignal=SIGINT
+LimitNOFILE=65536
+LimitNPROC=infinity
+Restart=on-failure
+RestartSec=2
+
+## Configure unit start rate limiting. Units which are started more than
+## *burst* times within an *interval* time span are not permitted to start any
+## more. Use `StartLimitIntervalSec` or `StartLimitInterval` (depending on
+## systemd version) to configure the checking interval and `StartLimitBurst`
+## to configure how many starts per interval are allowed. The values in the
+## commented lines are defaults.
+
+# StartLimitBurst = 5
+
+## StartLimitIntervalSec is used for systemd versions >= 230
+# StartLimitIntervalSec = 10s
+
+## StartLimitInterval is used for systemd versions < 230
+# StartLimitInterval = 10s
+
+TasksMax=infinity
+OOMScoreAdjust=-1000
+
+[Install]
+WantedBy=multi-user.target
+

실행 및 UI 확인

$ systemctl start nomad
+$ systemctl enable nomad
+
  • UI 기본 포트 : 4646
  • http://<server_ip>:4646

Job 실행

Job 실행은 CLI, API, UI 실행 가능

CLI

$ NOMAD_ADDR=http://<server_ip>:4646
+$ nomad job run <job_file_path>
+

UI

  • http://<server_ip>:4646 에 접속
  • 왼쪽 메뉴의 WORKLOAD/Jobs 선택
  • 우측의 Run Job 버튼 클릭
  • Job 내용 입력 후 Plan, Apply

Job Sample

batch - basic

job "batch" {
+  datacenters = ["dc1"]
+  
+  type        = "batch"
+  
+  group "batch-1" {
+    count = 1
+    task "batch" {
+      driver = "raw_exec"
+      template {
+        data = <<EOF
+#!/bin/bash
+echo $(hostname) > /tmp/check.txt
+EOF
+        destination = "run.sh"
+      }
+      config {
+        command = "run.sh"
+      }
+      resources {
+        cpu    = 100
+        memory = 64
+      }
+    }
+    
+    task "batch-2" {
+      driver = "raw_exec"
+      artifact {
+        source = "http://<shared_ip>:<port>/path/run.sh"
+        destination = "local"
+      }
+      config {
+        command = "local/run.sh"
+      }
+      resources {
+        cpu    = 100
+        memory = 64
+      }
+    }
+  }
+}
+
+
  • driverraw_exec이면 isolation 없이 스크립트를 실행하는 방식
  • template 에서 작성하는 파일은 동적으로 생성되며 변수 조합도 가능
  • artifact를 정의하여 중앙 저장소의 파일을 다운로드 받아 구성 가능
  • 한번 정상 동작 후 Job은 종료됨(기록은 남으나 GC 후 최종적으로 삭제됨)

batch - periodic

job "periodic" {
+  datacenters = ["dc1"]
+  
+  type        = "batch"
+
+  periodic {
+    cron             = "*/5 * * * * * *"
+    prohibit_overlap = true
+    time_zone        = "Asia/Seoul"
+  }
+
+  constraint {
+    attribute = "${attr.unique.hostname}"
+    value     = "cn-client-2"
+  }
+
+  group "batch" {
+    count = 1
+    task "batch" {
+      driver = "raw_exec"
+      template {
+        data = <<EOF
+#!/bin/bash
+echo $(date) >> /tmp/periodic.txt
+EOF
+        destination = "run.sh"
+      }
+      config {
+        command = "run.sh"
+      }
+      resources {
+        cpu    = 100
+        memory = 64
+      }
+    }
+  }
+}
+
  • periodic 에서 cron 형태로 정의 가능

  • constraint은 조건을 부여하는 옵션으로 attribute와 meta 정보를 활용 가능, 해당 예제에서는 hostname 기준으로 동작 타겟 호스트를 정의함

    • attribute를 조건으로 주는 방법은 attribute값 앞에 attr. prefix를 추가

    • meta의 경우 meta로 선언된 키 앞에 meta. prefix를 추가

    • CLI로 확인하는 방법

      $ nomad agent-info | grep node_id
      +  node_id = ae3cf7ee-09e6-c158-d883-fe4e4f39eb2b
      +$ nomad node status -verbose ae3cf7ee-09e6-c158-d883-fe4e4f39eb2b
      +ID              = ae3cf7ee-09e6-c158-d883-fe4e4f39eb2b
      +Name            = gs-C02CT3ZFML85
      +...
      +Attributes
      +cpu.arch                  = amd64
      +cpu.frequency             = 2300
      +cpu.modelname             = Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
      +cpu.numcores              = 8
      +cpu.totalcompute          = 18400
      +driver.java               = 1
      +driver.java.runtime       = OpenJDK Runtime Environment Temurin-11.0.14.1+1 (build 11.0.14.1+1)
      +driver.java.version       = 11.0.14.1
      +...
      +Meta
      +connect.gateway_image     = envoyproxy/envoy:v${NOMAD_envoy_version}
      +connect.log_level         = info
      +connect.proxy_concurrency = 1
      +...
      +
    • UI로 확인하는 방법

      • http://<server_ip>:4646 에 접속
      • 왼쪽 메뉴의 CLUSTER/Clients 선택
      • 우측의 클라이언트 목록에서 원하는 클라이언트 선택

Batch - Prameterized

job "param" {
+  datacenters = ["dc1"]
+  
+  type        = "batch"
+
+  parameterized {
+    payload = "optional"
+    meta_required = ["param"]
+  }
+
+  constraint {
+    attribute = "${attr.unique.hostname}"
+    value     = "cn-client-1"
+  }
+
+  group "batch" {
+    count = 1
+    task "batch" {
+      driver = "raw_exec"
+      template {
+        data = <<EOF
+#!/bin/bash
+echo 'batch param {{ env "NOMAD_META_PARAM" }}' >> /tmp/param.txt
+EOF
+        destination = "run.sh"
+      }
+      config {
+        command = "run.sh"
+      }
+      resources {
+        cpu    = 100
+        memory = 64
+      }
+    }
+  }
+}
+
  • parameterized 항목에서 json형태의 payload, 또는 URL Param 형태를 입력 받아 동작 가능
  • 요청시에만 동작

sysbatch

job "install_docker" {
+  datacenters = ["dc1"]
+  
+  type        = "sysbatch"
+
+  // periodic {
+  //   cron             = "*/5 * * * * * *"
+  //   prohibit_overlap = true
+  //   time_zone        = "Asia/Seoul"
+  // }
+
+  constraint {
+    attribute = "${attr.os.name}"
+    value     = "ubuntu"
+  }
+  
+  group "install" {
+    count = 1
+    task "docker" {
+      driver = "raw_exec"
+      template {
+        data = <<EOF
+#!/bin/bash
+apt-get update
+apt-get install -y apt-transport-https ca-certificates curl software-properties-common
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
+apt-get update
+apt-cache policy docker-ce
+apt-get install docker-ce -y
+systemctl enable docker
+systemctl start docker
+EOF
+        destination = "docker_install.sh"
+      }
+      config {
+        command = "docker_install.sh"
+      }
+      resources {
+        cpu    = 100
+        memory = 64
+      }
+    }
+  }
+}
+
+
  • docker를 ubuntu에 일괄 설치하는 예제
  • sysbatch는 조건에 맞는 모든 host node에서 실행

system

job "system" {
+  datacenters = ["dc1"]
+
+  type        = "system"
+
+  group "cache" {
+    count = 1
+
+    network {
+      port "db" {
+        to = 6379
+      }
+    }
+
+    task "redis" {
+      driver = "docker"
+
+      config {
+        image = "redis:6.2.6-alpine3.15"
+        ports = ["db"]
+      }
+
+      resources {
+        cpu    = 500
+        memory = 512
+      }
+    }
+  }
+}
+
  • system 타입은 모든 노드에서 실행하는 agent 같은 역할의 실행을 수행
  • 예제에서는 docker 타입의 실행을 예로 redis:6.2.6-alpine3.15를 모든 노드에서 실행하도록 구성
  • group/network 에서 사용할 network 조건을 정의
    • to : 대상의 포트 정보
    • static : 고정할 클라이언트에서의 포트 정보 (정의되어 있지 않으면 랜덤 부여)
    • 사용할 포트 이름은 docker로 정의된 task의 config/ports 에서 맵핑하여 관리

Service

job "service" {
+  datacenters = ["dc1"]
+
+  // spread {
+  //   attribute = "${node.datacenter}"
+  // }
+
+  group "nginx" {
+    count = 2
+
+    scaling {
+      enabled = true
+      min = 0
+      max = 3
+    }
+
+
+    network {
+      port "http" {
+        to = 80
+        static = 18080
+      }
+    }
+
+    service {
+      name = "nginx-backend"
+      port = "http"
+      tags = ["prod"]
+    }
+
+    task "nginx" {
+      driver = "docker"
+
+      config {
+        image = "nginx"
+        ports = ["http"]
+        volumes = [
+          "local/html:/usr/share/nginx/html",
+        ]
+      }
+
+      template {
+        data = <<EOF
+        <h1>Welcome to {{ env "NOMAD_JOB_NAME" }} Production {{ env "NOMAD_HOST_PORT_http" }}</h1>
+        node_dc:       {{ env "node.datacenter" }}<br>
+        node_hostname: {{ env "attr.unique.hostname" }}<br>
+        node_cores:    {{ env "attr.cpu.numcores" }}<br>
+        os_name:       {{ env "attr.os.name" }}<br>
+        cpu_model:     {{ env "attr.cpu.modelname" }}<br>
+        EOF
+        destination = "local/html/index.html"
+      }
+    }
+  }
+}
+
  • Service 타입은 상시 실행된 서비스를 명시
  • count가 2 이므로 해당 서비스는 2개를 띄우려고 시도
  • scaling 정의가 있는 경우 UI/CLI/API 에서 scaling count값 지정 가능
  • 포트에 static 명시가 되어있으므로 해당 서비스는 18080을 사용할 수 있는 count 만큼의 노드가 필요
+ + + diff --git a/04-HashiCorp/07-Nomad/02-Config/nomad-on-windows.html b/04-HashiCorp/07-Nomad/02-Config/nomad-on-windows.html new file mode 100644 index 0000000000..1bc0cef92b --- /dev/null +++ b/04-HashiCorp/07-Nomad/02-Config/nomad-on-windows.html @@ -0,0 +1,115 @@ + + + + + + + + + + Nomad on Windows | docmoa + + + + + +
본문으로 건너뛰기

Nomad on Windows

약 2 분NomadWindows

Nomad on Windows

Nomad를 Windows환경에 구성하고 실행을위해 서비스로 등록하는 방법을 알아봅니다. 솔루션 실행 환경 또는 운영/개발자의 익숙함 정도에 따라 다양한 OS를 선택하여 애플리케이션을 배포하게 됩니다. Nomad를 통해 배포를 위한 오케스트레이터를 Windows 환경에 적용하고 서비스에 등록하여 상시적으로 실행될 수 있도록하는 구성을 안내합니다.

Port 구성

참고 url : Port usedopen in new window

Nomad는 서버와 클라이언트 모드로 나뉩니다. 서버를 위해서는 3 개의 포트가 필요하고 클라이언트에서는 2 개의 포트가 필요합니다. 클라이언트에 배포되는 애플리케이션에서 사용하는 포트를 동적으로 할당하는 영역을 구성합니다.

종류기본값프로토콜설명
HTTP API4646TCP서버와 클라이언트에서 HTTP API를 제공하는데 사용됩니다.
RPC4647TCP서버와 클라이언트간의 내부. RPC 통신 및 서버간 통신에 사용됩니다.
Serf WAN4648TCP/UDP서버간 LAN/WAN 으로 GOSSIP 프로토콜로 사용됩니다.
Dynamic1025–60000TCP/UDP클라이언트에서 사용할 동적 포트를 할당합니다.

Windows에서의 동적포트 설명은 다음을 참고합니다. : Ephemeral_portopen in new window

Windows에서의 동적포트 설정은 다음을 참고합니다. : default-dynamic-port-range-tcpip-changopen in new window

디렉토리 구성

디렉토리 구성의 예는 아래와 같습니다.

└── Nomad
+    ├── bin
+    ├── config
+    └── data
+
  • bin : Nomad 실행 바이너리 파일을 다운로드하여 별도 관리하는 경우 활용할 수 있습니다. 시스템 전체에서 사용하는 경우 해당 경로를 PATH로 등록 하거나 C:\WINDOWS\system32\ 의 경로에 바이너리를 위치시키는 방법도 있습니다.
  • config : Nomad에서 사용하는 config 파일을 저장하는 위치로 사용합니다.
  • data : Nomad가 서버 또는 클라이언트로 사용되는 경우 기록, 저장되는 데이터 저장소로 사용됩니다.

설치

설치 참고 url : Installopen in new window

Dev모드 참고 url : Get Startopen in new window

Windows에 설치하는 방식은 수동, Chocolatey, 컴파일 방식을 지원합니다. 여기서는 수동 구성 방법에 대해 설명합니다.

미리 컴파일된 바이너리 파일은 다음의 경로에서 확인할 수 있습니다. 2021년 4월 18일 기준 1.0.4 버전이 최신 버전입니다.

  • 다운로드 url : Releases HashiCorp - Nomadopen in new window
    • 오픈소스는 nomad_<버전> 으로 표기됩니다.
    • 엔터프라이즈는 nomad_<버전>+ent 로 표기됩니다.
  • Windows 환경을 위해 미리 컴파일된 바이너리는 32bit/64bit 로 구분됩니다.
    • nomad_<버전>_windows_386.zip - 32bit
    • nomad_<버전>_windows_amd64.zip - 64bit

릴리즈 된 zip을 다운로드 받고 적절한 위치에 압축을 해제 합니다. 위 디렉토리 구성 에서의 예로는 bin 디렉토리 아래 위치 시킵니다.

Nomad 버전을 확인하여 바이너리가 정상적으로 실행되는지 확인합니다.

PS C:₩hashicorp₩nomad₩bin> ./nomad.exe version
+Nomad v1.0.4 (9294f35f9aa8dbb4acb6e85fa88e3e2534a3e41a)
+

Dev 모드로 실행하여 테스트 하는것도 가능합니다.

PS C:₩hashicorp₩nomad₩bin> ./nomad agent -dev
+==> No configuration files loaded
+==> Starting Nomad agent...
+==> Nomad agent configuration:
+
+       Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
+            Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
+                Client: true
+             Log Level: DEBUG
+                Region: global (DC: dc1)
+                Server: true
+               Version: 1.0.4
+
+==> Nomad agent started! Log data will stream in below:
+...생략...
+

실행 후 나열된 정보를 사용하여 UI에 접속해봅니다.
image-20210418110743732

설정

설정 설명 url : Configurationopen in new window

go_sockaddr_template : go-sockaddropen in new window

Nomad 실행시 CLI 상에 설정을 하는 Inline 방식과 설정파일을 지정하는 방식으로 구성이 가능합니다. 여기서는 구성파일을 지정하도록 하는 방식을 설명합니다.

설정 파일을 디렉토리 구성에서 지정한 config 디렉토리에 위치 시킵니다. 여기서는 예로 nomad.hcl 이라 명명합니다. 테스트를 위한 몇가지 설정요소를 아래에 설명합니다.

[nomad.hcl]

datacenter = "dc1"
+data_dir   = "C:\\hashicorp\\nomad\\data"
+bind_addr  = "0.0.0.0"
+
+advertise {
+  //  http = "{{ GetInterfaceIP \"Network 1\" }}"
+  rpc  = "{{ GetInterfaceIP \"Network 1\" }}"
+  serf = "{{ GetInterfaceIP \"Network 1\" }}"
+}
+
+server {
+  enabled          = true
+  bootstrap_expect = 1
+}
+
+client {
+  enabled           = true
+  network_interface = "Network 2"
+  meta {
+    subject = "server1"
+    purpose = "test,sample"
+  }
+  options = {
+    driver.raw_exec.enable = "1"
+  }
+}
+
+server_join {
+  retry_join = ["{{ GetInterfaceIP \"Network 1\" }}:4647"]
+}
+
  • datacenter : Nomad의 클러스터는 datacenter로 클러스터의 클라이언트들을 그룹화 할 수 있습니다. Nomad에 배포하는 대상으로 지정됩니다. 기본값은 dc1 입니다.
  • data_dir : Nomad에서 생성하는 데이터 디렉토리 위치를 지정합니다. 위 디렉토리 구성에서 지정한 data 디렉토리를 지정하였습니다.
    • Windows 환경에서 디렉토리 구분자는 \\ 를 사용합니다.
  • bind_addr : nomad의 http, rpc, serf가 사용할 기본 ip 주소를 지정합니다. 실행 환경의 모든 IP와 매핑하기 위해 0.0.0.0 으로 설정하였습니다.
  • advertise : 생략하는 경우 bind_addr 값을 상속 받지만, 사용자가 지정한 네트워크 주소를 지정하기 위해 사용됩니다. ip 형식을 사용할 수도 있고 go-sockaddr 템플릿 구성을 사용 가능하기 때문에 템플릿 형태로 지정도 가능합니다. 예제에서는 네트워크 인터페이스에 할당된 IP를 가져오는 방식 입니다. 한글도 지원합니다.
      {{ GetInterfaceIP \"이더넷 1\" }}
    +
  • server : 서버인 경우에 설정을 구성하는 항목 입니다.
    • enabled : true인 경우 서버 모드로 실행됩니다.
    • bootstrap_expect : 서버의 HA를 위해 3중화 또는 5중화 하는 경우 서버의 기대 개수 값을 넣습니다. 여기서는 하나의 Nomad 프로세스가 서버와 클라이언트 역할을 모두 수행합니다.
  • client : 클라이언트인 경우에 설정을 구성하는 항목 입니다.
    • enabled : true인 경우 클라이언트 모드로 실행됩니다.
    • network_interface : Nomad 클라이언트에서 실행하는 애플리케이션이 할당되는 네트워크 인터페이스가 Nomad 프로세스와 다른 네트워크를 사용하는 경우 해당 네트워크 인터페이스 이름을 지정 합니다.
    • meta : 배포할 조건을 사용자 정의하는 항목입니다. Label 이나 Tag와 비슷한 역할입니다. map 타입으로 여러줄을 나열하여 설정할 수 있습니다. 값은 쉼표로 구분하여 리스트처럼 사용할 수 있습니다.
    • options : 클라이언트 구성의 옵션을 지정합니다. 주로 실행 드라이버와 배포를 위한 설정을 위해 사용됩니다.
      • driver.raw_exec.enable : raw_exec 드라이버는 기본적으로 비활성화 되어있습니다. Windows에서는 exec 드라이버가 동작하지 않으므로 해당 드라이버를 활성화 합니다.
      • execopen in new window : OS에서 지원하는 격리 기본 요소를 사용하여 배포 작업으로 할당되는 리소스에 대한 접근을 제한합니다.
      • raw_execopen in new window : OS 지원 격리 요소를 사용하지 않고 Nomad가 작업 실행시 동일한 사용자로 시작 됩니다.
  • server_join : 서버와 클라이언트 구성의 경우, 또는 서버 HA 구성의 경우 서버 접속을 위한 주소를 나열합니다.

실행과 서비스 등록

구성파일을 작성하였다면, 해당 구성파일을 지정하여 Nomad를 실행할 수 있습니다.

PS C:₩hashicorp₩nomad₩bin> ./nomad agent -config=C:\hashicorp\nomad\config\nomad.hcl
+

Windows 서비스로 등록하는 경우 다음을 참고합니다.

sc.exe delete nomad
+sc.exe create nomad binPath= "C:\hashicorp\nomad\bin\nomad.exe agent -config=C:\hashicorp\nomad\config\nomad.hcl" start= auto
+net start nomad
+
DESKTOP-LenovoMini 2021-04-18 10-22-39
DESKTOP-LenovoMini 2021-04-18 10-22-39

테스트

Job 구성 url : Manage-jobopen in new window

Windows에서만 실행되는 커맨드를 활용하여 동작을 테스트 합니다.

[test.nomad]

job "test" {
+  datacenters = ["dc1"]
+  type        = "batch"
+
+  constraint {
+    attribute = "${attr.kernel.name}"
+    value     = "windows"
+  }
+
+  group "windows" {
+    count = 1
+    task "systeminfo" {
+      driver = "raw_exec"
+      config {
+        command = "C:\\windows\\system32\\systeminfo"
+      }
+    }
+  }
+}
+
  • job : job 이름을 명시 합니다.
  • datacenters : 해당 job을 배포할 대상 datacenter를 지정합니다. list 타입인 이유는 Enterprise에서는 멀티 DC 배포를 지원하기 때문입니다.
  • type : Nomad가 지원하는 배포 애플리케이션 타입은 batch, service, system 입니다.
    • batch : 지속되지 않는 1회성 작업에 대한 타입으로 반복적 작업과 파라메터를 받는 형식도 지원합니다.
    • service : 지속되는 작업, 예를들어 웹서버와 같은 형식의 애플리케이션 실행 타입을 지원합니다.
    • system : 전체 클라이언트에서 실행될 애플리케이션 실행 타입을 지원합니다. 예를들어 모니터링을 위해 모든 클라이언트에서 실행되어야하는 경우에 활용합니다.
  • constraint : job이 배포될 조건을 지정하는 것으로 반복적으로 구성가능합니다.
  • group : job에서 함께 실행될 단위를 지정합니다. task가 여럿 할당될 수 있습니다.
  • count : group에서 실행되는 배포의 개수를 지정합니다.
  • task : 실제 동작하는 배포 형식을 정의 합니다.
    • driveropen in new window : task로 실행할 배포 형태 드라이버를 지정합니다. 대표적으로 exec, raw_exec, java, docker 등이 있습니다.
    • config : driver 의 실행을위한 구성을 정의합니다. 예제에서는 raw_exec 를 사용하였으므로 command 를 입력합니다.

job의 내용은 파일로 구성하여 CLI로 등록하는 것도 가능하고 UI에서 입력하는 것도 가능합니다. UI등록의 예는 다음과 같습니다.

  1. UI의 좌측 Jobs 를 클릭하여 우측 상단의 Run Job 버튼을 클릭합니다.
    image-20210418110840679

  2. Job Definition 란에 job 정의를 채우고 Plan을 클릭합니다.
    Run a job - Nomad 2021-04-18 10-47-26

  3. Plan의 결과를 확인하고 Run 버튼으로 배포를 실행합니다.
    Run a job - Nomad 2021-04-18 10-48-47

  4. 배포의 결과를 확인합니다.
    Job test - Nomad 2021-04-18 10-49-41

    • 배포에 실패하면 붉은색으로 표기되며, Task Groups의 task 이름을 클릭하면 원인을 확인할 수 있습니다.
    • 정상 배포되면 Running 상태로 확인됩니다. batch 의 경우 실행 후 종료되기 때문에 최종적으로 Complete 상태가 됩니다.
    • 해당 task를 선택하고 마지막 Allocation 항목을 클릭하면 마지막 실행 task를 확인할 수 있고, 원격에서 실행된 Log나 생성된 File을 확인할 수 있습니다.
    Task systeminfo - Nomad 2021-04-18 10-57-18
    Task systeminfo - Nomad 2021-04-18 10-57-18

마치며

Windows 환경에 실행되는 애플리케이션을 원격에서 일괄적으로 관리하기위한 환경을 제공합니다. CI/CD 과정에서 마지막 단계인 배포 동작에 대해 API를 지원하고 스케쥴링 및 배포의 상태를 관리해주는 역할로 동작합니다. Batch, Service, System 의 배포 실행 형태를 지정할 수 있고, 다양한 실행 드라이버(exec, java, docker, 등)을 지원하여 다중의 OS 환경 및 온프레미스와 클라우드 환경 전반에 배포를 위한 쉽고 간단한 워크로드 오케스트레이션 환경을 구성할 수 있습니다.

+ + + diff --git a/04-HashiCorp/07-Nomad/04-UseCase/index.html b/04-HashiCorp/07-Nomad/04-UseCase/index.html new file mode 100644 index 0000000000..312212d22c --- /dev/null +++ b/04-HashiCorp/07-Nomad/04-UseCase/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 04 Use Case | docmoa + + + + + +
본문으로 건너뛰기

04 Use Case

1분 미만

+ + + diff --git a/04-HashiCorp/07-Nomad/04-UseCase/jenkins-pipeline.html b/04-HashiCorp/07-Nomad/04-UseCase/jenkins-pipeline.html new file mode 100644 index 0000000000..a664704a49 --- /dev/null +++ b/04-HashiCorp/07-Nomad/04-UseCase/jenkins-pipeline.html @@ -0,0 +1,420 @@ + + + + + + + + + + Jenkins Pipeline Nomad (Integrated Vault) | docmoa + + + + + +
본문으로 건너뛰기

Jenkins Pipeline Nomad (Integrated Vault)

약 6 분NomadJenkinsJavaDockerVault

Jenkins Pipeline Nomad (Integrated Vault)

Test ENV

$ sw_vers
+ProductName:	macOS
+ProductVersion:	12.4
+
+$ brew --version
+Homebrew 3.5.2
+
+$ git version
+git version 2.27.0
+
+$ java -version
+openjdk version "11.0.14.1" 2022-02-08
+
+$ gradle --version
+Welcome to Gradle 7.4.2!
+
+$ docker version
+Client:
+ Version:           20.10.9
+
+Server:
+ Engine:
+  Version:          20.10.14
+
+$ vault version
+Vault v1.11.0
+
+$ nomad version
+Nomad v1.3.1
+
+$ curl --version
+curl 7.79.1 (x86_64-apple-darwin21.0)
+
+$ aws --version
+aws-cli/2.7.11 Python/3.10.5 Darwin/21.5.0 source/x86_64 prompt/off
+

1. Vault & Nomad Integration (dev mode)

1.1 Vault Setup

Vault Start Dev mode

vault server -dev -dev-root-token-id=root
+

Vault Env

Another terminal

export VAULT_ADDR=http://127.0.0.1:8200
+export VAULT_TOKEN=root
+export NOMAD_POLICY=nomad-server
+

Vault Policy for Nomad

cat <<EOF | vault policy write $NOMAD_POLICY -
+# Allow creating tokens under "nomad-cluster" token role. The token role name
+# should be updated if "nomad-cluster" is not used.
+path "auth/token/create/nomad-cluster" {
+  capabilities = ["update"]
+}
+
+# Allow looking up "nomad-cluster" token role. The token role name should be
+# updated if "nomad-cluster" is not used.
+path "auth/token/roles/nomad-cluster" {
+  capabilities = ["read"]
+}
+
+# Allow looking up the token passed to Nomad to validate # the token has the
+# proper capabilities. This is provided by the "default" policy.
+path "auth/token/lookup-self" {
+  capabilities = ["read"]
+}
+
+# Allow looking up incoming tokens to validate they have permissions to access
+# the tokens they are requesting. This is only required if
+# `allow_unauthenticated` is set to false.
+path "auth/token/lookup" {
+  capabilities = ["update"]
+}
+
+# Allow revoking tokens that should no longer exist. This allows revoking
+# tokens for dead tasks.
+path "auth/token/revoke-accessor" {
+  capabilities = ["update"]
+}
+
+# Allow checking the capabilities of our own token. This is used to validate the
+# token upon startup.
+path "sys/capabilities-self" {
+  capabilities = ["update"]
+}
+
+# Allow our own token to be renewed.
+path "auth/token/renew-self" {
+  capabilities = ["update"]
+}
+EOF
+

Vault Policy for AWS

cat <<EOF | vault policy write aws_policy -
+path "aws/creds/s3" {
+  capabilities = ["read","update"]
+}
+EOF
+

Create Token Role

vault write auth/token/roles/nomad-cluster allowed_policies="aws_policy,db_policy" disallowed_policies="$NOMAD_POLICY" token_explicit_max_ttl=0 orphan=true token_period="259200" renewable=true
+

Create Token

vault token create -field token -policy $NOMAD_POLICY -period 72h -orphan > /tmp/token.txt
+

1.2 Nomad Setup

Nomad Start Dev mode

nomad agent -dev -vault-enabled=true -vault-address=http://127.0.0.1:8200 -vault-token=$(cat /tmp/token.txt) -vault-tls-skip-verify=true -vault-create-from-role=nomad-cluster -alloc-dir=/tmp/nomad/alloc -state-dir=/tmp/nomad/state
+

Nomad Env

Another terminal

export NOMAD_ADDR=http://127.0.0.1:4646
+

Jar file Up/Download Nexus Job

cat <<EOF | nomad job run -
+job "fileserver" {
+  datacenters = ["dc1"]
+
+  group "fileserver" {
+    count = 1
+
+    network {
+      port "http" {
+        to = 3000
+        static = 3000
+      }
+    }
+
+    task "fileserver" {
+      driver = "docker"
+
+      config {
+        image = "julienmeerschart/simple-file-upload-download-server"
+        ports = ["http"]
+      }
+    }
+  }
+}
+EOF
+

Upload Test

$ curl -F file=@/tmp/dynamic.properties http://localhost:3000
+{"downloadLink":"http://localhost:3000/file?file=dynamic.properties","curl":"curl http://localhost:3000/file?file=dynamic.properties > dynamic.properties"}
+

1.3 Vault-Nomad Integration Check

$ nomad agent-info
+client
+  heartbeat_ttl = 11.955357358s
+  known_servers = 127.0.0.1:4647
+  last_heartbeat = 9.248352347s
+  node_id = 69944736-5399-f805-9c03-35be83c9abfe
+  num_allocations = 0
+nomad
+  bootstrap = true
+  known_regions = 1
+  leader = true
+  leader_addr = 127.0.0.1:4647
+  server = true
+<...>
+vault
+  token_expire_time = 2022-06-30T08:44:26+09:00
+  token_last_renewal_time = 2022-06-27T08:44:26+09:00
+  token_next_renewal_time = 2022-06-28T20:44:26+09:00
+  token_ttl = 71h53m46s
+  tracked_for_revoked = 0
+

1.4 Vault Sample

AWS Dynamic Secret

Setup AWS Env

export AWS_ACCESS_KEY=AKIAU3NXXXXX
+export AWS_SECRET_KEY=Rex3GPUKO3++123
+export AWS_REGION=ap-northeast-2
+

Enable AWS Secret Engine

vault secrets enable aws
+

AWS Secret Engine Configuration

vault write aws/config/root \
+  access_key=$AWS_ACCESS_KEY \
+  secret_key=$AWS_SECRET_KEY \
+  region=$AWS_REGION
+

AWS Secret Engine Lease change

vault write /aws/config/lease lease=1m lease_max=1m
+

Role setup (e.g. s3)

vault write aws/roles/s3 \
+    credential_type=iam_user \
+    policy_document=-<<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+     		"s3:PutObject",
+      	"s3:PutObjectAcl"
+      ],
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+

Test AWS Secret

$ vault read aws/creds/s3
+Key                Value
+---                -----
+lease_id           aws/creds/s3/tt1sONqTebOsrJxBs6A3B4m4
+lease_duration     1m
+lease_renewable    true
+access_key         AKIAU3NXDWRUL5GVEB4H
+secret_key         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
+security_token     <nil>
+

2. Jenkins Setup

2.1 Jenkins Install (macOS)

macOS guide : https://www.jenkins.io/download/lts/macos/open in new window

brew install jenkins-lts
+
brew services start jenkins-lts
+

2.2 Unlock Jenkins

Home 디렉토리의 Jenkins 활성화를 위한 패스워드를 다음 경로에서 복사하여 http://localhost:8080open in new window 페이지의 Unlock Jenkins 에 입력

image-20220627095332250
image-20220627095332250

2.3 Jenkins Setup

Customize Jenkins

빠른 시작을 위해 기본 값인 Install suggested plugins 를 클릭

Create First Admin User

계정명, 암호, 이름, 이메일 주소를 기입하고 Save and Continue 버튼 클릭

Instance Configuration

올바른 Jenkins URL을 확인하고 Save and Finish 버튼 클릭

Jenkins is ready!

Start using Jenkins 버튼 클릭

2.4 Jenkins github plugin

GitHub

  1. GitHub 로그인

  2. 우측 상단 사용자 메뉴 클릭 후 Settings 클릭

    GitHub 2022-06-27 12-36-30
    GitHub 2022-06-27 12-36-30
  3. 좌측 메뉴 최하단 Developer settings 클릭

  4. 좌측 메뉴 Personal access tokens 클릭

  5. Generate new token 버튼 클릭

    Personal access tokens
    Personal access tokens
  6. Token 옵션 선택 후 Generate token 클릭

    • Note : 토큰 목적 입력 (e.g. Jenkins Token)
    • Expiration : 기간을 설정 (e.g. No expiration)
    • Select scopes
      • repo
      • admin:org
      • admin:repo_hook
  7. 생성된 토큰을 기록/보관

    Personal access tokens 2022-06-27 12-42-58
    Personal access tokens 2022-06-27 12-42-58

Jenkins

  1. Jenkins 관리 > 시스템 설정 으로 이동

  2. JDK 항목에서 Add JDK 클릭

    • Name : 이름 입력 (e.g. jdk11)
    • JAVA_HOME : 자바 홈 경로 (e.g. /Library/Java/JavaVirtualMachines/temurin-11.jdk/Contents/Home)
    image-20220627143641019
    image-20220627143641019
  3. Git 항목에서 Add Git을 클릭

    • Name : 이름 입력 (e.g. local)
    • Path to Git executable : git 실행파일 경로 입력 (e.g. /usr/local/bin/git)
  4. GitHub 항목에서 Add GitHub Server 드롭박스의 GitHub Server를 클릭

    • Name : 이름 입력 (e.g. jenkins_github)
    • API URL : 기본 값 (https://api.github.comopen in new window)
    • Credentials : 아래 +Add 버튼 클릭하여 Jenkins선택 후 새로운 크리덴셜 생성 후 생성된 항목 지정
    • Kind : Secret Test 선택
    • Secret : GitHub에서 생성한 토큰 입력
    • ID : 사용자 지정 (e.g. jenkins_github)
    • Test Connection 버튼으로 연결 확인
    스크린샷 2022-06-27 12.48.18
    스크린샷 2022-06-27 12.48.18
  5. Gradle 항목에서 Add Gradle 클릭

    • Name : 이름 입력 (e.g. gradle)

    • GRADLE_HOME : Gradle 홈 디렉토리 입력 (e.g. /usr/local/Cellar/gradle/7.4.2/libexec)

3. Sample Java

3.1 App Setting

Spring-boot Initializr

https://start.spring.io/open in new window

image-20220627155858474
image-20220627155858474
  • Project : Gradle Project
  • Language : Java
  • Spring Boot : 2.7.2
  • Packageing : Jar
  • Java : 11
  • Dependencies : Spring Web

App Setup

demo>src>main>resources>application.yml

dynamic:
+  path: ${DYNAMIC_PROPERTIES_PATH:/tmp/dynamic.properties}
+server:
+  port: ${NOMAD_HOST_PORT_http:8080}
+

demo>src>main>java>com>example>demo>DemoApplication.java

package com.example.demo;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+@RestController
+@SpringBootApplication
+@EnableScheduling
+public class DemoApplication {
+
+	private static String FILE_PATH;
+
+	@Value("${dynamic.path}")
+	public void setKey(String value) {
+		FILE_PATH = value;
+	}
+
+	public static void main(String[] args) {
+		SpringApplication.run(DemoApplication.class, args);
+	}
+
+	@Scheduled(fixedRate=1000)
+	public void filecheck() throws IOException {
+		List<String> str = Files.readAllLines(Paths.get(FILE_PATH));
+		System.out.println(str);
+	}
+
+	@RequestMapping(method = RequestMethod.GET, path = "/")
+	public String index() throws IOException {
+		List<String> str = Files.readAllLines(Paths.get(FILE_PATH));
+		System.out.println(str);
+
+		return "<h1>AWS</h1>"
+		.concat("<h2>" + str.get(0) + "</h2>")
+		.concat("<h2>" + str.get(1) + "</h2>");
+	}
+}
+

Set dummy properties & Test

cat <<EOF> /tmp/dynamic.properties
+aws_access_key=my_access_key
+aws_secret_key=my_secret_key
+EOF
+
$ gradle bootRun
+...
+[aws_access_key=my_access_key, aws_secret_key=my_secret_key]
+[aws_access_key=my_access_key, aws_secret_key=my_secret_key]
+<==========---> 80% EXECUTING [5s]
+> :bootRun
+

4. Pipeline 생성

Pipeline 구성 case 1

  1. GitHub checkout
  2. Gradle build
  3. jar upload
  4. Nomad Job Start

Pipeline 구성 case 2

  1. GitHub checkout
  2. Gradle build
  3. docker build
  4. docker push
  5. Nomad Job Start

4.1 Jenkins Job - Jar

  1. 좌측 + 새로운 Item 버튼 클릭

  2. 이름 입력 (e.g. Nomad Job - Java Driver)

  3. Pipeline 선택 후 OK

  4. 성성된 Jenkins Job의 Pipeline에 스크립트 구성

    pipeline {
    +    agent any
    +    triggers {
    +        cron('H */8 * * *') //regular builds
    +        pollSCM('* * * * *') //polling for changes, here once a minute
    +    }
    +    tools { 
    +        git('local')
    +        gradle('gradle')
    +        jdk("jdk11")
    +    }
    +    environment {
    +        NOMAD_ADDR = 'http://localhost:4646'
    +    }
    +    stages {
    +        stage('Clone') {
    +            steps {
    +                git branch: 'main',
    +                    credentialsId: 'jenkins_github',
    +                    url: 'https://github.com/Great-Stone/jenkins-gradle-nomad-pipeline'
    +                sh "ls -lat"
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh './gradlew test'
    +            }
    +        }
    +        stage('Build') {
    +            steps {
    +                sh './gradlew build'
    +            }
    +        }
    +        stage('Upload') {
    +            steps {
    +                sh 'mv ./build/libs/demo-0.0.1-SNAPSHOT.jar ./demo-${BUILD_NUMBER}.jar'
    +                sh 'curl -F file=@./demo-${BUILD_NUMBER}.jar http://localhost:3000'
    +            }
    +        }
    +        stage('Nomad Download') {
    +            steps {
    +                sh 'curl -C - --output nomad_1.3.1_darwin_amd64.zip https://releases.hashicorp.com/nomad/1.3.1/nomad_1.3.1_darwin_amd64.zip'
    +                sh 'unzip -o nomad_1.3.1_darwin_amd64.zip'
    +            }
    +        }
    +        stage('Deploy To Nomad') {
    +            input{
    +                message "Do you want to proceed for production deployment?"
    +            }
    +            steps {
    +                sh './nomad job run -var version=${BUILD_NUMBER} ./nomad-java.hcl'
    +            }
    +        }
    +    }
    +}
    +
  5. 지금 빌드를 클릭하여 빌드를 진행합니다. 마지막 단계에서 마우스 오버하여 승인처리합니다.

    image-20220627213452717
    image-20220627213452717
    스크린샷 2022-06-27 21.35.35
    스크린샷 2022-06-27 21.35.35

4.2 Jenkins Job - Docker / ECR

ECR - AWS 구성 필요

  1. AWS 로그인 후 ECR 서비스로 이동한 후 우측의 리포지토리 생성
  • Private 으로 생성하는 경우 AWS Credential 설정을 Jenkins에 추가
  1. 리포지토리 이름을 입력 후 태그 변경 불가능 옵션과 푸시할 때 스캔 설정

  2. 리포지토리 생성 후 URI 복사
    e.g. <id>.dkr.ecr.ap-northeast-2.amazonaws.com/demo

Plugin Setup

  1. Jenkins 관리 > 플러그인 관리 로 이동

  2. 설치 가능 탭을 선택하고 AWS Global Configuration , Docker, Docker Pipeline, Amazon ECR 를 검색 후 설치

    image-20220627220001821
    image-20220627220001821
    image-20220627220055393
    image-20220627220055393
  3. Jenkins 관리 > Manage Credentials 로 이동

  4. Domains의 (global) 항목 선택하여 Add credentials 클릭

image-20220627220603230
image-20220627220603230
  • Kind : Secret text

  • Scope : Global

  • ID : 이름 (e.g. ecr_cred)

  • Secret: JSON 형태의 Secret

    • aws_access_key_id

    • aws_secret_access_key

    • aws_session_token (sts 인 경우)

      {"aws_access_key_id":"ASIA2LEU5EPEJQGXNMJU","aws_secret_access_key":"psGEnC5COCwcUojDo6EO/Ztd7J58THSVerEc7EE9","aws_session_token":"IQoJb3JpZ2luX2VjEMj//////////wEaCXVzLXdlc3QtMiJIMEYCIQDkdZ+GEya0j8gxM/Ow5GD5Kjr8e//pA/hARZm2Tok+JgIhAOrl0c8ctXkerxsNgSwAKSjmIrdbUyHxXVsuPl+GHgywKuAECOH//////////wEQARoMNzExMTI5Mzc1Njg4IgxAc1B5sczNlB7TgyAqtASCHs37YorM6spNTNftpvajNFGewy4z8ztDo83qx5+I67ldNnWnJBt2IHCYdtLBp1/wd/8yGYFKb/TWuOgjo0pdDKc2wJQ7gAbnB8d65OYzP2SSipqUJ/E4Tz/Ojgb0UQiAd8GcXEMdEe+9WBciSK5AD2CraMmbYlq2ThBjot8BOXJHG688IbI29/Qq+Y1WpozDpjNaeLm+kd9a9GWtX1XUXUvaXDcz+81RKW271uUp4n0JhfJ5PPUilxQVfISXtv7rEpp1PjhDE3c/oK6muK8SeeIIX7fAGi1cXqHMw9PfolQY6oAHUp3Acq+8uakNyOw8Usfl9xRDf1hQyAfofsz00DCmiZinuam8dg32R1pHW7JRBYgXXD5/dnp1A7KgdrjhjECpU8+Ayo/dAOqohOfTBYS/xMrVs8tkfUxHd/AKCca5oYda1YyIxdweBp/kEZHCZTkEpzY2TxEtzVsm5fEbjTViemglVmnkeDoZGeERVyXJlxVzW2of8I3hmKAeCENcH5L+Y"}
      +

Pipeline

  1. 좌측 + 새로운 Item 버튼 클릭

  2. 이름 입력 (e.g. Nomad Job - Docker Driver)

  3. Pipeline 선택 후 OK

  4. 성성된 Jenkins Job의 Pipeline에 스크립트 구성 (Private)

    pipeline {
    +    agent any
    +    triggers {
    +        cron('H */8 * * *') //regular builds
    +        pollSCM('* * * * *') //polling for changes, here once a minute
    +    }
    +    tools { 
    +        git('local')
    +        gradle('gradle')
    +        jdk("jdk11")
    +    }
    +    environment {
    +        NOMAD_ADDR = 'http://localhost:4646'
    +        NOMAD_DOWNLOAD_URL = 'https://releases.hashicorp.com/nomad/1.3.1/nomad_1.3.1_darwin_amd64.zip'
    +        DOCKER_REGISTRY = '**********.dkr.ecr.ap-northeast-2.amazonaws.com'
    +        dockerImage = ''
    +        PATH = "/Users/gs/.rd/bin:${PATH}"
    +    }
    +    stages {
    +        stage('Clone') {
    +            steps {
    +                git branch: 'main',
    +                    credentialsId: 'jenkins_github',
    +                    url: 'https://github.com/Great-Stone/jenkins-gradle-nomad-pipeline'
    +                sh "ls -lat"
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh './gradlew test'
    +            }
    +        }
    +        stage('Java Build') {
    +            steps {
    +                sh './gradlew build'
    +            }
    +        }
    +        stage('Docker Build') {
    +            steps {
    +                script{
    +                    dockerImage = docker.build DOCKER_REGISTRY + "/demo:${BUILD_NUMBER}"
    +                }
    +            }
    +        }
    +        stage('Docker Push') {
    +            steps {
    +                script {
    +                    withCredentials([string(credentialsId: 'sts', variable: 'CREDS')]) {
    +                        def creds = readJSON text: CREDS
    +                        withEnv([
    +                            "AWS_ACCESS_KEY_ID=${creds.aws_access_key_id}",
    +                            "AWS_SECRET_ACCESS_KEY=${creds.aws_secret_access_key}",
    +                            "AWS_SESSION_TOKEN=${creds.aws_session_token}"
    +                        ]) {
    +                            sh '''
    +                            ECR_TOKEN=$(/usr/local/bin/aws ecr get-login-password --region ap-northeast-2)
    +                            echo "${ECR_TOKEN}" > ecr_token.txt
    +                            
    +                            echo "${ECR_TOKEN}" | docker login -u AWS --password-stdin ${DOCKER_REGISTRY}
    +														'''
    +                            dockerImage.push()
    +                        }
    +                    }
    +                }
    +            }
    +        }
    +        stage('Nomad Download') {
    +            steps {
    +                sh 'curl -C - --output nomad.zip ${NOMAD_DOWNLOAD_URL}'
    +                sh 'unzip -o nomad.zip'
    +            }
    +        }
    +        stage('Deploy To Nomad') {
    +            input{
    +                message "Do you want to proceed for production deployment?"
    +            }
    +            steps {
    +                sh './nomad job run -var image=${DOCKER_REGISTRY}/demo -var tag=${BUILD_NUMBER} -var ecr_token=$(cat ecr_token.txt) ./nomad-docker.hcl'
    +            }
    +        }
    +    }
    +    post {
    +        always {
    +            sh 'docker rmi ${DOCKER_REGISTRY}/demo:${BUILD_NUMBER}'
    +        }
    +    }
    +
    +}
    +
  5. 지금 빌드를 클릭하여 빌드를 진행합니다. 마지막 단계에서 마우스 오버하여 승인처리합니다.

    image-20220628190449600
    image-20220628190449600
    image-20220628190655610
    image-20220628190655610
+ + + diff --git a/04-HashiCorp/07-Nomad/04-UseCase/job-start-from-hcl.html b/04-HashiCorp/07-Nomad/04-UseCase/job-start-from-hcl.html new file mode 100644 index 0000000000..a48a87cf0a --- /dev/null +++ b/04-HashiCorp/07-Nomad/04-UseCase/job-start-from-hcl.html @@ -0,0 +1,97 @@ + + + + + + + + + + Pass HCL to API | docmoa + + + + + +
본문으로 건너뛰기

Pass HCL to API

약 1 분NomadAPIHCL

Pass HCL to API

HCL로 작성된 Job의 경우 Nomad CLI 또는 UI 접속이 가능하다면 바로 적용 가능하다.

HCL Job Sample (2048.hcl)
job "2048-game" {
+  datacenters = ["dc1"]
+  type        = "service"
+  group "game" {
+    count = 1 # number of instances
+
+    network {
+      port "http" {
+        static = 80
+      }
+    }
+
+    task "2048" {
+      driver = "docker"
+
+      config {
+        image = "alexwhen/docker-2048"
+
+        ports = [
+          "http"
+        ]
+
+      }
+
+      resources {
+        cpu    = 500 # 500 MHz
+        memory = 256 # 256MB
+      }
+    }
+  }
+}
+
nomad job run 2048.hcl
+

하지만 CLI/UI를 사용할 수 없는 환경에서 API를 사용하여 Job을 실행해야하는 경우, 특히 CICD Pipeline구성에서 API를 사용하여 Job을 실행해야하는 경우 HCL을 Json 형식으로 변경해야하는 경우가 있다.

HCL parse to Json

HCL을 Json으로 변경하는 방식의 첫번째는 CLI를 사용하는 방식이다.

nomad job run -output 2048.hcl > payload.json
+

하지만 이 경우 -output을 입력하지 않는 경우 Job이 실행되는 실수의 여지가 있고, CLI가 없다면 사용 불가하다.

다음은 API를 사용하는 방식이다.

Parsh Job : https://developer.hashicorp.com/nomad/api-docs/jobs#parse-jobopen in new window

문서의 내용처럼 HCL을 한줄로 변경하여 API로 요청하면 Json으로 형태를 출력해준다.

Sample Payload

{
+  "JobHCL": "job \"example\" {\n  type = \"service\"\n  group \"cache\" {}\n}",
+  "Canonicalize": true
+}
+

HCL을 한줄로 변경하기 까다롭거나 별도의 도구가 없다면 jq를 활용한 방식도 가이드하고 있다.

jq -Rsc '{ JobHCL: ., Canonicalize: true }' example.nomad.hcl > payload.json
+

Sample Request

/v1/jobs/parse 엔드포인트로 payload.json 데이터를 담아 요청한다.

curl \
+    --request POST \
+    --data @payload.json \
+    https://localhost:4646/v1/jobs/parse
+

Sample Response

Json으로 변경된 값을 반환한다.

{
+  "AllAtOnce": false,
+  "Constraints": null,
+  "Affinities": null,
+  "CreateIndex": 0,
+  "Datacenters": null,
+  "ID": "my-job",
+

HCL로 작성된 Job을 API로 실행하기

API가 제공하는 Json Parse를 사용, 다음과 같은 순서로 Job을 실행할 수 있다.

1. Json으로 생성할 HCL을 hcl.json으로 생성

jq -Rsc '{ JobHCL: ., Canonicalize: true }' 2048.hcl > hcl.json
+

2. /v1/jobs/parse 엔드포인트로 요청하여 Json형태로 파싱

curl --request POST --data @hcl.json http://127.0.0.1:4646/v1/jobs/parse
+

한가지 문제는, Job의 Json 정의에는 Job 이라는 키값이 최상위에 존재해야하는데, 반환되는 결과에는 Job 하위부터 출력된다. 따라서 jq를 사용하여 다음과 같이 출력을 수정하여 저장한다.

curl --request POST --data @hcl.json http://127.0.0.1:4646/v1/jobs/parse | jq -s '{ Job: .[] }' > 2048.json
+

3. 생성된 json으로 Job 실행

curl --request POST --data @2048.json http://127.0.0.1:4646/v1/jobs
+

위 과정을 다음과 같이 한줄로 정의할 수 있다.

jq -Rsc '{ JobHCL: ., Canonicalize: true }' 2048.hcl | \
+curl --request POST --data @- http://127.0.0.1:4646/v1/jobs/parse | \
+jq -s '{ Job: .[] }' - | \
+curl --request POST --data @- http://127.0.0.1:4646/v1/jobs
+
+ + + diff --git a/04-HashiCorp/07-Nomad/04-UseCase/springboot-graceful-shutdown.html b/04-HashiCorp/07-Nomad/04-UseCase/springboot-graceful-shutdown.html new file mode 100644 index 0000000000..1cde475743 --- /dev/null +++ b/04-HashiCorp/07-Nomad/04-UseCase/springboot-graceful-shutdown.html @@ -0,0 +1,94 @@ + + + + + + + + + + Graceful Shutdown 적용 (kill_signal) | docmoa + + + + + +
본문으로 건너뛰기

Graceful Shutdown 적용 (kill_signal)

약 2 분NomadSpringBootJava

Graceful Shutdown 적용 (kill_signal)

GitHub 리소스 : https://github.com/Great-Stone/nomad-springboot-graceful-shutdownopen in new window

테스트 환경

  • Gradle 7.4.2
  • Java 11
  • Spring Boot 2.7.7
  • Nomad 1.4.3

Spring Boot 리뷰

application.yml 구성

server:
+  port: 8080
+  shutdown: graceful 
+spring:
+  lifecycle:
+    timeout-per-shutdown-phase: 35s # Default 30s
+
  • server.shutdowngraceful 정의 필요
  • spring.lifecycle.timeout-per-shutdown-phase에 Graceful Shutdown 요청시 지연 시간 설정

Graceful Shutdown 테스트

  • 테스트는 빌드(gradle build) 후 해당 jar파일에 대해 실행

  • 실행 커맨드 예시 : java -jar ./build/libs/demo-0.0.1-SNAPSHOT.jar

  • 테스트 API : http://localhost:8080/test/1

    $ curl http://localhost:8080/test/1
    +(코드에서 지정한 20000ms 지연)
    +Process Success !!
    +
  • Graceful Shutdown 종료를 위해 kill -15 <PID> 형태의 시그널 전달 필요

    • KILL(-9)대신 TERM(-15)를 사용
    • KILL(SIGKILL) : 무조건적인 즉각적 종료
    • TERM(SIGTERM) : 자연스러운 실행 종료, 정상 종료 작업 처리 후 끝냄
  • SIGTERM 사용시 종료 메시지 확인 및 정상 응답 확인

      .   ____          _            __ _ _
    +/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
    +\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
    +  '  |____| .__|_| |_|_| |_\__, | / / / /
    +=========|_|==============|___/=/_/_/_/
    +:: Spring Boot ::                (v2.7.7)
    +
    +2023-01-12 14:10:14.572  INFO 45334 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 11.0.14.1 on gs-C02CT3ZFML85 with PID 45334 (/private/var/folders/5r/8y6t82xd1h183tq1l_whv8yw0000gn/T/NomadClient2000479524/d5d8f4a6-4fd7-87ea-b40b-93f0227371db/boot/local/uc started by gs in /private/var/folders/5r/8y6t82xd1h183tq1l_whv8yw0000gn/T/NomadClient2000479524/d5d8f4a6-4fd7-87ea-b40b-93f0227371db/boot)
    +2023-01-12 14:10:14.575  INFO 45334 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
    +2023-01-12 14:10:15.462  INFO 45334 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
    +2023-01-12 14:10:15.475  INFO 45334 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    +2023-01-12 14:10:15.476  INFO 45334 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.70]
    +2023-01-12 14:10:15.553  INFO 45334 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    +2023-01-12 14:10:15.553  INFO 45334 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 922 ms
    +2023-01-12 14:10:15.948  INFO 45334 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    +2023-01-12 14:10:15.957  INFO 45334 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.876 seconds (JVM running for 2.331)
    +2023-01-12 14:10:26.982  INFO 45334 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
    +2023-01-12 14:10:26.982  INFO 45334 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
    +2023-01-12 14:10:26.983  INFO 45334 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
    +2023-01-12 14:10:27.022  INFO 45334 --- [nio-8080-exec-1] com.example.demo.TestController          : ========================== Start Process -> Process Number: 4
    +2023-01-12 14:10:32.243  INFO 45334 --- [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
    +2023-01-12 14:10:47.030  INFO 45334 --- [nio-8080-exec-1] com.example.demo.TestController          : ========================== End Process -> Process Number: 4
    +2023-01-12 14:10:47.089  INFO 45334 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
    +
    • Sleep이 20초 소요되는 작업으로, 응답 중간에 SIGTERM 요청시 Commencing graceful shutdown. Waiting for active requests to complete 메시지 출력
    • 정상 종료 후 Graceful shutdown complete 메시지 출력

Nomad Job 리뷰

job "graceful" {
+  datacenters = ["dc1"]
+  
+  group "java" {
+    task "boot" {
+      driver = "java"
+      
+      kill_timeout = "40s"
+      kill_signal = "SIGTERM"
+
+      config {
+        jar_path    = "local/demo-0.0.1-SNAPSHOT.jar"
+        jvm_options = ["-Xmx2048m", "-Xms256m"]
+      }
+
+      artifact {
+        source = "https://github.com/Great-Stone/nomad-springboot-graceful-shutdown/releases/download/0.0.1/demo-0.0.1-SNAPSHOT.jar"
+      }
+    }
+  }
+}
+
  • Nomad의 기본 종료는 SIGKILL이므로 task 정의에서 kill_signalSIGTERM으로 변경 필요
  • kill_timeout 기본 값이 5초 이므로, Graceful Shutdown을 적용하려는 애플리케이션의 정의 보다 크게 설정 필요
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html b/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html new file mode 100644 index 0000000000..a01b1a032b --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html @@ -0,0 +1,244 @@ + + + + + + + + + + Dynamic application sizing | docmoa + + + + + +
본문으로 건너뛰기

Dynamic application sizing

약 1 분Nomadsamplejobautoscalerdas

Dynamic application sizing

  • Nomad autoscaler 배포 후 사용할 수 있는 기능 중에 하나
  • Dynamic application sizing(DAS)의 기능이 설정되어 있는 job을 배포 한 이후 autoscaler job에서 resource의 권고를 받아올 수 있음
  • 권고 받은 값을 사용자가 확인 후 허용할 경우 job의 resource의 변화가 정상적으로 적용됨

autoscaler job은 기존에 사용하던 job을 사용

테스트 및 사용전 확인해야 할 사항은 Nomad의 enterprise, 즉 라이선스가 필요하며, nomad-autosclaer의 경우에도 enterprise여야만 합니다.

Demo job의 배포

job "example" {
+  datacenters = ["dc1"]
+
+  group "cache-lb" {
+    count = 1
+
+    network {
+      port "lb" {}
+    }
+
+    task "nginx" {
+      driver = "docker"
+
+      config {
+        image = "nginx"
+        ports = ["lb"]
+        volumes = [
+          # It's safe to mount this path as a file because it won't re-render.
+          "local/nginx.conf:/etc/nginx/nginx.conf",
+          # This path hosts files that will re-render with Consul Template.
+          "local/nginx:/etc/nginx/conf.d"
+        ]
+      }
+
+      # This template overwrites the embedded nginx.conf file so it loads
+      # conf.d/*.conf files outside of the `http` block.
+      template {
+        data        = <<EOF
+user  nginx;
+worker_processes  1;
+error_log  /var/log/nginx/error.log warn;
+pid        /var/run/nginx.pid;
+events {
+    worker_connections  1024;
+}
+include /etc/nginx/conf.d/*.conf;
+EOF
+        destination = "local/nginx.conf"
+      }
+
+      # This template creates a TCP proxy to Redis.
+      template {
+        data          = <<EOF
+stream {
+  server {
+    listen {{ env "NOMAD_PORT_lb" }};
+    proxy_pass backend;
+  }
+  upstream backend {
+  {{ range nomadService "redis" }}
+    server {{ .Address }}:{{ .Port }};
+  {{ else }}server 127.0.0.1:65535; # force a 502
+  {{ end }}
+  }
+}
+EOF
+        destination   = "local/nginx/nginx.conf"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+      resources {
+        cpu    = 50
+        memory = 10
+      }
+
+      scaling "cpu" {
+        policy {
+          cooldown            = "1m"
+          evaluation_interval = "10s"
+
+          check "95pct" {
+            strategy "app-sizing-percentile" {
+              percentile = "95"
+            }
+          }
+        }
+      }
+
+      scaling "mem" {
+        policy {
+          cooldown            = "1m"
+          evaluation_interval = "10s"
+
+          check "max" {
+            strategy "app-sizing-max" {}
+          }
+        }
+      }
+    }
+
+    service {
+      name         = "redis-lb"
+      port         = "lb"
+      address_mode = "host"
+      provider     = "nomad"
+    }
+  }
+
+  group "cache" {
+    count = 3
+
+    network {
+      port "db" {
+        to = 6379
+      }
+    }
+
+    task "redis" {
+      driver = "docker"
+
+      config {
+        image = "redis:6.0"
+        ports = ["db"]
+      }
+
+      resources {
+        cpu    = 500
+        memory = 256
+      }
+
+      scaling "cpu" {
+        policy {
+          cooldown            = "1m"
+          evaluation_interval = "10s"
+
+          check "95pct" {
+            strategy "app-sizing-percentile" {
+              percentile = "95"
+            }
+          }
+        }
+      }
+
+      scaling "mem" {
+        policy {
+          cooldown            = "1m"
+          evaluation_interval = "10s"
+
+          check "max" {
+            strategy "app-sizing-max" {}
+          }
+        }
+      }
+
+      service {
+        name         = "redis"
+        port         = "db"
+        address_mode = "host"
+        provider     = "nomad"
+      }
+    }
+  }
+}
+

부하 테스트용 job 배포

job "das-load-test" {
+  datacenters = ["dc1"]
+  type        = "batch"
+
+  parameterized {
+    payload       = "optional"
+    meta_optional = ["requests", "clients"]
+  }
+
+  group "redis-benchmark" {
+    task "redis-benchmark" {
+      driver = "docker"
+
+      config {
+        image   = "redis:6.0"
+        command = "redis-benchmark"
+
+        args = [
+          "-h",
+          "${HOST}",
+          "-p",
+          "${PORT}",
+          "-n",
+          "${REQUESTS}",
+          "-c",
+          "${CLIENTS}",
+        ]
+      }
+
+      template {
+        destination = "secrets/env.txt"
+        env         = true
+
+        data = <<EOF
+{{ with nomadService "redis-lb" }}{{ with index . 0 -}}
+HOST={{.Address}}
+PORT={{.Port}}
+{{- end }}{{ end }}
+REQUESTS={{ or (env "NOMAD_META_requests") "100000" }}
+CLIENTS={{  or (env "NOMAD_META_clients") "50" }}
+EOF
+      }
+
+      resources {
+        cpu    = 100
+        memory = 128
+      }
+    }
+  }
+}
+

부하테스트를 진행하고 nomad ui에서 Optimize의 Recommended의 값이 변화하였고, Accept를 눌러 줄 경우 target job의 스펙이 변화합니다.

nomad Optimize
nomad Optimize
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.html b/04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.html new file mode 100644 index 0000000000..8437debdec --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.html @@ -0,0 +1,123 @@ + + + + + + + + + + Vault SWLB용도의 nginx | docmoa + + + + + +
본문으로 건너뛰기

Vault SWLB용도의 nginx

1분 미만NomadSampleJobVaultSWLB

Vault SWLB용도의 nginx

  • Vault의 HA구성 시에는 LB가 필요한데, LB대용으로 SWLB를 이용하여 Vault를 사용할 수 있다.
    • 해당 페이지에서는 nginx를 사용하였지만, HAproxy도 비슷하게 사용이 가능하다.

nginx job 파일

job "nginx" {
+  datacenters = ["dc1"]
+
+  group "nginx" {
+
+    constraint {
+      attribute = "${attr.unique.hostname}"
+      value     = "slave0"
+    }
+
+    #Vault tls가 있고 nomad client hcl 파일에 host volume이 명시되어 있는 설정 값
+    volume "cert-data" {
+      type      = "host"
+      source    = "cert-data"
+      read_only = false
+    }
+
+    #실패 없이 되라고 행운의 숫자인 7을 4번 줌
+    network {
+      port "http" {
+        to     = 7777
+        static = 7777
+      }
+    }
+
+    service {
+      name = "nginx"
+      port = "http"
+    }
+
+    task "nginx" {
+      driver = "docker"
+
+      volume_mount {
+        volume      = "cert-data"
+        destination = "/usr/local/cert"
+      }
+
+      config {
+        image = "nginx"
+
+        ports = ["http"]
+        volumes = [
+          "local:/etc/nginx/conf.d",
+
+        ]
+      }
+      template {
+        data = <<EOF        
+#Vault는 active서버 1대외에는 전부 standby상태이며 
+#서비스 호출 시(write)에는 active 서비스만 호출해야함으로 아래와 같이 consul에서 서비스를 불러옴
+
+upstream backend {
+{{ range service "active.vault" }}
+  server {{ .Address }}:{{ .Port }};
+{{ else }}server 127.0.0.1:65535; # force a 502
+{{ end }}
+}
+
+server {
+   listen 7777 ssl;
+   #위에서 nomad host volume을 mount한 cert를 가져옴
+   ssl on;
+   ssl_certificate /usr/local/cert/vault/global-client-vault-0.pem;
+   ssl_certificate_key /usr/local/cert/vault/global-client-vault-0-key.pem;
+   #vault ui 접근 시 / -> /ui redirect되기 때문에 location이 /외에는 되지 않는다.
+   location / {
+      proxy_pass https://backend;
+   }
+}
+EOF
+
+        destination   = "local/load-balancer.conf"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+      resources {
+        cpu = 100
+        memory = 201
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/autoscaler.html b/04-HashiCorp/07-Nomad/05-SampleJob/autoscaler.html new file mode 100644 index 0000000000..17c72a49ee --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/autoscaler.html @@ -0,0 +1,188 @@ + + + + + + + + + + Autoscaler | docmoa + + + + + +
본문으로 건너뛰기

Autoscaler

약 1 분Nomadsamplejobautoscaleraws

Autoscaler

Nomad Autoscaler - sample job

  • Nomad Autoscaler는 Container환경과 Non-Container환경 모두 설치 가능
  • 디버깅이 필요한 경우 log_level = "DEBUG" 옵션 설정
  • Nomad Autoscaler sampe job의 target "aws-asg" 설정방법
    • aws_asg_name은 aws cloud 환경의 Auto Scaling 그룹에 존재해야 함.
    • node_class는 nomad client에 동일하게 설정해야 함.
  • 주요 튜닝 포인트
    • policy의 cooldown, evaluation_interval 값을 워크로드 특성에 맞게 적절하게 설정
  • 오토스케일링 기준
    • 메모리 : check "mem_allocated_percentage"
    • cpu : check "cpu_allocated_percentage"
locals {
+    autoscaler_ver = "0.3.3"
+    #autoscaler_ver = "0.3.5"
+}
+
+job "autoscalerEnt" {
+  datacenters = ["dc1"]
+
+  group "autoscalerEnt" {
+    count = 1
+
+    network {
+      port "http" {}
+    }
+
+    task "autoscaler" {
+      // docker 기반의 Nomad Autoscaler는 다음과 같이 설정 
+      //   driver = "docker"
+      //   config {
+      //     image   = "hashicorp/nomad-autoscaler-enterprise:0.3.3"
+      //     command = "nomad-autoscaler"
+      //     args = [
+      //       "agent",
+      //       "-config",
+      //       "$${NOMAD_TASK_DIR}/config.hcl",
+      //       "-policy-dir",
+      //       "$${NOMAD_TASK_DIR}/policies/",
+      //     ]
+      //     ports = ["http"]
+      //   }
+      driver = "exec"
+
+      config {
+        command = "/usr/local/bin/nomad-autoscaler"
+        args = [
+          "agent",
+          "-config",
+          "$${NOMAD_TASK_DIR}/config.hcl",
+          "-http-bind-address",
+          "0.0.0.0",
+          "-http-bind-port",
+          "$${NOMAD_PORT_http}",
+          "-policy-dir",
+          "$${NOMAD_TASK_DIR}/policies/",
+        ]
+      }
+
+      artifact {
+        source      = "https://releases.hashicorp.com/nomad-autoscaler/${local.autoscaler_ver}+ent/nomad-autoscaler_${local.autoscaler_ver}+ent_linux_amd64.zip"
+        destination = "/usr/local/bin"
+      }
+      template {
+        data        = <<EOF
+nomad {
+  address = "http://{{env "attr.unique.network.ip-address" }}:4646"  #Adding nomad server addresss
+  token = "<#Adding nomad server token >"  
+}
+
+apm "nomad-apm" {
+  driver = "nomad-apm"
+  config  = {
+    address = "http://{{env "attr.unique.network.ip-address" }}:4646"
+  }  
+}
+
+dynamic_application_sizing {
+  // Lower the evaluate interval so we can reproduce recommendations after only
+  // 5 minutes, rather than having to wait for 24hrs as is the default.
+  evaluate_after = "5m"
+}
+
+#log_level = "DEBUG"
+
+target "aws-asg" {
+  driver = "aws-asg"
+  config = {
+    aws_region = "{{ $x := env "attr.platform.aws.placement.availability-zone" }}{{ $length := len $x |subtract 1 }}{{ slice $x 0 $length}}"
+  }
+}
+
+strategy "target-value" {
+  driver = "target-value"
+}
+
+  EOF
+        destination = "$${NOMAD_TASK_DIR}/config.hcl"
+      }
+      template {
+        data = <<EOF
+scaling "cluster_policy_media" {
+  enabled = true
+  min     = 1
+  max     = 100
+  
+  policy {
+    cooldown            = "5m"
+    evaluation_interval = "20s"
+    
+    check "mem_allocated_percentage" {
+      source = "nomad-apm"
+      query  = "percentage-allocated_memory"
+      strategy "target-value" {
+        target = 82
+      }
+    }
+
+    // check "cpu_allocated_percentage" {
+    //   source = "nomad-apm"
+    //   query  = "percentage-allocated_cpu"
+
+    //   strategy "target-value" {
+    //     target = 80
+    //   }
+    // }    
+
+    target "aws-asg" {
+      dry-run             = "false"
+      aws_asg_name        = "nomad_client_media_autoscaler" 
+      node_class          = "client_web"
+      node_drain_deadline = "3m"
+      node_purge          = "true"
+    }
+  }
+}
+
+EOF
+        destination = "$${NOMAD_TASK_DIR}/policies/hashistack.hcl"
+      }
+
+      resources {
+        cpu    = 200
+        memory = 256
+      }
+
+      service {
+        name = "autoscaler"
+        port = "http"
+
+        check {
+          type     = "http"
+          path     = "/v1/health"
+          interval = "5s"
+          timeout  = "2s"
+        }
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/code-server.html b/04-HashiCorp/07-Nomad/05-SampleJob/code-server.html new file mode 100644 index 0000000000..a5fa1181fa --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/code-server.html @@ -0,0 +1,104 @@ + + + + + + + + + + code-server | docmoa + + + + + +
본문으로 건너뛰기

code-server

1분 미만NomadSampleJobvs-code

code-server

  • vs-code를 local이 아닌 환경에서 사용할 수 있도록 도와주는 code-server의 예시입니다.
  • 이 code의 변수는 nomad variable를 활용해서 배포합니다.

변수 선언

  • web ui 접근 password와 code-server terminal에서 사용 할 sudo의 password 를 변수로 선언했습니다.
# nomad var put {path기반의 varialbes} key=vaule
+$ nomad var put code/config password=password
+

job sample

job "010-code-server" {
+  datacenters = ["dc1"]
+  type        = "service"
+
+  group "code-server" {
+    count = 1
+
+    network {
+      port "http" {
+        to = 8443
+        static = 8443
+      }
+    }
+
+    service {
+      name = "code-server"
+      port = "http"
+      provider = "nomad"
+
+      check {
+        type     = "http"
+        path     = "/"
+        interval = "2s"
+        timeout  = "30s"
+      }
+    }
+
+    task "code-server-runner" {
+      driver = "docker"
+
+      template {
+        data = <<EOH
+{{ with nomadVar "code/config" }}
+PASSWORD={{ .password }}
+SUDO_PASSWORD={{ .password }}
+{{ end }}
+EOH
+
+  destination = "secrets/file.env"
+  env         = true
+      }
+
+
+      config {
+        image = "linuxserver/code-server"
+        ports = ["http"]
+      }
+
+      env {
+        PGID=1000
+        PUID=1000
+      }
+
+
+      resources {
+        cpu    = 1000
+        memory = 900
+      }
+    }
+  }
+}
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/elastic.html b/04-HashiCorp/07-Nomad/05-SampleJob/elastic.html new file mode 100644 index 0000000000..599996abfd --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/elastic.html @@ -0,0 +1,136 @@ + + + + + + + + + + Elastic | docmoa + + + + + +
본문으로 건너뛰기

Elastic

1분 미만NomadSampleJob

Elastic

job "elastic" {
+  datacenters = ["dc1"]
+
+  group "elastic" {
+    network {
+      port "db" {
+        static = 9200
+      }
+      port "kibana" {
+        static = 5601
+      }
+    }
+
+    service {
+      port = "db"
+
+      check {
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+    task "elasticsearch" {
+      driver = "docker"
+
+      config {
+        image = "docker.elastic.co/elasticsearch/elasticsearch:6.8.9"
+        ports = ["db"]
+        mount = [
+          {
+            type   = "bind"
+            source = "local/elasticsearch.yml"
+            target = "/usr/share/elasticsearch/config/elasticsearch.yml"
+          }
+        ]
+      }
+
+      template {
+        data = <<EOH
+network.host: 0.0.0.0
+discovery.seed_hosts: ["127.0.0.1"]
+xpack.security.enabled: true
+xpack.license.self_generated.type: trial
+xpack.monitoring.collection.enabled: true
+EOH
+        destination = "local/elasticsearch.yml"
+      }
+
+      env {
+        # "discovery.type":"single-node",
+        ES_JAVA_OPTS = "-Xms512m -Xmx1024m"
+      }
+
+      resources {
+        cpu    = 2000
+        memory = 1024
+      }
+    }
+
+    task "kibana" {
+      driver = "docker"
+
+      config {
+        image = "docker.elastic.co/kibana/kibana:6.8.9"
+        ports = ["kibana"]
+        mount = [
+          {
+            type   = "bind"
+            source = "local/kibana.yml"
+            target = "/usr/share/kibana/config/kibana.yml"
+          }
+        ]
+      }
+
+      template {
+        data = <<EOH
+server.name: kibana
+elasticsearch.username: elastic
+elasticsearch.password: elastic
+EOH
+        destination = "local/kibana.yml"
+      }
+
+      env {
+        ELASTICSEARCH_URL="http://172.28.128.31:9200"
+      }
+
+      resources {
+        cpu    = 1000
+        memory = 1024
+      }
+    }
+  }
+}
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/elk_version7.html b/04-HashiCorp/07-Nomad/05-SampleJob/elk_version7.html new file mode 100644 index 0000000000..d07a18661f --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/elk_version7.html @@ -0,0 +1,210 @@ + + + + + + + + + + elk_version7 | docmoa + + + + + +
본문으로 건너뛰기

elk_version7

1분 미만NomadSampleJob

elk_version7

7버전에 elsaticsearch에서 기본패스워드를 찾지 못해서 env로 넣어줌

logstash는 적당한 샘플을 찾지 못해서 일단은 비워둠

job "elk" {
+
+    datacenters = ["dc1"]
+
+    constraint {
+        attribute = "${attr.kernel.name}"
+        value = "linux"
+    }
+
+    update {
+        stagger = "10s"
+        max_parallel = 1
+    }
+
+    group "elk" {
+        count = 1
+
+        restart {
+            attempts = 2
+            interval = "1m"
+            delay = "15s"
+            mode = "delay"
+        }
+        network {
+          port "elastic" {
+            to     = 9200
+            static = 9200
+          }
+          port "kibana" {
+            to     = 5601
+          }
+          port "logstash" {
+            to     = 5000
+          }
+        }
+
+        task "elasticsearch" {
+            driver = "docker"
+
+           constraint {
+             attribute = "${attr.unique.hostname}"
+             value     = "slave2"
+           }
+
+            config {
+                image = "elasticsearch:7.16.2"
+                ports = ["elastic"]
+                volumes = [
+          "local/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml",
+        ]
+      }
+      template {
+        data = <<EOF
+cluster.name: "my-cluster"
+network.host: 0.0.0.0
+discovery.type: single-node
+discovery.seed_hosts: ["127.0.0.1"]
+xpack.security.enabled: true
+xpack.license.self_generated.type: trial
+xpack.monitoring.collection.enabled: true
+EOF
+        destination   = "local/elasticsearch.yml"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+            env {
+              ELASTIC_PASSWORD = "elastic"
+            }
+
+            service {
+                name = "${TASKGROUP}-elasticsearch"
+                port = "elastic"
+            }
+
+            resources {
+                cpu = 500
+                memory = 2048
+            }
+        }
+
+        task "kibana" {
+            driver = "docker"
+
+            constraint {
+              attribute = "${attr.unique.hostname}"
+              value     = "slave2"
+            }
+
+            config {
+                image = "kibana:7.16.2"
+                ports = ["kibana"]
+                volumes = [
+          "local/kibana.yml:/usr/share/kibana/config/kibana.yml"
+        ]
+      }
+      template {
+        data = <<EOF
+#
+# ** THIS IS AN AUTO-GENERATED FILE **
+#
+
+# Default Kibana configuration for docker target
+server.host: "0.0.0.0"
+server.shutdownTimeout: "5s"
+elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
+elasticsearch.username: elastic
+elasticsearch.password: elastic
+EOF
+
+        destination   = "local/kibana.yml"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+            service {
+                name = "${TASKGROUP}-kibana"
+                port = "kibana"
+                check {
+                    type = "http"
+                    path = "/"
+                    interval = "10s"
+                    timeout = "2s"
+                }
+            }
+
+            resources {
+                cpu = 500
+                memory = 1200
+            }
+        }
+
+        task "logstash" {
+            driver = "docker"
+
+            constraint {
+              attribute = "${attr.unique.hostname}"
+              value     = "slave2"
+            }
+
+            config {
+                image = "logstash:7.16.2"
+                ports = ["logstash"]
+                volumes = [
+          "local/logstash.yml:/usr/share/logstash/config/logstash.yml"
+        ]
+      }
+      template {
+        data = <<EOF
+http.host: "0.0.0.0"
+xpack.monitoring.elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
+EOF
+
+        destination   = "local/logstash.yml"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+            service {
+                name = "${TASKGROUP}-logstash"
+                port = "logstash"
+            }
+
+            resources {
+                cpu = 200
+                memory = 1024
+            }
+        }
+    }
+}
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/index.html b/04-HashiCorp/07-Nomad/05-SampleJob/index.html new file mode 100644 index 0000000000..e5b7cee16f --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 05 Sample Job | docmoa + + + + + +
본문으로 건너뛰기

05 Sample Job

1분 미만

+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/ingress-gateway.html b/04-HashiCorp/07-Nomad/05-SampleJob/ingress-gateway.html new file mode 100644 index 0000000000..695ea3643c --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/ingress-gateway.html @@ -0,0 +1,142 @@ + + + + + + + + + + Nomad ingress gateway | docmoa + + + + + +
본문으로 건너뛰기

Nomad ingress gateway

1분 미만NomadSampleJob

Nomad ingress gateway

Nomad job으로 ingress gateway 사용하기 (with consul)

  • consul로 설정하는 ingress gateway가 아닌 Nomad job 기동 시에 ingress gateway 활성화 예제
    • hashicorp 공식 예제가 아닌 다른 걸 해보려하다, 특이한 부분을 확인함

테스트 job (python fastapi)

job "22-fastapi" {
+  datacenters = ["dc1"]
+
+  group "fastapi" {
+
+    network {
+      mode = "bridge"
+      #service가 80으로 뜸, 만약 다른 포트로 뜨는 서비스를 사용 할 경우 image와 to만 변경
+      port "http" {
+        to = 80
+      }
+    }
+    
+    service {
+      name = "fastapi"
+      #여기서 port에 위에서 미리 선언한 http를 쓸 경우 다이나믹한 port를 가져오는데 
+      #그럴 경우 ingress gateway에서 못 읽어 온다.
+      port = "80"
+      connect {
+        sidecar_service{}
+      }
+    }
+
+    task "fastapi" {
+      driver = "docker"
+
+      config {
+        image = "tiangolo/uvicorn-gunicorn-fastapi"
+        ports = ["http"]
+      }
+
+      resources {
+        cpu    = 500
+        memory = 282
+      }
+    }
+    scaling  {
+      enabled = true
+      min     = 1
+      max     = 3
+
+      policy {
+        evaluation_interval = "5s"
+        cooldown            = "1m"
+        #driver = "nomad-apm"
+        check "mem_allocated_percentage" {
+          source = "nomad-apm"
+          query  = "max_memory"
+
+          strategy "target-value" {
+            target = 80
+          }
+        }
+      }
+    }
+  }
+}
+
+

만약 service가 http로 떠야한다면 아래와 같이 service등록도 진행해야한다.

Kind      = "service-defaults"
+Name      = "fastapi"
+Namespace = "default"
+Protocol  = "http"
+

ingress job

  • 사실 하나의 job으로 만들어도 되는데 테스트 시 계속 두개의 job이 재기동되어서 둘로 나눔
job "ingress-demo" {
+
+  datacenters = ["dc1"]
+
+  group "ingress-group" {
+
+    network {
+      mode = "bridge"
+      #backend job인 fastapi의 port를 넣어줌
+      port "inbound" {
+        to     = 80
+      }
+    }
+
+    service {
+      name = "my-ingress-service"
+      port = "inbound"
+
+      connect {
+        gateway {
+
+          proxy {
+          }
+          ingress {
+            listener {
+              #backend job인 fastapi의 port를 넣어줌
+              port     = 80
+              #protocol = "http"
+              protocol = "tcp"
+              service {
+                name = "fastapi"
+              #  hosts = ["*"]
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/install-ansible-docker.html b/04-HashiCorp/07-Nomad/05-SampleJob/install-ansible-docker.html new file mode 100644 index 0000000000..ebce625ef8 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/install-ansible-docker.html @@ -0,0 +1,166 @@ + + + + + + + + + + Nomad에서 Ansible로 Docker 설치와 Template 주의사항 | docmoa + + + + + +
본문으로 건너뛰기

Nomad에서 Ansible로 Docker 설치와 Template 주의사항

1분 미만NomadAnsibleJobDocker

Nomad에서 Ansible로 Docker 설치와 Template 주의사항

참고 : https://discuss.hashicorp.com/t/escape-characters-recognized-as-a-variable-in-template-stanza/40525open in new window

Nomad를 통해 Ops작업을 수행할 때 sysbatch 타입의 Job에 Ansible을 raw_exec로 실행하면 전체 노드에서 일괄로 작업을 수행할 수 있다.

Ansible에서 사용하는 문법 중 {{}}의 부호로 팩트를 사용하는 경우 Nomad에서 사용하는 Template의 {{}}과 겹쳐 오류가 발생한다.

Template failed: (dynamic): parse: template: :23: function "ansible_distribution_release" not defined
+

이경우 Nomad에서 다음과 같이 표기하여 템플릿 문자에 대한 치환이 가능하다.

 {{ --> {{ "{{" }}
+ }} --> {{ "}}" }}
+

다음은 Ansible에서 apt_repository 수행 시 ansible_architectureansible_distribution_release 같은 팩트 값을 Template으로 Playbook을 작성한 예제 이다.

job "install-ansible-docker" {
+  datacenters = ["hashitalks-kr"]  # 사용할 데이터 센터 이름으로 수정
+
+  type = "sysbatch"  # 배치 작업 유형
+
+  constraint {
+    attribute = "${attr.os.name}"
+    value     = "ubuntu"
+  }
+
+  parameterized {
+    payload       = "forbidden"
+  }
+
+  group "install- group" {
+
+    task "install-ansible-task" {
+      lifecycle {
+        hook = "prestart"
+        sidecar = false
+      }
+      
+      driver = "raw_exec"  # 외부 스크립트를 실행
+
+      config {
+        command = "local/install_ansible.sh"
+      }
+
+      template {
+        destination = "local/install_ansible.sh"
+        data = <<EOF
+#!/bin/bash
+sudo apt-get update
+sudo apt-get install -y ansible
+EOF
+      }
+    }
+
+    task "install-docker-task" {
+      driver = "raw_exec"  # 외부 스크립트를 실행
+
+      config {
+        command = "ansible-playbook"
+        args = [
+          "local/playbook.yml"
+        ]
+      }
+
+      env {
+        JAVA_VERSION = "${NOMAD_META_DesiredJavaVersion}"
+      }
+
+      template {
+        destination = "local/playbook.yml"
+        data = <<EOF
+---
+- hosts:
+    - localhost
+  become: yes
+  tasks:
+    - name: Install aptitude
+      apt:
+        name: aptitude
+        state: latest
+        update_cache: true
+
+    - name: Install required packages
+      apt:
+        pkg:
+          - apt-transport-https
+          - ca-certificates
+          - curl
+          - software-properties-common
+          - python3-pip
+          - virtualenv
+          - python3-setuptools
+        state: latest
+        update_cache: true
+
+    - name: Add Docker GPG apt Key
+      apt_key:
+        url: https://download.docker.com/linux/ubuntu/gpg
+        state: present
+
+    - name: Add Docker repository
+      apt_repository:
+        repo: "deb [arch={{ env "attr.cpu.arch" }}] https://download.docker.com/linux/ubuntu {{"{{"}} ansible_lsb.codename {{"}}"}} stable"
+        state: present
+        update_cache: true
+
+    - name: Update the apt package index
+      apt:
+        update_cache: true
+
+    - name: Install Docker CE
+      apt:
+        name: docker-ce
+        state: latest
+
+    - name: Install Docker CE etc.
+      apt:
+        name:
+          - docker-ce-cli
+          - containerd.io
+          - docker-buildx-plugin
+          - docker-compose-plugin
+        state: present
+
+    - name: Ensure Docker starts on boot
+      service:
+        name: docker
+        enabled: true
+        state: started
+EOF
+      }
+
+      resources {
+        cpu    = 500
+        memory = 256
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/jboss.html b/04-HashiCorp/07-Nomad/05-SampleJob/jboss.html new file mode 100644 index 0000000000..e05e40132c --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/jboss.html @@ -0,0 +1,159 @@ + + + + + + + + + + Wildfly(Jboss) | docmoa + + + + + +
본문으로 건너뛰기

Wildfly(Jboss)

약 1 분NomadSampleJobwildflyJBoss

Wildfly(Jboss)

image info : https://quay.io/repository/wildfly/wildfly?tab=infoopen in new window
github : https://github.com/jboss-dockerfiles/wildflyopen in new window
wildfly docker example : http://www.mastertheboss.com/soa-cloud/docker/deploying-applications-on-your-docker-wildfly-image/open in new window

Wildfly 이미지를 베이스로 기존 Dockerfile을 작성하여 빌드 후 컨테이너를 기준으로 배포하는 것도 가능하지만, 베이스 이미지를 유지한 채로 애플리케이션(war)을 바인드하여 실행하는 것도 가능하다.

Dockerfile과 비교

  • dockerfile 의 예

    FROM jboss/wildfly
    +RUN /opt/jboss/wildfly/bin/add-user.sh admin admin --silent
    +ADD jboss-as-helloworld.war /opt/jboss/wildfly/standalone/deployments/
    +CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"]
    +
    • FROM은 Nomad가 실행시킬 이미지로 지정
    • RUN 절의 add-user.shmgmt-users.properties를 생성하고자 하는 목적이므로 Nomad의 artifact로 중앙레포에서 받거나 template으로 작성하여 바인딩 가능
    • ADD 절은 WAR파일을 추가하는 것으로 호스트의 파일 또는 artifact로 중앙레포에서 받아 바인딩
    • CMD 절은 기본 실행명령을 대체하는 것으로 Nomad에서 commandargs로 대체 가능
  • job > groups > task(docker) > artifact :
    WAR 파일을 다운로드 받아 준비

    참고

    Nomad 클라이언트 호스트에 미리 파일을 두는것도 가능하나 오케스트레이션 특성상 중랑 레포기능을 하는곳에서 배포시 다운받는 방식이 확장성 측면에서 고려되어야 함

  • job > groups > task(docker) > template :

    • add-user.sh를 통해 management 콘솔의 사용자를 생성해야 하지만 미리 생성된 내용(admin/admin)을 넣어 처리
    • 다른 사용자를 생성하고자 한다면 설치된 wildfly나 docker로 실행한 wildfly에서 다음 스크립트로 생성된 파일 내용 확인하여 template의 data로 처리
      # example (admin/admin)
      +/opt/jboss/wildfly/bin/add-user.sh admin admin --silent
      +cat /opt/jboss/wildfly/standalone/configuration/mgmt-users.properties
      +
      admin=c22052286cd5d72239a90fe193737253
      +
  • job > groups > task(docker) > config > mount :

    • 다운받은 WAR 파일과 mgmt-users.properties를 컨테이너에 바인딩
    • Nomad가 Host 내부적으로 별도 루트경로를 할당받으므로, 각 파일과 구성파일은 독립적으로 위치함
  • job > groups > task(docker) > config > args :
    management가 기본 127.0.0.1이므로 포트포워딩으로 접속이 불가하므로 0.0.0.0으로 변경

    args = ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"]
    +

    조금더 명확하게는 command"/opt/jboss/wildfly/bin/standalone.sh"를 구성하고 args를 분리하는 것도 가능

Sample Job

locals {
+  WAR_URL = "https://github.com/spagop/quickstart/raw/master/management-api-examples/mgmt-deploy-application/application/jboss-as-helloworld.war"
+  WAR_DEST = "local/deployments/jboss-as-helloworld.war"
+}
+
+job "jboss" {
+  datacenters = ["dc1"]
+
+  group "jboss" {
+
+    network {
+      port "http" {
+        to = "8080"
+      }
+      port "mgmt" {
+        to = "9990"
+      }
+    }
+
+    ### csi (nfs) 
+    # volume "nfs-vol" {
+    #   type            = "csi"
+    #   source          = "nfs-vol"
+    #   read_only       = false
+    #   attachment_mode = "file-system"
+    #   access_mode     = "single-node-writer"
+    #   #per_alloc       = true
+    # }
+
+    service {
+      name = "jboss-http"
+      port = "http"
+
+      check {
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+    task "http" {
+      driver = "docker"
+
+      config {
+        image = "jboss/wildfly"
+        ports = ["http", "mgmt"]
+        args = ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"]
+        mount {
+          type = "bind"
+          target = "/opt/jboss/wildfly/standalone/deployments/jboss-as-helloworld.war"
+          source = "local/deployments/jboss-as-helloworld.war"
+          readonly = false
+          bind_options {
+            propagation = "rshared"
+          }
+        }
+        mount {
+          type = "bind"
+          target = "/opt/jboss/wildfly/standalone/configuration/mgmt-users.properties"
+          source = "local/configuration/mgmt-users.properties"
+        }
+      }
+
+      env {
+      }
+
+      artifact {
+        source      = local.WAR_URL
+        destination = local.WAR_DEST
+      }
+      
+      template {
+        # mgmt : admin / admin
+        data        = "admin=c22052286cd5d72239a90fe193737253"
+        destination = "local/configuration/mgmt-users.properties"
+      }
+
+      resources {
+        cpu    = 500
+        memory = 512
+      }
+
+      ### csi (nfs) 
+      # volume_mount {
+      #   volume      = "nfs-vol"
+      #   destination = "/csi"
+      # }
+    }
+
+    scaling  {
+      enabled = true
+      min     = 1
+      max     = 4
+
+      ### cpu autoscale
+      # policy {
+      #   evaluation_interval = "10s"
+      #   cooldown            = "1m"
+      #   driver = "nomad-apm"
+      #   check "mem_allocated_percentage" {
+      #     source = "nomad-apm"
+      #     query  = "avg_cpu" 
+      #     strategy "target-value" {
+      #       target = 90
+      #     }
+      #   }
+      # }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/jenkins_java_driver.html b/04-HashiCorp/07-Nomad/05-SampleJob/jenkins_java_driver.html new file mode 100644 index 0000000000..3f8bc97def --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/jenkins_java_driver.html @@ -0,0 +1,154 @@ + + + + + + + + + + Jenkins(Java Driver) on Nomad | docmoa + + + + + +
본문으로 건너뛰기

Jenkins(Java Driver) on Nomad

약 1 분NomadSampleJobJenkins

Jenkins(Java Driver) on Nomad

Nomad

1. Jenkins의 기본 실행 방식

Jenkins의 공식 설치 안내에서 처럼 Java를 실행시킬 수 있는 환경에서 war 형태의 자바 웹어플리케이션 압축 파일을 실행하는 형태, 컨테이너, OS별 지원되는 패키지 형태로 실행된다.

각 설치 형태의 특징은 다음과 같다.

형태설명특징
warJRE 또는 JDK가 설치되어있는 환경에서 실행가능서버 재부팅, 장애 시 수동으로 실행 필요
Container컨테이너 런타임(e.g. docker, containerd ... )에서 실행컨테이너 관리 및 영구저장소 관리 필요
Package각 OS 마다 제공되는 패키지 관리자에서 관리필요한 패키지 자동설치 및 재부팅시 재시작 가능하나 장애시 수동 실행 필요

2. Nomad에서의 Java 애플리케이션 실행의 의미

Nomad는 애플리케이션 실행을 오케스트레이션 해주는 역할로, Java 애플리케이션의 여러 실행 형태에 장점만을 취합하여 실행 환경을 제공할 수 있다.

  • 운영에 익숙한 Host OS에서 실행
  • chroot, cgroups 등의 컨테이너 특징으로 격리 기능 제공
  • 프로세스 장애 시 재시작
  • 영구저장소 마운트 (Host 디렉토리와 CSI 둘 모두 지원)
  • Java 실행을 선언적으로 구성
  • Java의 범위정의되는 Memory(Xms, Xmx)에 맞춰 리소스 격리

3. Nomad 구성 요구사항

Nomoad 에서는 Jenkins 실행을 위해 다음 조건이 필요하다.

3.1 Nomad Client

  • Host Volume을 위한 디렉토리 생성
    # 예시
    +mkdir -p /opt/nomad/volume/jenkins
    +
  • Linux/macOS의 경우 Java Driver 실행시 계정을 nobody로 부여하므로 nobody계정에 권한으로 실행 필요
    # 예시
    +# macOS인 경우
    +chown -R nobody:nobody /opt/nomad/volume/jenkins
    +# ubuntu인 경우
    +chown -R nobody:nogroup /opt/nomad/volume/jenkins
    +
  • Client 구성 피알의 client 블록에 Host Volume을 지정
    client {
    +  enabled = true
    +  # 생략
    +  host_volume "jenkins" {
    +    path      = "/opt/nomad/volume/jenkins"
    +    read_only = false
    +  }
    +}
    +

3.2 Nomad Scheduler

Nomad 1.1open in new window부터 리소스에 대한 유연한 설정 기능이 추가되었다. 그 중 메모리 할당 추가 기능인 Memory Oversubscriptionopen in new window이 추가되었고, Java 애플리케이션이 갖는 특징인 JVM 메모리의 범위 할당과도 연계되는 설정 방식이다. JVM 64bit 부터는 기본적으로 소모하는 메모리가 크고, 한번 증가한 메모리는 장기간 유지되기 때문에 메모리에 대한 유연한 설정은 중요하다.

특히, Nomad에서 리소스를 격리하여 Java 드라이버에 제공되므로 지정된 메모리보다 초과 사용하는 경우 격리된 리소스로 인해 Error code 143 Signal 9 (Out Of Memory 로 인한 프로세스 강제종료)형상이 발생할 수 있다.

Oversubscription 기능은 고급 기능으로 구성설정에서 지정할 수는 없고 CLI/API/Terraform을 사용하여 설정을 변경해야 한다. (설명 링크open in new window)

CLI

링크 : https://developer.hashicorp.com/nomad/docs/commands/operator/scheduler/set-configopen in new window

$ nomad operator scheduler set-config -memory-oversubscription=true
+

4. Job Sample

  • resources
    • memory_max : 유동적 메모리 할당
    • network : Jenkins에서 사용할 포트 지정
  • env : Jenkins Home 디렉토리 위한 환경 변수 JENKINS_HOME 선언
  • config
    • jar_path : artifact로 받은 war파일 경로
    • jvm_options : jvm 실행 옵션
    • args : war 실행 문서에서 지정하는 --httpPort는 args 항목이고 jvm 옵션이 아님에 주의
  • volume_mount : Jenkins Home 디렉토리로 Host Volume으로 지정한 영구적 볼륨 할당
  • artifact : war 파일 다운로드 경로
variable "namespace" {
+  default = "default"
+}
+
+variable "jenkins_version" {
+  default = "2.361.3"
+}
+
+job "jenkins" {
+  datacenters = ["home"]
+  namespace = var.namespace
+
+  type = "service"
+
+  constraint {
+    attribute = "${meta.type}"
+    value     = "vraptor"
+  }
+
+  update {
+    healthy_deadline = "10m"
+    progress_deadline = "20m"
+  }
+
+  group "jenkins" {
+    count = 1
+    volume "jenkins" {
+      type      = "host"
+      source    = "jenkins"
+      read_only = false
+    }
+    network {
+      port "jenkins_http" {
+        // to = 8080
+        static = 8888
+      }
+    }
+    task "war" {
+      driver = "java"
+      resources {
+        cpu    = 1000
+        memory = 1024
+        memory_max = 2048
+      }
+      env {
+        JENKINS_HOME = "/jenkins_home"
+      }
+      config {
+        jar_path    = "local/jenkins.war"
+        jvm_options = ["-Xms1024m", "-Xmx2048m"]
+        args = ["--httpPort=${NOMAD_PORT_jenkins_http}"]
+      }
+      volume_mount {
+        volume      = "jenkins"
+        destination = "/jenkins_home"
+        read_only = false
+      }
+      service {
+        name = "jenkins"
+        tags = ["java", "cicd"]
+        provider = "nomad"
+
+        port = "jenkins_http"
+
+        check {
+          name = "jenkins port"
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+        }
+      }
+      logs {
+        max_files     = 10
+        max_file_size = 10
+      }
+      artifact {
+        source = "https://get.jenkins.io/war-stable/${var.jenkins_version}/jenkins.war"
+        options {
+          checksum = "sha256:f39cb8d09fd17c72dc096511ce50f245fc3004d1022aaaf60421a536f740c9b9"
+        }
+        // destination = "local"
+      }
+    }
+  }
+}
+

실행 후 Admin Token

Jenkins Home 디렉토리로 지정한 디렉토리에 관련 파일이 생성되며, 실제 호스트의 디렉토리에서 접근하는 것도 가능하나 Nomad의 Exec에서 접근하는 것도 가능하다

+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/keycloak.html b/04-HashiCorp/07-Nomad/05-SampleJob/keycloak.html new file mode 100644 index 0000000000..ad901a97ac --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/keycloak.html @@ -0,0 +1,105 @@ + + + + + + + + + + Keycloak | docmoa + + + + + +
본문으로 건너뛰기

Keycloak

1분 미만NomadSampleJob

Keycloak

Keycloak은 Stateful 한 특성이 있어서 볼륨을 붙여주는것이 좋다.

// nomad namespace apply -description "Keycloak" keycloak
+
+job "keycloak" {
+  type = "service"
+  datacenters = ["dc1"]
+  namespace = "keycloak"
+
+  group "keycloak" {
+    count = 1
+
+    volume "keycloak-vol" {
+      type = "host"
+      read_only = false
+      source = "keycloak-vol"
+    }
+
+    task "keycloak" {
+      driver = "docker"
+
+      volume_mount {
+        volume = "keycloak-vol"
+        destination = "/opt/jboss/keycloak/standalone/data"
+        read_only = false
+      }
+
+      config {
+        image = "quay.io/keycloak/keycloak:14.0.0"
+        port_map {
+          keycloak = 8080
+          callback = 8250
+        }
+      }
+      
+      env {
+        KEYCLOAK_USER = "admin"
+        KEYCLOAK_PASSWORD = "admin"
+      }
+
+      resources {
+        memory = 550
+
+        network {
+          port "keycloak" {
+            static = 18080
+          }
+          port "callback" {
+            static = 18250
+          }
+        }
+      }
+
+      service {
+        name = "keycloak"
+        tags = ["auth"]
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "keycloak"
+        }
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/mongodb.html b/04-HashiCorp/07-Nomad/05-SampleJob/mongodb.html new file mode 100644 index 0000000000..92ae89a386 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/mongodb.html @@ -0,0 +1,80 @@ + + + + + + + + + + MongoDB | docmoa + + + + + +
본문으로 건너뛰기

MongoDB

1분 미만NomadSampleJob

MongoDB

job "mongodb" {
+  datacenters = ["dc1"]
+
+  group "mongodb" {
+    network {
+      port "db" {
+        static = 27017
+      }
+    }
+
+    service {
+      port = "db"
+
+      check {
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+    task "mongodb" {
+      driver = "docker"
+
+      config {
+        image = "mongo:3.6.21"
+        ports = ["db"]
+      }
+
+      env {
+        MONGO_INITDB_ROOT_USERNAME = "admin"
+        MONGO_INITDB_ROOT_PASSWORD = "password"
+      }
+
+      resources {
+        cpu    = 2000
+        memory = 1024
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/nexus.html b/04-HashiCorp/07-Nomad/05-SampleJob/nexus.html new file mode 100644 index 0000000000..0f4c1031d9 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/nexus.html @@ -0,0 +1,72 @@ + + + + + + + + + + Nexus | docmoa + + + + + +
본문으로 건너뛰기

Nexus

1분 미만NomadSampleJob

Nexus

job "nexus" {
+  datacenters = ["dc1"]
+
+  group "nexus" {
+    count = 1
+
+    network {
+      port "http" {
+        to = 8081
+        static = 8081
+      }
+    }
+
+    task "nexus" {
+      driver = "docker"
+
+      config {
+        image = "sonatype/nexus3"
+        ports = ["http"]
+      }
+      
+      env {
+        INSTALL4J_ADD_VM_PARAMS = "-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/some-other-dir"  
+      }
+      
+      resources {
+        cpu    = 1000
+        memory = 2703
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/nginx.html b/04-HashiCorp/07-Nomad/05-SampleJob/nginx.html new file mode 100644 index 0000000000..e7621a71eb --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/nginx.html @@ -0,0 +1,155 @@ + + + + + + + + + + nginx | docmoa + + + + + +
본문으로 건너뛰기

nginx

1분 미만NomadSampleJobreverse proxyconsul service discovery

nginx

  • nomad와 consul로 cluster로 구성되어 있는 환경에 L4이후에 nomad로 LB를 해야할 경우 사용
    • nginx server_name설정에서 도메인을 받아오고 location에서는 각각의 서브도메인 별로 reverse proxy 동작
      • reverse proxy(up stream)에서는 각각의 consul의 등록된 서비스 호출
job "nginx" {
+  datacenters = ["dc1"]
+
+  group "nginx" {
+    //인증서는 host volume에 업로드
+    volume "certs" {
+      type      = "host"
+      source    = "certs"
+      read_only = true
+    }
+ 
+    network {
+      port "http" {
+        static  = 80
+        to      = 80
+      }
+      port "https" {
+        to      = 443
+        static  = 443
+      }
+    }
+
+    service {
+      name = "nginx"
+      port = "http"
+      tags = ["web"]
+      check {    
+        type     = "tcp"
+        port     = "http"
+        interval = "2s"
+        timeout  = "2s"
+      }
+    }
+
+    task "server" {
+
+      driver = "docker"
+
+      volume_mount {
+        volume      = "certs"
+        destination = "/etc/nginx/certs"
+      }
+
+      config {
+        image = "nginx"
+        ports = ["http","https"]
+        #ports = ["http","https"]
+        volumes = [
+          "local:/etc/nginx/conf.d",
+        ]
+      }
+
+      template {
+        data = <<EOF
+
+upstream login.shoping.co.kr {
+{{ range service "login" }}
+  server {{ .Address }}:{{ .Port }};
+{{ else }}server 127.0.0.1:65535; # force a 502
+{{ end }}
+}
+upstream singup.shoping.co.kr {
+{{ range service "signup" }}
+  server {{ .Address }}:{{ .Port }};
+{{ else }}server 127.0.0.1:65535; # force a 502
+{{ end }}
+}
+upstream main.shoping.co.kr {
+{{ range service "main" }}
+  server {{ .Address }}:{{ .Port }};
+{{ else }}server 127.0.0.1:65535; # force a 502
+{{ end }}
+}
+
+server {
+   listen 80;
+   listen 443 ssl;
+   //domain 및 subdomain호출
+   server_name *.shoping.co.kr;
+   ssl_certificate "/etc/nginx/certs/server.pem";
+   ssl_certificate_key "/etc/nginx/certs/server.key";
+   proxy_read_timeout      300;
+   proxy_buffers           64 16k;
+
+   //각 sub도메인별
+   location / {
+      if ($host = login.shoping.co.kr) {
+        proxy_pass login.shoping.co.kr;
+      }
+      if ($host = singup.shoping.co.kr) {
+        proxy_pass singup.shoping.co.kr;
+      }
+      if ($host !~ "(.*main)") {
+        proxy_pass main.shoping.co.kr;
+      }
+   }
+}
+
+
+
+EOF
+
+        destination   = "local/load-balancer.conf"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+      resources {
+        cpu = 2000
+        memory = 2000
+      }
+    }
+  }
+}
+
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/nomad-pack.html b/04-HashiCorp/07-Nomad/05-SampleJob/nomad-pack.html new file mode 100644 index 0000000000..404849d655 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/nomad-pack.html @@ -0,0 +1,202 @@ + + + + + + + + + + nomad-pack custom registry | docmoa + + + + + +
본문으로 건너뛰기

nomad-pack custom registry

약 2 분NomadSampleJobnomad-packvuepress

nomad-pack custom registry

Docker image build

  • 다같이 사용하던 docmoa를 build 해보기로함
  • core.js의 의존성 패키지 중에 python2.7, make, gcc, g++를 요구하는 이상한 패키지가 있어서 image 크기가 급증함...
    • 추후 CI/CD 재구성 시 vue.js는 어떤식으로 처리해야할 지 고민해봐야 할 듯
# Docker file
+FROM blasteh/vuepress:8.3 #기존에 돌아 다니는 vuepress의 npm 버전이 너무 낮아 하나 받아서 버전업함
+
+#특정 패키지 빌드 시 아래와 같은패캐지들을 필요로 함
+RUN apk add --no-cache python2
+RUN apk add --no-cache make
+RUN apk add --no-cache gcc
+RUN apk add --no-cache g++
+
+RUN mkdir /etc/bin
+
+RUN cp /usr/bin/python2 /etc/bin/python2.7
+RUN cp /usr/bin/make    /etc/bin/make
+RUN cp /usr/bin/gcc     /etc/bin/gcc
+RUN cp /usr/bin/g++     /etc/bin/g++
+
+RUN npm config set python /etc/bin/python2.7
+RUN npm config set make   /etc/bin/make
+RUN npm config set gcc    /etc/bin/gcc
+RUN npm config set g++    /etc/bin/g++
+
+ADD docs /root/src/docs
+
+WORKDIR /root/src/docs
+RUN npm install
+
+expose 8000
+
+ENTRYPOINT ["/usr/local/bin/npm", "run", "dev"]
+
+
+

vcs 구성

  • web gitlab을 사용함
  • private로 만들면 권한문제 해결이 안됨. 그래서 일단 public으로 구성

디렉토리 구조

pack/vuepres
+├── metadata.hcl 
+├── outputs.tpl
+├── templates
+│   └── vuepress.nomad.tpl
+└── variables.hcl
+
#metadata.hcl
+app {
+  url = "https://gitlab.com/swbs9000/nomad-pack"
+  author = "unghee"
+}
+
+pack {
+  name = "vuepress"
+  description = "vuepress test."
+  url = "https://github.com/swbs90/vuepress"
+  version = "0.0.1"
+}
+
#variables.hcl
+variable "job_name" {
+  description = "The name to use as the job name which overrides using the pack name"
+  type        = string
+  // If "", the pack name will be used
+  default = ""
+}
+
+variable "datacenters" {
+  description = "A list of datacenters in the region which are eligible for task placement"
+  type        = list(string)
+  default     = ["dc1"]
+}
+
+variable "region" {
+  description = "The region where the job should be placed"
+  type        = string
+  default     = "global"
+}
+
+variable "consul_service_name" {
+  description = "The consul service you wish to load balance"
+  type        = string
+  default     = "webapp"
+}
+
+variable "version_tag" {
+  description = "The docker image version. For options, see https://hub.docker.com/repository/docker/swbs90/vuepress"
+  type        = string
+  default     = "latest"
+}
+
+#variable "http_port" {
+#  description = "The Nomad client port that routes to the Vuepress. This port will be where you visit your load balanced application"
+#  type        = number
+#  default     = 8000
+#}
+
+variable "resources" {
+  description = "The resource to assign to the Vuepress system task that runs on every client"
+  type = object({
+    cpu    = number
+    memory = number
+  })
+  default = {
+    cpu    = 800,
+    memory = 1200
+  }
+}
+
+
#vuepress.nomad.tpl
+job "[[ .vuepress.job_name ]]" {
+  region      = "[[ .vuepress.region ]]"
+  datacenters = [[ .vuepress.datacenters | toPrettyJson ]]
+  // must have linux for network mode
+  constraint {
+    attribute = "${attr.kernel.name}"
+    value     = "linux"
+  }
+
+  group "vuepress" {
+    count = 1
+    network {
+      port "http" {
+        to = 8000
+      }
+    }
+
+    service {
+      name = "[[ .vuepress.consul_service_name ]]"
+      port = "http"
+    }
+
+    task "vuepress" {
+      driver = "docker"
+      config {
+        image = "swbs90/vuepress:[[ .vuepress.version_tag ]]"
+        ports = ["http"]
+      }
+      resources {
+        cpu    = [[ .vuepress.resources.cpu ]]
+        memory = [[ .vuepress.resources.memory ]]
+      }
+    }
+  }
+}
+
+

nomad-pack registry 등록 및 실행

  • 이제 다 끝났다. 등록하고 실행하면 된다.
#커스텀 registry 등록하기
+nomad-pack registry add vuepress https://gitlab.com/swbs9000/vuepress.git
+
+#배포하기
+nomad-pack plan vuepress --var="job_name=vuepress" --var="consul_service_name=vuepress" --var="version_tag=0.0.3" --registry=vuepress
++/- Job: "vuepress"
+- Meta[pack.deployment_name]: "vuepress@latest"
+- Meta[pack.job]:             "vuepress"
+- Meta[pack.name]:            "vuepress"
+- Meta[pack.path]:            "/root/.nomad/packs/vuepress/vuepress@latest"
+- Meta[pack.registry]:        "vuepress"
+- Meta[pack.version]:         "latest"
+Task Group: "vuepress" (1 create/destroy update)
+    Task: "vuepress"
+
+» Scheduler dry-run:
+- All tasks successfully allocated.
+Plan succeeded
+
+nomad-pack run vuepress --var="job_name=vuepress" --var="consul_service_name=vuepress" --var="version_tag=0.0.3" --registry=vuepress
+  Evaluation ID: d38e6717-cd12-6ef5-62d4-9b5da1755020
+  Job 'vuepress' in pack deployment 'vuepress@latest' registered successfully
+Pack successfully deployed. Use vuepress@latest with --ref=latest to manage this this deployed instance with plan, stop, destroy, or info
+
+Vuepress(my docma) successfully deployed.
+
+

작동 확인

  • 배포가 잘 되었으면, ip:port가 생성되고 그 정보로 우리가 잘 아는 페이지를 볼 수 있다.
    • 배포 확인 및 ip:port 확인
    • 접속 테스트
      • 아주 잘 보이고 잘 작동함
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/oracleXE.html b/04-HashiCorp/07-Nomad/05-SampleJob/oracleXE.html new file mode 100644 index 0000000000..ff92166919 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/oracleXE.html @@ -0,0 +1,84 @@ + + + + + + + + + + Oracle XE | docmoa + + + + + +
본문으로 건너뛰기

Oracle XE

1분 미만NomadSampleJob

Oracle XE

job "oracle" {
+  datacenters = ["dc1"]
+
+  group "oracle" {
+    network {
+      port "db" {
+        static = 1521
+      }
+      port "manage" {
+        static = 5500
+      }
+    }
+
+    service {
+      port = "db"
+
+      check {
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+    task "oracle" {
+      driver = "docker"
+
+      config {
+        image = "oracle/database:18.4.0-xe"
+        ports = ["db", "manage"]
+      }
+
+      env {
+        DB_MEMORY = "2GB"
+        ORACLE_PWD = "password"
+        ORACLE_SID = "XE"
+      }
+
+      resources {
+        cpu    = 2000
+        memory = 1024
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/param-batch-job.html b/04-HashiCorp/07-Nomad/05-SampleJob/param-batch-job.html new file mode 100644 index 0000000000..04f7ba31b9 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/param-batch-job.html @@ -0,0 +1,103 @@ + + + + + + + + + + param-batch-job | docmoa + + + + + +
본문으로 건너뛰기

param-batch-job

1분 미만NomadSampleJobparambatch

param-batch-job

  • parameter를 받아와서 해당 값을 이용하여 다음으로 실행될 job의 값을 다이나믹하게 넣어 만드는 샘플
    • meta_required에 원하는 값을 넣고 template에 env "NOMAD_META_메타명(key)"와 같이 넣어주면 된다.
job "24-paramete" {
+  datacenters = ["dc1"]
+  type = "batch"
+
+  parameterized {
+    payload       = "forbidden"
+    meta_required = ["room_num"]
+  }
+
+  group "run-main-job" {
+
+    task "run-main-job" {
+      driver = "raw_exec"
+
+      config {
+        command = "nomad"
+        # arguments
+        args = ["job", "run", "${NOMAD_TASK_DIR}/room.job" ]
+      }
+      template {
+        data = <<EOH
+
+#####################
+
+job "{{ env "NOMAD_META_room_num" }}" {
+    datacenters = ["dc1"]
+
+    group "jboss" {
+
+        network {
+            port "http" {
+                to = "8080"
+            }
+        }
+        service {
+            port = "http"
+            name = "{{ env "NOMAD_META_room_num" }}"
+            check {
+                type     = "tcp"
+                interval = "10s"
+                timeout  = "2s"
+            }
+        }
+        task "http" {
+            driver = "docker"
+            config {
+                image = "jboss/wildfly"
+                ports = ["http"]
+            }
+            resources {
+                cpu    = 500
+                memory = 282
+            }
+        }
+    }
+}
+
+EOH
+    destination = "local/room.job"
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/redis.html b/04-HashiCorp/07-Nomad/05-SampleJob/redis.html new file mode 100644 index 0000000000..db1a63549a --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/redis.html @@ -0,0 +1,185 @@ + + + + + + + + + + redis | docmoa + + + + + +
본문으로 건너뛰기

redis

1분 미만NomadSampleJob

redis

  • 추가내용: redis는 data dir, cluster info dir(클러스터 시) 이 두개의 dir가 필요하여 volume을 추가로 붙여줘야한다.
    • data dir을 변경이 번거로움(docker build를 다시 해야함) 그래서 클러스터 시에는 docker build, nomad volume으로 나눔과 같은 방법이 있음
    • cluster-enabled : 클러스터로 사용합니다. (cluster volume으로 빼둬야됨)
    • cluster-config-file : 노드별로 클러스터 노드 정보를 conf 파일에 저장합니다.
    • cluster-node-timeout : 노드간 통신이 되지 않아 timeout 되는 시간을 설정합니다.
job "redis-cluster" {
+
+  datacenters = ["dc1"]
+
+  group "redis" {
+
+    volume "redis-data" {
+      type      = "host"
+      source    = "redis-data"
+      read_only = false
+    }
+
+    volume "redis-cluster" {
+      type      = "host"
+      source    = "redis-cluster"
+      read_only = false
+    }
+
+    network {
+      
+      port "master" {
+        to     = 6379
+      }
+      port "slave" {
+        to     = 6380
+      }
+    }
+    service {
+      name = "master-redis"
+      tags = ["master-redis"]
+      port     = "master"
+      check {
+        port     = "master"
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+    service {
+      name = "slave-redis"
+      tags = ["slave-redis"]
+      port     = "slave"
+      check {
+        port     = "slave"
+        type     = "tcp"
+        interval = "10s"
+        timeout  = "2s"
+      }
+    }
+
+
+
+    task "redis-master" {
+      volume_mount {
+        volume      = "redis-data"
+        destination = "/data"
+      }
+      volume_mount {
+        volume      = "redis-cluster"
+        destination = "/master"
+      }
+      driver = "docker"
+      config {
+        network_mode = "host"
+        image = "redis:5.0.5-buster"
+        ports = ["master"]
+        command = "redis-server"
+        args = [
+          "${NOMAD_TASK_DIR}/redis.conf"
+        ]
+      }
+      template {
+        data = <<EOF
+port {{ env "NOMAD_PORT_master" }} 
+bind {{ env "NOMAD_IP_master" }} 
+#bind 127.0.0.1 ::1
+cluster-enabled yes
+cluster-config-file /master/nodes.conf
+cluster-node-timeout 3000
+appendonly yes
+
+EOF
+
+        destination   = "local/redis.conf"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+      resources {
+        cpu = 1000
+        memory = 1001
+      }
+    }
+    task "redis-slave" {
+
+      volume_mount {
+        volume      = "redis-data"
+        destination = "/data"
+      }
+      volume_mount {
+        volume      = "redis-cluster"
+        destination = "/slave"
+      }
+      env {
+        NODE_IP = "${NOMAD_IP_slave-redis}"
+      }
+      driver = "docker"
+      config {
+        network_mode = "host"
+        image = "redis:5.0.5-buster"
+        ports = ["slave"]
+        command = "redis-server"
+        args = [
+          "${NOMAD_TASK_DIR}/redis.conf"
+        ]
+      }
+      template {
+        data = <<EOF
+port {{ env "NOMAD_PORT_slave" }} 
+bind  {{ env "NOMAD_IP_slave" }} 
+#bind  127.0.0.1 ::1
+cluster-enabled yes
+cluster-config-file /slave/nodes.conf
+cluster-node-timeout 3000
+appendonly yes
+
+EOF
+
+        destination   = "local/redis.conf"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+      resources {
+        cpu = 1000
+        memory = 1001
+      }
+    }
+  }
+}
+
+
+
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/scouter.html b/04-HashiCorp/07-Nomad/05-SampleJob/scouter.html new file mode 100644 index 0000000000..d651593cdb --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/scouter.html @@ -0,0 +1,303 @@ + + + + + + + + + + Scouter | docmoa + + + + + +
본문으로 건너뛰기

Scouter

약 2 분NomadConsulScouterJob

Scouter

  • 공식 Github : https://github.com/scouter-project/scouteropen in new window
  • Scouter 다운로드
    • scouter collector와 host-agent 를 실행하는 job에서 버전정보를 기반으로 다운로드
    • host agent는 system 타입으로 모든 노드에서 실행되도록 구성
  • tomcat 다운로드
    • 다운로드 받지않고 호스트에 미리 설치해 놓는 방식이 더 가벼워보임 --> 아마도 Packer, Terraform의 조합이면 가능
    • 다운로드 받게 구성하면 컨테이너처럼 Nomad가 배포할 때마다 다운받아서 구성 가능
  • Template - server.xml
    • server.xml 기본 구성에서 port가 선언되는 자리를 java option에서 받을 수 있도록 변경
    • Template 구성도 가능하고 미리 구성한 xml을 다운로드 받게 하는것도 괜찮아 보임
  • Consul과 함께 구성된 경우 Nginx같은 LB 구성 Job 에서 backend를 동적으로 구성 가능
  • Nomad에 Scouter Collector와 Host Agent를 위한 별도 Namespace를 구성하는 것도 관리를 위해 좋아보임
    nomad namespace apply -description "scouter" scouter

Scouter - Collector Server

  • Collector의 경우 Client 에서 연결하기 위해 고정된 포트를 지정해야 하므로 static 포트로 지정
  • Collector의 경우 data를 영구적으로 보관하기 위해서는 Volume 할당하는 것을 권장
    • database
    • logs
variable "version" {
+  default = "2.15.0"
+  description = "scouter의 버전 기입 또는 배포시 입력 받기"
+}
+
+locals {
+  souter_release_url = "https://github.com/scouter-project/scouter/releases/download/v${var.version}/scouter-min-${var.version}.tar.gz"
+}
+
+job "scouter-collector" {
+  datacenters = ["dc1"]
+  // namespace = "scouter"
+
+  type = "service"
+
+  group "collector" {
+    count = 1
+
+    scaling {
+      enabled = false
+      min = 1
+      max = 1
+    }
+
+    task "collector" {
+      driver = "java"
+      resources {
+        network {
+          port "collector" {
+            to = 6100
+            static = 6100
+          }
+        }
+        cpu = 500
+        memory = 512
+      }
+      artifact {
+        source = local.souter_release_url
+        destination = "/local"
+      }
+      template {
+data = <<EOF
+# Agent Control and Service Port(Default : TCP 6100)
+net_tcp_listen_port={{ env "NOMAD_PORT_collector" }}
+
+# UDP Receive Port(Default : 6100)
+net_udp_listen_port={{ env "NOMAD_PORT_collector" }}
+
+# DB directory(Default : ./database)
+db_dir=./database
+
+# Log directory(Default : ./logs)
+log_dir=./logs
+EOF
+        destination = "local/scouter/server/conf/scouter.conf"
+      }
+      config {
+        class_path = "local/scouter/server/scouter-server-boot.jar"
+        class = "scouter.boot.Boot"
+        args = ["local/scouter/server/lib"]
+      }
+      service {
+        name = "scouter-collector"
+        tags = ["scouter"]
+
+        port = "collector"
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "collector"
+        }
+      }
+    }
+  }
+}
+
+
+

Scouter - Host Agent

  • 모든 Nomad Client 노드를 대상으로 구성하기 위해 system 타입으로 실행하나, 조건이 필요한 경우 Java가 있는 경우, 혹은 특정 노드에 대한 조건을 Constrain으로 구성할 수 있음
  • Collector Server의 정보를 Consul에서 동적으로 받아오도록 템플릿 구성
// nomad namespace apply -description "scouter" scouter
+
+variable "version" {
+  default = "2.15.0"
+}
+
+locals {
+  souter_release_url = "https://github.com/scouter-project/scouter/releases/download/v${var.version}/scouter-min-${var.version}.tar.gz"
+}
+
+job "scouter-host-agent" {
+  datacenters = ["dc1"]
+  // namespace = "scouter"
+
+  type = "system"
+
+  group "agent" {
+
+    task "agent" {
+      driver = "java"
+      resources {
+        cpu = 100
+        memory = 128
+      }
+      artifact {
+        source = local.souter_release_url
+        destination = "/local"
+      }
+      template {
+data = <<EOF
+obj_name=${node.unique.name}
+{{ range service "scouter-collector" }}
+net_collector_ip={{ .Address }}
+net_collector_udp_port={{ .Port }}
+net_collector_tcp_port={{ .Port }}
+{{ end }}
+#cpu_warning_pct=80
+#cpu_fatal_pct=85
+#cpu_check_period_ms=60000
+#cpu_fatal_history=3
+#cpu_alert_interval_ms=300000
+#disk_warning_pct=88
+#disk_fatal_pct=92
+EOF
+        destination = "local/scouter/agent.host/conf/scouter.conf"
+      }
+      config {
+        class_path = "local/scouter/agent.host/scouter.host.jar"
+        class = "scouter.boot.Boot"
+        args = ["local/lib"]
+      }
+    }
+  }
+}
+
+

Tomcat Sample

  • Tomcat 서버 구성 시 Tomcat과 Scouter 모두를 다운받게 구성
    • 둘 모두 다운받기 때문에 운영에서 구성하는 경우 필요한 파일들만 별도 압축하여 별도 관리하는 것을 권장
  • 구성에 따른 이름을 매번 다르게 구성하기 위해 변수 활용
    • -Dobj_name=Tomcat-${node.unique.name}-${NOMAD_PORT_http}
variable "tomcat_version" {
+  default = "10.0.14"
+}
+
+variable "scouter_version" {
+  default = "2.15.0"
+}
+
+locals {
+  tomcat_major_ver = split(".", var.tomcat_version)[0]
+  tomcat_download_url = "https://archive.apache.org/dist/tomcat/tomcat-${local.tomcat_major_ver}/v${var.tomcat_version}/bin/apache-tomcat-${var.tomcat_version}.tar.gz"
+  souter_release_url = "https://github.com/scouter-project/scouter/releases/download/v${var.scouter_version}/scouter-min-${var.scouter_version}.tar.gz"
+  war_download_url = "https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/sample.war"
+}
+
+job "tomcat-scouter" {
+  datacenters = ["dc1"]
+  // namespace = "scouter"
+
+  type = "service"
+
+  group "tomcat" {
+    count = 1
+
+    scaling {
+      enabled = true
+      min = 1
+      max = 3
+    }
+
+    task "tomcat" {
+      driver = "raw_exec"
+      resources {
+        network {
+          port "http" {}
+          port "stop" {}
+          port "jmx" {}
+        }
+        cpu = 500
+        memory = 512
+      }
+      env {
+        APP_VERSION = "0.1"
+        CATALINA_HOME = "${NOMAD_TASK_DIR}/apache-tomcat-${var.tomcat_version}"
+        CATALINA_OPTS = "-Dport.http=$NOMAD_PORT_http -Dport.stop=$NOMAD_PORT_stop -Ddefault.context=$NOMAD_TASK_DIR -Xms256m -Xmx512m -javaagent:local/scouter/agent.java/scouter.agent.jar -Dscouter.config=local/conf/scouter.conf -Dobj_name=Tomcat-${node.unique.name}-${NOMAD_PORT_http}"
+        JAVA_HOME = "/usr/lib/jvm/java-11-openjdk-amd64"
+      }
+      artifact {
+        source = local.tomcat_download_url
+        destination = "/local"
+      }
+      artifact {
+        source = local.souter_release_url
+        destination = "/local"
+      }
+      artifact {
+        source = local.war_download_url
+        destination = "/local/webapps"
+      }
+      template {
+data = <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<Server port="${port.stop}" shutdown="SHUTDOWN">
+    <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
+    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
+    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
+    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
+    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
+    <GlobalNamingResources>
+        <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml"/>
+    </GlobalNamingResources>
+    <Service name="Catalina">
+        <Connector port="${port.http}" protocol="HTTP/1.1" connectionTimeout="20000"/>
+        <Engine name="Catalina" defaultHost="localhost">
+            <Realm className="org.apache.catalina.realm.LockOutRealm">
+                <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
+            </Realm>
+            <Host name="localhost" appBase="${default.context}/webapps/" unpackWARs="true" autoDeploy="true">
+                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b"/>
+            </Host>
+        </Engine>
+    </Service>
+</Server>
+EOF
+        destination = "local/conf/server.xml"
+      }
+      template {
+data = <<EOF
+{{ range service "scouter-collector" }}
+net_collector_ip={{ .Address }}
+net_collector_udp_port={{ .Port }}
+net_collector_tcp_port={{ .Port }}
+{{ end }}
+#hook_method_patterns=sample.mybiz.*Biz.*,sample.service.*Service.*
+#trace_http_client_ip_header_key=X-Forwarded-For
+#profile_spring_controller_method_parameter_enabled=false
+#hook_exception_class_patterns=my.exception.TypedException
+#profile_fullstack_hooked_exception_enabled=true
+#hook_exception_handler_method_patterns=my.AbstractAPIController.fallbackHandler,my.ApiExceptionLoggingFilter.handleNotFoundErrorResponse
+#hook_exception_hanlder_exclude_class_patterns=exception.BizException
+EOF
+        destination = "local/conf/scouter.conf"
+      }
+      config {
+        command = "${CATALINA_HOME}/bin/catalina.sh"
+        args = ["run", "-config", "$NOMAD_TASK_DIR/conf/server.xml"]
+      }
+      service {
+        name = "tomcat-scouter"
+        tags = ["tomcat"]
+
+        port = "http"
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "http"
+        }
+      }
+      service {
+        name = "tomcat-scouter"
+        tags = ["jmx"]
+        port = "jmx"
+      }
+    }
+  }
+}
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/service-mesh-test.html b/04-HashiCorp/07-Nomad/05-SampleJob/service-mesh-test.html new file mode 100644 index 0000000000..00157d0e4c --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/service-mesh-test.html @@ -0,0 +1,113 @@ + + + + + + + + + + Service Mesh Test | docmoa + + + + + +
본문으로 건너뛰기

Service Mesh Test

1분 미만NomadConsulSampleJobService Mesh

Service Mesh Test

HashiCorp 공식 Service Mesh Test App

https://learn.hashicorp.com/tutorials/nomad/consul-service-meshopen in new window

job "countdash" {
+  region      = "global"
+  datacenters = ["dc1"]
+  # namespace   = "mesh"
+
+  group "api" {
+    network {
+      mode = "bridge"
+      port "api" {
+        to = 9001 # static 설정이 없으므로 컨테이너의 앱 9001과 노출되는 임의의 포트와 맵핑
+      }
+    }
+
+    service {
+      name = "count-api"
+      port = "api" # 임의의 포트 할당을 network port id로 지정
+
+      connect {
+        sidecar_service {}
+      }
+    }
+
+    task "web" {
+      driver = "docker"
+      config {
+        image = "hashicorpnomad/counter-api:v1"
+        ports = ["api"]
+      }
+    }
+  }
+
+  group "dashboard" {
+    network {
+      mode = "bridge"
+      port "http" {
+        static = 9002 # 컨테이너 앱 9002와 외부노출되는 사용자 지정 static port를 맵핑
+        to     = 9002
+      }
+    }
+
+    service {
+      name = "count-dashboard"
+      port = "http" # 할당된 포트를 network port id로 지정
+
+      connect {
+        sidecar_service {
+          proxy {
+            upstreams {
+              destination_name = "count-api"
+              local_bind_port  = 8080 # backend인 count-api의 실제 port와는 별개로 frontend가 호출할 port 지정
+            }
+          }
+        }
+      }
+    }
+
+    task "dashboard" {
+      driver = "docker"
+      env {
+        COUNTING_SERVICE_URL = "http://${NOMAD_UPSTREAM_ADDR_count_api}"
+      }
+      config {
+        image = "hashicorpnomad/counter-dashboard:v1"
+      }
+    }
+
+    scaling {
+      enabled = true
+      min = 1
+      max = 10
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/sidecar-tomcat.html b/04-HashiCorp/07-Nomad/05-SampleJob/sidecar-tomcat.html new file mode 100644 index 0000000000..b070c94580 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/sidecar-tomcat.html @@ -0,0 +1,98 @@ + + + + + + + + + + tomcat-sidecar | docmoa + + + + + +
본문으로 건너뛰기

tomcat-sidecar

1분 미만NomadSampleJobsidecartomcat

tomcat-sidecar

job "01_tomcat-sidecar" {
+  datacenters = ["dc1"]
+
+  #ingress용도의 group
+  group "ingress-tomcat" {
+    network {
+      mode = "bridge"
+      port "inbound" {
+        static = 8080
+        to     = 8080
+      }
+    }
+
+    service {
+      name = "tomcat-ingress"
+      port = "8080"
+
+      #여기서부터 sidecar ingress
+      connect {
+        gateway {
+          ingress {
+            listener {
+              port     = 8080
+              protocol = "tcp"
+              service {
+                #아래 tomcat group에 service를 호출함
+                name = "backend"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  group "tomcat" {
+    network {
+      mode = "bridge"
+    }
+
+    service {
+      name = "backend"
+      port = "8080"
+      connect {
+        sidecar_service {}
+      }
+    }
+
+    task "tomcat" {
+      driver = "docker"
+      config {
+        image = "tomcat"
+      }
+    }
+  }
+}
+
+
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/tomcat.html b/04-HashiCorp/07-Nomad/05-SampleJob/tomcat.html new file mode 100644 index 0000000000..73097de69e --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/tomcat.html @@ -0,0 +1,286 @@ + + + + + + + + + + Tomcat | docmoa + + + + + +
본문으로 건너뛰기

Tomcat

약 2 분NomadConsulSampleJob

Tomcat

  • tomcat 다운로드
    • 다운로드 받지않고 호스트에 미리 설치해 놓는 방식이 더 가벼워보임 --> 아마도 Packer, Terraform의 조합이면 가능
    • 다운로드 받게 구성하면 컨테이너처럼 Nomad가 배포할 때마다 다운받아서 구성 가능
  • Template - server.xml
    • server.xml 기본 구성에서 port가 선언되는 자리를 java option에서 받을 수 있도록 변경
    • Template 구성도 가능하고 미리 구성한 xml을 다운로드 받게 하는것도 괜찮아 보임
  • Consul과 함께 구성된 경우 Nginx같은 LB 구성 Job 에서 backend를 동적으로 구성 가능
Linux/MacOS
variables {
+  tomcat_download_url = "https://archive.apache.org/dist/tomcat/tomcat-10/v10.0.10/bin/apache-tomcat-10.0.10.tar.gz"
+  // https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/
+  war_download_url = "https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/sample.war"
+}
+
+job "tomcat-10" {
+  datacenters = ["dc1"]
+  # namespace = "legacy"
+
+  type = "service"
+
+  group "tomcat" {
+    count = 1
+
+    scaling {
+      enabled = true
+      min = 1
+      max = 3
+    }
+
+    task "tomcat" {
+      driver = "raw_exec"
+      resources {
+        network {
+          port "http" {}
+          port "stop" {}
+        }
+        cpu = 500
+        memory = 512
+      }
+      env {
+        APP_VERSION = "0.1"
+        CATALINA_HOME = "${NOMAD_TASK_DIR}/apache-tomcat-10.0.10"
+        CATALINA_OPTS = "-Dport.http=$NOMAD_PORT_http -Dport.stop=$NOMAD_PORT_stop -Ddefault.context=$NOMAD_TASK_DIR -Xms256m -Xmx512m"
+        JAVA_HOME = "/usr/lib/jvm/java-11-openjdk-amd64"
+      }
+      template {
+data = <<EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<Server port="${port.stop}" shutdown="SHUTDOWN">
+    <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
+    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
+    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
+    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
+    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
+    <GlobalNamingResources>
+        <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml"/>
+    </GlobalNamingResources>
+    <Service name="Catalina">
+        <Connector port="${port.http}" protocol="HTTP/1.1" connectionTimeout="20000"/>
+        <Engine name="Catalina" defaultHost="localhost">
+            <Realm className="org.apache.catalina.realm.LockOutRealm">
+                <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
+            </Realm>
+            <Host name="localhost" appBase="${default.context}/webapps/" unpackWARs="true" autoDeploy="true">
+                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b"/>
+            </Host>
+        </Engine>
+    </Service>
+</Server>
+EOF
+        destination = "local/conf/server.xml"
+      }
+      artifact {
+        source = var.tomcat_download_url
+        destination = "/local"
+      }
+      artifact {
+        source = var.war_download_url
+        destination = "/local/webapps"
+      }
+      config {
+        command = "${CATALINA_HOME}/bin/catalina.sh"
+        args = ["run", "-config", "$NOMAD_TASK_DIR/conf/server.xml"]
+      }
+      service {
+        name = "legacy-tomcat"
+        tags = ["tomcat"]
+        provider = "nomad"
+
+        port = "http"
+
+        check {
+          type  = "tcp"
+          interval = "10s"
+          timeout  = "2s"
+          port  = "http"
+        }
+      }
+    }
+  }
+}
+
+

"-Xrs" Java Option on Windows

Windows에서는 -Xrs 옵션을 자바 옵션에 추가하지 않으면, 종료시 Thread Dump만 발생하고 프로세스가 종료되지 않아 Nomad Job이 갱신되거나 새로운 배포를 수행하여도 기존 프로세스 종료가 안되어 신규 task가 실행되지 않는 현상이 있다.

nginx

Consul과 함께 구성된 경우 nginx에 동적으로 backend 구성

job "nginx" {
+  datacenters = ["dc1"]
+  # namespace = "legacy"
+
+  group "nginx" {
+    count = 1
+
+    network {
+      port "http" {
+        static = 28080
+      }
+    }
+
+    service {
+      name = "nginx"
+      port = "http"
+    }
+
+    task "nginx" {
+      driver = "docker"
+
+      config {
+        image = "nginx"
+        ports = ["http"]
+        volumes = [
+          "local:/etc/nginx/conf.d",
+        ]
+      }
+
+      template {
+        data = <<EOF
+upstream backend {
+{{ range service "legacy-tomcat" }}
+  server {{ .Address }}:{{ .Port }}; # Tomcat
+{{ else }}server 127.0.0.1:65535; # force a 502
+{{ end }}
+}
+
+server {
+   listen {{ env "NOMAD_PORT_http" }};
+
+   location /sample {
+      proxy_pass http://backend;
+   }
+
+   location /status {
+       stub_status on;
+   }
+}
+EOF
+
+        destination   = "local/load-balancer.conf"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/update.html b/04-HashiCorp/07-Nomad/05-SampleJob/update.html new file mode 100644 index 0000000000..3f1e713098 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/update.html @@ -0,0 +1,80 @@ + + + + + + + + + + update | docmoa + + + + + +
본문으로 건너뛰기

update

1분 미만NomadSampleJob

update

nomad의 배포 방법 중 canary와 rolling update 관련된 내용입니다.

...
+  #canary update - 새로운 버전의 task를 canary 변수의 수만큼 기동시키고 상황에 맞게 확인 후 배포
+  group "canary" {
+    count = 5
+
+    update {
+      max_parallel     = 1
+      canary           = 1
+      min_healthy_time = "30s"
+      healthy_deadline = "10m"
+      #배포 실패시 자동으로 전 버전으로 돌아감(배포 중이던 task 제거됨)
+      auto_revert      = true
+      #task가 기동되어도 자동으로 다음 버전으로 넘어가지 않음(배포 전 버전 task 제거되지않음)
+      auto_promote     = false
+    }
+  }
+  #rolling update - 기동 중이던 task를 하나씩(max_parallel만큼) 신규 task로 변환하면서 배포
+  group "api-server" {
+    count = 6
+
+    update {
+      max_parallel     = 2
+      min_healthy_time = "30s"
+      healthy_deadline = "10m"
+    }
+  }
+  #배포 시 service에 canary로 배포된 task에만 붙일 수 있는 tag 설정
+  service {
+    port = "http"
+    name = "canary-deployments"
+
+    tags = [
+      "live"
+    ]
+
+    canary_tags = [
+      "canary"
+    ]
+}
+...
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/withConsulKV.html b/04-HashiCorp/07-Nomad/05-SampleJob/withConsulKV.html new file mode 100644 index 0000000000..794a3eedbd --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/withConsulKV.html @@ -0,0 +1,111 @@ + + + + + + + + + + Consul KV Sample | docmoa + + + + + +
본문으로 건너뛰기

Consul KV Sample

1분 미만NomadSampleJob

Consul KV Sample

Consul의 KV에 값을 저장하고 비교하여 task batch를 수행하는 예제

  • curl 을 사용하는 경우
    curl -X GET http://127.0.0.1:8500/v1/kv/docmoa/commit_date | jq -r '.[0].Value | @base64d'
    +
  • template의 key를 사용하는 경우
    {{ key "docmoa/commit_date" }}
    +
job "gs-mac-docmoa-build" {
+  datacenters = ["home"]
+  type        = "batch"
+
+  periodic {
+    cron             = "0 */5 * * * * *"
+    prohibit_overlap = true
+    time_zone        = "Asia/Seoul"
+  }
+
+  constraint {
+    attribute = "${attr.unique.consul.name}"
+    value     = "my-macbook-air"
+  }
+
+  group "docmoa-build" {
+    count = 1
+
+    task "git-pull" {
+      driver = "raw_exec"
+      template {
+        data = <<EOH
+#!/bin/sh
+cd /Users/gslee/workspaces/docs
+git pull origin main
+        EOH
+
+        destination = "git.sh"
+      }
+      config {
+        command = "git.sh"
+      }
+      lifecycle {
+        hook    = "prestart"
+        sidecar = false
+      }
+    }
+    task "build" {
+      driver = "raw_exec"
+      template {
+        data = <<EOH
+#!/bin/sh
+LAST_COMMIT_DATE=$(curl https://api.github.com/repos/docmoa/docs/branches/main | jq -r '.commit.commit.committer.date')
+#STORE_COMMIT_DATE=$(curl -X GET http://127.0.0.1:8500/v1/kv/docmoa/commit_date | jq -r '.[0].Value | @base64d')
+STORE_COMMIT_DATE={{ key "docmoa/commit_date" }}
+echo "LAST_COMMIT_DATE = $LAST_COMMIT_DATE"
+echo "STORE_COMMIT_DATE = $STORE_COMMIT_DATE"
+if [ $LAST_COMMIT_DATE != $STORE_COMMIT_DATE ]; then
+  echo "do deploy"
+  # something todo...
+  # update new value
+  curl -X PUT --data $LAST_COMMIT_DATE http://127.0.0.1:8500/v1/kv/docmoa/commit_date
+else
+  echo "no change"
+fi
+        EOH
+
+        destination = "build.sh"
+      }
+      config {
+        command = "build.sh"
+      }
+      resources {
+        cpu    = 1000
+        memory = 256
+      }
+    }
+  }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/05-SampleJob/withVaultKV.html b/04-HashiCorp/07-Nomad/05-SampleJob/withVaultKV.html new file mode 100644 index 0000000000..24c952b2c3 --- /dev/null +++ b/04-HashiCorp/07-Nomad/05-SampleJob/withVaultKV.html @@ -0,0 +1,213 @@ + + + + + + + + + + vaultKV | docmoa + + + + + +
본문으로 건너뛰기

vaultKV

1분 미만NomadSampleJob

vaultKV

nomad job에서 vault의 secret에서 kv사용하기

nomad hcl 설정

job "logs" {
+    datacenters = ["dc1"]
+
+    constraint {
+        attribute = "${attr.kernel.name}"
+        value = "linux"
+    }
+
+    update {
+        stagger = "10s"
+        max_parallel = 1
+    }
+
+    group "elk" {
+        count = 1
+
+        restart {
+            attempts = 2
+            interval = "1m"
+            delay = "15s"
+            mode = "delay"
+        }
+        network {
+          port "elk" {
+            to     = 9200
+            static = 9200
+          }
+          port "kibana" {
+            to     = 5601
+          }
+          port "logstash" {
+            to     = 5000
+          }
+        }
+
+        task "elasticsearch" {
+            driver = "docker"
+
+            vault {
+              policies  = ["admin"]
+              change_mode   = "signal"
+              change_signal = "SIGINT"
+            }
+
+            config {
+                image = "elasticsearch:7.16.2"
+                ports = ["elk"]
+                volumes = [
+          "local/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml",
+        ]
+      }
+      template {
+        data = <<EOF
+cluster.name: "my-cluster"
+network.host: 0.0.0.0
+discovery.type: single-node
+discovery.seed_hosts: ["127.0.0.1"]
+xpack.security.enabled: true
+xpack.license.self_generated.type: trial
+xpack.monitoring.collection.enabled: true
+EOF
+        destination   = "local/elasticsearch.yml"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+      template {
+        data = <<EOH
+ELASTIC_PASSWORD="{{with secret "secret/elastic"}}{{.Data.passwd}}{{end}}"
+EOH
+
+  destination = "secrets/file.env"
+  env         = true
+}
+
+            service {
+                name = "${TASKGROUP}-elasticsearch"
+                port = "elk"
+            }
+
+            resources {
+                cpu = 500
+                memory = 1048
+            }
+        }
+
+        task "kibana" {
+            driver = "docker"
+
+            vault {
+              policies  = ["admin"]
+              change_mode   = "signal"
+              change_signal = "SIGINT"
+            }
+
+            config {
+                image = "kibana:7.16.2"
+                ports = ["kibana"]
+                volumes = [
+          "local/kibana.yml:/usr/share/kibana/config/kibana.yml"
+        ]
+      }
+      template {
+        data = <<EOF
+#
+# ** THIS IS AN AUTO-GENERATED FILE **
+#
+
+# Default Kibana configuration for docker target
+server.host: "0.0.0.0"
+server.shutdownTimeout: "5s"
+elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
+elasticsearch.username: elastic
+{{ with secret "secret/elastic" }}
+elasticsearch.password: {{.Data.passwd}}
+{{ end }}
+
+EOF
+
+        destination   = "local/kibana.yml"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+            service {
+                name = "${TASKGROUP}-kibana"
+                port = "kibana"
+                check {
+                    type = "http"
+                    path = "/"
+                    interval = "10s"
+                    timeout = "2s"
+                }
+            }
+
+            resources {
+                cpu = 500
+                memory = 1200
+            }
+        }
+
+        task "logstash" {
+            driver = "docker"
+
+            config {
+                image = "logstash:7.16.2"
+                ports = ["logstash"]
+                volumes = [
+          "local/logstash.yml:/usr/share/logstash/config/logstash.yml"
+        ]
+      }
+      template {
+        data = <<EOF
+http.host: "0.0.0.0"
+xpack.monitoring.elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
+EOF
+
+        destination   = "local/logstash.yml"
+        change_mode   = "signal"
+        change_signal = "SIGHUP"
+      }
+
+            service {
+                name = "${TASKGROUP}-logstash"
+                port = "logstash"
+            }
+
+            resources {
+                cpu = 200
+                memory = 1024
+            }
+        }
+    }
+}
+
+ + + diff --git a/04-HashiCorp/07-Nomad/index.html b/04-HashiCorp/07-Nomad/index.html new file mode 100644 index 0000000000..89552b4c33 --- /dev/null +++ b/04-HashiCorp/07-Nomad/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 07 Nomad | docmoa + + + + + +
본문으로 건너뛰기

07 Nomad

1분 미만

+ + + diff --git a/04-HashiCorp/08-Updates/97-2024/2024-01.html b/04-HashiCorp/08-Updates/97-2024/2024-01.html new file mode 100644 index 0000000000..16591e5cdf --- /dev/null +++ b/04-HashiCorp/08-Updates/97-2024/2024-01.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2024년 1월 | docmoa + + + + + +
본문으로 건너뛰기

2024년 1월

약 1 분HashicorpUpdateJan

2024년 1월

Product 소개

  • HashiCorp 2023 year in review: Community
    • Hashicorp Blogopen in new window
    • 작년 2023년 한 해동안 있었던 Hashicorp 관련 이야기: 개최된 컨퍼런스 및 이벤트부터 새로 출시된 솔루션 별 트레이닝 및 자격증 관련, 그리고 창업자 Mitchell Hashimoto 의 퇴사 소식 등을 한 번에 확인하실 수 있습니다.

Product Update

  • Terraform

    • CLI
    • Enterprise Releaseopen in new window
      • 12월 Releaseopen in new window 출시 (v202312-1 (745))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202312-1)
      • Priorty Variable Setopen in new window 를 통해 변수에 지정되는 값에 대한 우선순위 설정 지원
      • terraform plan -out <FILE> 또는 API 호출 시 save-plan 을 활용하여 plan 정보를 저장 후 추후 apply 시 활용하도록 지원
      • Workspace 의 상태 정보를 확인하고 inactive 인 Workspace 에 대해 자동 destroy 수행 지원
      • Known Issue: working directory 설정 관련하여 Operation failed: failed packing filesystem: illegal slug error: invalid symlink 에러 발생하는 부분에 대해 2024 1월 release 에서 patch 예정
    • Provider
      • AWS v5.31.0 주요 수정사항open in new window
        • 신규 resource 추가
          • aws_finspace_*
          • aws_ssoadmin_*
          • aws_polly_voices
        • 기능 개선
          • aws_lb 에 대해 connection_logs, dns_record_client_routing_policy 설정 추가
          • aws_lb 에 대해 자원 재생성 과정 없이 subnet_mapping 또는 subnets 변경 지원
          • aws_db_instance 에 대해 IBM Db2 지원
        • Bug Fix
          • aws_s3 의 endpoint 관련 no such host 오류 개선 위해 regional endpint 기본값을 us-east-1 로 설정
          • aws_cloud_watch_log_group 관련 invalid new value for .skip_destroy: was cty.False, but now null 오류 개선
          • aws_lb 관련 security_groups 에 대한 update 관련 오류 개선
      • Azure v3.85.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_locations
          • azurerm_iotcentral_organization
        • 기능 개선
          • azurerm_dns_txt_record 관련 record.value 에 대해 4096 자 허용
          • azurerm_linux_web_app 및 azurerm_linux_web_app_slot 관련 client_secret_setting_name 과 client_secret_certificate_thumbprint 에 대해 선택사항으로 변경하고 app_settings 설정에 대한 오류 개선
          • azurerm_windows_web_app 및 azurerm_windows_web_app_slot 관련 client_secret_setting_name 과 client_secret_certificate_thumbprint 에 대해 선택사항으로 변경하고 app_settings 설정에 대한 오류 개선
        • Bug Fix
          • China region 사용 시 Azure Storage 에 대한 인증 오류 개선
          • azurerm_virtual_machine 에 대해 additional_capabilities 검증 시 발생하는 패닉 오류 개선
          • azurerm_storage_share_file 에 대해 source 정보 공란일 시 발생하는 패닉 오류 개선
      • GCP v5.10.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_compute_region_disk
          • google_vmwareengine_*
          • google_workbench_instance_*
        • 기능 개선
          • google_compute_network 에 대해 numeric_id 설정 추가
          • google_compute_per_instance_config, google_compute_region_per_instance_config 에 대해 remove_instance_on_destroy 설정 추가
          • google_container_node_pool 에 대해 GKE tier 1 networking 지원되도록 network_performance_config 설정 추가
          • google_container_node_pool 에 대해 machine_type, disk_type, disk_size_gb 바로 update 가능하도록 개선
        • Bug Fix
          • google_logging_project_sink 에 대해 unique_writer_identity 설정 변경 시 발생하는 오류 개선
  • Vault

  • Consul

  • Nomad

    • 1.7.2
      • 상세 Release Noteopen in new window
      • Reschedule on Lost: Node Down 등으로 중지된 job 에 대해 nomad job stop 등의 수동 개입이 필요했던 부분에 대해 자동으로 reschedule 방지 되도록 지원
      • UI 에서 Task 에 대한 예제 template 추가
      • 잘못된 CPU 정보 기반 CPU Topology 작성 시 발생하는 패닉 오류 개선
+ + + diff --git a/04-HashiCorp/08-Updates/97-2024/2024-02.html b/04-HashiCorp/08-Updates/97-2024/2024-02.html new file mode 100644 index 0000000000..76ca3ef304 --- /dev/null +++ b/04-HashiCorp/08-Updates/97-2024/2024-02.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2024년 2월 | docmoa + + + + + +
본문으로 건너뛰기

2024년 2월

1분 미만HashicorpUpdateFeb

2024년 2월

Product 소개

  • HCP Vault Radar begins limited beta
    • Hashicorp Blogopen in new window
    • 작년 2023년 10월 Hashiconf 에서 공개된 HCP Vault Radar 가 Alpha 를 거쳐 Beta 가 출시되었습니다. Beta 에서는 RBAC/ABAC 을 지원하며 스캔할 수 있는 새로운 데이터 소스도 선보입니다

Product Update

  • Terraform
    • CLI
    • Enterprise Releaseopen in new window
      • 1월 Releaseopen in new window 출시 (v202401-1 (751))
      • 2023년 9월 (v202309-1) 에 출시된 FDO 설치방식과 기존 Replicated 설치방식을 선택할 수 있게 하는 consolidated_services_enabled 설정 지원 종료
      • Org 내 연결된 VCS 에 대한 Status Check 개선
      • Private Registry 관련 branch-based publishing workflowopen in new windowautomatically run tests for modulesopen in new window 기능 지원
      • Plan and Apply 수행 시 추가 옵션을 제공하여 선택한 자원에 대해 교체 되도록 지원
      • Workspace 설정에 Auto-apply run triggers 를 도입하여 연결된 다른 Workspace 에 변경 사항 발생 시 자동 apply 되도록 설정 지원
    • Provider
      • AWS v5.35.0 주요 수정사항open in new window
        • 신규 resource 추가
          • aws_lexv2models_*
          • aws_bedrock_*
        • 기능 개선
          • aws_redshift_cluster 에 대해 multi_az 설정 추가
          • aws_wafv2_rule_group 및 aws_wafv2_web_acl 에 header_order 와 field_to_match 설정 추가
        • Bug Fix
          • aws_networkmanager_core_network_policy_document 에 대해 core_network_configuration.edge_locations 에 대한 최대 설정 제한 제거
          • aws_cognito_user_group 에 대해 user group 이름에 / 가 포함되는 것 허용
      • Azure v3.90.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_nginx_configuration
          • azurerm_virtual_desktop_workspace
          • azurerm_kubernetes_fleet_update_strategy
        • 기능 개선
          • Resource Provider 추가: Microsoft.AppConfiguration, Microsoft.DataFactory, Microsoft.SignalRService
        • Bug Fix
          • azurerm_app_service_managed_certificate 에 대해 app_service_plan_id 가 대소문자 구분없이 파싱되어 발생하는 오류 개선
          • azurerm_cognitive_deployment 에 대해 version 설정 추가
          • azurerm_mssql_managed_instance_failover_group 에 대해 다른 subscription 에서 failover group 생성시 발생하는 이슈 해결
      • GCP v5.15.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_compute_region_network_endpoint
          • google_netapp_volume_snapshot
          • google_compute_machine_types
        • 기능 개선
          • google_compute_region_network_endpoint_group 에 대해 INTERNET_IP_PORT 와 INTERNET_FQDN_PORT 설정 추가
          • google_compute_region_instance_group_manager 와google_compute_instance_group_manager 에 대해 creation_timestamp 설정 추가
          • google_compute_disk 에 대해 disk_id 설정 추가
        • Bug Fix
          • google_vmwareengine_private_cloud 에 대해 provider upgrade 시 발생하는 재생성 오류 개선
  • Vault
  • Consul
    • 1.17.2
      • 상세 Release Noteopen in new window

      • Known Issue: 1.17.2 및 1.16.5 에서 Terminating Gatteway 가 TLS SAN 검증을 엄격하게 수행함으로 인해 Service Mesh 외부 Upstream 서비스 연결 방지

      • sameness-group 기반 failover 수행 시 해당 group이 속해있는 partition 이 아닌 기본 partition 으로 질의 하는 오류 개선

  • Nomad
    • 1.7.3
      • 상세 Release Noteopen in new window
      • exec driver 에 대해 OOM 감지 관련 지원
      • Consul 에 대해 admin partition 지원
      • 1.5 및 1.6 client 에서 발생하는 template 기반 Variable 및 Service 사용 오류 개선
      • UI 개선
+ + + diff --git a/04-HashiCorp/08-Updates/97-2024/index.html b/04-HashiCorp/08-Updates/97-2024/index.html new file mode 100644 index 0000000000..8519ce3b20 --- /dev/null +++ b/04-HashiCorp/08-Updates/97-2024/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 97 2024 | docmoa + + + + + +
본문으로 건너뛰기

97 2024

1분 미만

+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-01.html b/04-HashiCorp/08-Updates/98-2023/2023-01.html new file mode 100644 index 0000000000..b134ebaecd --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-01.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 1월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 1월

약 1 분HashicorpUpdateJan

2023년 1월

Product 소개

  • Dynamic Secrets for Waypoint with Vault

    • Hashicorp Blogopen in new window
    • Application 에 대한 Build, Deployment 및 Monitoring 을 간소화하고 쉽게 접근할 수 있도록 개발자를 지원하는 Waypoint 에서도 이제 Vault 와 연동하여 Hashicorp Vault config sourcer plugin 을 통해 Application Config 에 포함되는 보안 정보를 관리할 수 있습니다.

Product Update

  • Terraform
    • CLI
    • Enterprise Releaseopen in new window
      • 12월 Releaseopen in new window 출시 (v202212-2)
      • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)
      • 2023년 2월 Release (v202302-1) 을 기점으로 구버전 OS 및 Postrgres DB 에 대한 지원 종료
        • 대상 OS

          • Debian 8, 9

          • Ubuntu 14.04, 16.04

          • Amazon Linux 2014.03, 2014.09, 2015.03, 2015.09, 2016.03, 2016.09, 2017.03, 2017.09, 2018.03

        • 대상 Postgres

          • Postgres 11
          • TFE 에 대해 External PostgresSQL 사용하는 경우 최소 버전 12 이상으로 Upgrade 필요 (Release 659 부터 적용)
      • CA Bundle 관련 이슈로 인해 발생하는 HTTPS endpoint 접속 관련 Sentinel 정책 오류 수정
      • sentinel, cost-estimation, plan-exporter 등을 처리하는 tfe-nomad container 에 대해 성능 및 안정성 제고를 위해 tfe-task-worker 로 대체.
      • Pre-plan 및 Pre-Apply 를 위한 Run Taskopen in new window 지원
      • List Workspaces API endpointopen in new window 사용 시 wildcard 매칭 지원
      • terraform apply 진행 도중 취소 시 해당 resource 의 상태 정보에 대해 'unknown' 으로 출력하도록 개선
    • Provider
      • AWS v4.48.0 주요 수정사항open in new window
        • aws_eks_node_group 에 대해 WINDOWS_CORE_2019_x86_64, WINDOWS_CORE_2022_x86_64 등 Windows OS 기반 Node 사용 지원
        • aws_networkfirewall_rule_group 에 대해 reference_sets 지원
        • aws_networkmanager_vpc_attachment 에 대해 options.appliance_mode_support 지원
      • Azure v3.37.0 주요 수정사항open in new window
        • 신규 resource 추가: azurerm_coginitive_deployment, azurerm_billing_account_cost_management_export, azurerm_key_vault_certificate_contacts, azurerm_lab_service_plan, azurerm_resource_deployment_script, azurerm_spring_cloud_customized_accelerator 등
        • azurerm_netapp_volume 에 대해 zone 설정 추가
        • azurerm_virtual_network_gateway_connection 에 대해 다른 resource group 에서 생성 안되는 오류 개선
      • GCP v4.47.0 주요 수정사항open in new window
        • 신규 resource: google_alloydb_backup, google_filestore_backup
        • compute 에 대해 ipv6_access_type 설정 지원
        • container 에 대해 autoscaling 에 대한 default 설정 지원 추가
        • container 에 대해 gke gateway api controller 지원을 위한 gateway_api_config 설정 추가
        • google_sql_database 에 대해 삭제 정책이 기본 "abandon" 처리 되어 있음으로 인해 발생 가능한 오류 개선
  • Vault
    • 1.12.2
      • 상세 CHANGELOGopen in new window
      • PKI Secret Engine: 기본 issuer 에 대한 변경을 위해 default_follows_latest_issuer 설정 추가
      • Raft Storage: retry_join_as_non_voter 설정 추가
      • Okta Auth Method 관련 AuthRenew 로 인한 Panic 오류 수정
      • ciphertext 가 4 byte 이하인 경우 발생 가능한 deadlock 오류 수정
      • Performance Standby Node 에 대한 start up race condition 수정
  • Consul
    • 1.14.3
      • 상세 CHANGELOGopen in new window

      • consul connect: vault connect CA test 에 대해 제한된 권한의 token 사용하도록 수정

      • consul connect 관련 Mesh Gateway 의 Failover peering 에 대한 오류 개선

      • consul connect 관련 Vault 1.11+ 를 CA Provider 로 사용 시 발생하는 Intermediate CA 관련 오류 개선

      • Cluster Peering 기반 Service Failover 포함 Mesh Traffic Management 기능 개선

      • Windows 에 대한 Service Mesh 지원

      • AWS Lambda 에 대한 Service Mesh 지원

  • Nomad
    • 1.4.3
      • 상세 CHANGELOGopen in new window
      • 동일 Job 에 대해 여러 evaluation 이 pending 되어 있는 경우, 가장 최근 성공한 evaluation 에 대해서만 반영
      • File Upload, Consul Upstream 설정 등 UI 개선
      • Consul 연동 시 gRPC fingerprint 관련 발생 오류 개선 (Consul 1.14 연동부터 적용)
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-02.html b/04-HashiCorp/08-Updates/98-2023/2023-02.html new file mode 100644 index 0000000000..2aed458e08 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-02.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 2월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 2월

1분 미만HashicorpUpdateFeb

2023년 2월

Product 소개

  • Terraform Cloud Adds ‘Projects’ to Organize Workspaces at Scale

    • Hashicorp Blogopen in new window
    • 기존의 Terraform Cloud 에서는 연관되는 Workspace 간 그룹화가 불가능하고 Organization 및 Workspace 단위로만 권한 할당이 가능함으로 인해 사용자들은 자원 활용에 대한 제약사항을 고려하여 Organization 및 Workspace 를 분할하는 불편함을 감수해야 했습니다. 'Projects' 는 Workspace 를 그룹화하고 Project 단위로 권한 설정을 지원함으로써 앞서 소개한 제약사항을 해소하도록 지원합니다.

Product Update

  • Terraform
    • CLI
      • 1.3.7 Releaseopen in new window
        • Module 사용 시 Version Constraint 에 대한 Parsing 관련 오류 개선
        • ignore_changes 설정된 resource 가 있을 경우 panic 이 발생하는 오류 개선
    • Enterprise Releaseopen in new window
      • 1월 Releaseopen in new window 출시 (v202301-2)
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)
      • 2023년 2월 Release (v202302-1) 을 기점으로 구버전 OS 및 Postrgres DB 에 대한 지원 종료
        • 대상 OS

          • Debian 8, 9

          • Ubuntu 14.04, 16.04

          • Amazon Linux 2014.03, 2014.09, 2015.03, 2015.09, 2016.03, 2016.09, 2017.03, 2017.09, 2018.03

        • 대상 Postgres

          • Postgres 11
          • TFE 에 대해 External PostgresSQL 사용하는 경우 최소 버전 12 이상으로 Upgrade 필요 (Release 659 부터 적용)
      • Log Level (debug) 로 설정 시 tfe-atlas, tfe-atlas-ui, tfe-sidekiq 등 일부 구성요소로 인한 대량 log 수집되는 이슈 해결
      • Policy Manager 에 대한 Resource 접근 권한 제한, 필요 시 필요한 만큼의 추가 권한 필요
      • Private Module 처럼 Organization 간 Provider 공유 가능
      • State Rollback 기능 지원
    • Provider
      • AWS v4.52.0 주요 수정사항open in new window
        • 신규 resource 추가: aws_auditmanager resource 추가
        • Bug Fix
          • aws_dynamodb_table 에 대해 kms_key_arn 관련 오류 개선
          • aws_api_gateway_integration 에 대해 connection type 이 변경되는 오류 개선
      • Azure v3.41.0 주요 수정사항open in new window
        • 신규 resource 추가: azure_site_recovery_replication_recovery_plan
        • 기능 개선
          • azurerm_databricks_workspace 에 대해 customer managed keypublic_network_access_enabled 설정 지원
          • azurerm_kubernetes_cluster 에 대해 node_public_ip_tags 설정 지원
        • Bug Fix
          • azurerm_stream_analytics_output_servicebus 에 대해 MSI Authentication 설정 관련 개선
      • GCP v4.51.0 주요 수정사항open in new window
        • 신규 resource 추가: google_iam_access_boundary_policy, google_tags_location_tag_bindings
        • 기능 개선
          • compute 에 대해 google_compute_node_group 의 share_settings 설정 지원
          • spanner 에 대해 google_spanner_database_iam_membergoogle_spanner_instance_iam_member 설정 지원
        • Bug Fix
          • bigquery 에 대해 IAM 설정 및 External Data Config 관련 오류 개선
  • Vault
    • 1.12.2
      • 상세 CHANGELOGopen in new window
      • PKI Secret Engine: 기본 issuer 에 대한 변경을 위해 default_follows_latest_issuer 설정 추가
      • Raft Storage: retry_join_as_non_voter설정 추가
      • Okta Auth Method 관련 AuthRenew 로 인한 Panic 오류 수정
      • ciphertext 가 4 byte 이하인 경우 발생 가능한 deadlock 오류 수정
      • Performance Standby Node 에 대한 start up race condition 수정
  • Consul
    • 1.14.4
      • 상세 CHANGELOGopen in new window
      • connect 관련 transparent proxy upstreams 에 필요한 세부 구성 (proxy-defaults, service-default) 수정
      • peering 구성 시 이름은 반드시 소문자로 구성 필요. 향후 호환성 등을 고려하여 기존 구성된 peering 에 대해서 이름에 대문자 사용한 경우에 대해 소문자로 재구성 권고
      • consul connect envoy 명령어 사용 시 envoy-ready-bind-portenvoy-ready-bind-address 설정 지원
      • connect 관련 filter expression 이 처리 되도록 ConsulResolver 지원
      • 인증서 auto-reloading 에 대한 오류 개선
  • Nomad
    • 1.4.3
      • 상세 CHANGELOGopen in new window
      • 동일 Job 에 대해 여러 evaluation 이 pending 되어 있는 경우, 가장 최근 성공한 evaluation 에 대해서만 반영
      • File Upload, Consul Upstream 설정 등 UI 개선
      • Consul 연동 시 gRPC fingerprint 관련 발생 오류 개선 (Consul 1.14 연동부터 적용)
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-03.html b/04-HashiCorp/08-Updates/98-2023/2023-03.html new file mode 100644 index 0000000000..315c3a8d11 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-03.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 3월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 3월

약 1 분HashicorpUpdateMar

2023년 3월

Product 소개

  • Writing Terraform for unsupported resources

    • Hashicorp Blogopen in new window
    • Terraform 과 대상 환경 간 연동을 위해 Provider 를 활용할 때 대상 환경이 API 을 통해서는 지원하는 기능이지만 Provider 에는 구현되어 있지 않아 사용할 수 없는 기능이 종종 있습니다 (예: Vault Provider 기반 Vault 운영 시 Unseal 기능 사용 불가). Terracurl 을 통해 API 을 통해서만 지원되는 기능들을 Terraform Code 로 구성하여 하나의 Resource 로 관리할 수 있습니다.

Product Update

  • Terraform
    • CLI
    • Enterprise Releaseopen in new window
      • 2월 Releaseopen in new window 출시 (v202302-1 (681))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • 구버전 OS 및 Postrgres DB 에 대한 지원 종료
        • 대상 OS
          • Debian 8, 9 / Ubuntu 14.04, 16.04 / Amazon Linux 2014.03, 2014.09, 2015.03, 2015.09, 2016.03, 2016.09, 2017.03, 2017.09, 2018.03
        • 대상 Postgres
          • Postgres 11
          • TFE 에 대해 External PostgresSQL 사용 시 최소 버전 12 이상 필요 (Release 659 부터 적용)
      • Known Issue 1: 관리자 설정에서 TFC Agent 에 대해 사용 여부를 선택하지 않은 상태에서 agent run pipeline mode 사용 시 실행 계획이 무기한 Queue 에 대기하는 문제 발생. 관련하여 tfe-task-worker 를 통해 [ERROR] core: Unexpected HTTP response code: method=POST url=https://terraform.example.com/api/agent/register status=404 라는 Error Log 출력되며 2023년 3월 Release (v202303-1) 에서 해결 예정
      • Known Issue 2: API 를 통해 team 에게 manage-workspaces 권한 부여 시, read-workspaces 도 부여되는 문제 발생. 더불어 manage-workspaces 에 대한 권한 제거 시 read-workspaces 권한은 제거 되지 않고 부여된 채로 유지 되는 문제 발생. 이후 출시 예정인 Release 에서 해결 예정
      • Breaking Changes: 여러 Workspace 를 그룹으로 엮는 Project 기능이 추가됨에 따라
    • Provider
      • AWS v4.57.1 주요 수정사항open in new window
        • Bug Fix
          • resource/aws_lambda_function 에 대해 skip_destroy 를 Null 값 처리 시 발생하는 Provider produced inconsitent final plan 오류 개선
      • Azure v3.46.0 주요 수정사항open in new window
        • 신규 resource 추가: azurerm_mobile_networkazurerm_sentinel_alert_rule
        • 기능 개선
          • resource 에 대해 AuthV2(EasyAuthV2) 지원
        • Bug Fix
          • azurerm_storage_object_replication 에 대해 cross tenant replication 이 비활성화 되어 있는 경우 동작하지 않는 오류 개선
      • GCP v4.56.0 주요 수정사항open in new window
        • 신규 resource 추가: google_data_catalog, google_scc_mute_config, google_workstations_workstation_config
        • 기능 개선
          • compute 에 대해 resource-policy 관련 max_distance 설정 추가
          • compute 에 대해 google_compute_shared_vpc_service_project 관련 deletion_policy 설정 추가
          • networkservices 에 대해 allow_origins 에 대한 최대 설정치를 기존 5 에서 25 로 변경
        • Bug Fix
          • spanner 에 대해 google_spanner_database 관련 deletion_protection 수정 시 발생하는 오류 개선
          • spanner 에 대해 google_spanner_instance 에 대해 force_destory 수정 시 발생하는 오류 개선
  • Vault
    • 1.13.0
      • Hashicorp Blogopen in new window
      • 상세 Release Noteopen in new window
      • Multi-namespace access: namespace 간 권한 부여를 통한 secret 공유 기능 추가
      • Multi Vault Agent: 여러 config 파일 연동하여 다양한 namespace 및 path 에 agent 접근 기능 추가
      • Certificate revocation for cross-cluster management: PR 구성과 같은 Multi Cluster 구조에서 Cluster 간 PKI 인증서 만료 처리에 대한 동기화 지원
      • Managed transit keys: Transit 암호화 처리 시 사용자 key 사용 지원
  • Consul
    • 1.15.0
      • Hashicorp Blogopen in new window
      • 상세 Release Noteopen in new window
      • Envoy access logging: service mesh 구성 시 config entries 와 CRD 를 기반한 손쉬운 envoy log 활성화 지원
      • Consul Envoy extensions: EnvoyExtensions 설정을 통해 lua 및 lambda 등에 대한 service mesh 연동 지원
      • Service-to-service troubleshooting: consul troubleshoot 명령어 지원을 통해 서비스 간 통신 오류에 대한 진단 지원
      • Linux VM runtime support in Consul-native API Gateway (beta): Linux VM 환경 기반 Application 의 service mesh 구성에 필요한 API Gateway 지원
  • Nomad
    • 1.5.0
      • Hashicorp Blogopen in new window
      • 상세 Release Noteopen in new window
      • Single sign-on and OIDC support: OIDC 기반 SSO 지원 및 Login 기능 추가
      • Dynamic node metadata: client node 에 대해 constraints, affinity, spread 등 job scheduling 결정에 영향을 주는 meta 정보 동적 설정 지원
      • Job Templates: Job 명세 작성 지원을 위한 Template 기능 지원
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-04.html b/04-HashiCorp/08-Updates/98-2023/2023-04.html new file mode 100644 index 0000000000..598b2420ef --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-04.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 4월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 4월

1분 미만HashicorpUpdateApr

2023년 4월

Product 소개

  • Dynamic provider credentials now generally available for Terraform Cloud

    • Hashicorp Blogopen in new window
    • AWS, MS Azure, GCP 등 Cloud 환경을 Terraform 과 연동 및 인증하기 위해 Workspace Variable 또는 Variable Set 을 활용하여 Credential 정보를 설정해서 사용했습니다. 해당 Credential 정보는 장기간의 TTL 을 설정하여 사용하거나 혹은 보안취약점을 보완하기 위해 관리자가 수동으로 갱신 및 설정하는 등의 번거로움을 동반하고 있었습니다. Dynamic Provider Credential 은 각 Cloud Service 의 OIDC Provider 를 기반으로 Terraform 에 대한 TLS 인증 확인을 수행함으로써 매 Apply 마다 Terraform 에 대한 인증 처리 후 Resource Provisioning 등을 수행하는 동적인증처리 를 지원합니다.
    • Hashicorp Officlal Tutorialopen in new window 을 참고하여 테스트 해보실 수 있습니다.

Product Update

  • Terraform
    • CLI
    • Enterprise Releaseopen in new window
      • 3월 Releaseopen in new window 출시 (v202303-1 (688))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • OPA Support: OPA (Open Policy Agent) 기반 정책 설정 지원
      • Dynamic Provider Credential: Terraform 과 Provider 간 동적 인증처리 지원 (지원대상 Provider: AWS, AzureRM, AzureAD, GCP, Vault)
      • Drift Detection: State 파일 내 상태정보와 실제 대상환경 간 상이한 부분 체크 및 알림 설정, Workspace 또는 Organization 단위로 활성화
    • Provider
      • AWS v4.60.0 주요 수정사항open in new window
        • 신규 Resource: data/appmesh_route, data/appmesh_virtual_gateway, resource/aws_cognito_managed_user_pool_client 등
        • 기능 개선
          • data/aws_ecs_cluster: tags 설정 지원
          • resource/aws_rds_cluster: snapshot_identifierglobal_cluster_identifier 설정에 의한 잘못된 복원 수행 개선
        • Bug Fix
          • aws_appmesh 관련 mesh owner 설정에 대한 오류 개선
          • aws_medialive_channel 관련 type casting 설정에 대한 오류 개선
      • Azure v3.49.0 주요 수정사항open in new window
        • 신규 resource 추가: azurerm_databricks_virtual_network_peering, azurerm_sentinel_threat_intelligence_indicator, azurerm_voice_services_communications_gateway
        • 기능 개선
          • azurerm_mysql_flexible_server 에 대해 geo_backup_key_vault_key_idgeo_backup_user_assigned_identity_id 설정 지원
          • azurerm_virtual_network_peering 에 대해 allow_forwarded_traffic, allow_gateway_transit, use_remote_gateways 에 대한 기본값 설정
          • azurerm_virtual_hub 에 대해 hub_routing_preference 설정 지원
        • Bug Fix
          • azurerm_linux_function_app 에 대해 auth_v2, token_store_enabled, ip_restriction, scp_ip_restriction 관련 오류 개선
          • azurerm_linux_wep_app 에 대해 auth_v2, token_store_enabled, ip_restriction, scp_ip_restriction 관련 오류 개선
          • azurerm_windows_function_app 에 대해 auth_v2, token_store_enabled, ip_restriction, scp_ip_restriction 관련 오류 개선
          • azurerm_windows_web_app 에 대해 auth_v2, token_store_enabled, ip_restriction, scp_ip_restriction 관련 오류 개선
      • GCP v4.59.0 주요 수정사항open in new window
        • 신규 resource 추가: google_dataplex, google_network_services_gateway
        • 기능 개선
          • oauth2 기반 mTLS 지원
          • bigquery 에 대해 is_case_insensitivedefault_collation 설정 지원
          • compute 에 대해 scratch_disk.sizelocal_nvme_ssd_block 설정 지원
        • Bug Fix
          • certificatemanager 에 대해 import 과정 중 managed.dns_authorizations 관련 오류 개선
          • compute 에 대해 enforce_on_key_name 관련 설정 오류 개선
  • Vault
    • 1.13.1
      • 상세 Release Noteopen in new window
      • Github Auth Method 에 대해 환경변수 VAULT_AUTH_CONFIG_GITHUB_TOKEN 설정 지원
      • DB Secrets Engine (Elasticsearch) 에 대해 API 오류 시 알림 메세지 강화
      • CKR_FUNCTION_FAILED 오류 발생 시 PKCS#11 HSM 에 대한 재연결 시도 개선
      • UI 개선
  • Consul
    • 1.15.1
      • 상세 Release Noteopen in new window
      • consul token update 명령어 수행 시 -append-policy-id, -append-policy-name, -append-role-id, -append-service-identity, -append-node-identity 매개변수 설정 지원
      • 호환 Envoy Version: 1.22.5 ~ 1.22.7, 1.23.2 ~ 1.23.4, 1.24.0 ~ 1.24.2 지원 (1.21.5 제외)
      • Service Mesh 에 사용되는 Gateway 에서 발생하는 HTTPRoute 관련 오류 개선
      • 존재하지 않는 ACL 정책 호출 시 발생하는 오류 개선
  • Nomad
    • 1.5.2
      • 상세 Release Noteopen in new window
      • namespace status, quota status, server members 명령어에 대해 -json-t 매개변수 설정 지원
      • 등록된 서비스 제거 시 두번 제거 되는 오류 개선
      • Consul Discovery 사용하는 서비스에 대해 발생하는 Cluster Join 권한 오류 개선
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-05.html b/04-HashiCorp/08-Updates/98-2023/2023-05.html new file mode 100644 index 0000000000..fd6f0d5cf5 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-05.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 5월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 5월

1분 미만HashicorpUpdateMay

2023년 5월

Product 소개

Product Update

  • Terraform
    • CLI

    • Enterprise Releaseopen in new window

      • 4월 Releaseopen in new window 출시 (v202304-1 (692))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • Project List API 사용 시 Project 이름 기반 필터 처리 지원
      • 신규 생성된 User, Team, Org 정보에 대한 강조 표시 등 UI 개선
      • Sentinel 0.21 Upgrade
    • Provider

      • AWS v4.64.0 주요 수정사항open in new window

        • 신규 Resource
          • data/aws_route53_resolver_query_log_config
          • data/aws_redshiftserverless_workgroup
          • resource/aws_cloudwatch_event_endpoint
        • 기능 개선
          • data/aws_eks_node_group 에 대해 launch_template 설정 추가
          • aws_iam_role 에 대해 role_last_used 설정 추가
          • aws_lambda_* 에 대해 compatible_runtimes = python 3.10 설정 추가
        • Bug Fix
          • aws_default_vpc 에 대해 ipv6 vpc 사용 시 발생하는 오류 개선
          • aws_ecs_service 에 대해 IAM Role 기반 service import 수행 시 오류 개선
          • aws_lakeformation_permissions 사용 시 발생하는 오류 개선
      • Azure v3.53.0 주요 수정사항open in new window

        • 신규 resource 추가
          • azurerm_storage_mover_*
          • azurerm_cost_management_scheduled_action
        • 기능 개선
          • azurerm_windows_* 에 대해 hosting_environment_id 설정 추가
          • azurerm_linux_* 에 대해 hosting_environment_id 설정 추가
        • Bug Fix
          • azurerm_cdn_endpoint 사용 시 query_string 길이 제약 제거
          • azurerm_monitor_metric_alert 사용 시 dynamic_criteria.0.ignore_data_before 미설정시 발생하는 오류 개선
          • azurerm_postgresql_flexible_server 사용 시 point_in_time_restore_time_in_utc 관련 오류 개선
      • GCP v4.63.0 주요 수정사항open in new window

        • 신규 resource 추가
          • google_compute_region_commitment
          • google_compute_public_advertised_prefix
          • google_network_service_http_route
        • 기능 개선
          • google_alloydb_cluster 및 google_alloydb_backup 사용 시 location 설정 required 로 변경 (기존: optional)\
          • google_data_loss_prevention_job_trigger 사용 시 inspect_job.actions.job_notification_emails, inspect_job.actions.deidentify, triggers.manual 그리고 inspect_job.storage_config.hybrid_options 설정 추가
        • Bug Fix
          • google_alloydb_cluster 에 대해 weekly_schedule 설정 optional 로 변경
          • google_compute_backend_bucket 에 대해 USE_ORIGIN_HEADERS 사용시 발생하는 TTL 관련 오류 개선
  • Vault
    • 1.13.1
      • 상세 Release Noteopen in new window
      • Github Auth Method 에 대해 환경변수 VAULT_AUTH_CONFIG_GITHUB_TOKEN 설정 지원
      • DB Secrets Engine (Elasticsearch) 에 대해 API 오류 시 알림 메세지 강화
      • CKR_FUNCTION_FAILED 오류 발생 시 PKCS#11 HSM 에 대한 재연결 시도 개선
      • UI 개선
  • Consul
    • 1.15.2
      • 상세 Release Noteopen in new window
      • connect proxy 설정 기반 service mesh 관련 telemetry 를 HCP metrics collection service 로 전달하는 기능 추가
      • Vault CA Provider 와 연동 시 발생하는 Service Mesh TLS 설정 관련 오류 개선
      • Gateway 에 대해 Namespace 및 Partition 관련 unmarshal 되는 오류 개선
      • Default Namespace 또는 Partition 을 사용하지 않는 서비스에 대한 Resolver, Router, Splitter 사용 시 Peering 이 올바르게 수행되지 않는 오류 개선
      • Audit Log 이용 시 Streaming not supported 발생하는 오류 개선
  • Nomad
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-06.html b/04-HashiCorp/08-Updates/98-2023/2023-06.html new file mode 100644 index 0000000000..3ee9fab4f3 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-06.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 6월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 6월

약 1 분HashicorpUpdateJun

2023년 6월

Product 소개

  • Terraform Cloud updates plans with an enhanced Free tier and more flexibility

    • Hashicorp Blogopen in new window
    • Terraform Cloud 에 대한 요금제가 개편됩니다. 요금제 개편과 함께 각 요금제에서 사용가능한 기능들도 추가되었습니다. (예를 들어 기존 Free Tier 에서는 사용불가 했던 Sentinel/OPA, SSO, Terraform Agent, Run Tasks 등)
  • Terraform Cloud adds Vault-backed dynamic credentials

    • Hashicorp Blogopen in new window
    • 지난 4월 소개된 Dynamic provider credentials now generally available for Terraform Cloud 에 이어 Vault의 Cloud Secrets Engine (AWS, Azure, GCP) 를 연계한 동적 자격증명 발급 기능이 출시되었습니다. 이제, Terraform Apply 수행 시 마다 Vault 로 부터 자격증명을 발급받아 사용함으로써 보다 보안 강화된 워크플로우를 구성할 수 있습니다.

Product Update

  • Terraform
    • CLI
      • 1.4.6 Releaseopen in new window
        • terraform plan 시 null string 또는 잘못 정의된 map 으로 인한 오류 개선
        • terraform plan 시 구버전 TFE 에서 plan 두번 되는 이슈 개선
        • forces replacement 설정된 자원 관련 오류 개선
    • Enterprise Releaseopen in new window
      • 5월 Releaseopen in new window 출시 (v202305-1 (703))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • Terraform run 시 구동되는 Build Worker 관련 지원 종료 (v202306-1 부터 적용, Terraform run 시 TFC Agent 로 대체)
      • Project 단위 Variable Set 사용 가능
      • Vault 와 연계한 Dynamic Credentials 사용 시 base64 encoded PEM format Cert 지정시 TFC_VAULT_ENCODED_CACERT 환경변수 사용 지원
      • Authentication Token 발행 시 폐기 시점 (Expire date) 설정지원
      • OPA Version 자동 업그레이드 지원
    • Provider
      • AWS v5.0 주요 수정사항open in new window
        • 신버전 출시 관련 Hashicorp Blogopen in new window
        • Provider 단위별 생성/관리 되는 자원에 대한 Default Tag 설정 지원 및 기존 발생하던 Tag 관련 이슈에 대한 개선
        • EC2 Classic 관련 기능 지원 종료
        • 기존에 사용자가 고지 받던 Warning 알림 (지원 종료된 자원 사용 등) 에 대한 개선
        • 신규 자원 관련 지원 추가
          • Amazon CloudWatch Observability Access Manager
          • Amazon EC2 Recycle Bin
          • Amazon QuickSight
          • Amazon VPC Lattice
          • AWS Directory Service trust relationships
          • Amazon EventBridge Pipes
      • Azure v3.58.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_cosmosdb_mongo_*
          • azurerm_mobile_network_packet_core_control_plane
          • azurerm_monitor_alert_prometheus_rule_group
          • azurerm_site_recovery_*
        • 기능 개선
          • azurerm_subnet_service_endpoint_policy 에 대해 service 설정 가능
          • azurerm_site_recovery_replication_recovery_plan 에 대해 azure_to_azure_settings 설정 가능
        • Bug Fix
          • azurerm_kubernetes_cluster 에 대해 nil 값 반환으로 인한 panic 오류 개선
          • azurerm_api_management 에 대해 triple_des_ciphers_enabled 설정값 오류 개선
          • azurerm_key_vault 에 대해 createMode 를 nil 대신 default 로 대체
      • GCP v4.67.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_____*_iam_policy
          • google_vertex_ai_index
        • 기능 개선
          • google_compute_instance 와 google_compute_instance_template 에 대해 network_performance_config 설정 지원
          • google_compute_disk 와 google_compute_region_disk 에 대해 guest_os_features 및 licenses 설정 지원
          • google_datastream_stream 에 대해 mysql_source_config.max_concurrent_backfill_tasks 설정 지원
        • Bug Fix
          • google_apigee_orginization 에 대한 default timeout 을 기존 20분에서 45분으로 개선
          • google_container_node_pool 에 대해 crash 오류 관련 개선
          • google_gkeonprem_vmware_cluster 에 대해 hostname 설정을 필수에서 선택으로 변경
  • Vault
    • 1.13.2
      • 상세 Release Noteopen in new window
      • namespace 에 대한 output 정보 상세화
      • pki secret engine 에 대해 OCSP 에 대한 사이즈 조정 및 호환성 제고
      • ldap auth method 에 대해 max_page_size 설정 지원
      • 신규 생성된 Token 이 인지되지 않음으로 인해 간헐적으로 발생되는 error performing token check: no lease entry found for token that ought to have one, possible eventual consistency issue 오류 개선
      • Perf Standby Cluster 에 대해 Leader 변경, Unseal 등의 작업 이후 발생하는 412 오류 개선
      • PR Cluster 에 filter 된 정보 replication 작업 시 발생하는 caching 오류 개선
      • UI 개선
  • Consul
  • Nomad
    • 1.5.6
      • 상세 Release Noteopen in new window
      • Job allocation 이 재시작 되었을 때 발생하는 group service 가 등록 해제 되는 오류 개선
      • 종료되었음에도 삭제 되지 않은 Job allocation 에 대한 오류 개선
      • Job Evaluation 이 잘못된 type 으로 생성되는 오류 개선
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-07.html b/04-HashiCorp/08-Updates/98-2023/2023-07.html new file mode 100644 index 0000000000..54038f4d39 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-07.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 7월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 7월

약 1 분HashicorpUpdateJul

2023년 7월

Product 소개 (Hashidays 2023)

  • 매년 유럽 네덜란드에서 이틀간 진행되던 Hashiconf 가 올해는 한단계 더 성장하여 영국 런던, 프랑스 파리 그리고 독일 뮌헨에서 동시에 진행되는 Hashidays 2023 으로 개최되었습니다. 새롭게 펼쳐진 Hashidays 에서는 Terraform, Vault, Consul 그리고 Boundary 에 대해 그동안 사용자들이 사용하면서 느꼈던 불편함을 해소할 신기능을 출시했습니다.
    • Hashicorp Blogopen in new window
    • HCP Vault Secretsopen in new window (Public Beta): Vault 에서 가장 많이 활용되고 있는 Secret Engine 중 하나인 KV Engine 기반 시크릿 관리에 특화된 HCP SaaS 서비스로써 애플리케이션 개발 및 운영에 사용되는 시크릿에 대한 저장, 접근 그리고 자동 동기화 기능을 더욱 쉽게 구성 가능하도록 지원합니다. 특히 기존 사용 중인 AWS Secret Manager 등 클라우드 서비스와도 원클릭 동기화를 지원함으로써 기존 워크플로우에 대해 최소한의 변경으로 시크릿 동적관리가 가능합니다.
    • Vault Secrets Operatoropen in new window: CRD 기반으로 K8S의 Secrets 와 연계하여 Secrets 를 동적으로 관리함으로써 최소한의 추가 구성으로 기존 구성하여 사용중인 Secrets 에 대해 보안을 강화합니다.
    • Boundary Enterpriseopen in new window: 기존에 Opensource 그리고 HCP SaaS 로만 제공되던 Boundary 가 드디어 Enterprise Edition 이 출시되었습니다. Enterprise Edition 에서는 그동안 많은 사용자들이 필요로 했던 Session Recording 과 더불어 그외 다양한 신규 기능들이 추가되었습니다.
    • Terraformopen in new window: 지난 6월호에서 소개한 Vault-backed dynamic credentialsopen in new window 의 정식 GA 출시, 그동안 너무 불편하고 어려웠던 Terraform import 를 보완해줄 Config-driven Importopen in new window, 생성 및 관리 중인 자원에 대한 보다 효과적인 관리를 위한 Explorer (Beta) 등 다양한 기능이 추가되었습니다.
    • Consul 1.16open in new window: Envoy Proxy 에 Extension 이 출시되어 WASM (Web Assembly) Code 기반 추가 기능을 활용하거나 외부 보안 서비스와 연계하여 인증 기반 기능 활용합니다, 또한 여러 Cluster 에 걸쳐 동일 서비스에 대해 동일 서비스 이름을 사용하도록 하는 Sameness Groups 를 통해 서비스 관리 뿐만 아니라 장애 발생 시 수행하는 Failover 를 보다 간단하게 처리할 수 있습니다.

Product Update

  • Terraform
    • CLI
      • 1.5.0 Releaseopen in new window
        • checkopen in new window Block 추가, 최소 1개 이상의 assert 구문을 요구하며 assert 구문 내 1개 혹은 여러 개의 conditionerror_message 를 지정하여 resource 또는 data 자원에 대한 검증 지원
        • import Block 추가, 기존 terraform import 명령어 기반 import 작업을 hcl code 로 작성하여 보다 손쉽게 import 작업 수행하도록 지원
    • Enterprise Releaseopen in new window
      • 6월 Releaseopen in new window 출시 (v202306-1 (713))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • Terraform run 시 구동되는 Build Worker 관련 지원 종료 (v202306-1 부터 적용, Terraform run 시 TFC Agent 로 대체)
      • v202308-1 부터 TFE 구동을 위한 Container 구성이 terraform-enterprise 라는 단일 Container 로 통합 (terraform plan 또는 terraform apply 는 기존 방식과 동일하게 수행 때마다 agent container 생성)
      • v202308-1 부터 Docker 19.03 지원 종료 예정
      • No-code Provisioningopen in new window 지원
    • Provider
      • AWS v5.7.0 주요 수정사항open in new window
        • 신규 resource 추가
          • data/aws_opensearchserverless_*
          • aws_cleanrooms_collaboration
        • 기능 개선
          • aws_lambda_function 에 대해 ap-east-1 region 에서 code_signing_config_arn 설정 지원
        • Bug Fix
          • provider 에 대해 forbidden_account_ids 처리 오류 개선
          • aws_kms 에 대해 tag 삭제 오류 개선
          • aws_secretsmanager_secret_rotation 에 대해 update 시 발생하는InvalidParameterException: You cannot specify both rotation frequency and schedule expression together 오류 개선
          • aws_vpc_endpoint 에 대해 s3 interface vpc endpoint 생성 시 발생하는 InvalidParameter: PrivateDnsOnlyForInboundResolverEndpoint not supported for this service 오류 개선
      • Azure v3.64.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_automation_variables
          • azurerm_arc_private_link_scope
          • azurerm_kusto_cosmosdb_data_connection
          • azurerm_pim_*
        • 기능 개선
          • azurerm_windows_* 에 대해 public_network_access_enabled 설정 지원
          • azurerm_linux_* 에 대해 public_network_access_enabled 설정 지원
          • azurerm_spring_cloud_gateway 에 대해 allowed_origin_patterns 설정 지원
        • Bug Fix
          • azurerm_windows_* 에 대해 cors 설정 시 allowed_origins 에 대한 설정 항목 수 오류 개선
          • azurerm_linux_* 에 대해 cors 설정 시 allowed_origins 에 대한 설정 항목 수 오류 개선
      • GCP v4.73.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_firebase_extensions_instance
        • 기능 개선
          • compute 에 대해 google_compute_forwarding_rule 설정 내 no_automate_dns_zone 추가
          • compute 에 대해 google_compute_disk_async_replication 정식 지원
          • compute 에 대해 google_compute_disk 설정 내 async_primary_disk 추가
          • compute 에 대해 google_compute_region_disk 설정 내 async_primary_disk 추가
        • Bug Fix
          • networkservices 에 대해 google_network_services_edge_cache_keyset 의 기본 timeout 값을 90 분으로 설정
  • Vault
    • 1.14.0
      • 상세 Release Noteopen in new window
      • PKI Engine 에 대해 사용 중인 유효 인증서 정보 관리에 대한 기본 설정을 off 로 변경
      • AWS Secret Engine: Static Rotation 지원 (기존 생성된 IAM User 에 대해 Access Key / Secret Key Rotation)
      • Auth Method 추가: OCI (Oracle Cloud Infrastructure)
      • Vault Agent Supervisor Mode: Vault 에 저장된 Secret 을 환경변수로 등록
      • PKI: ACME 지원
      • UI: Terraform 과 같이 좌측 Sidebar 형태로 UI 변경
      • Vault Proxy: Vault Agent API Proxy 와 동일 기능 제공, 향후 기능 추가 및 Vault Agent API Proxy 사용 종료 예정
  • Consul
    • 1.16
      • 상세 Release Noteopen in new window
      • /v1/health/connect//v1/health/ingress/ api 에 대해 적절한 권한 없는 token 사용 시 403 오류 출력
      • cluster peering: 지원 종료된 version 을 사용하는 cluster 에 대한 하위 호환성 제거
      • consul services export: peer cluster 또는 타 partition 에 서비스 노출을 위한 명령어 추가
      • Permissive mTLS: Service Mesh 에 대해 mTLS 활성화 유무 선택
      • audit hash: audit log hash function 기반 data hash 값 검증 지원
  • Nomad
    • 1.5.6
      • 상세 Release Noteopen in new window
      • Job allocation 이 재시작 되었을 때 발생하는 group service 가 등록 해제 되는 오류 개선
      • 종료되었음에도 삭제 되지 않은 Job allocation 에 대한 오류 개선
      • Job Evaluation 이 잘못된 type 으로 생성되는 오류 개선
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-08.html b/04-HashiCorp/08-Updates/98-2023/2023-08.html new file mode 100644 index 0000000000..55ebc41d69 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-08.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 8월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 8월

약 1 분HashicorpUpdateAug

2023년 8월

Product 소개

  • Using Terraform dynamic provider credentials in your AWS landing zones
    • Hashicorp Blogopen in new window
    • 지난 4월 소개된 Terraform 사용 시 필요한 대상 환경에 대한 클라우드 자격증명을 Vault 와 연동하여 동적으로 사용 및 관리하는 Dynamic Provider Credentials 기능을 AWS Landing Zone 에서도 사용하실 수 있습니다. Terraform 과 함께 Landing Zone 으로 시작하는 AWS 의 여정에서 더 보안 강화된 IaC 를 경험해보세요!

Product Update

  • Terraform
    • CLI
    • Enterprise Releaseopen in new window
      • 7월 Releaseopen in new window 출시 (v202307-1 (722))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • TFE 구동을 위한 Container 구성이 terraform-enterprise 라는 단일 Container 로 통합. terraform plan 또는 terraform apply 는 기존 방식과 동일하게 수행 때마다 agent container 생성 (적용시점: v202309-1 부터 적용)
      • Docker 19.03, 20.10 미지원 (적용시점: v202308-1 부터 적용)
      • Redis v5 지원 종료
      • Manage Policy Overrides 에 대해 기본 부여된 정책 수정 (기존 Read -> List)
      • 신규 Org 에 대해 Cost Estimation 기본 비활성화
    • Provider
      • AWS v5.10.0 주요 수정사항open in new window
        • 신규 resoure 추가
          • aws_iam_security_token_service_preferences
        • 기능 개선
          • aws_nat_gateway 에 대해 secondary_allocation_ids, secondary_private_ip_address, secondary_private_ip_count 등 설정 추가
          • aws_batch_compute_environment 에 대해 compute_resources.allocation_strategy, compute_resources.bid_percentage 를 포함한 세부 설정 추가
        • Bug Fix
          • aws_quicksight_* 에 대해 definition.sheets.visuals.pie_chart_visual.chart_configuration.data_labels.measure_label_visibility 설정 시 발생하는 오류 개선
          • aws_apigatewayv2_authorizer 에 대해 TTL 설정 관련 오류 개선
          • aws_kms_* 에 대해 tag 전체 삭제 관련 오류 개선
      • Azure v3.67.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_eventhub_sas
          • azurerm_kubernetes_cluster_trusted_access_role_binding
          • azurerm_marketplace_role_assignment
          • azurerm_network_function_azure_traffic_collector
        • 기능 개선
          • azurerm_image 에 대해 disk_encryption_set_id 설정 추가
          • azurerm_linux_virtual_machine 에 대해 bypass_platform_safety_checks_on_user_schedule_enabled 와 reboot_setting 설정 추가
          • azurerm_windows_virtual_machine 에 대해 bypass_platform_safety_checks_on_user_schedule_enabled 와 reboot_setting 설정 추가
        • Bug Fix
          • azurerm_cosmosdb_account 에 대해 max_age_in_seconds 가 2147483647 초까지 설정할 수 있도록 개선
          • azurerm_redis_cache 에 대해 patch_schedule 업데이트 조건 설정 관련 오류 개선
          • azurerm_postgresql_flexible_server 에 대해 storage_mb 설정에 대한 유효성 검사 정보를 3355340 에서 33554432 으로 수정
      • GCP v4.76.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_compute_region_ssl_policy
          • google_iap_web_region_backend_service_iam_*
          • google_dataplex_task
        • 기능 개선
          • compute 에 대해 google_compute_service_attachment를 위한 reconcile_connections 설정 추가
          • container 에 대해 google_container_cluster 의 addons_config 를 위한 gcs_fuse_csi_driver_config 설정 추가
          • container 에 대해 google_container_cluster 를 위한 allow_net_admin 설정 추가
          • networkservices 에 대해 google_network_services_gateway 를 위한 scope 설정 추가
        • Bug Fix
          • compute 에 대해 google_compute_security_policy 를 위한enforce_on_key_configs 의 트리거 시점 관련 오류 개선
  • Vault
    • 1.14.1
      • 상세 Release Noteopen in new window
      • Administrative Namespace 도입. 기존 Root Namespace 에서만 접근 가능한 System Backend Path 에 대한 접근 권한 등을 기반으로 Cluster 관리용 Namespace 로 활용
      • Transform Secrets Engine 사용 시 Encoding 요청에 대한 Max TTL 적용
      • Performance Standby (Read Replica) Node 에서 발생하는 요청에 대한 Logging 알람 방지
  • Consul
    • 1.16
      • 상세 Release Noteopen in new window
      • /v1/health/connect//v1/health/ingress/ api 에 대해 적절한 권한 없는 token 사용 시 403 오류 출력
      • cluster peering: 지원 종료된 version 을 사용하는 cluster 에 대한 하위 호환성 제거
      • consul services export: peer cluster 또는 타 partition 에 서비스 노출을 위한 명령어 추가
      • Permissive mTLS: Service Mesh 에 대해 mTLS 활성화 유무 선택
      • audit hash: audit log hash function 기반 data hash 값 검증 지원
  • Nomad
    • 1.6.0
      • Hashicorp Blogopen in new window
      • 상세 Release Noteopen in new window
      • Datacenter 내 Client Node 에 대해 논리적 파티션으로 구분지어 Job 배포 시 해당하는 Pool 에만 배포되도록 하는 Node Pool 기능 추가
      • Namespace 를 생성하고 각 Namespace 별 기본 Node Pool 지정 및 배포 허용 여부를 제어할 수 있는 Node Pool Governance 기능 추가
      • Job Status 및 Job Deployments 에 대한 UI 개선
      • Job Spec 소스코드에 대한 기존 Raw JSON 지원했던 부분에 대해 소스코드 원문을 UI 상에서 확인 및 수정할 수 있도록 개선
      • Podman 정식 지원
      • Job restart 명령어 지원 (기존에는 stop 후 start)
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-09.html b/04-HashiCorp/08-Updates/98-2023/2023-09.html new file mode 100644 index 0000000000..19fb20dc01 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-09.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 9월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 9월

1분 미만HashicorpUpdateSep

2023년 9월

Product 소개

  • Terraform ephemeral workspaces public beta now available
    • Hashicorp Blogopen in new window
    • 개발 및 테스트 등을 위해 임시로 사용하는 Workspace 에 대해 사용 완료 후 방치함으로 인해 발생되는 자원 낭비 또는 보안 유출 위험성을 방지하고자 Workspace 사용에 대해 미리 시간 설정을 할 수 있는 기능이 베타로 출시되었습니다. 이제, 정해놓은 타이머가 도래하면 Workspace 와 해당 Workspace 를 통해 생성한 자원을 자동 폐기 및 정리함으로써 자원 관리의 효율성을 높이고 미사용 자원에 대한 보안 유출 등을 방지할 수 있습니다. 베타 버전은 Terraform Cloud (Plus 요금제 이상) 에서 체험 및 사용 가능합니다.

Product Update

  • Terraform
    • CLI
      • 8월 Releaseopen in new window 출시 (v202308-1 (725))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • Postgres DB v14.0, v14.1, v14.2, v14.3 에 대한 지원 종료
      • TFE 구동을 위한 Container 구성이 terraform-enterprise 라는 단일 Container 로 통합. terraform plan 또는 terraform apply 는 기존 방식과 동일하게 수행 때마다 agent container 생성 (적용시점: v202309-1 부터 적용)
      • Docker 19.03, 20.10 지원 종료
      • dynamic provider credentials 에 대해 Workspace 내 Provider 당 설정 지원
      • UI 출력 및 Cloud Provider 사용 위한 Key 검증 처리 등 성능 개선
    • Provider
      • AWS v5.15.0 주요 수정사항open in new window
        • 기능 개선
          • aws_efs_file_system 에 대해 name 설정 지원
          • aws_datasync_location_fsx_openzfs_file_system 에 대해 setting protocol: Invalid address to set 오류 수정
          • aws_opensearch_domain 에 대해 cluster_config.multi_az_with_standby_enabled 설정 지원
        • Bug Fix
          • tag 사용 관련 오류 개선
          • aws_efs_file_system_policy 에 대해 IAM 관련 일관성 오류 개선
          • aws_kms_key 에 대해 tag 정보가 공란일 때 발생하는 tag propagation: timeout while waiting for state to become 'TRUE' 오류 개선
      • Azure v3.71.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_databricks_workspace_root_dbfs_customer_managed_key
        • 기능 개선
          • azurerm_kubernetes_cluster 에 대해 sysctl_config 설정 가능 범위 확대
          • azurerm_linux(windows)_virtual_machine_scale_set 에 대해 자원 삭제 전 rolling upgrade 취소 지원
        • Bug Fix
          • 자동 복구를 위한 win32_status 설정 시 발생하는 오류 관련 azurerm_linux_web_app, azurerm_linux_web_app_slot, azurerm_windows_web_app, azurerm_windows_web_app_slot 자원에 대해 win32_status_code (3.63.0 이후 버전에서는 int로 유지) 로 대체
          • azurerm_kubernetes_cluster 에 대해 public_network_access_enabled 설정 삭제
      • GCP v4.80.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_certificate_manager_trust_config
          • google_compute_region_security_policy_rule
          • google_sql_database_instance_latest_recovery_time
          • google_iam_deny_policy
          • google_bigquery_bi_reservation
        • 기능 개선
          • google_compute_target_instance 에 대해 security_policy 설정 지원
          • google_compute_target_pool 에 대해 security_policy 설정 지원
          • google_compute_instance 에 대해 regional disk 지원
          • google_container_cluster 에 대해 additional_pod_ranges_config 설정 지원
          • google_dns_response_policy 에 대해 networks 설정 지원 제거
        • Bug Fix
          • bigquery 에 대해 table schema 대상 policy set 설정 해제 지원
  • Vault
    • 1.14.2
      • 상세 Release Noteopen in new window
      • Azure Auth Method 에 대해 Auto auth 위한 Azure Workload Identity Federation 설정 지원
      • KMIP 에 대해 Namespace Lock/Unlock 설정 지원
      • Replication 관련 Flush 단계에서 쓰기 지원하여 Index 재생성 작업에 영향 주지 않도록 개선
      • Vault Agent Template 에 대해 VAULT_CACERT_BYTES 환경변수 사용 지원
      • Transform Engine 관련 Standby Node 에서 발생하는 인코딩 오류 개선
  • Consul
    • 1.16.1
      • 상세 Release Noteopen in new window
      • consul members 명령어에 대해 -filter 설정 추가
      • operator/usage api 에 대해 node 수 출력 지원 (CLI 사용 시 consul operator usage 명령어 사용)
      • Service Mesh 관련 Virtual Service 및 Failover 에 대한 Transparent Proxy 성능 개선
      • Topology 관점 UI 출력 개선
      • Envoy Proxy 관련 xDS configuration 완료되기 전 구동 되어 발생하는 오류 개선
      • RSA Key 에 대해 사이즈가 2048 bits 이하여서 발생하는 오류를 검증 단계에서 미리 확인하도록 개선
  • Nomad
    • 1.6.1
      • 상세 Release Noteopen in new window
      • Datacenter 내 Client Node 에 대해 논리적 파티션으로 구분지어 Job 배포 시 해당하는 Pool 에만 배포되도록 하는 Node Pool 기능 추가
      • Namespace 를 생성하고 각 Namespace 별 기본 Node Pool 지정 및 배포 허용 여부를 제어할 수 있는 Node Pool Governance 기능 추가
      • Job Status 및 Job Deployments 에 대한 UI 개선
      • Job Spec 소스코드에 대한 기존 Raw JSON 지원했던 부분에 대해 소스코드 원문을 UI 상에서 확인 및 수정할 수 있도록 개선
      • Podman 정식 지원
      • Job restart 명령어 지원 (기존에는 stop 후 start)
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-10.html b/04-HashiCorp/08-Updates/98-2023/2023-10.html new file mode 100644 index 0000000000..27e473bc19 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-10.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 10월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 10월

1분 미만HashicorpUpdateOct

2023년 10월

Product 소개

  • Creating a multi-cloud golden image pipeline with Terraform Cloud and HCP Packer
    • Hashicorp Blogopen in new window
    • 조직 내 클라우드 사용환경에서 "표준화" 되지 않은 VM OS Image 로 인해 장애 발생 시 케이스를 표준화 하지 못하고 대응에 미진한 경우를 종종 접하곤 합니다. Hashicorp Packer 와 Terraform 을 연동하여 조직 내 사용중인 각 클라우드 환경 마다 Golden OS Image 를 구성하고 이를 활용하여 인스턴스 자원 배포하는 과정까지의 라이프사이클을 "표준화" 함으로써 보다 더 효율적이고 안정적인 시스템 환경을 구성해보세요.

Product Update

  • Terraform
    • CLI
      • 1.5.7 Releaseopen in new window
        • terraform init 수행 시 잘못된 경로로 모듈을 다운로드 하는 경우에 대한 오류 개선
        • terraform remote state 관리 시 발생 가능한 state 간 호환성 불일치에 대한 이슈 방지
    • Enterprise Releaseopen in new window
      • 9월 Releaseopen in new window 출시 (v202309-1 (733))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • 기존에 수십 가지의 container 로 분산 구성되어 있던 서비스 구성을 단일 container 로 통합 (terraform-enterprise). 이를 기반으로 flexible deployment optionsopen in new window 로써 VM 또는 K8S 등 다양한 플랫폼에 설치 및 구동방식 제공
      • Azure DevOps VCS 연동 시 발생하는 Known Issue 에 대해 Azure DevOps Server 의 버전을 2020 Update 1.2 Patch 7 로 업그레이드 하여 해결
      • Terraform Enterprise 로그인 진행 후 발생하는 step-up 추가 인증 이슈에 10월 Release 에서 개선 예정
    • Provider
      • AWS v5.19.0 주요 수정사항open in new window
        • 기능 개선
          • aws_s3_bucket_object 에 대해 AWS SDK for Go v2 의 정책에 따라 metadata 의 keys 정보는 항상 소문자로 반환
          • aws_s3_object 에 대해 AWS SDK for Go v2 의 정책에 따라 metadata 의 keys 정보는 항상 소문자로 반환
        • 신규 resource 추가
          • aws_rds_custom_db_engine_version
          • aws_vpclattice_service_network
        • Bug Fix
          • aws_cloudfront_distribution 에 대해 aws_cloudfront_continuous_deployment_policy 가 업데이트 됨에 따라 발생하는 오류 개선
          • aws_iam_* 에 대해 policy 에 존재 하는 중복 키로 인해 발생하는 validation 오류 개선
      • Azure v3.75.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_application_load_balancer
          • azurerm_resource_management_private_link
        • 기능 개선
          • azurerm_kubernetes_cluster 에 대해 network_profile.network_policy 를 cilium 으로 migration 하도록 지원
          • azurerm_log_analytics_workspace 에 대해 data_collection_rule_id 설정 지원
          • azurerm_mysql_flexible_server 에 대해 io_scaling_enabled 설정 지원
        • Bug Fix
          • azurerm_api_management_api 에 대해 openapi format 의 파일 import 시 발생하는 오류 개선
          • azurerm_key_vault_certificate 에 대해 soft-deleted 된 인증서 관련 오류 개선
      • GCP v5.0.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_scc_folder_custom_module
          • google_scc_organization_custom_module
        • 기능 개선
          • default labels, terraform_labels, effective_labels 등 label 관련 기능 개선
          • provider level 의 credentials, access_token, impersonate_service_account 등의 정보 검증
          • terraform import 시 gcp resource id 에 대한 온전한 매핑 지원
        • Bug Fix
          • bigquery 에 대해 view 와 materialized view 생성 관련 오류 개선
          • bigtable 에 대해 클러스터가 업데이트 중이거나 storage 타입 변경 시 발생하는 google_bigtable_instance 재생성 오류 개선
  • Vault
    • 1.15
      • 상세 Release Noteopen in new window
      • Auth Method (alicloud, azure, cf, gcp, jet, k8s, oci 등) plugin 버전 업그레이드
      • RGP (Role Governing Policies) 설정 시 Namespace 단위로 적용
      • System Backend Path에 접근 가능한 Administrative Namespace 도입 (기존에는 root 만 가능)
      • UI 상에서 KV v2 의 Path 정보에 대한 복사 기능 지원하여 CLI 또는 API 에서의 활용 편의성 증가
      • UI 첫 페이지에 Dashbaord 도입
      • SAML Auth Method 지원
  • Consul
    • 1.16.2
      • 상세 Release Noteopen in new window
      • Vault 와 연동하여 사용하는 인증서 관련 Leaf Cert 생성 시 발생하는 오류 개선
      • Snapshot 기반 복원 후 Envoy Endpoint 올바르게 생성되지 않는 이슈 해결
  • Nomad
    • 1.6.2
      • 상세 Release Noteopen in new window
      • Service Mesh 위해 Consul 연동 시 upstream 설정 관련 DestinationPeer, DestinationType, LocalBindSocketPath 등 설정 지원
      • jobspec 관련 여러개의 cron 정의를 위해 crons 지원
      • UI 개선
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/2023-11.html b/04-HashiCorp/08-Updates/98-2023/2023-11.html new file mode 100644 index 0000000000..92ed04858e --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/2023-11.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2023년 11월 | docmoa + + + + + +
본문으로 건너뛰기

2023년 11월

1분 미만HashicorpUpdateNov

2023년 11월

Product 소개

  • Infrastructure and security releases open HashiConf 2023
    • Hashicorp Blogopen in new window
    • 샌프란시스코에서 개최된 Hashiconf 2023 에서 8가지의 솔루션에 대해 그룹을 크게 Infrastructure 와 Security 로 구분지어 앞으로의 솔루션 포트폴리오 및 업데이트를 진행하며, Terraform test framework, Vault Secret Sync, Vault Radar 등 워크플로우 개선을 위한 새로운 기능이 공개했습니다. 자세한 사항은 행사 현장을 직접 다녀온 이들이 전해주는 Hashicorp Korea: Hashiconf 2023open in new window 에서 확인하세요!

Product Update

  • Terraform
    • CLI
      • 1.6 Releaseopen in new window
        • terraform test 기능 추가 - 작성한 terraform code 에 대해 .tftest.hcl code 를 작성하여 검증 지원
        • Terraform CLI 설치 대상 OS 최소버전 명시
          • MacOS: 10.15 Catalina 이상
          • Windows: Windows 10 또는 Windows Server 2016 이상
        • S3 Remote Backend 설정 시 AWS 서비스 endpoint 지정에 필요한 환경변수 정의 - AWS_ENDPOINT_URL_DYNAMODB, AWS_ENDPOINT_URL_IAM, AWS_ENDPOINT_URL_S3, AWS_ENDPOINT_URL_STS
    • Enterprise Releaseopen in new window
      • 10월 Releaseopen in new window 출시 (v202310-1 (741))
      • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
      • v202309-1 release 부터 FDO (flexible deployment optionsopen in new window) 지원하며,consolidated_services_enabled 설정 여부에 따라 기존 replicated 설치 방식 사용 지원.
      • Known Issue 1. Azure DevOps VCS 연동 시 발생하는 Known Issue 에 대해 Azure DevOps Server 의 버전을 2020 Update 1.2 Patch 7 로 업그레이드 하여 해결
      • Known Issue 2. GCP 환경에서 consolidated_services_enabled 설정 활성화하고 설치 시 발생하는 Object Storage Issue 는 v202311-1 에서 개선 예정
    • Provider
      • AWS v5.23.1 주요 수정사항open in new window
        • Bug Fix
          • data-source/aws_lambda_function 에 대해 vpc_config.ipv6_allowed_for_dual_stack 설정 시 발생하는 오류 개선
      • Azure v3.78.0 주요 수정사항open in new window
        • 신규 resource 추가
          • azurerm_resource_management_private_link_association
        • 기능 개선
          • azurerm_redis_enterprise_cluster 에 대한 Japan East 리전 설정 지원
        • Bug Fix
          • azurerm_backup_policy_vm 에 대해 현재 시간정보 사용 관련 오류 개선
      • GCP v5.4.0 주요 수정사항open in new window
        • 신규 resource 추가
          • google_cloud_identity_group_lookup
          • google_network_connectivity_policy_based_route
          • google_pubsub_schema_iam_
        • 기능 개선
          • google_bigquery_connection 에 대해 cloud_spanner.use_serverless_analytics 설정 대신 cloud_spanner.use_data_boost 설정 지원
          • google_bigquery_connection 에 대해 cloud_spanner.database_role, cloud_spanner.use_data_boost, cloud_spanner.max_parallelism 설정 지원
          • google_bigquery_dataset.access 에 대해 iam_member 설정 지원
        • Bug Fix
          • google_bigquery_capacity_commitment 에 대해 잘못된 capacity_commitment_id 설정으로 인한 자원 재생성 오류 개선
          • cloudrunv2 에 대해 annotations 그리고 labels 관련 오류 개선
  • Vault
    • 1.15.1
      • 상세 Release Noteopen in new window
      • Secret Sync 관련 telemetry 추가
      • UI 개선
      • Audit Device 설정 관련 file 사용 시 SIGHUP 에서 발생하는 log file 재열기 오류 개선
      • AWS Auth Method 관련 Client Config 가 존재하지 않는 경우 발생하는 패닉 오류 개선
      • Transit Engine 관련 managed key 사용 시 sign/verify 과정 추가 key auto rotation 불가하도록 개선
      • DB Secret Engine 관련 Mongo DB 에 대해 admin 이 아닌 계정에 대한 root rotate 지원
  • Consul
    • 1.16.2
      • 상세 Release Noteopen in new window
      • Vault 와 연동하여 사용하는 인증서 관련 Leaf Cert 생성 시 발생하는 오류 개선
      • Snapshot 기반 복원 후 Envoy Endpoint 올바르게 생성되지 않는 이슈 해결
  • Nomad
    • 1.6.2
      • 상세 Release Noteopen in new window
      • Service Mesh 위해 Consul 연동 시 upstream 설정 관련 DestinationPeer, DestinationType, LocalBindSocketPath 등 설정 지원
      • jobspec 관련 여러개의 cron 정의를 위해 crons 지원
      • UI 개선
+ + + diff --git a/04-HashiCorp/08-Updates/98-2023/index.html b/04-HashiCorp/08-Updates/98-2023/index.html new file mode 100644 index 0000000000..84f0d36411 --- /dev/null +++ b/04-HashiCorp/08-Updates/98-2023/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 98 2023 | docmoa + + + + + +
본문으로 건너뛰기

98 2023

1분 미만

+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/2022-07.html b/04-HashiCorp/08-Updates/99-2022/2022-07.html new file mode 100644 index 0000000000..f601bafea4 --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/2022-07.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2022년 7월 | docmoa + + + + + +
본문으로 건너뛰기

2022년 7월

약 1 분HashicorpUpdateJuly

2022년 7월

Product 소개

  • HCP Boundary 출시 (Public Beta)

    • HCP Boundary 소개 Blogopen in new window
    • Hashicorp Korea Snapshotopen in new window
    • HCP Boundary 시작하기open in new window
    • Hashicorp 는 모든 솔루션에 대해 사용자가 직접 설치하는 설치형 을 비롯해 이와 동일한 경험을 기반으로 SaaS 형태의 Cloud 서비스를 제공하고 있습니다. 지난 6월 21일, HCP Boundary 의 Public Beta 가 공개되어 무료 제공 중입니다.
    • AWS Platform 에 One click 으로 Cluster 생성 및 이용 가능하며 간단한 Network Peering 과정을 거쳐 AWS Platform 에 구성된 HVN (Hashicorp Virtual Network) 및 Cluster 와 사용자의 AWS 환경을 연결하여 미리 구성한 서비스들을 연동합니다. (AWS 지원 Region 확장 및 타 Cloud Platform 지원 예정)
    • HCP 계정 생성 시, USD 50불이 기본 Credit 으로 제공되며 이를 활용하여 Boundary Public Beta 외에도 다양한 Vault, Consul 과 같은 HCP Service 을 약 1개월간 체험해보실 수 있습니다.
  • Hashicorp Developer Site 출시 (Public Beta)

    • Hashicorp Developer Site 소개 Blogopen in new window
    • Hashicorp Developer Siteopen in new window
    • Tutorial 과 Reference Architecture 정보가 learn.hashicorp.comopen in new window 을 비롯, 각 solution 별 website 에 파편화 되어 있어 Hashicorp Solution 을 보다 더 쉽게 이해하고 업무에 적용하는데에 어려움이 있었습니다. 새롭게 출시된 Hashicorp Developer Site 에서는 이러한 그동안 축적된 유용한 자료와 이를 테스트 해볼 수 있는 환경을 한 곳에 모아 통합 제공함으로써 보다 더 쉽게 Hashicorp Solution 을 경험할 수 있습니다.
    • Public Beta 기간에는 Hashicorp Solution 중 Vault 와 Waypoint 에 대해 이용 가능하고, 추후 모든 Solution 에 대해 제공 예정입니다.

Product Update

+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/2022-08.html b/04-HashiCorp/08-Updates/99-2022/2022-08.html new file mode 100644 index 0000000000..fb076a331f --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/2022-08.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2022년 8월 | docmoa + + + + + +
본문으로 건너뛰기

2022년 8월

1분 미만HashicorpUpdateAug

2022년 8월

Product 소개

Product Update

  • Terraform
    • CLI
      • 1.25 Releaseopen in new window
        • Minor Bug Fix
      • Enterprise Releaseopen in new window
        • 7월 Releaseopen in new window 출시 (v202206-1)
        • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202204-2)
        • Default 값 없는 변수 표시: VCS 연동 시 Default 값 없는 변수 표시 및 Default 값 설정 지
        • Workspace 최대개수 제한: ORG 당 생성 가능한 최대 Workspace 수 설정
        • exclude-tags 추가: Workspace 조회 시 특정 tag 가 붙은 Workspace 제외
        • API Rate Limit Logic 변경: 기존 IP 주소 기반 -> Token 유형 기반
    • Provider
  • Vault
  • Consul
    • 1.12.3 주요 수정사항open in new window
      • Envoy Proxy 의 최신 보안 Patch 에 대한 지원
      • HTTP Handler의 URL Decoding 오류 개선
      • GRPC 통신 관련 Memory Leak 오류 개선
      • GRPC 혹은 HTTP2 에 대한 Ingress Gateway 구성 시 발생하는 오류 개선
  • Nomad
+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/2022-09.html b/04-HashiCorp/08-Updates/99-2022/2022-09.html new file mode 100644 index 0000000000..d026266f44 --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/2022-09.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2022년 9월 | docmoa + + + + + +
본문으로 건너뛰기

2022년 9월

1분 미만HashicorpUpdateSep

2022년 9월

Product 소개

  • CDKTF (Cloud Development Kit for Terraform) General Available

    • Hashicorp Blogopen in new window
    • Python, Go 등 프로그래밍 언어 기반으로 Terraform 을 활용하실 수 있도록 지원하는 CDKTF 가 정식 출시 되었습니다. CDKTF를 사용하면 개발자는 익숙한 프로그래밍 언어에서 컨텍스트 전환 없이 코드로 인프라를 설정할 수 있으며, 애플리케이션 비즈니스 로직을 정의하는 데 사용하는 인프라 리소스를 프로비저닝하기 위해 동일한 도구와 구문을 사용할 수 있습니다. 팀은 익숙한 구문으로 협업하면서 Terraform 에코시스템의 기능을 계속 활용하고 확립된 Terraform 배포 파이프라인을 통해 인프라 구성을 배포할 수 있습니다.
    • 참고문서 1: CDK for Terraform v0.12: CHANGELOGopen in new window
    • 참고문서 2: CDKTF Overviewopen in new window
    • 참고문서 3: CDKTF Tutorialsopen in new window

Product Update

  • Terraform

    • CLI

      • 1.28 Releaseopen in new window

        • tolist, tomap, toset 등 type 변환 작업 시 변환 대상 type 유추로 인한 panic 이 발생하는 오류 개선
      • Enterprise Releaseopen in new window

        • 8월 Releaseopen in new window 출시 (v202208-3)

        • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)

        • VCS 기반 Workspace 생성 시 정의된 variable 에 대한 type 검증 및 오류 안내 기능 추가

        • Known Issue: TFE 내부 모듈 중 하나인 Postgres - ver 10 또는 11 에 대한 Migration 실패 오류 개선 (v202208-2)

        • Run Pipeline 에 대한 Metric 정보가 Prometheus 에 표시 되지 않는 이슈 개선

        • Module Registry Protocol endpoint 인 '/v1/modules/{namespace}/{name}/{provider}/versions' 에서 version 갯수가 많은 Module 처리시 발생하는 오류 개선

    • Provider

      • AWS v4.27.0 주요 수정사항open in new window
        • networkmanager 관련 resource 추가
        • firewall data source에 대한 capacity_usage_summary, firewall_status 등 argument 추가
        • lb_target_group resource 에 대한 ip_address_type argument 추가
        • aws_db_instance resource 에 대한 특정 argument 수정 시 발생하는 'InvalidParameterCombination: No modifications were requested' 오류 개선
        • aws_opsworks resource 에 대한 region 정보 및 tag 적용 관련 오류 개선
      • Azure v3.19.1 주요 수정사항open in new window
        • azurerm_dns resource 에 대해 resource ID parsing 시 발생하는 대소문자 관련 오류 개선
      • GCP v4.33.0 주요 수정사항open in new window
        • google_cloudfunctions2_function resource 추가
        • google_container_cluster 에 대해 authenticator_groups_config 지원
        • google_cloud_sql resource 에 대해 password_validation_policy argument 추가
        • google_dns_managed_zone data source 에 대해 managed_zone_id argument 추가
        • google_bigquery_data_transfer_config 에 대해 display_name 강제 변경 오류 개선
  • Vault

    • 1.11.2 주요 수정사항open in new window
      • Vaunt Agent 사용 시 keep_alives 비활성화 설정 지원
      • 잘못된 Server Side Consistent Token 에 대해 500 이 아닌 403 에러 출력
      • UI 에서 제공 되는 JWT auth method 관련 log 정보 개선
  • Consul

  • Nomad

    • 1.3.3 주요 수정사항open in new window
      • CSI Plugin 에 대한 stage_publish_base_dir 통해 특정 stage / publishing directory mount 지원
      • qemu 에 대한 socket file 명 축약 기능 지원
      • 만료된 일회성 token 의 timestamp 관련 오류 개선
      • UI 의 Evaluation tab 에서 일부 누락된 정보 출력 개선
      • task 가 사용하는 메모리가 0 으로 출력되는 오류 개선
+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/2022-10.html b/04-HashiCorp/08-Updates/99-2022/2022-10.html new file mode 100644 index 0000000000..03e77d01b2 --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/2022-10.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2022년 10월 | docmoa + + + + + +
본문으로 건너뛰기

2022년 10월

1분 미만HashicorpUpdateOct

2022년 10월

Product 소개

  • Nomad: Nomad Variables and Service Discovery

    • Hashicorp Blogopen in new window
    • Hashicorp Nomad는 Container 뿐만 아니라 Container 화 하기 어려운 Legacy Application 에 대해 배포하고 관리하는 데 사용되는 간단하고 유연한 오케스트레이터입니다. Nomad는 On-prem 및 Cloud 환경을 가리지 않고 작동합니다. Cloudflare, Roblox, Q2 및 Pandora와 같은 조직의 프로덕션에서 널리 채택되고 사용됩니다. 새롭게 출시된 HashiCorp Nomad 1.4 Beta Release 에서는 상태 확인을 통해 Service Discovery 지원을 강화하고 사용자가 구성 값을 저장할 수 있도록 하는 Nomad Variable 기능이 도입 되었습니다.

Product Update

  • Terraform
    • CLI
      • 1.30 Releaseopen in new window
        • Optional attributes for object type constraints: 변수 사용 시 Type 지정에 대한 'Optional' 지원
        • 'moved' block 기능개선: resource 에 대한 refactor 기능 개선 (예: aws_instance 를 instance module 로 refactoring)
      • Enterprise Releaseopen in new window
        • 9월 Releaseopen in new window 출시 (v202209-2)

        • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)

        • TFE 엔진 내 Vault 에 대한 정책 수정하여 간헐적으로 TFE 구동 시 발생하는 403 오류 해결

        • TFE 엔진 내 Data Migration Logic 에 대해 Postgres 11 이상에서만 지원

        • TF Code 변경에 대한 Test 등을 지원하는 예측 계획 (Speculative Plans)open in new window 기능 지원

    • Provider
      • AWS v4.32.0 주요 수정사항open in new window
        • resource/aws_eks_cluster 에 대한 outpost_config 설정 추가 (Outpost 에서 EKS 사용 지원)
        • resource/aws_ec2_managed_prefix_list 에 대해 MaxEntries and Entry 반영 오류 개선
      • Azure v3.24.0 주요 수정사항open in new window
        • azurerm_linux(windows)_virtual_machine 에 대한 patch_assessment_mode 설정 추가
        • azurerm_managed_disk 에 대한 PremiumV2_LRS type 지원
        • azurerm_private_endpoint 에 대한 custom_network_interface_name 설정 추가
        • azurerm_storage_account 에 대한 azure_files_identity_based_auth 정보 export 지원
      • GCP v4.38.0 주요 수정사항open in new window
        • appengine 에 대한 egress_setting 설정 추가
        • bigquery 에 대한 json_extension 설정 추가
        • compute 에 대한 json_custom_config 설정 추가
        • compute 에 대한 most_disruptive_allowed_action update 불가 오류 개선
        • storage 에 대한 lifecycle_rule.condition.age 설정 오류 개선
  • Vault
  • Consul
    • 1.13.2 주요 수정사항open in new window
      • 신규 consul cli command: peeringopen in new window
      • Metric 관련 Label 기능 추가
      • Envoy 관련 Outlier (이상감지) 에 대한 추가 Parameter 지원
      • Consul Connect 에 대해 ConnectCA CSR requests 의 URI Length Check 개선
      • auto-config JWT authorization 에 대한 input 값 검증 개선
      • Consul Connect (Service Mesh) 의 TLS 인증서 관련 오류 개선
  • Nomad
+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/2022-11.html b/04-HashiCorp/08-Updates/99-2022/2022-11.html new file mode 100644 index 0000000000..86786346e3 --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/2022-11.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2022년 11월 | docmoa + + + + + +
본문으로 건너뛰기

2022년 11월

약 2 분HashicorpUpdateNov

2022년 11월

Product 소개

Product Update

  • Terraform
    • Terraform Cloud 신규 기능 (현재 모두 Beta, Hashiconf Global 발표open in new window 참고)

      • Continous Validationopen in new window: Terraform 으로 Provisioning 한 (Day 0) Resource 에 대한 수동 변화를 감지하는 Drift Detectionopen in new window 와 더불어 장기적 관리 및 유지보수 관점에서 필요한 사전 (Precondition) 및 사후 (Postcondition) 조건을 기반으로 Resource 의 상태를 점검하고 관리하는 기능

      • No-Code Provisioningopen in new window: Terraform 에 대해 Code 작성과 같은 기본 지식 또는 Module 과 같은 고급 지식에 대한 이해 없이 최소한의 변수 정보 입력만으로 Terraform 기반의 Workspace 생성 부터 Resource Provisioning 까지 사용할 수 있게 지원하는 Self-Service 특화 기능

      • OPA (Open Policy Agent)open in new window 기반 정책 관리: Rego 정책 언어 기반 OPA 릴 지원하여 기존에 OPA 기반 표준 정책 수립한 사용자도 손쉽게 Terraform Cloud 에 Import 하여 정책 기반 Resource Provisioning 을 지원하는 기능

    • CLI

      • 1.33 Releaseopen in new window
        • 이미 삭제됐으나, Code 상에 존재하는 Resource 삭제 시 발생하는 오류 개선
      • Enterprise Releaseopen in new window
        • 10월 Releaseopen in new window 출시 (v202210-1)

        • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)

        • PostgresSQL 버전 10 지원종료: TFE 에 대해 External PostgresSQL 사용하는 경우 최소 버전 12 이상으로 Upgrade 필요

          • 2023년 2월 release (v202302-1) 을 기점으로 PostgresSQL 버전 11 지원 종료 예정
        • 구버전 OS 지원종료: 2023년 2월 release (v202302-1) 을 기점으로 아래 OS 목록에 대해 지원 종료

          • Debian 8, 9

          • Ubuntu 14.04, 16.04

          • Amazon Linux 2014.03, 2014.09, 2015.03, 2015.09, 2016.03, 2016.09, 2017.03, 2017.09, 2018.03

    • Provider

      • AWS v4.36.0 주요 수정사항open in new window
        • aws_lightsail 관련 resource 추가
        • aws_route53_zone 에 대해 primary_name_server 설정 추가
        • aws_resourcegroups_group 에 대해 configuration 설정 추가
        • aws_sqs_queue 관련 오류 개선
      • Azure v3.28.0 주요 수정사항open in new window
        • azurerm_sentinel_data_connector 관련 resource 추가
        • azurerm_linux(windows)_web_app, azurerm_linux(windows)function_app 에 대해 certificate 관련 설정 추가
        • azurerm_storage_account 관련 account_tier 설정 추가
        • azurerm_linux(windows)_web_app, azurerm_linux(windows)function_app 에 대해 오류 개선
      • GCP v4.41.0 주요 수정사항open in new window
        • google_sql_user.sql_server_user_details 에 대해 read-only 만 가능하도록 수정
        • google_bigquery_table 에 대해 avro_options 설정 추가
        • google_container_node_pool 에 대해 node_config.0.guest_accelerator.0.gpu_sharing_config 설정 추가
        • google_filestore_instance 이 여러개 연속 생성 되도록 강제하는 조건 삭제
  • Vault
  • Consul
    • 1.13.3 주요 수정사항open in new window
      • Ingress Gateway (Service Mesh) 에 대한 Upstream Max Connection 설정 개선
      • Terminating Gateway 및 Mesh Gateway (Service Mesh) 에 대한 TCP Keepalives 설정 추가
      • Agent Cache 관련 goroutine leak 발생 bug 수정
      • Snapshot Agent 관련 Session 조회 불가로 인한 Panic 발생 bug 수정
    • 1.14 Betaopen in new window (Hashiconf Global Announcement)
      • Consul Client on K8S 에 대한 구조변화: Daemonset 에서 Sidecar 방식으로 변경
      • Cluster Peering 기반 Service Failover 포함 Mesh Traffic Management 기능 개선
      • Windows 에 대한 Service Mesh 지원
      • AWS Lambda 에 대한 Service Mesh 지원
  • Nomad
+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/2022-12.html b/04-HashiCorp/08-Updates/99-2022/2022-12.html new file mode 100644 index 0000000000..369c692b00 --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/2022-12.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2022년 12월 | docmoa + + + + + +
본문으로 건너뛰기

2022년 12월

1분 미만HashicorpUpdateDec

2022년 12월

Product 소개

  • Terraform Run Tasks in Public Registry

    • Hashicorp Blogopen in new window
    • 활발하게 활용되고 있는 Terraform Run Tasks (3rd party 연동 및 통합) 기능이 강화되었습니다. 이제 Terraform Public Registryopen in new window 에서 Run Tasks 를 검색하여 필요한 3rd party service 와 연동하여 자원 관리에 필요한 Cost Management, Policy Compliance 와 같은 다양한 기능들을 적용하여 보다 효율적인 자원 관리가 가능합니다.

Product Update

  • Terraform

    • CLI
      • 1.35 Releaseopen in new window
        • Size 확인 불가한 Temp File 에 대한 처리 오류 개선
        • Empty 및 Null Collection 에 대한 처리 오류 개선
        • Optional object 에 대핸 처리 오류 개선
    • Enterprise Releaseopen in new window
      • 11월 Releaseopen in new window 출시 (v202211-1)
      • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)
      • 2023년 2월 Release (v202302-1) 을 기점으로 구버전 OS 및 Postrgres DB 에 대한 지원 종료
        • 대상 OS

          • Debian 8, 9

          • Ubuntu 14.04, 16.04

          • Amazon Linux 2014.03, 2014.09, 2015.03, 2015.09, 2016.03, 2016.09, 2017.03, 2017.09, 2018.03

        • 대상 Postgres

          • Postgres 11
          • TFE 에 대해 External PostgresSQL 사용하는 경우 최소 버전 12 이상으로 Upgrade 필요 (Release 659 부터 적용)
      • Workspaces API 관련 workspace 삭제 기능을 safe / force 로 세분화
    • Provider
      • AWS v4.41.0 주요 수정사항open in new window
        • aws_rds_clusters resource 추가
        • aws_nat_gateway 에 대해 private_ip 설정 지원
        • aws_resourcegroups_group 에 대해 configuration.parameters 설정을 optional 로 변경
      • Azure v3.33.0 주요 수정사항open in new window
        • azurerm_mssql_managed_instance_transparent_data_encryption resource 추가
        • azurerm_mssql_managed_instance 에 대해 customer_managed_key_id 와 user-assigned identity 설정 지원
        • azurerm_mysql_flexible_server 에 대해 iops 설정 오류 개선
        • azurerm_storage_account 에 대해 multichannel checking 과정 관련 오류 개선
      • GCP v4.41.1 주요 수정사항open in new window
        • google_compute_instance_template 에 대해 advanced_machine_features 설정으로 인한 오류 개선
  • Vault

    • 1.12.1 주요 수정사항open in new window
      • DB Secret Enging - Snowflake 에 대해 병렬 요청 허용
      • SDK/LDAP 에 대해 group filter 사용 시 paging 지원
      • MFA (Multi Factor Authentication) 수행 시 발생 가능한 namespace 관련 오류 개선
      • Vault Agent 가 Certificate Template 렌더링 하는 과정에서 발생 가능한 오류 개선
  • Consul

    • 1.14open in new window

      • 상세 CHANGELOGopen in new window

      • Consul Client on K8S 에 대한 구조변화: Daemonset 에서 Sidecar 방식으로 변경

      • Cluster Peering 기반 Service Failover 포함 Mesh Traffic Management 기능 개선

      • Windows 에 대한 Service Mesh 지원

      • AWS Lambda 에 대한 Service Mesh 지원

  • Nomad

    • 1.4.3 주요 수정사항open in new window
      • 동일 Job 에 대해 여러 evaluation 이 pending 되어 있는 경우, 가장 최근 성공한 evaluation 에 대해서만 반영
      • File Upload, Consul Upstream 설정 등 UI 개선
      • Consul 연동 시 gRPC fingerprint 관련 발생 오류 개선 (Consul 1.14 연동부터 적용)
+ + + diff --git a/04-HashiCorp/08-Updates/99-2022/index.html b/04-HashiCorp/08-Updates/99-2022/index.html new file mode 100644 index 0000000000..0c9ef05c1c --- /dev/null +++ b/04-HashiCorp/08-Updates/99-2022/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 99 2022 | docmoa + + + + + +
본문으로 건너뛰기

99 2022

1분 미만

+ + + diff --git a/04-HashiCorp/08-Updates/index.html b/04-HashiCorp/08-Updates/index.html new file mode 100644 index 0000000000..de79d3ea82 --- /dev/null +++ b/04-HashiCorp/08-Updates/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 08 Updates | docmoa + + + + + +
본문으로 건너뛰기

08 Updates

1분 미만

+ + + diff --git a/04-HashiCorp/index.html b/04-HashiCorp/index.html new file mode 100644 index 0000000000..6ba70b7ddb --- /dev/null +++ b/04-HashiCorp/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + HashiCorp | docmoa + + + + + +
본문으로 건너뛰기

HashiCorp

약 1 분HashiCorp

HashiCorp

Recent pages

Packer

다양한 플랫폼에 대한 VM, 컨테이너 이미지 생성 자동화 도구

Vagrant

로컬 개발환경을 관리하는 프로비저닝 자동화 도구

Terraform

클라우드, 온프레미스, 플랫폼 서비스의 리소스 프로비저닝과 자동화

Consul

서비스 디스커버리와 서비스 메시로 네트워크 자동화

Boundary

인증/인가 기반으로 서버와 서비스에 대한 접근관리

Vault

민감 정보의 관리와 접근에 중앙화된 관리 서비스

Nomad

애플리케이션 배포와 실행을 위한 오케스트레이터

Waypoint

단일 구성으로 컨테이너 환경에 애플리케이션 빌드 및 배포

Etc.

Mac에서 한번에 업데이트 받기 예제
  • BIN_DIR로 지정된 /Users/my/Tools/bins/는 PATH에 적용된 위치
#!/bin/bash
+
+echo "<<<<<<< CHECK POINT HASHICORP RELEASE >>>>>>>"
+export RELEASE_URL="https://releases.hashicorp.com/"
+export DOWNLOAD_DIR="/tmp/hashistack-zip/"
+export BIN_DIR="/Users/my/Tools/bins/"
+
+if [ ! -d ${DOWNLOAD_DIR} ]; then
+    mkdir -p ${DOWNLOAD_DIR}
+fi
+
+cd ${DOWNLOAD_DIR}
+rm -rf ${DOWNLOAD_DIR}/*
+
+UPDATE_LIST=""
+
+for SOLUTION in "terraform" "consul" "vault" "nomad" "packer" "consul-terraform-sync" "waypoint" "boundary"; do
+    echo "Check - ${SOLUTION}"
+    TAG=$(curl -fsS https://api.github.com/repos/hashicorp/${SOLUTION}/releases \
+    | jq -re '.[] | select(.prerelease != true) | .tag_name' \
+    | sed 's/^v\(.*\)$/\1/g' \
+    | sort -V \
+    | tail -1)
+
+    export CURRENT_VERSION="0.0.0"
+    if [ -f "${BIN_DIR}/${SOLUTION}" ]; then
+        if [ ${SOLUTION} = "consul-terraform-sync" ]; then
+            CURRENT_VERSION=$(${SOLUTION} --version | awk '{print $2}' | head -1 | sed 's/v//' | sed 's/+ent//')
+        elif [ ${SOLUTION} = "boundary" ]; then
+            CURRENT_VERSION=$(${SOLUTION} version | head -4 | tail -1 | awk '{print $3}')
+        else
+            CURRENT_VERSION=$(${SOLUTION} version | awk '{print $2}' | head -1 | sed 's/v//')
+        fi
+    fi
+
+    if [ $TAG != $CURRENT_VERSION ]; then
+        if [ ${SOLUTION} = "consul-terraform-sync" ]; then
+            TAG=${TAG}+ent
+        fi
+        echo ">>>> ${SOLUTION} update ${CURRENT_VERSION} --> ${TAG}"
+        ZIP="${SOLUTION}_${TAG}_darwin_amd64.zip"
+        DOWNLOAD_URL="${RELEASE_URL}${SOLUTION}/${TAG}/${ZIP}"
+        wget -O "${DOWNLOAD_DIR}${ZIP}" ${DOWNLOAD_URL}
+        unzip -o ${DOWNLOAD_DIR}${ZIP} -d $BIN_DIR
+        rm -rf ${DOWNLOAD_DIR}${ZIP} 
+        UPDATE_LIST="${UPDATE_LIST} ${SOLUTION}_${CURRENT_VERSION}\t-->>\t${SOLUTION}_${TAG}"
+    else
+        UPDATE_LIST="${UPDATE_LIST} ${SOLUTION}_${CURRENT_VERSION}"
+    fi
+done
+
+if [ "$(ls -A ${DOWNLOAD_DIR})" ]; then
+    mv ${DOWNLOAD_DIR}* /Users/gs/Tools/bins/
+fi
+
+echo -e "\n==== HASHISTACK VERSION ===="
+for list in $UPDATE_LIST
+do
+    echo -e $list
+done
+
+
+ + + diff --git a/05-Software/Jenkins/index.html b/05-Software/Jenkins/index.html new file mode 100644 index 0000000000..09e87705a9 --- /dev/null +++ b/05-Software/Jenkins/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Jenkins | docmoa + + + + + +
본문으로 건너뛰기

Jenkins

1분 미만

+ + + diff --git a/05-Software/Jenkins/pipeline101/00-introduction.html b/05-Software/Jenkins/pipeline101/00-introduction.html new file mode 100644 index 0000000000..b38f021219 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/00-introduction.html @@ -0,0 +1,65 @@ + + + + + + + + + + Pipeline on Jenkins 101 : Introduction | docmoa + + + + + +
본문으로 건너뛰기

Pipeline on Jenkins 101 : Introduction

1분 미만cicdjenkins

Pipeline on Jenkins 101 : Introduction

Update at 31 Jul, 2019

Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

CategoryNameVersion
VMVirtualBox6.0.10
OSRed Hat Enterprise Linux8.0.0
JDKRed Hat OpenJDK1.8.222
JenkinsJenkins rpm2.176.2

Jenkins 실행 및 구성

Jenkins를 실행 및 구성하기위한 OS와 JDK가 준비되었다는 가정 하에 진행합니다. 필요 JDK 버전 정보는 다음과 같습니다.

  • 2.164 (2019-02) and newer: Java 8 or Java 11
  • 2.54 (2017-04) and newer: Java 8
  • 1.612 (2015-05) and newer: Java 7

필요 JDK를 설치합니다.

$ subscription-manager repos --enable=rhel-8-for-x86_64-baseos-rpms --enable=rhel-8-for-x86_64-appstream-rpms
+
+### Java JDK 8 ###
+$ yum -y install java-1.8.0-openjdk-devel
+
+### Check JDK version ###
+$ java -version
+openjdk version "1.8.0_222"
+OpenJDK Runtime Environment (build 1.8.0_222-b10)
+OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
+

Red Hatsu/Fedora/CentOS 환경에서의 Jenkins 다운로드 및 실행은 다음의 과정을 수행합니다.

참고 url : https://pkg.jenkins.io/redhat-stable/open in new window

  • repository를 등록합니다.

    $ sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
    +$ sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
    +
  • 작성일 기준 LTS 버전인 2.176.2버전을 설치합니다.

    $ yum -y install jenkins
    +

패키지로 설치된 Jenkins의 설정파일은 /etc/sysconfig/jenkins에 있습니다. 해당 파일에서 실행시 활성화되는 포트 같은 설정을 변경할 수 있습니다.

## Type:        integer(0:65535)
+## Default:     8080
+## ServiceRestart: jenkins
+#
+# Port Jenkins is listening on.
+# Set to -1 to disable
+#
+JENKINS_PORT="8080"
+

외부 접속을 위해 Jenkins에서 사용할 포트를 방화벽에서 열어줍니다.

$ firewall-cmd --permanent --add-port=8080/tcp
+$ firewall-cmd --reload
+

서비스를 부팅시 실행하도록 활성화하고 Jenkins를 시작합니다.

$ systemctl enable jenkins 
+$ systemctl start jenkins
+

실행 후 브라우저로 접속하면 Jenkins가 준비중입니다. 준비가 끝나면 Unlock Jenkins 페이지가 나오고 /var/lib/jenkins/secrets/initialAdminPassword의 값을 입력하는 과정을 설명합니다. 해당 파일에 있는 토큰 복사하여 붙여넣습니다.

이후 과정은 Install suggested plugins를 클릭하여 기본 플러그인을 설치하여 진행합니다. 경우에 따라 Select plugins to install을 선택하여 플러그인을 지정하여 설치할 수 있습니다.

플러그인 설치 과정을 선택하여 진행하면 Getting Started 화면으로 전환되어 플러그인 설치가 진행됩니다.

설치 후 기본 Admin User 를 생성하고, 접속 Url을 확인 후 설치과정을 종료합니다.

GitHub 계정생성

진행되는 실습에서는 일부 GitHub를 SCM으로 연동합니다. 원활한 진행을 위해 GitHub계정을 생성해주세요. 또는 별개의 Git 서버를 구축하여 사용할 수도 있습니다.

Jenkins Theme (Optional)

Jenkins는 간단히 테마와 회사 CI를 적용할 수 있는 플러그인이 제공됩니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.

  • 설치 가능 탭을 클릭하고 상단의 검색에 theme를 입력하면 Login ThemeSimple Theme를 확인 할 수 있습니다. 둘 모두 설치합니다.

  • 로그아웃을 하면 로그인 페이지가 변경된 것을 확인 할 수 있습니다.

기본 Jenkins 테마를 변경하기 위해서는 다음의 과정을 수행합니다.

  • http://afonsof.com/jenkins-material-theme/open in new window 에 접속합니다.

  • Build your own theme with a company logo! 에서 색상과 로고를 업로드 합니다.

  • DOWNLOAD YOUR THEME!버튼을 클릭하면 CSS파일이 다운됩니다.

  • Jenkins 관리로 이동하여 시스템 설정를 클릭합니다.

  • Theme항목의 Theme elements의 드롭다운 항목에서 Extra CSS를 클릭하고 앞서 다운받은 CSS파일의 내용을 붙여넣고 설정을 저장하면 적용된 테마를 확인할 수 있습니다.

+ + + diff --git a/05-Software/Jenkins/pipeline101/01-cicd.html b/05-Software/Jenkins/pipeline101/01-cicd.html new file mode 100644 index 0000000000..4bd0d4a806 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/01-cicd.html @@ -0,0 +1,40 @@ + + + + + + + + + + 1. CI/CD | docmoa + + + + + +
본문으로 건너뛰기

1. CI/CD

1분 미만cicdjenkins

1. CI/CD

1.1 CI/CD Concept Definitions

  • Continuous integration
  • Continuous delivery
  • Continuous deployment
  • Source control management (SCM)

1.2 Delivery vs Deployment

  • Continuous Delivery requires user intervention
    • When? : Stage to Production

1.3 Jenkins for CI/CD

  • Open-source governance and community
  • Stability
  • Extensible
  • Visibility
  • Pipelines
+ + + diff --git a/05-Software/Jenkins/pipeline101/02-jobs.html b/05-Software/Jenkins/pipeline101/02-jobs.html new file mode 100644 index 0000000000..226c5da369 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/02-jobs.html @@ -0,0 +1,179 @@ + + + + + + + + + + 2. Jobs | docmoa + + + + + +
본문으로 건너뛰기

2. Jobs

약 2 분cicdjenkins

2. Jobs

프로젝트는 Job의 일부 입니다. 즉, 모든 프로젝트가 Job이지만 모든 Job이 프로젝트는 아닙니다. Job의 구조는 다음과 같습니다.

FreeStyleProejct, MatrixProject, ExternalJob만 New job에 표시됩니다.

2.1 New pipeline

Step 1에서는 stage없이 기본 Pipeline을 실행하여 수행 테스트를 합니다.

  1. Jenkins 로그인

  2. 좌측 새로운 Item 클릭

  3. Enter an item name에 Job 이름 설정 (e.g. 2.Jobs)

  4. Pipeline 선택 후 OK 버튼 클릭

  5. Pipeline 항목 오른 쪽 Try sample Pipelie...클릭하여 Hello world 클릭 후 저장

    node {
    +   echo 'Hello World'
    +}
    +
  6. 좌측 Build now클릭

  7. 좌측 Build History의 최근 빌드된 항목(e.g. #1) 우측에 마우스를 가져가면 dropdown 버튼이 생깁니다. 해당 버튼을 클릭하여 Console Output 클릭

  8. 수행된 echo 동작 출력을 확인합니다.

    Started by user GyuSeok.Lee
    +Running in Durability level: MAX_SURVIVABILITY
    +[Pipeline] Start of Pipeline
    +[Pipeline] node
    +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
    +[Pipeline] {
    +[Pipeline] echo
    +Hello World
    +[Pipeline] }
    +[Pipeline] // node
    +[Pipeline] End of Pipeline
    +Finished: SUCCESS
    +

2.2 New pipeline

Step 2에서는 stage 를 구성하여 실행합니다.

  1. 기존 생성한 Job 클릭 (e.g. 02-02.Jobs)

  2. 좌측 구성을 클릭하여 Pipeline 스크립트를수정합니다.

    pipeline{
    +    agent any
    +    stages {
    +        stage("Hello") {
    +            steps {
    +                echo 'Hello World'
    +            }
    +        }
    +    }
    +}
    +
  3. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

  4. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

    1563942302074
    1563942302074
  5. 수행된 빌드의 Console Output을 확인하면 앞서 Step 1에서는 없던 stage 항목이 추가되어 수행됨을 확인 할 수 있습니다.

    Started by user GyuSeok.Lee
    +Running in Durability level: MAX_SURVIVABILITY
    +[Pipeline] Start of Pipeline
    +[Pipeline] node
    +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
    +[Pipeline] {
    +[Pipeline] stage
    +[Pipeline] { (Hello)
    +[Pipeline] echo
    +Hello World
    +[Pipeline] }
    +[Pipeline] // stage
    +[Pipeline] }
    +[Pipeline] // node
    +[Pipeline] End of Pipeline
    +Finished: SUCCESS
    +

2.3 Parameterizing a job

Pipeline 내에서 사용되는 매개변수 정의를 확인해 봅니다. Pipeline 스크립트는 다음과 같습니다.

pipeline {
+    agent any
+    parameters {
+        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
+    }
+    stages {
+        stage('Example') {
+            steps {
+                echo "${params.Greeting} World!"
+            }
+        }
+    }
+}
+

parameters항목내에 매개변수의 데이터 유형(e.g. string)을 정의합니다. name은 값을 담고있는 변수이고 defaultValue의 값을 반환합니다. Pipeline에 정의된 parametersparams내에 정의 되므로 ${params.매개변수이름}과 같은 형태로 호출 됩니다.

저장 후 다시 구성을 확인하면 이 빌드는 매개변수가 있습니다가 활성화 되고 내부에 추가된 매개변수 항목을 확인 할 수 있습니다.

1563944944350
1563944944350

이렇게 저장된 Pipeline Job은 매개변수를 외부로부터 받을 수 있습니다. 따라서 좌측의 기존 Build Nowbuild with Parameters로 변경되었고, 이를 클릭하면 Greeting을 정의할 수 있는 UI가 나타납니다. 해당 매개변수를 재정의 하여 빌드를 수행할 수 있습니다.

1563944733249
1563944733249
1563944765637
1563944765637

2.4 Creating multiple steps for a job

다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-04.MultiStep)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+                sh 'echo "Hello World"'
+                sh '''
+                    echo "Multiline shell steps works too"
+                    ls -lah
+                '''
+            }
+        }
+    }
+}
+

'''은 스크립트 정의 시 여러줄을 입력할 수 있도록 묶어주는 역할을 합니다. 해당 스크립트에서는 sh로 구분된 스크립트 명령줄이 두번 수행됩니다.

1563945323777
1563945323777

실행되는 여러 스크립트의 수행을 stage로 구분하기위해 기존 Pipeline 스크립트를 다음과 같이 수정합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build-1') {
+            steps {
+                sh 'echo "Hello World"'
+            }
+        }
+        stage('Build-2') {
+            steps {
+                sh '''
+                    echo "Multiline shell steps works too"
+                    ls -lah
+                '''
+            }
+        }
+    }
+}
+

stage를 구분하였기 때문에 각 실행되는 sh 스크립트는 각 스테이지에서 한번씩 수행되며, 이는 빌드의 결과로 나타납니다.

1563945539114
1563945539114

2.5 Adding scripts as a job step

Pipeline의 step을 추가하여 결과를 확인하는 과정을 설명합니다. 피보나치 수열을 수행하는 쉘 스크립트를 시간제한을 두어 수행하고 그 결과를 확인합니다.

Jenkins가 설치된 서버에 [피보나치 수열]([https://namu.wiki/w/피보나치 수열open in new window](https://namu.wiki/w/피보나치open in new window 수열))을 수행하는 스크립트를 작성합니다. Sleep이 있기 때문에 일정 시간 이상 소요 됩니다.

$ mkdir -p /var/jenkins_home/scripts
+$ cd /var/jenkins_home/scripts
+$ vi ./fibonacci.sh
+#!/bin/bash
+N=${1:-10}
+
+a=0
+b=1
+
+echo "The Fibonacci series is : "
+
+for (( i=0; i<N; i++ ))
+do
+    echo "$a"
+    sleep 2
+    fn=$((a + b))
+    a=$b
+    b=$fn
+done
+# End of for loop
+
+$ chown -R jenkins /var/jenkins_home/
+$ chmod +x /var/jenkins_home/scripts/fibonacci.sh
+

다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-05.AddingStep)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages {
+        stage('Deploy') {
+            steps {
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh '/var/jenkins_home/scripts/fibonacci.sh 5'
+                }
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh '/var/jenkins_home/scripts/fibonacci.sh 32'
+                }
+            }
+        }
+    }
+}
+

steps에 스크립트를 timeout이 감싸고 있으며, 각 스크립트의 제한시간은 1분입니다. 빌드를 수행하면 최종적으로는 aborted, 즉 중단됨 상태가 되는데 그 이유는 빌드 기록에서 해당 빌드를 클릭하면 확인 가능합니다.

  • Build History에서 최신 빌드를 클릭합니다.

  • 좌측 Pipeline Steps를 클릭하면 Pipeline 수행 스텝을 확인할 수 있습니다.

  • 첫번째로 나타나는 /var/jenkins_home/scripts/fibonacci.sh 5 를 수행하는 Shell Script의 콘솔창 버튼을 클릭하면 잘 수행되었음을 확인 할 수 있습니다.

  • 두번째로 나타나는 /var/jenkins_home/scripts/fibonacci.sh 32 를 수행하는 Shell Script의 콘솔창 버튼을 클릭하면 다음과 같이 중도에 프로세스를 중지한 것을 확인 할 수 있습니다.

    + /var/jenkins_home/scripts/fibonacci.sh 32
    +The Fibonacci series is : 
    +0
    +1
    +1
    +2
    +3
    +...
    +317811
    +514229
    +Sending interrupt signal to process
    +/var/jenkins_home/scripts/fibonacci.sh: line 16: 13543 Terminated              sleep 2
    +832040
    +/var/lib/jenkins/workspace/02-05.AddingStep@tmp/durable-e44bb232/script.sh: line 1: 13109 Terminated              /var/jenkins_home/scripts/fibonacci.sh 32
    +script returned exit code 143
    +
+ + + diff --git a/05-Software/Jenkins/pipeline101/03-builds.html b/05-Software/Jenkins/pipeline101/03-builds.html new file mode 100644 index 0000000000..0b5788dd89 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/03-builds.html @@ -0,0 +1,59 @@ + + + + + + + + + + 3. Builds | docmoa + + + + + +
본문으로 건너뛰기

3. Builds

약 2 분cicdjenkins

3. Builds

3.1 Tracking build state

Pipeline이 수행되는 동작을 추적하는 과정을 확인합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-01.TrackingBuildState)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages {
+        stage('Deploy') {
+            steps {
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh 'for n in `seq 1 10`; do echo $n; sleep 1; done'
+                }
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh 'for n in `seq 1 50`; do echo $n; sleep 1; done'
+                }
+            }
+        }
+    }
+}
+

Build Now를 클릭하여 빌드를 수행합니다. 그러면, 좌측의 Build History에 새로운 기록이 생성되면서 동작 중인것을 확인 할 수 있습니다.

첫번째 방법은 앞서 확인한 Pipeline Steps를 확인하는 것입니다. 다시한번 확인하는 방법을 설명합니다.

  • Build History에서 최신 빌드를 클릭합니다.
  • 좌측 Pipeline Steps를 클릭하면 Pipeline 수행 스텝을 확인할 수 있습니다.

현재 수행중인 Pipeline이 어떤 단계가 수행중인지 각 스탭별로 확인할 수 있고 상태를 확인할 수 있습니다.

1563948810815
1563948810815

두번째 방법은 출력되는 콘솔 로그를 확인하는 것입니다. Jenkins에서 빌드를 수행하면 빌드 수행 스크립트가 내부에 임시적으로 생성되어 작업을 실행합니다. 이때 발생되는 로그는 Console Output을 통해 거의 실시간으로 동작을 확인 할 수 있습니다.

  • Build History에서 최신 빌드에 마우스 포인터를 가져가면 우측에 드롭박스가 생깁니다. 또는 해당 히스토리를 클릭합니다.
  • 드롭 박스의 Console Output나 클릭된 빌드 히스토리 상태에서 Console Output를 클릭하면 수행중인 콘솔상의 출력을 확인합니다.
1563948863834
1563948863834

마지막으로는 Pipeline을 위한 UI인 BlueOcean 플러그인을 활용하는 방법입니다. Blue Ocean은 Pipeline에 알맞은 UI를 제공하며 수행 단계와 각 단게별 결과를 쉽게 확인할 수 있습니다.

  • Jenkins 관리에서 플러그인 관리를 선택합니다.
  • 설치 가능 탭에서 Blue Ocean을 선택하여 재시작 없이 설치를 클릭 합니다.
  • Blue Ocean플러그인만 선택하여 설치하더라도 관련 플러그인들이 함께 설치 진행됩니다.
  • 설치 완료되면 좌측 메뉴에서 Blue Ocean항목을 확인 할 수 있습니다.
1563949823939
1563949823939

3.2 Polling SCM for build triggering

Git SCM을 기반으로 Pipeline을 설정하는 과정을 설명합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-02.PollingSCMforBuildTriggering)

해당 과정을 수행하기 위해서는 다음의 구성이 필요합니다.

  • Jenkins가 구성된 호스트에 git 설치

    $ yum -y install git
    +
  • Jenkins 설정

    • Jenkins 관리Global Tool Configuration클릭
    • Git 항목에 Path to Git executable 칸에 Path 입력 (e.g. /usr/bin/git)

Pipeline을 다음과 같이 설정합니다.

추가로 빌드 트리거를 위한 설정을 합니다.

  • Build TriggersPoll SCM 활성화

  • Schedule 등록

    # min hour day month day_of_week
    +* * * * *
    +# will run every minute on the minute
    +

Polling으로 인한 빌드 트리거가 동작하면 좌측 메뉴의 Polling Log에서 상태 확인이 가능합니다.

1563958295010
1563958295010

1분마다 확인 하도록 되어있기 때문에 다시 Polling을 시도하지만 변경사항이 없는 경우에는 Polling Log에 No changes 메시지가 나타나고 빌드는 수행되지 않습니다.

1563958396611
1563958396611

3.3 Connecting Jenkins to GitHub

GitHub를 통한 CI 과정을 설명합니다. WebHook의 설정과 Jenkins에 관련 설정은 어떻게 하는지 알아봅니다.

Jenkins에서 접속가능하도록 GitHub에서 Token을 생성합니다.

  • github.comopen in new window에 접속하여 로그인합니다.

  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-integration) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

    1564115701058
    1564115701058

우선 Jenkins에 Git연동을 위한 설정을 추가합니다.

  • Jenkins 관리에서 시스템 설정을 클릭합니다.
  • GitHub 항목의 GitHub ServersAdd GitHub Server > GitHub Server를 선택합니다.
  • 항목의 입력정보는 다음과 같습니다.
    • Name : 설정이름을 입력합니다. (e.g. github)
    • API URL : https://api.github.comopen in new window
    • Credentials : ADD트롭박스를 선택합니다.
      • 기본 Credendial을 선택합니다. (e.g. Jenkins)
      • Kind 항목을 Secret text로 선택합니다.
        • Scope : Global(Jenkins, nodes, Items, all child items, etc)
        • Secret : GitHub에서 복사한 Token 값을 입력합니다.
        • ID : Credential ID를 입력합니다. (e.g. jenkins-integration)
        • ADD 버튼 클릭하여 새로운 Credendial을 추가합니다.
      • 다시 시스템 설정화면으로 나오면 Credentials의 -none-드롭박스에 추가한 Credential을 선택합니다.
    • TEST CONNECTION버튼을 클릭하여 정상적으로 연결이 되는지 확인합니다.
      • 정상적으로 연결되면 Credentials verified for user Great-Stone, rate limit: 4998 와같은 메시지가 출력됩니다.
    • Manage hook 를 활성화 합니다.
  • 시스템 설정을 저장합니다.

3.4 Webhook build triggering

git repo의 Webhook 을 통한 빌드를 수행합니다. GitHub에 다음과 같이 설정합니다.

  • https://github.com/Great-Stone/jenkins-gitopen in new windowfork합니다.

    1564122799631
    1564122799631
  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-webhook) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

Webhook을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-04.WebhookBuild Triggering)

설정은 다음과 같이 수행합니다.

  1. Pipeline 설정의 Definition의 드롭다운을 선택하여 Pipeline script from SCM을 선택합니다.

  2. SCM항목은 Git을 선택하고 하위 필드를 다음과 같이 정의합니다.

    • Repositories :

      • Repository URL을 입력하는데, GitHub에서 git url을 얻기위해서는 웹브라우저에서 해당 repository로 이동하여 Clone or download 버튼을 클릭하여 Url을 복사하여 붙여넣습니다.

        1564365395583
        1564365395583
      • Credentials : ADD트롭박스를 선택합니다.

        • 기본 Credendial을 선택합니다. (e.g. Jenkins)
        • Kind 항목을 Username with password로 선택합니다.
          • Scope : Global(Jenkins, nodes, Items, all child items, etc)
          • Username : GitHub의 사용자 아이디를 입력합니다.
          • Secret : GitHub사용자 패스워드를 입력합니다.
          • ID : Credential ID를 입력합니다. (e.g. jenkins-webhook)
          • ADD 버튼 클릭하여 새로운 Credendial을 추가합니다.
        • 다시 시스템 설정화면으로 나오면 Credentials의 -none-드롭박스에 추가한 Credential을 선택합니다.
    • Script Path : Pipeline 스크립트가 작성된 파일 패스를 지정합니다. 예제 소스에서는 root 위치에 Jenkinsfile로 생성되어있으므로 해당 칸에는 Jenkinsfile이라고 입력 합니다.

저장 후 좌측 메뉴의 Build Now를 클릭하면 SCM에서 소스를 받고 Pipeline을 지정한 스크립트로 수행하는 것을 확인 할 수 있습니다.

+ + + diff --git a/05-Software/Jenkins/pipeline101/04-agent.html b/05-Software/Jenkins/pipeline101/04-agent.html new file mode 100644 index 0000000000..bb194ac48f --- /dev/null +++ b/05-Software/Jenkins/pipeline101/04-agent.html @@ -0,0 +1,138 @@ + + + + + + + + + + 4. Agents and Distributing Builds | docmoa + + + + + +
본문으로 건너뛰기

4. Agents and Distributing Builds

약 2 분cicdjenkins

4. Agents and Distributing Builds

빌드를 수행하기 위한 Worker로 다중 Jenkins를 컨트롤 할 수 있습니다. 이때 명령을 수행하는 Jenkins는 Master, 빌드를 수행하는 Jenkins는 Worker로 구분합니다. 여기서는 Worker의 연결을 원격 호스트의 Jenkins를 SSH를 통해 연결하는 방식과 컨테이너로 구성된 Jenkins를 연결하는 과정을 확인 합니다.

Master-Slave 방식, 또는 Master-Agent 방식으로 표현합니다.

※ Slave 호스트에 Jenkins를 설치할 필요는 없습니다.

4.1 Adding an SSH build agent to Jenkins

Worker가 실행되는 Slave 호스트에 SSH key를 생성하고 Worker 호스트에 인증 키를 복사하는 과정은 다음과 같습니다.

  1. 키 생성 및 복사(jenkins 를 수행할 유저를 생성해야 합니다.)

    # User가 없는 경우 새로운 Jenkins slave 유저 추가
    +$ useradd jenkins
    +$ passwd jenkins
    +Changing password for user jenkins.
    +New password:
    +Retype new password:
    +
    +# Slave 호스트에서 ssh 키를 생성합니다.
    +$ ssh-keygen -t rsa
    +Generating public/private rsa key pair.
    +Enter file in which to save the key (/root/.ssh/id_rsa): <enter>
    +Created directory '/root/.ssh'.
    +Enter passphrase (empty for no passphrase): <enter>
    +Enter same passphrase again: <enter>
    +Your identification has been saved in /root/.ssh/id_rsa.
    +Your public key has been saved in /root/.ssh/id_rsa.pub.
    +The key fingerprint is: <enter>
    +SHA256:WFU7MRVViaU1mSmCA5K+5yHfx7X+aV3U6/QtMSUoxug root@jenkinsecho.gyulee.com
    +The key's randomart image is:
    ++---[RSA 2048]----+
    +|     .... o.+.=*O|
    +|     ..  + . *o=.|
    +|    .   .o. +o. .|
    +|     . o. + ... +|
    +|      o.S. .   +.|
    +|     o oE    .oo.|
    +|      = o . . +o=|
    +|       o . o ..o=|
    +|          . ..o+ |
    ++----[SHA256]-----+
    +
    +$ cd ~/.ssh
    +$ cat ./id_rsa.pub > ./authorized_keys
    +
  2. Jenkins 관리노드 관리를 선택합니다.

  3. 좌측 메뉴에서 신규 노드를 클릭합니다.

  4. 노드명에 고유한 이름을 입력하고 Permanent Agent 를 활성화 합니다.

  5. 새로운 노드에 대한 정보를 기입합니다.

    • Name : 앞서 입력한 노드 이름 입니다.
  • # of executors : Jenkins에서 빌드시 사용할 실행 스레드 개수 입니다. 가용 Core수에 비례하여 설정합니다.
    • Remote root directory : 빌드시 사용할 디렉토리를 지정합니다. Lunux/Unix 계열에서는 해당 디렉토리의 권한을 확인해줍니다.
  • Labels : Worker노드를 논리적으로 그룹화하는데 사용되는 값입니다. 예를들어 GPU나 HighCPU 모델같은 용도로 구분할 수 있습니다. (e.g. Metal)
    • Usage : Use this node as much as possible
    • Launch method : Launch agent agents via SSH 로 설정합니다.
      • Host : Worker 호스트에 접근 가능한 IP 혹은 Hostname을 입력합니다.
      • Credentials : 앞서 설정한 SSH 키를 등록합니다.
        • 우측에 ADD > Jenkins를 클릭합니다.
        • Kind : SSH Username with private key를 선택합니다.
        • ID : 고유한 키 값을 넣어줍니다. (e.g. jenkins-ssh)
        • Username : jenkins (Slave 호스트의 사용자 이름입니다.)
        • Private Key : Enter directly 를 사용하여 앞서 생성한 ~/.ssh/id_rsa 의 내용을 붙여넣어줍니다. (일반적으로 -----BEGIN RSA PRIVATE KEY-----로 시작하는 내용입니다.)
      • Host Key Verification Strategy : Non verifying verification strategy 를 선택합니다.
    • 저장 버튼을 클릭하면 Node 설정 화면과 왼쪽 빌드 실행 상태에 새로운 Slave Node가 추가됨을 확인 할 수 있습니다.

Label 지정한 Slave Worker에서 빌드가 수행되도록 기존 02-02.Jobs의 Pipeline 스크립트를 수정합니다. 기존 agent any를 다음과 같이 agent { label 'Metal' }로 변경합니다. 해당 pipeline은 label이 Metal로 지정된 Worker에서만 빌드를 수행합니다.

pipeline {
+    agent { label 'Metal' }
+    parameters {
+        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
+    }
+    stages {
+        stage('Example') {
+            steps {
+                echo "${params.Greeting} World!"
+            }
+        }
+    }
+}
+
1564378013035
1564378013035

4.2 Using Docker images for agents

Master Jenkins 호스트에서 docker 서비스에 설정을 추가합니다. docker 설치가 되어있지 않은 경우 설치가 필요합니다.

$ yum -y install docker
+

RHEL8 환경이 Master인 경우 위와 같은 방식으로 설치를 진행하면 변경된 패키지에 따라 podman-docker가 설치 됩니다. 아직 Jenkins에서는 2019년 7월 29일 기준 podman을 지원하지 않음으로 별도 yum repository를 추가하여 진행합니다. docker-ce 최신 버전에서는 containerd.io 의 필요 버전이 1.2.2-3 이상이나 RHEL8에서 지원하지 않음으로 별도로 버전을 지정하여 설치합니다.

$ yum -y install yum-utils
+$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+$ sudo yum repolist -v
+...
+Repo-id      : docker-ce-stable
+Repo-name    : Docker CE Stable - x86_64
+Repo-revision: 1564098258
+Repo-updated : Fri 26 Jul 2019 08:44:18 AM KST
+Repo-pkgs    : 47
+Repo-size    : 982 M
+Repo-baseurl : https://download.docker.com/linux/centos/7/x86_64/stable
+Repo-expire  : 172,800 second(s) (last: Thu 25 Jul 2019 07:33:33 AM KST)
+Repo-filename: /etc/yum.repos.d/docker-ce.repo
+...
+
+$ yum -y install docker-ce-3:18.09.1-3.el7
+$ systemctl enable docker
+$ systemctl start docker
+
  • docker를 설치 한 뒤 API를 위한 TCP 포트를 활성화하는 작업을 진행합니다./lib/systemd/system/docker.serviceExecStart 옵션 뒤에 다음과 같이 -H tcp://0.0.0.0:4243을 추가합니다.

    ...
    +[Service]
    +Type=notify
    +# the default is not to use systemd for cgroups because the delegate issues still
    +# exists and systemd currently does not support the cgroup feature set required
    +# for containers run by docker
    +ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:4243
    +ExecReload=/bin/kill -s HUP $MAINPID
    +TimeoutSec=0
    +RestartSec=2
    +Restart=always
    +...
    +
  • 수정 후 서비스를 재시작합니다.

    $ systemctl daemon-reload
    +$ systemctl restart docker
    +$ docker ps
    +CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
    +
    +$ usermod -aG docker jenkins
    +$ chmod 777 /var/run/docker.sock
    +

Jenkins에 새로운 플러그인을 추가하고 설정합니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.

  • 설치 가능 탭을 클릭하고 상단의 검색에 docker를 입력하면 docker플러그인이 나타납니다. 선택하여 설치를 진행하고 Jenkins를 재시작 합니다.

  • Jenkins 관리로 이동하여 시스템 설정을 클릭합니다.

  • Cloud항목 아래 ADD A NEW CLOUD 드롭박스에 있는 Docker를 선택합니다.

    • Name은 기본 값으로 진행하고 DOCKER CLOUD DETAILS... 버튼을 클릭합니다.

    • Docker Host URI : 앞서 설정한 port로 연결합니다. (e.g. tcp://master:4243)

    • TEST CONNECTION 버튼을 눌러 정상적으로 Version 정보와 API Version이 표기되는지 확인합니다.

      Version = 18.09.1, API Version = 1.39
      +
    • Enabled를 활성화 합니다.

Docker 실행을 위한 Item을 생성합니다. (e.g. 04-02.UsingDockerImagesForAgents)

  1. Pipeline 스크립트를 구성합니다.
pipeline {
+    agent {
+        docker { image 'node:latest' }
+    }
+    stages {
+        stage('Test'){
+            steps {
+                sh 'node --version'
+            }
+        }
+    }
+}
+
  1. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

  2. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

    1564388703234
    1564388703234

4.3 Configuring specific agents

Freestyle project 형태의 Item을 생성합니다. (e.g. 04-03.ConfiguringSpecificAgents)

Jenkins는 각 단계, 빌드, 그리고 빌드 후 작업일 지정할 수 있습니다. Freestyle project에서는 이같은 전체 빌드 단계를 구성하고 여러가지 플러그인을 사용할 수 있는 환경을 제공합니다.

1564444282455
1564444282455
  • General

    • Restrict where this project can be run : 빌드 수행을 특정 Label 노드에 제한하도록 설정할 수 있습니다.

      • Label Expression : 앞서의 과정에서 생성한 노드 Metal을 지정해봅니다. 해당 조건의 노드가 존재하는 경우 노드 개수 정보가 표기됩니다.

        Label Metal is serviced by 1 node.
        +
  • Build

    • ADD BUILD STEP 드롭박스에서 Excute shell 항목을 선택하여 추가 합니다.
      • Command 칸에 echo "Hello world."를 넣어봅니다.
    • ADD BUILD STEP 드롭박스에서 Excute shell 항목을 선택하여 추가 합니다.
      • Command 칸에 ls -al"를 넣어봅니다.
  • 저장하고 좌측의 Build Now를 클릭하여 빌드를 수행합니다.

  • 콘솔 출력을 확인하면 지정한 Label 노드에서 각 빌드 절차가 수행된 것을 확인할 수 있습니다.

    1564449679656
    1564449679656
+ + + diff --git a/05-Software/Jenkins/pipeline101/05-plugins.html b/05-Software/Jenkins/pipeline101/05-plugins.html new file mode 100644 index 0000000000..9747edb0bc --- /dev/null +++ b/05-Software/Jenkins/pipeline101/05-plugins.html @@ -0,0 +1,44 @@ + + + + + + + + + + 5. Plugins | docmoa + + + + + +
본문으로 건너뛰기

5. Plugins

1분 미만cicdjenkins

5. Plugins

Jenkins가 유용한 툴인 이유중 하나는 방대한 양의 플러그인 입니다. Jenkins의 기능을 확장시키고, 관리, 빌드 정책 등을 확장 시켜주고, 타 서비스와의 연계를 쉽게 가능하도록 합니다.

Plugin Indexopen in new window

1564450122219
1564450122219

5.1 Adding plugins via plugin manager

Jenkins는 온라인에 연결된 plugin을 검색, 설치할 수 있는 플러그인 관리기능을 갖고 있습니다. 좌측 메뉴에서 Jenkins 관리를 클릭하면 플러그인 관리 링크를 통하여 해당 기능에 접근할 수 있습니다.

  • 업데이트된 플러그인 목록 : 설치된 플러그인 중 업데이트가 있는 플러그인 목록이 나타납니다.
  • 설치 가능 : 아직 해당 Jenkins에 설치되어있지 않은 플러그인 목록이 나타납니다.
  • 설치된 플러그인 목록 : 해당 Jenkins에 설치되어있는 플러그인이 나타납니다. 필수적이지 않은 플러그인인 경우 삭제도 해당 탭에서 가능합니다.
  • 고급 : 플러그인 서버에 접속할 수 있도록 별도의 프록시를 설정하거나, .hpi확장자를 갖는 플러그인을 설치하거나 업데이트 사이트를 지정할 수 있습니다.

각 플러그인 이름을 클릭하면 플러그인 정보를 확인할 수 있는 plugins.jenkins.io 사이트로 이동하여 정보를 보여줍니다. 사용방법은 우측에 wiki링크를 클릭합니다. 대략적인 UI나 사용방법은 wiki.jenkins.io에서 제공합니다.

5.2 Using shared libraries

Jenkins Pipeline의 Shared libraries에 대한 상세 내용은 다음 링크를 참고합니다. https://jenkins.io/doc/book/pipeline/shared-libraries/open in new window

이번 실습을 진행하기전에 GitHub에서 https://github.com/Great-Stone/evenOddopen in new window repository를 본인 계정의 GitHub에 Fork 하여 진행합니다.

소스의 var 디렉토리에는 Pipeline에서 사용하는 Shared Library들이 들어있습니다. groovy 스크립트로 되어있으며 Pipeline을 구성한 jenkinsfile에서 이를 사용합니다.

vars/evenOdd.groovy를 호출하고 값을 받아오는 형태를 갖고, evenOdd.groovy에서 사용하는 log.infolog.warningvars/log.groovy에 구현되어있습니다.

다음과 같이 Jenkins에 설정을 수행합니다.

  1. Jenkins 관리클릭 후 시스템 설정을 선택합니다.
  2. Global Pipeline Libraries 의 추가 버튼을 클릭하여 새로운 구성을 추가합니다.
    • Name : evenOdd (어플리케이션 이름과 동일하게 하는 것을 추천합니다.)
    • Default version : master
    • Retrieval method > Modern SCM : 활성화 하면 아래 Source Code Management 항목이 추가됩니다.
    • SCM의 GitHub를 클릭하여 내용을 채웁니다.
      • Credentials : 이전 실습 내용에서 생성한 Credential을 선택합니다.
      • Owner : GitHub Owner를 입력합니다. https://github.com/Great-Stone/evenOdd인 경우 Great-Stone이 Owner가 됩니다.
      • Repository : 위정보가 맞는 경우 자동으로 목록이 나타납니다. evenOdd를 선택합니다.
    • Load implicitly : SCM 정보를 기입하고 다시 위쪽 Library에 있는 Load implicitly를 활성화 합니다.

Shared Libraries가 준비가 되면 Pipeline 타입의 Item을 생성하고 (e.g. 05-02.UsingSharedLibraries) Pipeline 설정을 추가합니다.

저장 후 Build Now를 클릭하여 빌드를 수행합니다. 빌드의 결과로는 2 단계로 수행되는데 1단계는 Declarative: Checkout SCM으로 SCM으로부터 소스를 받아 준비하는 단계이고, 2단계는 jenkinsfile을 수행하는 단계입니다. vars/evenOdd.goovy 스크립트에는 stage가 두개 있으나 해당 Pipeline 을 호출하는 값에 따라 하나의 stage만을 수행하도록 되어있어서 하나의 stage가 수행되었습니다.

// Jenkinsfile
+//@Library('evenOdd') _
+
+evenOdd(currentBuild.getNumber())
+

currentBuild.getNumber()는 현재 생성된 Pipeline Item의 빌드 숫자에 따라 값을 evenOdd(빌드 숫자)형태로 호출하게 됩니다.

Jenkins shared libraries를 사용하는 가장 좋은 예는 재사용성 있는 Groovy 함수를 타 작업자와 공유하는 것 입니다. 빌드의 상태는 다른 파이프 라인 단계로 계속할 것인지 결정하는 데 사용할 수도 있습니다.

경고

해당 설정은 모든 빌드에 영향을 주기 때문에 타 작업을 위해 추가된 Global Pipeline LibrariesLibrary를 삭제하여 진행합니다.

+ + + diff --git a/05-Software/Jenkins/pipeline101/06-notifications.html b/05-Software/Jenkins/pipeline101/06-notifications.html new file mode 100644 index 0000000000..9811f5f707 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/06-notifications.html @@ -0,0 +1,52 @@ + + + + + + + + + + 6. Notifications | docmoa + + + + + +
본문으로 건너뛰기

6. Notifications

1분 미만cicdjenkins

6. Notifications

Jenkins빌드의 결과를 받아볼 수 있는 몇가지 방안에 대해 알아봅니다.

6.1 Notifications of build state

Jenkins에서는 플러그인이나 외부 툴에 의해 빌드에 대한 결과를 받아 볼 수 있습니다. 대표적으로는 Jenkins의 슬랙 플러그인을 사용하여 슬랙으로 빌드에 결과를 받아보거나, catlight.ioopen in new window 에서 데스크탑용 어플리케이션에 연동하는 방법도 있습니다.

1564463655933
1564463655933

여기서는 Chrome 확장 프로그램을 통한 알림을 받을 수 있는 설정을 설명합니다. 이 과정을 진행하기 위해서는 Chrome 웹브라우저가 필요합니다.

  1. chrome://apps/에 접속하여 앱스토어를 클릭합니다.

  2. jenkins를 검색하여 Yet Another Jenkins Notifier를 확인합니다. Chrome에 추가버튼으로 확장 프로그램을 설치합니다.

  3. 설치가 완료되면 브라우저 우측 상단에 Jenkins 아이콘이 나타납니다. 클릭합니다.

  4. 각 Item(Job)의 url을 입력하여 +버튼을 클릭합니다.

  5. 등록된 Item을 확인하고 해당 빌드를 Jenkins 콘솔에서 실행해봅니다. 결과에 대한 알림이 발생하는 것을 확인 할 수 있습니다.

    1564464197547
    1564464197547

6.2 Build state badges for SCM

Jenkins에서 빌드가 수행된 결과를 SCM에 반영하는 기능도 플러그인을 통해 가능합니다. SCM에서 해당 Jenkins에 접근이 가능해야 하므로 Jenkins는 SCM에서 접근가능한 네트워크 상태여야 합니다.

Jenkins에 새로운 플러그인을 추가하고 설정합니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.
  • 설치 가능 탭을 클릭하고 상단의 검색에 embed를 입력하면 Embeddable Build Status플러그인이 나타납니다. 선택하여 설치를 진행합니다.
  • Jenkins 관리로 이동하여 Configure Global Security을 클릭합니다.
  • Authorization 항목에서 Matrix-based security를 체크합니다.
  • Authenticated Users의 경우 필요한 각 항목에 대해 체크박스를 활성화 합니다.
  • Anonymous Users에 대해서 Job/ViewStatus 항목을 활성화 합니다.

이제 기존의 외부 SCM이 연결된 Item을 선택합니다. 여기서는 05-02.UsingSharedLibraries에 설정합니다. 해당 Item을 선택하면 좌측에 Embeddable Build Status 항목이 새로 생긴것을 확인 할 수 있습니다.

1564464880533
1564464880533

해당 항목을 클릭하고 Markdownunprotected의 항목을 복사합니다.

[![Build Status](http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries)](http://myjenkins.com/job/05-02.UsingSharedLibraries/)
+

복사한 형식을 GitHub의 evenOdd repository의 README.mdopen in new window 파일 상단에 위치 시킵니다.

# evenOdd
+[![Build Status](http://myjenkins.com/buildStatus/icon?job=libraries)](http://myjenkins.com/job/libraries/)
+
+A Jenkins even/odd playbook from the Jenkins.io documentation
+
+Add this as a shared library called evenOdd in your jenkins
+instance, and then instantiate the pipeline in your project Jenkinsfile
+
+This will also use an example of global variabls from the log.groovy
+definitions
+
+

이같이 반영하면 각 빌드에 대한 결과를 SCM에 동적으로 상태를 반영 할 수 있습니다.

1564465713857
1564465713857

이같은 알림 설정은 코드의 빌드가 얼마나 잘 수행되는지 이해하고 추적할 수 있도록 도와줍니다.

+ + + diff --git a/05-Software/Jenkins/pipeline101/07-testing.html b/05-Software/Jenkins/pipeline101/07-testing.html new file mode 100644 index 0000000000..4188e026fc --- /dev/null +++ b/05-Software/Jenkins/pipeline101/07-testing.html @@ -0,0 +1,108 @@ + + + + + + + + + + 7. Testing | docmoa + + + + + +
본문으로 건너뛰기

7. Testing

1분 미만cicdjenkins

7. Testing

7.1 Code coverage tests and reports

테스트 Pipeline 구성시 테스트 과정을 지정할 수 있습니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-01.CodeCoverageTestsAndReports)

설정은 다음과 같이 수행합니다.

  1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh '''
    +                  echo This > app.sh
    +                  echo That >> app.sh
    +                '''
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh '''
    +                  grep This app.sh >> ${BUILD_ID}.cov
    +                  grep That app.sh >> ${BUILD_ID}.cov
    +                '''
    +            }
    +        }
    +        stage('Coverage'){
    +            steps {
    +                sh '''
    +                  app_lines=`cat app.sh | wc -l`
    +                  cov_lines=`cat ${BUILD_ID}.cov | wc -l`
    +                  echo The app has `expr $app_lines - $cov_lines` lines uncovered > ${BUILD_ID}.rpt
    +                  cat ${BUILD_ID}.rpt
    +                '''
    +                archiveArtifacts "${env.BUILD_ID}.rpt"
    +            }
    +        }
    +    }
    +}
    +
  2. 빌드가 완료되면 해당 Job화면을 리로드 합니다. Pipeline에 archiveArtifacts가 추가되었으므로 해당 Job에서 이를 관리합니다.
    1564470826126

  3. 해당 아카이브에는 코드 검증 후의 결과가 저장 됩니다.

7.2 Using test results to stop the build

테스트 결과에 따라 빌드를 중지시키는 Pipeline 스크립트를 확인합니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-02.UsingTestResultsToStopTheBuild)

설정은 다음과 같이 수행합니다.

  1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh '''
    +                  echo This > app.sh
    +                  echo That >> app.sh
    +                  echo The Other >> app.sh
    +                '''
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh '''
    +                  for n in This That Those
    +                   do if grep $n app.sh >> ${BUILD_ID}.cov
    +                    then exit 1
    +                   fi
    +                  done
    +                '''
    +            }
    +        }
    +        stage('Coverage'){
    +            steps {
    +                sh '''
    +                  app_lines=`cat app.sh | wc -l`
    +                  cov_lines=`cat ${BUILD_ID}.cov | wc -l`
    +                  echo The app has `expr $app_lines - $cov_lines` lines uncovered > ${BUILD_ID}.rpt
    +                  cat ${BUILD_ID}.rpt
    +                '''
    +                archiveArtifacts "${env.BUILD_ID}.rpt"
    +            }
    +        }
    +    }
    +}
    +
  2. 저장을 하고 빌드를 수행하면, Pipeline 스크립트 상 Test Stage에서 조건 만족 시 exit 1를 수행하므로 빌드는 중간에 멈추게 됩니다.

    1564471729123
    1564471729123
+ + + diff --git a/05-Software/Jenkins/pipeline101/08-restapi.html b/05-Software/Jenkins/pipeline101/08-restapi.html new file mode 100644 index 0000000000..7af086eff2 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/08-restapi.html @@ -0,0 +1,73 @@ + + + + + + + + + + 8. REST API | docmoa + + + + + +
본문으로 건너뛰기

8. REST API

1분 미만cicdjenkins

8. REST API

Jenkins는 외부 서비스와의 연동이나 정보 조회를 위한 API를 제공합니다.

8.1 Triggering builds via the REST API

Jenkins REST API 테스트를 위해서는 Jenkins에 인증 가능한 Token을 취득하고 curl이나 Postman 같은 도구를 사용하여 확인 가능 합니다. 우선 Token을 얻는 방법은 다음과 같습니다.

  1. Jenkins에 로그인 합니다.

  2. 우측 상단의 로그인 아이디에 마우스를 호버하면 드롭박스 버튼이 나타납니다. 설정을 클릭합니다.

  3. API Token에서 Current token을 확인합니다. 등록된 Token이 없는 경우 다음과 같이 신규 Token을 발급 받습니다.

    • ADD NEW TOKEN을 클릭합니다.

    • 이름을 기입하는 칸에 로그인 한 아이디를 등록합니다. (e.g. admin)

    • GENERATE를 클릭하여 Token을 생성합니다.

  4. 이름과 Token을 사용하여 다음과 같이 curl로 접속하면 Jenkins-Crumb 프롬프트가 나타납니다.

    $ curl --user "admin:TOKEN" 'http://myjenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
    +
    +Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605
    +
  5. Crumb를 확인했으면 다시 헤더 값에 Jenkins-Crumb:를 추가하여 02-04.MultiStep Job을 빌드하기 위해 다음과 같이 요청합니다.

    $ curl -X POST http://myjenkins.com/job/02-04.MultiStep/build --user gyulee:11479bdec9cada082d189938a3946348be --data-urlencode json='' -H "Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605"
    +

API로 호출된 빌드가 수행되어 빌드 번호가 증가하는 것을 확인합니다.

8.2 Retriving build status via the REST API

빌드에 대한 결과를 REST API를 통해 요청하는 방법을 알아봅니다. 앞서 진행시의 Token값이 필요합니다. Json 형태로 출력되기 때문에 정렬을 위해 python이 설치 되어있다면 mjson.tool을 사용하여 보기 좋은 형태로 출력 가능합니다.

# Python이 설치되어있지 않은 경우
+$ yum -y install python2
+
+# Jenkins에 REST API로 마지막 빌드 상태 요청
+$ curl  -s --user gyulee:11479bdec9cada082d189938a3946348be http://myjenkins.com/job/02-04.MultiStep/lastBuild/api/json | python2 -mjson.tool
+
+{
+    "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
+    "actions": [
+        {
+            "_class": "hudson.model.CauseAction",
+            "causes": [
+                {
+                    "_class": "hudson.model.Cause$UserIdCause",
+                    "shortDescription": "Started by user GyuSeok.Lee",
+                    "userId": "gyulee",
+                    "userName": "GyuSeok.Lee"
+                }
+            ]
+        },
+        {},
+        {
+            "_class": "hudson.plugins.git.util.BuildData",
+            "buildsByBranchName": {
+                "master": {
+                    "_class": "hudson.plugins.git.util.Build",
+                    "buildNumber": 5,
+                    "buildResult": null,
+...
+
+ + + diff --git a/05-Software/Jenkins/pipeline101/09-security.html b/05-Software/Jenkins/pipeline101/09-security.html new file mode 100644 index 0000000000..01c98f2b6d --- /dev/null +++ b/05-Software/Jenkins/pipeline101/09-security.html @@ -0,0 +1,57 @@ + + + + + + + + + + 9. Security | docmoa + + + + + +
본문으로 건너뛰기

9. Security

약 1 분cicdjenkins

9. Security

9.1 Securing your deployment with users

사용자별 배포수행을 위한 사용자 설정을 설명합니다.

  • Jenkins 관리로 이동하여 Configure Global Security를 클릭합니다.

Enable security는 보안 설정 여부를 설정하는 항목으로 기본적으로는 비활성화되어있습니다. 체크하여 활성화하면 다양한 보안 옵션을 설정할 수 있는 항목이 표기 됩니다.

  • Disable remember me : 로그인 시 사용자를 기억할지의 여부를 묻는 체크박스에 대한 활성/비활성 설정입니다.

Security Realm 에서는 Jenkins에서 사용하는 사용자 관리 방식을 선택합니다.

  • Delegate to servlet container : Jenkins를 실행하는 서블릿 컨테이너에서 접근을 관리합니다.
  • Jenkins' own user database : 기본 설정입니다. Jenkins 자체적으로 사용자 데이터를 관리합니다. 사용자의 가입 허용이 활성화되면 Jenkins 에 접속하는 사용자는 스스로 계정을 생성하고 접근 가능합니다.
  • LDAP : 외부 LDAP과 연동하여 사용자를 관리합니다. LDAP 으로 계정을 통합 관리하는 경우 유용합니다.
  • Unix user/group database : Unix/Linux에서 해당 호스트의 사용자를 관리를 기반으로 연동합니다.

Authorization 에서는 사용자 권한에 대한 설정을 정의합니다.

  • Anyone can do anything : Jenkins에 접근할 수 있는 모든 요청을 허용합니다.
  • Legacy mode : 1.164이전 버전의 동작과 동일하게 관리됩니다. Admin사용자만 모든 기능을 수행하며, 일반 사용자와 비로그인 사용자는 읽기만 가능합니다.
  • Logged-in users can do anything : 로그인만 되면 모든 기능을 수행할 수 있는 설정압니다.
  • Matrix-based security : 매트릭스 기반으로 각 기능을 사용자와 그룹별로 조절할 수 있습니다.
  • Project-based Matrix Authorization Strategy : 매트릭스 기반 권한설정의 확장으로, 개별 프로젝트 별로 권한을 조정할 수 있습니다.

다음은 권한 매트릭스의 항목과 권한별 설명입니다.

항목권한의미
OverallAdminister시스템의 전역 설정을 변경할 수 있다. OS 에서 허용된 범위안에서 전체 시스템 엑세스드의 매우 민감한 설정을 수행
Read젠킨스의 모든 페이지 확인 가능
RunScripts그루비 콘솔이나 그루비 CLI 명령을 통해 그루비 스크립트를 실행
UploadPlugins특정 플러그인을 업로드
ConfigureUpdateCenter업데이트 사이트와 프록시 설정
SlaveConfigure기존 슬레이브 설정 가능
Delete기존 슬레이브 삭제
Create신규 슬레이브 생성
Disconnect슬레이브 연결을 끊거나 슬레이브를 임시로 오프라인으로 표시
Connect슬레이브와 연결하거나 슬레이브를 온라인으로 표시
JobCreate새로운 작업 생성
Delete기존 작업 삭제
Configure기존 작업의 설정 갱신
Read프로젝트 설정에 읽기 전용 권한 부여
Discover익명 사용자가 작업을 볼 권한이 없으면 에러 메시지 표시를 하지 않고 로그인 폼으로 전환
Build새로운 빌드 시작
Workspace젠킨스 빌드를 실행 하기 위해 체크아웃 한 작업 영역의 내용을 가져오기 가능
Cancel실행중인 빌드 취소
RunDelete빌드 내역에서 특정 빌드 삭제
Update빌드의 설명과 기타 프로퍼티 수정(빌드 실패 사유등)
ViewCreate새로운 뷰 생성
Delete기존 뷰 삭제
Configure기존 뷰 설정 갱신
Read기존 뷰 보기
SCMTag특정 빌드와 관련된 소스 관리 시스템에 태깅을 생성

CSRF Protection 항목에 있는 Prevent Cross Site Request Forgery exploits 항목은 페이지마다 nonce 또는 crumb 이라 불리우는 임시 값을 삽입하여 사이트 간 요청 위조 공격을 막을 수 있게 해줍니다. 사용방법은 위에서 REST API 에 대한 설명 시 crumb 값을 얻고, 사용하는 방법을 참고합니다.

9.2 Securing secret credentials and files

Jenkins에서 Pipeline을 설정하는 경우 일부 보안적인 값이 필요한 경우가 있습니다. 예를 들면 UsernamePassword 같은 값입니다. 앞서의 과정에서 Credentials를 생성하는 작업을 일부 수행해 보았습니다. 여기서는 생성된 인증 값을 Pipeline에 적용하는 방법을 설명합니다.

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 09-02.SecuringSecretCredentialsAndFiles) 설정은 다음과 같이 수행합니다.

  1. Pipeline 스크립트에 다음과 같이 입력 합니다.

    pipeline {
    +    agent any
    +    environment {
    +       SECRET=credentials('jenkins-secret-text')
    +    }
    +    stages {
    +        stage('Build') {
    +            steps {
    +                echo "${env.SECRET}"
    +            }
    +        }
    +    }
    +}
    +
  2. 저장 후 Build Now를 클릭하여 빌드를 수행하면 실패하게 되고 Console Output에서 진행사항을 보면, Pipeline 스크립트에서 선언한 jenkins-secret-text때문에 에러가 발생한 것을 확인할 수 있습니다.

  3. 좌측 상단의 Jenkins버튼을 클릭하여 최상위 메뉴로 이동합니다.

  4. 좌측 메뉴의 Credentials를 클릭하고 (global) 도메인을 클릭합니다.

    1564534571013
    1564534571013
  5. 좌측에 Add Credentials를 클릭하여 새로운 항목을 추가합니다.

    • Kind : Secret text
    • Secret : 해당 Credential에 담을 값을 기입합니다. (e.g. This is credential text.)
    • ID : jenkins-secret-text
  6. 저장 후 다시 빌드를 수행하면 정상적으로 수행됩니다. 해당 값은 숨기기 위한 값이므로 Pipeline 스크립트에서 echo로 호출하더라도 ****이란 값으로 표기 됩니다.

이같은 방법은 Password같은 보안에 민감한 정보를 사용하기에 유용합니다.

9.3 Auditing your environment

Jenkins의 변화와 활동에 대한 감시를 위한 설정 방법을 설명합니다. Jenkins에 새로운 플러그인을 추가하고 설정합니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.
  • 설치 가능 탭을 클릭하고 상단의 검색에 audit를 입력하면 Audit Trail플러그인이 나타납니다. 선택하여 설치합니다.
  • Jenkins 관리로 이동하여 시스템 설정을 클릭합니다.
  • Audit Trail 항목이 추가되었습니다. Loggers의 ADD LOGGER드롭박스에서 Log File을 선택하여 설정합니다.
    • Log Location : /var/jenkins_home/audit/audit.log
    • Log File Size MB : 100MB
    • Log File Count : 5

저장 후 빌드나 Job의 설정 변경등의 작업을 수행하면, audit.log.0으로 지정된 파일 경로에 생성됨을 확인 할 수 있습니다.

$ tail -f ./audit.log.0
+Jul 31, 2019 10:47:32,727 AM job/02-02.Jobs/ #12 Started by user GyuSeok.Lee
+Jul 31, 2019 10:47:42,738 AM /job/03-04.WebhookBuild Triggering/configSubmit by gyulee
+Jul 31, 2019 10:48:09,001 AM /configSubmit by gyulee
+

9.4 Using forders to create security realms

다양한 프로젝트를 관리하는 경우 관리상, 빌드 프로젝트를 관리해야할 필요성이 발생합니다. Jenkins에서 Forder 아이템을 생성하여 관리 편의성과 보안요소를 추가할 수 있습니다.

우선 테스트를 위한 사용자를 추가합니다.

  • 좌측 메뉴에서 Jenkins 관리를 클릭하여 Manage Users로 이동합니다.
  • 사용자 생성을 클릭하여 새로운 사용자를 추가합니다.
    • 계정명 : test
    • 암호 : test
    • 암호 확인 : test
    • 이름 : tester
    • 이메일 주소 : test@redhat.com

다음으로 Forder 타임의 Item을 추가합니다.

  • 좌측 메뉴에서 새로운 Item을 클릭하여 이름을 02-Project로 예를 들어 지정하고, Forder를 클릭하여 OK버튼을 클릭합니다.
    1564537956019
  • 설정 페이지가 나오면 SAVE버튼을 클릭하고 좌측 상단의 Jenkins 버튼을 클릭하여 최상위 페이지로 이동합니다.
  • 기존 프로젝트를 새로 생성한 Forder 타입으로 이동시켜 봅니다. 최상위 화면에서 02-02.Jobs에 마우스를 대면 드롭박스 메뉴를 확장할 수 있습니다. Move를 클릭합니다.
    1564538201169
  • 드롭박스에서 Jenkins >> 02-Project를 선택하고 MOVE버튼을 클릭합니다. 다시 최상위 메뉴로 오면 02-02.Jobs가 사라진 것을 확인할 수 있습니다. 02 로 시작하는 다은 프로젝트도 같은 작업을 수행하여 이동시킵니다.
  • 02-Project를 클릭하면 이동된 프로젝트들이 나타납니다.
    1564538419183

권한 설정을 하여 현재 Admin 권한의 사용자는 접근 가능하고 새로 생성한 tester는 접근불가하도록 설정합니다.

  • Folder에 접근하는 권한을 설정하기위해 Jenkins 관리Configure Global Security로 이동합니다.

  • Authorization항목의 Project-based Matrix Authorization Strategy를 선택합니다.

  • ADD USER OR GROUP...을 클릭하여 Admin 권한의 사용자를 추가합니다.

  • Admin 권한의 사용자에게는 모든 권한을 주고 Authenticated Users에는 Overall의 Read 권한만 부여합니다.
    1564543546039

  • 생성한 02-Project로 이동하여 좌측 메뉴의 Configure를 클릭합니다.

  • Properties에 추가된 Enable project-based security를 확성화하면 항목별 권한 관리 메트릭스가 표시됩니다. Job의 Build, Read, ViewStatus, Workspace를 클릭하고 View의 Read를 클릭하여 권한을 부여합니다.

    1564540010695
    1564540010695
  • 로그아웃 후에 앞서 추가한 test사용자로 로그인 하면 기본적으로 다른 프로젝트나 Item들은 권한이 없기 때문에 보이지 않고, 앞서 설정한 02-Project 폴더만 리스트에 나타납니다.
    1564543591394

Jenkins의 인증 기능을 사용하여 보안적 요소를 구성할 수 있습니다. Audit 로그를 활용하여 사용자별 활동을 기록할 수도 있고 Folder를 활용하면 간단히 사용자/그룹에 프로젝트를 구분하여 사용할 수 있도록 구성할 수 있습니다.

+ + + diff --git a/05-Software/Jenkins/pipeline101/10-artifacts.html b/05-Software/Jenkins/pipeline101/10-artifacts.html new file mode 100644 index 0000000000..3f71f0c476 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/10-artifacts.html @@ -0,0 +1,70 @@ + + + + + + + + + + 10. Artifacts | docmoa + + + + + +
본문으로 건너뛰기

10. Artifacts

1분 미만cicdjenkins

10. Artifacts

빌드 이후 빌드의 결과를 기록하고 저장하는 방법을 설명합니다.

10.1 Creating and storing artifacts

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-01.CreatingAndStoringArtifacts)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages{
+        stage('Build') {
+            steps{
+                sh 'echo "Generating artifacts for ${BUILD_NUMBER}" > output.txt'
+            }
+        }
+        stage('Archive') {
+            steps {
+                archiveArtifacts artifacts: 'output.txt', onlyIfSuccessful: true
+            }
+        }
+    }
+}
+

Archive Stage에 archiveArtifacts 스크립트가 동작하는 예제입니다. 이같은 Pipeline 스크립트 작성을 도와주는 툴을 추가로 확인해 봅니다.

  • Pipeline 하단의 Pipeline Syntax 링크를 클릭합니다.
    1564546308113
  • Sample Step에서 archiveArtifacts: Archive the artifacts를 선택합니다.
    • Files to archive : output.txt
    • 고급...을 클릭합니다.
    • 다음 항목을 활성화 합니다.
      • Archive artifacts only if build is successful
      • Use default excludes
      • Treat include and exclude patterns as case sensitive
    • 하단의 GENERATE PIPELINE SCRIPT를 클릭합니다.

결과물을 확인하면 Pipeline 스크립트에 작성한 형태와 같은 것을 확인 할 수 있습니다.

1564545470815
1564545470815

좌측 메뉴의 Build Now를 클릭하여 빌드 수행 후에 화면에 Artifacts 항목이 추가된 것을 확인할 수 있습니다. UI 상에는 마지막 빌드 결과가 강조되어 나오고 각 빌드에 대한 결과물은 각각의 빌드단계의 다운로드 버튼으로 확인하고 다운로드 할 수 있습니다.

1564545639205
1564545639205

10.2. Fingerprinting for artifact tracking

빌드 이후 보관되는 파일에 대해 어떤 프로젝트, 어떤 빌드 에서 발생한 결과물인지 확인할 수 있는 핑거프린팅 기능을 설명합니다.

Step 1의 프로젝트를 그대로 사용하거나 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-02.FingerprintingForArtifactTracking)

Step 1 Pipeline 스크립트의 archiveArtifactsfingerprint: true를 추가합니다.

pipeline {
+    agent any
+    stages{
+        stage('Build') {
+            steps{
+                sh 'echo "Generating text artifacts: Build:${BUILD_NUMBER}" > output.txt'
+            }
+        }
+        stage('Archive') {
+            steps {
+                archiveArtifacts artifacts: 'output.txt', fingerprint: true, onlyIfSuccessful: true
+            }
+        }
+    }
+}
+

파일의 지문을 확인합니다.

  • 첫번째 빌드를 수행하고 빌드 결과 아카이브 파일 output.txt파일을 다운로드 받습니다. (파일을 우클릭하고 다른 이름으로 링크 저장... or Download Linked File 을 클릭하여 파일을 받습니다.)
    1564546697375

  • 좌측 상단의 Jenkins를 클릭하여 최상위 메뉴로 돌아갑니다.

  • 좌측 메뉴의 파일 핑거프린트 확인을 클릭합니다.

  • 파일 선택버튼을 클릭하여 앞서 다운로드한 파일을 선택하고 확인하기버튼을 클릭합니다.

  • 어떤 프로젝트의 몇번째 빌드에서 발생한 파일인지 확인합니다.

  • 두번째 빌드를 수행하고 파일 핑거프린트를 확인해 봅니다.

  • 빌드 번호 정보가 변경된 것을 확인합니다.

+ + + diff --git a/05-Software/Jenkins/pipeline101/11-pipelines.html b/05-Software/Jenkins/pipeline101/11-pipelines.html new file mode 100644 index 0000000000..c8a83d4189 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/11-pipelines.html @@ -0,0 +1,119 @@ + + + + + + + + + + 11. Pipelines | docmoa + + + + + +
본문으로 건너뛰기

11. Pipelines

약 1 분cicdjenkins

11. Pipelines

11.1 Automating deployment with pipelines

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-01.AutomatingDeploymentWithPipelines)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+                sh 'echo "Hello World"'
+            }
+        }
+        stage('Test') {
+            steps {
+                sh 'echo "Test Hello World!"'
+            }
+        }
+    }
+}
+

두개의 Stage를 갖는 Pipeline 스크립트입니다. Pipeline은 빌드 수행시의 각 단계를 구분하여 빌드의 과정을 확인하고 실패에 따른 단계별 확인이 가능합니다.

좌측 Build Now를 클릭하여 빌드를 수행하면 빌드에 대한 결과는 Stage 별로 성공 실패의 여부와 로그를 확인할 수 있도록 Stage View가 UI로 제공됩니다. Stage 별로 Stage View는 기록되며, Stage에 변경이 있거나 이름이 변경되는 경우에는 해당 UI에 변경이 발생하여 기존 Pipeline 기록을 보지 못할 수 있습니다.

1564547435978
1564547435978

11.2 Creating pipeline gates

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-02.CreatingPipelineGates)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+                sh 'echo "Hello World"'
+            }
+        }
+        stage('BuildMore'){
+            steps {
+                input message: "Shall we build more?"
+                sh '''
+                    echo "We are approved; continue!"
+                    ls -lah
+                '''
+            }
+        }
+    }
+}
+

개의 Stage를 갖는 Pipeline 스크립트입니다. 두번째 Stage에 input 스크립트가 있습니다. 이 스크립트가 추가되면 Pipeline을 진행하면서 해당하는 동작을 수행할 것인지, 마치 승인 작업과 같은 동작을 수행할 수 있습니다.

좌측 Build Now를 클릭하여 빌드를 수행하면 두번째 Stage에서 해당 작업을 수행할 지에 대한 물음을 확인 할 수 있습니다.

1564547694870
1564547694870

Abort를 선택하면 빌드 취소와 같은 동작으로 실패로 처리되지는 않습니다.

11.3 Job promotion for long-running pipeline

빌드 단계를 구현할 때 Pipeline 스크립트로 하나의 프로젝트 내에서 모든 동작을 정의 할 수도 있지만 서로다른 Job을 연계하고, 승인 절차를 따르도록 구성할 수 있습니다.

Job promotion 기능을 사용하기 위한 플러그인을 설치합니다.

  • Jenkins 관리에서 플러그인 관리를 선택합니다.
  • 설치 가능 탭을 클릭하고 상단의 검색에 promoted를 입력하면 promoted builds를 확인 할 수 있습니다. 설치합니다.

FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-one)

  • General 탭의 Promote builds when...를 활성화 하여 설정합니다.

    • Name : Manual
    • Criteria 설정의 Only when manually approved 활성화
      • Approvers : 승인자를 입력합니다. (e.g. admin)
      • ADD PRAMETER 드롭박스에서 Boolean Parameter를 선택합니다.
        • Name : approve
  • Build 드롭박스에서 Execute shell을 선택합니다.

  • 다음을 입력합니다.

    echo 'This is the Job-one'
    +
  • 저장하면 생성된 프로젝트에 Promotion Status항목이 추가되어 생성됩니다.

11-03.Job-one 빌드 후 승인에 대한 다음 빌드를 진행할 FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-two)

  • 빌드 유발 항목에서 Build when another project is promoted를 활성화 합니다. 어떤 Job에서 promote 상황이 발생하였을 때 빌드를 수행할지 지정합니다.

    • Job Name : 11-03.Job-one
    • Promotion : Manual
  • Build 드롭박스에서 Execute shell을 선택합니다.

  • 다음을 입력합니다.

    echo 'This is the Job-two'
    +

11-03.Job-one에 대한 빌드를 수행합니다. 수행 완료 후 빌드 히스토리의 최근 빌드를 클릭(e.g. #1)하면 Promotion Status에 승인절차를 기다리고 있음을 확인할 수 있습니다. Parameters 항목의 approve를 체크하고 APPROVE버튼을 클릭합니다.

1564554095622
1564554095622

승인이 완료되면 해당 프로젝트의 승인에 대한 이벤트를 통해 빌드를 수행하는 11-03.Job-two가 이어서 빌드됨을 확인 할 수 있습니다.

11.4 Multibranch repository automation

SCM의 Multibranch를 빌드하는 과정에 대해 설명합니다.

다음의 GitHub repository를 fork 합니다.

Multibranch Pipeline 형태의 Item을 생성합니다. (e.g. 11-04.MultibranchRepositoryAutomation)

  • Branch Sources의 ADD SOURCE드롭박스에서 GitHub를 클릭합니다.
    • Credentials에서 앞서 생성한 GitHub 접속을 위한 Credential을 선택합니다.
    • Repository HTTPS URL에 앞서 fork한 GitHub URL을 입력하고 VALIDATE버튼을 클릭하여 잘 접근 되는지 확인합니다.
  • Scan Multibranch Pipeline Triggers에서 Periodically if not otherwise run를 활성화 합니다.
    • Interval 주기를 1 minute으로 설정합니다.

저장 후에는 자동적으로 모든 브랜치의 소스를 빌드 수행합니다.

1564555063361
1564555063361
1564554995103
1564554995103

SCM에서 브랜치를 여러개 관리하고 모두 빌드와 테스팅이 필요하다면 Multibranch 프로젝트를 생성하여 등록하고, 빌드 관리가 가능합니다.

11.5 Creating pipeline with snippets

Pipeline 을 스크립트를 작성하는 방법을 배워봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-05. CreatingPipelineWithSnippets)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage("Hello") {
+            steps {
+                echo 'Hello World'
+            }
+        }
+    }
+}
+

echo가 동작할때 시간을 기록하도록 스크립트를 수정해보겠습니다.

  • Pipeline Syntax 링크를 클릭합니다.

  • Sample Step에서 timestamps: timestamps를 선택하고 GENERATE PIPELINE SCRIPT버튼을 클릭합니다.

    timestamps {
    +    // some block
    +}
    +
  • 사용방식을 확인하고 앞서 Pipeline 스크립트의 stage에 시간을 기록하도록 수정합니다.

    ...
    +stage("Hello") {
    +    steps {
    +        timestamps {
    +            echo 'Hello World'
    +        }
    +    }
    +}
    +...
    +

빌드를 수행하고 로그를 확인해 봅니다. echo 동작이 수행 될때 시간이 함께 표기되는 것을 확인 할 수 있습니다.

1564555730104
1564555730104

11.6 Discovering global pipeline variables

Pipeline에서 사용할 수 있는 변수를 확인하고 사용하는 방법을 알아봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-06.DiscoveringGlobalPipelineVariables)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+               echo "We are in build ${currentBuild.number}"
+               echo "Our current result is ${currentBuild.currentResult}"
+            }
+        }
+        stage('BuildMore'){
+            steps {
+               echo "Name of the project is ${currentBuild.projectName}"
+            }
+        }
+        stage('BuildEnv'){
+            steps {
+                echo "Jenkins Home : ${env.JENKINS_HOME}"
+            }
+        }
+    }
+}
+

Pipeline 스크립트에서 사용가능한 변수와 사용방법은 Pipeline Syntax 링크의 Global Variables Reference 항목에서 확인 가능합니다.

1564557613406
1564557613406
+ + + diff --git a/05-Software/Jenkins/pipeline101/12-appendix.html b/05-Software/Jenkins/pipeline101/12-appendix.html new file mode 100644 index 0000000000..3c99f97ab4 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/12-appendix.html @@ -0,0 +1,46 @@ + + + + + + + + + + Apendix | docmoa + + + + + +
본문으로 건너뛰기

Apendix

1분 미만cicdjenkins

Apendix

GitHub SCM 연동 이슈

GitHub를 SCM으로 사용하는 경우 다음과 같은 메시지가 출력되면서 진행되지 않는 경우가 있습니다.

GitHub API Usage: Current quota has 5000 remaining (447380 over budget). Next quota of 5000 in 5 days 0 hr. Sleeping for 4 days 23 hr.
+14:07:33 GitHub API Usage: The quota may have been refreshed earlier than expected, rechecking...
+

이 경우 서버 시간과 GitHub의 시간이 맞지 않아 발생할 수 있는 이슈 입니다. ntpdate를 재설정 합니다.

유용한 플러그인

  • Restart Safely : Jenkins를 재기동해야하는 경우 빌드가 수행중이지 않을 때 자동으로 Restart 시켜줍니다. 설치 후에는 왼쪽 주 메뉴에 표시됩니다.
  • ThinBackup : Jenkins의 구성을 백업, 복구할 수 있는 기능을 제공합니다. 백업 주기나 백업 개수등을 정의 할 수 있습니다.
+ + + diff --git a/05-Software/Jenkins/pipeline101/13-jenkins_101_single.html b/05-Software/Jenkins/pipeline101/13-jenkins_101_single.html new file mode 100644 index 0000000000..e2b6603129 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/13-jenkins_101_single.html @@ -0,0 +1,570 @@ + + + + + + + + + + Pipeline on Jenkins 101 (Single Page) | docmoa + + + + + +
본문으로 건너뛰기

Pipeline on Jenkins 101 (Single Page)

약 13 분cicdjenkins

Pipeline on Jenkins 101 (Single Page)

Update at 31 Jul, 2019

Introduction

Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

CategoryNameVersion
VMVirtualBox6.0.10
OSRed Hat Enterprise Linux8.0.0
JDKRed Hat OpenJDK1.8.222
JenkinsJenkins rpm2.176.2

Jenkins 실행 및 구성

Jenkins를 실행 및 구성하기위한 OS와 JDK가 준비되었다는 가정 하에 진행합니다. 필요 JDK 버전 정보는 다음과 같습니다.

  • 2.164 (2019-02) and newer: Java 8 or Java 11
  • 2.54 (2017-04) and newer: Java 8
  • 1.612 (2015-05) and newer: Java 7

필요 JDK를 설치합니다.

$ subscription-manager repos --enable=rhel-8-for-x86_64-baseos-rpms --enable=rhel-8-for-x86_64-appstream-rpms
+
+### Java JDK 8 ###
+$ yum -y install java-1.8.0-openjdk-devel
+
+### Check JDK version ###
+$ java -version
+openjdk version "1.8.0_222"
+OpenJDK Runtime Environment (build 1.8.0_222-b10)
+OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
+

Red Hatsu/Fedora/CentOS 환경에서의 Jenkins 다운로드 및 실행은 다음의 과정을 수행합니다.

참고 url : https://pkg.jenkins.io/redhat-stable/open in new window

  • repository를 등록합니다.

    $ sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
    +$ sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
    +
  • 작성일 기준 LTS 버전인 2.176.2버전을 설치합니다.

    $ yum -y install jenkins
    +

패키지로 설치된 Jenkins의 설정파일은 /etc/sysconfig/jenkins에 있습니다. 해당 파일에서 실행시 활성화되는 포트 같은 설정을 변경할 수 있습니다.

## Type:        integer(0:65535)
+## Default:     8080
+## ServiceRestart: jenkins
+#
+# Port Jenkins is listening on.
+# Set to -1 to disable
+#
+JENKINS_PORT="8080"
+

외부 접속을 위해 Jenkins에서 사용할 포트를 방화벽에서 열어줍니다.

$ firewall-cmd --permanent --add-port=8080/tcp
+$ firewall-cmd --reload
+

서비스를 부팅시 실행하도록 활성화하고 Jenkins를 시작합니다.

$ systemctl enable jenkins 
+$ systemctl start jenkins
+

실행 후 브라우저로 접속하면 Jenkins가 준비중입니다. 준비가 끝나면 Unlock Jenkins 페이지가 나오고 /var/lib/jenkins/secrets/initialAdminPassword의 값을 입력하는 과정을 설명합니다. 해당 파일에 있는 토큰 복사하여 붙여넣습니다.

이후 과정은 Install suggested plugins를 클릭하여 기본 플러그인을 설치하여 진행합니다. 경우에 따라 Select plugins to install을 선택하여 플러그인을 지정하여 설치할 수 있습니다.

플러그인 설치 과정을 선택하여 진행하면 Getting Started 화면으로 전환되어 플러그인 설치가 진행됩니다.

설치 후 기본 Admin User 를 생성하고, 접속 Url을 확인 후 설치과정을 종료합니다.

GitHub 계정생성

진행되는 실습에서는 일부 GitHub를 SCM으로 연동합니다. 원활한 진행을 위해 GitHub계정을 생성해주세요. 또는 별개의 Git 서버를 구축하여 사용할 수도 있습니다.

Jenkins Theme (Optional)

Jenkins는 간단히 테마와 회사 CI를 적용할 수 있는 플러그인이 제공됩니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.

  • 설치 가능 탭을 클릭하고 상단의 검색에 theme를 입력하면 Login ThemeSimple Theme를 확인 할 수 있습니다. 둘 모두 설치합니다.

  • 로그아웃을 하면 로그인 페이지가 변경된 것을 확인 할 수 있습니다.

기본 Jenkins 테마를 변경하기 위해서는 다음의 과정을 수행합니다.

  • http://afonsof.com/jenkins-material-theme/open in new window 에 접속합니다.

  • Build your own theme with a company logo! 에서 색상과 로고를 업로드 합니다.

  • DOWNLOAD YOUR THEME!버튼을 클릭하면 CSS파일이 다운됩니다.

  • Jenkins 관리로 이동하여 시스템 설정를 클릭합니다.

  • Theme항목의 Theme elements의 드롭다운 항목에서 Extra CSS를 클릭하고 앞서 다운받은 CSS파일의 내용을 붙여넣고 설정을 저장하면 적용된 테마를 확인할 수 있습니다.

1. CI/CD

CI/CD Concept Definitions

  • Continuous integration
  • Continuous delivery
  • Continuous deployment
  • Source control management (SCM)

Delivery vs Deployment

  • Continuous Delivery requires user intervention
    • When? : Stage to Production

Jenkins for CI/CD

  • Open-source governance and community
  • Stability
  • Extensible
  • Visibility
  • Pipelines

2. Jobs

Job and Project

프로젝트는 Job의 일부 입니다. 즉, 모든 프로젝트가 Job이지만 모든 Job이 프로젝트는 아닙니다. Job의 구조는 다음과 같습니다.

FreeStyleProejct, MatrixProject, ExternalJob만 New job에 표시됩니다.

Step 1. New pipeline

Step 1에서는 stage없이 기본 Pipeline을 실행하여 수행 테스트를 합니다.

  1. Jenkins 로그인

  2. 좌측 새로운 Item 클릭

  3. Enter an item name에 Job 이름 설정 (e.g. 2.Jobs)

  4. Pipeline 선택 후 OK 버튼 클릭

  5. Pipeline 항목 오른 쪽 Try sample Pipelie...클릭하여 Hello world 클릭 후 저장

    node {
    +   echo 'Hello World'
    +}
    +
  6. 좌측 Build now클릭

  7. 좌측 Build History의 최근 빌드된 항목(e.g. #1) 우측에 마우스를 가져가면 dropdown 버튼이 생깁니다. 해당 버튼을 클릭하여 Console Output 클릭

  8. 수행된 echo 동작 출력을 확인합니다.

    Started by user GyuSeok.Lee
    +Running in Durability level: MAX_SURVIVABILITY
    +[Pipeline] Start of Pipeline
    +[Pipeline] node
    +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
    +[Pipeline] {
    +[Pipeline] echo
    +Hello World
    +[Pipeline] }
    +[Pipeline] // node
    +[Pipeline] End of Pipeline
    +Finished: SUCCESS
    +

Step 2. New pipeline

Step 2에서는 stage 를 구성하여 실행합니다.

  1. 기존 생성한 Job 클릭 (e.g. 02-02.Jobs)

  2. 좌측 구성을 클릭하여 Pipeline 스크립트를수정합니다.

    pipeline{
    +    agent any
    +    stages {
    +        stage("Hello") {
    +            steps {
    +                echo 'Hello World'
    +            }
    +        }
    +    }
    +}
    +
  3. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

  4. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

    1563942302074
    1563942302074
  5. 수행된 빌드의 Console Output을 확인하면 앞서 Step 1에서는 없던 stage 항목이 추가되어 수행됨을 확인 할 수 있습니다.

    Started by user GyuSeok.Lee
    +Running in Durability level: MAX_SURVIVABILITY
    +[Pipeline] Start of Pipeline
    +[Pipeline] node
    +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
    +[Pipeline] {
    +[Pipeline] stage
    +[Pipeline] { (Hello)
    +[Pipeline] echo
    +Hello World
    +[Pipeline] }
    +[Pipeline] // stage
    +[Pipeline] }
    +[Pipeline] // node
    +[Pipeline] End of Pipeline
    +Finished: SUCCESS
    +

Step 3. Parameterizing a job

Pipeline 내에서 사용되는 매개변수 정의를 확인해 봅니다. Pipeline 스크립트는 다음과 같습니다.

pipeline {
+    agent any
+    parameters {
+        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
+    }
+    stages {
+        stage('Example') {
+            steps {
+                echo "${params.Greeting} World!"
+            }
+        }
+    }
+}
+

parameters항목내에 매개변수의 데이터 유형(e.g. string)을 정의합니다. name은 값을 담고있는 변수이고 defaultValue의 값을 반환합니다. Pipeline에 정의된 parametersparams내에 정의 되므로 ${params.매개변수이름}과 같은 형태로 호출 됩니다.

저장 후 다시 구성을 확인하면 이 빌드는 매개변수가 있습니다가 활성화 되고 내부에 추가된 매개변수 항목을 확인 할 수 있습니다.

1563944944350
1563944944350

이렇게 저장된 Pipeline Job은 매개변수를 외부로부터 받을 수 있습니다. 따라서 좌측의 기존 Build Nowbuild with Parameters로 변경되었고, 이를 클릭하면 Greeting을 정의할 수 있는 UI가 나타납니다. 해당 매개변수를 재정의 하여 빌드를 수행할 수 있습니다.

1563944733249
1563944733249
1563944765637
1563944765637

Step 4. Creating multiple steps for a job

다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-04.MultiStep)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+                sh 'echo "Hello World"'
+                sh '''
+                    echo "Multiline shell steps works too"
+                    ls -lah
+                '''
+            }
+        }
+    }
+}
+

'''은 스크립트 정의 시 여러줄을 입력할 수 있도록 묶어주는 역할을 합니다. 해당 스크립트에서는 sh로 구분된 스크립트 명령줄이 두번 수행됩니다.

1563945323777
1563945323777

실행되는 여러 스크립트의 수행을 stage로 구분하기위해 기존 Pipeline 스크립트를 다음과 같이 수정합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build-1') {
+            steps {
+                sh 'echo "Hello World"'
+            }
+        }
+        stage('Build-2') {
+            steps {
+                sh '''
+                    echo "Multiline shell steps works too"
+                    ls -lah
+                '''
+            }
+        }
+    }
+}
+

stage를 구분하였기 때문에 각 실행되는 sh 스크립트는 각 스테이지에서 한번씩 수행되며, 이는 빌드의 결과로 나타납니다.

1563945539114
1563945539114

Step 5. Adding scripts as a job step

Pipeline의 step을 추가하여 결과를 확인하는 과정을 설명합니다. 피보나치 수열을 수행하는 쉘 스크립트를 시간제한을 두어 수행하고 그 결과를 확인합니다.

Jenkins가 설치된 서버에 [피보나치 수열]([https://namu.wiki/w/피보나치 수열open in new window](https://namu.wiki/w/피보나치open in new window 수열))을 수행하는 스크립트를 작성합니다. Sleep이 있기 때문에 일정 시간 이상 소요 됩니다.

$ mkdir -p /var/jenkins_home/scripts
+$ cd /var/jenkins_home/scripts
+$ vi ./fibonacci.sh
+#!/bin/bash
+N=${1:-10}
+
+a=0
+b=1
+
+echo "The Fibonacci series is : "
+
+for (( i=0; i<N; i++ ))
+do
+    echo "$a"
+    sleep 2
+    fn=$((a + b))
+    a=$b
+    b=$fn
+done
+# End of for loop
+
+$ chown -R jenkins /var/jenkins_home/
+$ chmod +x /var/jenkins_home/scripts/fibonacci.sh
+

다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-05.AddingStep)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages {
+        stage('Deploy') {
+            steps {
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh '/var/jenkins_home/scripts/fibonacci.sh 5'
+                }
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh '/var/jenkins_home/scripts/fibonacci.sh 32'
+                }
+            }
+        }
+    }
+}
+

steps에 스크립트를 timeout이 감싸고 있으며, 각 스크립트의 제한시간은 1분입니다. 빌드를 수행하면 최종적으로는 aborted, 즉 중단됨 상태가 되는데 그 이유는 빌드 기록에서 해당 빌드를 클릭하면 확인 가능합니다.

  • Build History에서 최신 빌드를 클릭합니다.

  • 좌측 Pipeline Steps를 클릭하면 Pipeline 수행 스텝을 확인할 수 있습니다.

  • 첫번째로 나타나는 /var/jenkins_home/scripts/fibonacci.sh 5 를 수행하는 Shell Script의 콘솔창 버튼을 클릭하면 잘 수행되었음을 확인 할 수 있습니다.

  • 두번째로 나타나는 /var/jenkins_home/scripts/fibonacci.sh 32 를 수행하는 Shell Script의 콘솔창 버튼을 클릭하면 다음과 같이 중도에 프로세스를 중지한 것을 확인 할 수 있습니다.

    + /var/jenkins_home/scripts/fibonacci.sh 32
    +The Fibonacci series is : 
    +0
    +1
    +1
    +2
    +3
    +...
    +317811
    +514229
    +Sending interrupt signal to process
    +/var/jenkins_home/scripts/fibonacci.sh: line 16: 13543 Terminated              sleep 2
    +832040
    +/var/lib/jenkins/workspace/02-05.AddingStep@tmp/durable-e44bb232/script.sh: line 1: 13109 Terminated              /var/jenkins_home/scripts/fibonacci.sh 32
    +script returned exit code 143
    +

3. Builds

Step 1. Tracking build state

Pipeline이 수행되는 동작을 추적하는 과정을 확인합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-01.TrackingBuildState)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages {
+        stage('Deploy') {
+            steps {
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh 'for n in `seq 1 10`; do echo $n; sleep 1; done'
+                }
+                timeout(time: 1, unit: 'MINUTES') {
+                    sh 'for n in `seq 1 50`; do echo $n; sleep 1; done'
+                }
+            }
+        }
+    }
+}
+

Build Now를 클릭하여 빌드를 수행합니다. 그러면, 좌측의 Build History에 새로운 기록이 생성되면서 동작 중인것을 확인 할 수 있습니다.

첫번째 방법은 앞서 확인한 Pipeline Steps를 확인하는 것입니다. 다시한번 확인하는 방법을 설명합니다.

  • Build History에서 최신 빌드를 클릭합니다.
  • 좌측 Pipeline Steps를 클릭하면 Pipeline 수행 스텝을 확인할 수 있습니다.

현재 수행중인 Pipeline이 어떤 단계가 수행중인지 각 스탭별로 확인할 수 있고 상태를 확인할 수 있습니다.

1563948810815
1563948810815

두번째 방법은 출력되는 콘솔 로그를 확인하는 것입니다. Jenkins에서 빌드를 수행하면 빌드 수행 스크립트가 내부에 임시적으로 생성되어 작업을 실행합니다. 이때 발생되는 로그는 Console Output을 통해 거의 실시간으로 동작을 확인 할 수 있습니다.

  • Build History에서 최신 빌드에 마우스 포인터를 가져가면 우측에 드롭박스가 생깁니다. 또는 해당 히스토리를 클릭합니다.
  • 드롭 박스의 Console Output나 클릭된 빌드 히스토리 상태에서 Console Output를 클릭하면 수행중인 콘솔상의 출력을 확인합니다.
1563948863834
1563948863834

마지막으로는 Pipeline을 위한 UI인 BlueOcean 플러그인을 활용하는 방법입니다. Blue Ocean은 Pipeline에 알맞은 UI를 제공하며 수행 단계와 각 단게별 결과를 쉽게 확인할 수 있습니다.

  • Jenkins 관리에서 플러그인 관리를 선택합니다.
  • 설치 가능 탭에서 Blue Ocean을 선택하여 재시작 없이 설치를 클릭 합니다.
  • Blue Ocean플러그인만 선택하여 설치하더라도 관련 플러그인들이 함께 설치 진행됩니다.
  • 설치 완료되면 좌측 메뉴에서 Blue Ocean항목을 확인 할 수 있습니다.
1563949823939
1563949823939

Step 2. Polling SCM for build triggering

Git SCM을 기반으로 Pipeline을 설정하는 과정을 설명합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-02.PollingSCMforBuildTriggering)

해당 과정을 수행하기 위해서는 다음의 구성이 필요합니다.

  • Jenkins가 구성된 호스트에 git 설치

    $ yum -y install git
    +
  • Jenkins 설정

    • Jenkins 관리Global Tool Configuration클릭
    • Git 항목에 Path to Git executable 칸에 Path 입력 (e.g. /usr/bin/git)

Pipeline을 다음과 같이 설정합니다.

추가로 빌드 트리거를 위한 설정을 합니다.

  • Build TriggersPoll SCM 활성화

  • Schedule 등록

    # min hour day month day_of_week
    +* * * * *
    +# will run every minute on the minute
    +

Polling으로 인한 빌드 트리거가 동작하면 좌측 메뉴의 Polling Log에서 상태 확인이 가능합니다.

1563958295010
1563958295010

1분마다 확인 하도록 되어있기 때문에 다시 Polling을 시도하지만 변경사항이 없는 경우에는 Polling Log에 No changes 메시지가 나타나고 빌드는 수행되지 않습니다.

1563958396611
1563958396611

Step 3. Connecting Jenkins to GitHub

GitHub를 통한 CI 과정을 설명합니다. WebHook의 설정과 Jenkins에 관련 설정은 어떻게 하는지 알아봅니다.

Jenkins에서 접속가능하도록 GitHub에서 Token을 생성합니다.

  • github.comopen in new window에 접속하여 로그인합니다.

  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-integration) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

    1564115701058
    1564115701058

우선 Jenkins에 Git연동을 위한 설정을 추가합니다.

  • Jenkins 관리에서 시스템 설정을 클릭합니다.
  • GitHub 항목의 GitHub ServersAdd GitHub Server > GitHub Server를 선택합니다.
  • 항목의 입력정보는 다음과 같습니다.
    • Name : 설정이름을 입력합니다. (e.g. github)
    • API URL : https://api.github.comopen in new window
    • Credentials : ADD트롭박스를 선택합니다.
      • 기본 Credendial을 선택합니다. (e.g. Jenkins)
      • Kind 항목을 Secret text로 선택합니다.
        • Scope : Global(Jenkins, nodes, Items, all child items, etc)
        • Secret : GitHub에서 복사한 Token 값을 입력합니다.
        • ID : Credential ID를 입력합니다. (e.g. jenkins-integration)
        • ADD 버튼 클릭하여 새로운 Credendial을 추가합니다.
      • 다시 시스템 설정화면으로 나오면 Credentials의 -none-드롭박스에 추가한 Credential을 선택합니다.
    • TEST CONNECTION버튼을 클릭하여 정상적으로 연결이 되는지 확인합니다.
      • 정상적으로 연결되면 Credentials verified for user Great-Stone, rate limit: 4998 와같은 메시지가 출력됩니다.
    • Manage hook 를 활성화 합니다.
  • 시스템 설정을 저장합니다.

Step 4. Webhook build triggering

git repo의 Webhook 을 통한 빌드를 수행합니다. GitHub에 다음과 같이 설정합니다.

  • https://github.com/Great-Stone/jenkins-gitopen in new windowfork합니다.

    1564122799631
    1564122799631
  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-webhook) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

Webhook을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-04.WebhookBuild Triggering)

설정은 다음과 같이 수행합니다.

  1. Pipeline 설정의 Definition의 드롭다운을 선택하여 Pipeline script from SCM을 선택합니다.

  2. SCM항목은 Git을 선택하고 하위 필드를 다음과 같이 정의합니다.

    • Repositories :

      • Repository URL을 입력하는데, GitHub에서 git url을 얻기위해서는 웹브라우저에서 해당 repository로 이동하여 Clone or download 버튼을 클릭하여 Url을 복사하여 붙여넣습니다.

        1564365395583
        1564365395583
      • Credentials : ADD트롭박스를 선택합니다.

        • 기본 Credendial을 선택합니다. (e.g. Jenkins)
        • Kind 항목을 Username with password로 선택합니다.
          • Scope : Global(Jenkins, nodes, Items, all child items, etc)
          • Username : GitHub의 사용자 아이디를 입력합니다.
          • Secret : GitHub사용자 패스워드를 입력합니다.
          • ID : Credential ID를 입력합니다. (e.g. jenkins-webhook)
          • ADD 버튼 클릭하여 새로운 Credendial을 추가합니다.
        • 다시 시스템 설정화면으로 나오면 Credentials의 -none-드롭박스에 추가한 Credential을 선택합니다.
    • Script Path : Pipeline 스크립트가 작성된 파일 패스를 지정합니다. 예제 소스에서는 root 위치에 Jenkinsfile로 생성되어있으므로 해당 칸에는 Jenkinsfile이라고 입력 합니다.

저장 후 좌측 메뉴의 Build Now를 클릭하면 SCM에서 소스를 받고 Pipeline을 지정한 스크립트로 수행하는 것을 확인 할 수 있습니다.

4. Agents and Distributing Builds

빌드를 수행하기 위한 Worker로 다중 Jenkins를 컨트롤 할 수 있습니다. 이때 명령을 수행하는 Jenkins는 Master, 빌드를 수행하는 Jenkins는 Worker로 구분합니다. 여기서는 Worker의 연결을 원격 호스트의 Jenkins를 SSH를 통해 연결하는 방식과 컨테이너로 구성된 Jenkins를 연결하는 과정을 확인 합니다.

Master-Slave 방식, 또는 Master-Agent 방식으로 표현합니다.

※ Slave 호스트에 Jenkins를 설치할 필요는 없습니다.

Step 1. Adding an SSH build agent to Jenkins

Worker가 실행되는 Slave 호스트에 SSH key를 생성하고 Worker 호스트에 인증 키를 복사하는 과정은 다음과 같습니다.

  1. 키 생성 및 복사(jenkins 를 수행할 유저를 생성해야 합니다.)

    # User가 없는 경우 새로운 Jenkins slave 유저 추가
    +$ useradd jenkins
    +$ passwd jenkins
    +Changing password for user jenkins.
    +New password:
    +Retype new password:
    +
    +# Slave 호스트에서 ssh 키를 생성합니다.
    +$ ssh-keygen -t rsa
    +Generating public/private rsa key pair.
    +Enter file in which to save the key (/root/.ssh/id_rsa): <enter>
    +Created directory '/root/.ssh'.
    +Enter passphrase (empty for no passphrase): <enter>
    +Enter same passphrase again: <enter>
    +Your identification has been saved in /root/.ssh/id_rsa.
    +Your public key has been saved in /root/.ssh/id_rsa.pub.
    +The key fingerprint is: <enter>
    +SHA256:WFU7MRVViaU1mSmCA5K+5yHfx7X+aV3U6/QtMSUoxug root@jenkinsecho.gyulee.com
    +The key's randomart image is:
    ++---[RSA 2048]----+
    +|     .... o.+.=*O|
    +|     ..  + . *o=.|
    +|    .   .o. +o. .|
    +|     . o. + ... +|
    +|      o.S. .   +.|
    +|     o oE    .oo.|
    +|      = o . . +o=|
    +|       o . o ..o=|
    +|          . ..o+ |
    ++----[SHA256]-----+
    +
    +$ cd ~/.ssh
    +$ cat ./id_rsa.pub > ./authorized_keys
    +
  2. Jenkins 관리노드 관리를 선택합니다.

  3. 좌측 메뉴에서 신규 노드를 클릭합니다.

  4. 노드명에 고유한 이름을 입력하고 Permanent Agent 를 활성화 합니다.

  5. 새로운 노드에 대한 정보를 기입합니다.

    • Name : 앞서 입력한 노드 이름 입니다.
  • # of executors : Jenkins에서 빌드시 사용할 실행 스레드 개수 입니다. 가용 Core수에 비례하여 설정합니다.
    • Remote root directory : 빌드시 사용할 디렉토리를 지정합니다. Lunux/Unix 계열에서는 해당 디렉토리의 권한을 확인해줍니다.
  • Labels : Worker노드를 논리적으로 그룹화하는데 사용되는 값입니다. 예를들어 GPU나 HighCPU 모델같은 용도로 구분할 수 있습니다. (e.g. Metal)
    • Usage : Use this node as much as possible
    • Launch method : Launch agent agents via SSH 로 설정합니다.
      • Host : Worker 호스트에 접근 가능한 IP 혹은 Hostname을 입력합니다.
      • Credentials : 앞서 설정한 SSH 키를 등록합니다.
        • 우측에 ADD > Jenkins를 클릭합니다.
        • Kind : SSH Username with private key를 선택합니다.
        • ID : 고유한 키 값을 넣어줍니다. (e.g. jenkins-ssh)
        • Username : jenkins (Slave 호스트의 사용자 이름입니다.)
        • Private Key : Enter directly 를 사용하여 앞서 생성한 ~/.ssh/id_rsa 의 내용을 붙여넣어줍니다. (일반적으로 -----BEGIN RSA PRIVATE KEY-----로 시작하는 내용입니다.)
      • Host Key Verification Strategy : Non verifying verification strategy 를 선택합니다.
    • 저장 버튼을 클릭하면 Node 설정 화면과 왼쪽 빌드 실행 상태에 새로운 Slave Node가 추가됨을 확인 할 수 있습니다.

Label 지정한 Slave Worker에서 빌드가 수행되도록 기존 02-02.Jobs의 Pipeline 스크립트를 수정합니다. 기존 agent any를 다음과 같이 agent { label 'Metal' }로 변경합니다. 해당 pipeline은 label이 Metal로 지정된 Worker에서만 빌드를 수행합니다.

pipeline {
+    agent { label 'Metal' }
+    parameters {
+        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
+    }
+    stages {
+        stage('Example') {
+            steps {
+                echo "${params.Greeting} World!"
+            }
+        }
+    }
+}
+
1564378013035
1564378013035

Step 2. Using Docker images for agents

Master Jenkins 호스트에서 docker 서비스에 설정을 추가합니다. docker 설치가 되어있지 않은 경우 설치가 필요합니다.

$ yum -y install docker
+

RHEL8 환경이 Master인 경우 위와 같은 방식으로 설치를 진행하면 변경된 패키지에 따라 podman-docker가 설치 됩니다. 아직 Jenkins에서는 2019년 7월 29일 기준 podman을 지원하지 않음으로 별도 yum repository를 추가하여 진행합니다. docker-ce 최신 버전에서는 containerd.io 의 필요 버전이 1.2.2-3 이상이나 RHEL8에서 지원하지 않음으로 별도로 버전을 지정하여 설치합니다.

$ yum -y install yum-utils
+$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+$ sudo yum repolist -v
+...
+Repo-id      : docker-ce-stable
+Repo-name    : Docker CE Stable - x86_64
+Repo-revision: 1564098258
+Repo-updated : Fri 26 Jul 2019 08:44:18 AM KST
+Repo-pkgs    : 47
+Repo-size    : 982 M
+Repo-baseurl : https://download.docker.com/linux/centos/7/x86_64/stable
+Repo-expire  : 172,800 second(s) (last: Thu 25 Jul 2019 07:33:33 AM KST)
+Repo-filename: /etc/yum.repos.d/docker-ce.repo
+...
+
+$ yum -y install docker-ce-3:18.09.1-3.el7
+$ systemctl enable docker
+$ systemctl start docker
+
  • docker를 설치 한 뒤 API를 위한 TCP 포트를 활성화하는 작업을 진행합니다./lib/systemd/system/docker.serviceExecStart 옵션 뒤에 다음과 같이 -H tcp://0.0.0.0:4243을 추가합니다.

    ...
    +[Service]
    +Type=notify
    +# the default is not to use systemd for cgroups because the delegate issues still
    +# exists and systemd currently does not support the cgroup feature set required
    +# for containers run by docker
    +ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:4243
    +ExecReload=/bin/kill -s HUP $MAINPID
    +TimeoutSec=0
    +RestartSec=2
    +Restart=always
    +...
    +
  • 수정 후 서비스를 재시작합니다.

    $ systemctl daemon-reload
    +$ systemctl restart docker
    +$ docker ps
    +CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
    +
    +$ usermod -aG docker jenkins
    +$ chmod 777 /var/run/docker.sock
    +

Jenkins에 새로운 플러그인을 추가하고 설정합니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.

  • 설치 가능 탭을 클릭하고 상단의 검색에 docker를 입력하면 docker플러그인이 나타납니다. 선택하여 설치를 진행하고 Jenkins를 재시작 합니다.

  • Jenkins 관리로 이동하여 시스템 설정을 클릭합니다.

  • Cloud항목 아래 ADD A NEW CLOUD 드롭박스에 있는 Docker를 선택합니다.

    • Name은 기본 값으로 진행하고 DOCKER CLOUD DETAILS... 버튼을 클릭합니다.

    • Docker Host URI : 앞서 설정한 port로 연결합니다. (e.g. tcp://master:4243)

    • TEST CONNECTION 버튼을 눌러 정상적으로 Version 정보와 API Version이 표기되는지 확인합니다.

      Version = 18.09.1, API Version = 1.39
      +
    • Enabled를 활성화 합니다.

Docker 실행을 위한 Item을 생성합니다. (e.g. 04-02.UsingDockerImagesForAgents)

  1. Pipeline 스크립트를 구성합니다.
pipeline {
+    agent {
+        docker { image 'node:latest' }
+    }
+    stages {
+        stage('Test'){
+            steps {
+                sh 'node --version'
+            }
+        }
+    }
+}
+
  1. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

  2. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

    1564388703234
    1564388703234

Step3. Configuring specific agents

Freestyle project 형태의 Item을 생성합니다. (e.g. 04-03.ConfiguringSpecificAgents)

Jenkins는 각 단계, 빌드, 그리고 빌드 후 작업일 지정할 수 있습니다. Freestyle project에서는 이같은 전체 빌드 단계를 구성하고 여러가지 플러그인을 사용할 수 있는 환경을 제공합니다.

1564444282455
1564444282455
  • General

    • Restrict where this project can be run : 빌드 수행을 특정 Label 노드에 제한하도록 설정할 수 있습니다.

      • Label Expression : 앞서의 과정에서 생성한 노드 Metal을 지정해봅니다. 해당 조건의 노드가 존재하는 경우 노드 개수 정보가 표기됩니다.

        Label Metal is serviced by 1 node.
        +
  • Build

    • ADD BUILD STEP 드롭박스에서 Excute shell 항목을 선택하여 추가 합니다.
      • Command 칸에 echo "Hello world."를 넣어봅니다.
    • ADD BUILD STEP 드롭박스에서 Excute shell 항목을 선택하여 추가 합니다.
      • Command 칸에 ls -al"를 넣어봅니다.
  • 저장하고 좌측의 Build Now를 클릭하여 빌드를 수행합니다.

  • 콘솔 출력을 확인하면 지정한 Label 노드에서 각 빌드 절차가 수행된 것을 확인할 수 있습니다.

    1564449679656
    1564449679656

5. Plugins

Jenkins가 유용한 툴인 이유중 하나는 방대한 양의 플러그인 입니다. Jenkins의 기능을 확장시키고, 관리, 빌드 정책 등을 확장 시켜주고, 타 서비스와의 연계를 쉽게 가능하도록 합니다.

Plugin Indexopen in new window

1564450122219
1564450122219

Step 1. Adding plugins via plugin manager

Jenkins는 온라인에 연결된 plugin을 검색, 설치할 수 있는 플러그인 관리기능을 갖고 있습니다. 좌측 메뉴에서 Jenkins 관리를 클릭하면 플러그인 관리 링크를 통하여 해당 기능에 접근할 수 있습니다.

  • 업데이트된 플러그인 목록 : 설치된 플러그인 중 업데이트가 있는 플러그인 목록이 나타납니다.
  • 설치 가능 : 아직 해당 Jenkins에 설치되어있지 않은 플러그인 목록이 나타납니다.
  • 설치된 플러그인 목록 : 해당 Jenkins에 설치되어있는 플러그인이 나타납니다. 필수적이지 않은 플러그인인 경우 삭제도 해당 탭에서 가능합니다.
  • 고급 : 플러그인 서버에 접속할 수 있도록 별도의 프록시를 설정하거나, .hpi확장자를 갖는 플러그인을 설치하거나 업데이트 사이트를 지정할 수 있습니다.

각 플러그인 이름을 클릭하면 플러그인 정보를 확인할 수 있는 plugins.jenkins.io 사이트로 이동하여 정보를 보여줍니다. 사용방법은 우측에 wiki링크를 클릭합니다. 대략적인 UI나 사용방법은 wiki.jenkins.io에서 제공합니다.

Step 2. Using shared libraries

Jenkins Pipeline의 Shared libraries에 대한 상세 내용은 다음 링크를 참고합니다. https://jenkins.io/doc/book/pipeline/shared-libraries/open in new window

이번 실습을 진행하기전에 GitHub에서 https://github.com/Great-Stone/evenOddopen in new window repository를 본인 계정의 GitHub에 Fork 하여 진행합니다.

소스의 var 디렉토리에는 Pipeline에서 사용하는 Shared Library들이 들어있습니다. groovy 스크립트로 되어있으며 Pipeline을 구성한 jenkinsfile에서 이를 사용합니다.

vars/evenOdd.groovy를 호출하고 값을 받아오는 형태를 갖고, evenOdd.groovy에서 사용하는 log.infolog.warningvars/log.groovy에 구현되어있습니다.

다음과 같이 Jenkins에 설정을 수행합니다.

  1. Jenkins 관리클릭 후 시스템 설정을 선택합니다.
  2. Global Pipeline Libraries 의 추가 버튼을 클릭하여 새로운 구성을 추가합니다.
    • Name : evenOdd (어플리케이션 이름과 동일하게 하는 것을 추천합니다.)
    • Default version : master
    • Retrieval method > Modern SCM : 활성화 하면 아래 Source Code Management 항목이 추가됩니다.
    • SCM의 GitHub를 클릭하여 내용을 채웁니다.
      • Credentials : 이전 실습 내용에서 생성한 Credential을 선택합니다.
      • Owner : GitHub Owner를 입력합니다. https://github.com/Great-Stone/evenOdd인 경우 Great-Stone이 Owner가 됩니다.
      • Repository : 위정보가 맞는 경우 자동으로 목록이 나타납니다. evenOdd를 선택합니다.
    • Load implicitly : SCM 정보를 기입하고 다시 위쪽 Library에 있는 Load implicitly를 활성화 합니다.

Shared Libraries가 준비가 되면 Pipeline 타입의 Item을 생성하고 (e.g. 05-02.UsingSharedLibraries) Pipeline 설정을 추가합니다.

저장 후 Build Now를 클릭하여 빌드를 수행합니다. 빌드의 결과로는 2 단계로 수행되는데 1단계는 Declarative: Checkout SCM으로 SCM으로부터 소스를 받아 준비하는 단계이고, 2단계는 jenkinsfile을 수행하는 단계입니다. vars/evenOdd.goovy 스크립트에는 stage가 두개 있으나 해당 Pipeline 을 호출하는 값에 따라 하나의 stage만을 수행하도록 되어있어서 하나의 stage가 수행되었습니다.

// Jenkinsfile
+//@Library('evenOdd') _
+
+evenOdd(currentBuild.getNumber())
+

currentBuild.getNumber()는 현재 생성된 Pipeline Item의 빌드 숫자에 따라 값을 evenOdd(빌드 숫자)형태로 호출하게 됩니다.

Jenkins shared libraries를 사용하는 가장 좋은 예는 재사용성 있는 Groovy 함수를 타 작업자와 공유하는 것 입니다. 빌드의 상태는 다른 파이프 라인 단계로 계속할 것인지 결정하는 데 사용할 수도 있습니다.

주의

해당 설정은 모든 빌드에 영향을 주기 때문에 타 작업을 위해 추가된 Global Pipeline LibrariesLibrary를 삭제하여 진행합니다.

6. Notifications

Jenkins빌드의 결과를 받아볼 수 있는 몇가지 방안에 대해 알아봅니다.

Step 1. Notifications of build state

Jenkins에서는 플러그인이나 외부 툴에 의해 빌드에 대한 결과를 받아 볼 수 있습니다. 대표적으로는 Jenkins의 슬랙 플러그인을 사용하여 슬랙으로 빌드에 결과를 받아보거나, catlight.ioopen in new window 에서 데스크탑용 어플리케이션에 연동하는 방법도 있습니다.

1564463655933
1564463655933

여기서는 Chrome 확장 프로그램을 통한 알림을 받을 수 있는 설정을 설명합니다. 이 과정을 진행하기 위해서는 Chrome 웹브라우저가 필요합니다.

  1. chrome://apps/에 접속하여 앱스토어를 클릭합니다.

  2. jenkins를 검색하여 Yet Another Jenkins Notifier를 확인합니다. Chrome에 추가버튼으로 확장 프로그램을 설치합니다.

  3. 설치가 완료되면 브라우저 우측 상단에 Jenkins 아이콘이 나타납니다. 클릭합니다.

  4. 각 Item(Job)의 url을 입력하여 +버튼을 클릭합니다.

  5. 등록된 Item을 확인하고 해당 빌드를 Jenkins 콘솔에서 실행해봅니다. 결과에 대한 알림이 발생하는 것을 확인 할 수 있습니다.

    1564464197547
    1564464197547

Step 2. Build state badges for SCM

Jenkins에서 빌드가 수행된 결과를 SCM에 반영하는 기능도 플러그인을 통해 가능합니다. SCM에서 해당 Jenkins에 접근이 가능해야 하므로 Jenkins는 SCM에서 접근가능한 네트워크 상태여야 합니다.

Jenkins에 새로운 플러그인을 추가하고 설정합니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.
  • 설치 가능 탭을 클릭하고 상단의 검색에 embed를 입력하면 Embeddable Build Status플러그인이 나타납니다. 선택하여 설치를 진행합니다.
  • Jenkins 관리로 이동하여 Configure Global Security을 클릭합니다.
  • Authorization 항목에서 Matrix-based security를 체크합니다.
  • Authenticated Users의 경우 필요한 각 항목에 대해 체크박스를 활성화 합니다.
  • Anonymous Users에 대해서 Job/ViewStatus 항목을 활성화 합니다.

이제 기존의 외부 SCM이 연결된 Item을 선택합니다. 여기서는 05-02.UsingSharedLibraries에 설정합니다. 해당 Item을 선택하면 좌측에 Embeddable Build Status 항목이 새로 생긴것을 확인 할 수 있습니다.

1564464880533
1564464880533

해당 항목을 클릭하고 Markdownunprotected의 항목을 복사합니다.

[![Build Status](http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries)](http://myjenkins.com/job/05-02.UsingSharedLibraries/)
+

복사한 형식을 GitHub의 evenOdd repository의 README.mdopen in new window 파일 상단에 위치 시킵니다.

# evenOdd
+[![Build Status](http://myjenkins.com/buildStatus/icon?job=libraries)](http://myjenkins.com/job/libraries/)
+
+A Jenkins even/odd playbook from the Jenkins.io documentation
+
+Add this as a shared library called evenOdd in your jenkins
+instance, and then instantiate the pipeline in your project Jenkinsfile
+
+This will also use an example of global variabls from the log.groovy
+definitions
+
+

이같이 반영하면 각 빌드에 대한 결과를 SCM에 동적으로 상태를 반영 할 수 있습니다.

1564465713857
1564465713857

이같은 알림 설정은 코드의 빌드가 얼마나 잘 수행되는지 이해하고 추적할 수 있도록 도와줍니다.

7. Testing

Step 1. Code coverage tests and reports

테스트 Pipeline 구성시 테스트 과정을 지정할 수 있습니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-01.CodeCoverageTestsAndReports)

설정은 다음과 같이 수행합니다.

  1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh '''
    +                  echo This > app.sh
    +                  echo That >> app.sh
    +                '''
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh '''
    +                  grep This app.sh >> ${BUILD_ID}.cov
    +                  grep That app.sh >> ${BUILD_ID}.cov
    +                '''
    +            }
    +        }
    +        stage('Coverage'){
    +            steps {
    +                sh '''
    +                  app_lines=`cat app.sh | wc -l`
    +                  cov_lines=`cat ${BUILD_ID}.cov | wc -l`
    +                  echo The app has `expr $app_lines - $cov_lines` lines uncovered > ${BUILD_ID}.rpt
    +                  cat ${BUILD_ID}.rpt
    +                '''
    +                archiveArtifacts "${env.BUILD_ID}.rpt"
    +            }
    +        }
    +    }
    +}
    +
  2. 빌드가 완료되면 해당 Job화면을 리로드 합니다. Pipeline에 archiveArtifacts가 추가되었으므로 해당 Job에서 이를 관리합니다.
    1564470826126

  3. 해당 아카이브에는 코드 검증 후의 결과가 저장 됩니다.

Step 2. Using test results to stop the build

테스트 결과에 따라 빌드를 중지시키는 Pipeline 스크립트를 확인합니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-02.UsingTestResultsToStopTheBuild)

설정은 다음과 같이 수행합니다.

  1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh '''
    +                  echo This > app.sh
    +                  echo That >> app.sh
    +                  echo The Other >> app.sh
    +                '''
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh '''
    +                  for n in This That Those
    +                   do if grep $n app.sh >> ${BUILD_ID}.cov
    +                    then exit 1
    +                   fi
    +                  done
    +                '''
    +            }
    +        }
    +        stage('Coverage'){
    +            steps {
    +                sh '''
    +                  app_lines=`cat app.sh | wc -l`
    +                  cov_lines=`cat ${BUILD_ID}.cov | wc -l`
    +                  echo The app has `expr $app_lines - $cov_lines` lines uncovered > ${BUILD_ID}.rpt
    +                  cat ${BUILD_ID}.rpt
    +                '''
    +                archiveArtifacts "${env.BUILD_ID}.rpt"
    +            }
    +        }
    +    }
    +}
    +
  2. 저장을 하고 빌드를 수행하면, Pipeline 스크립트 상 Test Stage에서 조건 만족 시 exit 1를 수행하므로 빌드는 중간에 멈추게 됩니다.

    1564471729123
    1564471729123

8. REST API

Jenkins는 외부 서비스와의 연동이나 정보 조회를 위한 API를 제공합니다.

Step 1. Triggering builds via the REST API

Jenkins REST API 테스트를 위해서는 Jenkins에 인증 가능한 Token을 취득하고 curl이나 Postman 같은 도구를 사용하여 확인 가능 합니다. 우선 Token을 얻는 방법은 다음과 같습니다.

  1. Jenkins에 로그인 합니다.

  2. 우측 상단의 로그인 아이디에 마우스를 호버하면 드롭박스 버튼이 나타납니다. 설정을 클릭합니다.

  3. API Token에서 Current token을 확인합니다. 등록된 Token이 없는 경우 다음과 같이 신규 Token을 발급 받습니다.

    • ADD NEW TOKEN을 클릭합니다.

    • 이름을 기입하는 칸에 로그인 한 아이디를 등록합니다. (e.g. admin)

    • GENERATE를 클릭하여 Token을 생성합니다.

  4. 이름과 Token을 사용하여 다음과 같이 curl로 접속하면 Jenkins-Crumb 프롬프트가 나타납니다.

    $ curl --user "admin:TOKEN" 'http://myjenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
    +
    +Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605
    +
  5. Crumb를 확인했으면 다시 헤더 값에 Jenkins-Crumb:를 추가하여 02-04.MultiStep Job을 빌드하기 위해 다음과 같이 요청합니다.

    $ curl -X POST http://myjenkins.com/job/02-04.MultiStep/build --user gyulee:11479bdec9cada082d189938a3946348be --data-urlencode json='' -H "Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605"
    +

API로 호출된 빌드가 수행되어 빌드 번호가 증가하는 것을 확인합니다.

Step 2. Retriving build status via the REST API

빌드에 대한 결과를 REST API를 통해 요청하는 방법을 알아봅니다. 앞서 진행시의 Token값이 필요합니다. Json 형태로 출력되기 때문에 정렬을 위해 python이 설치 되어있다면 mjson.tool을 사용하여 보기 좋은 형태로 출력 가능합니다.

# Python이 설치되어있지 않은 경우
+$ yum -y install python2
+
+# Jenkins에 REST API로 마지막 빌드 상태 요청
+$ curl  -s --user gyulee:11479bdec9cada082d189938a3946348be http://myjenkins.com/job/02-04.MultiStep/lastBuild/api/json | python2 -mjson.tool
+
+{
+    "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
+    "actions": [
+        {
+            "_class": "hudson.model.CauseAction",
+            "causes": [
+                {
+                    "_class": "hudson.model.Cause$UserIdCause",
+                    "shortDescription": "Started by user GyuSeok.Lee",
+                    "userId": "gyulee",
+                    "userName": "GyuSeok.Lee"
+                }
+            ]
+        },
+        {},
+        {
+            "_class": "hudson.plugins.git.util.BuildData",
+            "buildsByBranchName": {
+                "master": {
+                    "_class": "hudson.plugins.git.util.Build",
+                    "buildNumber": 5,
+                    "buildResult": null,
+...
+

9. Security

Step 1. Securing your deployment with users

사용자별 배포수행을 위한 사용자 설정을 설명합니다.

  • Jenkins 관리로 이동하여 Configure Global Security를 클릭합니다.

Enable security는 보안 설정 여부를 설정하는 항목으로 기본적으로는 비활성화되어있습니다. 체크하여 활성화하면 다양한 보안 옵션을 설정할 수 있는 항목이 표기 됩니다.

  • Disable remember me : 로그인 시 사용자를 기억할지의 여부를 묻는 체크박스에 대한 활성/비활성 설정입니다.

Security Realm 에서는 Jenkins에서 사용하는 사용자 관리 방식을 선택합니다.

  • Delegate to servlet container : Jenkins를 실행하는 서블릿 컨테이너에서 접근을 관리합니다.
  • Jenkins' own user database : 기본 설정입니다. Jenkins 자체적으로 사용자 데이터를 관리합니다. 사용자의 가입 허용이 활성화되면 Jenkins 에 접속하는 사용자는 스스로 계정을 생성하고 접근 가능합니다.
  • LDAP : 외부 LDAP과 연동하여 사용자를 관리합니다. LDAP 으로 계정을 통합 관리하는 경우 유용합니다.
  • Unix user/group database : Unix/Linux에서 해당 호스트의 사용자를 관리를 기반으로 연동합니다.

Authorization 에서는 사용자 권한에 대한 설정을 정의합니다.

  • Anyone can do anything : Jenkins에 접근할 수 있는 모든 요청을 허용합니다.
  • Legacy mode : 1.164이전 버전의 동작과 동일하게 관리됩니다. Admin사용자만 모든 기능을 수행하며, 일반 사용자와 비로그인 사용자는 읽기만 가능합니다.
  • Logged-in users can do anything : 로그인만 되면 모든 기능을 수행할 수 있는 설정압니다.
  • Matrix-based security : 매트릭스 기반으로 각 기능을 사용자와 그룹별로 조절할 수 있습니다.
  • Project-based Matrix Authorization Strategy : 매트릭스 기반 권한설정의 확장으로, 개별 프로젝트 별로 권한을 조정할 수 있습니다.

다음은 권한 매트릭스의 항목과 권한별 설명입니다.

항목권한의미
OverallAdminister시스템의 전역 설정을 변경할 수 있다. OS 에서 허용된 범위안에서 전체 시스템 엑세스드의 매우 민감한 설정을 수행
Read젠킨스의 모든 페이지 확인 가능
RunScripts그루비 콘솔이나 그루비 CLI 명령을 통해 그루비 스크립트를 실행
UploadPlugins특정 플러그인을 업로드
ConfigureUpdateCenter업데이트 사이트와 프록시 설정
SlaveConfigure기존 슬레이브 설정 가능
Delete기존 슬레이브 삭제
Create신규 슬레이브 생성
Disconnect슬레이브 연결을 끊거나 슬레이브를 임시로 오프라인으로 표시
Connect슬레이브와 연결하거나 슬레이브를 온라인으로 표시
JobCreate새로운 작업 생성
Delete기존 작업 삭제
Configure기존 작업의 설정 갱신
Read프로젝트 설정에 읽기 전용 권한 부여
Discover익명 사용자가 작업을 볼 권한이 없으면 에러 메시지 표시를 하지 않고 로그인 폼으로 전환
Build새로운 빌드 시작
Workspace젠킨스 빌드를 실행 하기 위해 체크아웃 한 작업 영역의 내용을 가져오기 가능
Cancel실행중인 빌드 취소
RunDelete빌드 내역에서 특정 빌드 삭제
Update빌드의 설명과 기타 프로퍼티 수정(빌드 실패 사유등)
ViewCreate새로운 뷰 생성
Delete기존 뷰 삭제
Configure기존 뷰 설정 갱신
Read기존 뷰 보기
SCMTag특정 빌드와 관련된 소스 관리 시스템에 태깅을 생성

CSRF Protection 항목에 있는 Prevent Cross Site Request Forgery exploits 항목은 페이지마다 nonce 또는 crumb 이라 불리우는 임시 값을 삽입하여 사이트 간 요청 위조 공격을 막을 수 있게 해줍니다. 사용방법은 위에서 REST API 에 대한 설명 시 crumb 값을 얻고, 사용하는 방법을 참고합니다.

Step 2. Securing secret credentials and files

Jenkins에서 Pipeline을 설정하는 경우 일부 보안적인 값이 필요한 경우가 있습니다. 예를 들면 UsernamePassword 같은 값입니다. 앞서의 과정에서 Credentials를 생성하는 작업을 일부 수행해 보았습니다. 여기서는 생성된 인증 값을 Pipeline에 적용하는 방법을 설명합니다.

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 09-02.SecuringSecretCredentialsAndFiles) 설정은 다음과 같이 수행합니다.

  1. Pipeline 스크립트에 다음과 같이 입력 합니다.

    pipeline {
    +    agent any
    +    environment {
    +       SECRET=credentials('jenkins-secret-text')
    +    }
    +    stages {
    +        stage('Build') {
    +            steps {
    +                echo "${env.SECRET}"
    +            }
    +        }
    +    }
    +}
    +
  2. 저장 후 Build Now를 클릭하여 빌드를 수행하면 실패하게 되고 Console Output에서 진행사항을 보면, Pipeline 스크립트에서 선언한 jenkins-secret-text때문에 에러가 발생한 것을 확인할 수 있습니다.

  3. 좌측 상단의 Jenkins버튼을 클릭하여 최상위 메뉴로 이동합니다.

  4. 좌측 메뉴의 Credentials를 클릭하고 (global) 도메인을 클릭합니다.

    1564534571013
    1564534571013
  5. 좌측에 Add Credentials를 클릭하여 새로운 항목을 추가합니다.

    • Kind : Secret text
    • Secret : 해당 Credential에 담을 값을 기입합니다. (e.g. This is credential text.)
    • ID : jenkins-secret-text
  6. 저장 후 다시 빌드를 수행하면 정상적으로 수행됩니다. 해당 값은 숨기기 위한 값이므로 Pipeline 스크립트에서 echo로 호출하더라도 ****이란 값으로 표기 됩니다.

이같은 방법은 Password같은 보안에 민감한 정보를 사용하기에 유용합니다.

Step 3. Auditing your environment

Jenkins의 변화와 활동에 대한 감시를 위한 설정 방법을 설명합니다. Jenkins에 새로운 플러그인을 추가하고 설정합니다.

  • Jenkins 관리로 이동하여 플러그인 관리를 클릭합니다.
  • 설치 가능 탭을 클릭하고 상단의 검색에 audit를 입력하면 Audit Trail플러그인이 나타납니다. 선택하여 설치합니다.
  • Jenkins 관리로 이동하여 시스템 설정을 클릭합니다.
  • Audit Trail 항목이 추가되었습니다. Loggers의 ADD LOGGER드롭박스에서 Log File을 선택하여 설정합니다.
    • Log Location : /var/jenkins_home/audit/audit.log
    • Log File Size MB : 100MB
    • Log File Count : 5

저장 후 빌드나 Job의 설정 변경등의 작업을 수행하면, audit.log.0으로 지정된 파일 경로에 생성됨을 확인 할 수 있습니다.

$ tail -f ./audit.log.0
+Jul 31, 2019 10:47:32,727 AM job/02-02.Jobs/ #12 Started by user GyuSeok.Lee
+Jul 31, 2019 10:47:42,738 AM /job/03-04.WebhookBuild Triggering/configSubmit by gyulee
+Jul 31, 2019 10:48:09,001 AM /configSubmit by gyulee
+

Step 4. Using forders to create security realms

다양한 프로젝트를 관리하는 경우 관리상, 빌드 프로젝트를 관리해야할 필요성이 발생합니다. Jenkins에서 Forder 아이템을 생성하여 관리 편의성과 보안요소를 추가할 수 있습니다.

우선 테스트를 위한 사용자를 추가합니다.

  • 좌측 메뉴에서 Jenkins 관리를 클릭하여 Manage Users로 이동합니다.
  • 사용자 생성을 클릭하여 새로운 사용자를 추가합니다.
    • 계정명 : test
    • 암호 : test
    • 암호 확인 : test
    • 이름 : tester
    • 이메일 주소 : test@redhat.com

다음으로 Forder 타임의 Item을 추가합니다.

  • 좌측 메뉴에서 새로운 Item을 클릭하여 이름을 02-Project로 예를 들어 지정하고, Forder를 클릭하여 OK버튼을 클릭합니다.
    1564537956019
  • 설정 페이지가 나오면 SAVE버튼을 클릭하고 좌측 상단의 Jenkins 버튼을 클릭하여 최상위 페이지로 이동합니다.
  • 기존 프로젝트를 새로 생성한 Forder 타입으로 이동시켜 봅니다. 최상위 화면에서 02-02.Jobs에 마우스를 대면 드롭박스 메뉴를 확장할 수 있습니다. Move를 클릭합니다.
    1564538201169
  • 드롭박스에서 Jenkins >> 02-Project를 선택하고 MOVE버튼을 클릭합니다. 다시 최상위 메뉴로 오면 02-02.Jobs가 사라진 것을 확인할 수 있습니다. 02 로 시작하는 다은 프로젝트도 같은 작업을 수행하여 이동시킵니다.
  • 02-Project를 클릭하면 이동된 프로젝트들이 나타납니다.
    1564538419183

권한 설정을 하여 현재 Admin 권한의 사용자는 접근 가능하고 새로 생성한 tester는 접근불가하도록 설정합니다.

  • Folder에 접근하는 권한을 설정하기위해 Jenkins 관리Configure Global Security로 이동합니다.

  • Authorization항목의 Project-based Matrix Authorization Strategy를 선택합니다.

  • ADD USER OR GROUP...을 클릭하여 Admin 권한의 사용자를 추가합니다.

  • Admin 권한의 사용자에게는 모든 권한을 주고 Authenticated Users에는 Overall의 Read 권한만 부여합니다.
    1564543546039

  • 생성한 02-Project로 이동하여 좌측 메뉴의 Configure를 클릭합니다.

  • Properties에 추가된 Enable project-based security를 확성화하면 항목별 권한 관리 메트릭스가 표시됩니다. Job의 Build, Read, ViewStatus, Workspace를 클릭하고 View의 Read를 클릭하여 권한을 부여합니다.

    1564540010695
    1564540010695
  • 로그아웃 후에 앞서 추가한 test사용자로 로그인 하면 기본적으로 다른 프로젝트나 Item들은 권한이 없기 때문에 보이지 않고, 앞서 설정한 02-Project 폴더만 리스트에 나타납니다.
    1564543591394

Jenkins의 인증 기능을 사용하여 보안적 요소를 구성할 수 있습니다. Audit 로그를 활용하여 사용자별 활동을 기록할 수도 있고 Folder를 활용하면 간단히 사용자/그룹에 프로젝트를 구분하여 사용할 수 있도록 구성할 수 있습니다.

10. Artifacts

빌드 이후 빌드의 결과를 기록하고 저장하는 방법을 설명합니다.

Step 1. Creating and storing artifacts

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-01.CreatingAndStoringArtifacts)

Pipeline에 다음과 같이 스크립트를 추가합니다.

pipeline {
+    agent any
+    stages{
+        stage('Build') {
+            steps{
+                sh 'echo "Generating artifacts for ${BUILD_NUMBER}" > output.txt'
+            }
+        }
+        stage('Archive') {
+            steps {
+                archiveArtifacts artifacts: 'output.txt', onlyIfSuccessful: true
+            }
+        }
+    }
+}
+

Archive Stage에 archiveArtifacts 스크립트가 동작하는 예제입니다. 이같은 Pipeline 스크립트 작성을 도와주는 툴을 추가로 확인해 봅니다.

  • Pipeline 하단의 Pipeline Syntax 링크를 클릭합니다.
    1564546308113
  • Sample Step에서 archiveArtifacts: Archive the artifacts를 선택합니다.
    • Files to archive : output.txt
    • 고급...을 클릭합니다.
    • 다음 항목을 활성화 합니다.
      • Archive artifacts only if build is successful
      • Use default excludes
      • Treat include and exclude patterns as case sensitive
    • 하단의 GENERATE PIPELINE SCRIPT를 클릭합니다.

결과물을 확인하면 Pipeline 스크립트에 작성한 형태와 같은 것을 확인 할 수 있습니다.

1564545470815
1564545470815

좌측 메뉴의 Build Now를 클릭하여 빌드 수행 후에 화면에 Artifacts 항목이 추가된 것을 확인할 수 있습니다. UI 상에는 마지막 빌드 결과가 강조되어 나오고 각 빌드에 대한 결과물은 각각의 빌드단계의 다운로드 버튼으로 확인하고 다운로드 할 수 있습니다.

1564545639205
1564545639205

Step 2. Fingerprinting for artifact tracking

빌드 이후 보관되는 파일에 대해 어떤 프로젝트, 어떤 빌드 에서 발생한 결과물인지 확인할 수 있는 핑거프린팅 기능을 설명합니다.

Step 1의 프로젝트를 그대로 사용하거나 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-02.FingerprintingForArtifactTracking)

Step 1 Pipeline 스크립트의 archiveArtifactsfingerprint: true를 추가합니다.

pipeline {
+    agent any
+    stages{
+        stage('Build') {
+            steps{
+                sh 'echo "Generating text artifacts: Build:${BUILD_NUMBER}" > output.txt'
+            }
+        }
+        stage('Archive') {
+            steps {
+                archiveArtifacts artifacts: 'output.txt', fingerprint: true, onlyIfSuccessful: true
+            }
+        }
+    }
+}
+

파일의 지문을 확인합니다.

  • 첫번째 빌드를 수행하고 빌드 결과 아카이브 파일 output.txt파일을 다운로드 받습니다. (파일을 우클릭하고 다른 이름으로 링크 저장... or Download Linked File 을 클릭하여 파일을 받습니다.)
    1564546697375

  • 좌측 상단의 Jenkins를 클릭하여 최상위 메뉴로 돌아갑니다.

  • 좌측 메뉴의 파일 핑거프린트 확인을 클릭합니다.

  • 파일 선택버튼을 클릭하여 앞서 다운로드한 파일을 선택하고 확인하기버튼을 클릭합니다.

  • 어떤 프로젝트의 몇번째 빌드에서 발생한 파일인지 확인합니다.

  • 두번째 빌드를 수행하고 파일 핑거프린트를 확인해 봅니다.

  • 빌드 번호 정보가 변경된 것을 확인합니다.

11. Pipelines

Pipeline에 대해 설명합니다.

Step 1. Automating deployment with pipelines

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-01.AutomatingDeploymentWithPipelines)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+                sh 'echo "Hello World"'
+            }
+        }
+        stage('Test') {
+            steps {
+                sh 'echo "Test Hello World!"'
+            }
+        }
+    }
+}
+

두개의 Stage를 갖는 Pipeline 스크립트입니다. Pipeline은 빌드 수행시의 각 단계를 구분하여 빌드의 과정을 확인하고 실패에 따른 단계별 확인이 가능합니다.

좌측 Build Now를 클릭하여 빌드를 수행하면 빌드에 대한 결과는 Stage 별로 성공 실패의 여부와 로그를 확인할 수 있도록 Stage View가 UI로 제공됩니다. Stage 별로 Stage View는 기록되며, Stage에 변경이 있거나 이름이 변경되는 경우에는 해당 UI에 변경이 발생하여 기존 Pipeline 기록을 보지 못할 수 있습니다.

1564547435978
1564547435978

Step 2. Creating pipeline gates

Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-02.CreatingPipelineGates)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+                sh 'echo "Hello World"'
+            }
+        }
+        stage('BuildMore'){
+            steps {
+                input message: "Shall we build more?"
+                sh '''
+                    echo "We are approved; continue!"
+                    ls -lah
+                '''
+            }
+        }
+    }
+}
+

개의 Stage를 갖는 Pipeline 스크립트입니다. 두번째 Stage에 input 스크립트가 있습니다. 이 스크립트가 추가되면 Pipeline을 진행하면서 해당하는 동작을 수행할 것인지, 마치 승인 작업과 같은 동작을 수행할 수 있습니다.

좌측 Build Now를 클릭하여 빌드를 수행하면 두번째 Stage에서 해당 작업을 수행할 지에 대한 물음을 확인 할 수 있습니다.

1564547694870
1564547694870

Abort를 선택하면 빌드 취소와 같은 동작으로 실패로 처리되지는 않습니다.

Step 3. Job promotion for long-running pipeline

빌드 단계를 구현할 때 Pipeline 스크립트로 하나의 프로젝트 내에서 모든 동작을 정의 할 수도 있지만 서로다른 Job을 연계하고, 승인 절차를 따르도록 구성할 수 있습니다.

Job promotion 기능을 사용하기 위한 플러그인을 설치합니다.

  • Jenkins 관리에서 플러그인 관리를 선택합니다.
  • 설치 가능 탭을 클릭하고 상단의 검색에 promoted를 입력하면 promoted builds를 확인 할 수 있습니다. 설치합니다.

FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-one)

  • General 탭의 Promote builds when...를 활성화 하여 설정합니다.

    • Name : Manual
    • Criteria 설정의 Only when manually approved 활성화
      • Approvers : 승인자를 입력합니다. (e.g. admin)
      • ADD PRAMETER 드롭박스에서 Boolean Parameter를 선택합니다.
        • Name : approve
  • Build 드롭박스에서 Execute shell을 선택합니다.

  • 다음을 입력합니다.

    echo 'This is the Job-one'
    +
  • 저장하면 생성된 프로젝트에 Promotion Status항목이 추가되어 생성됩니다.

11-03.Job-one 빌드 후 승인에 대한 다음 빌드를 진행할 FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-two)

  • 빌드 유발 항목에서 Build when another project is promoted를 활성화 합니다. 어떤 Job에서 promote 상황이 발생하였을 때 빌드를 수행할지 지정합니다.

    • Job Name : 11-03.Job-one
    • Promotion : Manual
  • Build 드롭박스에서 Execute shell을 선택합니다.

  • 다음을 입력합니다.

    echo 'This is the Job-two'
    +

11-03.Job-one에 대한 빌드를 수행합니다. 수행 완료 후 빌드 히스토리의 최근 빌드를 클릭(e.g. #1)하면 Promotion Status에 승인절차를 기다리고 있음을 확인할 수 있습니다. Parameters 항목의 approve를 체크하고 APPROVE버튼을 클릭합니다.

1564554095622
1564554095622

승인이 완료되면 해당 프로젝트의 승인에 대한 이벤트를 통해 빌드를 수행하는 11-03.Job-two가 이어서 빌드됨을 확인 할 수 있습니다.

Step 4. Multibranch repository automation

SCM의 Multibranch를 빌드하는 과정에 대해 설명합니다.

다음의 GitHub repository를 fork 합니다.

Multibranch Pipeline 형태의 Item을 생성합니다. (e.g. 11-04.MultibranchRepositoryAutomation)

  • Branch Sources의 ADD SOURCE드롭박스에서 GitHub를 클릭합니다.
    • Credentials에서 앞서 생성한 GitHub 접속을 위한 Credential을 선택합니다.
    • Repository HTTPS URL에 앞서 fork한 GitHub URL을 입력하고 VALIDATE버튼을 클릭하여 잘 접근 되는지 확인합니다.
  • Scan Multibranch Pipeline Triggers에서 Periodically if not otherwise run를 활성화 합니다.
    • Interval 주기를 1 minute으로 설정합니다.

저장 후에는 자동적으로 모든 브랜치의 소스를 빌드 수행합니다.

1564555063361
1564555063361
1564554995103
1564554995103

SCM에서 브랜치를 여러개 관리하고 모두 빌드와 테스팅이 필요하다면 Multibranch 프로젝트를 생성하여 등록하고, 빌드 관리가 가능합니다.

Step 5. Creating pipeline with snippets

Pipeline 을 스크립트를 작성하는 방법을 배워봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-05. CreatingPipelineWithSnippets)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage("Hello") {
+            steps {
+                echo 'Hello World'
+            }
+        }
+    }
+}
+

echo가 동작할때 시간을 기록하도록 스크립트를 수정해보겠습니다.

  • Pipeline Syntax 링크를 클릭합니다.

  • Sample Step에서 timestamps: timestamps를 선택하고 GENERATE PIPELINE SCRIPT버튼을 클릭합니다.

    timestamps {
    +    // some block
    +}
    +
  • 사용방식을 확인하고 앞서 Pipeline 스크립트의 stage에 시간을 기록하도록 수정합니다.

    ...
    +stage("Hello") {
    +    steps {
    +        timestamps {
    +            echo 'Hello World'
    +        }
    +    }
    +}
    +...
    +

빌드를 수행하고 로그를 확인해 봅니다. echo 동작이 수행 될때 시간이 함께 표기되는 것을 확인 할 수 있습니다.

1564555730104
1564555730104

Step 6. Discovering global pipeline variables

Pipeline에서 사용할 수 있는 변수를 확인하고 사용하는 방법을 알아봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-06.DiscoveringGlobalPipelineVariables)

Pipeline에 다음과 같은 스크립트를 입력합니다.

pipeline {
+    agent any
+    stages {
+        stage('Build') {
+            steps {
+               echo "We are in build ${currentBuild.number}"
+               echo "Our current result is ${currentBuild.currentResult}"
+            }
+        }
+        stage('BuildMore'){
+            steps {
+               echo "Name of the project is ${currentBuild.projectName}"
+            }
+        }
+        stage('BuildEnv'){
+            steps {
+                echo "Jenkins Home : ${env.JENKINS_HOME}"
+            }
+        }
+    }
+}
+

Pipeline 스크립트에서 사용가능한 변수와 사용방법은 Pipeline Syntax 링크의 Global Variables Reference 항목에서 확인 가능합니다.

1564557613406
1564557613406

Apendix

GitHub SCM 연동 이슈

GitHub를 SCM으로 사용하는 경우 다음과 같은 메시지가 출력되면서 진행되지 않는 경우가 있습니다.

GitHub API Usage: Current quota has 5000 remaining (447380 over budget). Next quota of 5000 in 5 days 0 hr. Sleeping for 4 days 23 hr.
+14:07:33 GitHub API Usage: The quota may have been refreshed earlier than expected, rechecking...
+

이 경우 서버 시간과 GitHub의 시간이 맞지 않아 발생할 수 있는 이슈 입니다. ntpdate를 재설정 합니다.

유용한 플러그인

  • Restart Safely : Jenkins를 재기동해야하는 경우 빌드가 수행중이지 않을 때 자동으로 Restart 시켜줍니다. 설치 후에는 왼쪽 주 메뉴에 표시됩니다.
  • ThinBackup : Jenkins의 구성을 백업, 복구할 수 있는 기능을 제공합니다. 백업 주기나 백업 개수등을 정의 할 수 있습니다.
+ + + diff --git a/05-Software/Jenkins/pipeline101/index.html b/05-Software/Jenkins/pipeline101/index.html new file mode 100644 index 0000000000..be3d542893 --- /dev/null +++ b/05-Software/Jenkins/pipeline101/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Pipeline101 | docmoa + + + + + +
본문으로 건너뛰기

Pipeline101

1분 미만

+ + + diff --git a/05-Software/Tomcat/index.html b/05-Software/Tomcat/index.html new file mode 100644 index 0000000000..d0d7c0e0a0 --- /dev/null +++ b/05-Software/Tomcat/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Tomcat | docmoa + + + + + +
본문으로 건너뛰기

Tomcat

1분 미만

+ + + diff --git a/05-Software/Tomcat/tomcat101/01-Introduction.html b/05-Software/Tomcat/tomcat101/01-Introduction.html new file mode 100644 index 0000000000..761f14ec3c --- /dev/null +++ b/05-Software/Tomcat/tomcat101/01-Introduction.html @@ -0,0 +1,40 @@ + + + + + + + + + + 1. Tomcat 소개 | docmoa + + + + + +
본문으로 건너뛰기

1. Tomcat 소개

약 1 분TomcatJava

1. Tomcat 소개

본 내용은 톰캣을 좀더 잘 알고 잘 써보기 위한 제안이랄까요?

톰캣의 특성상 쉽게 접할 수 있는 메뉴얼적인 지식보다는, 톰캣을 더 잘 사용하고 운영 할 수 있을만한 아이디어를 공유하고자 시작한 지식공유 활동입니다. 담고 있는 내용은 '톰캣 알고 쓰기open in new window' 유튜브 강의 내용에 대한 정리입니다. 유튜브에 강의를 올리면 출퇴근 시간을 이용해 짬짬히 들을 수 있을 것 같은 생각이 들어 시작하였지만 얼마나 출퇴근 시간에 이용하셨을지는 미지수이고 동영상으로 모든 것을 다 표현할 수 없다는 점을 감안하여 다시 글로 정리합니다.

톰캣을 잘 사용하고 이해하는 것은 개발자, 운영자, 기술자, 엔지니어등 톰캣을 접하는 모두에게 요구되는 사항이지만 일반적으로는 필요한 기능과 기술만을 습득하게 됩니다. 이번 강의를 통해 약간의 시간 투자로 모두에게 도움이 되었으면 합니다.

특히 WEB/WAS 엔지니어로 자주 겪었던 개발은 톰캣으로하고 정작 운영은 다른 WAS를 사용하는 아이러니함에도 도움이 되었으면 합니다.


  • 왜 톰캣을 쓰는가?
  • 톰캣 이력
  • 톰캣 구성
  • TomEE?

1.1 왜 톰캣을 쓰는가?

톰캣을 사용하는 첫번째 이유는 기능적인 이유일 것입니다. 즉, 톰캣이 수행하는 역할인 JSP/Servlet 엔진으로서의 역할이겠지요. 톰캣은 세계적으로 가장 많은 Java 기반의 웹어플리케이션 플랫폼으로 사용되고 있습니다. 많은 개발자들이 자신의 첫번째 Java 웹 어플리케이션의 JSP/Servlet 엔진으로 선택하고 있고 실제 운영환경에서도 사용하기에 상당한 양의 노하우를 쉽게 접할 수 있다는 장점이 있습니다.

두번째로는 아마도 무료로 사용할 수 있다는 점이 톰캣을 사용하게 되는 이유일 것입니다. 수많은 벤더사에서 JSP/Servlet 엔진과 추가로 Java Enterprise 기능을 사용할 수 있는 세련되고 검증된 자신만의 제품을 개발하고 상용화 하고 있습니다. 하지만 이러한 제품은 비용이 추가된다는 (큰)고민이 생깁니다. 물론 유지보수 계약을 통해 기술지원과 더불어 든든한 책임전가의 대상(?)인 벤더사가 존재한다는 장점이 있지만 모든 사용자가 이런 비용을 지불할 수 있는것은 아니겠지요.

2020년 JRebel 자료
Most Popular Java Application Server

2014년 자료
rebellabs:App Server Most of Used
rebellabs:App Server Most of Used

Java 웹 어플리케이션을 실행하는 Application Server의 종류는 거의 30가지에 달하나 조사된 JRebel의 통계open in new window에 따르면 여전히 과반 이상을 Tomcat이 점유하고 있다고 합니다. 여러가지 이유가 더 있겠지만 앞서 언급한 많은 레퍼런스와 무료라는 큰 특징으로 인해 수많은 사용자가 톰캣을 사용하고 있습니다.

JRebel에서 언급한것과 같이 Spring Boot, Docker, Hybris 및 AWS와 같은 다른 주요 Java 플랫폼과의 호환성이 뒷받침이 되는것 같습니다.


1.2 톰캣 이력

톰캣의 정식 명칭은 Apache Tomcat Server 입니다. 이를 줄여 톰캣 Tomcat 이라고 흔히 불려지고 있지요. 톰캣의 간단한 이력은 다음과 같습니다.

기준2021년 12월 23일
DeveoloperApache Software Foundation
Last Stable release10.0.14
Development StatusActive
Written inJava
Operating SystemCross-Platform
TypeServlet Container / HTTP Web Server
LicenseApache License 2.0
Websitetomcat.apache.org

현재까지 출시된 버전은 10.0.14 버전이고 앞으로도 지속적으로 업데이트가 있을 예정입니다. 무료로 제공되는 이유로 인해 톰캣을 기반으로 한 소프트웨어들이 있는데, 이경우 개발된 시점을 기준으로 톰캣 버전이 고정되어 있는 경우가 있습니다. 4.0 버전은 최근 보이지 않지만 JDK 1.4.2 를 사용하던 시기가 가장 많은 Java 웹 어플리케이션이 개발되던 시기이기에 톰캣 5.5 버전은 아직도 상당히 많은 사용처가 있을것이라 보여집니다.


1.3 톰캣 구성 요소

톰캣을 구성하는 핵심적인 요소는 다음의 세가지 컴포넌트입니다.

Tomcat_Component
Tomcat_Component

Catalina는 아마도 톰캣을 사용하면 가장 많이 보게되는 단어 중 하나일 것입니다.

  • Catalina 컴포넌트는 서블릿을 처리하는 역할을 수행합니다.
  • Coyote는 HTTP 요청을 처리하는 역할을 수행하는 컴포넌트로서 톰캣에 TCP를 통한 프로토콜을 생성하고 관리합니다.
  • Jasper는 jsp 페이지를 처리하는 컴포넌트로서 jsp 로 만들어진 페이지를 처리하는 서블릿 입니다.

처리되는 컴포넌트의 역할을 이해한다면 톰캣에서 어플리케이션 수행시 발생되는 코드 스텍을 이해하는데 도움이 될 수 있습니다.


1.4 TomEE?

톰캣에 대하여 흔히들 '톰캣은 완전한 WAS(Web Application Server)가 아니다'라고 합니다. 앞서 설명하였지만 톰캣은 JSP/Servlet 엔진의 역할을 수행합니다. 하지만 Java Enterprise 기능인 EJB, JTA, JMS, WebService 등은 포함되어 있지 않죠. 이러한 이유로 WAS의 일부 기능만을 수행 할 수 있을 뿐 WAS는 아니다 라고 합니다. 이러한 Enterprise 요소를 지원하기위한 요구사항으로 OpenEJB나 Apache ActiveMQ, Apache CXF등의 컴포넌트 요소가 톰캣과는 별도의 프로젝트로 진행되어 해당 컴포넌트들을 결합함으로 톰캣에서 이를 지원할 수 있었습니다. 이제는 어느정도 시간이 지나 Enterprise 컴포넌트와의 연계성이 뚜렷해졌고 통합이 가능해짐에 따라 톰캣을 Java Enterprise 스펙에 맞게 재 조정하는 프로젝트가 시작됩니다. 이를 TomEE(Tomcat Enterprise Edition)이라 합니다.

TomEE의 대표 홈페이지는 tomee.apache.orgopen in new window이며 상세한 설명과 문서가 준비되어있기 때문에 사용하는데 큰 어려움이 없습니다. TomEE에서 지원하는 Java Enterprise 컴포넌트들은 다음과 같습니다.

https://tomee.apache.org/comparison.htmlopen in new window

TomcatTomEE WebProfileTomEE MicroProfileTomEE PlumeTomEE Plus
Jakarta Annotations✔︎✔︎✔︎✔︎✔︎
Jakarta Debugging Support for Other Languages✔︎✔︎✔︎✔︎✔︎
Jakarta Security (Java EE Enterprise Security)✔︎✔︎✔︎✔︎✔︎
Jakarta Server Pages (JSP)✔︎✔︎✔︎✔︎✔︎
Jakarta Servlet✔︎✔︎✔︎✔︎✔︎
Jakarta Standard Tag Library (JSTL)✔︎✔︎✔︎✔︎✔︎
Jakarta WebSocket✔︎✔︎✔︎✔︎✔︎
Jakarta Expression Language (EL)✔︎✔︎✔︎✔︎✔︎
Jakarta Activation✔︎✔︎✔︎✔︎
Jakarta Bean Validation✔︎✔︎✔︎✔︎
Jakarta Contexts and Dependency Injection (CDI)✔︎✔︎✔︎✔︎
Jakarta Dependency Injection (@Inject)✔︎✔︎✔︎✔︎
Jakarta Enterprise Beans (EJB)✔︎✔︎✔︎✔︎
Jakarta Interceptors✔︎✔︎✔︎✔︎
Jakarta JSON Binding (JSON-B)✔︎✔︎✔︎✔︎
Jakarta JSON Processing (JSON-P)✔︎✔︎✔︎✔︎
Jakarta Mail (JavaMail)✔︎✔︎✔︎✔︎
Jakarta Managed Beans✔︎✔︎✔︎✔︎
Jakarta Persistence (JPA)✔︎✔︎✔︎✔︎
Jakarta RESTful Web Services (JAX-RS)✔︎✔︎✔︎✔︎
Jakarta Server Faces (JSF)✔︎✔︎✔︎✔︎
Jakarta Transactions (JTA)✔︎✔︎✔︎✔︎
Jakarta XML Binding (JAXB)✔︎✔︎✔︎✔︎
Jakarta Authentication (JAAS)✔︎✔︎
Jakarta Authorization (JACC)✔︎✔︎
Jakarta Concurrency✔︎✔︎
Jakarta Connectors✔︎✔︎
Jakarta Enterprise Web Services✔︎✔︎
Jakarta Messaging (JMS)✔︎✔︎
Jakarta SOAP with Attachments✔︎✔︎
Jakarta Web Services Metadata✔︎✔︎
Jakarta XML Web Services (JAX-WS)✔︎✔︎
Jakarta Batch (JBatch)✔︎

현재 국내의 개발 환경이나 운영 환경에서는 이전에 잠시 EJB가 사용되었을 뿐 최근 전자정부 프레임워크에서도 보이듯 Enterprise 환경을 요구하는 상황은 거의 없는 것으로 보입니다. 아무래도 WAS 의존성을 낮추기 위해서는 벤더에서 주도하여 각각의 컴포넌트를 제공하는 Enterprise 구성 요소에 대한 의존도를 낮출 수 있는 방향이기는 하겠지요. 하지만 일부의 경우는 Enterprise 구성 요소를 잘 사용하기만 하면 어렵지 않게 안정적이고 보안적으로 보장되는 서비스의 구현이 가능할 것입니다.

부가적인 설명을 드리자면 JDK 버전과 Java Enterprise 버전은 서로 범위가 다릅니다. Java Standard기능은 JDK 버전과 같지만 Java Enterprise 기능은 JDK 버전과는 별개로 WAS에서 지원하는 컴포넌트의 요소에 따라 그 버전을 달리 합니다. 앞서 대표적인 국내 프레임워크로 Spring 프레임워크를 사용하는 전자정부 프레임워크를 예로 들었지만 국내와는 달리 국외 통계를 본다면 Java Enterprise의 사용은 그리 생소하지 않습니다.

따라서 Java Enterprise 환경을 고려한다면 톰캣을 기반으로 한 TomEE를 활용하는 방법도 톰캣에 익숙한 개발자들에게는 도움이 될 수 있겠습니다.

+ + + diff --git a/05-Software/Tomcat/tomcat101/02-env.html b/05-Software/Tomcat/tomcat101/02-env.html new file mode 100644 index 0000000000..6a028edb8f --- /dev/null +++ b/05-Software/Tomcat/tomcat101/02-env.html @@ -0,0 +1,40 @@ + + + + + + + + + + 2. Tomcat 설치환경 | docmoa + + + + + +
본문으로 건너뛰기

2. Tomcat 설치환경

약 1 분TomcatJava

2. Tomcat 설치환경


2.1 OS

톰캣을 설치하는 OS 플랫폼 환경은 모든 환경을 지원합니다. 그나마 예전에는 일부 Unix/Linux/OSX 환경에서 Apache HTTP Server 설치하듯 컴파일을 통해 구성하였으나, 최근에는 압축파일을 해제하고 바로 사용할 수 있는 경우가 대부분입니다.
톰캣을 운영하기 위해 OS를 선택해야하는 입장이라면 다음과 같은 설치 타입을 고려할 수 있습니다.

  • Windows : Zip 파일을 풀어 사용하거나 msi 설치로 윈도우 서비스에 등록하는 설치
  • Unix/Linux/OSX : 압축 파일을 풀어 사용하거나 컴파일하여 설치

2.2 JDK

https://tomcat.apache.org/whichversion.htmlopen in new window

톰켓의 버전이 올라감에 따라 지원하는 Java Standard Spec Version 또한 변경됩니다. 이 경우 일부 상위 버전은 JDK의 특정 버전에서 지원되지 않을 수 있지요. 따라서 개발되는 어플리케이션의 JDK요구치나 표준화된 톰캣 버전에 따라 지원되는 JDK 버전이 상이할 수 있습니다. 다음의 표를 참고하시기 바랍니다.

Servlet SpecJSP SpecEL SpecWebSocket SpecAuthentication (JASPIC) SpecApache Tomcat VersionLatest Released VersionSupported Java Versions
6.03.15.02.13.010.1.x10.1.0-M8 (alpha)11 and later
5.03.04.02.02.010.0.x10.0.148 and later
4.02.33.01.11.19.0.x9.0.568 and later
3.12.33.01.11.18.5.x8.5.737 and later
3.12.33.01.1N/A8.0.x (superseded)8.0.53 (superseded)7 and later
3.02.22.21.1N/A7.0.x (archived)7.0.109 (archived)6 and later (7 and later for WebSocket)
2.52.12.1N/AN/A6.0.x (archived)6.0.53 (archived)5 and later
2.42.0N/AN/AN/A5.5.x (archived)5.5.36 (archived)1.4 and later
2.31.2N/AN/AN/A4.1.x (archived)4.1.40 (archived)1.3 and later
2.21.1N/AN/AN/A3.3.x (archived)3.3.2 (archived)1.1 and later

톰캣 5.5.x 버전의 경우 5.5.12 버전 이후로는 JDK 5 이상을 지원함에 유의합니다.

Java SE의 경우 OS 플랫폼에 따라 제공하는 벤더가 다른 경우가 있습니다. Oracle이 서브스크립션 형태로, 업데이트에 대해 유료화 선언을 한 이후로 여러 파생 Java를 고려할 수 있습니다. 여전히 8 버전을 사용하는 서비스가 많아 OracleJDK가 점유율이 높으나, 이후 높은 버전으로 이전시에는 다른 JDK를 고려하는 상황도 발생할 것으로 보입니다.

Most Popular JRE/JDK Distribution (JRebel, 2020)
2020Java

3. Java 제공자

ProviderFree Builds from SourceFree Binary DistributionsExtended UpdatesCommercial SupportPermissive License
AdoptOpenJDKopen in new windowYesYesYesNoYes
Amazon – Correttoopen in new windowYesYesYesNoYes
Azul Zuluopen in new windowNoYesYesYesYes
BellSoft Libericaopen in new windowNoYesYesYesYes
IBMopen in new windowNoNoYesYesYes
OpenJDK Upstreamopen in new windowYesYesYesNoYes
Oracle JDKopen in new windowNoYesNoYesNo
Oracle OpenJDKopen in new windowYesYesNoNoYes
ojdkbuildopen in new windowYesYesNoNoYes
RedHatopen in new windowYesYesYesYesYes
SapMachineopen in new windowYesYesYesYesYes
  • Oracle의 경우 Java가 필요한 미들웨어를 구매한 경우 Java에 대한 지원이 포함됩니다.
  • RedHat의 경우 RedHat Linux, RedHat Middelware를 서브스크립션하는 경우 Java에 대한 지원이 포함됩니다.
  • AIX : IBM에서 제공하는 JDK를 사용합니다.
  • HP-UX : HP에서 제공하는 JDK를 사용합니다.
  • AIX의 JDK경우 Windows환경에도 설치가 가능하기는 하지만 일반적으로는 Oracle에서 제공하는 기존 SunJDK를 설치하여 사용합니다.
  • OSX(Mac)는 JDK6 까지는 Apple사에서 제공하지만 JDK7부터는 Oracle에서 설치파일을 받아 설치합니다.
+ + + diff --git a/05-Software/Tomcat/tomcat101/03-installation.html b/05-Software/Tomcat/tomcat101/03-installation.html new file mode 100644 index 0000000000..095b86e411 --- /dev/null +++ b/05-Software/Tomcat/tomcat101/03-installation.html @@ -0,0 +1,60 @@ + + + + + + + + + + 3. Tomcat 설치 | docmoa + + + + + +
본문으로 건너뛰기

3. Tomcat 설치

약 1 분TomcatJava

3. Tomcat 설치

  • 설치파일 받기
  • 윈도우에 설치하기
  • 유닉스/리눅스에 설치하기
  • 설치 후 작업
  • 디렉토리 구조

설치되는 톰캣의 최상위 경로는 $CATALINA_HOME 으로 표현합니다.

3.1 설치파일 받기

설치파일은 톰캣 홈페이지open in new window에서 좌측의 Downloads에서 설치하고자 하는 버전을 선택하면 우측에 Binary와 Source Code 두가지 형태로 받을 수 있습니다. Binary는 OS플랫폼에 맞게 미리 준비된 설치파일로서 Core에 해당하는 파일을 받습니다. 종류는 여섯가지로 나뉘어 있습니다.

zip과 tar.gz는 Unix/Linux환경에서 사용할 압축된 형태의 파일이고 나머지 네가지는 CPU 아키텍쳐에 맞게 컴파일된 zip 형태의 압축 파일과 서비스에 등록할수 있는 형태의 인스톨러로 구성되어 있습니다. 설치하고자 하는 OS와 CPU 아키텍처에 맞는 설치파일을 받아 준비합니다.

3.2 윈도우에 설치하기

Windows에는 압축파일을 풀어 설치하는 방법과 서비스 등록을 위한 인스톨러 두가지 방식이 있었습니다. 압축파일 형태의 경우 압축을 풀기만하면 바로 실행이 가능합니다. 서비스 인스톨러의 경우 서비스에 등록하기 위한 설치 경로와 같은 정보를 입력하여 진행합니다.

압축파일로 설치한 경우 %CATALINA_HOME%\bin에 위치한 startup.bat으로 시작하고 shutdown.bat으로 종료합니다.

서비스로 설치한 경우 윈도우 서비스 관리 유틸리티에서 서비스의 '시작/종료'를 사용하거나 net start (서비스이름) 또는 net stop (서비스이름)을 사용하여 서비스의 시작과 종료가 가능합니다.

3.3 유닉스/리눅스/맥 에 설치하기

Unix/Linux의 binary 설치파일은 압축을 풀어 설치합니다.

$CATALINA_HOME/bin에 위치한 startup.sh으로 시작하고 shutdown.sh으로 종료합니다.

3.4 설치 후 작업

설치 방법은 매우 간단하나 설치 후 꼭 해야할 작업이 있습니다. Java Home을 설정하고 성능을 위한 Native library를 설치하는 작업 입니다.

Java Home의 경우 앞서 JDK를 OS에 설치하였다면 톰캣에서 이를 사용할 수 있도록 경로를 잡아주는 과정입니다. OS자체의 PATH나 환경변수로 지정하는 방법도 있고 톰캣의 스크립트에 변수로 넣어주는 방법이 있습니다. OS자체의 PATH로 설정하는 경우 해당 OS에 설치된 모든 Java 실행환경이 영향을 받게 됩니다. 따라서 일관된 서비스, 일관된 톰캣 운영 환경인 경우 같은 Java Home이 설정되는 장점이 있습니다. 이와달리 스크립트에 Java Home을 설정하면 해당 톰캣에서만 관련 설정의 영향을 받습니다. OS내에 서로 다른 JDK로 동작하는 서비스나 어플리케이션이 있다면 스크립트를 이용하는 방법을 사용할 수 있습니다.

3.4.1 Java Home 설정하기

OS 환경에 Java Home을 설정하는 방법은 다음과 같습니다. (Java Home은 JDK의 bin 디렉토리를 포함한 상위 디렉토리 입니다.)

3.4.1.1 환경변수로 지정

Unix/Linux/OSX

계정 루트의 .profile (bash 쉘의 경우 .bash_profile)에 다음을 설정

# .bash_profile
+export JAVA_HOME=/usr/java/jre1.8.0_241
+

3.4.1.2 스크립트에 추가

스크립트에 Java_Home을 설정하는 경우 catalina.sh(bat)JAVA_HOME 으로 지정하는 경우가 있습니다. 추가로 setenv.sh(bat)을 생성하여 해당 스크립트에 설정하는 방법을 권장합니다.

Unix/Linux/OSX
# setenv.sh
+JAVA_HOME="/usr/java/jre1.8.0_241"
+

Windows환경에서 Java를 C:\Program Files에 설치하는 경우 중간에 공백이 있기 때문에 C:\Progra~1로 표현함에 주의합니다.

3.4.2 Native Library 적용하기 (옵션)

톰캣의 Native Library를 적용하지 않고도 충분히 톰캣을 실행하고 사용할 수 있습니다. 다만 톰캣의 콘솔 로그에 다음의 메시지가 걸리적(?)거리게 발생합니다.

The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path
+

굳이 필요하지 않다면 적용하지 않아도 되지만 Native Library가 주는 이점은 다음과 같습니다.

  • Non-blocking I/O for Keep-Alive requests (between requests)
  • Uses OpenSSL for TLS/SSL capabilities (if supported by linked APR library)
  • FIPS 140-2 support for TLS/SSL (if supported by linked OpenSSL library)

결국 NIO(Non-Blocking I/O)를 지원하는 것과 SSL관련 설정을 지원합니다. OpenSSL을 사용하지 않아도 NIO를 사용하는 성능상의 이점을 제공합니다.

Native Library를 사용하기 위해서는 APR과 Tomcat Native 소스 파일이 필요합니다. APR은 Apache 2.0 이상 버전이 설치되어있다면 해당 설치 경로의 $APACHE_HOME/bin/apr-1-config과 같이 존재하나 없는 경우 별도의 APR을 설치합니다.

(Apache APR 홈페이지 : http://apr.apache.org/open in new window)

APR 홈페이지에서 받은 apr-x.x.x.tar.gz 파일은 다음의 순서를 따라 설치를 진행하며, 예제와 같이 /usr/local에 설치하는 경우 root 계정이 필요합니다.

$ gzip -d apr-1.5.1.tar.gz
+$ tar -xvf apr-1.5.1.tar
+$ configure --prefix=/usr/local/apr-1.5.1
+$ make
+$ make install
+

일반적인 경우 Tomcat이 설치되면 설치된 경로의 "$CATALINA_HOME/bin" 위치에 tomcat-native.tar.gz 형태로 설치 파일이 존재하나 없는 경우, 혹은 높은 버전을 설치하고 싶은 경우 별도로 다운로드 받아 진행합니다. Native의 다운로드는 톰켓 홈페이지의 좌측 Downloads의 하위에 Tomcat Nativeopen in new window로 존재합니다. 관련 링크를 클릭하면 우측에 OS플랫폼에 맞게 소스를 받을 수 있는 링크가 제공됩니다.

Native Library는 다음과 같이 설치를 진행합니다.

$ gzip -d tomcat-native.tar.gz
+$ tar -xvf tomcat-native.tar
+$ configure --prefix=$CATALINA_HOME --with-apr=/usr/local/apr-1.5.1/bin/apr-1-config  --with-java-home=$JAVA_HOME
+$ make
+$ install
+

소스의 컴파일과 설치가 완료되면 "$CATALINA_HOME/lib"에서 libtcnative 관련 파일의 확인을 할 수 있습니다.

Native Library가 해당 플랫폼 환경에 맞게 생성되었다면 Shared Library로 설정하여 적용하며 적용방법은 앞서 Java Home을 설정하는 방법을 이용합니다. Shared Library 환경변수명은 OS마다 상이함에 주의하며 setenv.shopen in new window(bat)에 적용하는 방법은 다음과 같습니다.

# setenv.sh(bat)
+LD_LIBRARY_PATH "$CATALINA_HOME/lib"
+
OSShared Library Path
WindowsPATH
SolarisLD_LIBRARY_PATH
HP-UXSHLIB_PATH
AIXLIBPATH,LD_LIBRARY_PATH
LinuxLD_LIBRARY_PATH
OS/2LIBPATH
OSXLD_LIBRARY_PATH

적용된 Native Library는 톰캣의 콘솔 로그 "$CATALINA_HOME/logs/catalina.out(log)"에서 다음의 메시지를 확인 할 수 있습니다.

Info: Loaded APR based Apache Tomcat Native library 1.1.31.
+
+ + + diff --git a/05-Software/Tomcat/tomcat101/04-configuration.html b/05-Software/Tomcat/tomcat101/04-configuration.html new file mode 100644 index 0000000000..5ba5b27f68 --- /dev/null +++ b/05-Software/Tomcat/tomcat101/04-configuration.html @@ -0,0 +1,75 @@ + + + + + + + + + + 4. Tomcat 환경설정 | docmoa + + + + + +
본문으로 건너뛰기

4. Tomcat 환경설정

약 2 분TomcatJava

4. Tomcat 환경설정

  • 리스너
  • 자바옵션
  • 클래스로더
  • setenv?
  • web.xml
  • 로그

4.1 리스너

리스너는 Tcp로 활성화되는 HTTP 프로토콜을 상징하여 설명합니다. 톰캣은 기본적으로 HTTP, AJP, Shutdown 을 위한 port가 활성화 됩니다. 리스너는 우리 몸의 귀와 같은 역할을 합니다. 들려오는 요청을 받는 역할을 하지요. 톰캣 또한 요청을 받아들이기 위해 리스너를 활성화하여 요청을 받아 들입니다.

이러한 리스너는 톰캣의 "Coyote" 컴포넌트가 담당하는데 톰캣의 Startup.sh(bat)을 수행하면 다음과 같은 메시지를 확인할 수 있습니다.

7월 15, 2014 5:46:18 오후 org.apache.coyote.AbstractProtocol start 정보: Starting ProtocolHandler ["http-bio-8080"]
+7월 15, 2014 5:46:18 오후 org.apache.coyote.AbstractProtocol start 정보: Starting ProtocolHandler ["ajp-bio-8009"]
+7월 15, 2014 5:46:18 오후 org.apache.catalina.startup.Catalina start 정보: Server startup in 1002 ms
+

http에 8080포트가 할당되고 ajp에 8009포트가 할당됩니다. 이런 포트로 톰캣에 HTTP 혹은 AJP로 요청을 전달할 수 있습니다. 만약 톰캣이 기동된 서버의 IP가 "192.168.0.10"이고 사용되는 HTTP 포트가 8080 이라면

http://192.168.0.10:8080

이렇게 브라우저에서 호출이 가능합니다. 해당 IP가 DNS나 별도의 호스팅 서비스를 통해 www.tomcat-gm.com에 연결되어 있다면

http://www.tomcat-gm.com:8080

이렇게 호출이 가능합니다.

일반적으로는 HTTP의 기본 포트로 80이 사용되고 HTTPS(SSL)의 기본 포트로 443이 사용됩니다. 이경우 별도의 포트 지정없이 url 요청이 가능합니다.

http://www.tomcat-gm.com(:80)

https://www.tomcat-gm.com(:443)

이러한 리스너 설정은 톰캣의 설정에서 Connector로 정의됩니다. $CATALINA_HOME/conf 디렉토리의 server.xml을 열어보시면 다음과 같은 Connector 설정을 확인 할 수 있습니다.

<Connector port="8080" protocol="HTTP/1.1"
+    connectionTimeout="20000"
+    minSpareThreads="10"
+    maxSpareThreads="5"
+    maxThreads="15"
+    redirectPort="8443" />
+

프로토콜의 형태가 "HTTP/1.1"이고 포트는 "8080"으로 활성화 됩니다. 해당 플랫폼에 IP가 여러개 존재한다면 "address" 항목을 추가하여 별도의 IP를 지정 할 수도 있습니다. 이같은 설정은 AJP나 SSL 또한 마찬가지 입니다.

4.2 Java Options

Java의 장점이 무엇인가 물으면 그 대표적인 한가지는 OS 플랫폼에 종속적이지 않은 어플리케이션 개발이 가능하다 일 것입니다. 이런 개발 환경이 가능한 이유는 JVM(Java Virtual Machine)이 제공하는 환경 때문입니다. JVM이 동작하면 각 OS에 Java가 공통적으로 수행되기 위한 Runtime 환경을 만들고 이후 생성된 JVM 환경에서 어플리케이션이 수행되기 때문에 OS 플랫폼 마다 개발을 달리하지 않아도 됩니다. 하지만 각각의 플랫폼에서의 JVM은 그 동작의 목적은 같아도 어플리케이션에 따라 성능에 차이가 발생할 수 있습니다. 어떤 어플리케이션은 한번에 큰 메모리를 요구하는가 하면 때로는 계산을 주로 한다던가, IO 작업이 많다던가하여 CPU 자원을 많이 필요로 하는 식이죠. 따라서 JVM에서는 사용자가 조절할 수 있는 수많은 옵션을 제공합니다. 물론 아무것도 설정하지 않은 상태가 가장 일반적일 수는 있지만 성능이나 장애극복을 위해 Java Option이 추가되기도 합니다. 적용되는 Java Option의 예는 다음과 같습니다.

옵션설명
-Xms(ms)Heap 메모리의 최소값을 정의합니다.
-Xmx(mx)Heap 메모리의 최대값을 정의합니다.
-verbosegcJVM에서 수행하는 GC를 로그로 남깁니다.
-XX:+AggressiveOpts소수점 컴파일을 최적화 합니다.
-Djava.net.preferIPv4Stack=trueIPv4와 IPv6모두 사용할 수 있는 환경에서 IPv4를 우선하여 서비스 합니다.

이 외에도 수많은 Java Option이 존재하기에 각 환경에 맞는 Java Option의 적용이 필요하겠습니다. 하지만 어플리케이션이 실제 수행되기 전에는 어떤 요구사항이 발생하는지는 알 수 없기 때문에 반드시 실 서비스를 하기 전 충분한 테스트가 필요합니다.

톰캣에서 Java Options의 추가를 위해서는 setenv.sh(bat) 혹은 catalina.sh(bat)의 스크립트에 추가하는 방법과 Windows 서비스에 등록된 경우 관련 설정창에 추가하는 방법이 있습니다.

TomcatServiceOptions
TomcatServiceOptions

(서비스로 등록된 톰캣에 Java Options 적용 예)

4.3 ClassLoader

Java 환경에서는 class를 호출하여 서비스를 수행합니다. 각 class는 단독으로, 혹은 여러개가 함께 각각의 Class에 정의된 역할을 수행합니다. 이런 Class를 사용하기 위해서는 ClassLoader가 Class를 읽어 Class를 나열하는 과정이 수행됩니다. 나열되는 Class들은 경로의 형태를 띄며 이를 ClassPath라고도 부릅니다.

...:class:class:class:class:...

ClassLoader가 Class를 읽지 못한다면 JVM에서는 해당 Class에 들어있는 Method를 요청할 때 찾지 못하는 상황이 발생하며, 이경우 ClassNotFound 메시지를 발생시킵니다.
또한 이렇게 정의되는 ClassPath에는 우선순위가 있습니다. 동일한 Class명의 동일한 Method이지만 다른 역할을 수행하는 Class가 로딩된다면 어떤것이 우선권을 갖을까요? ClassPath순서상 앞서있는 Class가 우선권을 갖습니다. 아래와 같은 ClassPath가 생성되었다면 classA가 우선권을 갖습니다.

...:classA:classB:classC:classE:...

그렇다면 어플리케이션 개발자가 의도한 Class를 호출하기 위해서는 ClassLoader가 원하는 class를 앞서 설정하도록 해야합니다. 물론 겹치는 Class가 없다는 가정하에는 어떤 위치에 있던지 상관없이 읽히기만 하면 되겠지요.

일반적으로 웹 어플리케이션을 위한 war형태의 애플리케이션 개발시 Class는 WEB-INF/classes jar형태의 라이브러리는 WEB-INF/lib에 위치시킵니다. 이렇게 위치된 Class들은 톰캣 혹은 WAS에 배치(deploy)되면 전체 JVM의 가장 뒤에 위치하게 됩니다. 간혹 웹 어플리케이션 형태가 아닌 Class나 라이브러리를 적용하기위해서는 CLASSPATH라는 변수를 사용하여 ClassLoader가 읽을 수 있도록 합니다. 해당 변수는 WAS마다 상이할 수 있습니다.

실행되는 JVM에서의 ClassLoader 순서를 보면 다음과 같습니다.

BootClassPath:ExtensionClassPath:ClassPath

BootClassPath와 ExtensionClassPath는 Java의 기본 라이브러리를 로딩합니다. rt.jar와 같은 필수 라이브러리가 그 예입니다. 만약 기존 JVM을 hooking하는 식의 클래스를 사용하는 경우에는 이보다 앞서 클래스를 위치시킬 필요가 있습니다. HelloWorld라는 클래서를 BootClassPath앞에 위치하게 하려면 -Xbootclasspath/p:HelloWorld, 뒤에 적용하려면 -Xbotclasspath/a:HelloWorld 형태를 사용하여 적용합니다. p와 a에 주의합니다. 그리고 일반적인 Class가 위치하는 ClassPath에 위치하게 하기 위해서는 톰켓의 경우 스크립트에 'CLASSPATH'변수를 치환합니다. 그 예는 다음과 같습니다.

export CLASSPATH=HelloWorld
+export CLASSPATH=${CLASSPATH}:HelloWorld
+export CLASSPATH=HelloWorld:${CLASSPATH}
+

기 적용된 CLASSPATH가 있는가에 따라 적용하고자하는 Class 혹은 라이브러리 앞, 뒤에 기존 CLASSPATH를 넣어줄 수도 있습니다.

경고

Windows의 경우 구분자가 세미콜론(;)이고 그 외에는 콜론(:)임에 주의합니다.

4.4 setenv

Windows 환경의 서비스 실행방법을 제외하고는 대부분 스크립트에 앞서 설명한 Java Option이나 ClassPath를 설정합니다. 일반적으로, 그리고 여러 운영환경에서 이러한 실행 환경 변수를 catalina.sh(bat)에 설정하여 사용하는 경우를 보았습니다. 하지만 한번이라도 해당 스크립트를 열어 읽어보셨다면 다음과 같은 메시지를 확인 할 수 있을 것입니다.

# -----------------------------------------------------------------------------
+# Control Script for the CATALINA Server
+#
+# Environment Variable Prerequisites
+#
+#   Do not set the variables in this script. Instead put them into a script
+#   setenv.sh in CATALINA_BASE/bin to keep your customizations separate.
+

즉, catalina.sh는 건드리지 말고 setenv.sh(bat)에 추가적은 설정을 하라는 안내 문구 입니다. catalina.sh를 수정하는 경우 해당 톰캣을 이전하거나, 동일한 톰캣을 구성하거나, 또는 복구해야 하는 상황에서 추가로 설정되거나 수정된 내용의 확인이 힘들 수 있고, 또한 설정과 수정으로 인한 비정상 동작을 할 수 있기 때문입니다. 그렇다면 setenv.sh(bat)은 어떻게 작용할까요? catalina.sh(bat)에서 setenv를 찾아보면 다음과 같이 setenv스크립트에 적용된내용을 읽어오는 것을 확인 할 수 있습니다.

if [ -r "$CATALINA_BASE/bin/setenv.sh" ]; then
+  . "$CATALINA_BASE/bin/setenv.sh"
+elif [ -r "$CATALINA_HOME/bin/setenv.sh" ]; then
+  . "$CATALINA_HOME/bin/setenv.sh"
+fi
+

이같이 톰캣에서는 추가/수정해야하는 환경변수나 설정값을 하나의 스크립트에서 관리할 수 있는 환경을 제공합니다. 다만 setenv.sh(bat)스크립트는 별도로 만들어야 합니다. 앞서 catalina.sh(bat)의 설명된 변수들을 보면 Java Options은 JAVA_OPTS로하지 말고 CATALINA_OPTS로 하라는 점도 주의해서 보시기 바랍니다. JAVA_OPTS의 경우 톰캣을 정지시키는 shutdown.sh(bat)에서도 호출되기 때문에 차후 소개되는 JMX 모니터링을 위한 옵션과 같이 별도의 포트를 활성화하는 옵션과 같은 성격의 설정 적용시 문제가 될 수 있습니다. setenv.sh(bat)스크립트에 다음과 같이 추가하면 해당 옵션을 별도로 export하지 않아도 톰캣 기동시 적용됩니다.

CATALINA_OPTS="-Xms1024m -Xmx2048m -XX:MaxPermSize=512m -verbosegc"
+CLASSPATH="${CLASSPATH}:/app/libs/myapi.jar"
+

4.5 web.xml

웹 어플리케이션에서 web.xml은 서블릿을 정의하고 이어주는 역할을 수행합니다. 이와 마찬가지로 톰캣에 있는 $CATALINA_HOME/conf/web.xml 또한 톰캣에 있는 서블릿을 정의하고 이어주는 역할을 수행합니다. 다만 JSP/Servlet 엔진으로서의 최소한의 정의를 합니다.

  • Servlet 처리 맵핑
  • Jsp 처리 맵핑
  • 추가 모듈 설정
  • Global Session Timeout
  • mime 정의

따라서 톰캣에 배치되는 모든 어플리케이션에서 공통으로 수정되어야 할 사항은 web.xml에도 정의할 수 있습니다. 하지만 앞서 catalina 스크립트와 마찬가지로 추가/수정시 부작용이 있을 수 있음에 중의합니다.

4.6 Log

톰캣의 로그는 다음과 같이 종류와 정의는 다음에서 정의합니다. 다만 Windows 서비스는 서비스 환경설정에서 정의힙니다.

LogConfig File
Catalina.outCATALINA_OUT 환경변수로 정의, catalina.sh에 정의되어 있고 setenv 스크립트에서 재정의
access.logserver.xml
*.loglogging.properties
+ + + diff --git a/05-Software/Tomcat/tomcat101/05-deployment.html b/05-Software/Tomcat/tomcat101/05-deployment.html new file mode 100644 index 0000000000..1a4dc0527b --- /dev/null +++ b/05-Software/Tomcat/tomcat101/05-deployment.html @@ -0,0 +1,97 @@ + + + + + + + + + + 5. Tomcat 애플리케이션 배포 | docmoa + + + + + +
본문으로 건너뛰기

5. Tomcat 애플리케이션 배포

약 2 분TomcatJava

5. Tomcat 애플리케이션 배포

  • Web Application
  • by Manager
  • by webapps DIR
  • by context.xml
  • ROOT
  • Auto Deployment & Hot Deployment
  • Parallel Deployment

5.1 Web Application

톰캣에 배치되는 어플리케이션은 Java Web Application입니다. 간단히 웹 어플리케이션이라고도 합니다. 간략한 구조는 다음과 같습니다.

파일 구조

./APPDIR
+├── WEB-INF
+│   ├── classes
+│   │   └── class-files
+│   ├── lib
+│   │   └── jar-files
+│   └── web.xml
+├── index.html
+└── index.jsp
+

APP 디렉토리 하위에는 웹어플리케이션의 정의를 넣을 WEB-INF 디렉토리가 필요합니다. 아주 간단한 어플리케이션은 web.xml에 다음의 태그만 넣어도 웹 어플리케이션으로 인지할 수 있습니다.

<web-app/>
+

5.2 by Manager

어플리케이션을 배치하는 방법에는 톰캣에서 제공하는 manager를 사용하는 방법이 있습니다. manager는 톰캣을 설치하면 제공되는 어플리케이션 관리 툴로 다음과 같이 확인할 수 있습니다.

  • http://ipopen in new window:port 로 기본 톰캣 http 요청 (로컬에서 기본 설정으로 실행한 경우 http://localhost:8080open in new window)
  • ROOT 어플리케이션의 호출 확인 후 좌측의 'Manager App' 버튼 클릭
  • 톰캣 유저의 설정이 되어있지 않으므로 로그인 창에서 '취소'버튼 클릭
  • $CATALINA_HOME/conf/tomcat-user.xml 에 설정하는 방법을 에러페이지에서 확인
  • tomcat-user.xml에 다음과 같이 설정 추가 (e.g. user/passwdadmin/admin으로 설정)
<tomcat-users>
+  <role rolename="manager-gui"/>
+  <user username="admin" password="admin" roles="manager-gui"/>
+</tomcat-users>
+
  • 톰캣 재기동 후 다시 로그인 페이지 호출하여 설정한 user/passwd 입력 후 로그인

manager의 중간에 Deploy에서 배치를 수행할 수 있으며 두가지 타입이 제공됩니다. 한가지는 Deploy directory or WAR file located on server로서 톰캣이 기동된 서버내의 어플리케이션을 지정하여 배치하는 방법과 WAR file to deploy는 현재 접속중인 로컬의 WAR파일을 업로드하여 배치하는 방법입니다. 두 방법 모두 수행 후 $CATALINA_HOME/webapps 에 해당 어플리케이션이 위치하게 됩니다.

5.3 by webapps DIR

톰캣에는 자동으로 어플리케이션을 인지하고 배치하는 디렉토리가 있습니다. 바로 $CATALINA_HOME/webapps입니다. 해당 경로는 앞서 manager 를 통한 배치시에도 어플리케이션이 위치하게되는 경로인데, manager를 사용하는 방법은 결국 webapps 디렉토리에 어플리케이션을 위치시키는 작업이라고 볼 수 있습니다. 따라서 직접 해당 경로에 어플리케이션을 위치시켜도 동일하게 배치 작업이 발생합니다.

webapps 디렉토리가 자동으로 배치를 수행하는 설정은 server.xml에서 해당 경로가 배치를 수행하도록 설정되었기 때문입니다.

<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
+    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
+           prefix="localhost_access_log." suffix=".txt"
+           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
+</Host>
+

server.xml에서 확인 할 수 있는 톰캣의 기본 host 환경인 localhost에서 webapps 디렉토리를 대상으로 autoDeploy를 수행한다는 설정 내용입니다. 이러한 설정으로 인해 자동으로 어플리케이션의 배치가 가능합니다.

5.4 by context.xml

앞서 두가지의 manager나 webapps 디렉토리를 통한 배치 방법은 모두 톰캣 내부의 webapps 디렉토리에 어플리케이션이 위치하게 된다는 특징이 있습니다. 이러한 점은 사용자가 원하는 임의의 위치에 어플리케이션은 배제 된다는 의미 일 수도 있습니다. 따라서 이경우 context.xml 형태의 xml을 통한 배치 방법을 사용 할 수 있겠습니다.

배치를 설명하기에 앞서 context.xmlcontext 디스크립터는 본래 server.xml에 위치하는 디스크립터였습니다. 하지만 해당 디스크립터에 설정되는 내용들은 변경사항이 자주 발생하는 항목들이기에 별도의 xml에서도 정의할 수 있도록 변경되었으며, 톰캣 5.5.12 버전부터는 server.xml이 아닌 별도의 context.xml을 통하여 해당 설정들을 관리하도록 권고하고 있습니다.

context.xml에서 설정하는 정보는 어플리케이션 뿐만이 아니기 때문에 어플리케이션 설정을 제외한 설정을 톰캣에 적용하는 경우에도 사용됩니다. context.xml<Context> 디스크립터로 시작되며 다음의 위치에서 적용되고, 위치에 따라 적용 범위가 달라집니다.

  1. $CATALINA_HOME/conf/context.xml : $CATALINA_HOME 내의 모든 톰켓 프로세스 내의 서비스
  2. $CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME]/context.xml.default : Host 범위 내의 모든 서비스
  3. $CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME]/[WEBAPPNAME].xml : 어플리케이션 범위 내의 모든 서비스
  4. $WEBAPP/META-INF/context.xml : 어플리케이션 범위 내의 모든 서비스

이같은 위치에 따른 적용 범위는 context로 정의되는 대표적인 자원중 하나인 데이터소스(DataSource)의 경우 톰캣전체 또는 어플리케이션 별로 구분할 수 있는 기능을 사용할 수 있습니다.

어플리케이션을 배포하는 경우 위의 4가지 방법 중 3, 4번 항목을 들 수 있으며, 특히 context.xml을 사용한 임의의 위치의 어플리케이션 배포는 3번 항목을 사용하게 됩니다.

sample context-root를 갖는 어플리케이션은 다음과 같이 context.xml을 설정할 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
+
+<Context path="sample" docBase="/Users/GSLee/APP/sample" debug="0" reloadable="true" crossContext="true" privileged="true"/>
+
+<!-- path는 해당 설정을 server.xml에 하는 경우 의미가 있고 3번 방법의 경우 xml 파일 이름이 context-root로 설정됩니다. -->
+

5.5 ROOT

일반적으로 어플리케이션을 배치하는 경우 해당 어플리케이션의 디렉토리 이름이나 context로 설정된 xml의 파일 이름이 context-root로 사용됩니다.

http://www.mytomcat.co.kr/[WEBAPPNAME]/index.jsp

하지만 대부분의 경우 다음과 같이 요청되기를 바라죠.

http://www.mytomcat.co.kr/index.jsp

이경우 배치 방식은 동일하지만 다음의 네가지 방법을 통해 어플리케이션 배치 시 context-root를 /로 설정할 수 있습니다.

구성 위치설명
$CATALINA_HOME/webapps/ROOTwebapps 기본 디렉토리에 ROOT인 디렉토리명으로 배치된 어플리케이션
$CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME]/ROOT.xmlROOT를 이름으로 갖는 context 타입의 xml로 배치된 어플리케이션
Tomcat ManagerAPP에서 context path 항목을 비워놓은 채로 배치하는 어플리케이션
server.xml에 배치 어플리케이션을 설정context 디스크립터의 path항목을 비워놓음

방법은 여러가지가 있지만 앞서 설명드린 context디스크립터로 설정한 별도의 xml을 사용한 배치 방식을 권장합니다.

sample 어플리케이션을 ROOT로 배치한 로그는 다음과 같이 확인됩니다.

정보: Starting Servlet Engine: Apache Tomcat/8.5.73
+9월 06, 2014 8:30:52 오후 org.apache.catalina.startup.HostConfig deployDescriptor
+정보: Deploying configuration descriptor /Users/GSLee/APP/tomcat/apache-tomcat-8.5.73/conf/Catalina/localhost/ROOT.xml
+9월 06, 2014 8:30:52 오후 org.apache.catalina.core.StandardContext setPath
+경고: A context path must either be an empty string or start with a '/'. The path [sample] does not meet these criteria and has been changed to [/sample]
+9월 06, 2014 8:30:52 오후 org.apache.catalina.startup.SetContextPropertiesRule begin
+경고: [SetContextPropertiesRule]{Context} Setting property 'debug' to '0' did not find a matching property.
+9월 06, 2014 8:30:53 오후 org.rhq.helpers.rtfilter.filter.RtFilter init
+정보: Initialized response-time filter for webapp with context root 'ROOT'.
+9월 06, 2014 8:30:53 오후 org.apache.catalina.startup.HostConfig deployDescriptor
+정보: Deployment of configuration descriptor /Users/GSLee/APP/tomcat/apache-tomcat-8.5.73/conf/Catalina/localhost/ROOT.xml has finished in 1,115 ms
+

5.6 Auto Deployment & Hot Deployment

Auto Deploy와 Hot Deploy는 Auto와 Hot을 어떻게 해석하는가에 따라 다음과 같이 혼용되어 사용됩니다.

  • 서버 프로세스가 기동 된 상태에서 배치
  • 배치된 어플리케이션에 변경사항이 적용된 어플리케이션을 재배치
  • 수행중인 어플리케이션의 일부 소스의 변경 적용과 반영

의미가 어떻게 해석되던지 이런 용어를 사용함에 있어서 바라는점은 서비스가 실행중인 도중에도 변경사항을 사용자 모르게 바꾸고자 하는 의도가 대부분일 것입니다.

5.6.1 webapps 'autoDeploy'

webapps 디렉토리에 어플리케이션을 넣으면 자동으로 배치가 됩니다. 서버가 기동된 상태에서도 말이죠. 해당 설정은 다음의 'Host' 디스크립터에서 정의합니다.

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
+      ...
+</Host>
+

Host 설정에 autoDeploy가 true인 경우 해당 디렉토리에 위치하는 어플리케이션을 감지하여 자동으로 톰캣에 배치를 수행합니다.

5.6.2 jsp 'checkInterval'

jsp를 사용자 뷰로 사용하는 경우 서비스의 컨텐츠, 또는 jsp에서 실행하는 코드상의 변경사항이 자주 발생하게 됩니다. 이경우 jsp를 새로 반영하기 위해 서버가 실행중임에도 자동으로 업데이트된 jsp를 컴파일하여 해당 소스를 반영하는 동작을 지원하는 설정이 있습니다. 해당 설정은 다음의 $CATAILNA_HOME/conf/web.xml의 jsp 서블릿에 정의되어 있습니다.

<servlet>
+    <servlet-name>jsp</servlet-name>
+    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+    ...
+    <init-param>
+        <param-name>development</param-name>
+        <param-value>true</param-value>
+    </init-param>
+    <init-param>
+        <param-name>checkInterval</param-name>
+        <param-value>1</param-value>
+    </init-param>
+    <load-on-startup>3</load-on-startup>
+</servlet>
+

jsp 서블릿에서는 두가지 경우에 대해 jsp의 업데이트를 수행합니다.

  • 조건 1. developmenttrue인 경우 항상 확인
  • 조건 2. developmentfalse이고 checkInterval0보다 큰 경우 확인합니다.

관련 옵션에 대한 상세 내용이나 추가적인 jasper 컴포넌트의 옵션은 해당 설정의 위에 주석으로 설명이 되어있습니다.

5.6.3 Class 'reloadable'

클래스를 수정하여 컴파일한 뒤 어플리케이션에 업로드하면 얼마 후 해당 어플리케이션(컨텍스트)를 리로딩하는 과정을 수행합니다. 해당 설정은 Context 디스크립터가 설정된 xml에 정의합니다.

<Context reloadable="true" ...>
+    <Loader checkInterval="15"/>
+</Context>
+

reloadabletrue로 설정하는 경우 해당 어플리케이션(컨텍스트)는 클래스에 변경이 발생하면 다시 리로딩하는 기능을 수행합니다. 간격은 기본 15초이기 때문에 더 빠른 방영을 원하시면 Context디스크립터 내에 LoadercheckInterval을 정의함으로 시간을 조절할 수 있습니다. 하지만 이런 리로드 현상을 싫어하시는 분도 있습니다. "Tomcat Context 수동 Reload"라는 블로그 글에서도 보이듯 자동 리로드를 비활성화 하고 Valve를 사용하는 별도의 방법을 사용할 수도 있습니다.


5.7 Parallel Deployment

어플리케이션의 클래스가 수정되어 리로드가 발생하거나 라이브러리나 xml등의 재기동 후 반영되는 변경사항을 적용하기 위해서는 톰캣을 재기동해야 하는 상황이 발생합니다. 이런 경우 기존 어플리케이션은 기존에 사용중인 사용자가 계속 이용 할 수 있도록 활성화된 상태에서 새로운 버전의 어플리케이션을 배치, 새로운 사용자는 새로운 어플리케이션을~~(새술은 새부대에?)~~ 사용하도록 하는 Parallel Deployment 기능을 사용 할 수 있습니다.

parallelDeploy
parallelDeploy

WebLogic Server 같은 타 WAS에서도 이와 유사한 'production redeploymentopen in new window'기능이 있지만 톰캣이 좀더 쉬운 방법을 제공합니다.

그 방법은 WEBAPP##[VersionNumber]입니다. sample 어플리케이션으로 예를 들면 sample##1.0으로 배치를 수행하는 것입니다. 기존 어플리케이션 뒤에 샵 기호 두개와 버전이름만 붙이면 되기 때문에 매우 간단하지만 단점으로는 거의 동일한 구성과 용량의 독립적인 어플리케이션이 필요하기 때문에 어느정도 변경사항이 많은 경우 활용도가 높겠습니다. 버전은 float 형태로 정의하며 상대적으로 높은 값이 신규 배치가 됩니다. Context에서 지정하는 경우에는 어플리케이션 경로를 설정한 대로 샵 기호가 추가된 어플리케이션 이름을 지정하면 서비스 컨텍스트는 샵 기호와 버전이 제외된 기존 경로를 사용하게 됩니다.

<Context docBase="/app/sample##01" ... /></Context>
+
<Context docBase="/app/sample##02" ... /></Context>
+

배치된 어플리케이션은 톰캣 Manager에서도 확인할 수 있습니다.

버전이 높은 어플리케이션이 배치되면 기존 사용자는 이전 버전의 어플리케이션 서비스를 이용하고 새로 접속하는 사용자는 신규 어플리케이션의 서비스를 이용하게 됩니다. 이전 버전의 어플리케이션은 톰캣 Manager에서 Session을 확인하여 사용자가 없는것을 확인 후 제거할 수 있습니다.

+ + + diff --git a/05-Software/Tomcat/tomcat101/06-dbconnection.html b/05-Software/Tomcat/tomcat101/06-dbconnection.html new file mode 100644 index 0000000000..547247a1fe --- /dev/null +++ b/05-Software/Tomcat/tomcat101/06-dbconnection.html @@ -0,0 +1,154 @@ + + + + + + + + + + 6. Tomcat Database 연동 | docmoa + + + + + +
본문으로 건너뛰기

6. Tomcat Database 연동

약 2 분TomcatJava

6. Tomcat Database 연동

  • JDBC Connection Pool
  • DB 연동 예제
  • DB 연동 설정값
  • JNDI Lookup

6.1 JDBC Connection Pool

JDBC Connection Pool은 Java에서 DB(Data Base)의 Session 자원을 미리 확보함으로 재생성하는 비용을 줄이기 위한 기술 입니다. Java에서 사용되는 기술이기 때문에 각 DB 벤더사들은 Java로 구현되는 서비스에서 자사의 DB를 사용할 수 있도록 별도의 라이브러리를 제공하며 이를 사용하여 DB와의 Connection을 생성하고 DB를 사용할 수 있게 됩니다.

JDBC는 여타 드라이버와는 다르게 미리 Connection을 확보하여 JVM상에 Object상태로 만들어두고 이를 요청하는 서비스에 빌려줍니다. 빌려준다는 표현을 사용한 이유는 반드시 반환되어야 하기 때문입니다. 앞서 미리 만든다는 표현은 만드는 개수가 제한되어 있다는 의미로 사용하였으며, 때문에 한정된 자원을 DB와의 연계 처리만을 하는 경우 잠시 사용하고 다시 반납하는 과정을 거칩니다.

다음은 jsp에서 단일 Oracle DB와의 Connection Pool을 생성하고 반납하는 샘플 코드입니다.(테스트 용도로 입니다.)

<%@ page import="java.sql.*" %>
+<%
+  StringBuffer sbError = new StringBuffer();
+  DatabaseMetaData dbMetaData = null;
+  Connection conn = null;
+%>
+<font size="-1"><p>
+<%
+  DriverManager.registerDriver (new oracle.jdbc.OracleDriver());
+  try {
+    conn = DriverManager.getConnection("jdbc:oracle:thin:@172.16.1.128:1521:TOSA1", "fmsvr", "fmsvr");
+    dbMetaData = conn.getMetaData();
+%>
+<p>
+Name of JDBC Driver: <%= dbMetaData.getDriverName() %><br>
+Version: <%= dbMetaData.getDriverVersion() %><br>
+Major: <%= dbMetaData.getDriverMajorVersion() %><br>
+Minor: <%= dbMetaData.getDriverMinorVersion() %><br>
+<p>
+Database Name: <%= dbMetaData.getDatabaseProductName() %><br>a
+Version: <%= dbMetaData.getDatabaseProductVersion() %><br>
+<%
+  } catch (SQLException e) {
+    sbError.append(e.toString());
+  } finally {
+    if (conn != null) {
+      try {
+        conn.close();
+      } catch (SQLException e) {
+        sbError.append(e.toString());
+      }
+    }
+  }
+  if (sbError.length() != 0) {
+    out.println(sbError.toString());
+  } else {
+%>
+<p>No error</font>
+<%
+  }
+%>
+

주어진 정보로 getConnection()을 수행하고 다시 close()를 수행하여 반납하는 과정이며, close()하지 않는 경우 해당 객체는 모두 사용하였음에도 불구하고 메모리상에 남아 차후 메모리 이슈를 발생시킬 수 있습니다.

톰캣에서는 이런 일련의 Connection을 생성하는 작업을 어플리케이션 대신 생성할 수 있으며 내부적으로 생성하는 개수나 연결이 끊어졌을 때의 재시도, 사용하지 않는 Connection의 강제 반환 등의 설정이 가능합니다.

다음은 Context 디스트립터 내에 설정하는 Resource에서 정의한 DataSource 예제 입니다.

<Resource name="jdbc/test"
+          auth="Container"
+          type="javax.sql.DataSource"
+          username="oracle"
+          password="oracle"
+          driverClassName="oracle.jdbc.driver.OracleDriver"
+          url="jdbc:oracle:thin:@address:1521:SID"
+          removeAbandoned="true"
+          removeAbandonedTimeout="60"
+          logAbandoned="true"
+          maxActive="25"
+          maxIdle="10"
+          maxWait="-1"
+/>
+

6.2 DB 연동 예제

톰캣에서 DB를 연동하기 위해서는 우선 사용할 DB의 벤더사에서 제공하는 JDBC driver를 ClassLoader에서 읽도록 해야 합니다. 우선 JDBC driver를 받고 두가지 방법으로 적용이 가능합니다.

  • setenv script
#JDBC Driver Classpath
+CLASSPATH=/app/lib/jdbc.jar
+
  • lib 디렉토리에 복사
    • $CATALINA_HOME/common/lib/jdbc.jar (5.5)
    • $CATALINA_HOME/shared/lib/jdbc.jar (5.5)
    • $CATALINA_HOME/lib/jdbc.jar (6+)

대표적인 DB의 Context 디스크립터에 설정하는 방법은 다음과 같습니다.

<!-- MySQL - Connector/J -->
+<Resource name="jdbc/test"
+          auth="Container"
+          type="javax.sql.DataSource"
+          username="javauser"
+          password="javadude" 
+          driverClassName="com.mysql.jdbc.Driver" 
+          url="jdbc:mysql://ipaddress:3306/javatest" 
+          maxActive="25"
+          maxIdle="10"
+          maxWait="-1"
+/>
+
<!-- Oracle - classes12.jar(jdk1.4.2), ojdbc#.jar(5+) -->
+<Resource name="jdbc/test"
+          auth="Container"
+          type="javax.sql.DataSource"
+          username="oracle"
+          password="oracle" 
+          driverClassName="oracle.jdbc.driver.OracleDriver" 
+          url="jdbc:oracle:thin:@ipaddress:1521:SID" 
+          maxActive="25"
+          maxIdle="10"
+          maxWait="-1"
+ />
+
<!-- PostgreSQL - JDBC # -->
+<Resource name="jdbc/test"
+          auth="Container" 
+          type="javax.sql.DataSource" 
+          username="myuser"
+          password="mypasswd" 
+          driverClassName="org.postgresql.Driver" 
+          url="jdbc:postgresql://ipaddress:5432/mydb" 
+          maxActive="25"
+          maxIdle="10"
+          maxWait="-1"
+/>
+

6.3 DB 연동 설정값

Resource에 정의되는 항목은 기본적인 url이나 DB접근 계정정도만 있어도 가능하지만 간혹 튜닝이나 문제해결을 위해 추가적인 옵션이 요구되는 경우가 있습니다. 톰캣에서는 다음의 설정값을 제공합니다.

ATTRIBUTEDESCRIPTOINDEFAULT
maxActive최대 Connection 값100
maxIdleIdle Connection 최대 허용치=maxActive
minIdleIdle Connection 최소 허용치=initialSize
initialSizeConnection Pool의 최초 생성 개수10
maxWaitConnection을 얻기위해 대기하는 최대 시간30000(ms)
removeAbandoned특정시간 동안 사용하지 않는 Connection 반환false
removeAbandonedTimeoutremoveAbandoned가 동작하는데 소요되는 시간60(s)
logAbandonedConnection이 remove될 때 log에 기록false
testOnBorrowgetConnection()이 수행될 때 유효성 테스트false
validationQuery테스트를 위한 쿼리null

validationQuery로는 다음과 같이 적용 가능합니다.

  • mysql, MSSql : SELECT 1
  • Oracle : select 1 from dual

http://tomcat.apache.org/tomcat-10.0-doc/jdbc-pool.htmlopen in new window

6.4 JNDI Lookup

톰캣에서 생성한 JDBC Connection Pool을 DataSource로서 사용하기 위해서는 JNDI를 Lookup하는 방법을 사용합니다. JNDI를 사용하면 이를 지원하는 다른 프레임워크나 API에서도 톰캣의 자원을 사용할 수 있습니다. mybatis/ibatis의 경우도 JNDI 설정을 할 수 있습니다.

Context디스크립터로 정의한 DataSource는 어플리케이션의 web.xml에서 정의하고 소스에서는 lookup을 이용하여 사용합니다. 일련의 설정방법의 예는 다음과 같습니다. Context의 정의의 위치에 따라 전체 어플리케이션에 적용될 수도 있고 host단위, 혹은 단일 어플리케이션 내에서만 자원을 생성하게 됩니다.

<!-- context.xml -->
+<Resource name="jdbc/test"
+          auth="Container"
+          type="javax.sql.DataSource"
+          username="oracle"
+          password="oracle" 
+          driverClassName="oracle.jdbc.driver.OracleDriver" 
+          url="jdbc:oracle:thin:@ipaddress:1521:SID" 
+ />
+
<!-- web.xml -->
+<web-app>
+    ...
+    <resource-ref>
+        <res-ref-name>jdbc/test</res-ref-name>
+        <res-type>javax.sql.DataSource</res-type>
+        <res-auth>Container</res-auth>
+    </resource-ref>
+    ...
+</web-app>
+
//Source Code
+ds = ctx.lookup("java:comp/env/jdbc/test");
+
+ + + diff --git a/05-Software/Tomcat/tomcat101/07-host.html b/05-Software/Tomcat/tomcat101/07-host.html new file mode 100644 index 0000000000..f553146fc5 --- /dev/null +++ b/05-Software/Tomcat/tomcat101/07-host.html @@ -0,0 +1,58 @@ + + + + + + + + + + 7. Tomcat HOST | docmoa + + + + + +
본문으로 건너뛰기

7. Tomcat HOST

1분 미만TomcatJava

7. Tomcat HOST

  • 호스트 구성
  • 호스트 특징
  • host manager

7.1 호스트 구성

톰캣에 정의된 바로는 Host로 정의되나 일반적인 기능으로 표현한다면 가상 호스트(Virtual Host)와 같은 기능 입니다. 특정 host 명, 즉 http url로 서비스를 분기하는 역할을 합니다. server.xml 기본으로 설정되어있는 localhost인 호스트의 내용은 다음과 같습니다.

<Engine name="Catalina" defaultHost="localhost">
+    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
+    ...
+    </Host>
+</Engine>
+

설정된 내용을 분석해본다면 Catalina라는 톰캣의 엔진에서 처리하는 기본 호스트는 localhost이고, localhostwebapps 디렉토리를 기본 배치 디렉토리고 갖는다는 내용입니다. 기본 호스트로 지정된 호스트는 이외에 설정된 호스트 조건에 맞지 않은 모든 요청을 처리하게 됩니다. 이렇게 생성된 localhost$CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME] 과 같은 경로에 호스트만의 설정 값을 갖게 됩니다.

별도의 호스트 추가는 'Engine' 디스크립터 하위에 'Host' 디스크립터로 정의합니다. 'myhost'라는 호스트는 다음과 같이 추가할 수 있습니다.

<Engine name="Catalina" defaultHost="localhost">
+    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
+    ...
+    </Host>
+    <Host name="myhost" appBase="webapps_myhost" unpackWARs="true" autoDeploy="true">
+    ...
+    </Host>
+</Engine>
+

새롭게 추가되는 호스트는 기본 배치경로를 다르게 설정합니다. 때문에 동일한 컨텍스트의 요청이라도 어떤 호스트가 처리하는가에 따라 다른 어플리케이션의 서비스를 이용하게 됩니다. 설정 후 톰캣 프로세스를 재기동하면 호스트 설정 디렉토리가 생성됨을 확인 할 수 있습니다.

7.2 호스트 특징

호스트의 기능은 주로 웹서버에서 많이 사용되는 기능입니다. 특정 url로 호출되는 요청을 각 요청 전달 목적지에 맞게 분배하는 역할을 수행하지요. 호스트는 톰캣에도 구현되어 있으며 이를 통해 하나의 톰캣 내에서 같은 컨텍스트를 갖는 요청의 처리가 가능합니다.

hostRoot
hostRoot

톰캣의 프로세스를 서비스 마다 생성하지 못하는 상황(e.g. 자원의 한계와 같은) 또는 서비스에서 소모하는 자원이 크지 않아 추가적인 프로세스를 기동하지 않아도 되는 상황 등 호스트의 기능을 활용 할수 있습니다.

7.3 host manager

호스트를 관리하기 위해 톰캣에서는 'host manager'를 제공합니다. 앞서 배치에서 보았던 'Manage APP'와 같이 호스트를 쉽게 구성할 수 있도록 UI환경을 제공합니다. 이를 통해 설정파일에 직접 수정하기보다 제공되는 UI로 정확하고 쉽게 구성하고 관리할 수 있도록 합니다.

  • http://ipopen in new window:port 로 기본 톰캣 http 요청 (로컬에서 기본 설정으로 실행한 경우 http://localhost:8080open in new window)
  • ROOT 어플리케이션의 호출 확인 후 좌/우측의 Host Manager 클릭
  • 톰캣 유저의 설정이 되어있지 않으므로 로그인 창에서 '취소'버튼 클릭
  • $CATALINA_HOME/conf/tomcat-user.xml 에 설정하는 방법을 에러페이지에서 확인
  • tomcat-user.xml에 다음과 같이 설정 추가 (e.g. user/passwdadmin/admin으로 설정)
<tomcat-users>
+  <role rolename="manager-gui"/>
+  <role rolename="admin-gui"/>
+  <user username="admin" password="admin" roles="manager-gui,admin-gui"/>
+</tomcat-users>```
+
  • 톰캣 재기동 후 다시 로그인 페이지 호출하여 설정한 user/passwd 입력 후 로그인
hostManager
hostManager

host manager에서는 기본 호스트 외에 추가적인 호스트에 대한 추가를 할 수 있도록 Add Virtual Host를 사용할 수 있습니다. Host 디스크립터에서 정의되는 내용을 각 항목에 맞게 입력 할 수 있고 이렇게 추가된 호스트는 Host name 테이블의 commands에서 개별적으로 시작과 정지가 가능합니다. 호스트가 정지되어 비활성화 된 상태에서는 해당 호스트의 요청 url에 맞게 들어오더라도 기본 호스트가 처리하게 됩니다. host manager는 웹페이지를 통한 호스트의 추가/삭제/컨트롤이 가능하므로 외부, 또는 관리자가 아닌 사용자가 접근하지 못하도록 해야 합니다.

+ + + diff --git a/05-Software/Tomcat/tomcat101/08-webserver.html b/05-Software/Tomcat/tomcat101/08-webserver.html new file mode 100644 index 0000000000..e0c19b129e --- /dev/null +++ b/05-Software/Tomcat/tomcat101/08-webserver.html @@ -0,0 +1,108 @@ + + + + + + + + + + 8. Tomcat 웹서버 연동 | docmoa + + + + + +
본문으로 건너뛰기

8. Tomcat 웹서버 연동

약 2 분TomcatJava

8. Tomcat 웹서버 연동

  • 웹서버 연동의 이유
  • mod_jk
  • 클러스터

8.1 웹서버 연동의 이유

톰캣 단일로 서비스하는 경우도 있지만 일반적으로 웹서버와 연동하여 사용하는 경우가 보다 더 많습니다. 그 이유를 다음과 같이 정리합니다.

8.1.1 요청분산

톰캣에서 처리하는 서비스 요청이 증가하면 단일 프로세스로 처리가 부족한 상황이 발생합니다. 처리에 필요한 힙 메모리를 추가해야 한다면 현재 적용된 메모리 설정보다 더 많은 값을 설정하고 CPU 자원이 부족하다면 장비의 교체도 고려해 봐야겠습니다. 이런 접근은 가용한 메모리가 없거나 더 나은 장비를 추가 구입/구성 해야하는 점이 있습니다. 따라서 단일 프로세스의 한계를 유연하게 대처하기 위해 복수의 프로세스에서 동일한 서비스를 구성하는 방안을 고려할 수 있습니다. 그리고 복수의 프로세스에 요청을 전달하기 위해 LB(LoadBalancer)가 필요합니다.

LB 기능을 수행하는 대표적인 두가지는 네트워크 장비(스위치 장비: L2, L4, L7)를 사용하는 방법과 HTTP 요청을 받아 분산이 가능한 웹서버 입니다. 어떤 방법을 사용하던지 복수개의 톰캣을 사용하면 상황에 따라 프로세스를 추가하여 처리하는 용량을 증가시킬 수 있습니다. 물론 앞서 장비의 추가 상황을 제외한다면 단일 프로세스 보다는 복수의 프로세스를 사용하여 부하를 분산시킬 수 있습니다.

8.1.2 소스분산

톰캣은 웹서버의 역할을 함께 수행할 수 있는 기능을 동반하고 있습니다. 하지만 정적인 소스를 처리함에 있어서는 기존 웹서버의 처리 능력이 더 우월하기 때문에 소스 처리의 추체를 분산시켜 처리 속도를 증가시킬 수 있습니다. 대표적인 정적인 소스는 html, css, 이미지 파일 입니다. 앞서 요청의 분산으로 부하를 분산시키는 역할과 더불어 어플리케이션 소스 또한 처리추체를 분산시키고, 더불어 웹서버와 톰캣에서 좀더 빠르게 처리 할 수 있고, 처리 가능한 요청의 처리를 분담할 수 있습니다.

8.1.3 장애극복

일반적으로 Failover로 표현하는 장애처리 및 장애극복은 복수의 톰캣 프로세스를 사용함에 따른 장점입니다. 특정 톰캣 프로세스에 장애가 발생하더라도 다른 톰캣 프로세스에서 요청을 처리하게 됨으로 단일 프로세스로 운영할때보다 서비스 지속성에 장점을 갖습니다.


8.2 mod_jk

웹서버는 프록시 기능만을 사용하여도 톰캣과의 연동이 가능하나 톰캣으로의 연동을 좀더 긴밀하게 하기 위해 별도의 모듈을 제공합니다. 이는 Tomcat Connector로 제공되는데 http://tomcat.apache.orgopen in new window의 Document와 Download에서 확인할 수 있습니다. 연동가능한 대표적인 웹서버로는 다음의 웹서버와 모듈이 요구됩니다.

  • Apache HTTP Server : mod_jk.so, mod_jk.dll
  • IIS : isapi_redirect.dll
  • iPlanet : nsapi_redirector.so, nsapi_redirector.dll

아파치(Apache HTTP Server)는 가장 많이 사용되고 모든 플랫폼을 지원하는 대표적인 웹서버로서 이번 장에서 설명하고자하는 웹서버와의 연동에서 사용하고자 합니다. 기타 웹서버의 경우 톰캣의 토큐먼트open in new window 내용을 참고하시기 바랍니다.

아파치가 설치되었다는 가정하에 톰캣을 연동하는 방법은 다음과 같습니다.

8.2.1 mod_jk 다운로드

유닉스/리눅스/맥의 경우 mod_jk의 소스를 다운받아 아파치의 apxs와 함께 컴파일하는 과정이 필요합니다. 윈도우에서도 컴파일하여 사용할 수 있으나 비쥬얼 스튜디오가 있어야 컴파일을 할 수 있기 때문에 별도의 바이너리 파일로 제공됩니다. 다운로드페이지에서 플랫폼에 맞는 모듈을 다운받습니다.

8.2.2 모듈 컴파일

유닉스/리눅스/맥의 경우 컴파일을 수행하기위해 아파치의 'apxs'가 필요합니다. 다운받은 소스 압축파일을 풀고 다음과 같이 컴파일 합니다.

$ tar xvfz tomcat-connectors-1.2.40-src.tar.gz
+$ cd ~/Downloads/tomcat-connectors-1.2.40-src/native
+$ ./configure —with-apxs=$APACHE_HOME_DIR/bin/apxs
+$ make
+$ make install
+

컴파일이 완료된 모듈은 자동으로 $APACHE_HOME/modules/mod_jk.so로 생성됩니다. 윈도우에서는 다운 받은 바이너리 모듈의 압축을 풀어 동일한 디렉토리에 복사하면 됩니다.

8.2.3 모듈 설정

생성된 모듈을 아파치에서 사용할 수 있도록 설정하는 작업을 합니다. $APACHE_HOME/conf/httpd.conf에 설정하거나 별도의 conf파일을 생성하여 읽게 하여도 됩니다. httpd.conf에 설정하는 내용은 다음과 같습니다.

LoadModule jk_module modules/mod_jk.so
+
+<IfModule jk_module>
+    JkWorkersFile conf/workers.properties
+    JkLogFile logs/mod_jk.log
+    JkLogLevel info
+    JkMountFile conf/uri.properties
+</IfModule>
+

JkWorkersFile은 요청을 처리하는 워커, 즉 톰캣을 정의하는 파일을 지정합니다.

JkMountFile은 워커와 워커가 처리할 요청을 맵핑하는 파일을 지정합니다. JkMount만으로도 설정이 가능하나 하나의 파일에서 별도로 관리하기 위해서는 해당 파일을 지정하는 것을 권장합니다. httpd.confJkMount를 사용하는 경우 다음과 같이 정의할 수 있습니다.

#jsp 파일을 worker1 워커가 처리하는 경우
+JkMount /*.jsp  worker1
+
+#server 경로의 요청을 worker2 워커가 처리하는 경우
+JkMount /servlet/* worker2
+

8.2.4 워커 정의

워커는 그 단어의 의미에서도 추측할 수 있듯이 mod_jk에서 지정하는 요청을 처리하는 대상, 즉 톰켓 프로세스를 의미합니다. 워커는 다음과 같은 설정 방식을 따릅니다.

worker.[WORKER_NAME].[TYPE]=[VALUE]
+

설정의 예는 다음과 같습니다.

  • worker.list=[WORKER_NAME] : 요청을 처리하는 워커를 나열합니다.
  • worker.worker1.type=ajp13 : worker1 워커의 형태를 정의합니다.
  • worker.worker2.host=192.168.0.11 : worker2 워커의 address를 정의합니다.
  • worker.loadbalancer.type=lb : loadbalancer 워커의 형태를 정의합니다.
  • worker.loadbalancer.balance_workers=[WORKER_NAME] : lb 형태인 loadbalancer 워커에서 요청을 분산시킬 워커를 나열합니다.

worker.properties의 설정 예제는 다음과 같습니다.
workersproperties

해당 설정은 LB로 구성되는 워커를 정의합니다. LB로 구성될 worker1worker2를 정의합니다.

  • worker1192.168.0.10의 ip와 8009포트로 ajp13형태의 요청을 받아들이며 lbfactor는 1입니다.
  • worker2192.168.0.11의 ip와 8009포트로 ajp13형태의 요청을 받아들이며 lbfactor는 1입니다.
  • loadbalancer는 LB를 수행하기 위한 워커로 lb 워커 형태입니다.
  • lb형태의 워커는 LB 대상 워커를 balace_workers를 정의하여 나열합니다. 예제에서는 worker1worker2가 대상으로 지정되었습니다.
  • 각 톰캣과의 연동 워커인 worker1worker2lbfactor가 같기 때문에 같은 비율로 요청이 전달됩니다.
  • 요청을 처리하는 워커는 worker.list에 지정합니다. 예제에서는 loadbalancer 워커를 지정하였습니다.

8.2.5 처리할 요청의 정의

워커의 정의로 요청을 처리할 워커가 준비되었다면 어떤 요청을 전달할지 정의해햐 합니다. 앞서 JkMount를 사용한 방식은 간단히 설명하였고 여기서는 uri.properties 파일에서 별도로 요청의 처리 맵핑을 관리하도록 하였습니다. JkMountFile로 지정되는 이 설정 파일은 다음과 같은 설정 방식을 따릅니다.

[URL or FILE_EXTENSION]=[WORKER or WORKER_LIST]
+

이렇게 설정되는 설정 파일의 내용의 예는 다음과 같습니다.

/*.do=worker1
+/*.jsp=worker2
+/servlet/*=loadbalancer
+

JkMount와 표현방식에 약간의 차이('='의 사용여부)가 있음에 주의하여 설정합니다.

8.2.6 테스트

설정이 완료되면 아파치 프로세스를 재기동 합니다. 이후 맵핑한 요청설정에 따라 아파치에 요청을 합니다. jsp파일을 톰캣이 처리하도록 설정하였다면 톰캣에서 요청해보고 url을 아파치로 변경하여 동일하게 요청되는지 확인합니다.


8.3 클러스터

웹서버와 연동하는 주요 기능중 한가지는 장애처리입니다. 일반적으로는 이런 장애처리 동작시 기존 처리중이던 HTTP Session 정보는 장애가 발생한 톰캣이 가지고 있었기 때문에 없어지게 됩니다. 이같은 현상은 기존에 로그인하여 작업을 하던 중 해당 톰캣 프로세스에 문제가 발생하여 다른 톰캣 프로세스로 요청이 넘어가면 로그인 하던 세션이 끊겨 다시금 작업을 수행하는 현상이 발생하는 것을 예로 들수 있습니다.

톰캣에서는 장애처리시의 HTTP Session을 복구하여 지속적인 세션의 유지를 가능하게 하고자 '클러스터' 기능을 제공합니다. 클러스터는 Multicast로 톰캣 프로세스간에 클러스터를 형성하고 멤버로 구성된 톰캣간에 세션을 공유하는 방식입니다.

기능의 활성화는 단순히 server.xmlCluster 디스크립터의 주석을 해제하는 것만으로도 가능합니다.

tomcatCluster.png
tomcatCluster.png

하지만 동일 장비에서 기동되는 톰캣간이나 서비스가 다른 톰캣이 여럿 기동중인 경우에는 설정값들이 중복되어 톰캣 기동이나 서비스 처리시 문제가 발생할 수 있습니다. 따라서 기본적인 설정 값 외에 별도의 설정들을 적용해야 하는 경우 server.xml에서 클러스터를 사용하기 위한 디스크립터 위에 설명한 도큐먼트의 내용을 참고해야 합니다.

만약 도큐먼트의 설정들이 너무 많거나 어떻게 적용해야 하는지 이해하기 힘든경우 톰캣 5.5버전의 server.xml을 참고하시기 바랍니다. 해당 버전에서는 6.0 이후 단순히 한줄로 적용된 Cluster 디스크립터와는 다르게 기본적인 설정과 값이 같이 적용되어 있습니다. 아래 예제는 도큐먼트의 기본 설정에서 가져온 내용입니다.

http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html: 기본 설정open in new window

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
+         channelSendOptions="8">
+
+  <Manager className="org.apache.catalina.ha.session.DeltaManager"
+           expireSessionsOnShutdown="false"
+           notifyListenersOnReplication="true"/>
+
+  <Channel className="org.apache.catalina.tribes.group.GroupChannel">
+    <Membership className="org.apache.catalina.tribes.membership.McastService"
+                address="228.0.0.4"
+                port="45564"
+                frequency="500"
+                dropTime="3000"/>
+    <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
+              address="auto"
+              port="4000"
+              autoBind="100"
+              selectorTimeout="5000"
+              maxThreads="6"/>
+
+    <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
+      <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
+    </Sender>
+    <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
+    <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
+  </Channel>
+
+  <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
+         filter=""/>
+  <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
+
+    <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
+              tempDir="/tmp/war-temp/"
+              deployDir="/tmp/war-deploy/"
+              watchDir="/tmp/war-listen/"
+              watchEnabled="false"/>
+
+    <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
+    <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
+</Cluster>
+

앞서 여러개의 톰캣 클러스터를 사용하는 경우 MembershipReciver 디스크립터의 내용에 주의합니다. Membership은 동일한 설정을 갖는 톰캣 끼리 같은 클러스터 멤버 그룹으로 인지하는 내용으로 멀티캐스트 통신을 수행합니다. Membershopaddressport가 동일한 톰캣 프로세스 끼지 클러스터 기능을 수행합니다. Reciver는 클러스터간의 메시지를 주고받는 역할을 수행하며 TCP 통신을 수행합니다. 따라서 동일한 장비의 톰캣은 Reciver에서 설정되는 port에 차이가 있어야 합니다.

설정된 톰캣 클러스터의 기능은 어플리케이션이 세션 복제를 허용하는가의 여부에 따라 동작하게 됩니다. 따라서 어플리케이션의 web.xml에 복제가능을 활성화하는 디스크립터를 추가합니다.

<web-app>
+  ...
+  <distributeable/>
+  ...
+</web-app>
+

복제 설정이 추가된 어플리케이션이 배치된 톰캣은 기동시 클러스터를 활성화하고 멤버간에 통신을 수행하는 메시지가 로그에 나타납니다.
distributable

구성된 클러스터와 어플리케이션은 LB로 구성되어 요청하며 각 톰캣 프로세스는 세션을 공유하기 때문에 하나의 톰캣 프로세스가 종료되더라도 다른 톰캣 프로세스에서 세션을 받아 수행하는 것을 확인할 수 잇습니다.

+ + + diff --git a/05-Software/Tomcat/tomcat101/09-thread.html b/05-Software/Tomcat/tomcat101/09-thread.html new file mode 100644 index 0000000000..d1c3d1514b --- /dev/null +++ b/05-Software/Tomcat/tomcat101/09-thread.html @@ -0,0 +1,69 @@ + + + + + + + + + + 9. Tomcat 쓰레드 | docmoa + + + + + +
본문으로 건너뛰기

9. Tomcat 쓰레드

1분 미만TomcatJava

9. Tomcat 쓰레드

  • Thread?
  • 설정
  • Thread Dump

9.1 Thread?

Thread는 JVM내에 요청된 작업을 동시에 처리하기 위한 작은 cpu라고 볼 수 있습니다. 톰캣에 서비스 처리를 요청하는 경우 해당 요청은 Queue에 쌓여 FIFO로 Thread에 전달되고 Thread에 여유가 있는 경우 Queue에 들어온 요청은 바로 Thread로 전달되어 Queue Length는 0을 유지하지만 Thread가 모두 사용중이여서 더이상의 요청 처리를 하지 못하는 경우 새로 발생한 요청은 Queue에 쌓이면서 지연이 발생합니다.

Thread가 많을수록 동시에 많은 요청을 처리하기 때문에 작은 Thread 수는 서비스를 지연시키지만 이에 반해 Thread도 자원을 소모하므로 필요이상의 큰 값은 불필요한 JVM의 자원을 소모하게 되고 하나의 프로세스 내의 Thread 수는 톰캣 기준으로 700개 이하로 설정할 것을 권장합니다.

사실상 요청은 지연이 최소화 되어야 하며 지연이 길어질수록 Thread를 점유하여 동시간대에 사용가능한 Thread 수를 줄이므로 적정한 Thread 개수의 설정 상태에서 요청을 더 많이 받고자 한다면 지연에 대한 문제점을 찾는 것을 우선해야 합니다.

9.2 설정

쓰레드는 Connector 기준으로 생성됩니다. 따라서 HTTP나 AJP, SSL이 설정된 Connector마다 다른 쓰레드 수를 설정할 수 있습니다. 또는 하나의 쓰레드 풀을 생성하고 Connector에서 해당 쓰레드 풀의 쓰레드를 같이 사용하도록 설정할 수도 있습니다.

9.2.1 Connector의 쓰레드 설정

기본적인 'Connector'는 다음과 같이 설정되어있습니다.

<Connector port="8080" protocol="HTTP/1.1"
+           connectionTimeout="20000" redirectPort="8443" />
+           
+<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
+

HTTP나 AJP 프로토콜이 정의된 Connector는 설정되어 있지는 않지만 기본값으로 최대 쓰레드 200개의 설정을 가지고 있습니다. 쓰레드 관련 설정값은 다음과 같습니다.

AttributeDescription
maxSpareThreadsIdle 상태로 유지할 max thread pool size
maxThreads동시 요청에 의해 Connector가 생성 할 수 있는 최대 request 수
minSpareThreadstomcat을 실행할때 최소로 유지할 Idle Thread 수
maxIdleTimeThread를 유지하는 시간(ms)

이런 설정 값들로 다시금 정의하면 기본 Connector를 다음과 같이 설정할 수 있습니다.

<Connector port="8080" protocol="HTTP/1.1"
+           connectionTimeout="20000" redirectPort="8443"
+           maxSpareThreads="5"
+           maxThreads="15"
+           minSpareThreads="10" />
+           
+<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
+           maxSpareThreads="5"
+           maxThreads="15"
+           minSpareThreads="10" />
+

9.2.2 Executor

Executor 디스크립터는 Connector의 쓰레드 설정에 별도의 실행자로 설정하여 동일한 Executor를 사용하는 Connector는 같은 쓰레드 풀에서 쓰레드를 사용하도록 하는 기능입니다.

별도의 Connector를 사용하여 서비스하지만 모두 같은 쓰레드 자원을 사용하기 위함이며 connectorexecutor라는 설정을 사용하여 공통의 쓰레드 풀을 이용할 수 있습니다. tomcatThreadPool이라는 이름을 갖는 Executor와 각 Connector에 설정하는 예는 다음과 같습니다.

<Executor name="tomcatThreadPool"
+          namePrefix="catalina-exec-"
+          maxThreads="150"
+          minSpareThreads="4"/>
+
+<Connector executor="tomcatThreadPool"
+           port="8080" protocol="HTTP/1.1"
+           connectionTimeout="20000" redirectPort="8443"/>
+           
+<Connector executor="tomcatThreadPool"
+           port="8009" protocol="AJP/1.3" redirectPort="8443"/>
+

Executor에는 name을 정의하여 다른 Connector에서 해당 Executor를 정의할 수 있는 연결고리를 만듭니다. 그리고 쓰레드의 이름을 정의하는 namePrifix설정으로 다른 쓰레드와 구분할 수 있도록 합니다. 기존 Connector에 설정하던 쓰레드 관련 설정을 Excutor에 함으로서 Connector에 공통 쓰레드 풀을 제공합니다.


9.3 Thread Dump

쓰레드 덤프는 실행중인 Thread의 종류와 시작점, 실행한 클래스와 메소드 순서, 현재 상태등을 기록하는 JVM의 고유 기능입니다. 쓰레드 덤프로 서비스의 흐름과 서비스 지연시 수행중인 작업, 병목등을 확인할 수 있습니다.

쓰레드 덤프의 시작에는 쓰레드 이름과 쓰레드의 정보가 기록되며 이후 쓰레드 상태에 대해 설명합니다.
트레이스의 읽는 순서는 위가 최근 실행한 클래스와 메소드이기 때문에 아래서부터 위로 읽습니다.

ThreadDump
ThreadDump

쓰레드 덤프를 발생시키는 법은 다음과 같습니다.

  1. 프로세스 ID를 확인

    • 유닉스/리눅스/맥

       ps -ef | grep java
      +
    • JDK 5+

       $JAVA_HOME/bin/jps
      +
  2. 쓰레드 덤프 발생

    • 유닉스/리눅스/맥

      kill -3 <pid>
      +
    • JDK 5+

       $JAVA_HOME/bin/jstack <pid>
      +
  3. 쓰레드 덤프 확인

    • catalina.out 파일 확인
    • AIX 플랫폼의 경우 별도의 javacore 파일 확인
+ + + diff --git a/05-Software/Tomcat/tomcat101/10-monitoring.html b/05-Software/Tomcat/tomcat101/10-monitoring.html new file mode 100644 index 0000000000..63308114b2 --- /dev/null +++ b/05-Software/Tomcat/tomcat101/10-monitoring.html @@ -0,0 +1,57 @@ + + + + + + + + + + 10. Tomcat 모니터링 | docmoa + + + + + +
본문으로 건너뛰기

10. Tomcat 모니터링

약 1 분TomcatJava

10. Tomcat 모니터링

  • 모니터링은 왜 하나?
  • 톰캣 기본 모니터링 툴
  • psi-Probe
  • jkstatus
  • visualVM
  • JMC
  • APM

10.1 모니터링은 왜 하나?

무엇인가에 대한 모니터링은 그 대상의 상태를 확인하기 위함입니다. 문제가 있는지, 어떤 동작을 하고 있는지, 알아야 할 내용이 있다면 그 사항을 알수 있도록 하는, 즉 대상의 상태를 감시하는 것입니다.

톰캣을 사용하여 서비스를 제공하는 입장에서는 톰캣의 상태를 감시할 수 있어야 합니다. 톰캣의 작업 상태나 자원의 상태, 특정 문제 상황이 발생하는 징조를 파악하는 것입니다. 모니터링을 잘 수행하면 더나은 서비스와 서비스 장애로 인한 손실을 예방할 수 있습니다.


10.2 톰캣 기본 모니터링 툴

톰캣에는 기본적으로 제공하는 모니터링 툴이 있습니다. 자세하지는 않더라도 필요한 만큼의 정보를 제공합니다.

10.2.1 Manager APP

앞서 어플리케이션의 배치를 통해 알아보았던 Manager APP에서는 다음의 정보를 확인 할 수 있습니다. 만약 호스트가 여러개라면 해당 Manager APP 어플리케이션을 별도로 배치하여 해당 호스트의 배치 정보를 확인 할 수 있습니다.

  • Manager APP에서의 수행 결과 메시지
  • 배치된 어플리케이션의 상태
  • 어플리케이션 배치 수행
  • 메모리 릭 감지와 SSL 진단
  • 실행되는 플랫폼 정보

그리고 상단의 Server Status링크를 통해 이동하면 다음의 정보를 확인 할 수 있습니다.

  • 톰캣 프로세스의 JVM 메모리 모니터링
  • 프로토콜 리스너의 처리중인 작업

배치된 어플리케이션의 Session의 숫자에 링크된 페이지에서는 현재 생성된 세션의 정보와 해당 세션을 강제 종료시킬 수 있는 Sessoin Administration을 제공합니다.

10.2.2 host manager

host manager에서는 다음의 정보를 확인 할 수 있습니다.

  • 호스트 목록의 확인과 정보 확인
  • 호스트의 컨트롤
  • 호스트 생성
  • 실행되는 플랫폼 정보

10.2.3 admin (톰캣 5.5)

톰캣 5.5 버전에서는 admin 어플리케이션이 제공됩니다. 다운로드의 아카이브 사이트에서 확인 가능하며 톰캣을 다운받기위한 버전하위에 apache-tomcat-x.x.xx-admin 이름을 갖는 파일이 있습니다. 여타 WAS에서 제공되는 만큼의 웹 콘솔 UI를 제공하는 관리 툴로서 톰캣 내에 설정과 각 항목의 정보를 파악할 수 있습니다. 다만 톰캣 5.5 이후로는 제공되지 않습니다.


10.3 pis-Probe

http://code.google.com/p/psi-probeopen in new window

psi-probe는 예전에 lambda probe였으나 현재 구글에서 관리하기 시작한 후로 명칭이 변경되었습니다.

psi-probe의 공식 웹 사이트의 다운로드 항목에서 파일을 받아 압축을 풀면 probe.war 웹 어플리케이션이 있습니다. 해당 어플리케이션을 톰캣에 배치하면 psi-probe를 실행할 수 있으며 tomcat-user.xmlmanager권한을 갖는 사용자로 접근하게 됩니다.

ip:port/probeopen in new window'와 같이 톰캣에 요청하면 psi-probe가 제공하는 톰캣의 모니터링과 관리를 수행하는 어플리케이션을 확인 할 수 있습니다.

앞서 텍스트로만 표현되던 정보들도 보다 보기좋게 제공하고 각 자원이나 설정을 파악하는데 있어서 기본 톰캣 모니터링 툴보다 나은 기능을 제공합니다. 다만 일부 모니터링 항목은 5.5까지를 지원하고 톰캣 8.0에 대한 지원이 불가능하며 2013년 3월 이후로 업데이트가 없다는 점이 단점입니다.


10.4 jkstatus

jkstatus는 mod_jk를 사용하여 연동한 경우 아파치에서 확인할 수 있는 톰켓 연동에 대한 모니터링 툴 입니다.

사용을 위해서 worker.propertiesstatus 워커를 추가합니다.

worker.list=tomcat1,tomcat2,loadbalancer,status
+...
+worker.status.type=status
+

그리고 uri.properties에 요청을 수행할 경로를 워커에 맵핑합니다.

...
+/jkstatus=status
+

설정 후 아파치를 재기동하면 아파치 요청 url의 컨텍스트에 설정한 요청 경로를 입력하여 'jkstatus' 툴을 확인 할 수 있습니다. 앞서 80으로 요청하는 아파치의 경우 다음과 같이 요청 할 수 있습니다.

'ip:port/jkstatusopen in new window'

jkstatus
jkstatus

jkstatus로 확인할 수 있는 정보는 다음과 같습니다.

  • 아파치 버전, mod_jk 버전, uptime, jkstatus 뷰 타입
  • 각 워커의 설정 값
  • 워커에서 수행중인 요청
  • 워커에 맵핑된 정보
  • 항목 설명

10.5 visualVM

visualVM은 톰캣만이 아닌 JVM 전반에 대해 모니터링을 제공하는 툴로서 제공됩니다. Oracle JDK 1.6.0_18 버전 이상부터는 기본으로 포함되어 있고 별도의 다운로드를 위해서는 관련 공식 페이지인 'http://visualvm.java.netopen in new window'을 통해 받을 수 있습니다.

JDK에는 $JAVA_HOMe/bin/jvisualvm에 실행파일이 위치합니다.

visualVM의 장점중 하나는 플러그인 입니다. 현재까지도 상당수의 플러그인이 제공되고 있으며 플러그인 API가 공개되어 있어 원하는 모니터링 플러그인을 생성할 수도 있습니다.

자바 프로세스는 자체적으로 로컬환경에서는 visualVM에 자동으로 인지되게 됩니다. 따라서 수행중인 JVM 프로세스는 visualVM의 Local항목에 감지되어 목록에 나타납니다. 그리고 원격지의 JVM 프로세스 또한 JMX(Java Monitoring Extension)을 통해 로컬의 visualVM에서 모니터링 할 수 있습니다. 서비스로 등록된 로컬의 톰캣은 프로세스가 보이지 않기 때문에 리모트로 구성하는 방법을 따릅니다. 톰캣의 리모트 구성 방법은 두가지가 있습니다.

  1. Java 기본 JMX 설정
    Java에서는 옵션을 통해 JMX를 활성화하고 설정 할 수 있습니다. 스크립트에 다음의 JMX의 옵션을 설정합니다.

    #setenv.sh
    +CATALINA_OPTS="-Dcom.sun.management.jmxremote
    +-Dcom.sun.management.jmxremote.port=18080
    +-Dcom.sun.management.jmxremote.ssl=false
    +-Dcom.sun.management.jmxremote.authenticate=false"
    +

    이 후 visualVM의 'Remote'에 플랫폼 IP를 등록하고 우클릭을 하면 'Add JMX connection...'을 통해 원격지의 톰캣을 등록할 수 있습니다.

  2. jmx remote 모듈
    톰캣에서는 jmx를 위한 모듈을 제공합니다. 톰캣의 다운로드에 보면 Extra항목에 Remote JMX jar가 있습니다.

    • 6.0 이상 버전의 경우 해당 jar 파일을 받아 $CATALINA_HOME/lib/catalina-jmx-remote.jar에 위치시킵니다.
    • 5.5 버전은 $CATALINA_HOME/common/lib/catalina-jmx-remote.jar에 위치시킵니다.

Java에서는 옵션을 통해 JMX를 활성화하고 설정 할 수 있습니다. 스크립트에 다음의 JMX의 옵션을 설정합니다.

#setenv.sh
+CATALINA_OPTS="-Dcom.sun.management.jmxremote.ssl=false
+-Dcom.sun.management.jmxremote.authenticate=false"
+

그리고 server.xmlListener 디스크립터로 JmxRemoteLifecycleListener를 추가합니다.

<Server port="8005" shutdown=“SHUTDOWN">
+
+       <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
+                 rmiRegistryPortPlatform="10001" rmiServerPortPlatform="10002" />
+

설정된 톰캣은 visualVM의 Remote 등록시 다음의 정보로 접근 정보를 생성합니다.
service:jmx:rmi://192.168.56.101:10002/jndi/rmi://192.168.56.101:10001/jmxrmi

로컬이나 리모트에 설정된 톰캣 프로세스는 좌측의 네비게이션에 나타나며 해당 항목을 더블클릭하여 우측 화면에서 모니터링 하게 됩니다.

visualVM에서는 기본적으로 다음의 기능을 제공합니다.

  • Overview: 해당 JVM의 설정 정보와 환경
  • Monitor: CPU, 메모리, 쓰레드 수, 클래스 수의 모니터링과 힘 덤프 수행
  • Threads: 쓰레드의 동작 상태와 쓰레드 덤프 수행

이외에도 플러그인에서 제공하는 기능을 활용한 다양한 모니터링이 가능합니다.


10.6 JMC

http://www.oracle.com/technetwork/java/javase/2col/jmc-relnotes-2004763.htmlopen in new window

JMC(Java Mision control)은 bea사에서 만든 별도의 JDK인 JRockit에서 제공하던 모니터링 툴입니다. 현재는 bea가 오라클사에서 인수하면서 관련 소프트웨어도 오라클이 관리하고 있으며 관련하여 Sun사도 인수하면서 기존 Sun Hotspot JDK와 JRockit의 장점을 합친 결과로 여러 기능이 추가되고 있습니다. 특히 JDK 7에서 많은 변화가 있었으며 여기에 JMC가 포함되었습니다.

JDK 7 이상의 버전에서 "$JAVA_HOME/bin/jmc"로 실행시키며, 수행된 툴은 다음과 같은 모습을 갖고 있습니다.
jmc

visualVM과 거의 비슷한 정도의 모니터링 기능을 제공하는 JMC의 대표적인 특징은 GC 정책에 따른 모니터링 탭의 변화 입니다. GC 정책은 기본 Parellel GC외에도 필요에 따라 CMS(Concurrent Mark Sweep)이나 G1 정책이 사용될 수 있는데 이런 GC 정책에 따른 뷰가 변경됨이 큰 특징입니다.


10.7 APM

APM은 Application Performence Manager의 약자로 모니터링의 역할과 더불어 어플리케이션의 성능을 향샹시킬 목적으로 사용되는 별도의 어플리케이션입니다. WAS의 상용 APM 으로는 Jennifer, Pharos, Performizer 등이 있고 Opensource로 Scouteropen in new window가 대표적입니다.

+ + + diff --git a/05-Software/Tomcat/tomcat101/11-tip.html b/05-Software/Tomcat/tomcat101/11-tip.html new file mode 100644 index 0000000000..92bb70b628 --- /dev/null +++ b/05-Software/Tomcat/tomcat101/11-tip.html @@ -0,0 +1,44 @@ + + + + + + + + + + 11. Tomcat 팁 | docmoa + + + + + +
본문으로 건너뛰기

11. Tomcat 팁

1분 미만TomcatJava

11. Tomcat 팁

  • 디렉토리
  • setenv
  • 실행 유저
  • Connector

11.1 디렉토리

하나의 장비에는 둘 이상의 톰캣을 운영하는 경우가 발생합니다. 이경우 설정 파일을 별도로 생성하고 스크립트를 통해 해당 설정을 읽게 하는 식의 방법을 사용할 수도 있지만 기본 제공되는 스크립트가 수정되는 양이 많을수록 관리의 난이도가 증가합니다. 따라서 톰캣의 프로세스를 여러개 기동하기 위한 방법으로 권장하는 것을 $CATALINA_HOME을 단순히 여러개 만드는 것입니다. 톰캣은 그 자체 용량이 그리 크지 않기 때문에 여러개 복사한다하여도 큰 무리 없이 사용가능한 가벼운 엔진 입니다. 따라서 설정 파일을 추가로 구성하고 스크립트를 수정하는 방법 보다는 기본 톰캣 엔진 전체를 복사하는 것을 권장합니다.

11.2 setenv

앞서 옵션을 적용함에 있어 강조한 setenv를 이야기 하고자 합니다. 톰캣에 대한 서비스를 지원하다보면 주로 catalina.sh(bat)를 수정하는 경우가 대부분이고 현재가지는 운영중인 서비스에 setenv사용한 사례찾기는 힘든것 같습니다. 하지만 catalina.sh(bat) 스크립트에서도 명시하듯 해당 스크립트를 수정하는 것은 설정한다는 이유 외에는 단점이 더 많기 때문에 반드시 setenv를 통해 추가적인 스크립트 추가 설정을 권장합니다.

11.3 실행 유저

Unix/Linux/Mac 플랫폼에서 톰캣이 별도의 계정으로 구성되어 있지만 간혹 root 계정으로 실수로 기동하는 경우가 발생합니다. 톰캣에서는 server.xml에 다음의 설정으로 root 로의 기동을 방지 할 수 있습니다.

<Server port="8005" shutdown="SHUTDOWN">
+  <Listener className="org.apache.catalina.security.SecurityListener" checkedOsUsers="root" />
+  ...
+

이러한 Listener 디스크립터의 설정으로 root 계정의 실행을 방지하며, 만약 root로 기동하는 경우 다음과 같은 메시지를 발생시킵니다.

java.lang.Error: Start attempted while running as user [root]. Running Tomcat as this user has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml)
+

11.4 Connector

Connector 디스크립터로 정의되는 프로토콜에 대한 정의는 톰캣이 요청을 받아들이는 통로를 설정하는 것이기 때문에 주요 설정 중 하나 입니다. 앞서 살펴본 쓰레드 설정외에도 도움이 될만한 옵션에 대한 내용은 다음과 같습니다.

옵션기능 설명
acceptCount="10"request Queue의 길이를 정의
: idle thread가 없으면 queue에서 idle thread가 생길때 까지 요청을 대기하는 queue의 길이
: 요청을 처리할 수 없는 상황이면 빨리 에러 코드를 클라이언트에게 보내서 에러처리 표시
enableLookups="false"Servlet/JSP 코드 중에서 들어오는 http request에 대한 ip를 조회 하는 명령등이 있을 경우 DNS 이름을 IP주소로 바꾸기 위해서 DNS 서버에 look up 요청을 보냄
: 서버간의 round trip 발생을 막을 수 있음
compression="off"HTTP message body를 gzip 형태로 압축해서 리턴하지 않음
maxConnection="8192"하나의 톰캣인스턴스가 유지할 수 있는 Connection의 수를 정의
: 현재 연결되어 있는 실제 Connection의 수가 아니라 현재 사용중인 socket fd (file descriptor)의 수
maxKeepAliveRequest="1"HTTP 1.1 Keep Alive Connection을 사용할 때, 최대 유지할 Connection 수를 결정하는 옵션
: Keep Alive를 사용할 환경이 아닌 경우에 설정
tcpNoDelay="true"TCP 프로토콜은 기본적으로 패킷을 보낼때 바로 보내지 않음
: 버퍼사이즈에 데이터가 모두 담길때까지 패킷 전송을 보류함으로 대기 시간이 발생하는 것을 방지
: 트래픽이 증가하지만 현 망 속도를 고려하였을 때 문제가 크지 않음
+ + + diff --git a/05-Software/Tomcat/tomcat101/index.html b/05-Software/Tomcat/tomcat101/index.html new file mode 100644 index 0000000000..a14807223d --- /dev/null +++ b/05-Software/Tomcat/tomcat101/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Tomcat101 | docmoa + + + + + +
본문으로 건너뛰기

Tomcat101

1분 미만

+ + + diff --git a/05-Software/index.html b/05-Software/index.html new file mode 100644 index 0000000000..32327e6d9b --- /dev/null +++ b/05-Software/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Software | docmoa + + + + + +
본문으로 건너뛰기

Software

1분 미만Software

Software

Recent pages

Jenkins

  • 젠킨스는 소프트웨어 개발 시 지속적 통합 서비스를 제공하는 툴이다.

Tomcat

  • 아파치 톰캣은 아파치 소프트웨어 재단에서 개발한 서블릿 컨테이너만 있는 웹 애플리케이션 서버이다.
+ + + diff --git a/06-etc/class/devops-discussion-1st.html b/06-etc/class/devops-discussion-1st.html new file mode 100644 index 0000000000..3d2aafcdbb --- /dev/null +++ b/06-etc/class/devops-discussion-1st.html @@ -0,0 +1,40 @@ + + + + + + + + + + DevOps 연구소 좌담회 (1차) | docmoa + + + + + +
본문으로 건너뛰기

DevOps 연구소 좌담회 (1차)

1분 미만devopscontainer

DevOps 연구소 좌담회 (1차)

일시 : 2019년 4월 24일 수요일 저녁 19시 ~ 21시

안내 : 컨테이너 연구소open in new window - 컨테이너 시스템의 활용 방향 및 미래에 관련해서 좌담

장소 : 메가존 지하 강연장

Q. 컨테이너란 무엇일까?

  • 자원을 잘 나눠주는 프로세스.

  • Zip같은 패키지인데 바퀴도 있고 엔진도 있는

  • 개발자들의 공용어

  • VM이 H/W와의 분리였다면 컨테이너는 OS와 분리

  • 떠나보낸 연인...하지만 사랑한다

Q. 왜 쓸까?

  • 효율성
  • 불변성
  • 코드나 사람에 대한 종속성 제거
  • OS 커플링 제거
  • 떠나보내야 할 수도, 하지만 추천

Q. Infra as Code 과 엔터프라이즈

  • 도입시 롤에 대한 정의를 필요로함
  • 하고싶은 내부 인력을 교육시키는 1안
  • 외부의 잘하는 DevOps 엔지니어를 영입하는 2안

컨테이너 선진국의 경우...

  • 컨테이너 기술보다는 그위의 서비스에 관심
  • 기술 비교보다 어떻게 하면 잘 쓸까에 고민

Q. K8S, DC/OS, Swarm, Rancher 누가남을까?

  • K8S가 지금의 승자
  • K8S는 마케팅에 비해 안정화 필요
  • 밥줄 유지를 위해서는 솔루션 줄타기를 잘 해야함

Q. Monitoring과 Admin은?

  • K8S를 직접 도입시 따르는 실패의 원인중 하나
  • 직접 만들고 구축하는것이 SaaS나 이미 있는 솔루션을 쓰는것 보다 비효율적
  • 수시로 바뀌고 Scale out/in 하는 환경을 자체적인 툴이 따라가지 못함

엔터프라이즈의 고민거리

  • 어떤 서비스를 컨테이너에 올릴까?
  • 보안팀과의 딜
  • HA는? DR은?
  • 데이터베이스는?
  • 어떻게 이모든걸 표준화 하지?
+ + + diff --git a/06-etc/class/devops-discussion-20240213.html b/06-etc/class/devops-discussion-20240213.html new file mode 100644 index 0000000000..22b9d39f58 --- /dev/null +++ b/06-etc/class/devops-discussion-20240213.html @@ -0,0 +1,40 @@ + + + + + + + + + + DevOps Korea 좌담회 2024.2.13. | docmoa + + + + + +
본문으로 건너뛰기

DevOps Korea 좌담회 2024.2.13.

GS1분 미만devopsai

torder-townhall
torder-townhall

DevOps Korea 좌담회 2024.2.13.

🧑‍⚖️ 사례 1. 법원 판례

법원의 판결은 기존 판례에 큰 영향을 받는다. 사건마다 그 배경, 상황, 증거, 정황 등의 정보를 기반으로 이전에 어떤 결과가 나왔는지가 현재의 판결문 작성과 연관성이 있다는 것은 정보들의 분석으로 현재의 사건이 어떤 결과가 나올지 예측할 수 있고, 더불어 생성형 AI를 사용하는 소비자 또한 이런 '법률 서비스'를 받는 효과를 얻을 수 있다는 것이다.

관련 뉴스 - 법원도 AI 도입 시작…"판결문 작성 돕는 AI도 필요"open in new window

법률과 AI는 이전에 미국에서 ChatGPT로 찾은 자료를 법원에 제출하여 벌금형을 받은 사례도 있지만 그것은 잘못 사용된 예시이자 사실 검증을 해야한다는 경종이기도 하다

패널로 참여하신 파란두루미open in new window의 이인희 대표님은 AI에 이전 판례들을 적용하여 주변인의 어렵게만 보이는 법률 싸움(?)에 도움을 준 적이 있었다.

생성형 AI는 전문가적 소견이나 전체 설계는 아직 무리일 수 있지만, 검증, 자료정리, 부분적 구현에 탁월함을 보여준다. (아직이라는 것은 앞으로는 그럴 가능성도 있다는 것이다.) 특히 어떤 정보를 학습시키는가에 따라 원하는 답을 얻을 수 있다.

🧑‍💻 사례 2. 전문 영역의 교차, 가능성의 확대

게임 업계에서는 예술가의 영역이였던 캐릭터 일러스트와 배경 같은 영역에 생성형 AI의 도입이 활발해지고 있다고 했다.

게임 같은 엔터테인먼트 요소가 강한 소프트웨어 산업에서는 눈으로 보여지는 요소가 흥행에 많은 비중을 차지할 수 밖에 없다. 때문에 아티스트에 많은 비용이 지불되고, 또는 유명한 아티스트를 섭외해야하는 경우도 잦았지만, 생성형 AI가 이를 대체한다면 원하는 원화를 얻을 때까지 반복적으로 요청 할 수 있고, 이것은 이전에 사람이 직접 모든 예술적 작업을 수행했던 상황에 비해 상대적으로 적은 비용으로도 게임 제작을 진행할 수 있다는 장점이 있다.

눈으로 보여지고 사용자 경험이 요구되는 프론트엔드 개발, 백엔드 개발자도 할 수 있어요.

전문 영역을 벗어나 다른 영역을 새로 배우기에는 시간과 비용이 필요하지만 생성형 AI를 활용하면, 백엔드 개발자도 스마트폰 앱이나 UI 작업 같은 다른 역량이 필요한 영역에 대해서도 개발 가능한 기회를 얻게 된다.

생성형 AI는 초급자 개발자를 두는 것 같은 효과가 있어요.

결국 명령(프롬프팅)을 하는 사람이 원하는 바를 정확히 전달할 수 있다면 생성형 AI는 그 일을 초급자가 할 수 있을 정도의 결과물을 만들고, 이것은 기존의 전문영역을 벗어나 더 많은 가능성을 열어주는 역할을 수행한다.

🏭 사례 3. 자체 구축 생성형 AI

생성형 AI를 자체 구축하여 사용하는 경우도 있다. 그 유용성과 업무 효율성을 기대하지만 외부 SaaS 형태를 사용하게 된다면, 기업의 정보가 유출 될 가능성이 있기 때문이다.

채팅하는 것처럼 사용하면, 그게 다 토큰이고 돈이에요.

하지만 생성형 AI이 이제 막 활성화 되듯, 사용자도 아직 노하우가 많은편은 아니다. 질문 하나 당 모두 모델로부터의 결과를 도출하기 위한 비용(전기, 프로세싱)이 발생하는 작업인데, 마치 일반 사용자들은 채팅하듯, 아주적은 정보들로 주거니 받거니 하며 사용하는 사례도 있었다.

대화 한 번에 ‘생수 한 병씩’…챗GPT의 불편한 진실open in new window

참석자로 참여하다 패널로 전향(?)하신 삼성SDS의 조남호 프로님의 비용에 대한 언급, 그리고 사용법에 대해 교육했음에도 아직은 제대로 사용하지 못한다는 사실에서 답답함이 느껴지기는 했지만, 한편으로는 기업내부에서 생성형 AI에 대한 올바른 사용법과 활용법에 대해 고민하고 실행하고 있다는 것은 앞으로의 AI와 함께 하는 세상에서 또 다른 기업/개인 경쟁력이 될 것도 같다.

어떻게 하면 올바른 질문을 할 수 있을까에 대한 해법으로는 서로다른 LLM을 사용해보는것도 방안으로 제시되었다. 예를들어, Edge Copilot에게 프롬프트를 만들어달라고 요청하고, 생성된 프롬프트를 ChatGPT에 입력하는 방식이다. 조남호 프로님은 더 나은 사용을 위해서 무료 강의를 들어볼 것을 추천하였다.

LERAN GENERATIVE AI - Short Coursesopen in new window

  • ChatGPT Prompt Engineering for Developers
  • Building Systems with the ChatGPT API
  • LangChain for LLM Application Development

👫 사례 4. "우린 생성형 AI 사용하지 않기로 했어요."

ChatGPT의 발표 이후로 수많은 관련 서적들이 출간되었고, 더불어 개발자 영역에서도 다양한 활용법, 특히 귀찮은 작업을 시키거나, 사용자에게 비 전문적인 영역의 해답(?)을 얻는 방식이 제안되었다.

Copilot이 생성해준 코드를 붙여넣으면, 설명 할 수 없고 우리의 코드 규칙을 지키기 어려워요.

참석하신 한 CTO분은 개발에 더이상 생성형 AI를 사용하지 않는 것으로 방향을 정했다고 이야해 해주었다. 앞서 다양한 활용법, 긍정적인 부분이 부각되었다면 이것은 반대적 입장이여서 관심이 가는 부분이였다. 특히 당장의 생산성은 높아질 수 있지만, 작성된 코드를 붙여넣은 사람도 설명할 수 없고 협업을 함에 추가적인 노력과 디버깅이 필수적이 되면서 생산성과 조직 거버넌스를 유지시키기 어려운 것이 그 이유 였다.

생성형 AI의 주요 문제점중 하나는 이미 학습된 데이터에 의존하는 편향적 경향이다. 이 문제는 편견이 완화되기보다는 편향된 결과를 더 확대하거나 지속적으로 출력하기 때문에 공개되어있는 학습모델을 그대로 활용하는 것은 작업자의 의도와는 다르게 흘러할 수 있다. 때문에, B2B 서비스를 개발하는 참여자 분은 자주 변경되고 일관성없는 프론트엔드를 관리하고 통합하기 위해 사내 표준 코드들을 학습시키고 이를 활용할 계획을 고려하고 있었다.

티오더의 하담님도 학교에서 생성형 AI와 관련하여 현 개발에서의 분위기, 그리고 미래에는 많은 부분이 사람을(개발자도) 대체할 것이라는 강의를 했고, 지금의 자녀들에게 코딩을 배우게 하는 것이 옳은지에 대한 질문을 받았다고 했다.

미래에 모든것을 AI가 대체한다면, 지금 코딩을 배우는게 의미 없는거 아닌가요?

이런 직업적 위기는 비단 생성형 AI 이전에도 있었다. 산업이 발전하면서 자동화로 인해 로봇이 제조 공정에서 사람이 하던 일, 즉, 물리적 노동력을 대체하는 것에 대한 위기가 있었다. 그리고 이제는 인간의 지식 노동력을 대체하는 것으로 AI가 그 위협으로 다가오고 있다.

인간의 지식 노동 대체하는 인공지능, 시장 경제 뿌리부터 흔들 것open in new window

혹자는 살아남을 직종이 창조적인 일, 예술이나 미술이라 이야기 했지만, 이미 대중적이고 일반적인 일을 대체 할 수 있다는 것은 앞선 게임 업계에서의 일로도 충분히 그렇지만은 아닐 것임을 알려주고 있다. 또한 화제가 되었던 테슬라의 로봇이 빨래를 접는 영상에서도 보이듯, 섬세한 운동능력도 기계와 AI가 결합하면서 그 가능성을 보여주고 있다.

Elon Musk’s Latest Robot Video Accidentally Gives Away The Magic Trickopen in new window

Optimus folds a shirt

👷‍♀️ 고민 1. 나의 자리를 위협하는가?

좌담회에서 이런 고민과 걱정을 하기 전부터도 생성형 AI가 사람들의 일자리를 위협하리라는 가설들이 쏟아졌다. 물론 이전 역사를 돌아보아도 기술의 발전으로 없어진 직업들이 있었다. 하지만 새로운 기술 및 그에 따른 새로운 행동 양식은 또 다른 역량을 요구하였고 새로운 직업들이 생겨났다. 심지어 이전에는 중요하지 않았던 직무가 중요해진 경우도 있다.

일자리가 가장 빠르게 증가하는 산업과 가장 빠르게 감소하는 산업 (단위:%) - career.go.kr
일자리가 가장 빠르게 증가하는 산업과 가장 빠르게 감소하는 산업 (단위:%) - career.go.kropen in new window

'프롬프트 엔지니어링' 이라는 용어가 나올정도로 어떤 질문을 던지는가가 생성형 AI를 대하는 요구조건이라고 한다면, 올바른 질문을 던질 수 있는 역량이 중요해질 수도 있다. 때문에, 좌담회에서는 오히려 중급 고급 인력은 유지될테지만 초급 인력의 자리가 위협받을 수 있음에 우려가 있었다.

또한 일 이라는 것은 소통의 연속이고, 배경 지식이 없다면 시장을 파악하고 상대의 의중을 아는데 어려움을 겪는다. 생성형 AI가 모든것을 해줄 것 같지만 아직 전체를 파악하고 통찰을 제공하지는 못한다.(현재로서는...)

🫥 고민 2. DevOps 측면에서 간극을 줄여줄까?

좌담회의 주제가 'AI의 시대에서 DevOps가 가야할 방향' 인 것을 고려하면, DevOps를 발전시킴에 있어서 AI는 어떤 작용을 할 것인지 우리는 선택해야 한다. 필자의 예로, 최근 '테라폼으로 시작하는 IaC'라는 책을 냈고, 코드로 인프라를 만든다는 목적의 도구를 다루다보니 안좋은 피드백은 주로 '이건 실무랑 달라,' '내가 원하는건 클라우드를 어떻게 잘 만드는지에 관한건데 그런 내용은 없더라' 같은 내용이였다. 이미 특정 환경을 프로비저닝 한다는 예를 든 책들이 있고, 온라인에 문서에 더 잘 나와있기도 하고, 이번 주제인 AI가 이미 그런 코드를 더 잘 만들기도 하기에 그런 내용은 의도적으로 피하려고도 했다. 오히려 도구가 만들어진 사상과 개발자가 의도한 기능과 방식을 이해하는데 목적을 두었다. 지금은 아는만큼 물을 수 있고, 물어보는 질문의 수준에 따라 답을 얻을 수 있는 AI를 접하는 시기이기 때문이다.

DevOps가 일하는 방식을 고려했을 때 중간에 언급된 'X-LLM'이란 용어도 중요해 보인다. 기존 LLM 모델보다도 작고, 극한 상황에서도 실행되어야 하는 Extreme 이라는 수식이 붙은 이 모델은 엣지, IoT에 연관이 높은 통신에서 큰 관심이 있다. DevOps는 효율적으로 모델을 생성하고 배포하는 일련의 작업을 극한의 환경에 잘 배포하고 업데이트 할지에 대한 고민도 필요할 것이다.

silos | silos | Doc Searls | Flickr
silos | silos | Doc Searls | Flickr

돌아와서, DevOps는 '문화'라 표현하지만 약간 뜬구름 같은 느낌이 있고, 누군가 알려준 '조직간 간극을 줄이는 활동open in new window'이라는 것이 더 잘 와닿는다고 생각한다. 앞서 사례에서도 이제는 다른 사람의 도움 없이도 혼자 해볼 수 있는 환경을 제공하기도 하고, 주니어 개발자의 자리도 대신할 수 있다는 가능성도 있지만 DevOps 측면에서 AI를 활용해서 어떻게 조직간의 간극을 줄일 수 있을지의 방안들도 고민해 볼 시기인 것 같다.

🪑 좌담회를 다녀와서

기술의 발전으로 디스토피아를 상상할 수도 있지만 유토피아 또한 마찬가지이고, 유토피아는 저절로 오지 않고 '유토피아적'으로 만들려는 노력이 있어야만 할 것이다. 최신의 기술 이지만 뜨거운 감자이기도 한 생성형 AI를 두고 아직은 동상이몽이지만, 결국 돈이 어떻게 흐르는가에 따라 방향성이 정해지지 않을까?

앞서 자체 구축형 AI 인프라를 갖고 있다는 사례에서 다시한번 느끼는 것은 생성형 AI는 비용을 지불하거나 투자 받을 수 있는 조건에서야 가능한 자본 집약적 기술이다는 것이다. NVIDIA의 주가는 치솟는 이유, 그리고 최근 샘 올트먼이 AI 반도체에 투자하겠다고 한 것도 생성형 AI의 판도를 반도체와 그 반도체로 구성된 무엇인가를(현재는 GPU) 좌우 할 수 있는 것이 향후 판도를 가름할 것이라 보인다.

뒷풀이에서 회자된 것 처럼 결국 NVIDIA 주식이 답인가? 🤣

+ + + diff --git a/06-etc/class/devops-discussion-2nd.html b/06-etc/class/devops-discussion-2nd.html new file mode 100644 index 0000000000..45f2e52a6e --- /dev/null +++ b/06-etc/class/devops-discussion-2nd.html @@ -0,0 +1,40 @@ + + + + + + + + + + DevOps 연구소 좌담회 (2차) | docmoa + + + + + +
본문으로 건너뛰기

DevOps 연구소 좌담회 (2차)

1분 미만devopscontainer

DevOps 연구소 좌담회 (2차)

일시 : 2019년 5월 23일 (목) 19:00 ~ 21:30

안내 : 컨테이너 연구소open in new window - 컨테이너 시스템의 활용 방향 및 미래에 관련해서 좌담 part2

장소 : 대륭서초타워 베스핀글로벌

Q. 컨테이너란?

  • Namespace가 지원되는 Process (Tech 관점)
  • 스타트업에서는 비용 절감 가능 ($ 관점)

Q. 왜 컨테이너를 사용해야 하는가?

  • 개발자는 인프라를 이해할 수 있다.
  • 운영자는 코드로 인프라를 관리 할 수 있다.
  • 시대적인 흐름이다.
  • 컨테이너 IDC는 돈을 절감할 수 있다.

Q. 어디에 어떻게?

모바일 광고 회사 사례

  • 컨테이너 환경 사용 전 : 오전과 오후 특정 시간에 트래픽이 발생하는데, 평소에는 최대 트래픽을 예상하여 인프라를 구성하여 사용 >> 유지보수 비용이 많이 발생
  • 컨테이너 환경 사용 후 : 예상되는 피크시간대에 자동으로 컨테이너를 확장하는 방식으로 트래픽 처리하는 방식으로 변경, 서비스 중에도 로직을 변경하여 적용할 수 있는 이점 확보

Q. 어디에 쓰지 못할까?

  1. System reliability 유지를 위한 환경
  2. 고성능이 요구되는 환경
  3. DB같은 Stateful한 환경을 컨테이너로? DB CI/CD는 어떻게

Q. 편하게 사용할 수 있는 컨테이너?

  • 컨테이너는 rootless 하다.

  • kubernetes는 할렘가 같다

    • 사용하지 않는 자원으로 인한 소비가 많다.
    • 이런건 MicroVM으로 해결하능할 것으로 예상한다.
  • 스타트업의 경우 서버를 구축해서 사용하기 보다는 어느정도 서비스 형태로 된 컨테이너 서비스를 사용할 후 구축해보는 것을 추천한다.

Q. 컨테이너 사용 시 주의 사항

  • 대문자 주의!
  • 컨테이너 빌드 시 베이스 이미지도 지속적으로 관리 필요 (버전 등)
  • 주객전도 주의! 비즈니스에 포커싱하자!
  • 개발자 판단 후 결정이 필요

Appendix

  • KEDA

    • https://github.com/kedacore/kedaopen in new window
    • 마이크로소프트(MS)가 레드햇과 손잡고 개발한 컨테이너 인프라의 확장(scaling) 자동화 기술입니다.
    • CPU 메트릭 정보를 기반으로 했던 확장방식의 한계를 넘어 주요 '이벤트'를 기반으로 확장을 정의, 수행할 수 있습니다.
    • 즉, Kubernetes Metrics Server 역할을하며 사용자는 전용 Kubernetes 사용자 지정 리소스 정의를 사용하여 자동 확장(Autoscaling)을 위한 규칙을 정의 할 수 있습니다.
  • Podman vs. Docker

    • https://developers.redhat.com/blog/2019/02/21/podman-and-buildah-for-docker-users/open in new window

    • Docker

      • 단일 프로세스가 단일 실패 지점이 될 수 있습니다.

      • 이 프로세스는 모든 하위 프로세스 (실행중인 컨테이너)를 소유합니다.

      • 상위 프로세스에 문제가 발생하면 컨트롤에서 벗어나는 프로세스가 발생합니다.

      • 컨테이너 환경으로 인해 보안 취약성이 발생할 수 있습니다.

      • 모든 Docker 작업은 동일한 전체 루트 권한을 가진 사용자 (또는 사용자)가 수행해야했습니다.

        Docker Work - Docker
        Docker Work - Docker
    • Podman

      • Podman siteopen in new window

      • Podman 방식은 이미지 레지스트리, 컨테이너 및 이미지 저장소, runC 컨테이너 런타임 프로세스 (no Daemon)를 통해 Linux 커널과 직접 상호 작용하는 것입니다.

        Podman Work - Podman
        Podman Work - Podman
+ + + diff --git a/06-etc/class/index.html b/06-etc/class/index.html new file mode 100644 index 0000000000..57aea853a6 --- /dev/null +++ b/06-etc/class/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Class | docmoa + + + + + +
본문으로 건너뛰기

Class

1분 미만

+ + + diff --git a/06-etc/index.html b/06-etc/index.html new file mode 100644 index 0000000000..510fe6a46e --- /dev/null +++ b/06-etc/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Etc. | docmoa + + + + + +
본문으로 건너뛰기

Etc.

1분 미만Etc

+ + + diff --git a/06-etc/infomation/Keyboard-Eng.html b/06-etc/infomation/Keyboard-Eng.html new file mode 100644 index 0000000000..6c0a8a9bec --- /dev/null +++ b/06-etc/infomation/Keyboard-Eng.html @@ -0,0 +1,40 @@ + + + + + + + + + + 키보드의 특수기호 영어 명칭 | docmoa + + + + + +
본문으로 건너뛰기

키보드의 특수기호 영어 명칭

1분 미만keyboardtip

키보드의 특수기호 영어 명칭

원본링크 : https://www.facebook.com/DoppioLover/posts/10225430970749092?__cft__[0]=AZVQVje3HpQ-XcR1aulTomjrKkwP3dgMkwtxvqSrRed0-yZn5vMd_fFoawk9FqeqSj7bIGYg4Ui1zUDYaE2anZDkndlmTgjhFCEnIkTD__lyGfjDt8Kf8od2Ayz3ZPT4PSo&__tn__=%2CO%2CP-Ropen in new window

기호영어 명칭
~tilde. 이건 미국에서는 "틸다" 쯤 발음한다. 그런데 이게 뭐라 부르는지 모르는 분들 꽤 많음. 그냥 wavy thingy under escape하는 사람도 본 적이 있다.
`backtick 또는 backquote
!exclamation 또는 exclamation point. IT업계 종사자는 bang이라고도 한다.
@at sign
#pound 또는 crosshatch 또는 number sign 또는 hash. 한국에서는 흔히 sharp라고 불리는데 의외로 미국에서는 그렇게 부르는 사람을 거의 못봤다. 아마도 한국에서는 음악교육에서 악보일기를 가르쳐서 그런 것 아닐까 싶다.
$dollar sign
%percent sign
^caret 또는 circumflex. 과학용 계산기에 익숙한 사람들은 exponential power sign이라고 하기도 한다. 그런데 미국사람들도 이걸 뭐라는지 모르는 사람이 의외로 많다. 그냥 6키 위에 있는 것(that thing above 6 key)라고 하는 사람들 간혹 만난다.
&ampersand 또는 and sign
*asterisk 또는 star symbol.
(opening parenthesis. 줄여서 open paren만 하는 경우가 흔하다.
)closing parenthesis. 줄여서 close paren...
_underscore. 아주 드물게 underbar라고 하는 사람도 있다.
-minus sign 또는 hyphen 또는 dash
+plus sign
=equal sign
{opening brace . 흔히 left curly brace라고불린다.
}closing brace 또는 right curly brace
[opening bracket. 흔히 left square bracket이라고도 불린다.
]closing bracket 또는 right square bracket
|pipe 또는 vertical bar
\back slash 또는 backward slash
:colon
;semicolon 발음은 세미콜론 또는 세마이콜론
quotation mark 또는 double quote
apostrophe 또는 single quote
<less than sign. 가끔 left angle bracket이라고 부르는 경우가 있다.
,comma
>greater than sign. 가끔 right angle bracket이라고 하는 사람들이 있다.
.period 또는 dot. 일반적으로 period는 영어 언어적 용도로, dot은 컴퓨터 프로그래밍 관련으로 쓰인다.
?question mark
/slash 또는 forward slash.
+ + + diff --git a/06-etc/infomation/acronyms.html b/06-etc/infomation/acronyms.html new file mode 100644 index 0000000000..c6bb796762 --- /dev/null +++ b/06-etc/infomation/acronyms.html @@ -0,0 +1,40 @@ + + + + + + + + + + 약어 | docmoa + + + + + +
본문으로 건너뛰기

약어

약 2 분acronymstip

약어

Full name definition
List of informationtechnology initialismsopen in new window

A

  • ACL : Access Control List
  • AD : Active Directory
  • AES : Advanced Encryption Standard
  • AJAX : Asynchronous JavaScript and XML
  • API : Application Programming Interface
  • ARP : Address Resolution Protocol
  • AWS : Amazon Web Service
  • APM : Application Performance Monitoring(Management)

B

  • BE : Back-End
  • B2B : Business-to-Business
  • B2C : Business-to-Consumer
  • B2G : Business-to-Government
  • BDD : Behavioral Driven Development

C

  • C2C : Consumer-to-Consumer
  • CIDR : Classless Inter-Domain Routing
  • CLI : Command Line Interpreter
  • CPU : Central Processing Unit
  • CRUD : Create, Read, Update, Delete
  • CS : Customer Service
  • CS : Consumer Satisfaction
  • CS : Computer Science
  • CSS : Cascading Style Sheets
  • CSV : Comma-Separated Values
  • CSU : Channel Service Unit

D

  • DES : Data Encryption Standard
  • DHCP : Dynamic Host Configuration Protocol
  • DNS : Domain Name System
  • DM : Direct Mail
  • DOM : Document Object Model
  • DSU(DU) : Data Service Unit
  • DRAM : Dynamic random-access memory
  • DW : Data Warehouse
  • DL : Data Lake

E

  • ERD : Entity-Relationship Diagram
  • EOF : End Of File
  • eDM : Electronic Direct Mail

F

  • FE : Front-End
  • FIFO : First In First Out
  • FTP : File Transfer Protocol

G

  • GIF : Graphics Interchange Format
  • GNU : GNU's Not Unix!
  • GUI : Graphic User Interface
  • GCP : Google Cloud Platform

H

  • HTTP : Hyper Text Transfer Protocol
  • HTTPS : Hyper Text Transfer Protocol Secure
  • HTML : Hyper Text Markup Language
  • HCL : HarchiCorp Configuration Language

I

  • IaC : Infrastructure as a Code
  • IaaS : Infrastructure as a Service
  • IDE : Integrated Development Environment
  • IEEE : Institute for Electrical and Electronic Engineers
  • IMAP : Internet Message Access Protocol
  • IP : Internet Protocol
  • iSCSI : Internet Small Computer System Interface

J

  • JIT : Just-In-Time Compiler
  • JPG(JPEG) : Joint Photographic Experts Group
  • JSON : Java Script Object Notation
  • JSP : Jakarta Server Pages

K

  • kbps : Kilobit per second
  • KPI : Key Performance Indicator
  • K8S : Kubernetes

L

  • LAN : Local area network

M

  • MAC : Media access control
  • NAS : Network-Attached storage
  • Mbps : Megabits per second
  • MIME : Multipurpose Internet Mail Extensions
  • MTU : Maximum Transmission Unit
  • MVC : Model View Controller

N

  • NCP : Naver Cloud Platform
  • NIC : Network Interface Card
  • NoSQL : Not-only SQL / Non SQL
  • NPM : Node Package Manager
  • NTP : Network Time Protocol
  • NVRAM : Non-volatile RAM

O

  • O2O : Online to Offline / Offline to Online
  • OCP : OpenShift Container Platform
  • OSI : Open System Interconnect
  • OOM : Out Of Memory

P

  • PaaS : Platform as a Services
  • PC : Personal Computer
  • PDF : Portable Document Format file
  • PNG : Portable Network Graphics
  • POP3 : Post Office Protocol, version 3

Q

  • QoS : Quality of Service

R

  • RAM : Random Access Memory
  • RDB : Relational Database
  • Regex : REGular EXPression
  • REST : REpresentational State Transfer
  • RFC : Request for Comments
  • ROM : Read-Only Memory

S

  • SAN : Storage Area Network
  • SaaS : Software as a Service
  • SI : System Integrator
  • SVG : Scalable Vector Graphics
  • SDK : Software Development Kit
  • SDN : Software Defined Networking
  • SMTP : Simple Mail Transfer Protocol
  • SNMP : Simple Network Management Protocol
  • SPA : Single Page Application
  • SSH : Secure SHell
  • SQL : Structured Query Language
  • SYN (TCP) : Synchronization

T

  • TCP/IP : Transmission Control Protocol / Internet Protocol
  • TDD : Test-Driven Development

U

  • UDP : User Datagram Protocol
  • UI : User Interface
  • UX : User eXperience
  • URI : Uniform Resource Identifier
  • URL : Uniform Resource Locator
  • USB : Universal Serial Bus
  • UTP : Unshielded twisted pair

V

  • VLAN : Virtual local area network
  • VXLAN : Virtual Extensible LAN
  • VPN : Virtual Private Network

W

  • WAS : Web Application Server
  • WAN : Wide-Area Network
  • Wi-Fi : Wireless Fidelity
  • WWW : World Wide Web

X

  • XML : eXtensible Markup Language

Y

  • YAML : Yet Another Markup Language

Z

+ + + diff --git a/06-etc/infomation/index.html b/06-etc/infomation/index.html new file mode 100644 index 0000000000..65e9f8c92e --- /dev/null +++ b/06-etc/infomation/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Infomation | docmoa + + + + + +
본문으로 건너뛰기

Infomation

1분 미만

+ + + diff --git a/06-etc/mac/brew-cert-issue.html b/06-etc/mac/brew-cert-issue.html new file mode 100644 index 0000000000..f86ff5e067 --- /dev/null +++ b/06-etc/mac/brew-cert-issue.html @@ -0,0 +1,61 @@ + + + + + + + + + + homebrew install - certificate has expired | docmoa + + + + + +
본문으로 건너뛰기

homebrew install - certificate has expired

1분 미만machomebrewbrew

homebrew install - certificate has expired

  • 현상 : brew 설치시 인증서 에러 발생

https://apple.stackexchange.com/questions/393481/homebrew-cask-download-failure-ssl-certificate-problem-certificate-has-expiredopen in new window

  • 오류 내용 :
Already downloaded: /Users/gslee/Library/Caches/Homebrew/downloads/b6ccc5a2a602c2af3480bbcf1656bd9844595974ba60501871ac12504508e818--openssl-1.1.1l.tar.gz
+==> Downloading https://ftp.gnu.org/gnu/wget/wget-1.21.2.tar.gz
+
+curl: (60) SSL certificate problem: certificate has expired
+More details here: https://curl.haxx.se/docs/sslcerts.html
+
+curl performs SSL certificate verification by default, using a "bundle"
+ of Certificate Authority (CA) public keys (CA certs). If the default
+ bundle file isn't adequate, you can specify an alternate file
+ using the --cacert option.
+If this HTTPS server uses a certificate signed by a CA represented in
+ the bundle, the certificate verification probably failed due to a
+ problem with the certificate (it might be expired, or the name might
+ not match the domain name in the URL).
+If you'd like to turn off curl's verification of the certificate, use
+ the -k (or --insecure) option.
+HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
+Error: wget: Failed to download resource "wget"
+Download failed: https://ftp.gnu.org/gnu/wget/wget-1.21.2.tar.gz
+



 















  • 원인 : 다운로드를 위한 링크의 인증서가 만료된 경우 brew에서 다운로드를 위해 사용하는 curl에서 인증서 오류 발생

  • 해결방안 :

    • curl 에 curlrc를 사용하도록 설치
      • ~/.curlrc 파일에 --insecure 추가
      • HOMEBREW_CURLRC 환경변수를 enable 하여 curl 설치
      HOMEBREW_CURLRC=1 brew install curl
      +
      • 이후 패키지 재설치ㅉ
    • 기존 설정으로 원복하고 옵션으로 --insecure를 활성화
      • ~/.curlrc 파일을 삭제하거나 해당 파일에서 --insecure 삭제
      • 필요시 HOMEBREW_FORCE_BREWED_CURL 환경변수를 enable 하여 사용
      HOMEBREW_FORCE_BREWED_CURL=1 brew install curl
      +
+ + + diff --git a/06-etc/mac/index.html b/06-etc/mac/index.html new file mode 100644 index 0000000000..c433d649cb --- /dev/null +++ b/06-etc/mac/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Mac | docmoa + + + + + +
본문으로 건너뛰기

Mac

1분 미만

+ + + diff --git a/06-etc/mac/libunistring-issue.html b/06-etc/mac/libunistring-issue.html new file mode 100644 index 0000000000..9c98626880 --- /dev/null +++ b/06-etc/mac/libunistring-issue.html @@ -0,0 +1,79 @@ + + + + + + + + + + Library not loaded: libunistring.2.dylib | docmoa + + + + + +
본문으로 건너뛰기

Library not loaded: libunistring.2.dylib

1분 미만machomebrewbrewwget

Library not loaded: libunistring.2.dylib

현상

macOS Ventura 업그레이드 후 wget 실행시 오류 발생

$ wget
+dyld[4414]: Library not loaded: /usr/local/opt/libunistring/lib/libunistring.2.dylib
+  Referenced from: <1ECBA17E-A426-310D-9902-EFF0D9E10532> /usr/local/Cellar/wget/1.21.3/bin/wget
+  Reason: tried: '/usr/local/opt/libunistring/lib/libunistring.2.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/opt/libunistring/lib/libunistring.2.dylib' (no such file), '/usr/local/opt/libunistring/lib/libunistring.2.dylib' (no such file), '/usr/local/lib/libunistring.2.dylib' (no such file), '/usr/lib/libunistring.2.dylib' (no such file, not in dyld cache), '/usr/local/Cellar/libunistring/1.1/lib/libunistring.2.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/libunistring/1.1/lib/libunistring.2.dylib' (no such file), '/usr/local/Cellar/libunistring/1.1/lib/libunistring.2.dylib' (no such file), '/usr/local/lib/libunistring.2.dylib' (no such file), '/usr/lib/libunistring.2.dylib' (no such file, not in dyld cache)
+[1]    4414 abort      wget
+

해결방안 찾기 과정

$ brew uninstall --force gettext
+xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
+Error: Refusing to uninstall /usr/local/Cellar/gettext/0.21.1
+because it is required by cairo, gdk-pixbuf, git, glib, gnupg, gnutls, gobject-introspection, graphviz, gts, harfbuzz, libidn2, librsvg, libslirp, pango, podman, qemu and wget, which are currently installed.
+You can override this and force removal with:
+brew uninstall --ignore-dependencies gettext
+
  • 오류 메시지에서의 안내대로 --ignore-dependencies 추가하여 실행
$ brew uninstall --ignore-dependencies gettext
+Uninstalling /usr/local/Cellar/gettext/0.21.1... (1,983 files, 20.6MB)
+
  • gettext 재설치하면 도중에 실행이 안됐던 wget도 재설치
$ brew install gettext
+...
+==> Upgrading wget
+1.21.3 -> 1.21.3_1
+
+==> Installing dependencies for wget: openssl@3
+==> Installing wget dependency: openssl@3
+==> Pouring openssl@3--3.0.7.ventura.bottle.tar.gz
+🍺  /usr/local/Cellar/openssl@3/3.0.7: 6,454 files, 28.2MB
+==> Installing wget
+==> Pouring wget--1.21.3_1.ventura.bottle.1.tar.gz
+🍺  /usr/local/Cellar/wget/1.21.3_1: 89 files, 4.2MB
+==> Running `brew cleanup wget`...
+Removing: /usr/local/Cellar/wget/1.21.3... (89 files, 4.2MB)
+==> Checking for dependents of upgraded formulae...
+...
+
  • git도 업그레이드 후 영향을 받았는지 재설치 필요
Error: 'git' must be installed and in your PATH!
+Warning: gettext 0.21.1 is already installed and up-to-date.
+To reinstall 0.21.1, run:
+    brew reinstall gettext
+
  • brew install git으로 다시 git설치

  • 마지막 재설치 요구에 따라 gettext 재설치

$ brew reinstall gettext
+
  • wget 재실행 시 정상 동작 확인
$ wget
+wget: URL 빠짐
+사용법: wget [<옵션>]... [URL]...
+
+자세한 옵션은 `wget --help'를 입력하십시오.
+
+ + + diff --git a/06-etc/nodejs/index.html b/06-etc/nodejs/index.html new file mode 100644 index 0000000000..9b4dd1ecfb --- /dev/null +++ b/06-etc/nodejs/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + Nodejs | docmoa + + + + + +
본문으로 건너뛰기

Nodejs

1분 미만

+ + + diff --git a/06-etc/nodejs/node-sass.html b/06-etc/nodejs/node-sass.html new file mode 100644 index 0000000000..6c20bd95b5 --- /dev/null +++ b/06-etc/nodejs/node-sass.html @@ -0,0 +1,45 @@ + + + + + + + + + + node-sass와 sass로의 전환 | docmoa + + + + + +
본문으로 건너뛰기

node-sass와 sass로의 전환

1분 미만armnodejs

node-sass와 sass로의 전환

aarch64에서 vuepress 실행을 위해 테스트를 하던 도중 node-gyp와 node-sass에 대한 오류를 맞이하게 되었다.

node-sass의 경우 arm환경에 대한 빌드 릴리즈가 없는 관계로 npm install을 실행하면 다시 빌드를 하게되는데, 이때 node-sass를 빌드하는 과정에서 빌드 실패가 발생함

node-sass란?

node환경에서 sass는 css 코드로 변환해주는 스타일 전처리언어이다. c/c++로 되어있는 구성요소로 인해 빠른 빌드 속도를 제공한다.

node-sass와 Nodejs 버전별 호환성

관련 내용을 찾다보면 Node 버전에 맞는 node-sass를 사용해야 한다고 나오는데 그 이유는 libsass 때문이고 c/c++로 되어있는 해당 라이브러리 특성상 Node 버전과 실행 환경에 종속적이다.

node-sass 지원 환경 (https://github.com/sass/node-sass/releasesopen in new window)

OSArchitectureNode
Windowsx86 & x6412, 14, 16, 17
OSXx6412, 14, 16, 17
Linux6412, 14, 16, 17
Alpine Linuxx6412, 14, 16, 17
FreeBSDi386 & x6412, 14

추가로 node-sass는 항후 Dart Sass로 옮겨진다로 안내되어있음

https://github.com/sass/node-sassopen in new window

Dart Sass 로의 전환

stackoverflow에서 기존 node-sass를 사용하고 있는 경우 해당 프로젝트를 Dart Sass로 전환하는 선언 방식을 알려줬다.

https://stackoverflow.com/a/70171361open in new window

npm remove node-sass
+npm i node-sass@npm:sass -D
+

package.json에 다음과 같이 추가된다.

  "devDependencies": {
+    "node-sass": "npm:sass@^1.55.0",
+  },
+
+ + + diff --git a/404.html b/404.html new file mode 100644 index 0000000000..3392672b9b --- /dev/null +++ b/404.html @@ -0,0 +1,40 @@ + + + + + + + + + + docmoa + + + + + +
본문으로 건너뛰기

404

페이지를 찾을 수 없습니다.

4-0-4 입니다.

+ + + diff --git a/99-about/01-About.html b/99-about/01-About.html new file mode 100644 index 0000000000..28127458c0 --- /dev/null +++ b/99-about/01-About.html @@ -0,0 +1,40 @@ + + + + + + + + + + docmoa | docmoa + + + + + +
본문으로 건너뛰기

docmoa

1분 미만

docmoa

logo
logo

기술은 지속적으로 발전하고 시시각각 변화하고 있습니다. 더불어 IT라는 분야도 점점더 세분화되고, 혼자서는 모든것을 아는것은 거의 불가능합니다.
IT 업을 하면서 정리와 스크랩은 일상이 되어가지만 변화를 쫓아가기는 정말 버겁습니다.

하지만 혼자서가 아니라면 어떨까 라는 생각을 합니다.
집단지성 이란 표현이 있듯, 개인보다는 여럿이 만들어가는 노트입니다. 과거에는 이런 노하우가 개인의 자산으로 비밀처럼 감춰두던 지식이였지만 지금은 서로 공유하고, 알리고, 기여하는 것도 의미 있는 시기인것 같습니다.

코딩의 시대에 걸맞게 코드를 관리하듯 노트를 관리하고 기여하면서 커나가는 오픈소스 아닌 오픈노트, 오픈노하우 입니다.

다수의 기여자에 의해 문서가 생성되므로 글 작성 방식과 문맥이 서로 상이할 수 있습니다. 각 문서의 기여자는 github repoopen in new window 에서 확인해주세요.

문서의 성격

문서는 본인을 위해, 혹은 동료, 또는 누군가를 위해 필요로 할 때 바로 확인 할 수 있는 기술 문서여야 합니다.

  • 사실과 결과에 기반한 문서를 지향합니다.
  • 즉시적으로 도움이 될 수 있는 정보를 최우선으로 합니다.
  • 뉴스, 트렌드, 마케팅 관련 내용은 담지 않습니다.

라이선스

적용되어있는 라이선스는 CC BY-NC-ND 4.0open in new window 입니다.

docmoa 에 개시되는 모든 기여되는 문서의 저작권, 권리, 책임은 문서를 기여한 저작자 에게 있습니다.

이용자의 권리

포맷 변경을 포함하여 모든곳에 공유 가능합니다.

제약사항

  • 저작자 표시 : 출처를 반드시 표기해야 합니다. 변경이 필요한 경우 기여자에게 문의하셔야 합니다.
  • 비영리 : 기본적으로 모든 기여된 항목은 기여한 저작자에게 있습니다. 영리목적의 이용이 필요한 경우 저작자에게 문의하셔야 합니다.
  • 변경금지 : 원 저작물을 변형하여 2차 저작물을 작성할 수 없습니다.

면책조항

본 사이트의 포스팅에 담긴 관점과 의견은 작성자의 개인적인 생각을 바탕으로 작성되었으며 어떠한 내용도 특정 회사나 단체의 공적인 입장을 대변하지 않습니다.

VuePress

해당 페이지는 github page에서 VuePressopen in new window를 기반으로 구성되었습니다. 이외에 검토되었던 항목은 다음과 같습니다.

  • WordPress
  • jekyll
  • mkdocs
  • 제로보드
  • Wiki

페이지 구성을 위해 참고한 자료는 다음과 같습니다.

+ + + diff --git a/99-about/02-Thanks.html b/99-about/02-Thanks.html new file mode 100644 index 0000000000..cbbbf21b65 --- /dev/null +++ b/99-about/02-Thanks.html @@ -0,0 +1,40 @@ + + + + + + + + + + Thank you | docmoa + + + + + +
본문으로 건너뛰기

Thank you

1분 미만

Thank you

Contributors:

Loading contributors...

+ + + diff --git a/99-about/index.html b/99-about/index.html new file mode 100644 index 0000000000..2675f78a4d --- /dev/null +++ b/99-about/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + 99 about | docmoa + + + + + +
본문으로 건너뛰기

99 about

1분 미만

+ + + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..e30a02cf4e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,150 @@ +# LICENSE-CC-BY-SA-4.0.md + +UNLESS OTHERWISE NOTED, THE CONTENTS OF THIS REPOSITORY ARE LICENSED UNDER THE CREATIVE COMMONS ATTRIBUTION - SHARE ALIKE 4.0 INTERNATIONAL LICENSE + +[![https://creativecommons.org/licenses/by-sa/4.0/](https://camo.githubusercontent.com/0df8fd3e955d97ae69dedfa2568fb2dd4186cd60917ca40aefabb7466d5b46ce/68747470733a2f2f692e6372656174697665636f6d6d6f6e732e6f72672f6c2f62792d73612f342e302f38387833312e706e67)](https://camo.githubusercontent.com/0df8fd3e955d97ae69dedfa2568fb2dd4186cd60917ca40aefabb7466d5b46ce/68747470733a2f2f692e6372656174697665636f6d6d6f6e732e6f72672f6c2f62792d73612f342e302f38387833312e706e67) + +## License Summary of CC-BY-SA 4.0 International + +*This section is a human-readable summary of (and not a substitute for) the full license included below.* + +### You are free to: + +- **Share** — copy and redistribute the material in any medium or format +- **Adapt** — remix, transform, and build upon the material for any purpose, even commercially. + +The licensor cannot revoke these freedoms as long as you follow the license terms. + +### Under the following terms: + +- [![CC-BY](https://camo.githubusercontent.com/e3647f8ff7bba06b0e6adb0305ae78dd72781a82f3347f91408bdabf0d274d90/68747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f696d616765732f646565642f62792e706e67)](https://camo.githubusercontent.com/e3647f8ff7bba06b0e6adb0305ae78dd72781a82f3347f91408bdabf0d274d90/68747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f696d616765732f646565642f62792e706e67) **Attribution** — You must give **appropriate credit**, provide **a link to the license**, and **indicate if changes were made**. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +- [![CC-BY](https://camo.githubusercontent.com/bbcb6c03d9818ed27ec58ad0f91f187125f509b602a02f6da9807e50ccc86f2e/68747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f696d616765732f646565642f73612e706e67)](https://camo.githubusercontent.com/bbcb6c03d9818ed27ec58ad0f91f187125f509b602a02f6da9807e50ccc86f2e/68747470733a2f2f6372656174697665636f6d6d6f6e732e6f72672f696d616765732f646565642f73612e706e67) **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the **same license** as the original. + +**No additional restrictions** — You may not apply legal terms or **technological measures** that legally restrict others from doing anything the license permits. + +### Notices: + +You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable **exception or limitation**. + +No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as **publicity, privacy, or moral rights** may limit how you use the material. + +# Creative Commons Attribution-ShareAlike 4.0 International License + +FROM: https://creativecommons.org/licenses/by-sa/4.0/legalcode + +Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. + +### Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. + +- **Considerations for licensors:** Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). +- **Considerations for the public:** By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). + +### Creative Commons Attribution-ShareAlike 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. + +**Section 1 – Definitions.** + +1. **Adapted Material** means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. +2. **Adapter's License** means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. +3. **BY-SA Compatible License** means a license listed at [ creativecommons.org/compatiblelicenses][4], approved by Creative Commons as essentially the equivalent of this Public License. +4. **Copyright and Similar Rights** means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. +5. **Effective Technological Measures** means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. +6. **Exceptions and Limitations** means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. +7. **License Elements** means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike. +8. **Licensed Material** means the artistic or literary work, database, or other material to which the Licensor applied this Public License. +9. **Licensed Rights** means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. +10. **Licensor** means the individual(s) or entity(ies) granting rights under this Public License. +11. **Share** means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. +12. **Sui Generis Database Rights** means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. +13. **You** means the individual or entity exercising the Licensed Rights under this Public License. **Your** has a corresponding meaning. + +**Section 2 – Scope.** + +1. **License grant**. + 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: + 1. reproduce and Share the Licensed Material, in whole or in part; and + 2. produce, reproduce, and Share Adapted Material. + 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. + 3. Term. The term of this Public License is specified in Section 6(a). + 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. + 5. Downstream recipients. + 1. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. + 2. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter's License You apply. + 3. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. + 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). +2. **Other rights**. + 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. + 2. Patent and trademark rights are not licensed under this Public License. + 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. + +**Section 3 – License Conditions.** + +Your exercise of the Licensed Rights is expressly made subject to the following conditions. + +1. **Attribution**. + 1. If You Share the Licensed Material (including in modified form), You must: + 1. retain the following if it is supplied by the Licensor with the Licensed Material: + 1. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); + 2. a copyright notice; + 3. a notice that refers to this Public License; + 4. a notice that refers to the disclaimer of warranties; + 5. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; + 2. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and + 3. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. + 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. + 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. +2. **ShareAlike**. + +In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. + +``` +1. The Adapter's License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License. +2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. +3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. +``` + +**Section 4 – Sui Generis Database Rights.** + +Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: + +1. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; +2. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and +3. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. + +**Section 5 – Disclaimer of Warranties and Limitation of Liability.** + +1. **Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.** +2. **To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.** +3. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. + +**Section 6 – Term and Termination.** + +1. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. +2. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: + 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or + 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. +3. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. +4. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +**Section 7 – Other Terms and Conditions.** + +1. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. +2. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. + +**Section 8 – Interpretation.** + +1. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. +2. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. +3. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. +4. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. + +------ + +*Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." The text of the Creative Commons public licenses is dedicated to the public domain under the [CC0 Public Domain Dedication](http://creativecommons.org/publicdomain/zero/1.0/legalcode). Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.* + +*Creative Commons may be contacted at [creativecommons.org](http://creativecommons.org/).* + +*Additional languages available: [Bahasa Indonesia](http://creativecommons.org/licenses/by/4.0/legalcode.id), [Nederlands](http://creativecommons.org/licenses/by/4.0/legalcode.nl), [norsk]//creativecommons.org/licenses/by/4.0/legalcode.no, [suomeksi](https://creativecommons.org/licenses/by/4.0/legalcode.fi), [te reo Māori](https://creativecommons.org/licenses/by/4.0/legalcode.mi), [українська](https://creativecommons.org/licenses/by/4.0/legalcode.uk), [日本語](https://creativecommons.org/licenses/by/4.0/legalcode.ja). Please read the [FAQ](https://wiki.creativecommons.org/FAQ#officialtranslations) for more information about official translations.* diff --git a/README.md b/README.md new file mode 100644 index 0000000000..8038e3b555 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# docmoa + +docmoa (https://docmoa.github.io) 소스페이지 입니다. + diff --git a/article/index.html b/article/index.html new file mode 100644 index 0000000000..264861b719 --- /dev/null +++ b/article/index.html @@ -0,0 +1,124 @@ + + + + + + + + + + 게시글 | docmoa + + + + + +
본문으로 건너뛰기
Vault KMIP FAQ
+
KMIP 적용 흐름도
KMIP 적용 흐름도
+

Client 인증서의 유효 기간

+

기본 설정시 1,209,600초(2주)의 유효 기간을 갖게 되며, 설정에 따라 긴 유효시간의 적용이 가능합니다. (옵션 : deault_tls_client_ttl)
+설정은 상기 도식화한 절차 중 "2. kmip 기본 config" 단계에 적용 가능하며. 이는 KMIP 적용 흐름도의 "4. kmip scope, role 정의" 단계에서 override 할 수 있습니다.


hashicat(MZC), chadness12(MZC)약 2 분vaultkmip
DevOps Korea 좌담회 2024.2.13.
torder-townhall
torder-townhall
+

DevOps Korea 좌담회 2024.2.13.

+
    +
  • +

    일시 : 2024년 2월 13일 (화) 19:00

    +
  • +
  • +

    안내 :

    + +
  • +
  • +

    장소 : 파크원2타워 티오더

    +
  • +
  • +

    주요 아젠다

    +
      +
    • AI시대 관련 변화된 시대에 맞게 대응 할 방향
    • +
    • DevOps 업무에 도움이 될만한 AI도구 및 방법론
    • +
    • 이미 적용중인 AI를 활용한 엔지니어링 업무소개 등
    • +
    +
  • +

GS1분 미만devopsai
2024년 2월
+

Product 소개

+
    +
  • HCP Vault Radar begins limited beta +
      +
    • Hashicorp Blog
    • +
    • 작년 2023년 10월 Hashiconf 에서 공개된 HCP Vault Radar 가 Alpha 를 거쳐 Beta 가 출시되었습니다. Beta 에서는 RBAC/ABAC 을 지원하며 스캔할 수 있는 새로운 데이터 소스도 선보입니다
    • +
    +
  • +

1분 미만HashicorpUpdateFeb
Terraform Cloud Agent 가이드
+

Terraform Cloud Agent(Agent)는 Terraform Enterprise/Cloud(TFE/C)에서 사용가능한 사용자 정의 Terraform 실행 환경을 제공합니다. 사용자는 Agent를 사용하여 Terraform 실행을 위해 기본 제공되는 이미지 대신 커스텀 패키지가 설치된 별도 이미지를 사용할 수 있고, 이미지 실행 위치를 네트워크 환경에서 자체 호스팅 할 수 있습니다.

+
Monosnap Terraform Agent | onemodel 2024-01-08 14-40-18
Monosnap Terraform Agent | onemodel 2024-01-08 14-40-18

약 2 분Terraform
2024년 1월
+

Product 소개

+
    +
  • HashiCorp 2023 year in review: Community +
      +
    • Hashicorp Blog
    • +
    • 작년 2023년 한 해동안 있었던 Hashicorp 관련 이야기: 개최된 컨퍼런스 및 이벤트부터 새로 출시된 솔루션 별 트레이닝 및 자격증 관련, 그리고 창업자 Mitchell Hashimoto 의 퇴사 소식 등을 한 번에 확인하실 수 있습니다.
    • +
    +
  • +

약 1 분HashicorpUpdateJan
Transit Key Exportable Deny
+

1. EGP용 정책 생성 exportable_deny.sentinel

+
import "strings"
+
+exportable = request.data.exportable
+
+exportable_check = rule {
+  exportable is "false"
+}
+
+main = rule {
+  exportable_check
+}
+

1분 미만VaultSentinelPolicy
Kubernetes에 Vault Agent(Sidecar) 수동 구성
+

Kubernetes(K8s)환경에서 외부 Vault(External Vault Server)와 연계하는 경우 일반적으로 kubernetes 인증방식을 활용하여 Vault와 K8s 간 플랫폼 수준에서의 인증을 처리하나, K8s로의 Cluster API에 대한 inbound가 막혀있는 경우 이같은 방식은 사용할 수 없다. 따라서 helm, vso 같은 방식의 사용이 불가능하므로 Vault Agent를 Sidecar로 함께 배포하는 경우 수동으로 구성해주어야 한다.


약 3 분vaultkubernetes
2023년 11월
+

Product 소개

+
    +
  • Infrastructure and security releases open HashiConf 2023 +
      +
    • Hashicorp Blog
    • +
    • 샌프란시스코에서 개최된 Hashiconf 2023 에서 8가지의 솔루션에 대해 그룹을 크게 Infrastructure 와 Security 로 구분지어 앞으로의 솔루션 포트폴리오 및 업데이트를 진행하며, Terraform test framework, Vault Secret Sync, Vault Radar 등 워크플로우 개선을 위한 새로운 기능이 공개했습니다. 자세한 사항은 행사 현장을 직접 다녀온 이들이 전해주는 Hashicorp Korea: Hashiconf 2023 에서 확인하세요!
    • +
    +
  • +

1분 미만HashicorpUpdateNov
2023년 10월
+

Product 소개

+
    +
  • Creating a multi-cloud golden image pipeline with Terraform Cloud and HCP Packer +
      +
    • Hashicorp Blog
    • +
    • 조직 내 클라우드 사용환경에서 "표준화" 되지 않은 VM OS Image 로 인해 장애 발생 시 케이스를 표준화 하지 못하고 대응에 미진한 경우를 종종 접하곤 합니다. Hashicorp Packer 와 Terraform 을 연동하여 조직 내 사용중인 각 클라우드 환경 마다 Golden OS Image 를 구성하고 이를 활용하여 인스턴스 자원 배포하는 과정까지의 라이프사이클을 "표준화" 함으로써 보다 더 효율적이고 안정적인 시스템 환경을 구성해보세요.
    • +
    +
  • +

1분 미만HashicorpUpdateOct
+ + + diff --git a/assets/00-introduction.html-BslrSW0n.js b/assets/00-introduction.html-BslrSW0n.js new file mode 100644 index 0000000000..9e2f902025 --- /dev/null +++ b/assets/00-introduction.html-BslrSW0n.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as l,c as s,b as e,d as t,a as o,e as a}from"./app-Bzk8Nrll.js";const c={},p=e("h1",{id:"terraform-개념-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#terraform-개념-소개"},[e("span",null,"Terraform 개념 소개")])],-1),h=e("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/R6XxYKqB8EY",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),m=e("h2",{id:"_1-provision",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-provision"},[e("span",null,"1. Provision")])],-1),d=e("p",null,"프로비저닝과 관련하여 우리는 Day 0부터 Day 2까지의 여정이 있습니다.",-1),u=e("ul",null,[e("li",null,"Day 0에 요구사항을 분석하고 아키텍쳐를 설계하고 훈련을 합니다."),e("li",null,"Day 1에 드디어 설계된 아키텍쳐를 구현하지요. 인프라, 네트워크, 서비스 구성 등등 말이죠."),e("li",null,"Day 2는 이제 Day 1에서 구성된 요소를 유지하고 관리하고 모니터링하면서 더나은 아키텍쳐로 변경하거나 추가 서비스를 붙이는 반복적 작업을 합니다.")],-1),g=e("p",null,[t("우선은 프로비저닝할 준비가 되었다고 가정하고 Day 1에 "),e("s",null,"드디어"),t(" 인프라를 구성합니다. (VPC, Securty Group, VM, LB 등등) 그리고 이렇게 뭔가를 실행하면 실제 Day 2에서는 기존 인프라 집합에 추가로 비즈니스 요구사항에 따라 새로운 서비스를 추가하면서 그 시간이 지남에 따라 기존 인프라가 점점 변화합니다. 서비스를 제거하고 일반적으로 인프라의 모습을 발전시키는 이같은 활동은 테라폼에서 근본적으로 코드 접근 방식으로서의 인프라 즉 Infrastructure as Code 로 접근합니다.직역하면 코드가 인프라이고 인프라가 코드인 상태이죠.")],-1),f={href:"https://www.terraform.io/docs/configuration/index.html",target:"_blank",rel:"noopener noreferrer"},_=a('

복잡성에 따라 매주 또는 매일 인프라가 점차 변화하고 발전합니다. 테라폼 구성으로 이런 환경을 캡쳐해두는 것은 매우 쉽고 바른 방식입니다.

img
img

그럼 테라폼 설정이 어떻게 동작하는지 알아보겠습니다.

Refresh

우선 첫번째로, 테라폼은 Refresh를 통해 테라폼으로 만들어질 세상이 어떻게 생겼는지 조정합니다. 이를 통해 테라폼 View가 나오고 실제와 어떻게 다른지 비교합니다. VMware나 AWS, Azure, GCP 같은 인프라에 실제 무엇이 실행 중인지 API로 물어보고 각 상태를 확인할 수 있습니다.

Plan

플랜은 현재의 상태를 원하는 상태로 구성하는 단계 입니다. 실제 예상되는 무언가를 알려주고 우리는 미리 확인할 수 있습니다. 앞서 정의한 TF Config의 현재와 다른것이 무엇이있고 어떤 변화가 있는지 확인하고 앞으로의 변경점을 예측해주죠.

Apply

',8),y={href:"https://www.youtube.com/watch?v=V_TulH374hw",target:"_blank",rel:"noopener noreferrer"},w=a('

이렇게 구성하여 우리가 원하는 인프라를 정의하고 생성하고 적용합니다.

Day 2에 들어서서 기존에 없던 로드발란서와 연결되는 DNS구성이나 CDN, 또는 내 VM을 모니터링하고 싶은 시스템에 연결하는 작업이 필요할 수 있습니다.기존에 가지고 있는 구성을 업데이트하고 다시 실행하여 처음의 리소스에 추가로 새로운 것들을 추가합니다. Day 2에 중요한 것은 계속 변화하는 환경에서 변화될 것들만 추적하고 변경할 수 있다는 것입니다. 그리고 더이상 필요하지 않을 때 원래의 상태로 돌아올 수 있습니다.

Destory

코드로 정의된 각 인프라 리소스는 Destroy를 통해 다시 제로의 상태로 돌아옵니다. 이같은 방식은 언제나 일관된 상태와 리소스 정의를 만들어줍니다.

2. Providers

tf-how-it-works_core-extensible.png
tf-how-it-works_core-extensible.png

프로바이더는 테라폼 코어와 연동되는 플러그인으로, 각 플랫폼에서 제공하거나 누구나 개발해서 테라폼과 연결할 수 있습니다. 이런 플러그인들을 프로바이더라고 부릅니다.

프로바이더는 필요에 따라 인프라, 플랫폼, 서비스를 연계해서 사용하게도 가능합니다. 예를들어 앞서 인프라를 수행했다고 보고, Day2 에 쿠버네티스 연결이 필요하면 기존 설정을 확장해서 추가 리소스와 자원을 구성합니다. 그리고 그 위에 서비스가 DNS를 요구하거나 CDN을 요구하면 이런 서비스를 추가로 애드온 하게 됩니다.

',9),x={href:"https://www.terraform.io/docs/providers/index.html",target:"_blank",rel:"noopener noreferrer"},B=e("h2",{id:"_3-workflow",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_3-workflow"},[e("span",null,"3. Workflow")])],-1),b=e("figure",null,[e("img",{src:"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/M-XWeZGoGvexWM5BJwzU5WqAgsol63APP4dlm0iBh_XADq8xGJetiTCAgEbk0LXWDaU83cgGu0l2mh0rtBnsAySYA_j80j1W40Ug01iZSy2CtY7Xr6MV90OM2zQVOnlQU5p8iObm6-I.png",alt:"tf-features_consistent-workflow.png",tabindex:"0",loading:"lazy"}),e("figcaption",null,"tf-features_consistent-workflow.png")],-1),A=e("p",null,"테라폼을 로컬에서 사용하는 사용자의 워크 플로우는 테라폼 구성을 실행하고나서 plan이 만들어집니다. 구성에 대한 State 관리나 변수, 각각의 설정과 관련한 코드를 로컬에서 관리됩니다.",-1),D=e("p",null,"이제 다른 팀원을 추가합니다. 우리는 인프라 작업이 일관되게 변화하는 것을 기대합니다. 새로운 VM 이 생기거나 새로운 리소스를 생기지 않도록 하려면 어떻게 해야할까요? 이런 문제는 우리가 코드 관리를 위해 코드 버전 관리 서비스를 사용하는 현상과 유사합니다. Git기반의 테라폼은 코드를 중앙에서 관리하고 이를 테라폼 엔터프라이즈에서 관리하고 워크플로우의 헛점이 발생하지 않도록 도와줍니다.",-1),k={href:"https://www.terraform.io/docs/cloud/vcs/index.html",target:"_blank",rel:"noopener noreferrer"},G={href:"https://www.terraform.io/docs/cloud/sentinel/index.html",target:"_blank",rel:"noopener noreferrer"},v=e("p",null,"또한가지, 인프라를 구성하는 작업자는 혼자서 운영할 때는 필요한 변수들을 로컬에 저장하고 활용합니다. 이런 변수에는 키같은 민감한 정보토 포함될 수 있습니다. 엔터프라이즈에서는 변수를 중앙에서 관리하고 필요한 경우 암호화 해주는 기능이 필요합니다.",-1),P=e("figure",null,[e("img",{src:"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/81GxnfKd6RWAjGcWyAT_XA5FebyDXpcVcZXwjc215cOnYBeUcVcazui7JkcTqFUkpTcgYvRCSel9HKDFYGLW7FbvxVIWdWUo2ee6ykuCLxn6eUitcIxB9BrY6VJBySb_fl8YSXZov-I.png",alt:"Google Shape;5711;p420",tabindex:"0",loading:"lazy"}),e("figcaption",null,"Google Shape;5711;p420")],-1),S=e("p",null,"이런 엔터프라이즈 기능이 워크 플로우를 관리하고 작업자가 안전하게 협업할 수 있게 도와줍니다.이렇게 반복적으로 작업이 되다 보면 일련을 동작을 모듈화하여 관리할 수 있습니다. 일종의 입출력 예제와 같이 모듈을 쉽게 정의 할 수 있습니다. 예를 들어 Java애플리케이션을 배포하고자 하는 모듈에는 배포할 Jar파일이 무엇인지, 몇개나 띄울지 물어봅니다. 이런 모듈을 기업내에서 관리하는 프라이빗 레지스트리도 기업환경에서는 요구되곤 합니다.",-1),T=e("p",null,"테라폼만으로 좋은데 엔터프라이는 왜 사용하는가에 대한 대답은 이런 협업과 정책, 관리되는 static한 변수들과 해당 조직이나 기업을 위한 모듈을 관리할 수 있도록 만들어준다는 점입니다.",-1);function W(V,X){const r=i("ExternalLinkIcon");return l(),s("div",null,[p,h,m,d,u,g,e("p",null,[t("일련의 선언적으로 구성파일을 정의하고 읽을수 있는 이런 구성파일로 토폴로지를 구성합니다. "),e("a",f,[t("테라폼 config"),o(r)]),t("라고 설명해놓겠습니다. 보안 그룹 규칙을 프로비저닝하거나 네트워크 보안을 설정한 다음 해당 환경 내에서 가상 머신 세트 정의를 프로비저닝하려는 VM이 있고 로드밸런서가 있고...")]),_,e("p",null,[t("Apply는 원하는 상태를 만들기 위해 실행을 하는 단계 입니다. 필요한 것이 무엇인지 어떤것이 정의되었는지를 말이지요. 예를 들면 VM을 생성하기 전에 보안그룹을 정의하는 것 같은 순차적인 것이 무엇인지 병렬로 진행하는 것이 무엇인지를 이미 알고 있습니다. "),e("a",y,[t("그래프 이론"),o(r)]),t("에 기반한 이런 프로비저닝 방식은 사용자가 각 자원의 선후 관계를 명시하지 않아도 순차로 진행할 것과 병렬로 진행할 작업을 구분합니다.")]),w,e("p",null,[e("a",x,[t("테라폼 문서"),o(r)]),t("에보면 이미 100여개 이상의 프로바이더가 존재하고 커뮤니티의 프로바이더 까지 합치면 테라폼으로 관리가능한 코드기반 자원들은 무궁무진합니다.")]),B,b,A,D,e("p",null,[t("테라폼 엔터프라이즈는 "),e("a",k,[t("VCS와 연동"),o(r)]),t("하여 개인 로컬 환경이 아닌 중앙에서 프로비저닝을 하도록 관리하는 역할을 합니다. 이제 로컬에서 실행하는 대신 제어시스템, github나 빗버킷이나 깃랩같은 VCS를 활용하여 중앙에서 상태관리를 합니다.")]),e("p",null,[t("기업의 운영 환경에서 요구되는 건 또 무엇이 있을까요? 아마도 "),e("a",G,[t("정책"),o(r)]),t("이 필요할 것입니다. 예를 들면 태깅을 해야한다거나 특정 리전에만 프로비저닝을 해야하는 조건을 달 수 있습니다.")]),v,P,S,T])}const M=n(c,[["render",W],["__file","00-introduction.html.vue"]]),z=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/01-Information/00-introduction.html","title":"Terraform 개념 소개","lang":"ko-KR","frontmatter":{"description":"테라폼 소개","tag":["terraform","IaC"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/01-Information/00-introduction.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Terraform 개념 소개"}],["meta",{"property":"og:description","content":"테라폼 소개"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/PrGKxouOBWKZPAyp80ByMMnBlDDBCTJwBJpQA3APXwkoKhmjFUKWp-Ncc60TGNB6XNYEYhxBH6r3HFyEtNBeamu_DxAuRAtcG_3XEqyBH1g4pB6eufVZqwRJELzz8LEoR7xM8qU-BQs-20200701002631005.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Terraform 개념 소개"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"IaC"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Terraform 개념 소개\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/PrGKxouOBWKZPAyp80ByMMnBlDDBCTJwBJpQA3APXwkoKhmjFUKWp-Ncc60TGNB6XNYEYhxBH6r3HFyEtNBeamu_DxAuRAtcG_3XEqyBH1g4pB6eufVZqwRJELzz8LEoR7xM8qU-BQs-20200701002631005.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/YwvyuWTzXp2ZSKimOCvPaYP7GEU-AjWmCn1r3lr43BGW0zX_51LxzgU8DJkukvL5Ri5McV8FYBPgxn0jYGt0XJLNGDRTz0Af7TkUOD26xBTRxW1QZyFaAMqCKF24qS7zvkTwyIJ6d4s.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/M-XWeZGoGvexWM5BJwzU5WqAgsol63APP4dlm0iBh_XADq8xGJetiTCAgEbk0LXWDaU83cgGu0l2mh0rtBnsAySYA_j80j1W40Ug01iZSy2CtY7Xr6MV90OM2zQVOnlQU5p8iObm6-I.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/81GxnfKd6RWAjGcWyAT_XA5FebyDXpcVcZXwjc215cOnYBeUcVcazui7JkcTqFUkpTcgYvRCSel9HKDFYGLW7FbvxVIWdWUo2ee6ykuCLxn6eUitcIxB9BrY6VJBySb_fl8YSXZov-I.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1. Provision","slug":"_1-provision","link":"#_1-provision","children":[{"level":3,"title":"Refresh","slug":"refresh","link":"#refresh","children":[]},{"level":3,"title":"Plan","slug":"plan","link":"#plan","children":[]},{"level":3,"title":"Apply","slug":"apply","link":"#apply","children":[]},{"level":3,"title":"Destory","slug":"destory","link":"#destory","children":[]}]},{"level":2,"title":"2. Providers","slug":"_2-providers","link":"#_2-providers","children":[]},{"level":2,"title":"3. Workflow","slug":"_3-workflow","link":"#_3-workflow","children":[]}],"git":{"createdTime":1640262000000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.69,"words":207},"filePathRelative":"04-HashiCorp/03-Terraform/01-Information/00-introduction.md","localizedDate":"2021년 12월 23일","excerpt":"\\n\\n

1. Provision

\\n

프로비저닝과 관련하여 우리는 Day 0부터 Day 2까지의 여정이 있습니다.

\\n"}');export{M as comp,z as data}; diff --git a/assets/00-introduction.html-BtI6zsW3.js b/assets/00-introduction.html-BtI6zsW3.js new file mode 100644 index 0000000000..4d148abab1 --- /dev/null +++ b/assets/00-introduction.html-BtI6zsW3.js @@ -0,0 +1,26 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as l,b as e,d as n,a as s,e as t}from"./app-Bzk8Nrll.js";const r={},d=t(`

Pipeline on Jenkins 101 : Introduction

Update at 31 Jul, 2019

Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

CategoryNameVersion
VMVirtualBox6.0.10
OSRed Hat Enterprise Linux8.0.0
JDKRed Hat OpenJDK1.8.222
JenkinsJenkins rpm2.176.2

Jenkins 실행 및 구성

Jenkins를 실행 및 구성하기위한 OS와 JDK가 준비되었다는 가정 하에 진행합니다. 필요 JDK 버전 정보는 다음과 같습니다.

필요 JDK를 설치합니다.

$ subscription-manager repos --enable=rhel-8-for-x86_64-baseos-rpms --enable=rhel-8-for-x86_64-appstream-rpms
+
+### Java JDK 8 ###
+$ yum -y install java-1.8.0-openjdk-devel
+
+### Check JDK version ###
+$ java -version
+openjdk version "1.8.0_222"
+OpenJDK Runtime Environment (build 1.8.0_222-b10)
+OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
+

Red Hatsu/Fedora/CentOS 환경에서의 Jenkins 다운로드 및 실행은 다음의 과정을 수행합니다.

`,10),c={href:"https://pkg.jenkins.io/redhat-stable/",target:"_blank",rel:"noopener noreferrer"},m=t(`

패키지로 설치된 Jenkins의 설정파일은 /etc/sysconfig/jenkins에 있습니다. 해당 파일에서 실행시 활성화되는 포트 같은 설정을 변경할 수 있습니다.

## Type:        integer(0:65535)
+## Default:     8080
+## ServiceRestart: jenkins
+#
+# Port Jenkins is listening on.
+# Set to -1 to disable
+#
+JENKINS_PORT="8080"
+

외부 접속을 위해 Jenkins에서 사용할 포트를 방화벽에서 열어줍니다.

$ firewall-cmd --permanent --add-port=8080/tcp
+$ firewall-cmd --reload
+

서비스를 부팅시 실행하도록 활성화하고 Jenkins를 시작합니다.

$ systemctl enable jenkins 
+$ systemctl start jenkins
+

실행 후 브라우저로 접속하면 Jenkins가 준비중입니다. 준비가 끝나면 Unlock Jenkins 페이지가 나오고 /var/lib/jenkins/secrets/initialAdminPassword의 값을 입력하는 과정을 설명합니다. 해당 파일에 있는 토큰 복사하여 붙여넣습니다.

이후 과정은 Install suggested plugins를 클릭하여 기본 플러그인을 설치하여 진행합니다. 경우에 따라 Select plugins to install을 선택하여 플러그인을 지정하여 설치할 수 있습니다.

플러그인 설치 과정을 선택하여 진행하면 Getting Started 화면으로 전환되어 플러그인 설치가 진행됩니다.

설치 후 기본 Admin User 를 생성하고, 접속 Url을 확인 후 설치과정을 종료합니다.

GitHub 계정생성

진행되는 실습에서는 일부 GitHub를 SCM으로 연동합니다. 원활한 진행을 위해 GitHub계정을 생성해주세요. 또는 별개의 Git 서버를 구축하여 사용할 수도 있습니다.

Jenkins Theme (Optional)

Jenkins는 간단히 테마와 회사 CI를 적용할 수 있는 플러그인이 제공됩니다.

기본 Jenkins 테마를 변경하기 위해서는 다음의 과정을 수행합니다.

`,17),u={href:"http://afonsof.com/jenkins-material-theme/",target:"_blank",rel:"noopener noreferrer"},k=t("
  • Build your own theme with a company logo! 에서 색상과 로고를 업로드 합니다.

  • DOWNLOAD YOUR THEME!버튼을 클릭하면 CSS파일이 다운됩니다.

  • Jenkins 관리로 이동하여 시스템 설정를 클릭합니다.

  • Theme항목의 Theme elements의 드롭다운 항목에서 Extra CSS를 클릭하고 앞서 다운받은 CSS파일의 내용을 붙여넣고 설정을 저장하면 적용된 테마를 확인할 수 있습니다.

  • ",4);function h(b,g){const a=o("ExternalLinkIcon");return p(),l("div",null,[d,e("blockquote",null,[e("p",null,[n("참고 url : "),e("a",c,[n("https://pkg.jenkins.io/redhat-stable/"),s(a)])])]),m,e("ul",null,[e("li",null,[e("p",null,[e("a",u,[n("http://afonsof.com/jenkins-material-theme/"),s(a)]),n(" 에 접속합니다.")])]),k])])}const f=i(r,[["render",h],["__file","00-introduction.html.vue"]]),y=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/00-introduction.html","title":"Pipeline on Jenkins 101 : Introduction","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/00-introduction.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Pipeline on Jenkins 101 : Introduction"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Pipeline on Jenkins 101 : Introduction\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Jenkins 실행 및 구성","slug":"jenkins-실행-및-구성","link":"#jenkins-실행-및-구성","children":[]},{"level":2,"title":"GitHub 계정생성","slug":"github-계정생성","link":"#github-계정생성","children":[]},{"level":2,"title":"Jenkins Theme (Optional)","slug":"jenkins-theme-optional","link":"#jenkins-theme-optional","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.96,"words":287},"filePathRelative":"05-Software/Jenkins/pipeline101/00-introduction.md","localizedDate":"2021년 12월 24일","excerpt":"\\n
    \\n

    Update at 31 Jul, 2019

    \\n
    \\n

    Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

    \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
    CategoryNameVersion
    VMVirtualBox6.0.10
    OSRed Hat Enterprise Linux8.0.0
    JDKRed Hat OpenJDK1.8.222
    JenkinsJenkins rpm2.176.2
    "}');export{f as comp,y as data}; diff --git a/assets/00-overview.html-BPGT2Xsa.js b/assets/00-overview.html-BPGT2Xsa.js new file mode 100644 index 0000000000..21145f0a20 --- /dev/null +++ b/assets/00-overview.html-BPGT2Xsa.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as s,c as u,b as r,d as o,a as e,w as n}from"./app-Bzk8Nrll.js";const c={},p=r("h1",{id:"workshop-안내",tabindex:"-1"},[r("a",{class:"header-anchor",href:"#workshop-안내"},[r("span",null,"Workshop 안내")])],-1),d=r("hr",null,null,-1),h=r("h2",{id:"과정-안내",tabindex:"-1"},[r("a",{class:"header-anchor",href:"#과정-안내"},[r("span",null,"과정 안내")])],-1),_=r("li",null,[r("p",null,"이 과정은 IaC 도구인 Terraform을 사용하여 클라우드 리소스를 생성하는 실습(Hands-on)과정입니다.")],-1),m=r("li",null,[r("p",null,"💻 표시는 실제 실습을 수행하는 단계 입니다.")],-1),f=r("p",null,"사전 준비 사항",-1),g=r("li",null,"인터넷 연결이 가능한 사용자 별 랩탑 또는 데스크탑 환경이 필요합니다.",-1),b={href:"https://github.com/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://www.ncloud.com/",target:"_blank",rel:"noopener noreferrer"},T=r("li",null,[o("과정을 실행하기 위해서는 NCP 리소스 사용을 위한 "),r("u",null,[r("strong",null,"크래딧")]),o(" 또는 "),r("u",null,[r("strong",null,"결재수단")]),o(" 이 필요합니다. 과정 진행을 위해 강사가 크래딧을 제공할 수 있습니다.")],-1),C={href:"https://code.visualstudio.com/",target:"_blank",rel:"noopener noreferrer"},P=r("p",null,"컨텐츠",-1),v=r("li",null,[r("a",{href:"./01-terraform-intro"},"Terraform 소개")],-1),w=r("li",null,[r("a",{href:"./02-terraform-basic"},"Terraform 기본"),r("ul",null,[r("li",null,"💻 Lab - Setup and Basic Usage")])],-1),y=r("code",null,"plan",-1),N=r("code",null,"apply",-1),W=r("code",null,"destroy",-1),x=r("ul",null,[r("li",null,"💻 Lab - Terraform in Action")],-1),L=r("ul",null,[r("li",null,"💻 Lab - Terraform으로 프로비저닝 하기")],-1),S=r("ul",null,[r("li",null,"💻 Lab - Terraform Remote State")],-1),R=r("h2",{id:"워크샵을-진행시-참여자-소개-옵션",tabindex:"-1"},[r("a",{class:"header-anchor",href:"#워크샵을-진행시-참여자-소개-옵션"},[r("span",null,"워크샵을 진행시 참여자 소개 (옵션)")])],-1),B=r("p",null,"서로의 기술적 백그라운드를 이해하고 기술배경에 맞춰 워크샵이 진행됩니다.",-1),I=r("ul",null,[r("li",null,"이름"),r("li",null,"담당 업무와 기술적 백그라운드"),r("li",null,"자동화 경험 (Terraform, Ansible, Bash Script, Powershell 등)"),r("li",null,"주로 사용하는 편집기(editor)")],-1),A=r("h2",{id:"참고-링크",tabindex:"-1"},[r("a",{class:"header-anchor",href:"#참고-링크"},[r("span",null,"참고 링크")])],-1),E=r("li",null,[r("p",null,"Hashicorp Terraform 적용 단계 영상(자막)"),r("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/BlFKzTyjaTI",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""})],-1),V={href:"https://www.terraform.io/",target:"_blank",rel:"noopener noreferrer"},H={href:"https://registry.terraform.io/",target:"_blank",rel:"noopener noreferrer"},D={href:"https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest",target:"_blank",rel:"noopener noreferrer"};function K(Z,j){const t=a("ExternalLinkIcon"),l=a("RouteLink");return s(),u("div",null,[p,d,h,r("ul",null,[_,m,r("li",null,[f,r("ul",null,[g,r("li",null,[o("실습을 위한 샘플 코드활용을 위해 "),r("a",b,[o("github"),e(t)]),o("에 접속 가능해야 합니다.")]),r("li",null,[r("a",k,[o("Naver Cloud Platform(NCP)"),e(t)]),o("에 회원 가입이 필요합니다.")]),T,r("li",null,[o("실습을 수행하기 위한 랩탑 환경에 코드 편집기(IDE)로 Visual Studio Code 를 활용합니다. "),r("ul",null,[r("li",null,[o("홈페이지 및 다운로드 : "),r("a",C,[o("https://code.visualstudio.com/"),e(t)])])])])])]),r("li",null,[P,r("ol",null,[v,w,r("li",null,[e(l,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html"},{default:n(()=>[o("Terraform 실행")]),_:1}),o(" : "),y,o(),N,o(),W,x]),r("li",null,[e(l,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html"},{default:n(()=>[o("테라폼 프로비저닝 도구 사용 및 구성")]),_:1}),L]),r("li",null,[e(l,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html"},{default:n(()=>[o("테라폼 상태파일(State)")]),_:1})]),r("li",null,[e(l,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html"},{default:n(()=>[o("Terraform Cloud")]),_:1}),S])])])]),R,B,I,A,r("ul",null,[E,r("li",null,[r("p",null,[r("a",V,[o("Terraform Home Page"),e(t)])])]),r("li",null,[r("p",null,[r("a",H,[o("Terraform Registry"),e(t)])])]),r("li",null,[r("p",null,[r("a",D,[o("nCloud provider"),e(t)])])])])])}const U=i(c,[["render",K],["__file","00-overview.html.vue"]]),F=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.html","title":"Workshop 안내","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Workshop 안내"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Workshop 안내\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"과정 안내","slug":"과정-안내","link":"#과정-안내","children":[]},{"level":2,"title":"워크샵을 진행시 참여자 소개 (옵션)","slug":"워크샵을-진행시-참여자-소개-옵션","link":"#워크샵을-진행시-참여자-소개-옵션","children":[]},{"level":2,"title":"참고 링크","slug":"참고-링크","link":"#참고-링크","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.5,"words":149},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.md","localizedDate":"2023년 9월 18일","excerpt":"\\n\\n
    \\n

    과정 안내

    \\n"}');export{U as comp,F as data}; diff --git a/assets/01-About.html-7Tx-G8Iy.js b/assets/01-About.html-7Tx-G8Iy.js new file mode 100644 index 0000000000..5d23553208 --- /dev/null +++ b/assets/01-About.html-7Tx-G8Iy.js @@ -0,0 +1 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as s,c as i,b as e,d as t,a as n,e as r}from"./app-Bzk8Nrll.js";const c="/logo.png",p={},d=r('

    docmoa

    logo
    logo

    기술은 지속적으로 발전하고 시시각각 변화하고 있습니다. 더불어 IT라는 분야도 점점더 세분화되고, 혼자서는 모든것을 아는것은 거의 불가능합니다.
    IT 업을 하면서 정리와 스크랩은 일상이 되어가지만 변화를 쫓아가기는 정말 버겁습니다.

    하지만 혼자서가 아니라면 어떨까 라는 생각을 합니다.
    집단지성 이란 표현이 있듯, 개인보다는 여럿이 만들어가는 노트입니다. 과거에는 이런 노하우가 개인의 자산으로 비밀처럼 감춰두던 지식이였지만 지금은 서로 공유하고, 알리고, 기여하는 것도 의미 있는 시기인것 같습니다.

    코딩의 시대에 걸맞게 코드를 관리하듯 노트를 관리하고 기여하면서 커나가는 오픈소스 아닌 오픈노트, 오픈노하우 입니다.

    ',5),h={href:"https://github.com/docmoa/docs",target:"_blank",rel:"noopener noreferrer"},g=e("h2",{id:"문서의-성격",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#문서의-성격"},[e("span",null,"문서의 성격")])],-1),u=e("p",null,"문서는 본인을 위해, 혹은 동료, 또는 누군가를 위해 필요로 할 때 바로 확인 할 수 있는 기술 문서여야 합니다.",-1),m=e("ul",null,[e("li",null,"사실과 결과에 기반한 문서를 지향합니다."),e("li",null,"즉시적으로 도움이 될 수 있는 정보를 최우선으로 합니다."),e("li",null,"뉴스, 트렌드, 마케팅 관련 내용은 담지 않습니다.")],-1),_=e("h2",{id:"라이선스",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#라이선스"},[e("span",null,"라이선스")])],-1),b={href:"https://creativecommons.org/licenses/by-nc-nd/4.0/deed.ko",target:"_blank",rel:"noopener noreferrer"},f=r('

    docmoa 에 개시되는 모든 기여되는 문서의 저작권, 권리, 책임은 문서를 기여한 저작자 에게 있습니다.

    이용자의 권리

    포맷 변경을 포함하여 모든곳에 공유 가능합니다.

    제약사항

    면책조항

    본 사이트의 포스팅에 담긴 관점과 의견은 작성자의 개인적인 생각을 바탕으로 작성되었으며 어떠한 내용도 특정 회사나 단체의 공적인 입장을 대변하지 않습니다.

    VuePress

    ',9),k=e("code",null,"github page",-1),v={href:"https://v1.vuepress.vuejs.org/",target:"_blank",rel:"noopener noreferrer"},x=e("ul",null,[e("li",null,"WordPress"),e("li",null,"jekyll"),e("li",null,"mkdocs"),e("li",null,"제로보드"),e("li",null,"Wiki")],-1),y=e("p",null,"페이지 구성을 위해 참고한 자료는 다음과 같습니다.",-1),T={href:"https://v1.vuepress.vuejs.org/",target:"_blank",rel:"noopener noreferrer"},I={href:"https://vuepress-examples.netlify.app/",target:"_blank",rel:"noopener noreferrer"},V={href:"https://vuepressbook.com/",target:"_blank",rel:"noopener noreferrer"},P={href:"https://junilhwang.github.io/",target:"_blank",rel:"noopener noreferrer"},A={href:"https://kyounghwan01.github.io/blog",target:"_blank",rel:"noopener noreferrer"},N={href:"https://62che.com/blog",target:"_blank",rel:"noopener noreferrer"};function j(w,z){const o=l("ExternalLinkIcon");return s(),i("div",null,[d,e("p",null,[t("다수의 기여자에 의해 문서가 생성되므로 글 작성 방식과 문맥이 서로 상이할 수 있습니다. 각 문서의 기여자는 "),e("a",h,[t("github repo"),n(o)]),t(" 에서 확인해주세요.")]),g,u,m,_,e("p",null,[t("적용되어있는 라이선스는 "),e("a",b,[t("CC BY-NC-ND 4.0"),n(o)]),t(" 입니다.")]),f,e("p",null,[t("해당 페이지는 "),k,t("에서 "),e("a",v,[t("VuePress"),n(o)]),t("를 기반으로 구성되었습니다. 이외에 검토되었던 항목은 다음과 같습니다.")]),x,y,e("ul",null,[e("li",null,[e("a",T,[t("VuePress.org"),n(o)])]),e("li",null,[e("a",I,[t("VuePress Examples 3.0.1"),n(o)])]),e("li",null,[e("a",V,[t("VuePress tutorial"),n(o)])]),e("li",null,[e("a",P,[t("개발자 황준일"),n(o)])]),e("li",null,[e("a",A,[t("기억보다 기록을"),n(o)])]),e("li",null,[e("a",N,[t("유기체의 다락방"),n(o)])])])])}const E=a(p,[["render",j],["__file","01-About.html.vue"]]),D=JSON.parse('{"path":"/99-about/01-About.html","title":"docmoa","lang":"ko-KR","frontmatter":{"description":"docmoa logologo 기술은 지속적으로 발전하고 시시각각 변화하고 있습니다. 더불어 IT라는 분야도 점점더 세분화되고, 혼자서는 모든것을 아는것은 거의 불가능합니다. IT 업을 하면서 정리와 스크랩은 일상이 되어가지만 변화를 쫓아가기는 정말 버겁습니다. 하지만 혼자서가 아니라면 어떨까 라는 생각을 합니다. 집...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/99-about/01-About.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"docmoa"}],["meta",{"property":"og:description","content":"docmoa logologo 기술은 지속적으로 발전하고 시시각각 변화하고 있습니다. 더불어 IT라는 분야도 점점더 세분화되고, 혼자서는 모든것을 아는것은 거의 불가능합니다. IT 업을 하면서 정리와 스크랩은 일상이 되어가지만 변화를 쫓아가기는 정말 버겁습니다. 하지만 혼자서가 아니라면 어떨까 라는 생각을 합니다. 집..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://docmoa.github.io/logo.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"docmoa"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"docmoa\\",\\"image\\":[\\"https://docmoa.github.io/logo.png\\",\\"https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/by-nc-nd.svg\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"문서의 성격","slug":"문서의-성격","link":"#문서의-성격","children":[]},{"level":2,"title":"라이선스","slug":"라이선스","link":"#라이선스","children":[{"level":3,"title":"이용자의 권리","slug":"이용자의-권리","link":"#이용자의-권리","children":[]},{"level":3,"title":"제약사항","slug":"제약사항","link":"#제약사항","children":[]},{"level":3,"title":"면책조항","slug":"면책조항","link":"#면책조항","children":[]}]},{"level":2,"title":"VuePress","slug":"vuepress","link":"#vuepress","children":[]}],"git":{"createdTime":1628085698000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":3},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":0.26,"words":78},"filePathRelative":"99-about/01-About.md","localizedDate":"2021년 8월 4일","excerpt":"\\n
    \\"logo\\"
    logo
    \\n

    기술은 지속적으로 발전하고 시시각각 변화하고 있습니다. 더불어 IT라는 분야도 점점더 세분화되고, 혼자서는 모든것을 아는것은 거의 불가능합니다.
    \\nIT 업을 하면서 정리와 스크랩은 일상이 되어가지만 변화를 쫓아가기는 정말 버겁습니다.

    \\n

    하지만 혼자서가 아니라면 어떨까 라는 생각을 합니다.
    \\n집단지성 이란 표현이 있듯, 개인보다는 여럿이 만들어가는 노트입니다. 과거에는 이런 노하우가 개인의 자산으로 비밀처럼 감춰두던 지식이였지만 지금은 서로 공유하고, 알리고, 기여하는 것도 의미 있는 시기인것 같습니다.

    ","autoDesc":true}');export{E as comp,D as data}; diff --git a/assets/01-Install.html-8K2VTmo9.js b/assets/01-Install.html-8K2VTmo9.js new file mode 100644 index 0000000000..fb72f02b02 --- /dev/null +++ b/assets/01-Install.html-8K2VTmo9.js @@ -0,0 +1,47 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as i,b as n,d as e,a,e as t}from"./app-Bzk8Nrll.js";const p={},r=n("h1",{id:"_01-install",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_01-install"},[n("span",null,"01. Install")])],-1),u={class:"hint-container tip"},d=n("p",{class:"hint-container-title"},"팁",-1),h=n("p",null,"실습을 위한 조건은 다음과 같습니다.",-1),m=n("li",null,"Kubernetes 1.21 이상의 환경",-1),k={href:"http://releases.hashicorp.com/consul/",target:"_blank",rel:"noopener noreferrer"},b=n("li",null,"Install helm 3",-1),v=n("li",null,"Install Kubectl",-1),g={href:"http://consul.io/trial",target:"_blank",rel:"noopener noreferrer"},y={href:"https://learn.hashicorp.com/collections/consul/kubernetes-production",target:"_blank",rel:"noopener noreferrer"},_=t(`

    Consul Gossip

    다운받은 Consul 바이너리를 통해 Gossip 암호화 키를 생성합니다

    kubectl create secret generic consul-gossip-encryption-key --from-literal=key=$(consul keygen)
    +

    License (Option)

    발급받은 라이선스 파일을 저장(e.g. consul.hclic)하고 Kubernetes의 secret으로 적용합니다.

    kubectl create secret generic license --from-file='key=./consul.hclic'
    +

    Helm

    Helm repo add & update

    helm repo add hashicorp https://helm.releases.hashicorp.com && \\
    +helm repo update
    +

    Helm Chart

    `,10),f={href:"https://github.com/hashicorp/consul-helm/blob/master/values.yaml",target:"_blank",rel:"noopener noreferrer"},C=n("p",null,"Helm 설치를 위한 파일(e.g. value.yaml) 을 작성합니다.",-1),x={href:"https://hub.docker.com/r/hashicorp/consul-enterprise/tags",target:"_blank",rel:"noopener noreferrer"},K=n("li",null,[e("적용 시 "),n("code",null,"enterpriseLicense"),e(" 항목의 주석을 해제합니다.")],-1),I=t(`
    global:
    +  enabled: true
    +  name: consul
    +  image: hashicorp/consul-enterprise:1.11.3-ent
    +  enableConsulNamespaces: true
    +  adminPartitions:
    +    enabled: false
    +  datacenter: dc1
    +  # enterpriseLicense:
    +  #   secretName: license
    +  #   secretKey: key
    +  gossipEncryption:
    +    secretName: consul-gossip-encryption-key
    +    secretKey: key
    +  tls:
    +    enabled: false
    +    enableAutoEncrypt: true
    +  enableConsulNamespaces: true
    +
    +client:
    +  enabled: true
    +  grpc: true
    +
    +connectInject:
    +  enabled: true
    +  replicas: 2
    +
    +dns:
    +  enabled: true
    +
    +controller:
    +  enabled: true
    +
    +syncCatalog:
    +  enabled: true
    +  toConsul: false
    +  consulNamespaces:
    +    mirroringK8S: true
    +
    +

    설치

    kubectl config use-context $(grep gs-cluster-0 KCONFIG.txt)
    +helm install consul -f ./values.yaml hashicorp/consul --version v0.40.0 --debug
    +

    UI 확인

    kubectl port-forward service/consul-server 8500:8500
    +
    `,5),N={href:"http://localhost:8500/ui",target:"_blank",rel:"noopener noreferrer"};function E(S,H){const s=o("ExternalLinkIcon");return c(),i("div",null,[r,n("div",u,[d,h,n("ul",null,[m,n("li",null,[e("Consul binary "),n("a",k,[e("http://releases.hashicorp.com/consul/"),a(s)])]),b,v,n("li",null,[e("Consul Namespace 테스트는 Enterprise 라이선스가 필요합니다. : "),n("a",g,[e("http://consul.io/trial"),a(s)])])])]),n("blockquote",null,[n("p",null,[e("참고 : "),n("a",y,[e("https://learn.hashicorp.com/collections/consul/kubernetes-production"),a(s)])])]),_,n("blockquote",null,[n("p",null,[e("github : "),n("a",f,[e("https://github.com/hashicorp/consul-helm/blob/master/values.yaml"),a(s)])])]),C,n("ul",null,[n("li",null,[e("Enterprise "),n("ul",null,[n("li",null,[e("Enterprse 이미지 태그는 "),n("a",x,[e("hub.docker.com"),a(s)]),e("을 확인합니다.")]),K])])]),I,n("p",null,[n("a",N,[e("http://localhost:8500/ui"),a(s)])])])}const L=l(p,[["render",E],["__file","01-Install.html.vue"]]),G=JSON.parse('{"path":"/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.html","title":"01. Install","lang":"ko-KR","frontmatter":{"description":"Consul Service Mesh on Kubernetes (Ent)","tag":["Consul","ServiceMesh","K8s","Kubernetes"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"01. Install"}],["meta",{"property":"og:description","content":"Consul Service Mesh on Kubernetes (Ent)"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"ServiceMesh"}],["meta",{"property":"article:tag","content":"K8s"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"01. Install\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Consul Gossip","slug":"consul-gossip","link":"#consul-gossip","children":[]},{"level":2,"title":"License (Option)","slug":"license-option","link":"#license-option","children":[]},{"level":2,"title":"Helm","slug":"helm","link":"#helm","children":[]},{"level":2,"title":"Helm Chart","slug":"helm-chart","link":"#helm-chart","children":[]},{"level":2,"title":"설치","slug":"설치","link":"#설치","children":[]},{"level":2,"title":"UI 확인","slug":"ui-확인","link":"#ui-확인","children":[]}],"git":{"createdTime":1645936869000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.66,"words":198},"filePathRelative":"04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.md","localizedDate":"2022년 2월 27일","excerpt":"\\n
    \\n

    \\n

    실습을 위한 조건은 다음과 같습니다.

    \\n\\n
    "}');export{L as comp,G as data}; diff --git a/assets/01-Introduction.html-DUosUNA9.js b/assets/01-Introduction.html-DUosUNA9.js new file mode 100644 index 0000000000..d994f78bfb --- /dev/null +++ b/assets/01-Introduction.html-DUosUNA9.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as i,c as s,b as t,d as e,a,e as d}from"./app-Bzk8Nrll.js";const o={},g=t("h1",{id:"_1-tomcat-소개",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_1-tomcat-소개"},[t("span",null,"1. Tomcat 소개")])],-1),f=t("p",null,"본 내용은 톰캣을 좀더 잘 알고 잘 써보기 위한 제안이랄까요?",-1),c={href:"http://www.youtube.com/playlist?list=PLQUXE_kb6KOj0mvxoAGrz3FT9EDL3fa1z",target:"_blank",rel:"noopener noreferrer"},y=t("s",null,"얼마나 출퇴근 시간에 이용하셨을지는 미지수이고",-1),p=t("p",null,"톰캣을 잘 사용하고 이해하는 것은 개발자, 운영자, 기술자, 엔지니어등 톰캣을 접하는 모두에게 요구되는 사항이지만 일반적으로는 필요한 기능과 기술만을 습득하게 됩니다. 이번 강의를 통해 약간의 시간 투자로 모두에게 도움이 되었으면 합니다.",-1),x=t("p",null,[e("특히 WEB/WAS 엔지니어로 자주 겪었던 "),t("strong",null,"개발은 톰캣으로하고 정작 운영은 다른 WAS를 사용하는"),e(" 아이러니함에도 도움이 되었으면 합니다.")],-1),m=t("hr",null,null,-1),h=t("ul",null,[t("li",null,"왜 톰캣을 쓰는가?"),t("li",null,"톰캣 이력"),t("li",null,"톰캣 구성"),t("li",null,"TomEE?")],-1),u=t("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/P3H-7G_Y3rI",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),J=d('

    1.1 왜 톰캣을 쓰는가?

    톰캣을 사용하는 첫번째 이유는 기능적인 이유일 것입니다. 즉, 톰캣이 수행하는 역할인 JSP/Servlet 엔진으로서의 역할이겠지요. 톰캣은 세계적으로 가장 많은 Java 기반의 웹어플리케이션 플랫폼으로 사용되고 있습니다. 많은 개발자들이 자신의 첫번째 Java 웹 어플리케이션의 JSP/Servlet 엔진으로 선택하고 있고 실제 운영환경에서도 사용하기에 상당한 양의 노하우를 쉽게 접할 수 있다는 장점이 있습니다.

    두번째로는 아마도 무료로 사용할 수 있다는 점이 톰캣을 사용하게 되는 이유일 것입니다. 수많은 벤더사에서 JSP/Servlet 엔진과 추가로 Java Enterprise 기능을 사용할 수 있는 세련되고 검증된 자신만의 제품을 개발하고 상용화 하고 있습니다. 하지만 이러한 제품은 비용이 추가된다는 (큰)고민이 생깁니다. 물론 유지보수 계약을 통해 기술지원과 더불어 든든한 책임전가의 대상(?)인 벤더사가 존재한다는 장점이 있지만 모든 사용자가 이런 비용을 지불할 수 있는것은 아니겠지요.

    2020년 JRebel 자료
    Most Popular Java Application Server

    2014년 자료
    rebellabs:App Server Most of Used
    rebellabs:App Server Most of Used
    ',6),_={href:"https://www.jrebel.com/blog/2020-java-technology-report",target:"_blank",rel:"noopener noreferrer"},S=d('

    JRebel에서 언급한것과 같이 Spring Boot, Docker, Hybris 및 AWS와 같은 다른 주요 Java 플랫폼과의 호환성이 뒷받침이 되는것 같습니다.


    1.2 톰캣 이력

    톰캣의 정식 명칭은 Apache Tomcat Server 입니다. 이를 줄여 톰캣 Tomcat 이라고 흔히 불려지고 있지요. 톰캣의 간단한 이력은 다음과 같습니다.

    기준2021년 12월 23일
    DeveoloperApache Software Foundation
    Last Stable release10.0.14
    Development StatusActive
    Written inJava
    Operating SystemCross-Platform
    TypeServlet Container / HTTP Web Server
    LicenseApache License 2.0
    Websitetomcat.apache.org

    현재까지 출시된 버전은 10.0.14 버전이고 앞으로도 지속적으로 업데이트가 있을 예정입니다. 무료로 제공되는 이유로 인해 톰캣을 기반으로 한 소프트웨어들이 있는데, 이경우 개발된 시점을 기준으로 톰캣 버전이 고정되어 있는 경우가 있습니다. 4.0 버전은 최근 보이지 않지만 JDK 1.4.2 를 사용하던 시기가 가장 많은 Java 웹 어플리케이션이 개발되던 시기이기에 톰캣 5.5 버전은 아직도 상당히 많은 사용처가 있을것이라 보여집니다.


    1.3 톰캣 구성 요소

    톰캣을 구성하는 핵심적인 요소는 다음의 세가지 컴포넌트입니다.

    Tomcat_Component
    Tomcat_Component

    Catalina는 아마도 톰캣을 사용하면 가장 많이 보게되는 단어 중 하나일 것입니다.

    처리되는 컴포넌트의 역할을 이해한다면 톰캣에서 어플리케이션 수행시 발생되는 코드 스텍을 이해하는데 도움이 될 수 있습니다.


    1.4 TomEE?

    톰캣에 대하여 흔히들 '톰캣은 완전한 WAS(Web Application Server)가 아니다'라고 합니다. 앞서 설명하였지만 톰캣은 JSP/Servlet 엔진의 역할을 수행합니다. 하지만 Java Enterprise 기능인 EJB, JTA, JMS, WebService 등은 포함되어 있지 않죠. 이러한 이유로 WAS의 일부 기능만을 수행 할 수 있을 뿐 WAS는 아니다 라고 합니다. 이러한 Enterprise 요소를 지원하기위한 요구사항으로 OpenEJB나 Apache ActiveMQ, Apache CXF등의 컴포넌트 요소가 톰캣과는 별도의 프로젝트로 진행되어 해당 컴포넌트들을 결합함으로 톰캣에서 이를 지원할 수 있었습니다. 이제는 어느정도 시간이 지나 Enterprise 컴포넌트와의 연계성이 뚜렷해졌고 통합이 가능해짐에 따라 톰캣을 Java Enterprise 스펙에 맞게 재 조정하는 프로젝트가 시작됩니다. 이를 TomEE(Tomcat Enterprise Edition)이라 합니다.

    ',16),b={href:"http://tomee.apache.org",target:"_blank",rel:"noopener noreferrer"},v={href:"https://tomee.apache.org/comparison.html",target:"_blank",rel:"noopener noreferrer"},E=d('
    TomcatTomEE WebProfileTomEE MicroProfileTomEE PlumeTomEE Plus
    Jakarta Annotations✔︎✔︎✔︎✔︎✔︎
    Jakarta Debugging Support for Other Languages✔︎✔︎✔︎✔︎✔︎
    Jakarta Security (Java EE Enterprise Security)✔︎✔︎✔︎✔︎✔︎
    Jakarta Server Pages (JSP)✔︎✔︎✔︎✔︎✔︎
    Jakarta Servlet✔︎✔︎✔︎✔︎✔︎
    Jakarta Standard Tag Library (JSTL)✔︎✔︎✔︎✔︎✔︎
    Jakarta WebSocket✔︎✔︎✔︎✔︎✔︎
    Jakarta Expression Language (EL)✔︎✔︎✔︎✔︎✔︎
    Jakarta Activation✔︎✔︎✔︎✔︎
    Jakarta Bean Validation✔︎✔︎✔︎✔︎
    Jakarta Contexts and Dependency Injection (CDI)✔︎✔︎✔︎✔︎
    Jakarta Dependency Injection (@Inject)✔︎✔︎✔︎✔︎
    Jakarta Enterprise Beans (EJB)✔︎✔︎✔︎✔︎
    Jakarta Interceptors✔︎✔︎✔︎✔︎
    Jakarta JSON Binding (JSON-B)✔︎✔︎✔︎✔︎
    Jakarta JSON Processing (JSON-P)✔︎✔︎✔︎✔︎
    Jakarta Mail (JavaMail)✔︎✔︎✔︎✔︎
    Jakarta Managed Beans✔︎✔︎✔︎✔︎
    Jakarta Persistence (JPA)✔︎✔︎✔︎✔︎
    Jakarta RESTful Web Services (JAX-RS)✔︎✔︎✔︎✔︎
    Jakarta Server Faces (JSF)✔︎✔︎✔︎✔︎
    Jakarta Transactions (JTA)✔︎✔︎✔︎✔︎
    Jakarta XML Binding (JAXB)✔︎✔︎✔︎✔︎
    Jakarta Authentication (JAAS)✔︎✔︎
    Jakarta Authorization (JACC)✔︎✔︎
    Jakarta Concurrency✔︎✔︎
    Jakarta Connectors✔︎✔︎
    Jakarta Enterprise Web Services✔︎✔︎
    Jakarta Messaging (JMS)✔︎✔︎
    Jakarta SOAP with Attachments✔︎✔︎
    Jakarta Web Services Metadata✔︎✔︎
    Jakarta XML Web Services (JAX-WS)✔︎✔︎
    Jakarta Batch (JBatch)✔︎

    현재 국내의 개발 환경이나 운영 환경에서는 이전에 잠시 EJB가 사용되었을 뿐 최근 전자정부 프레임워크에서도 보이듯 Enterprise 환경을 요구하는 상황은 거의 없는 것으로 보입니다. 아무래도 WAS 의존성을 낮추기 위해서는 벤더에서 주도하여 각각의 컴포넌트를 제공하는 Enterprise 구성 요소에 대한 의존도를 낮출 수 있는 방향이기는 하겠지요. 하지만 일부의 경우는 Enterprise 구성 요소를 잘 사용하기만 하면 어렵지 않게 안정적이고 보안적으로 보장되는 서비스의 구현이 가능할 것입니다.

    부가적인 설명을 드리자면 JDK 버전과 Java Enterprise 버전은 서로 범위가 다릅니다. Java Standard기능은 JDK 버전과 같지만 Java Enterprise 기능은 JDK 버전과는 별개로 WAS에서 지원하는 컴포넌트의 요소에 따라 그 버전을 달리 합니다. 앞서 대표적인 국내 프레임워크로 Spring 프레임워크를 사용하는 전자정부 프레임워크를 예로 들었지만 국내와는 달리 국외 통계를 본다면 Java Enterprise의 사용은 그리 생소하지 않습니다.

    따라서 Java Enterprise 환경을 고려한다면 톰캣을 기반으로 한 TomEE를 활용하는 방법도 톰캣에 익숙한 개발자들에게는 도움이 될 수 있겠습니다.

    ',5);function k(T,A){const l=r("ExternalLinkIcon");return i(),s("div",null,[g,f,t("p",null,[e("톰캣의 특성상 쉽게 접할 수 있는 메뉴얼적인 지식보다는, 톰캣을 더 잘 사용하고 운영 할 수 있을만한 아이디어를 공유하고자 시작한 지식공유 활동입니다. 담고 있는 내용은 "),t("strong",null,[e("'"),t("a",c,[e("톰캣 알고 쓰기"),a(l)]),e("'")]),e(" 유튜브 강의 내용에 대한 정리입니다. 유튜브에 강의를 올리면 출퇴근 시간을 이용해 짬짬히 들을 수 있을 것 같은 생각이 들어 시작하였지만 "),y,e(" 동영상으로 모든 것을 다 표현할 수 없다는 점을 감안하여 다시 글로 정리합니다.")]),p,x,m,h,u,J,t("p",null,[e("Java 웹 어플리케이션을 실행하는 Application Server의 종류는 거의 30가지에 달하나 조사된 "),t("a",_,[e("JRebel의 통계"),a(l)]),e("에 따르면 여전히 과반 이상을 Tomcat이 점유하고 있다고 합니다. 여러가지 이유가 더 있겠지만 앞서 언급한 많은 레퍼런스와 무료라는 큰 특징으로 인해 수많은 사용자가 톰캣을 사용하고 있습니다.")]),S,t("p",null,[e("TomEE의 대표 홈페이지는 "),t("a",b,[e("tomee.apache.org"),a(l)]),e("이며 상세한 설명과 문서가 준비되어있기 때문에 사용하는데 큰 어려움이 없습니다. TomEE에서 지원하는 Java Enterprise 컴포넌트들은 다음과 같습니다.")]),t("p",null,[t("a",v,[e("https://tomee.apache.org/comparison.html"),a(l)])]),E])}const W=n(o,[["render",k],["__file","01-Introduction.html.vue"]]),L=JSON.parse(`{"path":"/05-Software/Tomcat/tomcat101/01-Introduction.html","title":"1. Tomcat 소개","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/01-Introduction.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"1. Tomcat 소개"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://marvel-b1-cdn.bc0a.com/f00000000156946/www.jrebel.com/sites/default/files/image/2020-01/7.%20what%20application%20server%20do%20you%20use%20on%20main%20application.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"1. Tomcat 소개"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"1. Tomcat 소개\\",\\"image\\":[\\"https://marvel-b1-cdn.bc0a.com/f00000000156946/www.jrebel.com/sites/default/files/image/2020-01/7.%20what%20application%20server%20do%20you%20use%20on%20main%20application.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/app-server-used-most-often-graph.jpg?token=ADUAZXKGEU5LVPPWSH3R4YK67EUKK\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/tomcat_component_do.jpg?token=ADUAZXMO7ZXOL64TU2WHS7267EULI\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1.1 왜 톰캣을 쓰는가?","slug":"_1-1-왜-톰캣을-쓰는가","link":"#_1-1-왜-톰캣을-쓰는가","children":[]},{"level":2,"title":"1.2 톰캣 이력","slug":"_1-2-톰캣-이력","link":"#_1-2-톰캣-이력","children":[{"level":3,"title":"1.3 톰캣 구성 요소","slug":"_1-3-톰캣-구성-요소","link":"#_1-3-톰캣-구성-요소","children":[]}]},{"level":2,"title":"1.4 TomEE?","slug":"_1-4-tomee","link":"#_1-4-tomee","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.32,"words":396},"filePathRelative":"05-Software/Tomcat/tomcat101/01-Introduction.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    본 내용은 톰캣을 좀더 잘 알고 잘 써보기 위한 제안이랄까요?

    \\n

    톰캣의 특성상 쉽게 접할 수 있는 메뉴얼적인 지식보다는, 톰캣을 더 잘 사용하고 운영 할 수 있을만한 아이디어를 공유하고자 시작한 지식공유 활동입니다. 담고 있는 내용은 '톰캣 알고 쓰기' 유튜브 강의 내용에 대한 정리입니다. 유튜브에 강의를 올리면 출퇴근 시간을 이용해 짬짬히 들을 수 있을 것 같은 생각이 들어 시작하였지만 얼마나 출퇴근 시간에 이용하셨을지는 미지수이고 동영상으로 모든 것을 다 표현할 수 없다는 점을 감안하여 다시 글로 정리합니다.

    "}`);export{W as comp,L as data}; diff --git a/assets/01-Overview.html-BtGYf7na.js b/assets/01-Overview.html-BtGYf7na.js new file mode 100644 index 0000000000..55abe8647e --- /dev/null +++ b/assets/01-Overview.html-BtGYf7na.js @@ -0,0 +1,22 @@ +import{_ as h}from"./vuepress-DVMSUmm5.js";import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as u,c as g,b as t,d as e,a as o,w as a,e as c}from"./app-Bzk8Nrll.js";const m="/assets/typora-BKtnqQMk.png",b="/assets/vscode-SimqJHZ6.png",_={},v=t("h1",{id:"docmoa-활용-가이드",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#docmoa-활용-가이드"},[t("span",null,"docmoa 활용 가이드")])],-1),f=t("p",null,[e("문서가 인터넷상에 공개되는 목적은 접근성을 극대화 하기 위함 입니다. 또한 로컬환경에서 빠르게 문서를 검색하기 위해 해당 git repo를 clone 받거나 download 받아서 별도의 마크다운 툴과 연동하는 것도 가능합니다. "),t("code",null,"VuePress"),e(" 기반으로 구성되었기 때문에 이외의 방식은 문서 표기에 제약이 있을 수 있습니다.")],-1),k=t("h2",{id:"github-page",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#github-page"},[t("span",null,"github page")])],-1),y={href:"https://docmoa.github.io/",target:"_blank",rel:"noopener noreferrer"},x=t("h2",{id:"vuepress-local-실행",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#vuepress-local-실행"},[t("span",null,"VuePress (Local 실행)")])],-1),w=t("p",null,"공개된 페이지는 내 로컬환경에서도 실행할 수 있습니다. Nodejs가 필요합니다.",-1),V=t("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[t("pre",{bash:"",class:"language-bash"},[t("code",null,[t("span",{class:"token comment"},"# git clone"),e(` +`),t("span",{class:"token function"},"git"),e(` clone https://github.com/docmoa/docs.git + +`),t("span",{class:"token comment"},"# npm install"),e(` +`),t("span",{class:"token builtin class-name"},"cd"),e(` docs +`),t("span",{class:"token function"},"npm"),e(),t("span",{class:"token function"},"install"),e(` + +`),t("span",{class:"token comment"},"# start VuePress writing"),e(` +`),t("span",{class:"token function"},"npm"),e(` run dev +`)])]),t("div",{class:"highlight-lines"},[t("br"),t("div",{class:"highlight-line"}," "),t("br"),t("br"),t("div",{class:"highlight-line"}," "),t("div",{class:"highlight-line"}," "),t("br"),t("br"),t("div",{class:"highlight-line"}," ")])],-1),S=t("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[t("pre",{bash:"",class:"language-bash"},[t("code",null,[t("span",{class:"token comment"},"# git clone"),e(` +`),t("span",{class:"token function"},"git"),e(` clone https://github.com/docmoa/docs.git + +`),t("span",{class:"token comment"},"# npm install"),e(` +`),t("span",{class:"token builtin class-name"},"cd"),e(` docs +`),t("span",{class:"token function"},"yarn"),e(),t("span",{class:"token function"},"install"),e(` + +`),t("span",{class:"token comment"},"# start VuePress writing"),e(` +`),t("span",{class:"token function"},"yarn"),e(` vuepress dev +`)])]),t("div",{class:"highlight-lines"},[t("br"),t("div",{class:"highlight-line"}," "),t("br"),t("br"),t("div",{class:"highlight-line"}," "),t("div",{class:"highlight-line"}," "),t("br"),t("br"),t("div",{class:"highlight-line"}," ")])],-1),C=c(`

    실행이 완료되면 로그에 다음과 같은 메시지와 접속할 수 있는 링크가 나타납니다.

    success [10:48:28] Build 6f9dd7 finished in 1179 ms! ( http://localhost:8000/ )
    +

    웹브라우저에서 실행시 표기되는 로그의 링크를 입력하면 공개된 웹화면과 동일한 환경을 확인할 수 있습니다.

    typora

    typora는 멀티 OS를 지원하는 마크다운 에디터/뷰어 입니다. VuePress의 플러그인과 일부 호환되지 않는 표기들이 있으나, 전역 검색이 가능하고 개인 노트를 활용하듯 관리할 수 있습니다.

    ',5),P={href:"https://typora.io/releases/all",target:"_blank",rel:"noopener noreferrer"},T=t("li",null,"1.0.0 이 릴리즈되면서 유료 모델로 전환되었습니다.",-1),O=t("li",null,"기존 beta 버전의 경우 무료 사용 가능합니다.",-1),A=t("li",null,[e("docmoa의 소스를 clone/download 합니다."),t("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[t("pre",{class:"language-bash"},[t("code",null,[t("span",{class:"token function"},"git"),e(` clone https://github.com/docmoa/docs.git +`)])])])],-1),L=c('

    3.typora를 실행하고 옵션에서 [파일] > [열기] 를 클릭하여 앞서 받은 소스 디렉토리의 docs 디렉토리를 열어줍니다.

    1. 전역검색은 다음과 같이 사용 가능합니다.

    VS Code

    ',3),N=t("code",null,"Visual Studio Code",-1),R=t("code",null,"VS Code",-1),B={href:"https://code.visualstudio.com/Download",target:"_blank",rel:"noopener noreferrer"},H=c(`
  • docmoa의 소스를 clone/download 합니다.
    git clone https://github.com/docmoa/docs.git
    +
  • VS Code를 실행하고 [파일] > [열기] 를 클릭하여 앞서 받은 소스 디렉토리의 docs 디렉토리를 열어줍니다.
  • Preview 버튼을 클릭하면 양쪽으로 비교하면서 글을 확인/편집 할 수 있습니다.
  • 전역검색은 다음과 같이 사용 가능합니다.
  • ',4);function z(M,Z){const n=i("ExternalLinkIcon"),d=i("CodeTabs"),r=i("RouteLink");return u(),g("div",null,[v,f,k,t("p",null,[t("a",y,[e("docmoa"),o(n)]),e("의 공개된 페이지를 통해 문서를 읽을 수 있습니다.")]),x,w,o(d,{id:"18",data:[{id:"npm"},{id:"yarn"}],"tab-id":"shell"},{title0:a(({value:s,isActive:l})=>[e("npm")]),title1:a(({value:s,isActive:l})=>[e("yarn")]),tab0:a(({value:s,isActive:l})=>[V]),tab1:a(({value:s,isActive:l})=>[S]),_:1}),C,t("ol",null,[t("li",null,[e("typora.io를 통해 에디터를 다운로드 받고 설치합니다. "),t("ul",null,[t("li",null,[e("Release 페이지 : "),t("a",P,[e("https://typora.io/releases/all"),o(n)])]),T,O])]),A]),L,t("p",null,[N,e("에서는 마크다운의 프리뷰를 지원합니다. 앞서 "),o(r,{to:"/00-Howto/#typora"},{default:a(()=>[e("typora")]),_:1}),e("는 마크다운으로 작성하는 즉시 마크다운 형태로 변경되었다면, "),R,e("에서는 좌우 비교하면서 작성한 표현이 어떻게 반영되는지 확인할 수 있습니다.")]),t("ol",null,[t("li",null,[e("VS Code "),t("a",B,[e("Download"),o(n)]),e(" 사이트에서 환경에 맞는 설치 파일을 다운로드 받고 설치합니다.")]),H])])}const j=p(_,[["render",z],["__file","01-Overview.html.vue"]]),q=JSON.parse('{"path":"/00-Howto/01-Overview.html","title":"docmoa 활용 가이드","lang":"ko-KR","frontmatter":{"description":"문서를 활용하는 방법을 안내","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/01-Overview.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"docmoa 활용 가이드"}],["meta",{"property":"og:description","content":"문서를 활용하는 방법을 안내"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"docmoa 활용 가이드\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"github page","slug":"github-page","link":"#github-page","children":[]},{"level":2,"title":"VuePress (Local 실행)","slug":"vuepress-local-실행","link":"#vuepress-local-실행","children":[]},{"level":2,"title":"typora","slug":"typora","link":"#typora","children":[]},{"level":2,"title":"VS Code","slug":"vs-code","link":"#vs-code","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.64,"words":192},"filePathRelative":"00-Howto/01-Overview.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    문서가 인터넷상에 공개되는 목적은 접근성을 극대화 하기 위함 입니다. 또한 로컬환경에서 빠르게 문서를 검색하기 위해 해당 git repo를 clone 받거나 download 받아서 별도의 마크다운 툴과 연동하는 것도 가능합니다. VuePress 기반으로 구성되었기 때문에 이외의 방식은 문서 표기에 제약이 있을 수 있습니다.

    \\n

    github page

    \\n

    docmoa의 공개된 페이지를 통해 문서를 읽을 수 있습니다.

    "}');export{j as comp,q as data}; diff --git a/assets/01-Start.html-CZXt5onJ.js b/assets/01-Start.html-CZXt5onJ.js new file mode 100644 index 0000000000..5e803b634d --- /dev/null +++ b/assets/01-Start.html-CZXt5onJ.js @@ -0,0 +1,56 @@ +import{_ as r}from"./vuepress-DVMSUmm5.js";import{_ as h}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as u,c as m,b as a,d as n,a as e,w as t,e as s}from"./app-Bzk8Nrll.js";const g={},v=a("h1",{id:"문서작성-시작",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#문서작성-시작"},[a("span",null,"문서작성 '시작'")])],-1),b=a("p",null,"docmoa에 문서 기여하기위한 가이드를 설명합니다.",-1),k=a("div",{class:"hint-container tip"},[a("p",{class:"hint-container-title"},"팁"),a("p",null,[n("다양한 방법으로 문서를 작성하고 기여할 수 있습니다."),a("br"),n(" 얽매이지 마세요.")])],-1),_=s(`
    git clone https://github.com/docmoa/docs.git
    +

    또는 github에서 fork하여 별도 관리 후 pull request 하여도 좋습니다.

    디렉토리 구조 이해하기

    clone 받은 구조는 VuePress의 구조를 갖고 있습니다. 문서의 기준이 되는 디렉토리는 docs 입니다.

    디렉토리 구조

    .
    +├── .gitignore
    +├── LICENSE.md
    +├── README.md
    +├── \`docs\`
    +│   ├── .vuepress
    +│   ├── 00-Howto
    +│   ├── 01-Infra
    +│   ├── 02-PrivatePlatform
    +│   ├── 03-PublicCloud
    +│   ├── 04-HashiCorp
    +│   ├── 05-etc
    +│   ├── 98-tag
    +│   ├── 99-about
    +│   └── README.md
    +└── package.json
    +

    기존 환경을 로컬에서 실행하고 확인하기

    브라우저에서 보여지는 화면을 실시간으로 확인하기 위해 로컬환경에서 Vewpress를 실행합니다. Nodejs가 필요합니다.

    `,7),f=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{"bash:no-line-numbers":"",class:"language-bash"},[a("code",null,[a("span",{class:"token comment"},"# 클론 받은 디렉토리 이동 후 npm install"),n(` +`),a("span",{class:"token builtin class-name"},"cd"),n(` docs +`),a("span",{class:"token function"},"npm"),n(),a("span",{class:"token function"},"install"),n(` + +`),a("span",{class:"token comment"},"# start VuePress writing"),n(` +`),a("span",{class:"token function"},"npm"),n(` run dev +`)])]),a("div",{class:"highlight-lines"},[a("br"),a("div",{class:"highlight-line"}," "),a("br"),a("br"),a("div",{class:"highlight-line"}," "),a("div",{class:"highlight-line"}," ")])],-1),E=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{"bash:no-line-numbers":"",class:"language-bash"},[a("code",null,[a("span",{class:"token comment"},"# 클론 받은 디렉토리 이동 후 npm install"),n(` +`),a("span",{class:"token builtin class-name"},"cd"),n(` docs +`),a("span",{class:"token function"},"yarn"),n(),a("span",{class:"token function"},"install"),n(` + +`),a("span",{class:"token comment"},"# start VuePress writing"),n(` +`),a("span",{class:"token function"},"yarn"),n(` vuepress dev +`)])]),a("div",{class:"highlight-lines"},[a("br"),a("div",{class:"highlight-line"}," "),a("br"),a("br"),a("div",{class:"highlight-line"}," "),a("div",{class:"highlight-line"}," ")])],-1),w=s(`

    실행이 완료되면 로그에 다음과 같은 메시지와 접속할 수 있는 링크가 나타납니다.

    실행 후 접속 링크 출력

    ✔ Initializing and preparing data - done in 12.34s
    +
    +  vite v4.4.9 dev server running at:
    +
    +  ➜  Local:   http://localhost:8080/
    +  ➜  Network: http://192.168.0.9:8080/
    +

    웹브라우저에서 실행시 표기되는 로그의 링크를 입력하면 공개된 웹화면과 동일한 환경을 확인할 수 있습니다.

    문서 트리

    docs 디렉토리 내에 기존에 구성된 항목 내에 작성도 가능하고 새로운 카테고리를 생성할 수도 있습니다. 디렉토리와 파일은 생성된 순서와 관계없이 정렬되기 때문에 좌측 Sidebar 표기시 원하는 순서대로 표기되기를 원하는 경우 {숫자}- 을 파일명 앞에 붙여 의도한대로 표시되도록 구성가능합니다.

    예를 들어 Howto에 작성된 내용을 바탕으로 설명합니다.

    문서 트리

    00-Howto
    +├── \`01-Overview.md\`
    +├── \`02-Guide\`
    +│   ├── 01-Start.md
    +│   ├── 02-PullRequest.md
    +│   └── 03-Fork.md
    +├── \`03-Tips\`
    +│   ├── CodeBlock.md
    +│   ├── Link.md
    +│   └── TipBox.md
    +└── \`README.md\`
    +
    `,8),x={href:"http://01.Overview.md",target:"_blank",rel:"noopener noreferrer"},y=a("li",null,"02.문서작성가이드 (디렉토리)",-1),C=a("li",null,"03.문서작성팁 (디렉토리)",-1),S=a("li",null,[n("README.md는 "),a("code",null,"index.html"),n("과 같이 해당 디렉토리의 기본 페이지로 등록됩니다. 디렉토리 경로만 입력하는 경우 해당 페이지가 나타납니다. "),a("ul",null,[a("li",null,[n("요청하는 페이지 경로 : "),a("code",null,"https://docmoa.github.io/00-Howto/")]),a("li",null,[n("실제하는 페이지 경로 : "),a("code",null,"https://docmoa.github.io/00-Howto/README.md")])])],-1),A=s(`

    디렉토리 이름

    디렉토리 이름에 공백이 있는 경우 다른 문서에서 참조 시 공백 문자인 %20 를 표기해야 하는 이슈가 발생할 수 있습니다.

    [](https://docmoa.github.io/00-Howto/공백이%20있는%20디렉토리)
    +

    첫번째 글 쓰기

    `,2),B=a("code",null,"SSH Too many authentication failures",-1),H=a("br",null,null,-1),T=s(`
    1. Linux 카테고리로 가정하고 01.Infra > Linux > TroubleShooting 이라는 디렉토리를 생성합니다.

    2. 문서 작성을 위한 SSH Too many authentication failures.md 파일을 생성합니다.

    3. 문서 내용에는 문서 기본 서문(Frontmatter)을 작성하기 위한 ---로 구성된 블록이 최상단에 명시됩니다. 내용은 기존 마크다운 문서를 작성하는 것과 동일하게 구성합니다.

      ---
      +
      +---
      +
      +# SSH Too many authentication failures (제목인 Title은 h1 스타일로)
      +
      +너무많은 인증 실패로 인한 SSH 접속이 안되는 메시지를 간혹 보게되는 경우가 있다.
      +<생략>
      +

    내가 쓴 문서를 docmoa에 개시 요청하기

    `,2);function D(R,G){const o=c("RouteLink"),d=c("CodeTabs"),p=c("ExternalLinkIcon");return u(),m("div",null,[v,b,k,a("p",null,[n("문서는 모두 git으로 관리되며 공개되어있습니다. "),e(o,{to:"/00-Howto/02-Guide/02-Contribute.html"},{default:t(()=>[n("문서 기여를 위한 방식")]),_:1}),n("은 별도 안내로 구분하여 설명합니다.")]),_,e(d,{id:"33",data:[{id:"npm"},{id:"yarn"}],"tab-id":"shell"},{title0:t(({value:l,isActive:i})=>[n("npm")]),title1:t(({value:l,isActive:i})=>[n("yarn")]),tab0:t(({value:l,isActive:i})=>[f]),tab1:t(({value:l,isActive:i})=>[E]),_:1}),w,a("ul",null,[a("li",null,[n("파일과 디렉토리 구분없이 같은 Depth에 위치하는 경우 함께 표기 됩니다. "),a("ul",null,[a("li",null,[a("a",x,[n("01.Overview.md"),e(p)]),n(" (파일)")]),y,C])]),S]),A,a("p",null,[n("여기서는 "),B,n(" 와 관련한 간단한 글을 개시하는 것을 예로 설명하겠습니다."),H,n(" 실제 VuePress 환경으로 어떤 내용이 표기되는지 동적인 확인을 위해 앞서 "),e(o,{to:"/00-Howto/02-Guide/01-Start.html#%EA%B8%B0%EC%A1%B4-%ED%99%98%EA%B2%BD%EC%9D%84-%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B3%A0-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0"},{default:t(()=>[n("기존 환경을 로컬에서 실행하고 확인하기")]),_:1}),n("에서 처럼 화면을 띄우고 작업하면 실제 작성하는 글들이 어떻게 보여지는지도 함께 확인할 수 있습니다.")]),T,a("p",null,[n("작성한 문서를 docmoa에 기여하는 방법은 다음 "),e(o,{to:"/00-Howto/02-Guide/02-contribute.html"},{default:t(()=>[n("Contribute")]),_:1}),n(" 항목에서 설명합니다.")])])}const N=h(g,[["render",D],["__file","01-Start.html.vue"]]),I=JSON.parse(`{"path":"/00-Howto/02-Guide/01-Start.html","title":"문서작성 '시작'","lang":"ko-KR","frontmatter":{"description":"문서작성 '시작' docmoa에 문서 기여하기위한 가이드를 설명합니다. 팁 다양한 방법으로 문서를 작성하고 기여할 수 있습니다. 얽매이지 마세요. 문서는 모두 git으로 관리되며 공개되어있습니다. 은 별도 안내로 구분하여 설명합니다. 또는 github에서 fork하여 별도 관리 후 pull request 하여도 좋습...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/02-Guide/01-Start.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"문서작성 '시작'"}],["meta",{"property":"og:description","content":"문서작성 '시작' docmoa에 문서 기여하기위한 가이드를 설명합니다. 팁 다양한 방법으로 문서를 작성하고 기여할 수 있습니다. 얽매이지 마세요. 문서는 모두 git으로 관리되며 공개되어있습니다. 은 별도 안내로 구분하여 설명합니다. 또는 github에서 fork하여 별도 관리 후 pull request 하여도 좋습..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-19T11:31:31.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-19T11:31:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"문서작성 '시작'\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-19T11:31:31.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"디렉토리 구조 이해하기","slug":"디렉토리-구조-이해하기","link":"#디렉토리-구조-이해하기","children":[{"level":3,"title":"기존 환경을 로컬에서 실행하고 확인하기","slug":"기존-환경을-로컬에서-실행하고-확인하기","link":"#기존-환경을-로컬에서-실행하고-확인하기","children":[]},{"level":3,"title":"문서 트리","slug":"문서-트리","link":"#문서-트리","children":[]}]},{"level":2,"title":"첫번째 글 쓰기","slug":"첫번째-글-쓰기","link":"#첫번째-글-쓰기","children":[]},{"level":2,"title":"내가 쓴 문서를 docmoa에 개시 요청하기","slug":"내가-쓴-문서를-docmoa에-개시-요청하기","link":"#내가-쓴-문서를-docmoa에-개시-요청하기","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1695123091000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":0.81,"words":242},"filePathRelative":"00-Howto/02-Guide/01-Start.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    docmoa에 문서 기여하기위한 가이드를 설명합니다.

    \\n
    \\n

    \\n

    다양한 방법으로 문서를 작성하고 기여할 수 있습니다.
    \\n얽매이지 마세요.

    \\n
    \\n

    문서는 모두 git으로 관리되며 공개되어있습니다. 문서 기여를 위한 방식은 별도 안내로 구분하여 설명합니다.

    ","autoDesc":true}`);export{N as comp,I as data}; diff --git a/assets/01-cicd.html-OsUvvkt4.js b/assets/01-cicd.html-OsUvvkt4.js new file mode 100644 index 0000000000..f60409ab1f --- /dev/null +++ b/assets/01-cicd.html-OsUvvkt4.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o,c as l,a as c,b as e,e as a}from"./app-Bzk8Nrll.js";const r={},s=e("h1",{id:"_1-ci-cd",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-ci-cd"},[e("span",null,"1. CI/CD")])],-1),d=e("h2",{id:"_1-1-ci-cd-concept-definitions",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-1-ci-cd-concept-definitions"},[e("span",null,"1.1 CI/CD Concept Definitions")])],-1),p=e("ul",null,[e("li",null,"Continuous integration"),e("li",null,"Continuous delivery"),e("li",null,"Continuous deployment"),e("li",null,"Source control management (SCM)")],-1),m=a('

    1.2 Delivery vs Deployment

    1.3 Jenkins for CI/CD

    ',4);function u(h,_){const n=t("Mermaid");return o(),l("div",null,[s,d,p,c(n,{id:"mermaid-28",code:"eJxtjcEJwzAQBN9RFWogDeThT1xAIGngkBdzIN8p0sng7mOHECPjfQ6zuwXvCgnomcZMk7skysaBE4n5HjOipha+UKwlT6MRLXpkHWowVnF+zW/o2nVb++bvKsZStRbPYlifT9Tv7N+1TR4QeUZeDub+dtRT1GWCmPsA04VS8A=="}),m])}const f=i(r,[["render",u],["__file","01-cicd.html.vue"]]),g=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/01-cicd.html","title":"1. CI/CD","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/01-cicd.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"1. CI/CD"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"1. CI/CD\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1.1 CI/CD Concept Definitions","slug":"_1-1-ci-cd-concept-definitions","link":"#_1-1-ci-cd-concept-definitions","children":[]},{"level":2,"title":"1.2 Delivery vs Deployment","slug":"_1-2-delivery-vs-deployment","link":"#_1-2-delivery-vs-deployment","children":[]},{"level":2,"title":"1.3 Jenkins for CI/CD","slug":"_1-3-jenkins-for-ci-cd","link":"#_1-3-jenkins-for-ci-cd","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.23,"words":70},"filePathRelative":"05-Software/Jenkins/pipeline101/01-cicd.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    1.1 CI/CD Concept Definitions

    \\n\\n

    1.2 Delivery vs Deployment

    \\n"}');export{f as comp,g as data}; diff --git a/assets/01-eks-deploy.html-9Qn8CqOo.js b/assets/01-eks-deploy.html-9Qn8CqOo.js new file mode 100644 index 0000000000..33dc596f2e --- /dev/null +++ b/assets/01-eks-deploy.html-9Qn8CqOo.js @@ -0,0 +1,281 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as i,b as n,d as a,a as e,e as t}from"./app-Bzk8Nrll.js";const c={},r=t('

    AEWS 1주차 - Amzaon EKS 설치 및 기본 사용

    이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

    img
    img

    필자는 기본적인 스터디내용을 이번 시리즈에 연재할 예정이며, 추가적으로 HashiCorp의 Consul, Vault 등을 샘플로 배포하며 연동하는 내용을 조금씩 다뤄볼 예정이다.

    AWS Workshop - EKS 관련

    참고 : AWS EKS 관련 핸즈온 워크샵을 해볼 수 있는 다양한 링크 모음이다.

    ',6),u={href:"https://www.eksworkshop.com/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/aws-samples/eks-workshop-v2",target:"_blank",rel:"noopener noreferrer"},d={href:"https://aws.amazon.com/ko/blogs/containers/introducing-the-amazon-eks-workshop/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.youtube.com/@ContainersfromtheCouch",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.youtube.com/@ContainersfromtheCouch/streams",target:"_blank",rel:"noopener noreferrer"},v={href:"https://catalog.us-east-1.prod.workshops.aws/workshops/9c0aa9ab-90a9-44a6-abe1-8dff360ae428/ko-KR",target:"_blank",rel:"noopener noreferrer"},g={href:"https://awskrug.github.io/eks-workshop/",target:"_blank",rel:"noopener noreferrer"},h={href:"https://awskocaptain.gitbook.io/aws-builders-eks/",target:"_blank",rel:"noopener noreferrer"},y={href:"https://tf-eks-workshop.workshop.aws/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.youtube.com/live/TXa-y-Uwh2w?feature=share",target:"_blank",rel:"noopener noreferrer"},S={href:"https://aws.github.io/aws-eks-best-practices/",target:"_blank",rel:"noopener noreferrer"},E={href:"https://catalog.us-east-1.prod.workshops.aws/workshops/b67b6665-f7a2-427f-affb-caccd087d50d/en-US",target:"_blank",rel:"noopener noreferrer"},w={href:"https://catalog.us-east-1.prod.workshops.aws/workshops/c15012ac-d05d-46b1-8a4a-205e7c9d93c9/en-US",target:"_blank",rel:"noopener noreferrer"},A={href:"https://catalog.workshops.aws/observability/en-US",target:"_blank",rel:"noopener noreferrer"},_={href:"https://catalog.us-east-1.prod.workshops.aws/workshops/a1101fcc-c7cf-4dd5-98c4-f599a65056d5/en-US",target:"_blank",rel:"noopener noreferrer"},C={href:"https://catalog.workshops.aws/general-immersionday/ko-KR",target:"_blank",rel:"noopener noreferrer"},P={href:"https://whchoi98.gitbook.io/aws-iam/",target:"_blank",rel:"noopener noreferrer"},T=t('

    여담이지만, HashiCorp 솔루션에 대한 다양한 HOL Workshop 실습도 사용자들이 많이 만들고 기여할 수 있도록 플랫폼을 오픈하면 좋을 것 같다.

    Amazon EKS 소개

    img
    img

    Amazon Elastic Kubernetes Service는 자체 Kubernetes 컨트롤 플레인 또는 노드를 설치, 운영 및 유지 관리할 필요 없이 Kubernetes 실행에 사용할 수 있는 관리형 서비스이다.

    EKS를 사용하는 다양한 이유가 있겠지만 대표적으로 여러 AWS 서비스와 통합할 수 있다는 장점이 크다.

    ',6),R={href:"https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html",target:"_blank",rel:"noopener noreferrer"},x=n("p",null,"이 부분이 단점이라고 이야기하는 이유는 실제 서비스를 운영하보면 EKS 클러스터 업그레이드를 하기위한 운영조직 체계가 갖춰져 있지 않은 상황에서 강제로 EKS 구버전에 대한 업그레이드를 해야하는 상황을 직면할 수 있기 때문이다.",-1),K=n("p",null,"물론, 보안, 서비스 지원 등 다양한 이유로 인해 클러스터 업그레이드는 불가피하지만 때때로 업그레이드를 강제해야하는 것은 특정 서비스를 운영하는 조직에게는 큰 걸림돌이 될 수 있다.",-1),N=n("p",null,"기회가 된다면 이번 스터디중에 EKS 업그레이드로 인한 어려움을 겪었던 사례를 발표해보려 한다.",-1),I={href:"https://endoflife.date/amazon-eks",target:"_blank",rel:"noopener noreferrer"},z=n("h2",{id:"amazon-eks-클러스터-구성-방안",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#amazon-eks-클러스터-구성-방안"},[n("span",null,"Amazon EKS 클러스터 구성 방안")])],-1),O=n("p",null,"EKS 클러스터는 다음과 같은 방식으로 배포할 수 있다.",-1),L=n("ul",null,[n("li",null,"웹 관리 콘솔"),n("li",null,[n("strong",null,"eksctl")]),n("li",null,"기타(CDK, CloudFormation, Terraform 등)")],-1),W=n("p",null,[a("이번 EKS 스터디 시리즈에서는 "),n("code",null,"eksctl"),a(" 을 활용한 배포방식을 활용할 예정이다.")],-1),D={href:"https://eksctl.io/",target:"_blank",rel:"noopener noreferrer"},q=t(`
    img
    img

    실습환경 구성

    실습환경은 외부에서 접근 가능한 Bastion 역할을 하는 EC2와 퍼블릭 서브넷 2개에 워커노드 두 대를 구성한다.

    참고 : 실습환경 변경 챕터에서 노드를 3대로 증설예정

    img

    간단하게 VPC, Security Group, EC2 등을 구성하는 CF 코드를 통해 사전 환경을 구성한다.

    aws cloudformation deploy --template-file myeks-1week.yaml \\
    +     --stack-name myeks --parameter-overrides KeyName=hw-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
    +
    +Waiting for changeset to be created..
    +Waiting for stack create/update to complete
    +Successfully created/updated stack - myeks
    +
    +# Public IP 확인
    +aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[*].OutputValue' --output text
    +13.124.14.182
    +
    +# ec2 에 SSH 접속
    +ssh -i ~/.ssh/id_rsa ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
    +
    img
    img

    필자의 경우에는 AWS IAM 계정 정책에 대한 제약사항이 있어, aws configure 명령등으로 access_key, secret_key 설정을 하지않고 EC2 인스턴스에 admin 권한을 부여하여 사용하였다.

    img
    img
    # EKS 배포할 VPC 정보 확인
    +export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" | jq -r .Vpcs[].VpcId)
    +echo "export VPCID=$VPCID" >> /etc/profile
    +echo VPCID
    +
    +## 퍼블릭 서브넷 ID 확인
    +export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
    +export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
    +echo "export PubSubnet1=$PubSubnet1" >> /etc/profile
    +echo "export PubSubnet2=$PubSubnet2" >> /etc/profile
    +echo $PubSubnet1
    +echo $PubSubnet2
    +
    +# EKS 클러스터 배포
    +eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium --node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.24 --ssh-access --external-dns-access --verbose 4
    +

    EKS 클러스터를 명령형으로 배포하지 않고 YAML로 작성하여 언언적으로 배포하는 것도 가능하다. 다음은 앞서 실행한 eksctl create cluster 명령의 --dry-run 옵션을 통해 추출한 명세이다.

    apiVersion: eksctl.io/v1alpha5
    +cloudWatch:
    +  clusterLogging: {}
    +iam:
    +  vpcResourceControllerPolicy: true
    +  withOIDC: false
    +kind: ClusterConfig
    +kubernetesNetworkConfig:
    +  ipFamily: IPv4
    +managedNodeGroups:
    +- amiFamily: AmazonLinux2
    +  desiredCapacity: 2
    +  disableIMDSv1: false
    +  disablePodIMDS: false
    +  iam:
    +    withAddonPolicies:
    +      albIngress: false
    +      appMesh: false
    +      appMeshPreview: false
    +      autoScaler: false
    +      awsLoadBalancerController: false
    +      certManager: false
    +      cloudWatch: false
    +      ebs: false
    +      efs: false
    +      externalDNS: true
    +      fsx: false
    +      imageBuilder: false
    +      xRay: false
    +  instanceSelector: {}
    +  instanceType: t3.medium
    +  labels:
    +    alpha.eksctl.io/cluster-name: myeks
    +    alpha.eksctl.io/nodegroup-name: myeks-nodegroup
    +  maxSize: 2
    +  minSize: 2
    +  name: myeks-nodegroup
    +  privateNetworking: false
    +  releaseVersion: ""
    +  securityGroups:
    +    withLocal: null
    +    withShared: null
    +  ssh:
    +    allow: true
    +    publicKeyPath: ~/.ssh/id_rsa.pub
    +  tags:
    +    alpha.eksctl.io/nodegroup-name: myeks-nodegroup
    +    alpha.eksctl.io/nodegroup-type: managed
    +  volumeIOPS: 3000
    +  volumeSize: 30
    +  volumeThroughput: 125
    +  volumeType: gp3
    +metadata:
    +  name: myeks
    +  region: ap-northeast-2
    +  version: "1.24"
    +privateCluster:
    +  enabled: false
    +  skipEndpointCreation: false
    +vpc:
    +  autoAllocateIPv6: false
    +  cidr: 192.168.0.0/16
    +  clusterEndpoints:
    +    privateAccess: false
    +    publicAccess: true
    +  id: vpc-0521fc003559b2f2c
    +  manageSharedNodeSecurityGroupRules: true
    +  nat:
    +    gateway: Disable
    +  subnets:
    +    public:
    +      ap-northeast-2a:
    +        az: ap-northeast-2a
    +        cidr: 192.168.1.0/24
    +        id: subnet-0fdff27653277aaf0
    +      ap-northeast-2c:
    +        az: ap-northeast-2c
    +        cidr: 192.168.2.0/24
    +        id: subnet-084a8752d4c7ddf6c
    +

    정상적으로 클러스터가 구성된 것을 확인할 수 있다.

    # eks클러스터 확인
    +eksctl get cluster
    +NAME	REGION		EKSCTL CREATED
    +myeks	ap-northeast-2	True
    +
    +# 노드확인
    +kubectl get node -v=6
    +I0423 22:10:48.050969    2339 loader.go:374] Config loaded from file:  /root/.kube/config
    +
    +I0423 22:10:48.880262    2339 round_trippers.go:553] GET https://6E205513BA73EEBC3CA693BADEEC5294.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 819 milliseconds
    +NAME                                               STATUS   ROLES    AGE   VERSION
    +ip-192-168-1-139.ap-northeast-2.compute.internal   Ready    <none>   61m   v1.24.11-eks-a59e1f0
    +ip-192-168-2-225.ap-northeast-2.compute.internal   Ready    <none>   61m   v1.24.11-eks-a59e1f0
    +
    +# 파드확인
    +k get pods -A
    +NAMESPACE     NAME                      READY   STATUS    RESTARTS   AGE
    +kube-system   aws-node-2bpxr            1/1     Running   0          62m
    +kube-system   aws-node-s7p5b            1/1     Running   0          62m
    +kube-system   coredns-dc4979556-knkkh   1/1     Running   0          68m
    +kube-system   coredns-dc4979556-m789b   1/1     Running   0          68m
    +kube-system   kube-proxy-lkp8f          1/1     Running   0          62m
    +kube-system   kube-proxy-z6hbk          1/1     Running   0          62m
    +

    참고 : eksctl 명령 예제

    # eksctl help
    +eksctl
    +eksctl create
    +eksctl create cluster --help
    +eksctl create nodegroup --help
    +

    실습환경 변경

    앞선 과정을 통해 실습을 위한 클러스터 구성이 완성되었다. 필자는 향후 샘플 애플리케이션으로 Vault, Consul 등을 배포할 예정이다. 때문에, 최소 3대 이상의 노드가 필요하여 기본 실습 노드를 3대로 구성한다.

    EKS는 nodegraup 개수의 최소/최대 개수를 선언적으로 관리할 수 있다. 다음은 노드 개수를 변경/확인 하는 방법이다.

    # eks 노드 그룹 정보 확인
    +eksctl get nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup
    +CLUSTER	NODEGROUP	STATUS		CREATED			MIN SIZE	MAX SIZE	DESIRED CAPACITY	INSTANCE TYPE	IMAGE ID	ASG NAME							TYPE
    +myeks	myeks-nodegroup	UPDATING	2023-04-23T12:07:57Z	2		2		2			t3.medium	AL2_x86_64	eks-myeks-nodegroup-fcc3d701-b90a-9c83-7907-fca8459770b9	managed
    +
    +# 노드 2개 → 3개 증가
    +eksctl scale nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup --nodes 3 --nodes-min 3 --nodes-max 6
    +
    +# 노드 그룹 변경 확인
    +eksctl get nodegroup --cluster myeks --region ap-northeast-2 --name myeks-nodegroup
    +CLUSTER	NODEGROUP	STATUS		CREATED			MIN SIZE	MAX SIZE	DESIRED CAPACITY	INSTANCE TYPE	IMAGE ID	ASG NAME							TYPE
    +myeks	myeks-nodegroup	UPDATING	2023-04-23T12:07:57Z	3		6		3			t3.medium	AL2_x86_64	eks-myeks-nodegroup-fcc3d701-b90a-9c83-7907-fca8459770b9	managed
    +
    +# 노드 확인
    +kubectl get nodes -o wide
    +
    +NAME                                               STATUS   ROLES    AGE    VERSION                INTERNAL-IP     EXTERNAL-IP      OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
    +ip-192-168-1-139.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0   192.168.1.139   43.201.51.34     Amazon Linux 2   5.10.176-157.645.amzn2.x86_64   containerd://1.6.19
    +ip-192-168-1-76.ap-northeast-2.compute.internal    Ready    <none>   53s    v1.24.11-eks-a59e1f0   192.168.1.76    13.124.158.208   Amazon Linux 2   5.10.176-157.645.amzn2.x86_64   containerd://1.6.19
    +ip-192-168-2-225.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0   192.168.2.225   52.79.236.227    Amazon Linux 2   5.10.176-157.645.amzn2.x86_64   containerd://1.6.19
    +
    +kubectl get nodes -l eks.amazonaws.com/nodegroup=$CLUSTER_NAME-nodegroup
    +NAME                                               STATUS   ROLES    AGE    VERSION
    +ip-192-168-1-139.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0
    +ip-192-168-1-76.ap-northeast-2.compute.internal    Ready    <none>   74s    v1.24.11-eks-a59e1f0
    +ip-192-168-2-225.ap-northeast-2.compute.internal   Ready    <none>   150m   v1.24.11-eks-a59e1f0
    +

    샘플 애플리케이션 배포

    필자는 본 글을 작성하던 시기에 고객사 환경에 ArgoCD + Helm을 기반으로 Consul Cluster 구성 테스트 요청이 있어 해당 클러스터를 활용해 보았다.

    img
    img

    ArgoCD 배포

    참고 : PKOS 2기에 사용한 ArgoCD 배포 가이드를 참고하여 배포한다.

    # 네임스페이스 생성
    +kubectl create ns argocd
    +
    +# argocd-helm 설치
    +cd
    +helm repo add argo https://argoproj.github.io/argo-helm
    +helm repo update
    +helm install argocd argo/argo-cd --set server.service.type=LoadBalancer --namespace argocd --version 5.19.14
    +
    +# 확인
    +helm list -n argocd
    +kubectl get pod,pvc,svc,deploy,sts -n argocd
    +kubectl get-all -n argocd
    +
    +kubectl get crd | grep argoproj
    +applications.argoproj.io              2023-03-19T11:39:26Z
    +applicationsets.argoproj.io           2023-03-19T11:39:26Z
    +appprojects.argoproj.io               2023-03-19T11:39:26Z
    +
    +# admin 계정의 암호 확인
    +ARGOPW=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
    +echo $ARGOPW
    +mf8bOtNEq7iHMqq1
    +
    +# 웹 접속 로그인 (admin) CLB의 hostname으로 접속
    +k get svc -n argocd argocd-server -o jsonpath='{.status.loadBalancer.ingress[].hostname}'
    +
    img
    img

    GitHub 저장소 준비

    `,38),M={href:"https://github.com/chosam2/gitops",target:"_blank",rel:"noopener noreferrer"},G=t(`

    사전에 로컬에서 다운로드 받은 Consul Helm Chart 파일에 새로 재정의 할 values 파일을 GitHub 저장소에 업로드 한다.

    img
    img

    참고 : 다음은 실제 작성된 Values 파일이다. 각 항목에 대한 상세한 설명은 향후 Consul Deploy on K8s 가이드를 작성해서 업로드 예정이다.

    client:
    +  grpc: true
    +connectInject:
    +  consulNamespaces:
    +    mirroringK8S: true
    +  enabled: true
    +controller:
    +  enabled: true
    +global:
    +  acls:
    +    manageSystemACLs: true
    +  enableConsulNamespaces: true
    +  enterpriseLicense:
    +    secretKey: key
    +    secretName: license
    +  gossipEncryption:
    +    autoGenerate: true
    +  image: hashicorp/consul-enterprise:1.13.7-ent
    +  imageEnvoy: envoyproxy/envoy:v1.22.5
    +  imageK8S: hashicorp/consul-k8s-control-plane:0.49.5
    +  metrics:
    +    enabled: false
    +  tls:
    +    enableAutoEncrypt: true
    +    enabled: true
    +    httpsOnly: false
    +    verify: false
    +ingressGateways:
    +  defaults:
    +    replicas: 1
    +    service:
    +      type: LoadBalancer
    +  enabled: true
    +  gateways:
    +  - name: ingress-gateway
    +meshGateway:
    +  enabled: false
    +  replicas: 1
    +  service:
    +    enabled: true
    +    nodePort: 32000
    +    type: NodePort
    +server:
    +  replicas: 3
    +terminatingGateways:
    +  defaults:
    +    replicas: 1
    +  enabled: false
    +ui:
    +  enabled: true
    +  service:
    +    port:
    +      http: 80
    +      https: 443
    +    type: LoadBalance
    +

    ArgoCD + GitHub 연동

    ArgoCD CLI를 통해 필자의 GitHub을 연동한다

    argocd login <argocd 주소> --username admin --password $ARGOPW
    +...
    +'admin:login' logged in successfully
    +
    +argocd repo add https://github.com/<깃헙 계정명>/<레파지토리명> --username <깃헙 계정명> --password <깃헙 계정 암호>
    + 
    +argocd repo list
    +TYPE  NAME  REPO                                   INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
    +git         https://github.com/chosam2/gitops.git  false     false  false  true   Successful
    +
    +# 기본적으로 아르고시디가 설치된 쿠버네티스 클러스터는 타깃 클러스터로 등록됨
    +argocd cluster list
    +SERVER                          NAME        VERSION  STATUS      MESSAGE  PROJECT
    +https://kubernetes.default.svc  in-cluster  1.24+    Successful
    +

    ArgoCD Application 생성 및 배포

    앞서 생성한 GitHub 저장소에 업로드한 consul helm values 파일을 통해 배포하기 위해 Application CRD를 생성 및 배포한다.

    apiVersion: argoproj.io/v1alpha1
    +kind: Application
    +metadata:
    +  name: consul-helm
    +  namespace: argocd
    +  finalizers:
    +  - resources-finalizer.argocd.argoproj.io
    +spec:
    +  destination:
    +    namespace: consul
    +    server: https://kubernetes.default.svc
    +  project: default
    +  source:
    +    repoURL: https://github.com/chosam2/gitops.git
    +    path: argocd
    +    targetRevision: main
    +    helm:
    +      valueFiles:
    +      - override-values.yaml
    +  syncPolicy:
    +    syncOptions:
    +    - CreateNamespace=true
    +
    k apply -f consul-helm-argo-application.yaml
    +application.argoproj.io/consul-helm created
    +

    최초 배포 시 OutOfSync 상태로 배포되었지만, 동기화 버튼을 클릭하여 강제로 동기화해준 뒤 정상적으로 배포된 것을 확인할 수 있다.
    다만, 최초 OutOfSync 상태로 배포되는 부분에 대해서는 Application YAML 작성 시 옵션을 통해 해결이 가능하지만, 실제 운영시 영향도 체크가 필요해 보인다. 이 부분은 다음 블로깅시에 조금 더 테스트 및 확인해볼 예정이다.

    img
    img
    img
    img
    img
    img

    마무리

    1주차 스터디는 EKS에 대한 전반적인 컨셉과 기본적으로 클러스터를 구성하고 Consul Cluster를 간단하게 배포해보았다. 다음주에는 네트워킹을 주제로 찾아올 예정이다.

    `,23);function V(U,j){const s=p("ExternalLinkIcon");return o(),i("div",null,[r,n("ul",null,[n("li",null,[a("[AWS] EKS Workshop - "),n("a",u,[a("링크"),e(s)]),a(" / Github - "),n("a",k,[a("링크"),e(s)]),a(" / Blog - "),n("a",d,[a("링크"),e(s)]),a(" / Youtube - "),n("a",m,[a("링크"),e(s)]),a(),n("a",b,[a("Streams"),e(s)])]),n("li",null,[a("[한글] EKS 웹 앱 구축 - "),n("a",v,[a("링크"),e(s)]),a(" / (Old) Amazon EKS 워크샵 - "),n("a",g,[a("링크"),e(s)])]),n("li",null,[a("[한글/whchoi98] EKS Hands On LAB - "),n("a",h,[a("링크"),e(s)])]),n("li",null,[a("[AWS] EKS Terraform Workshop - "),n("a",y,[a("링크"),e(s)]),a(" / Youtube - "),n("a",f,[a("링크"),e(s)])]),n("li",null,[a("[AWS] EKS Best Practices Guides - "),n("a",S,[a("링크"),e(s)])]),n("li",null,[a("[AWS workshop studio] Running batch workloads on Amazon EKS with AWS Batch - "),n("a",E,[a("링크"),e(s)])]),n("li",null,[a("[AWS workshop studio] Manage your EKS cluster in Full-stack with CDK - "),n("a",w,[a("링크"),e(s)])]),n("li",null,[a("[AWS workshop studio] One Observability Workshop - "),n("a",A,[a("링크"),e(s)])]),n("li",null,[a("[AWS workshop studio] Web Application Hosts on EKS Workshop - "),n("a",_,[a("링크"),e(s)])]),n("li",null,[a("[한글/AWS workshop studio] AWS General Immersion Day - "),n("a",C,[a("링크"),e(s)])]),n("li",null,[a("[한글/whchoi98] AWS IAM Hands On LAB - "),n("a",P,[a("링크"),e(s)])])]),T,n("p",null,[a("다만 단점(?)이라고 할 수 있는 부분은 지원 버전이 보통 4개의 마이너 버전 지원(현재 1.22~1.26), 평균 3개월마다 새 버전 제공, 각 버전은 12개월 정도 지원한다는 것이다. "),n("a",R,[a("링크"),e(s)])]),x,K,N,n("blockquote",null,[n("p",null,[a("참고 : EKS 업데이트 캘린더 : "),n("a",I,[a("https://endoflife.date/amazon-eks"),e(s)])])]),z,O,L,W,n("blockquote",null,[n("p",null,[a("eksctl : EKS 클러스터 구축 및 관리를 하기 위한 오프소스 명령줄 도구 - "),n("a",D,[a("링크"),e(s)])])]),q,n("blockquote",null,[n("p",null,[a("필자는 개인 GitHub에 "),n("a",M,[a("개인 퍼블릭 저장소"),e(s)]),a("를 만들어서 실습을 진행하였다.")])]),G])}const B=l(c,[["render",V],["__file","01-eks-deploy.html.vue"]]),Y=JSON.parse('{"path":"/02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.html","title":"AEWS 1주차 - Amzaon EKS 설치 및 기본 사용","lang":"ko-KR","frontmatter":{"description":"AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.","tag":["Kubernetes","EKS","PKOS"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"AEWS 1주차 - Amzaon EKS 설치 및 기본 사용"}],["meta",{"property":"og:description","content":"AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/751VTo.jpg"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"AEWS 1주차 - Amzaon EKS 설치 및 기본 사용"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:tag","content":"EKS"}],["meta",{"property":"article:tag","content":"PKOS"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"AEWS 1주차 - Amzaon EKS 설치 및 기본 사용\\",\\"image\\":[\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/751VTo.jpg\\",\\"https://static.us-east-1.prod.workshops.aws/public/e7ab9b91-502d-4ada-84e2-5c8b92d8f791/static/images/10-intro/eks_architecture.svg\\",\\"https://eksctl.io/img/eksctl.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/6ulJkf.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/eObYCj.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/helm_argo.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/T5kZFT.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/aY0au9.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/argo_%E1%84%80%E1%85%AE%E1%84%8C%E1%85%A9.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/argo_%E1%84%80%E1%85%AE%E1%84%8C%E1%85%A92.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/ejor4D.jpg\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"AWS Workshop - EKS 관련","slug":"aws-workshop-eks-관련","link":"#aws-workshop-eks-관련","children":[]},{"level":2,"title":"Amazon EKS 소개","slug":"amazon-eks-소개","link":"#amazon-eks-소개","children":[]},{"level":2,"title":"Amazon EKS 클러스터 구성 방안","slug":"amazon-eks-클러스터-구성-방안","link":"#amazon-eks-클러스터-구성-방안","children":[]},{"level":2,"title":"실습환경 구성","slug":"실습환경-구성","link":"#실습환경-구성","children":[]},{"level":2,"title":"실습환경 변경","slug":"실습환경-변경","link":"#실습환경-변경","children":[]},{"level":2,"title":"샘플 애플리케이션 배포","slug":"샘플-애플리케이션-배포","link":"#샘플-애플리케이션-배포","children":[{"level":3,"title":"ArgoCD 배포","slug":"argocd-배포","link":"#argocd-배포","children":[]},{"level":3,"title":"GitHub 저장소 준비","slug":"github-저장소-준비","link":"#github-저장소-준비","children":[]},{"level":3,"title":"ArgoCD + GitHub 연동","slug":"argocd-github-연동","link":"#argocd-github-연동","children":[]},{"level":3,"title":"ArgoCD Application 생성 및 배포","slug":"argocd-application-생성-및-배포","link":"#argocd-application-생성-및-배포","children":[]}]},{"level":2,"title":"마무리","slug":"마무리","link":"#마무리","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":5.19,"words":1556},"filePathRelative":"02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

    \\n
    \\"img\\"
    img
    \\n

    필자는 기본적인 스터디내용을 이번 시리즈에 연재할 예정이며, 추가적으로 HashiCorp의 Consul, Vault 등을 샘플로 배포하며 연동하는 내용을 조금씩 다뤄볼 예정이다.

    "}');export{B as comp,Y as data}; diff --git a/assets/01-infrastructure_maturity.html-CSXVrsyb.js b/assets/01-infrastructure_maturity.html-CSXVrsyb.js new file mode 100644 index 0000000000..be6b87b8c1 --- /dev/null +++ b/assets/01-infrastructure_maturity.html-CSXVrsyb.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as i,c as a,b as e,e as r}from"./app-Bzk8Nrll.js";const n={},o=e("h1",{id:"인프라의-변화와-적응",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#인프라의-변화와-적응"},[e("span",null,"인프라의 변화와 적응")])],-1),l=e("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/HB3LMVLNi_Q",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),s=r('

    이번에는 인프라의 변화와 적응이라는 제목으로 인프라의 성숙도와 관련한 이야기를 나누고자 합니다.

    image-20200707110714298
    image-20200707110714298

    HashiCorp의 테라폼을 이야기하면서 함께 이야기되는 것은 언제나 Infrastructure as Code라는 IaC 입니다. 즉, 인프라는 코드로 설명되고로 테라폼은 이를 지원하는 멋진 툴입니다.

    IaC는 많은 의미를 갖고 있지만, 이 이야기를 하기에 앞서 결국 이것도 코드 이기 때문에 최근의 DevOps 같은 맥락의 이야기를 할수 밖에 없을 것 같습니다.

    사람이 과거에서 오늘날 까지 진화를 했든 우리의 인프라도 각 시기마다 적절한 운영 성숙도를 갖추었습니다.

    자동화와 그 변화에 대해

    1. Manual Everything

    image-20200701184003515
    image-20200701184003515

    첫단계에서는 매뉴얼을 사용합니다. 인프라에 대한 모든 정보와 구성 방법, 변경 방법, 기존 아키텍쳐에 대한 내용은 문서로 관리되었습니다. 여전히 엑셀 시트를 가지고 인프라 아이피와 아이디, 서비스가 어떤게 올라가 있는지 관리하는 곳도 많이 있지요. 변경에 대한 모든 사항은 문서로 남겨야 하고, 그렇지 못한다면 기억에 의존해야 합니다.

    2. Basic Automation

    File:Script Shell.png - Wikimedia Commons
    File:Script Shell.png - Wikimedia Commons

    나름의 노하우를 담아 스크립팅 언어를 사용하여 작업합니다. 새로운 서버를 설정하거나 기능을 수행하기위한 작업으로 상당히 간단하고 편리합니다. 이단계에서는 비슷한 인프라나 애플리케이션 런타임이 일반적이고 조직간에 많은 의사 소통이 필요하지 않기 때문에 상당히 유용합니다. 일단 노가다를 많이 줄여줍니다. 특징은 다음과 같습니다.

    3. Machine Virtualization

    image-20200706221237156
    image-20200706221237156

    이제 이미 설정된 머신을 가상화를 통해 미리 저장해두고 사용합니다. 미리 필요한 패키지나 솔루션을 설치해두고 바로 사용 가능합니다. 미리 이미지들을 만드는 작업을 수행하기 때문에 가상머신을 사용하면 자동화를 향상시킬 수 있고 공동작업도 향상됩니다.

    4. Commoditization of Infrastructure

    img
    img

    다음은 이제 우리가 기본적으로 컨트롤 가능한 클라우드 리소스와 함께하는 시대입니다. 인프라를 더이상 소유하지 않고 상품화된 인프라, 데이터 센터를 사용하기 위해서 우리는 자동화를 요구받기 시작합니다. API를 통해 더 많은 기술 계층을 가상화 하고 더 많은 소프트웨어와 자동화 기술로 지금의 데이터 센터의 컴퓨터를 포함한 인프라를 대신하는 환경을 제공합니다.

    5. Datacenter as Computer

    image-20200706222116243
    image-20200706222116243

    이제 더 나아가 머신이 아닌 OS를 가상화 하기 시작합니다. 데이터 센터의 컴퓨터를 사용하는 대신 컴퓨터를 자원 풀의 하나로 여기고, 어디인지는 몰라도 애플리케이션을 적당한 리소스가 있는 그 어딘가에 배포합니다. 리소스 활용율을 높이고 이전 보다 더더더 자동화합니다. Agile이 요구되고 DevOps를 해야한다고 합니다.

    자동화 딜레마

    image-20200707180213752
    image-20200707180213752

    더 많은 자동화를 하면 이론적으로 우리는 더 적은 일을 해야합니다. 하지만 전반적으로 자동화의 단계를 살펴보면 이론적으로 가상화를 늘리고 컨테이너화를 하는 것은 우리의 삶을 더 좋게 만들어야 하는데, 정말 그런가요? 때때로 자동화는 정말 무수히 많은 반복과 검증 작업이 필요하고 이전보다 더 많은 시간과 노력을 요구합니다. 자동화나 DevOps에대한 장난스럽지만 마음을 후비는 글들도 넘쳐나죠. 자동화를 했음에도 불구하고 다시 새로운 플랜을 실행해야 하는 상황이나 여전히 우리가 인프라를 대하는 마음가짐이 하드웨어로 바라보고 있는 시각 처럼 말입니다.

    image-20200707085213583
    image-20200707085213583

    아마도 유발하라리의 소설 사피엔스를 재미있게 보신 분들은 호모사피엔스 뿐만아니라 다른 종도 함께 살았었다는 흥미로운 가정을 하는 이야기를 보셨을 것입니다. (다른종을 멸종시켰다고..) 그리고 소설에서의 이야기 처럼 앞서 다룬 자동화외에도 수십, 혹은 수백가지의 자동화를 위한 내 한몸 편하고자 하는 몸부림이 현 시점에도 병렬로 존재합니다. 퍼블릭과 프라이빗 클라우드 환경이 도입되고 있고 VM과 베어메탈 환경은 여전히 존재합니다. 더불어 더작은 엣지, IoT 인프라도 관리의 대상이 되고 있습니다.

    Infrastructure as Code

    인프라의 자동화를 이야기 함에 있어 빼뜨릴 수 없는것이 Infrastructure as Code, IoC 입니다. 코드로서의 인프라스트럭쳐, IaC가 취하는 전략이 무엇일까요? 다년간의 경험을 가진 팀이 보유한 시스템 환경을 코드로 바꾸면 무엇이 달라질까요?

    그럼 몇가지 상황을 제시해보겠습니다.

    코드로 인프라를 관리한다는 것은 자유롭게 변경하고 환경을 이해하고 반복적으로 동일한 상황으로 만들 수 있다는 점입니다. 그리고 그 명세를 별도의 문서로 정리할 필요 없이 명확하게 인프라가 정의되어 남아있습니다.

    image-20200707091154907
    image-20200707091154907

    좋은 코드

    우선 잘 만들어지는, 좋은 인프라 자동화를 이야기 하기 앞서 그 조건을 만드는 좋은 코드의 특징을 찾아보고 몇가지 공통된 항목을 뽑아보고 다음과 같이 정리해보았습니다.

    이외에도 몇가지 더 있겠지만 이런 좋은 코드의 특징과 IaC가 어떤 관계가 있을까요? 인프라도 마치 좋은 코드처럼 관리가 가능하다는 것입니다. 앞서 자동화를 위해 문서화 했어야 했고, 종속성을 분석하여 관리하고 인프라 자원의 변경이 있을때마다 변경하고 그 결과물, 혹은 결과물을 만들어내는 도구를 관리하고 다시 사용할 수 있게 만드는 것. 그것은 좋은 코드와 좋은 인프라 자동화 방식이 같은 맥락으로 이어질 수 있도록 만들어주는 IaC의 특징입니다. 하지만 '좋은' 이라는 수식이 붙는 인프라의 자동화가 그저 쉽게만 되지는 않을 수 있습니다. 물론 인프라를 위한 좋은 코드는 연습이 필요합니다.

    IaC starting with Terraform

    인프라 프로비저닝의 최우선 목표는 재현 가능한 인프라를 코드로 제공하는 것입니다. DevOps팀이 CI/CD 워크플로우 내에서 익숙한 도구를 사용하여 리소스를 계획하고 프로비저닝을 할 수 있는 방법을 제공하는 것입니다. Terraform은 DevOps 팀이나 클라우드 팀에서 구성한 아키텍쳐를 코드로 템플릿화 하고 기본적인 리소스와 세분화된 프로비저닝을 처리할 수 있습니다. 이런 구성은 주요 인프라 관리 도구와 통합되고 모니터링, APM 시스템, 보안 도구, 네트워크 등을 포함하여 다른 많은 ISV 공급자의 서비스로 확장 할 수 있습니다. 정의된 템플릿은 자동화된 방식으로 필요에 따라 프로비저닝 하고, 이를 통해 Terraform은 퍼블릭과 프라이빗 클라우드에 리소스를 프로비저닝 하는 공통 워크플로우를 생성합니다.

    Terraform Enterprise Workflow

    그리고 엔터프라이즈 환경을 위한 지원은 무엇이 있을까요? Terraform Enterprise는 오픈 소스의 코드 프로비저닝으로 인프라 스트럭처에서 협업, 거버넌스 및 셀프 서비스 워크 플로우를 제공합니다. Terraform Enterprise는 팀이 협력하여 인프라를 구축 할 수 있도록 워크스페이스, 모듈과 모듈 레지스트리, 거버넌스를 위한 구성을 제공합니다.

    모듈의 활용은 기존에 티켓 방식으로 수행하던 인프라 프로비저닝 워크플로우에서 인프라를 재사용 가능한 모듈에 코드로 패키지하여 개발자가 셀프 서비스 방식으로 신속하게 프로비저닝 할 수 있는 환경을 만들어 줍니다.

    그리고 프로비저닝과 관련한 정책 및 코드 로깅을 통해 조직은 전체 배포를 보호, 관리 및 감사로그를 확인 할 수 있습니다.

    Conclusion

    끊임없이 성장하고 확장되는 인프라 환경에서 Infrastructure as Code라는 아이디어가 탄생했습니다. IaC에 기반한 가치는 관리되는 프로비저닝으로 기존 레거시 환경과 퍼블릭/프라이빗 클라우드에 대한 비용을 최적화하고 기존 대비 빠른 속도와 위험 감소라는 측면에서 지금의 우리가 맞이하고 있는 현 시대에 적합한 모델이라고 볼 수 있습니다. DevOps의 핵심 구성요소기기도 하나 좋은 코드가 좋은 인프라를 만드는 것처럼 품질관릴와 체계적인 거버넌스를 구성하기 위한 노력이 반드시 필요한 영역입니다.

    ',49),c=[o,l,s];function m(p,u){return i(),a("div",null,c)}const d=t(n,[["render",m],["__file","01-infrastructure_maturity.html.vue"]]),f=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.html","title":"인프라의 변화와 적응","lang":"ko-KR","frontmatter":{"description":"인프라의 변화와 적응","tag":["terraform","IaC"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"인프라의 변화와 적응"}],["meta",{"property":"og:description","content":"인프라의 변화와 적응"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200707110714298.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"인프라의 변화와 적응"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"IaC"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"인프라의 변화와 적응\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200707110714298.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200701184003515.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/SBSEFUIa2AnSd8xKoXo-8J1HplKEGMY9UuFkqABcnfNkxjuSTg8yVk-C9WNFVSMUYaUdud_xjLoqPBp07hvCEYztIqveyuhoUWLZpXm694Ptp8y_mwMQbYIXq-NhVVZ_9ansWOi3_t0.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200706221237156.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/ppkX01eIvJ1Qn1dYKIMr0ckfCWyeYQOiNXFGaMZJkoj1oC1XidCnwhbiUbeeKpbKkLasVSr0UvVQa49u4OAOXgEJv0u3CxbnOu7Pg61tpSJXwXG5aKck411ixrEF9SwnQDDb_oWVkbI.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200706222116243.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200707180213752.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200707085213583.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/image-20200707091154907.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"자동화와 그 변화에 대해","slug":"자동화와-그-변화에-대해","link":"#자동화와-그-변화에-대해","children":[{"level":3,"title":"1. Manual Everything","slug":"_1-manual-everything","link":"#_1-manual-everything","children":[]},{"level":3,"title":"2. Basic Automation","slug":"_2-basic-automation","link":"#_2-basic-automation","children":[]},{"level":3,"title":"3. Machine Virtualization","slug":"_3-machine-virtualization","link":"#_3-machine-virtualization","children":[]},{"level":3,"title":"4. Commoditization of Infrastructure","slug":"_4-commoditization-of-infrastructure","link":"#_4-commoditization-of-infrastructure","children":[]},{"level":3,"title":"5. Datacenter as Computer","slug":"_5-datacenter-as-computer","link":"#_5-datacenter-as-computer","children":[]},{"level":3,"title":"자동화 딜레마","slug":"자동화-딜레마","link":"#자동화-딜레마","children":[]}]},{"level":2,"title":"Infrastructure as Code","slug":"infrastructure-as-code","link":"#infrastructure-as-code","children":[{"level":3,"title":"좋은 코드","slug":"좋은-코드","link":"#좋은-코드","children":[]},{"level":3,"title":"IaC starting with Terraform","slug":"iac-starting-with-terraform","link":"#iac-starting-with-terraform","children":[]},{"level":3,"title":"Terraform Enterprise Workflow","slug":"terraform-enterprise-workflow","link":"#terraform-enterprise-workflow","children":[]}]},{"level":2,"title":"Conclusion","slug":"conclusion","link":"#conclusion","children":[]}],"git":{"createdTime":1640262000000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.69,"words":208},"filePathRelative":"04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.md","localizedDate":"2021년 12월 23일","excerpt":"\\n\\n

    이번에는 인프라의 변화와 적응이라는 제목으로 인프라의 성숙도와 관련한 이야기를 나누고자 합니다.

    \\n
    \\"image-20200707110714298\\"
    image-20200707110714298
    "}');export{d as comp,f as data}; diff --git a/assets/01-kops-on-aws.html-ROAC0cUk.js b/assets/01-kops-on-aws.html-ROAC0cUk.js new file mode 100644 index 0000000000..c817d8dbf9 --- /dev/null +++ b/assets/01-kops-on-aws.html-ROAC0cUk.js @@ -0,0 +1,209 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as i,c as p,b as n,d as s,a as t,e as a}from"./app-Bzk8Nrll.js";const c={},r=a('

    [PKOS] 1편 - AWS Kops 설치 및 기본 사용

    💡 본 글은 PKOS(Production Kubernetes Online Study) 2기 스터디의 일부로 작성된 내용입니다.
    실제 Production Kubernetes 환경에서 활용 가능한 다양한 정보를 전달하기 위한 시리즈로 작성 예정입니다.

    1. 실습환경 사전준비

    본 스터디는 AWS 환경에서 Kops(Kubernetes Operations)를 활용한 실습으로 진행할 예정입니다.

    📌 참고 : 필자는 개인적인 이유로 Route 53 계정과, kOps 클러스터 운영 계정을 나눠서 진행합니다.
    하나의 계정에서 실습을 진행할 경우에는 사전 환경구성이 다를 수 있는 점 참고 부탁드립니다.

    1) Route 53 도메인 구매

    ',6),u={href:"https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/Welcome.html",target:"_blank",rel:"noopener noreferrer"},d=n("br",null,null,-1),m=n("code",null,"hyungwook.link",-1),k=n("code",null,"상태: 도메인 등록 진행 중",-1),v=a(`
    image-20230305211458121
    image-20230305211458121

    구입 시 등록했던 이메일 계정으로 발송된 verify 메일 링크를 클릭하여 활성화 합니다.

    (1) Route53 Verify mail

    image-20230305211833451
    image-20230305211833451

    일정시간이 지나면 정상적으로 도메인이 활성화 되된 것을 확인할 수 있습니다.

    (2) 도메인 등록완료 화면

    image-20230305212617366
    image-20230305212617366
    # 자신의 도메인에 NS 타입 조회
    +# dig ns <구입한 자신의 도메인> +short
    +dig ns hyungwook.link +short
    +ns-939.awsdns-53.net.
    +ns-1575.awsdns-04.co.uk.
    +ns-233.awsdns-29.com.
    +ns-1466.awsdns-55.org.
    +

    2) Route 53 등록

    필자는 서두에서 언급한 것 처럼 Route 53 구매한 계정과, kOps 클러스터를 생성할 계정이 다르므로 다음과 같은 과정을 추가적으로 수행하였습니다.

    (2) Route 53을 구매한 AWS 계정 : NS 레코드 등록

    image-20230305223503518
    image-20230305223503518
    `,15),b={href:"https://theburningmonk.com/2021/05/how-to-manage-route53-hosted-zones-in-a-multi-account-environment/",target:"_blank",rel:"noopener noreferrer"},g=a(`

    2. Kops 클러스터 배포 전 사전준비

    이제 실습에서 사용할 도메인 준비가 완료되었으므로, Kops 클러스터 생성을 위한 준비 단계로 넘어갑니다.

    1) AWS Credentials 설정

    # IAM User 자격 구성 : 실습 편리를 위해 administrator 권한을 가진 IAM User 의 자격 증명 입력
    +aws configure
    +

    2) S3 버킷 생성

    # k8s 설정 파일이 저장될 버킷 생성
    +## aws s3 mb s3://버킷<유일한 이름> --region <S3 배포될 AWS 리전>
    +aws s3 mb s3://버킷<유일한 이름> --region $REGION
    +aws s3 ls
    +
    +## 예시)
    +aws s3 mb s3://hyungwook-k8s-s3 --region ap-northeast-2
    +

    3. 클러스터 배포

    # 변수설정
    +export AWS_PAGER=""
    +export REGION=ap-northeast-2
    +export KOPS_CLUSTER_NAME=pkos.hyungwook.link
    +export KOPS_STATE_STORE=s3://hyungwook-k8s-s3
    +echo 'export AWS_PAGER=""' >>~/.bashrc
    +echo 'export REGION=ap-northeast-2' >>~/.bashrc
    +echo 'export KOPS_CLUSTER_NAME=pkos.hyungwook.link' >>~/.bashrc
    +echo 'export KOPS_STATE_STORE=s3://hyungwook-k8s-s3' >>~/.bashrc
    +
    +# kops 설정 파일 생성(s3) 및 k8s 클러스터 배포 : 6분 정도 소요
    +## CNI는 aws vpc cni 사용, 마스터 노드 1대(t3.medium), 워커 노드 2대(t3.medium), 파드 사용 네트워크 대역 지정(172.30.0.0/16)
    +## --container-runtime containerd --kubernetes-version 1.24.0 ~ 1.25.6
    +kops create cluster --zones="$REGION"a,"$REGION"c --networking amazonvpc --cloud aws \\
    +--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \\
    +--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version "1.24.10" --dry-run -o yaml > mykops.yaml
    +
    +kops create cluster --zones="$REGION"a,"$REGION"c --networking amazonvpc --cloud aws \\
    +--master-size t3.medium --node-size t3.medium --node-count=2 --network-cidr 172.30.0.0/16 \\
    +--ssh-public-key ~/.ssh/id_rsa.pub --name=$KOPS_CLUSTER_NAME --kubernetes-version "1.24.10" -y
    +

    1) kOps 클러스터 배포 시 Route 53 A 레코드 화면(등록중)

    A 레코드값이 자동으로 추가된 모습을 확인할 수 있습니다. 하지만 실제 api 서버와 내부 controller의 IP 주소가 등록되지 않았기 때문에, 실제 클러스터가 정상적으로 구성된 이후에는 자동으로 A 레코드가 업데이트 됩니다.

    image-20230305223938653
    image-20230305223938653
    aws route53 list-resource-record-sets --hosted-zone-id "\${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq
    +
    +[
    +  "api.pkos.hyungwook.link.",
    +  "api.internal.pkos.hyungwook.link.",
    +  "kops-controller.internal.pkos.hyungwook.link."
    +]
    +

    이때, kops validate 명령으로 확인하면 아직까지 api.pkos.hyungwook.link 가 relov 되지 않는 것을 확인할 수 있습니다.

    kops validate cluster --wait 10m
    +Validating cluster pkos.hyungwook.link
    +
    +W0305 22:38:08.780600    4256 validate_cluster.go:184] (will retry): unexpected error during validation: unable to resolve Kubernetes cluster API URL dns: lookup api.pkos.hyungwook.link: no such host
    +W0305 22:38:18.788067    4256 validate_cluster.go:184] (will retry): unexpected error during validation: unable to resolve Kubernetes cluster API URL dns: lookup api.pkos.hyungwook.link: no such host
    +

    어느정도 시간이 지난 후 정상적으로 A 레코드 값이 변경된 것을 확인할 수 있습니다.

    2) kOps 클러스터 배포 시 Route 53 A 레코드 화면(등록완료 )

    image-20230306000758876
    image-20230306000758876
    # A 레코드 및 값 반복조회
    +while true; do aws route53 list-resource-record-sets --hosted-zone-id "\${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done
    +[
    +  {
    +    "Name": "api.pkos.hyungwook.link.",
    +    "Type": "A",
    +    "TTL": 60,
    +    "ResourceRecords": [
    +      {
    +        "Value": "43.201.33.161"
    +      }
    +    ]
    +  },
    +  {
    +    "Name": "api.internal.pkos.hyungwook.link.",
    +    "Type": "A",
    +    "TTL": 60,
    +    "ResourceRecords": [
    +      {
    +        "Value": "172.30.37.41"
    +      }
    +    ]
    +  },
    +  {
    +    "Name": "kops-controller.internal.pkos.hyungwook.link.",
    +    "Type": "A",
    +    "TTL": 60,
    +    "ResourceRecords": [
    +      {
    +        "Value": "172.30.37.41"
    +      }
    +    ]
    +  }
    +]
    +202335일 일요일 224146초 KST
    +

    이제 정상적으로 A 레코드가 등록된 것을 확인할 수 있으며 설치가 자동으로 진행됩니다.

    3) kops validate cluster 명령(생성확인)

    실제 kops 클러스터가 정상적으로 배포된 것을 확인할 수 있습니다.

    kops validate cluster
    +Validating cluster pkos.hyungwook.link
    +
    +INSTANCE GROUPS
    +NAME			ROLE	MACHINETYPE	MIN	MAX	SUBNETS
    +master-ap-northeast-2a	Master	t3.medium	1	1	ap-northeast-2a
    +nodes-ap-northeast-2a	Node	t3.medium	1	1	ap-northeast-2a
    +nodes-ap-northeast-2c	Node	t3.medium	1	1	ap-northeast-2c
    +
    +NODE STATUS
    +NAME			ROLE	READY
    +i-089062ff9f50789ee	node	True
    +i-096a645be0dd932b6	node	True
    +i-0dce8997b4633b806	master	True
    +
    +Your cluster pkos.hyungwook.link is ready
    +

    📌 참고 : Kops 클러스터 kubeconfig 파일 업데이트 명령

    # 권한이 없을 경우
    +kubectl get nodes -o wide
    +error: You must be logged in to the server (Unauthorized)
    +
    +# kubeconfig 업데이트
    +kops export kubeconfig --name pkos.hyungwook.link --admin
    +

    4. 샘플 애플리케이션 배포

    1) Service / Pod with CLB : Mario 게임

    # 수퍼마리오 디플로이먼트 배포
    +curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/1/mario.yaml
    +kubectl apply -f mario.yaml
    +cat mario.yaml | yh
    +deployment.apps/mario created
    +service/mario created
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: mario
    +  labels:
    +    app: mario
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: mario
    +  template:
    +    metadata:
    +      labels:
    +        app: mario
    +    spec:
    +      containers:
    +      - name: mario
    +        image: pengbai/docker-supermario
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +   name: mario
    +spec:
    +  selector:
    +    app: mario
    +  ports:
    +  - port: 80
    +    protocol: TCP
    +    targetPort: 8080
    +  type: LoadBalancer
    +
    # 배포 확인 : CLB 배포 확인 >> 5분 이상 소요
    +kubectl get deploy,svc,ep mario
    +watch kubectl get svc mario
    +
    +# Watch 명령 실행 후 <pending>
    +Every 2.0s: kubectl get svc mario                                                                                                     hyungwook-Q9W5QX7FGY: Sat Mar 11 21:50:41 2023
    +NAME    TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
    +mario   LoadBalancer   100.67.138.178   <pending>     80:30624/TCP   92s
    +
    +# External-IP 할당
    +kubectl get svc mario
    +NAME    TYPE           CLUSTER-IP       EXTERNAL-IP                                                                   PORT(S)        AGE
    +mario   LoadBalancer   100.67.138.178   a643cc3e6e2c54ed8989c95d0481f48c-113657418.ap-northeast-2.elb.amazonaws.com   80:30624/TCP   3m7s
    +
    # 마리오 게임 접속 : CLB 주소로 웹 접속
    +kubectl get svc mario -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" | awk '{ print "Maria URL = http://"$1 }'
    +
    +# 결과 값
    +Maria URL = http://a643cc3e6e2c54ed8989c95d0481f48c-113657418.ap-northeast-2.elb.amazonaws.com
    +
    image-20230311215543207
    image-20230311215543207

    5. (추가) External DNS

    External DNS은 K8s Service / Ingress 생성 시 도메인을 설정하면 자동으로 AWS Route53의 A 레코드(TXT 레코드)에 자동 생성/삭제를 제공합니다.

    `,39),h={href:"https://edgehog.blog/a-self-hosted-external-dns-resolver-for-kubernetes-111a27d6fc2c",target:"_blank",rel:"noopener noreferrer"},_=a(`
    img
    img

    1) External DNS - Addon 설치

    # 정책 생성 -> 마스터/워커노드에 정책 연결
    +curl -s -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/AKOS/externaldns/externaldns-aws-r53-policy.json
    +aws iam create-policy --policy-name AllowExternalDNSUpdates --policy-document file://externaldns-aws-r53-policy.json
    +
    +# ACCOUNT_ID 변수 지정
    +export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
    +
    +# EC2 instance profiles 에 IAM Policy 추가(attach)
    +aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --role-name masters.$KOPS_CLUSTER_NAME
    +aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --role-name nodes.$KOPS_CLUSTER_NAME
    +
    +# 설치
    +kops edit cluster
    +--------------------------
    +spec:
    +  certManager:    # 없어도됨!
    +    enabled: true # 없어도됨!
    +  externalDns:
    +    provider: external-dns
    +--------------------------
    +
    +# 업데이트 적용
    +kops update cluster --yes && echo && sleep 3 && kops rolling-update cluster
    +
    +# externalDns 컨트롤러 파드 확인
    +kubectl get pod -n kube-system -l k8s-app=external-dns
    +NAME                          READY   STATUS    RESTARTS   AGE
    +external-dns-5bc8fcf8-7vznp   1/1     Running   0          14s
    +

    2) Mario 서비스에 도메인 연결

    # CLB에 ExternanDNS 로 도메인 연결
    +kubectl annotate service mario "external-dns.alpha.kubernetes.io/hostname=mario.$KOPS_CLUSTER_NAME"
    +

    (1) Route 53 A 레코드 등록 확인

    Mario 도메인
    Mario 도메인

    (2) 등록된 A 레코드에 대한 도메인 체크

    `,8),y={href:"https://www.whatsmydns.net/",target:"_blank",rel:"noopener noreferrer"},w=a(`
    도메인 체크
    도메인 체크
    # external-dns 등록로그 확인
    +kubectl logs -n kube-system -l k8s-app=external-dns
    +
    +time="2023-03-11T14:54:51Z" level=info msg="Applying provider record filter for domains: [pkos.hyungwook.link. .pkos.hyungwook.link.]"
    +time="2023-03-11T14:54:51Z" level=info msg="All records are already up to date"
    +...(생략)
    +
    +# 확인
    +dig +short mario.$KOPS_CLUSTER_NAME
    +
    +# 웹 접속 주소 확인 및 접속
    +echo -e "Maria Game URL = http://mario.$KOPS_CLUSTER_NAME"
    +
    +# 도메인 체크
    +echo -e "My Domain Checker = https://www.whatsmydns.net/#A/mario.$KOPS_CLUSTER_NAME"
    +

    6. 마무리

    1) 리소스 삭제

    kubectl delete deploy,svc mario
    +
    kops delete cluster --yes
    +

    본 편에서는 Kops 클러스터를 구성방안 및 External DNS를 연동한 외부 서비스 노출에 대한 방법을 살펴보았습니다.

    다음편에서는 네트워크 및 스토리지에 대한 활용방안을 살펴보겠습니다.

    `,11);function f(q,x){const e=l("ExternalLinkIcon");return i(),p("div",null,[r,n("p",null,[s("AWS의 DNS 웹 서비스인 "),n("a",u,[s("Route 53"),t(e)]),s("을 통해 도메인을 구입합니다."),d,s(" 필자는 "),m,s(" 도메인을 구입하였으며, 초기 구입 후 "),k,s(" 인 화면을 확인할 수 있습니다,")]),v,n("blockquote",null,[n("p",null,[s("📌 참고 : "),n("a",b,[s("How to manage Route53 hosted zones in a multi-account environment"),t(e)])])]),g,n("ul",null,[n("li",null,[s("이미지 참고 "),n("a",h,[s("링크"),t(e)])])]),_,n("ul",null,[n("li",null,[s("사이트를 통한 확인 - "),n("a",y,[s("참고"),t(e)])])]),w])}const E=o(c,[["render",f],["__file","01-kops-on-aws.html.vue"]]),P=JSON.parse('{"path":"/02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.html","title":"[PKOS] 1편 - AWS Kops 설치 및 기본 사용","lang":"ko-KR","frontmatter":{"description":"AWS Kops 설치 및 기본 사용","tag":["Kubernetes","Kops","EKS","PKOS"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"[PKOS] 1편 - AWS Kops 설치 및 기본 사용"}],["meta",{"property":"og:description","content":"AWS Kops 설치 및 기본 사용"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/1_route53.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"[PKOS] 1편 - AWS Kops 설치 및 기본 사용"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:tag","content":"Kops"}],["meta",{"property":"article:tag","content":"EKS"}],["meta",{"property":"article:tag","content":"PKOS"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"[PKOS] 1편 - AWS Kops 설치 및 기본 사용\\",\\"image\\":[\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/1_route53.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/2_route53.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/3_route53.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/4_route53_서브도메인등록.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/5_route53_호스팅영영_등록중.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/6_route53_호스팅영영_등록완료.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/9_CLA_마리오게임_접속.png\\",\\"https://miro.medium.com/v2/resize:fit:1400/0*HoU4pgcDE10AVTAu.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/ukvLhO.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/jGLPD7.jpg\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1. 실습환경 사전준비","slug":"_1-실습환경-사전준비","link":"#_1-실습환경-사전준비","children":[{"level":3,"title":"1) Route 53 도메인 구매","slug":"_1-route-53-도메인-구매","link":"#_1-route-53-도메인-구매","children":[]},{"level":3,"title":"2) Route 53 등록","slug":"_2-route-53-등록","link":"#_2-route-53-등록","children":[]}]},{"level":2,"title":"2. Kops 클러스터 배포 전 사전준비","slug":"_2-kops-클러스터-배포-전-사전준비","link":"#_2-kops-클러스터-배포-전-사전준비","children":[{"level":3,"title":"1) AWS Credentials 설정","slug":"_1-aws-credentials-설정","link":"#_1-aws-credentials-설정","children":[]},{"level":3,"title":"2) S3 버킷 생성","slug":"_2-s3-버킷-생성","link":"#_2-s3-버킷-생성","children":[]}]},{"level":2,"title":"3. 클러스터 배포","slug":"_3-클러스터-배포","link":"#_3-클러스터-배포","children":[{"level":3,"title":"1) kOps 클러스터 배포 시 Route 53 A 레코드 화면(등록중)","slug":"_1-kops-클러스터-배포-시-route-53-a-레코드-화면-등록중","link":"#_1-kops-클러스터-배포-시-route-53-a-레코드-화면-등록중","children":[]},{"level":3,"title":"2) kOps 클러스터 배포 시 Route 53 A 레코드 화면(등록완료 )","slug":"_2-kops-클러스터-배포-시-route-53-a-레코드-화면-등록완료","link":"#_2-kops-클러스터-배포-시-route-53-a-레코드-화면-등록완료","children":[]},{"level":3,"title":"3) kops validate cluster 명령(생성확인)","slug":"_3-kops-validate-cluster-명령-생성확인","link":"#_3-kops-validate-cluster-명령-생성확인","children":[]}]},{"level":2,"title":"4. 샘플 애플리케이션 배포","slug":"_4-샘플-애플리케이션-배포","link":"#_4-샘플-애플리케이션-배포","children":[{"level":3,"title":"1) Service / Pod with CLB : Mario 게임","slug":"_1-service-pod-with-clb-mario-게임","link":"#_1-service-pod-with-clb-mario-게임","children":[]}]},{"level":2,"title":"5. (추가) External DNS","slug":"_5-추가-external-dns","link":"#_5-추가-external-dns","children":[{"level":3,"title":"1) External DNS - Addon 설치","slug":"_1-external-dns-addon-설치","link":"#_1-external-dns-addon-설치","children":[]},{"level":3,"title":"2) Mario 서비스에 도메인 연결","slug":"_2-mario-서비스에-도메인-연결","link":"#_2-mario-서비스에-도메인-연결","children":[]}]},{"level":2,"title":"6. 마무리","slug":"_6-마무리","link":"#_6-마무리","children":[{"level":3,"title":"1) 리소스 삭제","slug":"_1-리소스-삭제","link":"#_1-리소스-삭제","children":[]}]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":3.52,"words":1055},"filePathRelative":"02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.md","localizedDate":"2023년 9월 18일","excerpt":"\\n
    \\n

    💡 본 글은 PKOS(Production Kubernetes Online Study) 2기 스터디의 일부로 작성된 내용입니다.
    \\n실제 Production Kubernetes 환경에서 활용 가능한 다양한 정보를 전달하기 위한 시리즈로 작성 예정입니다.

    \\n
    \\n

    1. 실습환경 사전준비

    \\n

    본 스터디는 AWS 환경에서 Kops(Kubernetes Operations)를 활용한 실습으로 진행할 예정입니다.

    \\n
    \\n

    📌 참고 : 필자는 개인적인 이유로 Route 53 계정과, kOps 클러스터 운영 계정을 나눠서 진행합니다.
    \\n하나의 계정에서 실습을 진행할 경우에는 사전 환경구성이 다를 수 있는 점 참고 부탁드립니다.

    \\n
    "}');export{E as comp,P as data}; diff --git a/assets/01-terraform-intro.html-B1MhIda9.js b/assets/01-terraform-intro.html-B1MhIda9.js new file mode 100644 index 0000000000..b20f4263e4 --- /dev/null +++ b/assets/01-terraform-intro.html-B1MhIda9.js @@ -0,0 +1,27 @@ +import{_ as c}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as p,c as u,a as r,w as e,b as t,d as s,e as o}from"./app-Bzk8Nrll.js";const m="/assets/GUI01-BXuH5Jcw.png",h="/assets/GUI02-D7PXN0N9.png",g="/assets/GUI03-DrpUzYVO.png",v="/assets/GUI04-BArdWfmu.png",f="/assets/GUI05-D_nSi561.png",k="/assets/GUI06-CRJWTx6F.png",_="/assets/infra_tools-B1A7wyiT.png",b="/assets/cloud-provisioning-tools-Bt5prm77.png",N={},C=o('

    01. 테라폼 소개

    NCP 서버를 어떻게 프로비저닝 하죠?

    새로운 NCP의 인스턴스를 프로비저닝 할 수있는 몇 가지 다른 방법을 살펴 보겠습니다. 시작하기 전에 다음을 포함한 몇 가지 기본 정보를 수집해야합니다 (더 많은 옵션이 있습니다).

    서버 만들기 Method 1: nCloud Console (GUI)

    ',5),y=t("figure",null,[t("img",{src:m,alt:"",tabindex:"0",loading:"lazy"}),t("figcaption")],-1),S=t("figure",null,[t("img",{src:h,alt:"",tabindex:"0",loading:"lazy"}),t("figcaption")],-1),q=t("figure",null,[t("img",{src:g,alt:"",tabindex:"0",loading:"lazy"}),t("figcaption")],-1),x=t("figure",null,[t("img",{src:v,alt:"",tabindex:"0",loading:"lazy"}),t("figcaption")],-1),I=t("figure",null,[t("img",{src:f,alt:"",tabindex:"0",loading:"lazy"}),t("figcaption")],-1),T=t("figure",null,[t("img",{src:k,alt:"",tabindex:"0",loading:"lazy"}),t("figcaption")],-1),P=t("hr",null,null,-1),M=t("h2",{id:"서버-만들기-method-2-ncloud-cli",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#서버-만들기-method-2-ncloud-cli"},[t("span",null,"서버 만들기 Method 2: nCloud CLI")])],-1),L={href:"https://cli.ncloud-docs.com/docs/cli-server-createserverinstances",target:"_blank",rel:"noopener noreferrer"},A=o(`
    ncloud server createServerInstances \\
    +  --serverImageProductCode SPSW0LINUX000046 \\
    +  --serverProductCode SPSVRSTAND000003 \\
    +  --serverName ncloud-mktest
    +
    CLI Options
    파라미터 명필수 여부타입제약사항
    serverImageProductCodeConditionalStringMin:1, Max:20
    serverProductCodeNoStringMin:1, Max:20
    memberServerImageNoConditionalString
    serverNameNoStringMin:3, Max:30
    serverDescriptionNoStringMin:1, Max:1000
    loginKeyNameNoStringMin:3, Max:30
    isProtectServerTerminationNoBoolean
    serverCreateCountNoIntegerMin:1, Max:20
    serverCreateStartNoNoInteger
    internetLineTypeCodeNoStringMin:1, Max:5
    feeSystemTypeCodeNoStringMin:1, Max:5
    zoneNoNoString
    accessControlGroupConfigurationNoListNoListMin:0, Max:5
    raidTypeNameConditionalString
    userDataNoStringMin:1, Max:21847
    initScriptNoNoString
    instanceTagList.tagKeyNoString
    instanceTagList.tagValueNoString
    isVaccineInstallNoBoolean
    blockDevicePartitionList.N.mountPointNoString"/" (root) 경로로 시작하는 마운트 포인트를 입력합니다. 첫 번째 마운트 포인트는 반드시 "/" (root) 파티션이어야 합니다. "/" (root) 하위 명칭은 소문자와 숫자만 허용되며, 소문자로 시작해야합니다. OS 종류에 따라서 /root, /bin, /dev 등의 특정 키워드는 사용 불가능 할 수 있습니다.
    blockDevicePartitionList.N.partitionSizeNoStringMin : 50 GiB

    nCloud CLI는 자동화할 수 있는 스크립트 방식을 제공합니다. 하지만 이 작업을 실행하기 전에 예측할 수 있나요?

    서버 만들기 Method 3: Provision with Terraform

    `,5),w={href:"https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/resources/server",target:"_blank",rel:"noopener noreferrer"},G=o(`
    resource "ncloud_server" "server" {
    +  name = "tf-test-vm1"
    +  server_image_product_code = "SPSW0LINUX000032"
    +  server_product_code = "SPSVRSTAND000004"
    +
    +  tag_list {
    +    tag_key = "samplekey1"
    +    tag_value = "samplevalue1"
    +  }
    +
    +  tag_list {
    +    tag_key = "samplekey2"
    +    tag_value = "samplevalue2"
    +  }
    +}
    +

    Terraform 이란?

    resource "ncloud_server" "server" {
    +  name = "tf-test-vm1"
    +  server_image_product_code = "SPSW0LINUX000032"
    +  server_product_code = "SPSVRSTAND000004"
    +}
    +

    Infrastructure as Code (IaC)

    IaC (Infrastructure as Code)는 컴퓨터에서 읽을 수있는 정의 파일을 사용하여 클라우드 인프라를 관리하고 프로비저닝하는 프로세스입니다.

    실행 가능한 '문서'라고 생각하시면 됩니다.

    Infrastructure as Code는 다음과 같은 특징이 있습니다.

    다른 운영 도구들

    Native-Cloud 프로비저닝 도구들

    Terraform vs. JSON

    JSON:

    "name": "{ "Fn::Join" : [ "-", [ PilotServerName, vm ] ] }",
    +

    Terraform:

    name = "${var.PilotServerName}-vm"
    +

    Terraform 코드 (HCL)는 배우기 쉽고 읽기 쉽습니다. 또한 동등한 JSON 구성보다 50-70 % 더 간결합니다.

    NCP를 위해 Terraform을 쓰는 이유

    `,23);function U(V,z){const d=l("Tabs"),i=l("ExternalLinkIcon");return p(),u("div",null,[C,r(d,{id:"39",data:[{id:"Start"},{id:"서버 이미지 선택"},{id:"서버 설정"},{id:"인증키 설정"},{id:"네트워크 접근 설정"},{id:"최종 확인"}]},{title0:e(({value:a,isActive:n})=>[s("Start")]),title1:e(({value:a,isActive:n})=>[s("서버 이미지 선택")]),title2:e(({value:a,isActive:n})=>[s("서버 설정")]),title3:e(({value:a,isActive:n})=>[s("인증키 설정")]),title4:e(({value:a,isActive:n})=>[s("네트워크 접근 설정")]),title5:e(({value:a,isActive:n})=>[s("최종 확인")]),tab0:e(({value:a,isActive:n})=>[y]),tab1:e(({value:a,isActive:n})=>[S]),tab2:e(({value:a,isActive:n})=>[q]),tab3:e(({value:a,isActive:n})=>[x]),tab4:e(({value:a,isActive:n})=>[I]),tab5:e(({value:a,isActive:n})=>[T]),_:1}),P,M,t("blockquote",null,[t("p",null,[s("서버 생성을 위한 CLI 가이드 : "),t("a",L,[s("https://cli.ncloud-docs.com/docs/cli-server-createserverinstances"),r(i)])])]),A,t("blockquote",null,[t("p",null,[s("nCloud 서버를 생성하는 Terraform 예제 코드 : "),t("a",w,[s("https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/resources/server"),r(i)])])]),G])}const O=c(N,[["render",U],["__file","01-terraform-intro.html.vue"]]),j=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.html","title":"01. 테라폼 소개","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"01. 테라폼 소개"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"01. 테라폼 소개\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"NCP 서버를 어떻게 프로비저닝 하죠?","slug":"ncp-서버를-어떻게-프로비저닝-하죠","link":"#ncp-서버를-어떻게-프로비저닝-하죠","children":[]},{"level":2,"title":"서버 만들기 Method 1: nCloud Console (GUI)","slug":"서버-만들기-method-1-ncloud-console-gui","link":"#서버-만들기-method-1-ncloud-console-gui","children":[]},{"level":2,"title":"서버 만들기 Method 2: nCloud CLI","slug":"서버-만들기-method-2-ncloud-cli","link":"#서버-만들기-method-2-ncloud-cli","children":[]},{"level":2,"title":"서버 만들기 Method 3: Provision with Terraform","slug":"서버-만들기-method-3-provision-with-terraform","link":"#서버-만들기-method-3-provision-with-terraform","children":[]},{"level":2,"title":"Terraform 이란?","slug":"terraform-이란","link":"#terraform-이란","children":[]},{"level":2,"title":"Infrastructure as Code (IaC)","slug":"infrastructure-as-code-iac","link":"#infrastructure-as-code-iac","children":[]},{"level":2,"title":"Infrastructure as Code는 다음과 같은 특징이 있습니다.","slug":"infrastructure-as-code는-다음과-같은-특징이-있습니다","link":"#infrastructure-as-code는-다음과-같은-특징이-있습니다","children":[]},{"level":2,"title":"다른 운영 도구들","slug":"다른-운영-도구들","link":"#다른-운영-도구들","children":[]},{"level":2,"title":"Native-Cloud 프로비저닝 도구들","slug":"native-cloud-프로비저닝-도구들","link":"#native-cloud-프로비저닝-도구들","children":[]},{"level":2,"title":"Terraform vs. JSON","slug":"terraform-vs-json","link":"#terraform-vs-json","children":[]},{"level":2,"title":"NCP를 위해 Terraform을 쓰는 이유","slug":"ncp를-위해-terraform을-쓰는-이유","link":"#ncp를-위해-terraform을-쓰는-이유","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1698218919000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":0.95,"words":285},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    NCP 서버를 어떻게 프로비저닝 하죠?

    \\n

    새로운 NCP의 인스턴스를 프로비저닝 할 수있는 몇 가지 다른 방법을 살펴 보겠습니다. 시작하기 전에 다음을 포함한 몇 가지 기본 정보를 수집해야합니다 (더 많은 옵션이 있습니다).

    \\n\\n

    서버 만들기 Method 1: nCloud Console (GUI)

    \\n\\n
    \\n

    서버 만들기 Method 2: nCloud CLI

    "}');export{O as comp,j as data}; diff --git a/assets/02-Contribute.html-CujV57Pk.js b/assets/02-Contribute.html-CujV57Pk.js new file mode 100644 index 0000000000..c4d3a5f10b --- /dev/null +++ b/assets/02-Contribute.html-CujV57Pk.js @@ -0,0 +1,29 @@ +import{_ as u}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as p,c as m,b as t,d as e,a,w as i,e as r}from"./app-Bzk8Nrll.js";const g="/assets/github-fork-BUciMNP3.png",h="/assets/github-fork-target-BaBEXIw-.png",b="/assets/github-ui-fetch-DnhnUzWp.png",_="/assets/github-ui-editor-zFm2B-of.png",k="/assets/github-ui-contribute-QYbEiEDl.png",f="/assets/github-ui-create-pr-CBzZgSS0.png",v="/assets/github-ui-create-pr-detail-_QdZwIti.png",C={},y=t("h1",{id:"contribute",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#contribute"},[t("span",null,"Contribute")])],-1),w=t("p",null,"docmoa에 문서 기여하기위한 가이드를 설명합니다. 일반적인 github 상에서의 코드 기여 방식과 동일합니다.",-1),x=t("h2",{id:"git-설치-option",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#git-설치-option"},[t("span",null,"git 설치(Option)")])],-1),E=t("p",null,"로컬 환경에서 git 명령을 수행하기 위해 설치합니다. github 브라우저 환경에서 수정하는 것도 가능하지만, 로컬에서 문서를 활용하고 오프라인 작업을 위해서는 설치하시기를 권장합니다.",-1),R={href:"https://git-scm.com/book/ko/v2/%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-Git-%EC%84%A4%EC%B9%98",target:"_blank",rel:"noopener noreferrer"},A=t("li",null,"Mavericks(10.9)부터는 Terminal에 단지 처음으로 'git’을 실행하는 것으로 설치가 시작됩니다.",-1),F={href:"http://git-scm.com/download/mac",target:"_blank",rel:"noopener noreferrer"},B={href:"http://git-scm.com/download/win",target:"_blank",rel:"noopener noreferrer"},I={href:"https://desktop.github.com/",target:"_blank",rel:"noopener noreferrer"},L=t("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[t("pre",{class:"language-bash"},[t("code",null,[t("span",{class:"token function"},"sudo"),e(" dnf "),t("span",{class:"token function"},"install"),e(` git-all +`)])])],-1),z=t("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[t("pre",{class:"language-bash"},[t("code",null,[t("span",{class:"token function"},"sudo"),e(),t("span",{class:"token function"},"apt"),e(),t("span",{class:"token function"},"install"),e(` git-all +`)])])],-1),P=r('

    github Fork

    문서는 github상에서 관리됩니다. 우선 문서를 추가구성하고 수정할 수 있도록 원본 github repo를 Fork 합니다.

    1. https://github.com/docmoa/docs로 이동합니다.
    2. 우측 상단의 Fork를 클릭하고 나의 github Org를 선택합니다.
      Fork
      Fork-Target

    git Fetch or Pull

    Fork의 원본 Repo에 변경에 대해 작업중인 Repo에 변경사항을 적용해야 하는 필요성이 있습니다. 여러 편집자가 동일한 시점에 동일 문서를 편집하게 되면 편집에 충돌이 발생할 수 있습니다.

    ',5),G=t("img",{src:"https://upload.wikimedia.org/wikipedia/commons/9/97/Paragraph-based_prototype_–_rough_visualization_of_the_functionality.png",alt:"Conflict",loading:"lazy"},null,-1),U=t("br",null,null,-1),O={href:"https://en.wikipedia.org/wiki/Edit_conflict",target:"_blank",rel:"noopener noreferrer"},T=t("p",null,"충돌을 사전에 최대한 방지하기 위해서 편집 전에 원본의 문서를 가져오고 병합하는 과정이 필요합니다.",-1),D=t("p",null,"CLI 컨트롤을 위해서는 앞서 git 유틸 설치가 필요합니다.",-1),H=t("div",{class:"language-bash line-numbers-mode","data-ext":"sh","data-title":"sh"},[t("pre",{class:"language-bash"},[t("code",null,[t("span",{class:"token comment"},"# 1. Fork 받은 본인 소유의 Repo를 Clone 받습니다."),e(` +`),t("span",{class:"token function"},"git"),e(` clone https://github.com/docmoa/docs + +`),t("span",{class:"token comment"},"# 2. 해당 소스 디렉토리로 이동하여 remote 를 확인합니다."),e(` +`),t("span",{class:"token builtin class-name"},"cd"),e(` docs +`),t("span",{class:"token function"},"git"),e(" remote "),t("span",{class:"token parameter variable"},"-v"),e(` +origin https://github.com/myorg/docs.git `),t("span",{class:"token punctuation"},"("),e("fetch"),t("span",{class:"token punctuation"},")"),e(` +origin https://github.com/myorg/docs.git `),t("span",{class:"token punctuation"},"("),e("push"),t("span",{class:"token punctuation"},")"),e(` + +`),t("span",{class:"token comment"},"# 3. 문서 원본 Repo와의 병합을 위해 `upstream` repo remote를 추가합니다."),e(` +`),t("span",{class:"token function"},"git"),e(" remote "),t("span",{class:"token function"},"add"),e(` upstream https://github.com/docmoa/docs + +`),t("span",{class:"token comment"},"# 4-1. pull 을 수행하거나"),e(` +`),t("span",{class:"token function"},"git"),e(` pull upstream main + +`),t("span",{class:"token comment"},"# 4-2. fetch & merge 를 수행합니다."),e(` +`),t("span",{class:"token function"},"git"),e(` fetch upstream +`),t("span",{class:"token function"},"git"),e(` merge upstream/main +`)])]),t("div",{class:"line-numbers","aria-hidden":"true"},[t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"}),t("div",{class:"line-number"})])],-1),S=t("p",null,[e("원본 Repo에 변화가 있으면 UI상에서 상태가 알려집니다. 이 경우 우측의 "),t("code",null,"Fetch upstream"),e(" 을 통해 "),t("code",null,"Compare"),e("로 변경사항을 확인하거나 "),t("code",null,"Fetch and merge"),e("로 현재의 Repo에 병합할 수 있습니다."),t("br"),t("img",{src:b,alt:"Fetch and merge UI",loading:"lazy"})],-1),q=t("h2",{id:"git-add-and-commit",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#git-add-and-commit"},[t("span",null,[e("git "),t("code",null,"add"),e(" and "),t("code",null,"commit")])])],-1),N=t("p",null,"CLI 컨트롤을 위해서는 앞서 git 유틸 설치가 필요합니다.",-1),M=t("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[t("pre",{class:"language-bash"},[t("code",null,[t("span",{class:"token comment"},"# 1. 작성한 파일을 git의 관리 대상으로 add 합니다."),e(` +`),t("span",{class:"token function"},"git"),e(),t("span",{class:"token function"},"add"),e(` path/문서.md + +`),t("span",{class:"token comment"},"# 2. 로컬 Repo에서 변경사항을 Commit 합니다."),e(` +`),t("span",{class:"token function"},"git"),e(" commit "),t("span",{class:"token parameter variable"},"-m"),e(),t("span",{class:"token string"},'"커밋메시지를 남깁니다.(예를들어 : 문서를 수정)"'),e(` + +`),t("span",{class:"token comment"},"# 3. 원격 Repo로 변경사항을 Push 합니다."),e(` +`),t("span",{class:"token function"},"git"),e(` push origin main +`)])])],-1),Z=t("p",null,[e("github 웹 환경에서 문서를 추가하고 수정하는 것도 가능합니다."),t("br"),t("img",{src:_,alt:"github ui edit",loading:"lazy"})],-1),V=r('

    Contribute

    최종적으로 Fork 한 Repo에 docmoa에 기여할 문서가 준비가 된경우 해당 Repo의 github ui에서 커밋된 정보가 있다는 문구와 Contribute를 클릭하여 Pull request를 진행 할 수 있습니다. Open pull request를 클릭하여 Upstream Repo에 요청을 보냅니다.

    Pull request
    Pull request

    Pull request를 생성하면 본인 소유의 Repo(Branch)로 부터 docmoa에 지금까지의 변경사항을 병합할 것을 요청할 수 있습니다.

    상단에 표기되는 repo의 방향과 branch를 다시한번 확인해주세요.
    아래 어떤 항목이 어떻게 변경되는지 내용을 확인할 수 있습니다.

    create pr
    create pr

    Create pull request 버튼을 클릭하면 디테일한 설명을 추가할 수 잇습니다. 문서 자체의 변경사항만으로 의도를 전달하기 힘든경우 해당 설명이 이해하는데 큰 도움이 됩니다.

    create pr detail
    create pr detail

    이제 Pull request가 받아들여지고나면 docmoa에 기여해주신 내용이 반영됩니다.

    Build 주기

    2021년 10월 4일 기준 매 5분마다 변경사항이 있으면 docmoa의 빌드가 수행됩니다.

    ',10);function W(K,Q){const s=c("ExternalLinkIcon"),l=c("Tabs"),d=c("RouteLink");return p(),m("div",null,[y,w,x,E,t("p",null,[t("a",R,[e("Git 설치 방법 안내"),a(s)]),e("를 참고하여 아래 설명합니다.")]),a(l,{id:"15",data:[{id:"Mac"},{id:"Windows"},{id:"Linux(Fedora/RHEL/CentOS/Rocky)"},{id:"Linux(Debian/Ubuntu)"}]},{title0:i(({value:n,isActive:o})=>[e("Mac")]),title1:i(({value:n,isActive:o})=>[e("Windows")]),title2:i(({value:n,isActive:o})=>[e("Linux(Fedora/RHEL/CentOS/Rocky)")]),title3:i(({value:n,isActive:o})=>[e("Linux(Debian/Ubuntu)")]),tab0:i(({value:n,isActive:o})=>[t("ul",null,[A,t("li",null,[e("공식 배포판 : "),t("a",F,[e("http://git-scm.com/download/mac"),a(s)]),e(" 에서 다운로드 받은 설치파일을 실행하여 설치합니다.")])])]),tab1:i(({value:n,isActive:o})=>[t("ul",null,[t("li",null,[e("공식 배포판 : "),t("a",B,[e("http://git-scm.com/download/win"),a(s)]),e(" 에서 다운로드 받은 설치파일을 실행하여 설치합니다.")]),t("li",null,[e("GitHub Desktop : "),t("a",I,[e("GitHub Desktop 웹사이트"),a(s)]),e("에서 내려받아 설치합니다.")])])]),tab2:i(({value:n,isActive:o})=>[L]),tab3:i(({value:n,isActive:o})=>[z]),_:1},8,["data"]),P,t("p",null,[G,U,e(" 참고 : "),t("a",O,[e("https://en.wikipedia.org/wiki/Edit_conflict"),a(s)])]),T,a(l,{id:"81",data:[{id:"CLI base"},{id:"UI base"}]},{title0:i(({value:n,isActive:o})=>[e("CLI base")]),title1:i(({value:n,isActive:o})=>[e("UI base")]),tab0:i(({value:n,isActive:o})=>[D,H]),tab1:i(({value:n,isActive:o})=>[S]),_:1}),q,t("p",null,[e("문서 작성은 앞서 "),a(d,{to:"/00-Howto/02-Guide/01-Start.html"},{default:i(()=>[e("문서작성 '시작'")]),_:1}),e("을 참고하세요. 문서 또는 문서 작성에 필요했던 이미지 등 준비가 끝나면 해당 파일을 본인 소유의 Repo에 추가합니다. 이 때 사용하는 것은 CLI도 가능하고, UI 기반에서 작성한 경우에는 저장 즉시 해당 Repo에 저장됩니다. Commit의 경우 필요에 따라 브랜치를 별도 생성하여 본인의 Repo를 기준으로 관리하는 것 또한 가능합니다.")]),a(l,{id:"100",data:[{id:"CLI base"},{id:"UI base"}]},{title0:i(({value:n,isActive:o})=>[e("CLI base")]),title1:i(({value:n,isActive:o})=>[e("UI base")]),tab0:i(({value:n,isActive:o})=>[N,M]),tab1:i(({value:n,isActive:o})=>[Z]),_:1}),V])}const X=u(C,[["render",W],["__file","02-Contribute.html.vue"]]),Y=JSON.parse('{"path":"/00-Howto/02-Guide/02-Contribute.html","title":"Contribute","lang":"ko-KR","frontmatter":{"description":"Contribute docmoa에 문서 기여하기위한 가이드를 설명합니다. 일반적인 github 상에서의 코드 기여 방식과 동일합니다. git 설치(Option) 로컬 환경에서 git 명령을 수행하기 위해 설치합니다. github 브라우저 환경에서 수정하는 것도 가능하지만, 로컬에서 문서를 활용하고 오프라인 작업을 위...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/02-Guide/02-Contribute.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Contribute"}],["meta",{"property":"og:description","content":"Contribute docmoa에 문서 기여하기위한 가이드를 설명합니다. 일반적인 github 상에서의 코드 기여 방식과 동일합니다. git 설치(Option) 로컬 환경에서 git 명령을 수행하기 위해 설치합니다. github 브라우저 환경에서 수정하는 것도 가능하지만, 로컬에서 문서를 활용하고 오프라인 작업을 위..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://upload.wikimedia.org/wikipedia/commons/9/97/Paragraph-based_prototype_%E2%80%93_rough_visualization_of_the_functionality.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Contribute"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Contribute\\",\\"image\\":[\\"https://upload.wikimedia.org/wikipedia/commons/9/97/Paragraph-based_prototype_%E2%80%93_rough_visualization_of_the_functionality.png\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"git 설치(Option)","slug":"git-설치-option","link":"#git-설치-option","children":[]},{"level":2,"title":"github Fork","slug":"github-fork","link":"#github-fork","children":[]},{"level":2,"title":"git Fetch or Pull","slug":"git-fetch-or-pull","link":"#git-fetch-or-pull","children":[]},{"level":2,"title":"git add and commit","slug":"git-add-and-commit","link":"#git-add-and-commit","children":[]},{"level":2,"title":"Contribute","slug":"contribute-1","link":"#contribute-1","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1698218919000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":1.12,"words":335},"filePathRelative":"00-Howto/02-Guide/02-Contribute.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    docmoa에 문서 기여하기위한 가이드를 설명합니다. 일반적인 github 상에서의 코드 기여 방식과 동일합니다.

    \\n

    git 설치(Option)

    \\n

    로컬 환경에서 git 명령을 수행하기 위해 설치합니다. github 브라우저 환경에서 수정하는 것도 가능하지만, 로컬에서 문서를 활용하고 오프라인 작업을 위해서는 설치하시기를 권장합니다.

    \\n

    Git 설치 방법 안내를 참고하여 아래 설명합니다.

    ","autoDesc":true}');export{X as comp,Y as data}; diff --git a/assets/02-SideCar.html-Cll9HLhK.js b/assets/02-SideCar.html-Cll9HLhK.js new file mode 100644 index 0000000000..b7fd077a78 --- /dev/null +++ b/assets/02-SideCar.html-Cll9HLhK.js @@ -0,0 +1,176 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as d,c,b as e,d as n,a as i,e as s}from"./app-Bzk8Nrll.js";const r="/assets/02-sidecar-intention-alldeny-ogH-XLae.png",o="/assets/02-sidecar-services-BZgCIlWq.png",v="/assets/02-sidecar-error-VTAGMmH1.png",p="/assets/02-sidecar-topology-blocked-BzutBfyM.png",m="/assets/02-sidecar-topology-allow-F944RXfc.png",u="/assets/02-sidecar-200-BKyjC3GP.png",b={},h=e("h1",{id:"_02-sidecar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_02-sidecar"},[e("span",null,"02. SideCar")])],-1),g=e("div",{class:"hint-container tip"},[e("p",{class:"hint-container-title"},"팁"),e("p",null,"실습을 위한 조건은 다음과 같습니다."),e("ul",null,[e("li",null,"Consul 이 구성된 Kubernetes 환경"),e("li",null,[n("설치 구성 시 "),e("code",null,"connectInject"),n(" 이 활성화 되어있어야 합니다.")])])],-1),k={href:"https://learn.hashicorp.com/tutorials/consul/service-mesh-application-secure-networking?in=consul/kubernetes",target:"_blank",rel:"noopener noreferrer"},f=s('

    Consul 서비스 메시를 사용하면 애플리케이션을 제로 트러스트 네트워크에 배포할 수 있습니다. 제로 트러스트 네트워크는 아무 것도 자동으로 신뢰되지 않는 네트워크입니다. 모든 연결은 인증과 승인을 모두 받아야 합니다. 이 패러다임은 동일한 네트워크에서 다수의 서비스가 실행될 수 있는 마이크로서비스 및 멀티 클라우드 환경에서 중요합니다. Consul 서비스 메시를 사용하면 mTLS를 사용하여 서비스 ID를 인증하고 의도를 사용하여 서비스 작업을 승인하거나 차단할 수 있습니다.

    이 튜토리얼에서는 두 개의 서비스 webapi를 Kubernetes 클러스터에서 실행되는 Consul의 서비스 메시에 배포합니다. 두 서비스는 Consul을 사용하여 서로를 검색하고 사이드카 프록시를 사용하여 mTLS를 통해 통신합니다. 두 서비스는 웹UI와 백엔드 서비스로 구성된 간단한 2-tier 애플리케이션으로 HTTP를 통해 서비스와 통신합니다.

    Sidecar Proxy

    Sidecar Proxy는 애플리케이션 컨테이너와 함께 동일 Pod상에 배포됨으로 localhost 로 통신할 수 있습니다. 사용자가 다른서비스에 대한 요청을 Sidecar Proxy에 지정하면 해당 요청을 맵핑된 다른 서비스로 전달합니다. 이 방식은 기존 개발자가 로컬에서 개발하는 환경과 동일하게 동작합니다. UI웹을 로컬 9090포트로 실행하고 백엔드 앱을 8080 포트로 실행한경우 UI웹은 백엔드 앱을 localhost:8080 으로 호출합니다. Consul Sidecar Proxy는 localhost 로 요청되는 포트를 지정한 Upstream 서비스로 전달하는 규칙을 처리하며, 여기에는 mTLS가 자동으로 구성됩니다.

    Intention 규칙 추가

    Sidecar Proxy의 서비스 접근 제어를 위해 모든 서비스에 대한 Deny 구성을 수행합니다. UI의 좌측 메뉴의 Intention을 클릭하고 우측의 Create 버튼을 클릭하여 모든 서비스 (엔터프라이즈의 경우 모든 Namespace 포함) 간에 Deny 규칙을 생성합니다.

    Service 구성

    테스트 구성을 저장하기 위한 디렉토리를 생성합니다.

    mkdir ./k8s_config
    +

    백엔드 서비스

    cat > ./k8s_config/api.yaml <<EOF
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: api-v1
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: api-v1
    +spec:
    +  selector:
    +    app: api-v1
    +  ports:
    +    - port: 9091
    +      targetPort: 9091
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: api-v1
    +  labels:
    +    app: api-v1
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: api-v1
    +  template:
    +    metadata:
    +      labels:
    +        app: api-v1
    +      annotations:
    +        consul.hashicorp.com/connect-inject: 'true'
    +    spec:
    +      serviceAccountName: api-v1
    +      containers:
    +        - name: api
    +          image: nicholasjackson/fake-service:v0.7.8
    +          ports:
    +            - containerPort: 9091
    +          env:
    +            - name: 'LISTEN_ADDR'
    +              value: '127.0.0.1:9091'
    +            - name: 'NAME'
    +              value: 'api-v1'
    +            - name: 'MESSAGE'
    +              value: 'Response from API v1'
    +EOF
    +

    프론트엔드 서비스

    cat > ./k8s_config/web.yaml <<EOF
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: web
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: web
    +spec:
    +  selector:
    +    app: web
    +  ports:
    +    - port: 9090
    +      targetPort: 9090
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: web-deployment
    +  labels:
    +    app: web
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: web
    +  template:
    +    metadata:
    +      labels:
    +        app: web
    +      annotations:
    +        consul.hashicorp.com/connect-inject: 'true'
    +        consul.hashicorp.com/connect-service-upstreams: 'api-v1:9091'
    +    spec:
    +      serviceAccountName: web
    +      containers:
    +        - name: web
    +          image: nicholasjackson/fake-service:v0.7.8
    +          ports:
    +            - containerPort: 9090
    +          env:
    +            - name: 'LISTEN_ADDR'
    +              value: '0.0.0.0:9090'
    +            - name: 'UPSTREAM_URIS'
    +              value: 'http://localhost:9091'
    +            - name: 'NAME'
    +              value: 'web'
    +            - name: 'MESSAGE'
    +              value: 'Hello World'
    +EOF
    +

    프론트엔드 서비스에 Deployment 구성 내용의 annotation 을 확인하세요. 이 형식은 9091로 요청된 localhost로의 요청을 sidecar가 api-v1서비스로 전달하는 것을 의미합니다.

    서비스 배포 및 등록 확인

    kubectl apply 명령을 통해 배포를 확인하고 Consul UI에서 확인합니다.

    kubectl apply -f ./k8s_config/api.yaml
    +
    # 출력
    +serviceaccount/api-v1 created
    +service/api-v1 created
    +deployment.apps/api-v1 created
    +
    kubectl apply -f ./k8s_config/web.yaml
    +
    # 출력
    +serviceaccount/web created
    +service/web created
    +deployment.apps/web-deployment created
    +

    UI에 접속하고 좌측 Namespace에서 사용중인 Namespace를 확인합니다. 서비스 목록에 api-v1, web 이 등록되고 상태가 정상임을 확인합니다.

    Services - Consul 2022-02-19 19-21-21
    Services - Consul 2022-02-19 19-21-21

    port-forward를 통해 로컬에서 web 앱을 확인합니다.

    kubectl port-forward service/web 9090:9090 --address 0.0.0.0
    +
    # 출력
    +Forwarding from 0.0.0.0:9090 -> 9090
    +
    `,28),x={href:"http://localhost:9090/ui",target:"_blank",rel:"noopener noreferrer"},y=s('
    image-20220219192259588
    image-20220219192259588

    500 에러가 발생하였습니다. Consul Service Mesh는 서비스간 의도적으로 접속 가능여부를 동적으로 통제합니다. 이 기능을 Intention 이라고 부릅니다. Consul UI에 접속하여 web 서비스 이름을 클릭하면 다음과 같이 요청이 거부되어 있는것을 확인할 수 있습니다.

    Intention 수정을 위해서는 권한이 필요합니다. 현재 환경에서는 전달받은 token (3108cbb3-005c-a3e4-9a42-6f13d1f5e4e6) 을 우측 상단 로그인에서 입력합니다.

    web - Consul 2022-02-19 19-24-22
    web - Consul 2022-02-19 19-24-22

    x 표시를 클릭하여 Create 버튼을 클릭하여 Intention 규칙을 생성합니다. 이후에 연결이 허용된것을 확인할 수 있습니다.

    web - Consul 2022-02-19 19-26-03
    web - Consul 2022-02-19 19-26-03
    ',6),_={href:"http://localhost:9090/ui",target:"_blank",rel:"noopener noreferrer"},S=s('
    image-20220219192915371
    image-20220219192915371

    다음 과정을 진행하기 위해 기존 적용된 구성을 삭제합니다.

    kubectl delete -f ./k8s_config
    +
    # 출력
    +serviceaccount "api-v1" deleted
    +service "api-v1" deleted
    +deployment.apps "api-v1" deleted
    +serviceaccount "web" deleted
    +service "web" deleted
    +deployment.apps "web-deployment" deleted
    +

    With Namespace (Ent)

    백엔드 서비스를 다른 Namespace에 배포하고, 프론트엔드가 해당 백엔드에 접근할 수 있도록 수정합니다.

    프론트엔드 서비스

    cat > ./k8s_config/web.yaml <<EOF
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: web
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: web
    +spec:
    +  selector:
    +    app: web
    +  ports:
    +    - port: 9090
    +      targetPort: 9090
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: web-deployment
    +  labels:
    +    app: web
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: web
    +  template:
    +    metadata:
    +      labels:
    +        app: web
    +      annotations:
    +        consul.hashicorp.com/connect-inject: 'true'
    +        consul.hashicorp.com/connect-service-upstreams: 'api-v1.<namespace>:9091'
    +    spec:
    +      serviceAccountName: web
    +      containers:
    +        - name: web
    +          image: nicholasjackson/fake-service:v0.7.8
    +          ports:
    +            - containerPort: 9090
    +          env:
    +            - name: 'LISTEN_ADDR'
    +              value: '0.0.0.0:9090'
    +            - name: 'UPSTREAM_URIS'
    +              value: 'http://localhost:9091'
    +            - name: 'NAME'
    +              value: 'web'
    +            - name: 'MESSAGE'
    +              value: 'Hello World'
    +EOF
    +

    프론트엔드 서비스에 Deployment 구성 내용의 annotation 을 확인하세요. 이 형식은 9091로 요청된 localhost로의 요청을 sidecar가 <namespace> Namespace의 api-v1서비스로 전달하는 것을 의미합니다.

    또한 Namespace 간 Intention을 작성하여 적용해야합니다.

    정리

    참고 - Intention 순위

    범위가 좁을수록 우선순위가 높습니다.

    Source NamespaceSource NameDestination NamespaceDestination Name높을수록 서열 높음
    ExactExactExactExact9
    Exact*ExactExact8
    **ExactExact7
    ExactExactExact*6
    Exact*Exact*5
    **Exact*4
    ExactExact**3
    Exact***2
    ****1
    `,14);function w(E,C){const a=l("ExternalLinkIcon");return d(),c("div",null,[h,g,e("blockquote",null,[e("p",null,[n("참고 : "),e("a",k,[n("https://learn.hashicorp.com/tutorials/consul/service-mesh-application-secure-networking?in=consul/kubernetes"),i(a)])])]),f,e("p",null,[e("a",x,[n("http://localhost:9090/ui"),i(a)]),n(" 에 브라우저로 접속하여 상태를 확인합니다.")]),y,e("p",null,[n("다시 "),e("a",_,[n("http://localhost:9090/ui"),i(a)]),n(" 에 브라우저로 접속하여 상태를 확인합니다.")]),S])}const A=t(b,[["render",w],["__file","02-SideCar.html.vue"]]),P=JSON.parse('{"path":"/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.html","title":"02. SideCar","lang":"ko-KR","frontmatter":{"description":"Consul Service Mesh on Kubernetes (Ent)","tag":["Consul","ServiceMesh","K8s","Kubernetes"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"02. SideCar"}],["meta",{"property":"og:description","content":"Consul Service Mesh on Kubernetes (Ent)"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://learn.hashicorp.com/img/consul/web-api-proxy.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"02. SideCar"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"ServiceMesh"}],["meta",{"property":"article:tag","content":"K8s"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"02. SideCar\\",\\"image\\":[\\"https://learn.hashicorp.com/img/consul/web-api-proxy.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Sidecar Proxy","slug":"sidecar-proxy","link":"#sidecar-proxy","children":[]},{"level":2,"title":"Intention 규칙 추가","slug":"intention-규칙-추가","link":"#intention-규칙-추가","children":[]},{"level":2,"title":"Service 구성","slug":"service-구성","link":"#service-구성","children":[{"level":3,"title":"백엔드 서비스","slug":"백엔드-서비스","link":"#백엔드-서비스","children":[]},{"level":3,"title":"프론트엔드 서비스","slug":"프론트엔드-서비스","link":"#프론트엔드-서비스","children":[]}]},{"level":2,"title":"서비스 배포 및 등록 확인","slug":"서비스-배포-및-등록-확인","link":"#서비스-배포-및-등록-확인","children":[]},{"level":2,"title":"With Namespace (Ent)","slug":"with-namespace-ent","link":"#with-namespace-ent","children":[{"level":3,"title":"프론트엔드 서비스","slug":"프론트엔드-서비스-1","link":"#프론트엔드-서비스-1","children":[]}]},{"level":2,"title":"정리","slug":"정리","link":"#정리","children":[]}],"git":{"createdTime":1645936869000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":2.14,"words":641},"filePathRelative":"04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.md","localizedDate":"2022년 2월 27일","excerpt":"\\n
    \\n

    \\n

    실습을 위한 조건은 다음과 같습니다.

    \\n\\n
    \\n
    \\n

    참고 : https://learn.hashicorp.com/tutorials/consul/service-mesh-application-secure-networking?in=consul/kubernetes

    \\n
    "}');export{A as comp,P as data}; diff --git a/assets/02-Thanks.html-BWdU5ZFB.js b/assets/02-Thanks.html-BWdU5ZFB.js new file mode 100644 index 0000000000..6a54dd71bc --- /dev/null +++ b/assets/02-Thanks.html-BWdU5ZFB.js @@ -0,0 +1 @@ +import{_ as c}from"./plugin-vue_export-helper-DlAUqK2U.js";import{i as l,j as u,o,c as e,b as t,F as h,g as d,d as p,t as m}from"./app-Bzk8Nrll.js";const g={props:{owner:{type:String,required:!0},repo:{type:String,required:!0}},setup(s){const a=l([]);return u(async()=>{try{const r=await fetch("https://api.github.com/repos/docmoa/docs/contributors");a.value=await r.json()}catch(r){console.error("Failed to fetch contributors:",r)}}),{contributors:a}}},_=t("h1",{id:"thank-you",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#thank-you"},[t("span",null,"Thank you")])],-1),b=t("h3",null,"Contributors:",-1),y={key:0},k=["href"],f=["src"],T={key:1};function v(s,a,r,i,x,w){return o(),e("div",null,[_,t("div",null,[b,i.contributors.length?(o(),e("ul",y,[(o(!0),e(h,null,d(i.contributors,n=>(o(),e("li",{key:n.id},[t("a",{href:n.html_url,target:"_blank"},[t("img",{src:n.avatar_url,alt:"avatar",width:"30"},null,8,f),p(" "+m(n.login),1)],8,k)]))),128))])):(o(),e("p",T,"Loading contributors..."))])])}const C=c(g,[["render",v],["__file","02-Thanks.html.vue"]]),j=JSON.parse('{"path":"/99-about/02-Thanks.html","title":"Thank you","lang":"ko-KR","frontmatter":{"description":"Thank you Contributors: avatar {{ contributor.login }} Loading contributors... ","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/99-about/02-Thanks.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Thank you"}],["meta",{"property":"og:description","content":"Thank you Contributors: avatar {{ contributor.login }} Loading contributors... "}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-19T04:54:16.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-19T04:54:16.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Thank you\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-19T04:54:16.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1628085698000,"updatedTime":1695099256000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":3}]},"readingTime":{"minutes":0.3,"words":89},"filePathRelative":"99-about/02-Thanks.md","localizedDate":"2021년 8월 4일","excerpt":"\\n
    \\n

    Contributors:

    \\n \\n

    Loading contributors...

    \\n
    ","autoDesc":true}');export{C as comp,j as data}; diff --git a/assets/02-eks-networking.html-C9T0s8D7.js b/assets/02-eks-networking.html-C9T0s8D7.js new file mode 100644 index 0000000000..575596f51f --- /dev/null +++ b/assets/02-eks-networking.html-C9T0s8D7.js @@ -0,0 +1,277 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as i,b as n,d as a,a as e,e as t}from"./app-Bzk8Nrll.js";const c={},r=t('

    AEWS 2주차 - Amzaon EKS Networking

    이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

    img
    img

    0. 실습환경 준비

    2주차 부터는 원클릭으로 EKS 실습환경을 배포할 수 있는 코드를 사용한다. 필자는 사용중인 AWS IAM 권한 제약사항으로 기존 CF 코드를 변경하여 베스천용 EC2에 관리자 권한을 위임하여 배포할 예정이다.

    ',5),u={href:"https://cloudkatha.com/attach-an-iam-role-to-an-ec2-instance-with-cloudformation/",target:"_blank",rel:"noopener noreferrer"},d=t(`
    # YAML 파일 다운로드
    +curl -O https://gist.githubusercontent.com/hyungwook0221/238d96b3b751362cc03ea40494d15313/raw/49de0a9056688b206a41349fc90727d2375f4f02/aews-eks-oneclick-with-ec2-profile.yaml
    +
    +# CloudFormation 스택 배포
    +# aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 MyIamUserAccessKeyID=<IAM User의 액세스키> MyIamUserSecretAccessKey=<IAM User의 시크릿 키> ClusterBaseName='<eks 이름>' --region ap-northeast-2
    +예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=hw-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 ClusterBaseName=myeks --region ap-northeast-2 --capabilities CAPABILITY_NAMED_IAM
    +
    +# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
    +aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text
    +
    +# 마스터노드 SSH 접속
    +ssh -i ~/.ssh/kp-gasida.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
    +

    1. AWS VPC CNI

    일반적으로 Calico와 같은 K8s CNI의 경우는 Node - Pod의 IP 대역이 다르지만 AWS VPC CNI의 경우에는 Node-Pod 대역을 동일하게 해서 통신이 가능하도록 구성할 수 있다.

    일반적으로 Outer 패킷을 감싸서 오버레이로 통신하지만 AWS VPC CNI는 오히려 심플한 구조를 가진다. 이로인해 간단하고 효율적인 통신이 가능하다!

    K8s Calico CNI vs AWS VPC CNI 비교

    imgimg

    2. Service & AWS LoadBalancer Controller

    K8s 환경에서는 내/외부 통신을 위한 서비스를 크게 3가지 형태로 제공한다.

    필자는 그 중에서 LoadBalancer 타입을 AWS 환경에서 어떻게 활용할 수 있는지를 집중적으로 확인하고 Consul 샘플 예제와 함께 적용해볼 예정이다.

    3. LoadBalancer NLB 모드

    LoadBalancer 배포 시 NLB 모드는 다음 두 가지 유형을 사용할 수 있다.

    유형1. 인스턴스

    1. externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
    2. externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP 유지, 워커 노드의 iptables 사용함
    img

    유형2. IP

    참고 : 반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요함!

    1. Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가능
    2. Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)
    img

    AWS LoadBalancer Controller 배포 with IRSA

    `,25),k={href:"https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/aws-load-balancer-controller.html",target:"_blank",rel:"noopener noreferrer"},m=t(`
    # AWSLoadBalancerControllerIAMPolicy 생성
    +curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
    +aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
    +
    +# 업데이트가 필요한 경우 
    +# aws iam update-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json
    +
    +# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 
    +eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \\
    +--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve
    +
    +## IRSA 정보 확인
    +eksctl get iamserviceaccount --cluster $CLUSTER_NAME
    +
    +## 서비스 어카운트 확인
    +kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh
    +
    +# Helm Chart 설치
    +helm repo add eks https://aws.github.io/eks-charts
    +helm repo update
    +helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \\
    +  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
    +  
    +# 설치 확인
    +kubectl get crd
    +kubectl get deployment -n kube-system aws-load-balancer-controller
    +kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
    +  Service Account:  aws-load-balancer-controller
    + 
    +# 클러스터롤, 클러스터 롤바인딩 확인
    +kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
    +kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
    +

    AWS LoadBalancer Controller가 동작하기 위해 필요한 SA를 생성 후 연결된 ClusterRole과 ClusterRoleBinding을 화인

    img
    img

    샘플 애플리케이션 테스트

    LoadBalancer 타입의 서비스와 및 파드를 배포하고 NLB 모드에 따라서 Client IP가 어떻게 확인되는지 확인해본다.

    # 모니터링
    +watch -d kubectl get pod,svc,ep
    +
    +# 작업용 EC2 - 디플로이먼트 & 서비스 생성
    +curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
    +cat echo-service-nlb.yaml | yh
    +kubectl apply -f echo-service-nlb.yaml
    +
    +# 파드 로깅 모니터링
    +kubectl logs -l app=deploy-websrv -f
    +
    +# 분산 접속 확인
    +NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
    +curl -s $NLB
    +
    img
    img

    NLB에 등록된 Target IP 정보는 생성된 샘플 Pod의 IP인 것을 확인할 수 있다.

    img
    img

    이제 NLB를 통해서 Pod를 호출할 경우 Client IP가 어떻게 확인되는지 확인해보자.

    img
    img

    다음 정보는 각 Node의 정보가 아닌 다른 IP 정보가 확인된다.

    img
    img

    그렇다면 Client IP의 정체는? 바로 NLB에 할당된 네트워크 인터페이스의 IP 이다.

    img
    img

    이제 실제로 Client IP를 추적하기 위한 방법을 알아본다.

    NLB IP Target & Proxy Protocol v2 활성화

    앞선 실습에서 NLB로 SNAT되어서Client IP 확인되지 못하는 것을 확인하였다. 이번에는 Proxy Protocol v2을 활성화 하여 IP 정보를 유지하는 방법을 알아본다. (이미지 출처 : 가시다님 스터디)

    img

    이때 중요한 부분은 SVC 생성 시 service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" 어노테이션을 활성화 하는 것이다.

    # 생성
    +cat <<EOF | kubectl create -f -
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: gasida-web
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: gasida-web
    +  template:
    +    metadata:
    +      labels:
    +        app: gasida-web
    +    spec:
    +      terminationGracePeriodSeconds: 0
    +      containers:
    +      - name: gasida-web
    +        image: gasida/httpd:pp
    +        ports:
    +        - containerPort: 80
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: svc-nlb-ip-type-pp
    +  annotations:
    +    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    +    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    +    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    +    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
    +spec:
    +  ports:
    +    - port: 80
    +      targetPort: 80
    +      protocol: TCP
    +  type: LoadBalancer
    +  loadBalancerClass: service.k8s.aws/nlb
    +  selector:
    +    app: gasida-web
    +EOF
    +
    +---
    +
    +# apache에 proxy protocol 활성화 확인
    +kubectl exec deploy/gasida-web -- apachectl -t -D DUMP_MODULES
    +kubectl exec deploy/gasida-web -- cat /usr/local/apache2/conf/httpd.conf
    +
    +# 접속 확인
    +NLB=$(kubectl get svc svc-nlb-ip-type-pp -o jsonpath={.status.loadBalancer.ingress[0].hostname})
    +curl -s $NLB
    +
    +# 지속적인 접속 시도
    +while true; do curl -s --connect-timeout 1 $NLB; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
    +
    +# 로그 확인
    +kubectl logs -l app=gasida-web -f
    +

    IP를 확인해본 결과 동일한 공인 IP로 찍히는 것으로 확인된다.

    img
    img

    그렇다면 해당 IP는 무엇일까? 바로 현재 curl -s 명령을 수행한 Bastion 노드의 정보이다.

    img
    img

    이렇게 NLB를 통해 호출하더라도 정상적으로 Client IP를 유지하는 방법을 알아보았다. 실제로 온프레미스 환경에서 3-Tier 기반의 WEB/WAS를 구성하다 보면 Client IP를 유지하기 위해 XFF 설정을 하는 것이 일반적이다. 다만, NLB의 경우에는 L4 계층까지만 패킷에 대한 이해와 분석이 가능하므로 Proxy Protocol을 써야한다는 새로운 정보를 알 수 있는 좋은 기회였다.

    4. Consul IngressGateway 샘플예제

    다음 예제는 Consul IngressGateway를 통한 ServiceMesh의 단일 진입점을 테스트해볼 예정이다. Consul 1.15x 버전에는 Envoy의 Access Log 기능이 추가되어 이번 스터디를 통해 학습한 NLB의 IP 유지방안에 대한 테스트를 진행해본다.

    `,34),v=n("p",null,"참고 : Consul Gateway에서 envoy access log 활성화 기능",-1),b={href:"https://developer.hashicorp.com/consul/docs/connect/observability/access-logs",target:"_blank",rel:"noopener noreferrer"},g={href:"https://github.com/hashicorp/consul/issues/5231",target:"_blank",rel:"noopener noreferrer"},h={href:"https://github.com/hashicorp/consul/pull/15864",target:"_blank",rel:"noopener noreferrer"},y=t(`

    실습1. NLB IP Target & Proxy Protocol v2 비활성화

    1) Consul deploy via Helm

    처음 설정시에는 PPv2를 사용하지 않고 NLB를 적용해볼 예정이다. => Client IP가 어떻게 찍히는지 확인!

    client:
    +  grpc: true
    +connectInject:
    +  consulNamespaces:
    +    mirroringK8S: true
    +  enabled: true
    +controller:
    +  enabled: true
    +global:
    +  acls:
    +    manageSystemACLs: true
    +  enableConsulNamespaces: true
    +  enterpriseLicense:
    +    secretKey: key
    +    secretName: license
    +  gossipEncryption:
    +    autoGenerate: true
    +  image: hashicorp/consul-enterprise:1.15.1-ent
    +  #imageEnvoy: envoyproxy/envoy:v1.22.5
    +  #imageK8S: hashicorp/consul-k8s-control-plane:0.49.5
    +  metrics:
    +    enabled: false
    +  tls:
    +    enableAutoEncrypt: true
    +    enabled: true
    +    httpsOnly: false
    +    verify: false
    +ingressGateways:
    +  defaults:
    +    replicas: 1
    +    service:
    +      type: LoadBalancer
    +      annotations: |
    +        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    +        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    +        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    +        #service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
    +  enabled: true
    +  gateways:
    +  - name: ingress-gateway
    +meshGateway:
    +  enabled: false
    +  replicas: 1
    +  service:
    +    enabled: true
    +    nodePort: 32000
    +    type: NodePort
    +server:
    +  replicas: 3
    +terminatingGateways:
    +  defaults:
    +    replicas: 1
    +  enabled: false
    +ui:
    +  enabled: true
    +  service:
    +    port:
    +      http: 80
    +      https: 443
    +    type: LoadBalancer
    +

    2) Consul CRD 배포

    apiVersion: consul.hashicorp.com/v1alpha1
    +kind: IngressGateway
    +metadata:
    +  name: ingress-gateway
    +spec:
    +  listeners:
    +    - port: 8080
    +      protocol: http
    +      services:
    +        - name: static-server
    +

    spec.accessLogs를 통해 AccessLog 활성화 및 파일경로 추가

    apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ProxyDefaults
    +metadata:
    +  name: global
    +spec:
    +  accessLogs:
    +    enabled: true
    +#    type: file
    +#    path: "/var/log/envoy/access-logs.txt" 
    +
    apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceDefaults
    +metadata:
    +  name: static-server
    +spec:
    +  protocol: http
    +
    apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: static-server
    +spec:
    +  destination:
    +    name: static-server
    +  sources:
    +    - name: ingress-gateway
    +      action: allow
    +
    apiVersion: v1
    +kind: Service
    +metadata:
    +  name: static-server
    +spec:
    +  selector:
    +    app: static-server
    +  ports:
    +    - protocol: TCP
    +      port: 80
    +      targetPort: 8080
    +---
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: static-server
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: static-server
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: static-server
    +  template:
    +    metadata:
    +      name: static-server
    +      labels:
    +        app: static-server
    +      annotations:
    +        'consul.hashicorp.com/connect-inject': 'true'
    +    spec:
    +      containers:
    +        - name: static-server
    +          image: hashicorp/http-echo:latest
    +          args:
    +            - -text="hello world"
    +            - -listen=:8080
    +          ports:
    +            - containerPort: 8080
    +              name: http
    +      serviceAccountName: static-server
    +

    3) 샘플 애플리케이션 호출

    EXTERNAL_IP=$(kubectl get services --selector component=ingress-gateway --output jsonpath="{range .items[*]}{@.status.loadBalancer.ingress[*].hostname}{end}")
    +echo "Connecting to \\"$EXTERNAL_IP\\""
    +curl --header "Host: static-server.ingress.consul" "http://$EXTERNAL_IP:8080"
    +

    호출결과 앞서 실습에서 확인해본 것과 동일하게 NLB IP Target & Proxy Protocol v2 비활성화 일 경우에는 로드밸런서 인터페이스 IP가 확인된다.

    img
    img
    img
    img

    실습2. NLB IP Target & Proxy Protocol v2 활성화

    1) Consul deploy via Helm

    이번에는 위와 동일하지만 NLB의 어노테이션만 PPv2를 활성화 한다.

    #(생략)
    +ingressGateways:
    +  defaults:
    +    replicas: 1
    +    service:
    +      type: LoadBalancer
    +      annotations: |
    +        service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    +        service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    +        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    +		    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
    +  enabled: true
    +  gateways:
    +  - name: ingress-gateway
    +#(생략)
    +

    2) Consul CRD 배포(생략)

    위와 동일하게 사용

    3) 샘플 애플리케이션 호출

    EXTERNAL_IP=$(kubectl get services --selector component=ingress-gateway --output jsonpath="{range .items[*]}{@.status.loadBalancer.ingress[*].hostname}{end}")
    +echo "Connecting to \\"$EXTERNAL_IP\\""
    +curl --header "Host: static-server.ingress.consul" "http://$EXTERNAL_IP:8080"
    +

    하지만 PPv2 설정 후 static-server 앱을 테스트해본 결과 정상적으로 동작하지 않는 것으로 보인다.

    img
    img

    위와 관련해서 확인해본 결과 Istio의 경우에는 EnvoyFilter 등을 통해 해결하는 방안(?)이 있는 것으로 보이며, 일반적으로 PPv2를 사용하기 위해서는 애플리케이션 단에서 사용할 수 있도록 설정이 필요한 것으로 확인되었다.

    `,37),w=n("p",null,"참고 :",-1),f={href:"https://preliminary.istio.io/latest/blog/2020/show-source-ip/",target:"_blank",rel:"noopener noreferrer"},P={href:"https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/proxy_protocol",target:"_blank",rel:"noopener noreferrer"},_=n("blockquote",null,[n("p",null,[a("📌 정보공유:"),n("br"),a(" 해당 이슈에 대하여 Consul Product Manager를 통해 FR(Feture Request)로 등록 후 신규 기능으로 추가할 수 있도록 지원할 것으로 답변받았다. 추후 업데이트에 대한 변동사항이 있으면 할 예정이다.")])],-1),x=n("img",{src:"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/bVHhwT.jpg",alt:"img",style:{zoom:"50%"}},null,-1),C=n("p",null,[n("s",null,"위에서 언급한 것 처럼 Consul Native하게는 PPv2 기능을 사용하기 어려운 상황이라 Apache 애플리케이션에서 PPv2 설정을 통해 해결이 가능한지 확인을 해보았다.")],-1),S=n("p",null,[a("확인결과 결과적으로 테스트가 "),n("strong",null,"불가능"),a(" 한 것으로 확인되었다. Apache 애플리케이션에 PPv2를 활성화 하고 Consul CRD를 적용하여 IngressGateway에서 호출하였으나, 400 에러가 발생한다. (NLB PPv2 활성화 시 발생)")],-1),I=n("p",null,"아쉽지만 본 테스트는 FR이 진행된 이후에 업데이트 하도록 하겠다.",-1);function A(N,L){const s=o("ExternalLinkIcon");return p(),i("div",null,[r,n("blockquote",null,[n("p",null,[a("참고 : "),n("a",u,[a("https://cloudkatha.com/attach-an-iam-role-to-an-ec2-instance-with-cloudformation/"),e(s)])])]),d,n("blockquote",null,[n("p",null,[a("참고 : "),n("a",k,[a("AWS Load Balancer Controller 추가 기능 설치"),e(s)])])]),m,n("blockquote",null,[v,n("ul",null,[n("li",null,[n("a",b,[a("https://developer.hashicorp.com/consul/docs/connect/observability/access-logs"),e(s)])]),n("li",null,[n("a",g,[a("https://github.com/hashicorp/consul/issues/5231"),e(s)])]),n("li",null,[n("a",h,[a("https://github.com/hashicorp/consul/pull/15864"),e(s)])])])]),y,n("blockquote",null,[w,n("ul",null,[n("li",null,[n("a",f,[a("https://preliminary.istio.io/latest/blog/2020/show-source-ip/"),e(s)])]),n("li",null,[n("a",P,[a("https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/proxy_protocol"),e(s)])])])]),_,x,C,S,I])}const E=l(c,[["render",A],["__file","02-eks-networking.html.vue"]]),W=JSON.parse('{"path":"/02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.html","title":"AEWS 2주차 - Amzaon EKS Networking","lang":"ko-KR","frontmatter":{"description":"AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다..","tag":["Kubernetes","EKS","PKOS"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"AEWS 2주차 - Amzaon EKS Networking"}],["meta",{"property":"og:description","content":"AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/9cxho8.jpg"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"AEWS 2주차 - Amzaon EKS Networking"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:tag","content":"EKS"}],["meta",{"property":"article:tag","content":"PKOS"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"AEWS 2주차 - Amzaon EKS Networking\\",\\"image\\":[\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/9cxho8.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/tKiW7W.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/Gr1F1F.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/XW9vaE.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/FVppDY.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/WAkmPs.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/BY1AQ6.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/ZOT5ep.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/mHh2Rs.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/oOWX0z.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/3O7lst.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/pCs0WD.jpg\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"0. 실습환경 준비","slug":"_0-실습환경-준비","link":"#_0-실습환경-준비","children":[]},{"level":2,"title":"1. AWS VPC CNI","slug":"_1-aws-vpc-cni","link":"#_1-aws-vpc-cni","children":[{"level":3,"title":"K8s Calico CNI vs AWS VPC CNI 비교","slug":"k8s-calico-cni-vs-aws-vpc-cni-비교","link":"#k8s-calico-cni-vs-aws-vpc-cni-비교","children":[]}]},{"level":2,"title":"2. Service & AWS LoadBalancer Controller","slug":"_2-service-aws-loadbalancer-controller","link":"#_2-service-aws-loadbalancer-controller","children":[]},{"level":2,"title":"3. LoadBalancer NLB 모드","slug":"_3-loadbalancer-nlb-모드","link":"#_3-loadbalancer-nlb-모드","children":[{"level":3,"title":"유형1. 인스턴스","slug":"유형1-인스턴스","link":"#유형1-인스턴스","children":[]},{"level":3,"title":"유형2. IP","slug":"유형2-ip","link":"#유형2-ip","children":[]},{"level":3,"title":"AWS LoadBalancer Controller 배포 with IRSA","slug":"aws-loadbalancer-controller-배포-with-irsa","link":"#aws-loadbalancer-controller-배포-with-irsa","children":[]}]},{"level":2,"title":"4. Consul IngressGateway 샘플예제","slug":"_4-consul-ingressgateway-샘플예제","link":"#_4-consul-ingressgateway-샘플예제","children":[{"level":3,"title":"실습1. NLB IP Target & Proxy Protocol v2 비활성화","slug":"실습1-nlb-ip-target-proxy-protocol-v2-비활성화","link":"#실습1-nlb-ip-target-proxy-protocol-v2-비활성화","children":[]},{"level":3,"title":"실습2. NLB IP Target & Proxy Protocol v2 활성화","slug":"실습2-nlb-ip-target-proxy-protocol-v2-활성화","link":"#실습2-nlb-ip-target-proxy-protocol-v2-활성화","children":[]}]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":4.48,"words":1344},"filePathRelative":"02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

    \\n
    \\"img\\"
    img
    \\n

    0. 실습환경 준비

    "}');export{E as comp,W as data}; diff --git a/assets/02-env.html-DG_OSaqw.js b/assets/02-env.html-DG_OSaqw.js new file mode 100644 index 0000000000..0a33d77dc0 --- /dev/null +++ b/assets/02-env.html-DG_OSaqw.js @@ -0,0 +1 @@ +import{_ as d}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as s,c as a,b as t,d as e,a as n,e as r}from"./app-Bzk8Nrll.js";const i={},c=t("h1",{id:"_2-tomcat-설치환경",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_2-tomcat-설치환경"},[t("span",null,"2. Tomcat 설치환경")])],-1),h=t("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/XvinTBrQ0ig",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),_=t("hr",null,null,-1),u=t("h2",{id:"_2-1-os",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_2-1-os"},[t("span",null,"2.1 OS")])],-1),g=t("p",null,[e("톰캣을 설치하는 OS 플랫폼 환경은 모든 환경을 지원합니다. 그나마 예전에는 일부 Unix/Linux/OSX 환경에서 Apache HTTP Server 설치하듯 컴파일을 통해 구성하였으나, 최근에는 압축파일을 해제하고 바로 사용할 수 있는 경우가 대부분입니다."),t("br"),e(" 톰캣을 운영하기 위해 OS를 선택해야하는 입장이라면 다음과 같은 설치 타입을 고려할 수 있습니다.")],-1),f=t("ul",null,[t("li",null,"Windows : Zip 파일을 풀어 사용하거나 msi 설치로 윈도우 서비스에 등록하는 설치"),t("li",null,"Unix/Linux/OSX : 압축 파일을 풀어 사용하거나 컴파일하여 설치")],-1),p=t("h2",{id:"_2-2-jdk",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_2-2-jdk"},[t("span",null,"2.2 JDK")])],-1),m={href:"https://tomcat.apache.org/whichversion.html",target:"_blank",rel:"noopener noreferrer"},x=r('

    톰켓의 버전이 올라감에 따라 지원하는 Java Standard Spec Version 또한 변경됩니다. 이 경우 일부 상위 버전은 JDK의 특정 버전에서 지원되지 않을 수 있지요. 따라서 개발되는 어플리케이션의 JDK요구치나 표준화된 톰캣 버전에 따라 지원되는 JDK 버전이 상이할 수 있습니다. 다음의 표를 참고하시기 바랍니다.

    Servlet SpecJSP SpecEL SpecWebSocket SpecAuthentication (JASPIC) SpecApache Tomcat VersionLatest Released VersionSupported Java Versions
    6.03.15.02.13.010.1.x10.1.0-M8 (alpha)11 and later
    5.03.04.02.02.010.0.x10.0.148 and later
    4.02.33.01.11.19.0.x9.0.568 and later
    3.12.33.01.11.18.5.x8.5.737 and later
    3.12.33.01.1N/A8.0.x (superseded)8.0.53 (superseded)7 and later
    3.02.22.21.1N/A7.0.x (archived)7.0.109 (archived)6 and later (7 and later for WebSocket)
    2.52.12.1N/AN/A6.0.x (archived)6.0.53 (archived)5 and later
    2.42.0N/AN/AN/A5.5.x (archived)5.5.36 (archived)1.4 and later
    2.31.2N/AN/AN/A4.1.x (archived)4.1.40 (archived)1.3 and later
    2.21.1N/AN/AN/A3.3.x (archived)3.3.2 (archived)1.1 and later

    톰캣 5.5.x 버전의 경우 5.5.12 버전 이후로는 JDK 5 이상을 지원함에 유의합니다.

    Java SE의 경우 OS 플랫폼에 따라 제공하는 벤더가 다른 경우가 있습니다. Oracle이 서브스크립션 형태로, 업데이트에 대해 유료화 선언을 한 이후로 여러 파생 Java를 고려할 수 있습니다. 여전히 8 버전을 사용하는 서비스가 많아 OracleJDK가 점유율이 높으나, 이후 높은 버전으로 이전시에는 다른 JDK를 고려하는 상황도 발생할 것으로 보입니다.

    Most Popular JRE/JDK Distribution (JRebel, 2020)
    2020Java

    3. Java 제공자

    ',6),y=t("thead",null,[t("tr",null,[t("th",null,"Provider"),t("th",null,"Free Builds from Source"),t("th",null,"Free Binary Distributions"),t("th",null,"Extended Updates"),t("th",null,"Commercial Support"),t("th",null,"Permissive License")])],-1),b={href:"https://adoptopenjdk.net",target:"_blank",rel:"noopener noreferrer"},v=t("td",null,"Yes",-1),w=t("td",null,"Yes",-1),k=t("td",null,"Yes",-1),Y=t("td",null,"No",-1),S=t("td",null,"Yes",-1),J={href:"https://aws.amazon.com/corretto",target:"_blank",rel:"noopener noreferrer"},N=t("td",null,"Yes",-1),j=t("td",null,"Yes",-1),A=t("td",null,"Yes",-1),T=t("td",null,"No",-1),O=t("td",null,"Yes",-1),D={href:"https://www.azul.com/downloads/zulu/",target:"_blank",rel:"noopener noreferrer"},K=t("td",null,"No",-1),B=t("td",null,"Yes",-1),L=t("td",null,"Yes",-1),P=t("td",null,"Yes",-1),R=t("td",null,"Yes",-1),M={href:"https://bell-sw.com/java.html",target:"_blank",rel:"noopener noreferrer"},X=t("td",null,"No",-1),E=t("td",null,"Yes",-1),H=t("td",null,"Yes",-1),V=t("td",null,"Yes",-1),z=t("td",null,"Yes",-1),I={href:"https://www.ibm.com/developerworks/java/jdk",target:"_blank",rel:"noopener noreferrer"},U=t("td",null,"No",-1),Z=t("td",null,"No",-1),C=t("td",null,"Yes",-1),W=t("td",null,"Yes",-1),F=t("td",null,"Yes",-1),Q={href:"https://adoptopenjdk.net/upstream.html",target:"_blank",rel:"noopener noreferrer"},q=t("td",null,"Yes",-1),G=t("td",null,"Yes",-1),$=t("td",null,"Yes",-1),tt=t("td",null,"No",-1),et=t("td",null,"Yes",-1),lt={href:"https://www.oracle.com/technetwork/java/javase/downloads",target:"_blank",rel:"noopener noreferrer"},nt=t("td",null,"No",-1),dt=t("td",null,"Yes",-1),ot=t("td",null,"No",-1),st=t("td",null,"Yes",-1),at=t("td",null,"No",-1),rt={href:"http://jdk.java.net",target:"_blank",rel:"noopener noreferrer"},it=t("td",null,"Yes",-1),ct=t("td",null,"Yes",-1),ht=t("td",null,"No",-1),_t=t("td",null,"No",-1),ut=t("td",null,"Yes",-1),gt={href:"https://github.com/ojdkbuild/ojdkbuild",target:"_blank",rel:"noopener noreferrer"},ft=t("td",null,"Yes",-1),pt=t("td",null,"Yes",-1),mt=t("td",null,"No",-1),xt=t("td",null,"No",-1),yt=t("td",null,"Yes",-1),bt={href:"https://developers.redhat.com/products/openjdk/overview",target:"_blank",rel:"noopener noreferrer"},vt=t("td",null,"Yes",-1),wt=t("td",null,"Yes",-1),kt=t("td",null,"Yes",-1),Yt=t("td",null,"Yes",-1),St=t("td",null,"Yes",-1),Jt={href:"https://sap.github.io/SapMachine",target:"_blank",rel:"noopener noreferrer"},Nt=t("td",null,"Yes",-1),jt=t("td",null,"Yes",-1),At=t("td",null,"Yes",-1),Tt=t("td",null,"Yes",-1),Ot=t("td",null,"Yes",-1),Dt=t("ul",null,[t("li",null,"Oracle의 경우 Java가 필요한 미들웨어를 구매한 경우 Java에 대한 지원이 포함됩니다."),t("li",null,"RedHat의 경우 RedHat Linux, RedHat Middelware를 서브스크립션하는 경우 Java에 대한 지원이 포함됩니다."),t("li",null,"AIX : IBM에서 제공하는 JDK를 사용합니다."),t("li",null,"HP-UX : HP에서 제공하는 JDK를 사용합니다."),t("li",null,"AIX의 JDK경우 Windows환경에도 설치가 가능하기는 하지만 일반적으로는 Oracle에서 제공하는 기존 SunJDK를 설치하여 사용합니다."),t("li",null,"OSX(Mac)는 JDK6 까지는 Apple사에서 제공하지만 JDK7부터는 Oracle에서 설치파일을 받아 설치합니다.")],-1);function Kt(Bt,Lt){const l=o("ExternalLinkIcon");return s(),a("div",null,[c,h,_,u,g,f,p,t("blockquote",null,[t("p",null,[t("a",m,[e("https://tomcat.apache.org/whichversion.html"),n(l)])])]),x,t("table",null,[y,t("tbody",null,[t("tr",null,[t("td",null,[t("a",b,[e("AdoptOpenJDK"),n(l)])]),v,w,k,Y,S]),t("tr",null,[t("td",null,[t("a",J,[e("Amazon – Corretto"),n(l)])]),N,j,A,T,O]),t("tr",null,[t("td",null,[t("a",D,[e("Azul Zulu"),n(l)])]),K,B,L,P,R]),t("tr",null,[t("td",null,[t("a",M,[e("BellSoft Liberica"),n(l)])]),X,E,H,V,z]),t("tr",null,[t("td",null,[t("a",I,[e("IBM"),n(l)])]),U,Z,C,W,F]),t("tr",null,[t("td",null,[t("a",Q,[e("OpenJDK Upstream"),n(l)])]),q,G,$,tt,et]),t("tr",null,[t("td",null,[t("a",lt,[e("Oracle JDK"),n(l)])]),nt,dt,ot,st,at]),t("tr",null,[t("td",null,[t("a",rt,[e("Oracle OpenJDK"),n(l)])]),it,ct,ht,_t,ut]),t("tr",null,[t("td",null,[t("a",gt,[e("ojdkbuild"),n(l)])]),ft,pt,mt,xt,yt]),t("tr",null,[t("td",null,[t("a",bt,[e("RedHat"),n(l)])]),vt,wt,kt,Yt,St]),t("tr",null,[t("td",null,[t("a",Jt,[e("SapMachine"),n(l)])]),Nt,jt,At,Tt,Ot])])]),Dt])}const Mt=d(i,[["render",Kt],["__file","02-env.html.vue"]]),Xt=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/02-env.html","title":"2. Tomcat 설치환경","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/02-env.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2. Tomcat 설치환경"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://marvel-b1-cdn.bc0a.com/f00000000156946/www.jrebel.com/sites/default/files/image/2020-01/12.%20what%20jre_jdk%20distribution%20do%20you%20use.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"2. Tomcat 설치환경"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2. Tomcat 설치환경\\",\\"image\\":[\\"https://marvel-b1-cdn.bc0a.com/f00000000156946/www.jrebel.com/sites/default/files/image/2020-01/12.%20what%20jre_jdk%20distribution%20do%20you%20use.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"2.1 OS","slug":"_2-1-os","link":"#_2-1-os","children":[]},{"level":2,"title":"2.2 JDK","slug":"_2-2-jdk","link":"#_2-2-jdk","children":[]},{"level":2,"title":"3. Java 제공자","slug":"_3-java-제공자","link":"#_3-java-제공자","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.25,"words":375},"filePathRelative":"05-Software/Tomcat/tomcat101/02-env.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n
    \\n

    2.1 OS

    \\n

    톰캣을 설치하는 OS 플랫폼 환경은 모든 환경을 지원합니다. 그나마 예전에는 일부 Unix/Linux/OSX 환경에서 Apache HTTP Server 설치하듯 컴파일을 통해 구성하였으나, 최근에는 압축파일을 해제하고 바로 사용할 수 있는 경우가 대부분입니다.
    \\n톰캣을 운영하기 위해 OS를 선택해야하는 입장이라면 다음과 같은 설치 타입을 고려할 수 있습니다.

    "}');export{Mt as comp,Xt as data}; diff --git a/assets/02-hcl.html-DQHZhYcD.js b/assets/02-hcl.html-DQHZhYcD.js new file mode 100644 index 0000000000..5d5695ab5e --- /dev/null +++ b/assets/02-hcl.html-DQHZhYcD.js @@ -0,0 +1,34 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as p,c as i,b as a,d as e,a as s,e as t}from"./app-Bzk8Nrll.js";const c={},l=a("h1",{id:"hcl-hashicorp-configuration-language",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#hcl-hashicorp-configuration-language"},[a("span",null,"HCL - HashiCorp Configuration Language")])],-1),u=a("p",null,"Terraform의 가장 주요한 기능으로 Infrastructure as Code 를 이야기 할 수 있습니다. 그리고 이를 지원하는 HCL에 대해 알아보고자 합니다.",-1),d=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/pSPZQdpWWjs",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),m=t('

    Terraform에서는 당연히 동작해야하는 필연적 기능이기 때문에 오픈소스를 포함하여 모든 유형의 Terraform에서 제공되는 기능입니다.


    유형지원여부
    Terraform OSS (Open Source Software)✔︎
    Terraform Cloud✔︎
    Terraform Cloud for Business✔︎
    Terraform Enterprise✔︎

    Infrastructure as Code에 대해 간단히 소개하자면 수작업으로 프로비저닝 하던 방식, 예를 들면 UI 클릭이나 개별적인 스크립트를 사용하여 프로비저닝하는 방식은 자동화하기 어렵고, 충분히 숙달되지 않거나 밤샘작업과 스트레스로 잠시 집중력이 떨어지면 실수가 발생할 수 있습니다. 그리고 스크립트 방식은 나름 잘 정의되어있지만 순차적으로 수행되고 중간에 오류가 나면 다시 돌이키기 힘든 방식이였습니다.

    Terraform에서는 이전의 프로비저닝 방식을 개선하여 좀더 안정적인고 체계적인 관리 방식을 도입할 수 있는 메커니즘과 Infrastructure as Code의 핵심인 Code를 잘 만들고 관리할 수 있는 도구를 제공합니다.

    기본적으로 Terraform은 HCL이라고하는 HashiCorp Configuration Language와 JSON가 코드의 영역을 담당하고 있습니다. 특히 HCL은 쉽게 읽을수 있고 빠르게 배울 수 있는 언어의 특성을 가지고 있습니다.

    인프라가 코드로 표현되고, 이 코드는 곧 인프라이기 때문에 선언적 특성을 갖게 되고 튜링 완전한 언어적 특성을 갖습니다. 즉, 일반적인 프로그래밍 언어의 일반적인 조건문 처리같은 동작이 가능하다는 것입니다.

    이렇게 코드화된 인프라는 주 목적인 자동화와 더불어 쉽게 버저닝하여 히스토리를 관리하고 함께 작업할 수 있는 기반을 제공하게 됩니다.

    Why HCL?

    사실 이미 테라폼을 조금이라도 써보신분들은 당연하게도 HCL을 써보셨을 수도 있지만 처음 접하시는 경우 뭔가 또 배워야 하는건가? 어려운건가? 라는 마음의 허들이 생길 수 있습니다.

    Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-27-59
    Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-27-59

    앞서 설명드렸듯 HCL은 JSON과 호환되고 이런 방식이 더 자연스러우신 분들은 JSON으로 관리가 가능합니다. 하지만 HCL에 대한 일반적인 질문은 왜 JSON이나 YAML같은 방식이 아닌지 입니다.

    HCL 이전에 HashiCorp에서 사용한 도구는 Ruby같은 여타 프로그래밍 언어를 사용하여 JSON같은 구조를 만들어내기 위해 다양한 언어를 사용했습니다. 이때 알게된 점은 어떤 사용자는 인간 친화적인 언어를 원했고 어떤 사람들은 기계 친화적 언어를 원한다는 것입니다.

    JSON은 이같은 요건 양쪽에 잘 맞지만 상당히 구문이 길어지고 주석이 지원되지 않는 다는 단점이 있습니다. YAML을 사용하면 처음 접하시는 분들은 실제 구조를 만들어내고 익숙해지는데 어려움이 발생하였습니다. 더군다나 일반적 프로그래밍 언어는 허용하지 않아야하는 많은 기능을 내장하고 있는 문제점도 있었습니다.

    이런 여러 이유들로 JSON과 호환되는 자체 구성언어를 만들게 되었고 HCL은 사람이 쉽게 작성하고 수정할 수 있도록 설계되었습니다. 덩달아 HCL용 API가 JSON을 함께 호환하기 때문에 기계 친화적이기도 합니다.

    Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-28-30
    Terraform_Features_Master(KR) - Google Slides 2020-07-14 22-28-30

    HCL을 익히고 사용하는 건 어떨까요?

    예를 들어 Python 코드로 비슷하게 정의를 내려보았습니다. 우리가 사용할 패키지를 Import하고 해당 패키지가 기본적으로 필요로하는 값을 넣어 초기화 합니다. 여기서는 aws라는 패키지에 regionprofile 이름을 넣어서 기본적으로 동작 할 수 있는 설정으로 초기화 하였습니다. 이후에 해당 패키지가 동작할 수 있는 여러 서브 펑션들에 대한 정의를 하고 마지막으로는 실행을 위한 큐에 넣습니다.

    HCL도 거의 이런 일반적 프로그래밍의 논리와 비슷합니다. 우선 사용할 프로바이더 라고하는 마치 라이브러리나 패키지 같은 것을 정의 합니다. 이 프로바이더에는 기본적으로 선언해주어야 하는 값들이 있습니다.

    그리고 이 프로바이더가 제공하는 기본적인 모듈들, 즉, 클래스나 구조체와 비슷한 형태로 정의 합니다. resource에 대한 정의는 마치 aws_instance라는 클래스를 example로 정의하는 것과 비슷한 메커니즘을 갖습니다. 그리고 해당 리소스의 값들을 사용자가 재정의 하는 방식입니다.

    HCL Syntax

    ',22),h={href:"https://github.com/hashicorp/hcl",target:"_blank",rel:"noopener noreferrer"},g=t(`
    // 한줄 주석 방법1
    +# 한줄 주석 방법2
    +
    +/*
    +다중
    +라인
    +주석
    +*/
    +
    +locals {
    +  key1 =      "value1" // = 를 기준으로 키와 값이 구분되며
    +  key2     = "value2"  // = 양쪽의 공백은 중하지 않습니다.
    +  myStr = "TF ♡ UTF-8" // UTF-8 문자를 지원합니다.
    +  multiStr = <<FOO     // <<EOF 같은 여러줄의 스트링을 지원합니다.
    +  Multi
    +  Line
    +  String
    +  with <<ANYTEXT
    +  FOO                  // 앞과 끝 문자만 같으면 됩니다.
    +  
    +  boolean1 = true      // boolean true
    +  boolean2 = false     // boolean false를 지원합니다.
    +
    +  deciaml = 123        // 기본적으로 숫자는 10진수,
    +  octal = 0123         // 0으로 시작하는 숫자는 8진수,
    +  hexadecimal = "0xD5" // 0x 값을 포함하는 스트링은 16진수,
    +  scientific = 1e10    // 과학표기 법도 지원합니다.
    +
    +  //funtion 들이 많이 준비되어있습니다.
    +  myprojectname = format("%s is myproject name", var.project)
    +  //인라인 조건문도 지원합니다.
    +  credentials = var.credentials == "" ? file(var.credentials_file) : var.credentials
    +}
    +
    `,1),k={href:"https://www.terraform.io/docs/configuration/functions.html",target:"_blank",rel:"noopener noreferrer"},f=a("hr",null,null,-1),v=a("p",null,"Terraform의 가장 기본적인 Infrastructure as Code에 대한 소개와 이를 구현하는 HCL에 대해 알아보았습니다.",-1);function b(_,y){const n=r("ExternalLinkIcon");return p(),i("div",null,[l,u,d,m,a("p",null,[e("실제 HCL의 몇가지 예는 다음과 같습니다. ("),a("a",h,[e("github"),s(n)]),e(")")]),g,a("p",null,[a("a",k,[e("Funtion Doc"),s(n)])]),f,v])}const H=o(c,[["render",b],["__file","02-hcl.html.vue"]]),L=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/01-Information/02-hcl.html","title":"HCL - HashiCorp Configuration Language","lang":"ko-KR","frontmatter":{"description":"Terraform Features","tag":["terraform","usecase","IaC","HCL"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/01-Information/02-hcl.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"HCL - HashiCorp Configuration Language"}],["meta",{"property":"og:description","content":"Terraform Features"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/Terraform_Features_Master%28KR%29%20-%20Google%20Slides%202020-07-14%2022-27-59.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"HCL - HashiCorp Configuration Language"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"usecase"}],["meta",{"property":"article:tag","content":"IaC"}],["meta",{"property":"article:tag","content":"HCL"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"HCL - HashiCorp Configuration Language\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/Terraform_Features_Master%28KR%29%20-%20Google%20Slides%202020-07-14%2022-27-59.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/Terraform_Features_Master%28KR%29%20-%20Google%20Slides%202020-07-14%2022-28-30.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Why HCL?","slug":"why-hcl","link":"#why-hcl","children":[]},{"level":2,"title":"HCL Syntax","slug":"hcl-syntax","link":"#hcl-syntax","children":[]}],"git":{"createdTime":1640262000000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.92,"words":275},"filePathRelative":"04-HashiCorp/03-Terraform/01-Information/02-hcl.md","localizedDate":"2021년 12월 23일","excerpt":"\\n

    Terraform의 가장 주요한 기능으로 Infrastructure as Code 를 이야기 할 수 있습니다. 그리고 이를 지원하는 HCL에 대해 알아보고자 합니다.

    \\n"}');export{H as comp,L as data}; diff --git a/assets/02-intention-alldeny-CpsNCiFl.png b/assets/02-intention-alldeny-CpsNCiFl.png new file mode 100644 index 0000000000..fa6dd3c60f Binary files /dev/null and b/assets/02-intention-alldeny-CpsNCiFl.png differ diff --git a/assets/02-intention-crd-DDJBL_BF.png b/assets/02-intention-crd-DDJBL_BF.png new file mode 100644 index 0000000000..227c9b5ee5 Binary files /dev/null and b/assets/02-intention-crd-DDJBL_BF.png differ diff --git a/assets/02-jobs.html-Cq6dIiBF.js b/assets/02-jobs.html-Cq6dIiBF.js new file mode 100644 index 0000000000..a3de1b0abc --- /dev/null +++ b/assets/02-jobs.html-Cq6dIiBF.js @@ -0,0 +1,140 @@ +import{_ as o,a as p,b as c,c as d,d as r,e as u}from"./1563945539114-BeDUHaoS.js";import{_ as v}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as m,c as b,a as s,b as n,d as e,e as t}from"./app-Bzk8Nrll.js";const g={},k=n("h1",{id:"_2-jobs",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_2-jobs"},[n("span",null,"2. Jobs")])],-1),h=n("p",null,"프로젝트는 Job의 일부 입니다. 즉, 모든 프로젝트가 Job이지만 모든 Job이 프로젝트는 아닙니다. Job의 구조는 다음과 같습니다.",-1),f=t(`

    FreeStyleProejct, MatrixProject, ExternalJob만 New job에 표시됩니다.

    2.1 New pipeline

    Step 1에서는 stage없이 기본 Pipeline을 실행하여 수행 테스트를 합니다.

    1. Jenkins 로그인

    2. 좌측 새로운 Item 클릭

    3. Enter an item name에 Job 이름 설정 (e.g. 2.Jobs)

    4. Pipeline 선택 후 OK 버튼 클릭

    5. Pipeline 항목 오른 쪽 Try sample Pipelie...클릭하여 Hello world 클릭 후 저장

      node {
      +   echo 'Hello World'
      +}
      +
    6. 좌측 Build now클릭

    7. 좌측 Build History의 최근 빌드된 항목(e.g. #1) 우측에 마우스를 가져가면 dropdown 버튼이 생깁니다. 해당 버튼을 클릭하여 Console Output 클릭

    8. 수행된 echo 동작 출력을 확인합니다.

      Started by user GyuSeok.Lee
      +Running in Durability level: MAX_SURVIVABILITY
      +[Pipeline] Start of Pipeline
      +[Pipeline] node
      +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
      +[Pipeline] {
      +[Pipeline] echo
      +Hello World
      +[Pipeline] }
      +[Pipeline] // node
      +[Pipeline] End of Pipeline
      +Finished: SUCCESS
      +

    2.2 New pipeline

    Step 2에서는 stage 를 구성하여 실행합니다.

    1. 기존 생성한 Job 클릭 (e.g. 02-02.Jobs)

    2. 좌측 구성을 클릭하여 Pipeline 스크립트를수정합니다.

      pipeline{
      +    agent any
      +    stages {
      +        stage("Hello") {
      +            steps {
      +                echo 'Hello World'
      +            }
      +        }
      +    }
      +}
      +
    3. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

    4. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

      1563942302074
      1563942302074
    5. 수행된 빌드의 Console Output을 확인하면 앞서 Step 1에서는 없던 stage 항목이 추가되어 수행됨을 확인 할 수 있습니다.

      Started by user GyuSeok.Lee
      +Running in Durability level: MAX_SURVIVABILITY
      +[Pipeline] Start of Pipeline
      +[Pipeline] node
      +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
      +[Pipeline] {
      +[Pipeline] stage
      +[Pipeline] { (Hello)
      +[Pipeline] echo
      +Hello World
      +[Pipeline] }
      +[Pipeline] // stage
      +[Pipeline] }
      +[Pipeline] // node
      +[Pipeline] End of Pipeline
      +Finished: SUCCESS
      +

    2.3 Parameterizing a job

    Pipeline 내에서 사용되는 매개변수 정의를 확인해 봅니다. Pipeline 스크립트는 다음과 같습니다.

    pipeline {
    +    agent any
    +    parameters {
    +        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
    +    }
    +    stages {
    +        stage('Example') {
    +            steps {
    +                echo "\${params.Greeting} World!"
    +            }
    +        }
    +    }
    +}
    +

    parameters항목내에 매개변수의 데이터 유형(e.g. string)을 정의합니다. name은 값을 담고있는 변수이고 defaultValue의 값을 반환합니다. Pipeline에 정의된 parametersparams내에 정의 되므로 \${params.매개변수이름}과 같은 형태로 호출 됩니다.

    저장 후 다시 구성을 확인하면 이 빌드는 매개변수가 있습니다가 활성화 되고 내부에 추가된 매개변수 항목을 확인 할 수 있습니다.

    1563944944350
    1563944944350

    이렇게 저장된 Pipeline Job은 매개변수를 외부로부터 받을 수 있습니다. 따라서 좌측의 기존 Build Nowbuild with Parameters로 변경되었고, 이를 클릭하면 Greeting을 정의할 수 있는 UI가 나타납니다. 해당 매개변수를 재정의 하여 빌드를 수행할 수 있습니다.

    1563944733249
    1563944733249
    1563944765637
    1563944765637

    2.4 Creating multiple steps for a job

    다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-04.MultiStep)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +                sh '''
    +                    echo "Multiline shell steps works too"
    +                    ls -lah
    +                '''
    +            }
    +        }
    +    }
    +}
    +

    '''은 스크립트 정의 시 여러줄을 입력할 수 있도록 묶어주는 역할을 합니다. 해당 스크립트에서는 sh로 구분된 스크립트 명령줄이 두번 수행됩니다.

    1563945323777
    1563945323777

    실행되는 여러 스크립트의 수행을 stage로 구분하기위해 기존 Pipeline 스크립트를 다음과 같이 수정합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build-1') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('Build-2') {
    +            steps {
    +                sh '''
    +                    echo "Multiline shell steps works too"
    +                    ls -lah
    +                '''
    +            }
    +        }
    +    }
    +}
    +

    stage를 구분하였기 때문에 각 실행되는 sh 스크립트는 각 스테이지에서 한번씩 수행되며, 이는 빌드의 결과로 나타납니다.

    1563945539114
    1563945539114

    2.5 Adding scripts as a job step

    Pipeline의 step을 추가하여 결과를 확인하는 과정을 설명합니다. 피보나치 수열을 수행하는 쉘 스크립트를 시간제한을 두어 수행하고 그 결과를 확인합니다.

    ',28),_={href:"https://namu.wiki/w/%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98%20%EC%88%98%EC%97%B4",target:"_blank",rel:"noopener noreferrer"},y={href:"https://namu.wiki/w/%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98",target:"_blank",rel:"noopener noreferrer"},P=t(`
    $ mkdir -p /var/jenkins_home/scripts
    +$ cd /var/jenkins_home/scripts
    +$ vi ./fibonacci.sh
    +#!/bin/bash
    +N=\${1:-10}
    +
    +a=0
    +b=1
    +
    +echo "The Fibonacci series is : "
    +
    +for (( i=0; i<N; i++ ))
    +do
    +    echo "$a"
    +    sleep 2
    +    fn=$((a + b))
    +    a=$b
    +    b=$fn
    +done
    +# End of for loop
    +
    +$ chown -R jenkins /var/jenkins_home/
    +$ chmod +x /var/jenkins_home/scripts/fibonacci.sh
    +

    다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-05.AddingStep)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Deploy') {
    +            steps {
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh '/var/jenkins_home/scripts/fibonacci.sh 5'
    +                }
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh '/var/jenkins_home/scripts/fibonacci.sh 32'
    +                }
    +            }
    +        }
    +    }
    +}
    +

    steps에 스크립트를 timeout이 감싸고 있으며, 각 스크립트의 제한시간은 1분입니다. 빌드를 수행하면 최종적으로는 aborted, 즉 중단됨 상태가 되는데 그 이유는 빌드 기록에서 해당 빌드를 클릭하면 확인 가능합니다.

    `,6);function x(w,j){const l=i("Mermaid"),a=i("ExternalLinkIcon");return m(),b("div",null,[k,h,s(l,{id:"mermaid-6",code:"eJxtjk0KwkAMRveeYpbpolcQpr8qCmLBfabEOlJmJKZYb68NIhaa5XuP8HWM96vZn1bmcxZ20SUmTdcZWPcQxlaOHG/USqJBNjlTwQGF/bigapjBWuEGKiZq5NXTgt1+v+UxXHw3MIqPYdZZ7XJoZHDTwD9YwNnT8wcLhSWUoxAH7FW8AUDhPw0="}),f,n("p",null,[e("Jenkins가 설치된 서버에 [피보나치 수열](["),n("a",_,[e("https://namu.wiki/w/피보나치 수열"),s(a)]),e("]("),n("a",y,[e("https://namu.wiki/w/피보나치"),s(a)]),e(" 수열))을 수행하는 스크립트를 작성합니다. Sleep이 있기 때문에 일정 시간 이상 소요 됩니다.")]),P])}const E=v(g,[["render",x],["__file","02-jobs.html.vue"]]),T=JSON.parse(`{"path":"/05-Software/Jenkins/pipeline101/02-jobs.html","title":"2. Jobs","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/02-jobs.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2. Jobs"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2. Jobs\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"2.1 New pipeline","slug":"_2-1-new-pipeline","link":"#_2-1-new-pipeline","children":[]},{"level":2,"title":"2.2 New pipeline","slug":"_2-2-new-pipeline","link":"#_2-2-new-pipeline","children":[]},{"level":2,"title":"2.3 Parameterizing a job","slug":"_2-3-parameterizing-a-job","link":"#_2-3-parameterizing-a-job","children":[]},{"level":2,"title":"2.4 Creating multiple steps for a job","slug":"_2-4-creating-multiple-steps-for-a-job","link":"#_2-4-creating-multiple-steps-for-a-job","children":[]},{"level":2,"title":"2.5 Adding scripts as a job step","slug":"_2-5-adding-scripts-as-a-job-step","link":"#_2-5-adding-scripts-as-a-job-step","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.83,"words":548},"filePathRelative":"05-Software/Jenkins/pipeline101/02-jobs.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    프로젝트는 Job의 일부 입니다. 즉, 모든 프로젝트가 Job이지만 모든 Job이 프로젝트는 아닙니다. Job의 구조는 다음과 같습니다.

    \\n

    FreeStyleProejct, MatrixProject, ExternalJob만 New job에 표시됩니다.

    \\n

    2.1 New pipeline

    \\n

    Step 1에서는 stage없이 기본 Pipeline을 실행하여 수행 테스트를 합니다.

    \\n
      \\n
    1. \\n

      Jenkins 로그인

      \\n
    2. \\n
    3. \\n

      좌측 새로운 Item 클릭

      \\n
    4. \\n
    5. \\n

      Enter an item name에 Job 이름 설정 (e.g. 2.Jobs)

      \\n
    6. \\n
    7. \\n

      Pipeline 선택 후 OK 버튼 클릭

      \\n
    8. \\n
    9. \\n

      Pipeline 항목 오른 쪽 Try sample Pipelie...클릭하여 Hello world 클릭 후 저장

      \\n
      node {\\n   echo 'Hello World'\\n}\\n
    10. \\n
    11. \\n

      좌측 Build now클릭

      \\n
    12. \\n
    13. \\n

      좌측 Build History의 최근 빌드된 항목(e.g. #1) 우측에 마우스를 가져가면 dropdown 버튼이 생깁니다. 해당 버튼을 클릭하여 Console Output 클릭

      \\n
    14. \\n
    15. \\n

      수행된 echo 동작 출력을 확인합니다.

      \\n
      Started by user GyuSeok.Lee\\nRunning in Durability level: MAX_SURVIVABILITY\\n[Pipeline] Start of Pipeline\\n[Pipeline] node\\nRunning on Jenkins in /var/lib/jenkins/workspace/2.Jobs\\n[Pipeline] {\\n[Pipeline] echo\\nHello World\\n[Pipeline] }\\n[Pipeline] // node\\n[Pipeline] End of Pipeline\\nFinished: SUCCESS\\n
    16. \\n
    "}`);export{E as comp,T as data}; diff --git a/assets/02-kops-network-storage.html-CvfHSXZ8.js b/assets/02-kops-network-storage.html-CvfHSXZ8.js new file mode 100644 index 0000000000..9a37459b25 --- /dev/null +++ b/assets/02-kops-network-storage.html-CvfHSXZ8.js @@ -0,0 +1,436 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as p,c,b as n,d as s,a as e,e as t}from"./app-Bzk8Nrll.js";const o={},r=t(`

    [PKOS] 2편 - 네트워크 & 스토리지

    지난 1주차 스터디에이어 2주차 스터디를 진행하였습니다. 이번 스터디에서는 "쿠버네티스 네트워크" 및 "쿠버네티스 스토리지"를 중심으로 학습하였습니다.

    참고 :
    원활한 실습을 위해 인스턴스 타입을 변경한 후 진행합니다.

    0. 사전준비

    1) Kops 클러스터의 인스턴 그룹 변경

    kops get ig
    +NAME			ROLE	MACHINETYPE	MIN	MAX	ZONES
    +master-ap-northeast-2a	Master	t3.medium	1	1	ap-northeast-2a
    +nodes-ap-northeast-2a	Node	t3.medium	1	1	ap-northeast-2a
    +nodes-ap-northeast-2c	Node	t3.medium	1	1	ap-northeast-2c
    +
    kops edit ig master-ap-northeast-2a
    +
    +# 예제화면
    +apiVersion: kops.k8s.io/v1alpha2
    +kind: InstanceGroup
    +metadata:
    +  creationTimestamp: "2023-03-05T13:37:26Z"
    +  labels:
    +    kops.k8s.io/cluster: pkos.hyungwook.link
    +  name: master-ap-northeast-2a
    +spec:
    +  image: 099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20230112
    +  instanceMetadata:
    +    httpPutResponseHopLimit: 3
    +    httpTokens: required
    +  machineType: t3.medium #기존 t3.medium에서 c5d.large로 변경
    +  maxSize: 1
    +  minSize: 1
    +  role: Master
    +  subnets:
    +  - ap-northeast-2a
    +
    kops get ig
    +NAME			ROLE	MACHINETYPE	MIN	MAX	ZONES
    +master-ap-northeast-2a	Master	c5d.large	1	1	ap-northeast-2a
    +nodes-ap-northeast-2a	Node	c5d.large	1	1	ap-northeast-2a
    +nodes-ap-northeast-2c	Node	c5d.large	1	1	ap-northeast-2c
    +
    kops update cluster --name pkos.hyungwook.link --yes
    +
    +kops rolling-update cluster --yes
    +

    2. NLB

    1) NLB Mode 정리

    (1) 인스턴스 유형

    1. externalTrafficPolicy : ClusterIP ⇒ 2번 분산 및 SNAT으로 Client IP 확인 불가능 ← LoadBalancer 타입 (기본 모드) 동작
    2. externalTrafficPolicy : Local ⇒ 1번 분산 및 ClientIP 유지, 워커 노드의 iptables 사용함
    NLB 이미지1
    NLB 이미지1
    NLB 이미지2
    NLB 이미지2

    (2) IP 유형

    반드시 AWS LoadBalancer 컨트롤러 파드 및 정책 설정이 필요함!

    1. Proxy Protocol v2 비활성화 ⇒ NLB에서 바로 파드로 인입, 단 ClientIP가 NLB로 SNAT 되어 Client IP 확인 불가능
    2. Proxy Protocol v2 활성화 ⇒ NLB에서 바로 파드로 인입 및 ClientIP 확인 가능(→ 단 PPv2 를 애플리케이션이 인지할 수 있게 설정 필요)

    2) 서비스 배포

    # 작업용 EC2 - 디플로이먼트 & 서비스 생성
    +cat ~/pkos/2/echo-service-nlb.yaml | yh
    +kubectl apply -f ~/pkos/2/echo-service-nlb.yaml
    +
    +# 확인
    +kubectl get deploy,pod
    +kubectl get svc,ep,ingressclassparams,targetgroupbindings
    +
    +NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP                                                                         PORT(S)        AGE
    +service/kubernetes        ClusterIP      100.64.0.1       <none>                                                                              443/TCP        7d
    +service/svc-nlb-ip-type   LoadBalancer   100.64.191.200   k8s-default-svcnlbip-bfcad9371a-250be02681485d95.elb.ap-northeast-2.amazonaws.com   80:31206/TCP   97s
    +
    +NAME                        ENDPOINTS                             AGE
    +endpoints/kubernetes        172.30.37.41:443                      7d
    +endpoints/svc-nlb-ip-type   172.30.55.31:8080,172.30.71.86:8080   97s
    +
    +NAME                                   GROUP-NAME   SCHEME   IP-ADDRESS-TYPE   AGE
    +ingressclassparams.elbv2.k8s.aws/alb                                           122m
    +
    +NAME                                                        SERVICE-NAME      SERVICE-PORT   TARGET-TYPE   AGE
    +targetgroupbinding.elbv2.k8s.aws/k8s-default-svcnlbip-c54bafee9a   svc-nlb-ip-type   80      ip            95s
    +
    +kubectl get targetgroupbindings -o json | jq
    +
    Listener
    Listener
    TargetGroup 확인
    TargetGroup 확인
    k get pods -o wide
    +NAME               READY    STATUS   RESTARTS   AGE     IP         NODE        NOMINATED NODE   READINESS GATES
    +deploy-echo-(생략)   1/1     Running   0    7m50s   172.30.55.31   i-089062ff9f50789ee   <none>           <none>
    +deploy-echo-(생략)   1/1     Running   0    7m50s   172.30.71.86   i-096a645be0dd932b6   <none>           <none>
    +

    3) 접속확인

    접속확인
    접속확인

    3. NLB에 TLS 적용하기(feat. ACM)

    사전 준비 :
    공인도메인 소유, AWS Route53 도메인등록 상태, NLB 가 위치한 리전(서울)의 인증서 요청/발급 완료상태, ExternalDNS 준비완료 상태

    1) 환경구성

    # 사용 리전의 인증서 ARN 확인
    +aws acm list-certificates
    +aws acm list-certificates --max-items 10
    +aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text
    +CERT_ARN=\`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text\`
    +echo $CERT_ARN
    +
    # 자신의 도메인 변수 지정
    +MyDomain=<자신의 도메인>
    +MyDomain=websrv.$KOPS_CLUSTER_NAME
    +

    2) 샘플 애플리케이션 배포

    cat <<EOF | kubectl create -f -
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: deploy-echo
    +spec:
    +  replicas: 2
    +  selector:
    +    matchLabels:
    +      app: deploy-websrv
    +  template:
    +    metadata:
    +      labels:
    +        app: deploy-websrv
    +    spec:
    +      terminationGracePeriodSeconds: 0
    +      containers:
    +      - name: akos-websrv
    +        image: k8s.gcr.io/echoserver:1.5
    +        ports:
    +        - containerPort: 8080
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: svc-nlb-ip-type
    +  annotations:
    +    external-dns.alpha.kubernetes.io/hostname: "\${MyDomain}"
    +    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    +    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    +    service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
    +    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    +    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
    +    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: ${CERT_ARN}
    +    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
    +spec:
    +  ports:
    +    - port: 80
    +      targetPort: 8080
    +      protocol: TCP
    +      name: http
    +    - port: 443
    +      targetPort: 8080
    +      protocol: TCP
    +      name: https
    +  type: LoadBalancer
    +  loadBalancerClass: service.k8s.aws/nlb
    +  selector:
    +    app: deploy-websrv
    +EOF
    +
    kubectl describe svc svc-nlb-ip-type | grep Annotations: -A8
    +
    +Annotations:              external-dns.alpha.kubernetes.io/hostname: websrv.pkos.hyungwook.link
    +                          service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    +                          service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: true
    +                          service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: 8080
    +                          service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    +                          service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    +                          service.beta.kubernetes.io/aws-load-balancer-ssl-cert:
    +                            arn:aws:acm:ap-northeast-2:856117747411:certificate/208e809e-9ebf-4bb5-92c2-795868429e88
    +                          service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https
    +

    3) 인증서 적용 확인

    (1) CLI 확인

    curl -s http://websrv.pkos.hyungwook.link | grep Hostname
    +Hostname: deploy-echo-5c4856dfd6-267pf
    +
    +curl -s  https://websrv.pkos.hyungwook.link | grep Hostname
    +Hostname: deploy-echo-5c4856dfd6-k9277
    +

    (2) 웹 브라우저 확인

    인증서 적용 확인
    인증서 적용 확인

    3. Ingress

    클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) - Web Proxy 역할

    ingress
    ingress

    1) 환경구성

    # EC2 instance profiles 에 IAM Policy 추가(attach)
    +aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
    +aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME
    +
    +# EC2 instance profiles 에 IAM Policy 추가(attach)
    +aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$KOPS_CLUSTER_NAME
    +aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$KOPS_CLUSTER_NAME
    +
    # kOps 클러스터 편집 : 아래 내용 추가
    +kops edit cluster
    +-----
    +spec:
    +  certManager:
    +    enabled: true
    +  awsLoadBalancerController:
    +    enabled: true
    +  externalDns:
    +    provider: external-dns
    +
    # 업데이트 적용
    +kops update cluster --yes && echo && sleep 3 && kops rolling-update cluster
    +

    2) 서비스/파드 배포 테스트 with Ingress(ALB)\\

    # 게임 파드와 Service, Ingress 배포
    +kubectl apply -f ~/pkos/3/ingress1.yaml
    +
    kubectl get targetgroupbindings -n game-2048
    +NAME                               SERVICE-NAME   SERVICE-PORT   TARGET-TYPE   AGE
    +k8s-game2048-service2-e48050abac   service-2048   80             ip            87s
    +
    kubectl describe ingress -n game-2048 ingress-2048
    +
    +Name:             ingress-2048
    +Labels:           <none>
    +Namespace:        game-2048
    +Address:          k8s-game2048-ingress2-fdfe8009a9-1424012699.ap-northeast-2.elb.amazonaws.com
    +Ingress Class:    alb
    +Default backend:  <default>
    +Rules:
    +  Host        Path  Backends
    +  ----        ----  --------
    +  *
    +              /   service-2048:80 (172.30.44.132:80,172.30.65.100:80)
    +Annotations:  alb.ingress.kubernetes.io/scheme: internet-facing
    +              alb.ingress.kubernetes.io/target-type: ip
    +Events:
    +  Type    Reason                  Age    From     Message
    +  ----    ------                  ----   ----     -------
    +  Normal  SuccessfullyReconciled  8m56s  ingress  Successfully reconciled
    +
    # 게임 접속 : ALB 주소로 웹 접속
    +kubectl get ingress -n game-2048 ingress-2048 -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" | awk '{ print "Game URL = http://"$1 }'
    +Game URL = http://k8s-game2048-ingress2-fdfe8009a9-1424012699.ap-northeast-2.elb.amazonaws.com
    +
    게임화면
    게임화면
    kubectl delete ingress ingress-2048 -n game-2048
    +kubectl delete svc service-2048 -n game-2048 && kubectl delete deploy deployment-2048 -n game-2048 && kubectl delete ns game-2048
    +

    4. 쿠버네티스 스토리지 : EBS

    1) 환경구성

    `,75),u=n("code",null,"c5d.large",-1),d={href:"https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/InstanceStorage.html",target:"_blank",rel:"noopener noreferrer"},v={href:"https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/ssd-instance-store.html",target:"_blank",rel:"noopener noreferrer"},m=t(`
           Amazon EC2 인스턴스 스토리지
    Amazon EC2 인스턴스 스토리지
    # 인스턴스 스토어 볼륨이 있는 c5 모든 타입의 스토리지 크기
    +aws ec2 describe-instance-types \\
    + --filters "Name=instance-type,Values=c5*" "Name=instance-storage-supported,Values=true" \\
    + --query "InstanceTypes[].[InstanceType, InstanceStorageInfo.TotalSizeInGB]" \\
    + --output table
    +--------------------------
    +|  DescribeInstanceTypes |
    ++---------------+--------+
    +|  c5d.large    |  50    |
    +|  c5d.12xlarge |  1800  |
    +...
    +
    +# 워커 노드 Public IP 확인
    +aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value}" --filters Name=instance-state-name,Values=running --output table
    +
    +-------------------------------------------------------------------------
    +|                           DescribeInstances                           |
    ++------------------------------------------------------+----------------+
    +|                     InstanceName                     |  PublicIPAdd   |
    ++------------------------------------------------------+----------------+
    +|  nodes-ap-northeast-2c.pkos.hyungwook.link           |  13.209.75.228 |
    +|  master-ap-northeast-2a.masters.pkos.hyungwook.link  |  3.38.117.78   |
    +|  nodes-ap-northeast-2a.pkos.hyungwook.link           |  52.79.61.228  |
    ++------------------------------------------------------+----------------+
    +
    +# 워커 노드 Public IP 변수 지정
    +W1PIP=<워커 노드 1 Public IP>
    +W2PIP=<워커 노드 2 Public IP>
    +W1PIP=13.209.75.228
    +W2PIP=52.79.61.228
    +echo "export W1PIP=$W1PIP" >> /etc/profile
    +echo "export W2PIP=$W2PIP" >> /etc/profile
    +
    ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo apt install -y nvme-cli
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo apt install -y nvme-cli
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo nvme list
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo nvme list
    +
    nvme_list
    nvme_list
    # 워커 노드 스토리지 확인 : NVMe SSD 인스턴스 스토어 볼륨 확인
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP lsblk -e 7
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP lsblk -e 7
    +NAME         MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
    +nvme1n1      259:0    0  46.6G  0 disk
    +nvme0n1      259:1    0   128G  0 disk
    +├─nvme0n1p1  259:2    0 127.9G  0 part /
    +├─nvme0n1p14 259:3    0     4M  0 part
    +└─nvme0n1p15 259:4    0   106M  0 part /boot/efi
    +
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP df -hT -t ext4
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP df -hT -t ext4
    +Filesystem     Type  Size  Used Avail Use% Mounted on
    +/dev/root      ext4  124G  4.2G  120G   4% /
    +
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP lspci | grep Non-Volatile
    +00:04.0 Non-Volatile memory controller: Amazon.com, Inc. Device 8061
    +00:1f.0 Non-Volatile memory controller: Amazon.com, Inc. NVMe SSD Controller
    +
    +# 파일시스템 생성
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo mkfs -t xfs /dev/nvme1n1
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo mkfs -t xfs /dev/nvme1n1
    +
    +# /data 디렉토리 생성 
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo mkdir /data
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo mkdir /data
    +
    +# /data 마운트
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP sudo mount /dev/nvme1n1 /data
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP sudo mount /dev/nvme1n1 /data
    +
    +# 파일시스템 포맷 및 마운트 확인
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP df -hT -t ext4 -t xfs
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP df -hT -t ext4 -t xfs
    +Filesystem     Type  Size  Used Avail Use% Mounted on
    +/dev/root      ext4  124G  4.2G  120G   4% /
    +/dev/nvme1n1   xfs    47G  365M   47G   1% /data
    +

    2) HostPath 실습

    (1) HostPath스토리지 클래스 배포

    `,10),b={href:"https://github.com/rancher/local-path-provisioner",target:"_blank",rel:"noopener noreferrer"},k=t(`
    # 마스터노드의 이름 확인해두기
    +kubectl get node | grep control-plane | awk '{print $1}'
    +i-066cdb714fc6545c0
    +
    +# 배포 : vim 직접 편집할것
    +curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.23/deploy/local-path-storage.yaml
    +vim local-path-storage.yaml
    +----------------------------
    +# 아래 빨간 부분은 추가 및 수정
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: local-path-provisioner
    +  namespace: local-path-storage
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: local-path-provisioner
    +  template:
    +    metadata:
    +      labels:
    +        app: local-path-provisioner
    +    spec:
    +      nodeSelector:
    +        kubernetes.io/hostname: "<각자 자신의 마스터 노드 이름 입력>"
    +      tolerations:
    +        - effect: NoSchedule
    +          key: node-role.kubernetes.io/control-plane
    +          operator: Exists
    +...
    +kind: ConfigMap
    +apiVersion: v1
    +metadata:
    +  name: local-path-config
    +  namespace: local-path-storage
    +data:
    +  config.json: |-
    +    {
    +            "nodePathMap":[
    +            {
    +                    "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
    +                    "paths":["/data/local-path"]
    +            }
    +            ]
    +    }
    +----------------------------
    +
    +# 배포
    +kubectl apply -f local-path-storage.yaml
    +
    +# 확인 : 마스터노드에 배포됨
    +kubectl get-all -n local-path-storage
    +NAME                                                   NAMESPACE           AGE
    +configmap/kube-root-ca.crt                             local-path-storage  12s
    +configmap/local-path-config                            local-path-storage  12s
    +pod/local-path-provisioner-6bff65dcd8-vgwfk            local-path-storage  12s
    +serviceaccount/default                                 local-path-storage  12s
    +serviceaccount/local-path-provisioner-service-account  local-path-storage  12s
    +deployment.apps/local-path-provisioner                 local-path-storage  12s
    +replicaset.apps/local-path-provisioner-6bff65dcd8      local-path-storage  12s
    +
    +kubectl get pod -n local-path-storage -owide
    +NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
    +local-path-provisioner-6bff65dcd8-vgwfk   1/1     Running   0          17s   172.30.63.103   i-072786762169226a7   <none>           <none>
    +
    +kubectl describe cm -n local-path-storage local-path-config
    +
    +kubectl get sc local-path
    +NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
    +local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  34s
    +

    (2) PV / PVC 샘플 배포

    # PVC 생성
    +kubectl apply -f ~/pkos/3/localpath1.yaml
    +
    +# PVC 확인 : 아직 Pod Boud가 되지 않았으므로 Pending
    +kubectl describe pvc
    +kubectl get pvc
    +
    +NAME              STATUS    VOLUME                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
    +localpath-claim   Pending                                                        local-path      58s
    +
    +# 파드 생성
    +kubectl apply -f ~/pkos/3/localpath2.yaml
    +
    +# 파드확인 : 정상적으로 Bound된 것으로 확인
    +kubectl get pod,pv,pvc
    +
    +NAME                READY   STATUS    RESTARTS   AGE
    +pod/app-localpath   1/1     Running   0          56s
    +
    +NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS    REASON   AGE
    +persistentvolume/pvc-37743f20-e30d-491c-b11c-5e5b7d33a476   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path               49s
    +
    +NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
    +persistentvolumeclaim/localpath-claim   Bound    pvc-37743f20-e30d-491c-b11c-5e5b7d33a476   1Gi        RWO            local-path      3m57s
    +
    # 파드 데이터 확인 : app-localpath 파드에서 데이터 쌓이는 것 확인
    +kubectl exec -it app-localpath -- tail -f /data/out.txt
    +Sun Jan 29 05:13:45 UTC 2023
    +
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP tree /data
    +/data
    +0 directories, 0 files
    +
    +ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP tree /data
    +/data
    +└── local-path
    +    └── pvc-37743f20-e30d-491c-b11c-5e5b7d33a476_default_localpath-claim
    +        └── out.txt
    +
    +2 directories, 1 file
    +
    +# 노드 데이터 확인 : app-localpath 파드가 배포된 노드에 접속 후 데이터 쌓이는 것 확인
    +ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP tail -f /data/local-path/pvc-ce742b90-755a-4b52-9693-595cbf55dfb0_default_localpath-claim/out.txt
    +Sun Jan 29 05:13:45 UTC 2023
    +...
    +
    데이터확인
    데이터확인

    LocalPath 성능측정은 추후 별도 정리 후 업로드 예정

    3) AWS EBS Controller

    `,8),h={href:"https://github.com/kubernetes-sigs/aws-ebs-csi-driver",target:"_blank",rel:"noopener noreferrer"},g=t(`
    EBS Controller
    EBS Controller

    (1) EBS Driver 배포확인

    # EBS Driver 확인 Kops 설치 시 기본 배포
    +kubectl get pod -n kube-system -l app.kubernetes.io/instance=aws-ebs-csi-driver
    +
    +NAME                                  READY   STATUS    RESTARTS   AGE
    +ebs-csi-controller-6d8fd64c78-q5qfn   5/5     Running   0          5d23h
    +ebs-csi-node-9cfss                    3/3     Running   0          5d23h
    +ebs-csi-node-crhbx                    3/3     Running   0          5d23h
    +ebs-csi-node-zbjgj                    3/3     Running   0          5d23h
    +
    +# 스토리지 클래스 확인
    +kubectl get sc kops-csi-1-21 kops-ssd-1-17
    +
    +kubectl describe sc kops-csi-1-21 | grep Parameters
    +Parameters:            encrypted=true,type=gp3
    +kubectl describe sc kops-ssd-1-17 | grep Parameters
    +Parameters:            encrypted=true,type=gp2
    +
    +

    (2) PV / PVC 샘플 배포

    # PVC 생성
    +kubectl apply -f ~/pkos/3/awsebs-pvc.yaml
    +
    +# 파드 생성
    +kubectl apply -f ~/pkos/3/awsebs-pod.yaml
    +
    +# PVC, 파드 확인
    +kubectl get pvc,pv,pod
    +NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
    +persistentvolumeclaim/ebs-claim   Bound    pvc-fb5b5e1c-76ef-4a43-9b94-9af2b1b1e5f7   4Gi        RWO            kops-csi-1-21   41m
    +
    +NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS    REASON   AGE
    +persistentvolume/pvc-fb5b5e1c-76ef-4a43-9b94-9af2b1b1e5f7   4Gi        RWO            Delete           Bound    default/ebs-claim   kops-csi-1-21            40m
    +
    +NAME      READY   STATUS    RESTARTS   AGE
    +pod/app   1/1     Running   0          3m15s
    +
    +# 실제 쌓이는 데이터 확인
    +kubectl exec app -- tail -f /data/out.txt
    +
    +Sat Mar 18 14:49:25 UTC 2023
    +Sat Mar 18 14:49:30 UTC 2023
    +Sat Mar 18 14:49:35 UTC 2023
    +...
    +
    +# 파드 내에서 볼륨 정보 확인
    +kubectl exec -it app -- sh -c 'df -hT --type=ext4'
    +Filesystem     Type  Size  Used Avail Use% Mounted on
    +/dev/nvme1n1   ext4  3.9G   16M  3.8G   1% /data
    +/dev/root      ext4  124G  4.9G  120G   4% /etc/hosts
    +
    +# 추가된 EBS 볼륨 상세 정보 확인 
    +while true; do aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" --output text; date; sleep 1; done
    +
    +(중략)
    +i-078613f7b7cd9e352	attached	vol-071ebb777dc2ac3cd	gp3 # 시간이 지난 후 추가되는 것 확인
    +

    (3) (번외) PVC 데이터 증설

    # 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경
    +kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
    +kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
    +kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
    +
    +# 확인 : 볼륨 용량 수정 반영이 되어야 되니, 수치 반영이 조금 느릴수 있다
    +kubectl exec -it app -- sh -c 'df -hT --type=ext4'
    +kubectl df-pv
    +aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq
    +
    kubectl delete pod app & kubectl delete pvc ebs-claim
    +

    5. 마무리

    이번 글에서는 Kops 환경에서의 네트워크와 스토리지를 활용하는 방법을 정리해보았습니다.

    일반적인 K8s와 달리 AWS 환경에서 EKS, Kops 등을 활용하게 된다면, AWS 리소스와의 연계를 통해 관리의 효율성과 편의성을 체감할 수 있었습니다.

    다만, K8s만 알고 AWS는 잘 모르거나 또 그 반대의 상황에는 진입장벽이 있을 수 있겠다는 생각이 들었네요. 물론 이러한 부분만 해소된다면 관리형 쿠버네티스를 200% 활용할 수 있을 것 같습니다.

    다음시간에는 GitOps와 관련된 주제로 찾아올 예정입니다.

    `,15);function f(y,P){const a=i("ExternalLinkIcon");return p(),c("div",null,[r,n("p",null,[u,s(" 의 EC2 인스턴스 스토어(임시 블록 스토리지) 설정 작업 - "),n("a",d,[s("링크"),e(a)]),s(" , NVMe SSD - "),n("a",v,[s("링크"),e(a)])]),m,n("ul",null,[n("li",null,[s("호스트 Path 를 사용하는 PV/PVC : local-path-provisioner 스트리지 클래스 배포 - "),n("a",b,[s("링크"),e(a)])])]),k,n("p",null,[s("Volume (ebs-csi-controller) : EBS CSI driver 동작 : 볼륨 생성 및 파드에 볼륨 연결 - "),n("a",h,[s("링크"),e(a)])]),g])}const S=l(o,[["render",f],["__file","02-kops-network-storage.html.vue"]]),E=JSON.parse('{"path":"/02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.html","title":"[PKOS] 2편 - 네트워크 & 스토리지","lang":"ko-KR","frontmatter":{"description":"AWS Kops 설치 및 기본 사용","tag":["Kubernetes","Kops","EKS","PKOS"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"[PKOS] 2편 - 네트워크 & 스토리지"}],["meta",{"property":"og:description","content":"AWS Kops 설치 및 기본 사용"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/IMeYr3.jpg"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"[PKOS] 2편 - 네트워크 & 스토리지"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:tag","content":"Kops"}],["meta",{"property":"article:tag","content":"EKS"}],["meta",{"property":"article:tag","content":"PKOS"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"[PKOS] 2편 - 네트워크 & 스토리지\\",\\"image\\":[\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/IMeYr3.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/rV5KOK.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/rZx1tE.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/kQcEaN.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/uVSCZA.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/OPTdsH.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/u5cdKs.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/KTZTUM.jpg\\",\\"https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/images/instance_storage.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/6iX9dg.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/Y6CYPg.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/dGge8O.jpg\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"0. 사전준비","slug":"_0-사전준비","link":"#_0-사전준비","children":[{"level":3,"title":"1) Kops 클러스터의 인스턴 그룹 변경","slug":"_1-kops-클러스터의-인스턴-그룹-변경","link":"#_1-kops-클러스터의-인스턴-그룹-변경","children":[]}]},{"level":2,"title":"2. NLB","slug":"_2-nlb","link":"#_2-nlb","children":[{"level":3,"title":"1) NLB Mode 정리","slug":"_1-nlb-mode-정리","link":"#_1-nlb-mode-정리","children":[]},{"level":3,"title":"2) 서비스 배포","slug":"_2-서비스-배포","link":"#_2-서비스-배포","children":[]},{"level":3,"title":"3) 접속확인","slug":"_3-접속확인","link":"#_3-접속확인","children":[]}]},{"level":2,"title":"3. NLB에 TLS 적용하기(feat. ACM)","slug":"_3-nlb에-tls-적용하기-feat-acm","link":"#_3-nlb에-tls-적용하기-feat-acm","children":[{"level":3,"title":"1) 환경구성","slug":"_1-환경구성","link":"#_1-환경구성","children":[]},{"level":3,"title":"2) 샘플 애플리케이션 배포","slug":"_2-샘플-애플리케이션-배포","link":"#_2-샘플-애플리케이션-배포","children":[]},{"level":3,"title":"3) 인증서 적용 확인","slug":"_3-인증서-적용-확인","link":"#_3-인증서-적용-확인","children":[]}]},{"level":2,"title":"3. Ingress","slug":"_3-ingress","link":"#_3-ingress","children":[{"level":3,"title":"1) 환경구성","slug":"_1-환경구성-1","link":"#_1-환경구성-1","children":[]},{"level":3,"title":"2) 서비스/파드 배포 테스트 with Ingress(ALB)\\\\","slug":"_2-서비스-파드-배포-테스트-with-ingress-alb","link":"#_2-서비스-파드-배포-테스트-with-ingress-alb","children":[]}]},{"level":2,"title":"4. 쿠버네티스 스토리지 : EBS","slug":"_4-쿠버네티스-스토리지-ebs","link":"#_4-쿠버네티스-스토리지-ebs","children":[{"level":3,"title":"1) 환경구성","slug":"_1-환경구성-2","link":"#_1-환경구성-2","children":[]},{"level":3,"title":"2) HostPath 실습","slug":"_2-hostpath-실습","link":"#_2-hostpath-실습","children":[]},{"level":3,"title":"3) AWS EBS Controller","slug":"_3-aws-ebs-controller","link":"#_3-aws-ebs-controller","children":[]}]},{"level":2,"title":"5. 마무리","slug":"_5-마무리","link":"#_5-마무리","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":7.23,"words":2169},"filePathRelative":"02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    지난 1주차 스터디에이어 2주차 스터디를 진행하였습니다. 이번 스터디에서는 \\"쿠버네티스 네트워크\\" 및 \\"쿠버네티스 스토리지\\"를 중심으로 학습하였습니다.

    \\n
    \\n

    참고 :
    \\n원활한 실습을 위해 인스턴스 타입을 변경한 후 진행합니다.

    \\n
    \\n

    0. 사전준비

    \\n

    1) Kops 클러스터의 인스턴 그룹 변경

    \\n
    kops get ig\\nNAME\\t\\t\\tROLE\\tMACHINETYPE\\tMIN\\tMAX\\tZONES\\nmaster-ap-northeast-2a\\tMaster\\tt3.medium\\t1\\t1\\tap-northeast-2a\\nnodes-ap-northeast-2a\\tNode\\tt3.medium\\t1\\t1\\tap-northeast-2a\\nnodes-ap-northeast-2c\\tNode\\tt3.medium\\t1\\t1\\tap-northeast-2c\\n
    "}');export{S as comp,E as data}; diff --git a/assets/02-sidecar-200-BKyjC3GP.png b/assets/02-sidecar-200-BKyjC3GP.png new file mode 100644 index 0000000000..a10bf25fcc Binary files /dev/null and b/assets/02-sidecar-200-BKyjC3GP.png differ diff --git a/assets/02-sidecar-error-VTAGMmH1.png b/assets/02-sidecar-error-VTAGMmH1.png new file mode 100644 index 0000000000..705d09a6e9 Binary files /dev/null and b/assets/02-sidecar-error-VTAGMmH1.png differ diff --git a/assets/02-sidecar-intention-alldeny-ogH-XLae.png b/assets/02-sidecar-intention-alldeny-ogH-XLae.png new file mode 100644 index 0000000000..b58a16ef37 Binary files /dev/null and b/assets/02-sidecar-intention-alldeny-ogH-XLae.png differ diff --git a/assets/02-sidecar-services-BZgCIlWq.png b/assets/02-sidecar-services-BZgCIlWq.png new file mode 100644 index 0000000000..547283418e Binary files /dev/null and b/assets/02-sidecar-services-BZgCIlWq.png differ diff --git a/assets/02-sidecar-topology-allow-F944RXfc.png b/assets/02-sidecar-topology-allow-F944RXfc.png new file mode 100644 index 0000000000..3a983611cc Binary files /dev/null and b/assets/02-sidecar-topology-allow-F944RXfc.png differ diff --git a/assets/02-sidecar-topology-blocked-BzutBfyM.png b/assets/02-sidecar-topology-blocked-BzutBfyM.png new file mode 100644 index 0000000000..a29a447e9c Binary files /dev/null and b/assets/02-sidecar-topology-blocked-BzutBfyM.png differ diff --git a/assets/02-terraform-basic.html-D7TMDab7.js b/assets/02-terraform-basic.html-D7TMDab7.js new file mode 100644 index 0000000000..a8fd513de1 --- /dev/null +++ b/assets/02-terraform-basic.html-D7TMDab7.js @@ -0,0 +1,59 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c,b as a,d as n,a as e,w as p,e as t}from"./app-Bzk8Nrll.js";const m="/assets/Terraform_Logo-Bt0ckPKZ.png",d={},u=t('

    02. 테라폼 기본

    Terraform 이란?

    logo
    logo
    ',3),h=a("p",null,"Terraform은 오픈 소스 프로비저닝 도구입니다.",-1),f={href:"https://github.com/hashicorp/terraform",target:"_blank",rel:"noopener noreferrer"},b=a("li",null,[a("p",null,"Go로 작성된 단일 바이너리로 제공됩니다. Terraform은 크로스 플랫폼이며 Linux, Windows 또는 MacOS에서 실행할 수 있습니다.")],-1),k=a("p",null,"terraform 설치는 쉽습니다. zip 파일을 다운로드하고 압축을 풀고 실행하기 만하면됩니다.",-1),g={href:"https://www.terraform.io/downloads.html",target:"_blank",rel:"noopener noreferrer"},_=t(`

    Terraform Command Line

    # Basic Terraform Commands
    +terraform version
    +terraform help
    +terraform init
    +terraform plan
    +terraform apply
    +terraform destroy
    +

    Terraform Help

    `,4),v=a("div",{class:"language-bash line-numbers-mode","data-ext":"sh","data-title":"sh"},[a("pre",{bash:"",class:"language-bash"},[a("code",null,[n("$ terraform "),a("span",{class:"token builtin class-name"},"help"),n(` +Usage: terraform `),a("span",{class:"token punctuation"},"["),n("-version"),a("span",{class:"token punctuation"},"]"),n(),a("span",{class:"token punctuation"},"["),n("-help"),a("span",{class:"token punctuation"},"]"),n(),a("span",{class:"token operator"},"<"),n("command"),a("span",{class:"token operator"},">"),n(),a("span",{class:"token punctuation"},"["),n("args"),a("span",{class:"token punctuation"},"]"),n(` +`),a("span",{class:"token punctuation"},".."),n(`. +Common commands: + apply Builds or changes infrastructure + console Interactive console `),a("span",{class:"token keyword"},"for"),n(` Terraform interpolations + destroy Destroy Terraform-managed infrastructure + `),a("span",{class:"token function"},"env"),n(` Workspace management + `),a("span",{class:"token function"},"fmt"),n(" Rewrites config files to canonical "),a("span",{class:"token function"},"format"),n(` + graph Create a visual graph of Terraform resources +`)])]),a("div",{class:"highlight-lines"},[a("div",{class:"highlight-line"}," "),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br")]),a("div",{class:"line-numbers","aria-hidden":"true"},[a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"})])],-1),T=t(`

    특정 하위 명령에 대한 도움말을 보려면 terraform <subcommand> help 를 입력합니다.

    Terraform Code

    resource "ncloud_vpc" "vpc" {
    +    ipv4_cidr_block = "10.0.0.0/16"
    +}
    +
    `,3),w={href:"https://github.com/hashicorp/hcl",target:"_blank",rel:"noopener noreferrer"},y=t(`

    Terraform 코드는 모든 클라우드 또는 플랫폼에서 인프라를 프로비저닝하기 위해 특별히 설계된 선언적 언어입니다.

    Terraform Comments

    줄 주석은 *(octothorpe, 별표) 또는 #(파운드) 기호로 시작합니다....샵! #

    # This is a line comment.
    +

    블록 주석은 /**/ 기호 사이에 포함됩니다.

    /* This is a block comment.
    +Block comments can span multiple lines.
    +The comment ends with this symbol: */
    +

    Terraform Workspaces

    Workspace는 Terraform 코드가 포함 된 폴더 또는 디렉토리입니다.

    Terraform 파일은 항상* .tf 또는* .tfvars 확장자로 끝납니다. 실행 시 해당 파일들은 하나로 동작합니다.

    대부분의 Terraform Workspaces에는 일반적으로 아래 3개정도의 파일을 둡니다. (정해진건 아닙니다.)

    `,10),x={href:"http://main.tf",target:"_blank",rel:"noopener noreferrer"},C={href:"http://variables.tf",target:"_blank",rel:"noopener noreferrer"},P={href:"http://outputs.tf",target:"_blank",rel:"noopener noreferrer"},q=a("h2",{id:"terraform-init",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#terraform-init"},[a("span",null,"Terraform Init")])],-1),L=a("div",{class:"language-hcl","data-ext":"hcl","data-title":"hcl"},[a("pre",{hcl:"",class:"language-hcl"},[a("code",null,[n(`$ terraform init +Initializing the backend... + +Initializing provider plugins... +- Finding navercloudplatform/ncloud versions matching `),a("span",{class:"token string"},'">= 2.1.2"'),n(`... +- Installing navercloudplatform/ncloud v2.`),a("span",{class:"token number"},"1.2"),n(`... +- Installed navercloudplatform/ncloud v2.`),a("span",{class:"token number"},"1.2"),n(" (signed by a HashiCorp partner, key ID "),a("span",{class:"token number"},"9"),n(`DCE24305722E9C9) +... +Terraform has been successfully initialized! +`)])]),a("div",{class:"highlight-lines"},[a("div",{class:"highlight-line"}," "),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br")])],-1),W=a("p",null,[n("Terraform은 필요한 Provider(공급자)와 Module(모듈)을 가져와 "),a("code",null,".terraform"),n(" 디렉터리에 저장합니다. 모듈 또는 공급자를 추가, 변경 또는 업데이트하는 경우 init를 다시 실행해야합니다.")],-1),I=a("h2",{id:"terraform-plan",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#terraform-plan"},[a("span",null,"Terraform Plan")])],-1),N=a("div",{class:"language-bash line-numbers-mode","data-ext":"sh","data-title":"sh"},[a("pre",{bash:"",class:"language-bash"},[a("code",null,[n(`$ terraform plan +`),a("span",{class:"token punctuation"},".."),n(`. +Terraform will perform the following actions: + + `),a("span",{class:"token comment"},"# ncloud_vpc.vpc will be created"),n(` + + resource `),a("span",{class:"token string"},'"ncloud_vpc"'),n(),a("span",{class:"token string"},'"vpc"'),n(),a("span",{class:"token punctuation"},"{"),n(` + + default_access_control_group_no `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + + default_network_acl_no `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + + default_private_route_table_no `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + + default_public_route_table_no `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + + `),a("span",{class:"token function"},"id"),n(" "),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + + ipv4_cidr_block `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token string"},'"10.0.0.0/16"'),n(` + + name `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + + vpc_no `),a("span",{class:"token operator"},"="),n(),a("span",{class:"token punctuation"},"("),n("known after apply"),a("span",{class:"token punctuation"},")"),n(` + `),a("span",{class:"token punctuation"},"}"),n(` + +Plan: `),a("span",{class:"token number"},"1"),n(" to add, "),a("span",{class:"token number"},"0"),n(" to change, "),a("span",{class:"token number"},"0"),n(` to destroy. +`)])]),a("div",{class:"highlight-lines"},[a("div",{class:"highlight-line"}," "),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br")]),a("div",{class:"line-numbers","aria-hidden":"true"},[a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"}),a("div",{class:"line-number"})])],-1),B=t(`

    변경 사항을 적용하기 전에 terraform plan 으로 미리 구성의 변경을 살펴봅니다.

    변수는 어디에?

    Terraform 변수는 variables.tf라는 파일에 일반적으로 위치 시킵니다.(이름은 변경 가능) 변수는 기본 설정을 가질 수 있습니다. 기본값을 생략하면 사용자에게 값을 입력하라는 메시지가 표시됩니다. 여기서 우리는 사용하려는 변수를 선언 합니다.

    variable "prefix" {
    +  description = "This prefix will be included in the name of most resources."
    +}
    +
    +variable "vpc_cidr" {
    +  description = "A cidr option for instances into the VPC."
    +  default     = "10.0.0.0/16"
    +}
    +

    변수에 값을 할당하는 방식과 우선순위

    일부 변수를 정의한 후에는 다른 방법으로 설정하고 재정의 할 수 있습니다. 다음은 각 방법의 우선 순위입니다.

    이 목록은 가장 높은 우선 순위 (1)에서 가장 낮은 순위 (5)로 나타냅니다.

    즉, CLI 실행시 -var 로 지정되는 Command line flag가 가장 우선합니다.

    1. Command line flag - 명령 줄 스위치로 실행
    2. Configuration file - terraform.tfvars 파일에 설정
    3. Environment variable - 쉘 환경의 일부
    4. Default Config - variables.tf의 기본값
    5. User manual entry - 지정되지 않은 경우 사용자에게 입력을 요청합니다.

    실습을 위해 다음장으로 이동하세요.

    `,11);function z(S,E){const r=o("ExternalLinkIcon"),s=o("RouteLink");return i(),c("div",null,[u,a("ul",null,[a("li",null,[h,a("ul",null,[a("li",null,[n("Terraform Github : "),a("a",f,[n("https://github.com/hashicorp/terraform"),e(r)])])])]),b,a("li",null,[k,a("ul",null,[a("li",null,[n("다운로드 : "),a("a",g,[n("https://www.terraform.io/downloads.html"),e(r)])])])])]),_,v,T,a("p",null,[n("Terraform 코드는 "),a("a",w,[n("HCL2 툴킷"),e(r)]),n("을 기반으로합니다. HCL은 HashiCorp Configuration Language를 나타냅니다.")]),y,a("ul",null,[a("li",null,[a("a",x,[n("main.tf"),e(r)]),n(" - 대부분의 기능 코드는 여기에 있습니다.")]),a("li",null,[a("a",C,[n("variables.tf"),e(r)]),n(" - 이 파일은 변수를 저장하기위한 것입니다.")]),a("li",null,[a("a",P,[n("outputs.tf"),e(r)]),n(" - 테라 폼 실행 후 표시되는 내용을 정의합니다.")])]),q,L,W,I,N,B,a("p",null,[e(s,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html"},{default:p(()=>[n("💻 Lab - Setup and Basic Usage")]),_:1})])])}const D=l(d,[["render",z],["__file","02-terraform-basic.html.vue"]]),G=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.html","title":"02. 테라폼 기본","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"02. 테라폼 기본"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"02. 테라폼 기본\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Terraform 이란?","slug":"terraform-이란","link":"#terraform-이란","children":[]},{"level":2,"title":"Terraform Command Line","slug":"terraform-command-line","link":"#terraform-command-line","children":[]},{"level":2,"title":"Terraform Help","slug":"terraform-help","link":"#terraform-help","children":[]},{"level":2,"title":"Terraform Code","slug":"terraform-code","link":"#terraform-code","children":[]},{"level":2,"title":"Terraform Comments","slug":"terraform-comments","link":"#terraform-comments","children":[]},{"level":2,"title":"Terraform Workspaces","slug":"terraform-workspaces","link":"#terraform-workspaces","children":[]},{"level":2,"title":"Terraform Init","slug":"terraform-init","link":"#terraform-init","children":[]},{"level":2,"title":"Terraform Plan","slug":"terraform-plan","link":"#terraform-plan","children":[]},{"level":2,"title":"변수는 어디에?","slug":"변수는-어디에","link":"#변수는-어디에","children":[]},{"level":2,"title":"변수에 값을 할당하는 방식과 우선순위","slug":"변수에-값을-할당하는-방식과-우선순위","link":"#변수에-값을-할당하는-방식과-우선순위","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.24,"words":372},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    Terraform 이란?

    \\n
    logo
    \\n"}');export{D as comp,G as data}; diff --git a/assets/02-z-lab_terraform_basic.html-updYTors.js b/assets/02-z-lab_terraform_basic.html-updYTors.js new file mode 100644 index 0000000000..152f5317f7 --- /dev/null +++ b/assets/02-z-lab_terraform_basic.html-updYTors.js @@ -0,0 +1,62 @@ +import{_ as p}from"./lab1-02-By4gwc7V.js";import{_ as h}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as m,c as u,b as e,d as a,a as n,w as r,e as l}from"./app-Bzk8Nrll.js";const f="/assets/lab1-03-Drco2QJg.png",b="/assets/lab1-04-CYjunJBM.png",v="/assets/lab1-01-BGqIA5Kk.png",g="/assets/lab1-06-BnEJgC0l.png",k="/assets/lab1-07-BARUbauy.png",_="/assets/lab1-08-Dl3VhhS1.png",x="/assets/lab1-09-CyFaaygn.png",w="/assets/lab1-10-C-HDMdlK.png",X={},T=l('

    💻 Lab - Setup and Basic Usage


    🏡 Moving in - Explore Your Workspace

    @slidestart blood

    Terraform 명령줄 도구는 MacOS, FreeBSD, OpenBSD, Windows, Solaris 및 Linux에서 사용할 수 있습니다.


    Terraform 언어는 사람과 기계가 읽을 수 있도록 설계되었습니다.


    대부분의 최신 코드 편집기는 Terraform 구문 강조 표시를 지원합니다.

    @slideend

    테라폼 설치 및 구성

    ',11),y={href:"https://www.terraform.io/downloads.html",target:"_blank",rel:"noopener noreferrer"},C=e("li",null,[e("p",null,"압축을 해제하여 terraform 바이너리 파일을 확인합니다.")],-1),P=e("ul",null,[e("li",null,[a("Linux/Mac : "),e("code",null,"terraform")]),e("li",null,[a("Windows : "),e("code",null,"terraform.exe")])],-1),A=e("ol",{start:"3"},[e("li",null,"파일을 적절한 위치에 넣고 PATH로 지정합니다.")],-1),S=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token function"},"mkdir"),a(` ~/hashicorp/bin +`),e("span",{class:"token function"},"mv"),a(` terraform ~/hashicorp/bin +`),e("span",{class:"token builtin class-name"},"cd"),a(` ~/hashicorp/bin +`),e("span",{class:"token builtin class-name"},"echo"),a(),e("span",{class:"token variable"},[e("span",{class:"token variable"},"$("),e("span",{class:"token builtin class-name"},"pwd"),e("span",{class:"token variable"},")")]),a(),e("span",{class:"token operator"},">>"),a(` ~/.bash_profile +`),e("span",{class:"token builtin class-name"},"source"),a(` ~/.bash_profile +`)])])],-1),E=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token function"},"mkdir"),a(` ~/hashicorp/bin +`),e("span",{class:"token function"},"mv"),a(` terraform ~/hashicorp/bin +`),e("span",{class:"token builtin class-name"},"cd"),a(` ~/hashicorp/bin +`),e("span",{class:"token builtin class-name"},"echo"),a(),e("span",{class:"token variable"},[e("span",{class:"token variable"},"$("),e("span",{class:"token builtin class-name"},"pwd"),e("span",{class:"token variable"},")")]),a(),e("span",{class:"token operator"},">>"),a(` ~/.zshrc +`),e("span",{class:"token builtin class-name"},"source"),a(` ~/.zshrc +`)])])],-1),z=e("h3",{id:"편집기-구성",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#편집기-구성"},[e("span",null,"편집기 구성")])],-1),N=e("p",null,"VSCode 편집기를 사용할 준비가 되었다면, 코드의 시인성을 위해 extension을 설치 합니다.",-1),L=e("ul",null,[e("li",null,[a("좌측 사이드 메뉴에서 Extentions 를 클릭하여 "),e("code",null,"Terraform"),a("을 검색합니다.")]),e("li",null,[e("code",null,"HashiCorp Terraform"),a("을 설치합니다."),e("br"),e("img",{src:f,alt:"",loading:"lazy"})])],-1),D=e("ul",null,[e("li",null,"설치가 완료되면 코드의 시인성이 향상됨을 확이할 수 있습니다."),e("li",null,[a("이 외에도 수많은 확장 모듈로 생산성을 향상시킬 수 있습니다."),e("br"),e("img",{src:b,alt:"",loading:"lazy"})])],-1),W=e("h3",{id:"실습을-위한-코드-받기",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#실습을-위한-코드-받기"},[e("span",null,"실습을 위한 코드 받기")])],-1),I=e("p",null,"이 실습에서는 Terraform을 실행하기 위한 IDE 설정과 Terraform CLI를 사용하고, NCP를 위한 기본 구성을 수행합니다.",-1),O=e("p",null,"실습에서 사용할 코드는 github에서 받습니다.",-1),q={href:"https://github.com/ncp-hc/workshop-oss",target:"_blank",rel:"noopener noreferrer"},B=l('

    편집기에서 열기


    👋 Getting to Know Terraform

    @slidestart blood

    Terraform 오픈 소스는 랩톱 또는 가상 워크스테이션에서 다운로드하여 실행할 수 있는 명령줄 응용 프로그램입니다.


    Go로 작성되었으며 macOS, Linux 또는 Windows에서 실행됩니다.


    ',12),K={id:"https-www-terraform-io-downloads-html-에서-항상-최신-버전의-terraform을-다운로드할-수-있습니다",tabindex:"-1"},M={class:"header-anchor",href:"#https-www-terraform-io-downloads-html-에서-항상-최신-버전의-terraform을-다운로드할-수-있습니다"},U={href:"https://www.terraform.io/downloads.html",target:"_blank",rel:"noopener noreferrer"},V=e("hr",null,null,-1),Y=e("h3",{id:"노트북이나-워크스테이션에-terraform을-설치하는-것은-쉽습니다-zip-파일을-다운로드하고-압축을-풀고-path의-어딘가에-배치하기만-하면-됩니다",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#노트북이나-워크스테이션에-terraform을-설치하는-것은-쉽습니다-zip-파일을-다운로드하고-압축을-풀고-path의-어딘가에-배치하기만-하면-됩니다"},[e("span",null,"노트북이나 워크스테이션에 Terraform을 설치하는 것은 쉽습니다. zip 파일을 다운로드하고 압축을 풀고 PATH의 어딘가에 배치하기만 하면 됩니다.")])],-1),H=e("hr",null,null,-1),R={id:"단계별-지침은-이-튜토리얼을-확인하세요-https-learn-hashicorp-com-terraform-getting-started-install-html",tabindex:"-1"},$={class:"header-anchor",href:"#단계별-지침은-이-튜토리얼을-확인하세요-https-learn-hashicorp-com-terraform-getting-started-install-html"},G={href:"https://learn.hashicorp.com/terraform/getting-started/install.html",target:"_blank",rel:"noopener noreferrer"},Q=l('

    @slideend

    편집기가 준비가 되었으면 터미널을 열고 몇가지 기본적인 Terraform 명령을 수행합니다.

    💻 컴퓨터에서 Terraform의 버전을 확인 하세요.

    terraform version
    +

    💻 명령 구문이 궁금하면 언제든지 도움을 받을 수 있습니다.

    terraform help
    +

    🔐 Terraform을 NCP에 연결하기

    @slidestart blood

    HCL이

    "HashiCorp Configuration Language"

    의 약자라는 것을 알고 계신가요?

    @slideend

    NCP 자격증명 받기

    NCP에 인증하고 리소스를 빌드하기 위해 Terraform은 적절한 자격 증명 세트를 제공하도록 요구합니다.

    💻 Terraform에서 사용할 NCP의 자격증명을 얻기위해 다음 단계를 수행합니다.

    `,17),F={href:"https://www.ncloud.com/",target:"_blank",rel:"noopener noreferrer"},Z=e("code",null,"인증키 관리",-1),j=e("li",null,[a("경우에 따라 사용자 패스워드를 요구할 수 있습니다."),e("br"),e("img",{src:k,alt:"",loading:"lazy"})],-1),J=e("ul",null,[e("li",null,[a("Terraform은 NCP에 API 인증키를 통해 자격증명을 취득합니다. "),e("code",null,"신규 API 인증키 생성"),a(" 버튼을 클릭합니다.")]),e("li",null,[e("code",null,"API 인증키가 생성 되었습니다."),a(" 라는 메시지를 확인합니다."),e("br"),e("img",{src:_,alt:"",loading:"lazy"})])],-1),ee=e("ul",null,[e("li",null,[a("생성된 API인증키의 "),e("code",null,"Secret Key"),a("항목의 "),e("code",null,"보기"),a("버튼을 클릭하여 "),e("code",null,"Secret Key"),a("를 확인합니다.")]),e("li",null,[a("Terraform을 위한 환경변수 설정을 위해 "),e("code",null,"Access Key ID"),a(", "),e("code",null,"Secret Key"),a("를 사용하게 됩니다."),e("br"),e("img",{src:x,alt:"",loading:"lazy"})])],-1),ae=l('

    NCP 자격증명 환경변수로 저장하기

    이 교육 환경을 위해 NCP의 자격 증명을 준비하여 환경 변수로 저장합니다. Terraform은 쉘 환경에 구성된 환경 변수를 자동으로 읽고 사용합니다.

    💻 터미널에서 다음 명령을 실행합니다.

    ',4),re=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token builtin class-name"},"export"),a(),e("span",{class:"token assign-left variable"},"NCLOUD_ACCESS_KEY"),e("span",{class:"token operator"},"="),e("span",{class:"token string"},'"XXXXXXXXXXXXX"'),a(` +`),e("span",{class:"token builtin class-name"},"export"),a(),e("span",{class:"token assign-left variable"},"NCLOUD_SECRET_KEY"),e("span",{class:"token operator"},"="),e("span",{class:"token string"},'"XXXXXXXXXXXXXXXXXXXXXXXXXX"'),a(` +`)])])],-1),te=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token builtin class-name"},"set"),a(),e("span",{class:"token assign-left variable"},"NCLOUD_ACCESS_KEY"),e("span",{class:"token operator"},"="),e("span",{class:"token string"},'"XXXXXXXXXXXXX"'),a(` +`),e("span",{class:"token builtin class-name"},"set"),a(),e("span",{class:"token assign-left variable"},"NCLOUD_SECRET_KEY"),e("span",{class:"token operator"},"="),e("span",{class:"token string"},'"XXXXXXXXXXXXXXXXXXXXXXXXXX"'),a(` +`)])])],-1),se=e("div",{class:"language-powershell","data-ext":"powershell","data-title":"powershell"},[e("pre",{class:"language-powershell"},[e("code",null,[e("span",{class:"token variable"},"$Env"),a(":NCLOUD_ACCESS_KEY="),e("span",{class:"token string"},'"XXXXXXXXXXXXX"'),a(` +`),e("span",{class:"token variable"},"$Env"),a(":NCLOUD_SECRET_KEY="),e("span",{class:"token string"},'"XXXXXXXXXXXXXXXXXXXXXXXXXX"'),a(` +`)])])],-1),ne=l('

    위험

    API 자격증명정보는 실수로 공개된 저장소에 노출되거나 복사되면 위헐합니다.
    자격증명(API 인증키)를 코드에 저장하지 않는 것을 권장합니다.


    👨‍💻 Terraform 코드는 어떻게 생겼나요?

    @slidestart blood

    Terraform은 현재 디렉토리에서 *.tf 또는 *.tfvars 로 끝나는 모든 것을 읽습니다.


    일반적으로 Terraform Workspace는 main.tf, variables.tf, outputs.tf 파일로 구성됩니다.


    Terraform 코드를 목적에 따라 파일로 그룹화할 수도 있습니다.

    예를 들어 모든 로드 밸런서 구성 코드를 load_balancer.tf 에 구성하기

    @slideend

    코드 편집기의 파일 목록이 보이십니까?

    Terraform 코드는 항상 .tf 확장자로 끝납니다. 원하는 만큼 Terraform 파일을 가질 수 있지만 일반적으로 다음 세 가지를 구성합니다.

    Terraform에서 *.tf*.tfvars로 끝나지 않는 파일은 무시됩니다.


    🏡 Terraform Init - Provider 설치

    @slidestart blood

    Terraform Core 프로그램은 그 자체로는 그다지 유용하지 않습니다.


    Terraform은 클라우드 API와 통신할 수 있도록 Provider(공급자) 의 도움이 필요합니다.


    Terraform에는 수백 개의 다양한 Provider가 있습니다. 여기에서 Provider 목록을 찾아볼 수 있습니다.

    ',23),le={href:"https://registry.terraform.io/browse/providers",target:"_blank",rel:"noopener noreferrer"},oe=l(`

    오늘 우리는 몇 가지 다른 Provider를 사용할 것이지만 주요 Provider는 ncloud provider 입니다.

    @slideend

    우리는 이 실습에서 사용할 Terraform 코드를 다운로드 했습니다. 나머지 실습에서 이 소스코드를 사용할 것입니다.

    Terraform으로 무엇이든 하기 전에 Workspace를 초기화 해야 합니다.

    💻 터미널에서 init 명령을 수행합니다.

    terraform init
    +
    +...
    +Terraform has been successfully initialized!
    +

    \`terraform init 명령은 Terraform 코드를 스캔하고 필요한 Provider를 식별하고 다운로드합니다.

    💻 ncloud provider가 .terraform 디렉토리에 설치되었는지 확인합니다.

    `,9),ie=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,[e("span",{class:"token function"},"ls"),a(` .terraform/providers/registry.terraform.io/navercloudplatform +`)])])],-1),ce=e("div",{class:"language-powershell","data-ext":"powershell","data-title":"powershell"},[e("pre",{class:"language-powershell"},[e("code",null,[e("span",{class:"token function"},"dir"),a(),e("span",{class:"token punctuation"},"."),a("terraform/providers/registry"),e("span",{class:"token punctuation"},"."),a("terraform"),e("span",{class:"token punctuation"},"."),a(`io/navercloudplatform +`)])])],-1),de=l('

    이 숨겨진 디렉토리는 모든 모듈과 플러그인이 저장되는 곳입니다.

    😱 Quiz Time 1. Provider와 Module

    Q. Terraform은 모듈과 공급자를 어디에 저장합니까?


    👩‍⚖️ Terraform Validate - 코드 테스트

    @slidestart blood

    Terraform에는 구문 검사기가 내장되어 있습니다.


    terraform validate 명령으로 실행할 수 있습니다.

    @slideend

    Terraform에는 validate 라는 하위 명령이 내장되어 있습니다. 이것은 코드가 올바르게 구문 분석되는지 확인하기 위해 코드의 빠른 구문 검사를 수행하려는 경우에 유용합니다.

    ',13),pe={id:"main-tf-파일을-편집합니다",tabindex:"-1"},he={class:"header-anchor",href:"#main-tf-파일을-편집합니다"},me={href:"http://main.tf",target:"_blank",rel:"noopener noreferrer"},ue=e("p",null,[e("code",null,"main.tf"),a(" 16번째 행의 사이에 큰 따옴표를 제거 "),e("code",null,"ncloud_vpc"),a("와 "),e("code",null,"hashicat"),a(" 사이의 큰 따옴표를 제거하고 저장합니다.")],-1),fe=e("div",{class:"language-hcl line-numbers-mode","data-ext":"hcl","data-title":"hcl"},[e("pre",{hcl:"",class:"language-hcl"},[e("code",null,[e("span",{class:"token keyword"},"terraform"),a(),e("span",{class:"token punctuation"},"{"),a(` + `),e("span",{class:"token keyword"},"required_providers"),a(),e("span",{class:"token punctuation"},"{"),a(` + `),e("span",{class:"token property"},"ncloud"),a(),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token punctuation"},"{"),a(` + `),e("span",{class:"token property"},"source"),a(" "),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token string"},'"NaverCloudPlatform/ncloud"'),a(` + `),e("span",{class:"token property"},"version"),a(),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token string"},'">= 2.1.2"'),a(` + `),e("span",{class:"token punctuation"},"}"),a(` + `),e("span",{class:"token punctuation"},"}"),a(` +`),e("span",{class:"token punctuation"},"}"),a(` + +`),e("span",{class:"token keyword"},[a("provider"),e("span",{class:"token type variable"},' "ncloud" ')]),e("span",{class:"token punctuation"},"{"),a(` + `),e("span",{class:"token property"},"region"),a(" "),e("span",{class:"token punctuation"},"="),a(` var.region + `),e("span",{class:"token property"},"site"),a(" "),e("span",{class:"token punctuation"},"="),a(` var.site + `),e("span",{class:"token property"},"support_vpc"),a(),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token boolean"},"true"),a(` +`),e("span",{class:"token punctuation"},"}"),a(` + +`),e("span",{class:"token keyword"},[a("resource "),e("span",{class:"token type variable"},'"ncloud_vpc"')]),a(),e("span",{class:"token string"},'"hashicat"'),a(),e("span",{class:"token punctuation"},"{"),a(" → resource "),e("span",{class:"token string"},'"ncloud_vpc"'),a(" hashicat"),e("span",{class:"token string"},`" { + ipv4_cidr_block = "`),e("span",{class:"token number"},"10.0"),a("."),e("span",{class:"token number"},"0.0"),a("/"),e("span",{class:"token number"},"16"),e("span",{class:"token string"},`" + name = lower("`),a("$"),e("span",{class:"token punctuation"},"{"),a("var.prefix"),e("span",{class:"token punctuation"},"}"),a("-vpc-$"),e("span",{class:"token punctuation"},"{"),a("var.region"),e("span",{class:"token punctuation"},"}"),a(`") +`),e("span",{class:"token punctuation"},"}"),a(` + +...생략... +`)])]),e("div",{class:"highlight-lines"},[e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("div",{class:"highlight-line"}," "),e("br"),e("br"),e("br"),e("br"),e("br")]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1),be=l(`

    terraform validate 명령을 터미널에서 실행합니다.

    terraform validate
    +

    다시 따옴표 표기를 넣고 저장한 다음 terraform validate 명령을 실행합니다. 이번에는 검증을 통과해야 합니다.

    terraform validate 명령은 자동화된 CI/CD 테스트 파이프라인에서 가장 자주 사용됩니다. 다른 단계를 수행하기 전에 코드에서 오류를 빠르게 포착할 수 있습니다.


    🤔 Terraform Plan - Dry run mode

    @slidestart blood

    terraform plan을 통해 환경에 대한 변경 사항을 안전한 방법으로 미리 볼 수 있습니다.


    이렇게 하면 이미 빌드된 후가 아니라 배포하기 전에 예기치 않은 변경 사항을 식별하는 데 도움이 될 수 있습니다.

    @slideend

    💻 terraform plan명령을 실행합니다.

    `,12),ve=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{bash:"",class:"language-bash"},[e("code",null,[a(`$ terraform plan +var.prefix + This prefix will be included `),e("span",{class:"token keyword"},"in"),a(" the name of "),e("span",{class:"token function"},"most"),a(` resources. + + Enter a value: +`)])]),e("div",{class:"highlight-lines"},[e("div",{class:"highlight-line"}," "),e("br"),e("br"),e("br"),e("br")])],-1),ge=l('

    이 명령을 실행하면 Terraform에서 prefix변수 를 입력하라는 메시지를 표시 합니다.

    소문자 또는 숫자의 짧은 문자열을 입력합니다. 영문 이니셜 소문자를 사용하는 것이 좋습니다.

    prefix는 현재 Terraform 코드 구성에서 VPC, 서브넷, 서버 등의 리소스 이름의 일부가 됩니다.

    🎛️ Terraform 변수로 작업하기

    @slidestart blood

    terraform.tfvars 파일은 사용자가 변수를 구성할 수 있는 편리한 위치입니다.

    @slideend

    Terraform에서 모든 변수는 사용하기 전에 선언되어야 합니다. 변수는 다른 *.tf 파일에서도 선언될 수 있지만 일반적으로 variables.tf 파일에서 선언됩니다. (default)

    ',8),ke=e("div",{class:"language-hcl","data-ext":"hcl","data-title":"hcl"},[e("pre",{hcl:"",class:"language-hcl"},[e("code",null,[e("span",{class:"token keyword"},[a("variable"),e("span",{class:"token type variable"},' "address_space" ')]),e("span",{class:"token punctuation"},"{"),a(` + `),e("span",{class:"token property"},"description"),a(),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token string"},'"The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created."'),a(` + `),e("span",{class:"token property"},"default"),a(" "),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token string"},'"10.0.0.0/8"'),a(` +`),e("span",{class:"token punctuation"},"}"),a(` +`)])]),e("div",{class:"highlight-lines"},[e("br"),e("br"),e("div",{class:"highlight-line"}," "),e("br")])],-1),_e=l(`

    해당 값은 terraform.tfvars 파일 및 나중에 다른 방법으로 설정할 수 있습니다.

    💻 terraform.tfvars 파일을 수정합니다.

    terraform.tfvars 파일을 열고 prefix의 줄 시작 부분에 주석 기호 # 를 제거합니다.

    yourname을 원하는 소문자 또는 숫자의 짧은 문자열을 입력합니다.

    # terraform.tfvars
    +prefix = "yourname"
    +

    이제 terraform plan을 다시 실행 합니다. 이번에는 prefix를 수동으로 입력할 필요가 없습니다.

    🗼 cidr_block 변경

    @slidestart blood

    개개인은 terraform.tfvars 파일에 설정하여 variables.tf 파일에 정의된 모든 변수를 재정의할 수 있습니다.


    이번 실습에서는 ncloud 리소스를 배포하는 위치를 지정합니다.

    @slideend

    이전 실습에서 prefixterraform.tfvars 파일에서 변수를 설정했습니다. ncloud 인프라가 배포될 vpc의 cidr를 결정할 또 다른 변수를 설정해 보겠습니다.

    먼저 다른 계획을 실행하여 위치를 변경한 후 어떻게 되는지 비교할 수 있습니다.

    terraform plan
    +

    💻 기본 address_space 정보를 수정합니다.

    default로 선언되어있는 값 외에 사용자 지정 변수로 변경해봅니다. terraform.tfvars파일을 열어 address_space을 추가하고 다시 terraform plan을 실행해 봅니다. 이번엔 무엇이 다른가요?

    # terraform.tfvars
    +prefix        = "yourname"
    +address_space = "10.0.0.0/16"
    +

    terraform.tfvars 파일은 variables.tf 파일에 선언된 모든 변수에 대한 값을 설정할 수 있음을 기억하십시오.


    😱 Quiz Time 2. Variables

    Q. Terraform 변수는 일반적으로 어디에 선언 됩니까?

    `,22),xe={class:"task-list-container"},we=l('
  • ',2),Xe={class:"task-list-item"},Te=e("input",{type:"checkbox",class:"task-list-item-checkbox",id:"task-item-7",disabled:"disabled"},null,-1),ye={class:"task-list-item-label",for:"task-item-7"},Ce={href:"http://variable.tf",target:"_blank",rel:"noopener noreferrer"},Pe=e("li",{class:"task-list-item"},[e("input",{type:"checkbox",class:"task-list-item-checkbox",id:"task-item-8",disabled:"disabled"}),e("label",{class:"task-list-item-label",for:"task-item-8"}," terraform.tfvars 파일에서")],-1),Ae={class:"hint-container details"},Se=e("summary",null,"답",-1),Ee={class:"task-list-container"},ze={class:"task-list-item"},Ne=e("input",{type:"checkbox",class:"task-list-item-checkbox",id:"task-item-9",checked:"checked",disabled:"disabled"},null,-1),Le={class:"task-list-item-label",for:"task-item-9"},De={href:"http://variable.tf",target:"_blank",rel:"noopener noreferrer"},We=e("hr",null,null,-1),Ie=e("p",null,"이 장에서 우리는 :",-1),Oe=e("ul",null,[e("li",null,"terraform init 명령을 확인했습니다."),e("li",null,"terraform plan 명령을 확인했습니다."),e("li",null,"변수에 대해 배웠습니다."),e("li",null,"prefix(접두사) 설정을 했습니다.")],-1);function qe(Be,Ke){const o=c("ExternalLinkIcon"),i=c("CodeTabs"),d=c("Tabs");return m(),u("div",null,[T,e("ol",null,[e("li",null,[e("p",null,[a("테라폼 다운로드 사이트 "),e("a",y,[a("https://www.terraform.io/downloads.html"),n(o)]),a(" 로 접속하여, 자신의 환경에 맞는 Terraform을 다운로드 받습니다.")])]),C]),P,A,n(i,{id:"58",data:[{id:"bash"},{id:"zsh"},{id:"windows"}],"tab-id":"shell"},{title0:r(({value:t,isActive:s})=>[a("bash")]),title1:r(({value:t,isActive:s})=>[a("zsh")]),title2:r(({value:t,isActive:s})=>[a("windows")]),tab0:r(({value:t,isActive:s})=>[S]),tab1:r(({value:t,isActive:s})=>[E]),tab2:r(({value:t,isActive:s})=>[]),_:1}),z,N,n(d,{id:"101",data:[{id:"1. Extention 설치"},{id:"2. 적용된 Extention 확인"}]},{title0:r(({value:t,isActive:s})=>[a("1. Extention 설치")]),title1:r(({value:t,isActive:s})=>[a("2. 적용된 Extention 확인")]),tab0:r(({value:t,isActive:s})=>[L]),tab1:r(({value:t,isActive:s})=>[D]),_:1},8,["data"]),W,I,O,e("p",null,[a("링크 : "),e("a",q,[a("https://github.com/ncp-hc/workshop-oss"),n(o)])]),B,e("h3",K,[e("a",M,[e("span",null,[e("a",U,[a("https://www.terraform.io/downloads.html"),n(o)]),a(" 에서 항상 최신 버전의 Terraform을 다운로드할 수 있습니다.")])])]),V,Y,H,e("h3",R,[e("a",$,[e("span",null,[a("단계별 지침은 이 튜토리얼을 확인하세요. "),e("a",G,[a("https://learn.hashicorp.com/terraform/getting-started/install.html"),n(o)])])])]),Q,n(d,{id:"256",data:[{id:"1. 인증키 관리"},{id:"2. 신규 API 인증키 생성"},{id:"3. API 인증키 쌍 확인"}]},{title0:r(({value:t,isActive:s})=>[a("1. 인증키 관리")]),title1:r(({value:t,isActive:s})=>[a("2. 신규 API 인증키 생성")]),title2:r(({value:t,isActive:s})=>[a("3. API 인증키 쌍 확인")]),tab0:r(({value:t,isActive:s})=>[e("ul",null,[e("li",null,[e("a",F,[a("https://www.ncloud.com/"),n(o)]),a("에 로그인하여 마이페이지 메뉴에서 "),Z,a("를 선택합니다.")]),j])]),tab1:r(({value:t,isActive:s})=>[J]),tab2:r(({value:t,isActive:s})=>[ee]),_:1},8,["data"]),ae,n(i,{id:"316",data:[{id:"bash"},{id:"CMD(Win)"},{id:"Powershell(Win)"}],"tab-id":"shell"},{title0:r(({value:t,isActive:s})=>[a("bash")]),title1:r(({value:t,isActive:s})=>[a("CMD(Win)")]),title2:r(({value:t,isActive:s})=>[a("Powershell(Win)")]),tab0:r(({value:t,isActive:s})=>[re]),tab1:r(({value:t,isActive:s})=>[te]),tab2:r(({value:t,isActive:s})=>[se]),_:1},8,["data"]),ne,e("p",null,[e("a",le,[a("https://registry.terraform.io/browse/providers"),n(o)])]),oe,n(i,{id:"426",data:[{id:"Linux/Mac"},{id:"Windows"},{id:"Code Editor"}],"tab-id":"shell"},{title0:r(({value:t,isActive:s})=>[a("Linux/Mac")]),title1:r(({value:t,isActive:s})=>[a("Windows")]),title2:r(({value:t,isActive:s})=>[a("Code Editor")]),tab0:r(({value:t,isActive:s})=>[ie]),tab1:r(({value:t,isActive:s})=>[ce]),tab2:r(({value:t,isActive:s})=>[]),_:1}),de,e("h4",pe,[e("a",he,[e("span",null,[a("💻 "),e("a",me,[a("main.tf"),n(o)]),a(" 파일을 편집합니다.")])])]),ue,fe,be,ve,ge,ke,_e,e("ul",xe,[we,e("li",Xe,[Te,e("label",ye,[e("a",Ce,[a("variable.tf"),n(o)]),a(" 파일에서")])]),Pe]),e("details",Ae,[Se,e("ul",Ee,[e("li",ze,[Ne,e("label",Le,[e("a",De,[a("variable.tf"),n(o)]),a(" 파일에서")])])])]),We,Ie,Oe])}const Ye=h(X,[["render",qe],["__file","02-z-lab_terraform_basic.html.vue"]]),He=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html","title":"💻 Lab - Setup and Basic Usage","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"💻 Lab - Setup and Basic Usage"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://icons.iconarchive.com/icons/bogo-d/project/16/OS-Windows-7-icon.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"💻 Lab - Setup and Basic Usage"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"💻 Lab - Setup and Basic Usage\\",\\"image\\":[\\"https://icons.iconarchive.com/icons/bogo-d/project/16/OS-Windows-7-icon.png\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"🏡 Moving in - Explore Your Workspace","slug":"🏡-moving-in-explore-your-workspace","link":"#🏡-moving-in-explore-your-workspace","children":[{"level":3,"title":"Terraform 명령줄 도구는 MacOS, FreeBSD, OpenBSD, Windows, Solaris 및 Linux에서 사용할 수 있습니다.","slug":"terraform-명령줄-도구는-macos-freebsd-openbsd-windows-solaris-및-linux에서-사용할-수-있습니다","link":"#terraform-명령줄-도구는-macos-freebsd-openbsd-windows-solaris-및-linux에서-사용할-수-있습니다","children":[]},{"level":3,"title":"Terraform 언어는 사람과 기계가 읽을 수 있도록 설계되었습니다.","slug":"terraform-언어는-사람과-기계가-읽을-수-있도록-설계되었습니다","link":"#terraform-언어는-사람과-기계가-읽을-수-있도록-설계되었습니다","children":[]},{"level":3,"title":"대부분의 최신 코드 편집기는 Terraform 구문 강조 표시를 지원합니다.","slug":"대부분의-최신-코드-편집기는-terraform-구문-강조-표시를-지원합니다","link":"#대부분의-최신-코드-편집기는-terraform-구문-강조-표시를-지원합니다","children":[]},{"level":3,"title":"테라폼 설치 및 구성","slug":"테라폼-설치-및-구성","link":"#테라폼-설치-및-구성","children":[]},{"level":3,"title":"편집기 구성","slug":"편집기-구성","link":"#편집기-구성","children":[]},{"level":3,"title":"실습을 위한 코드 받기","slug":"실습을-위한-코드-받기","link":"#실습을-위한-코드-받기","children":[]},{"level":3,"title":"편집기에서 열기","slug":"편집기에서-열기","link":"#편집기에서-열기","children":[]}]},{"level":2,"title":"👋 Getting to Know Terraform","slug":"👋-getting-to-know-terraform","link":"#👋-getting-to-know-terraform","children":[{"level":3,"title":"Terraform 오픈 소스는 랩톱 또는 가상 워크스테이션에서 다운로드하여 실행할 수 있는 명령줄 응용 프로그램입니다.","slug":"terraform-오픈-소스는-랩톱-또는-가상-워크스테이션에서-다운로드하여-실행할-수-있는-명령줄-응용-프로그램입니다","link":"#terraform-오픈-소스는-랩톱-또는-가상-워크스테이션에서-다운로드하여-실행할-수-있는-명령줄-응용-프로그램입니다","children":[]},{"level":3,"title":"Go로 작성되었으며 macOS, Linux 또는 Windows에서 실행됩니다.","slug":"go로-작성되었으며-macos-linux-또는-windows에서-실행됩니다","link":"#go로-작성되었으며-macos-linux-또는-windows에서-실행됩니다","children":[]},{"level":3,"title":"https://www.terraform.io/downloads.html 에서 항상 최신 버전의 Terraform을 다운로드할 수 있습니다.","slug":"https-www-terraform-io-downloads-html-에서-항상-최신-버전의-terraform을-다운로드할-수-있습니다","link":"#https-www-terraform-io-downloads-html-에서-항상-최신-버전의-terraform을-다운로드할-수-있습니다","children":[]},{"level":3,"title":"노트북이나 워크스테이션에 Terraform을 설치하는 것은 쉽습니다. zip 파일을 다운로드하고 압축을 풀고 PATH의 어딘가에 배치하기만 하면 됩니다.","slug":"노트북이나-워크스테이션에-terraform을-설치하는-것은-쉽습니다-zip-파일을-다운로드하고-압축을-풀고-path의-어딘가에-배치하기만-하면-됩니다","link":"#노트북이나-워크스테이션에-terraform을-설치하는-것은-쉽습니다-zip-파일을-다운로드하고-압축을-풀고-path의-어딘가에-배치하기만-하면-됩니다","children":[]},{"level":3,"title":"단계별 지침은 이 튜토리얼을 확인하세요. https://learn.hashicorp.com/terraform/getting-started/install.html","slug":"단계별-지침은-이-튜토리얼을-확인하세요-https-learn-hashicorp-com-terraform-getting-started-install-html","link":"#단계별-지침은-이-튜토리얼을-확인하세요-https-learn-hashicorp-com-terraform-getting-started-install-html","children":[]}]},{"level":2,"title":"🔐 Terraform을 NCP에 연결하기","slug":"🔐-terraform을-ncp에-연결하기","link":"#🔐-terraform을-ncp에-연결하기","children":[{"level":3,"title":"HCL이","slug":"hcl이","link":"#hcl이","children":[]},{"level":3,"title":"\\"HashiCorp Configuration Language\\"","slug":"hashicorp-configuration-language","link":"#hashicorp-configuration-language","children":[]},{"level":3,"title":"의 약자라는 것을 알고 계신가요?","slug":"의-약자라는-것을-알고-계신가요","link":"#의-약자라는-것을-알고-계신가요","children":[]},{"level":3,"title":"NCP 자격증명 받기","slug":"ncp-자격증명-받기","link":"#ncp-자격증명-받기","children":[]},{"level":3,"title":"NCP 자격증명 환경변수로 저장하기","slug":"ncp-자격증명-환경변수로-저장하기","link":"#ncp-자격증명-환경변수로-저장하기","children":[]}]},{"level":2,"title":"👨‍💻 Terraform 코드는 어떻게 생겼나요?","slug":"👨‍💻-terraform-코드는-어떻게-생겼나요","link":"#👨‍💻-terraform-코드는-어떻게-생겼나요","children":[{"level":3,"title":"Terraform은 현재 디렉토리에서 *.tf 또는 *.tfvars 로 끝나는 모든 것을 읽습니다.","slug":"terraform은-현재-디렉토리에서-tf-또는-tfvars-로-끝나는-모든-것을-읽습니다","link":"#terraform은-현재-디렉토리에서-tf-또는-tfvars-로-끝나는-모든-것을-읽습니다","children":[]},{"level":3,"title":"일반적으로 Terraform Workspace는 main.tf, variables.tf, outputs.tf 파일로 구성됩니다.","slug":"일반적으로-terraform-workspace는-main-tf-variables-tf-outputs-tf-파일로-구성됩니다","link":"#일반적으로-terraform-workspace는-main-tf-variables-tf-outputs-tf-파일로-구성됩니다","children":[]},{"level":3,"title":"Terraform 코드를 목적에 따라 파일로 그룹화할 수도 있습니다.","slug":"terraform-코드를-목적에-따라-파일로-그룹화할-수도-있습니다","link":"#terraform-코드를-목적에-따라-파일로-그룹화할-수도-있습니다","children":[]}]},{"level":2,"title":"🏡 Terraform Init - Provider 설치","slug":"🏡-terraform-init-provider-설치","link":"#🏡-terraform-init-provider-설치","children":[{"level":3,"title":"Terraform Core 프로그램은 그 자체로는 그다지 유용하지 않습니다.","slug":"terraform-core-프로그램은-그-자체로는-그다지-유용하지-않습니다","link":"#terraform-core-프로그램은-그-자체로는-그다지-유용하지-않습니다","children":[]},{"level":3,"title":"Terraform은 클라우드 API와 통신할 수 있도록 Provider(공급자) 의 도움이 필요합니다.","slug":"terraform은-클라우드-api와-통신할-수-있도록-provider-공급자-의-도움이-필요합니다","link":"#terraform은-클라우드-api와-통신할-수-있도록-provider-공급자-의-도움이-필요합니다","children":[]},{"level":3,"title":"Terraform에는 수백 개의 다양한 Provider가 있습니다. 여기에서 Provider 목록을 찾아볼 수 있습니다.","slug":"terraform에는-수백-개의-다양한-provider가-있습니다-여기에서-provider-목록을-찾아볼-수-있습니다","link":"#terraform에는-수백-개의-다양한-provider가-있습니다-여기에서-provider-목록을-찾아볼-수-있습니다","children":[]},{"level":3,"title":"오늘 우리는 몇 가지 다른 Provider를 사용할 것이지만 주요 Provider는 ncloud provider 입니다.","slug":"오늘-우리는-몇-가지-다른-provider를-사용할-것이지만-주요-provider는-ncloud-provider-입니다","link":"#오늘-우리는-몇-가지-다른-provider를-사용할-것이지만-주요-provider는-ncloud-provider-입니다","children":[]}]},{"level":2,"title":"😱 Quiz Time 1. Provider와 Module","slug":"quiz-time-1-provider와-module","link":"#quiz-time-1-provider와-module","children":[]},{"level":2,"title":"👩‍⚖️ Terraform Validate - 코드 테스트","slug":"👩‍⚖️-terraform-validate-코드-테스트","link":"#👩‍⚖️-terraform-validate-코드-테스트","children":[{"level":3,"title":"Terraform에는 구문 검사기가 내장되어 있습니다.","slug":"terraform에는-구문-검사기가-내장되어-있습니다","link":"#terraform에는-구문-검사기가-내장되어-있습니다","children":[]},{"level":3,"title":"terraform validate 명령으로 실행할 수 있습니다.","slug":"terraform-validate-명령으로-실행할-수-있습니다","link":"#terraform-validate-명령으로-실행할-수-있습니다","children":[]}]},{"level":2,"title":"🤔 Terraform Plan - Dry run mode","slug":"🤔-terraform-plan-dry-run-mode","link":"#🤔-terraform-plan-dry-run-mode","children":[{"level":3,"title":"terraform plan을 통해 환경에 대한 변경 사항을 안전한 방법으로 미리 볼 수 있습니다.","slug":"terraform-plan을-통해-환경에-대한-변경-사항을-안전한-방법으로-미리-볼-수-있습니다","link":"#terraform-plan을-통해-환경에-대한-변경-사항을-안전한-방법으로-미리-볼-수-있습니다","children":[]},{"level":3,"title":"이렇게 하면 이미 빌드된 후가 아니라 배포하기 전에 예기치 않은 변경 사항을 식별하는 데 도움이 될 수 있습니다.","slug":"이렇게-하면-이미-빌드된-후가-아니라-배포하기-전에-예기치-않은-변경-사항을-식별하는-데-도움이-될-수-있습니다","link":"#이렇게-하면-이미-빌드된-후가-아니라-배포하기-전에-예기치-않은-변경-사항을-식별하는-데-도움이-될-수-있습니다","children":[]}]},{"level":2,"title":"🎛️ Terraform 변수로 작업하기","slug":"🎛️-terraform-변수로-작업하기","link":"#🎛️-terraform-변수로-작업하기","children":[{"level":3,"title":"terraform.tfvars 파일은 사용자가 변수를 구성할 수 있는 편리한 위치입니다.","slug":"terraform-tfvars-파일은-사용자가-변수를-구성할-수-있는-편리한-위치입니다","link":"#terraform-tfvars-파일은-사용자가-변수를-구성할-수-있는-편리한-위치입니다","children":[]}]},{"level":2,"title":"🗼 cidr_block 변경","slug":"🗼-cidr-block-변경","link":"#🗼-cidr-block-변경","children":[{"level":3,"title":"개개인은 terraform.tfvars 파일에 설정하여 variables.tf 파일에 정의된 모든 변수를 재정의할 수 있습니다.","slug":"개개인은-terraform-tfvars-파일에-설정하여-variables-tf-파일에-정의된-모든-변수를-재정의할-수-있습니다","link":"#개개인은-terraform-tfvars-파일에-설정하여-variables-tf-파일에-정의된-모든-변수를-재정의할-수-있습니다","children":[]},{"level":3,"title":"이번 실습에서는 ncloud 리소스를 배포하는 위치를 지정합니다.","slug":"이번-실습에서는-ncloud-리소스를-배포하는-위치를-지정합니다","link":"#이번-실습에서는-ncloud-리소스를-배포하는-위치를-지정합니다","children":[]}]},{"level":2,"title":"😱 Quiz Time 2. Variables","slug":"quiz-time-2-variables","link":"#quiz-time-2-variables","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1698218919000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":2.29,"words":687},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.md","localizedDate":"2023년 9월 18일","excerpt":"\\n
    \\n

    🏡 Moving in - Explore Your Workspace

    \\n

    @slidestart blood

    \\n

    Terraform 명령줄 도구는 MacOS, FreeBSD, OpenBSD, Windows, Solaris 및 Linux에서 사용할 수 있습니다.

    \\n
    \\n

    Terraform 언어는 사람과 기계가 읽을 수 있도록 설계되었습니다.

    \\n
    \\n

    대부분의 최신 코드 편집기는 Terraform 구문 강조 표시를 지원합니다.

    \\n

    @slideend

    \\n

    테라폼 설치 및 구성

    "}');export{Ye as comp,He as data}; diff --git a/assets/03-builds.html-sZm3VZd3.js b/assets/03-builds.html-sZm3VZd3.js new file mode 100644 index 0000000000..a038a92c91 --- /dev/null +++ b/assets/03-builds.html-sZm3VZd3.js @@ -0,0 +1,20 @@ +import{_ as s,a,b as l,c,d as p,e as d,f as r,g as u}from"./1564365395583-BYzBww3x.js";import{_ as g}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as k,o as m,c as h,b as n,d as e,a as o,e as i}from"./app-Bzk8Nrll.js";const b={},_=i(`

    3. Builds

    3.1 Tracking build state

    Pipeline이 수행되는 동작을 추적하는 과정을 확인합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-01.TrackingBuildState)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Deploy') {
    +            steps {
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh 'for n in \`seq 1 10\`; do echo $n; sleep 1; done'
    +                }
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh 'for n in \`seq 1 50\`; do echo $n; sleep 1; done'
    +                }
    +            }
    +        }
    +    }
    +}
    +

    Build Now를 클릭하여 빌드를 수행합니다. 그러면, 좌측의 Build History에 새로운 기록이 생성되면서 동작 중인것을 확인 할 수 있습니다.

    첫번째 방법은 앞서 확인한 Pipeline Steps를 확인하는 것입니다. 다시한번 확인하는 방법을 설명합니다.

    현재 수행중인 Pipeline이 어떤 단계가 수행중인지 각 스탭별로 확인할 수 있고 상태를 확인할 수 있습니다.

    1563948810815
    1563948810815

    두번째 방법은 출력되는 콘솔 로그를 확인하는 것입니다. Jenkins에서 빌드를 수행하면 빌드 수행 스크립트가 내부에 임시적으로 생성되어 작업을 실행합니다. 이때 발생되는 로그는 Console Output을 통해 거의 실시간으로 동작을 확인 할 수 있습니다.

    1563948863834
    1563948863834

    마지막으로는 Pipeline을 위한 UI인 BlueOcean 플러그인을 활용하는 방법입니다. Blue Ocean은 Pipeline에 알맞은 UI를 제공하며 수행 단계와 각 단게별 결과를 쉽게 확인할 수 있습니다.

    1563949823939
    1563949823939

    3.2 Polling SCM for build triggering

    Git SCM을 기반으로 Pipeline을 설정하는 과정을 설명합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-02.PollingSCMforBuildTriggering)

    해당 과정을 수행하기 위해서는 다음의 구성이 필요합니다.

    Pipeline을 다음과 같이 설정합니다.

    `,21),f=n("li",null,"Definition : Pipeline script from SCM",-1),v=n("li",null,"SCM : Git",-1),y={href:"https://github.com/Great-Stone/jenkins-git",target:"_blank",rel:"noopener noreferrer"},S=i(`

    추가로 빌드 트리거를 위한 설정을 합니다.

    Polling으로 인한 빌드 트리거가 동작하면 좌측 메뉴의 Polling Log에서 상태 확인이 가능합니다.

    1563958295010
    1563958295010

    1분마다 확인 하도록 되어있기 때문에 다시 Polling을 시도하지만 변경사항이 없는 경우에는 Polling Log에 No changes 메시지가 나타나고 빌드는 수행되지 않습니다.

    1563958396611
    1563958396611

    3.3 Connecting Jenkins to GitHub

    GitHub를 통한 CI 과정을 설명합니다. WebHook의 설정과 Jenkins에 관련 설정은 어떻게 하는지 알아봅니다.

    Jenkins에서 접속가능하도록 GitHub에서 Token을 생성합니다.

    ',9),P={href:"http://github.com",target:"_blank",rel:"noopener noreferrer"},C=i('
  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-integration) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

    1564115701058
    1564115701058
  • ',4),G=n("p",null,"우선 Jenkins에 Git연동을 위한 설정을 추가합니다.",-1),T=n("li",null,[n("code",null,"Jenkins 관리"),e("에서 "),n("code",null,"시스템 설정"),e("을 클릭합니다.")],-1),x=n("li",null,[n("code",null,"GitHub"),e(" 항목의 "),n("code",null,"GitHub Servers"),e("의 "),n("code",null,"Add GitHub Server > GitHub Server"),e("를 선택합니다.")],-1),B=n("li",null,"Name : 설정이름을 입력합니다. (e.g. github)",-1),J={href:"https://api.github.com",target:"_blank",rel:"noopener noreferrer"},D=i("
  • Credentials : ADD트롭박스를 선택합니다.
  • TEST CONNECTION버튼을 클릭하여 정상적으로 연결이 되는지 확인합니다.
  • Manage hook 를 활성화 합니다.
  • ",3),I=n("li",null,"시스템 설정을 저장합니다.",-1),w=n("h2",{id:"_3-4-webhook-build-triggering",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_3-4-webhook-build-triggering"},[n("span",null,"3.4 Webhook build triggering")])],-1),j=n("p",null,"git repo의 Webhook 을 통한 빌드를 수행합니다. GitHub에 다음과 같이 설정합니다.",-1),H={href:"https://github.com/Great-Stone/jenkins-git",target:"_blank",rel:"noopener noreferrer"},M=n("code",null,"fork",-1),N=n("figure",null,[n("img",{src:s,alt:"1564122799631",tabindex:"0",loading:"lazy"}),n("figcaption",null,"1564122799631")],-1),U=i("
  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-webhook) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

  • ",4),O=i('

    Webhook을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-04.WebhookBuild Triggering)

    설정은 다음과 같이 수행합니다.

    1. Pipeline 설정의 Definition의 드롭다운을 선택하여 Pipeline script from SCM을 선택합니다.

    2. SCM항목은 Git을 선택하고 하위 필드를 다음과 같이 정의합니다.

    저장 후 좌측 메뉴의 Build Now를 클릭하면 SCM에서 소스를 받고 Pipeline을 지정한 스크립트로 수행하는 것을 확인 할 수 있습니다.

    ',4);function R(z,E){const t=k("ExternalLinkIcon");return m(),h("div",null,[_,n("ul",null,[f,v,n("li",null,[e("Repositories "),n("ul",null,[n("li",null,[e("Repository URL : "),n("a",y,[e("https://github.com/Great-Stone/jenkins-git"),o(t)])])])])]),S,n("ul",null,[n("li",null,[n("p",null,[n("a",P,[e("github.com"),o(t)]),e("에 접속하여 로그인합니다.")])]),C]),G,n("ul",null,[T,x,n("li",null,[e("항목의 입력정보는 다음과 같습니다. "),n("ul",null,[B,n("li",null,[e("API URL : "),n("a",J,[e("https://api.github.com"),o(t)])]),D])]),I]),w,j,n("ul",null,[n("li",null,[n("p",null,[n("a",H,[e("https://github.com/Great-Stone/jenkins-git"),o(t)]),e(" 를 "),M,e("합니다.")]),N]),U]),O])}const $=g(b,[["render",R],["__file","03-builds.html.vue"]]),q=JSON.parse(`{"path":"/05-Software/Jenkins/pipeline101/03-builds.html","title":"3. Builds","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/03-builds.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"3. Builds"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"3. Builds\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"3.1 Tracking build state","slug":"_3-1-tracking-build-state","link":"#_3-1-tracking-build-state","children":[]},{"level":2,"title":"3.2 Polling SCM for build triggering","slug":"_3-2-polling-scm-for-build-triggering","link":"#_3-2-polling-scm-for-build-triggering","children":[]},{"level":2,"title":"3.3 Connecting Jenkins to GitHub","slug":"_3-3-connecting-jenkins-to-github","link":"#_3-3-connecting-jenkins-to-github","children":[]},{"level":2,"title":"3.4 Webhook build triggering","slug":"_3-4-webhook-build-triggering","link":"#_3-4-webhook-build-triggering","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.62,"words":487},"filePathRelative":"05-Software/Jenkins/pipeline101/03-builds.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    3.1 Tracking build state

    \\n

    Pipeline이 수행되는 동작을 추적하는 과정을 확인합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-01.TrackingBuildState)

    \\n

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    \\n
    pipeline {\\n    agent any\\n    stages {\\n        stage('Deploy') {\\n            steps {\\n                timeout(time: 1, unit: 'MINUTES') {\\n                    sh 'for n in \`seq 1 10\`; do echo $n; sleep 1; done'\\n                }\\n                timeout(time: 1, unit: 'MINUTES') {\\n                    sh 'for n in \`seq 1 50\`; do echo $n; sleep 1; done'\\n                }\\n            }\\n        }\\n    }\\n}\\n
    "}`);export{$ as comp,q as data}; diff --git a/assets/03-eks-storage.html-Y0k0NC6t.js b/assets/03-eks-storage.html-Y0k0NC6t.js new file mode 100644 index 0000000000..30dbb71fc1 --- /dev/null +++ b/assets/03-eks-storage.html-Y0k0NC6t.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,b as e}from"./app-Bzk8Nrll.js";const r={},n=e("h1",{id:"aews-3주차-amzaon-eks-storage",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#aews-3주차-amzaon-eks-storage"},[e("span",null,"AEWS 3주차 - Amzaon EKS Storage")])],-1),s=[n];function m(i,c){return o(),a("div",null,s)}const d=t(r,[["render",m],["__file","03-eks-storage.html.vue"]]),g=JSON.parse('{"path":"/02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.html","title":"AEWS 3주차 - Amzaon EKS Storage","lang":"ko-KR","frontmatter":{"description":"AEWS 3주차 - Amzaon EKS Storage ","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"AEWS 3주차 - Amzaon EKS Storage"}],["meta",{"property":"og:description","content":"AEWS 3주차 - Amzaon EKS Storage "}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"AEWS 3주차 - Amzaon EKS Storage\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.02,"words":5},"filePathRelative":"02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.md","localizedDate":"2023년 9월 18일","excerpt":"\\n","autoDesc":true}');export{d as comp,g as data}; diff --git a/assets/03-installation.html-C2Y8sNUT.js b/assets/03-installation.html-C2Y8sNUT.js new file mode 100644 index 0000000000..64d46e3e6e --- /dev/null +++ b/assets/03-installation.html-C2Y8sNUT.js @@ -0,0 +1,21 @@ +import{_ as d}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as h,c as u,b as a,d as e,a as s,w as t,e as i}from"./app-Bzk8Nrll.js";const m={},_=a("h1",{id:"_3-tomcat-설치",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_3-tomcat-설치"},[a("span",null,"3. Tomcat 설치")])],-1),g=a("ul",null,[a("li",null,"설치파일 받기"),a("li",null,"윈도우에 설치하기"),a("li",null,"유닉스/리눅스에 설치하기"),a("li",null,"설치 후 작업"),a("li",null,"디렉토리 구조")],-1),b=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/621YGq7ulS4",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),v=a("hr",null,null,-1),A=a("p",null,[e("설치되는 톰캣의 최상위 경로는 "),a("code",null,"$CATALINA_HOME"),e(" 으로 표현합니다.")],-1),f=a("h2",{id:"_3-1-설치파일-받기",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_3-1-설치파일-받기"},[a("span",null,"3.1 설치파일 받기")])],-1),k={href:"https://tomcat.apache.org/whichversion.html",target:"_blank",rel:"noopener noreferrer"},w=a("code",null,"Downloads",-1),y=a("li",null,"zip",-1),T=a("li",null,"tar.gz",-1),S=a("li",null,"32-bit Windows zip",-1),x=a("li",null,"64-bit Windows zip",-1),L={href:"https://tomcat.apache.org/tomcat-8.5-doc/changelog.html",target:"_blank",rel:"noopener noreferrer"},O=a("li",null,"32-bit/64-bit Windows Service Installer",-1),H=i('

    zip과 tar.gz는 Unix/Linux환경에서 사용할 압축된 형태의 파일이고 나머지 네가지는 CPU 아키텍쳐에 맞게 컴파일된 zip 형태의 압축 파일과 서비스에 등록할수 있는 형태의 인스톨러로 구성되어 있습니다. 설치하고자 하는 OS와 CPU 아키텍처에 맞는 설치파일을 받아 준비합니다.

    3.2 윈도우에 설치하기

    Windows에는 압축파일을 풀어 설치하는 방법과 서비스 등록을 위한 인스톨러 두가지 방식이 있었습니다. 압축파일 형태의 경우 압축을 풀기만하면 바로 실행이 가능합니다. 서비스 인스톨러의 경우 서비스에 등록하기 위한 설치 경로와 같은 정보를 입력하여 진행합니다.

    압축파일로 설치한 경우 %CATALINA_HOME%\\bin에 위치한 startup.bat으로 시작하고 shutdown.bat으로 종료합니다.

    서비스로 설치한 경우 윈도우 서비스 관리 유틸리티에서 서비스의 '시작/종료'를 사용하거나 net start (서비스이름) 또는 net stop (서비스이름)을 사용하여 서비스의 시작과 종료가 가능합니다.

    3.3 유닉스/리눅스/맥 에 설치하기

    Unix/Linux의 binary 설치파일은 압축을 풀어 설치합니다.

    $CATALINA_HOME/bin에 위치한 startup.sh으로 시작하고 shutdown.sh으로 종료합니다.

    3.4 설치 후 작업

    설치 방법은 매우 간단하나 설치 후 꼭 해야할 작업이 있습니다. Java Home을 설정하고 성능을 위한 Native library를 설치하는 작업 입니다.

    Java Home의 경우 앞서 JDK를 OS에 설치하였다면 톰캣에서 이를 사용할 수 있도록 경로를 잡아주는 과정입니다. OS자체의 PATH나 환경변수로 지정하는 방법도 있고 톰캣의 스크립트에 변수로 넣어주는 방법이 있습니다. OS자체의 PATH로 설정하는 경우 해당 OS에 설치된 모든 Java 실행환경이 영향을 받게 됩니다. 따라서 일관된 서비스, 일관된 톰캣 운영 환경인 경우 같은 Java Home이 설정되는 장점이 있습니다. 이와달리 스크립트에 Java Home을 설정하면 해당 톰캣에서만 관련 설정의 영향을 받습니다. OS내에 서로 다른 JDK로 동작하는 서비스나 어플리케이션이 있다면 스크립트를 이용하는 방법을 사용할 수 있습니다.

    3.4.1 Java Home 설정하기

    OS 환경에 Java Home을 설정하는 방법은 다음과 같습니다. (Java Home은 JDK의 bin 디렉토리를 포함한 상위 디렉토리 입니다.)

    3.4.1.1 환경변수로 지정

    ',14),N=a("p",null,[e("계정 루트의 "),a("code",null,".profile"),e(" (bash 쉘의 경우 "),a("code",null,".bash_profile"),e(")에 다음을 설정")],-1),J=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token comment"},"# .bash_profile"),e(` +`),a("span",{class:"token builtin class-name"},"export"),e(),a("span",{class:"token assign-left variable"},"JAVA_HOME"),a("span",{class:"token operator"},"="),e(`/usr/java/jre1.8.0_241 +`)])])],-1),P=a("p",null,"내컴퓨터 우클릭 > 고급 > 환경변수 > JAVA_HOME 추가",-1),I=a("figure",null,[a("img",{src:"https://github.com/Great-Stone/great-stone.github.io/blob/master/assets/img/Tomcat_youtube/JavaHomeOnWindows.png?raw=true",alt:"JavaHomeOnWindows",tabindex:"0",loading:"lazy"}),a("figcaption",null,"JavaHomeOnWindows")],-1),R=a("h4",{id:"_3-4-1-2-스크립트에-추가",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_3-4-1-2-스크립트에-추가"},[a("span",null,"3.4.1.2 스크립트에 추가")])],-1),C=a("p",null,[e("스크립트에 Java_Home을 설정하는 경우 "),a("code",null,"catalina.sh(bat)"),e("에 "),a("code",null,"JAVA_HOME"),e(" 으로 지정하는 경우가 있습니다. 추가로 "),a("code",null,"setenv.sh(bat)"),e("을 생성하여 해당 스크립트에 설정하는 방법을 권장합니다.")],-1),E=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token comment"},"# setenv.sh"),e(` +`),a("span",{class:"token assign-left variable"},"JAVA_HOME"),a("span",{class:"token operator"},"="),a("span",{class:"token string"},'"/usr/java/jre1.8.0_241"'),e(` +`)])])],-1),$=a("div",{class:"language-powershell","data-ext":"powershell","data-title":"powershell"},[a("pre",{class:"language-powershell"},[a("code",null,[a("span",{class:"token comment"},"# setenv.bat"),e(` +JAVA_HOME=C:\\Progra~1\\java\\jre1`),a("span",{class:"token punctuation"},"."),e("8"),a("span",{class:"token punctuation"},"."),e(`0_241 +`)])])],-1),z=i(`

    Windows환경에서 Java를 C:\\Program Files에 설치하는 경우 중간에 공백이 있기 때문에 C:\\Progra~1로 표현함에 주의합니다.

    3.4.2 Native Library 적용하기 (옵션)

    톰캣의 Native Library를 적용하지 않고도 충분히 톰캣을 실행하고 사용할 수 있습니다. 다만 톰캣의 콘솔 로그에 다음의 메시지가 걸리적(?)거리게 발생합니다.

    The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path
    +

    굳이 필요하지 않다면 적용하지 않아도 되지만 Native Library가 주는 이점은 다음과 같습니다.

    결국 NIO(Non-Blocking I/O)를 지원하는 것과 SSL관련 설정을 지원합니다. OpenSSL을 사용하지 않아도 NIO를 사용하는 성능상의 이점을 제공합니다.

    Native Library를 사용하기 위해서는 APR과 Tomcat Native 소스 파일이 필요합니다. APR은 Apache 2.0 이상 버전이 설치되어있다면 해당 설치 경로의 $APACHE_HOME/bin/apr-1-config과 같이 존재하나 없는 경우 별도의 APR을 설치합니다.

    `,8),M={href:"http://apr.apache.org/",target:"_blank",rel:"noopener noreferrer"},W=i(`

    APR 홈페이지에서 받은 apr-x.x.x.tar.gz 파일은 다음의 순서를 따라 설치를 진행하며, 예제와 같이 /usr/local에 설치하는 경우 root 계정이 필요합니다.

    $ gzip -d apr-1.5.1.tar.gz
    +$ tar -xvf apr-1.5.1.tar
    +$ configure --prefix=/usr/local/apr-1.5.1
    +$ make
    +$ make install
    +
    `,2),B=a("code",null,"Downloads",-1),j={href:"http://tomcat.apache.org/download-native.cgi",target:"_blank",rel:"noopener noreferrer"},D=i(`

    Native Library는 다음과 같이 설치를 진행합니다.

    $ gzip -d tomcat-native.tar.gz
    +$ tar -xvf tomcat-native.tar
    +$ configure --prefix=$CATALINA_HOME --with-apr=/usr/local/apr-1.5.1/bin/apr-1-config  --with-java-home=$JAVA_HOME
    +$ make
    +$ install
    +

    소스의 컴파일과 설치가 완료되면 "$CATALINA_HOME/lib"에서 libtcnative 관련 파일의 확인을 할 수 있습니다.

    `,3),q={href:"http://setenv.sh",target:"_blank",rel:"noopener noreferrer"},U=i(`
    # setenv.sh(bat)
    +LD_LIBRARY_PATH "$CATALINA_HOME/lib"
    +
    OSShared Library Path
    WindowsPATH
    SolarisLD_LIBRARY_PATH
    HP-UXSHLIB_PATH
    AIXLIBPATH,LD_LIBRARY_PATH
    LinuxLD_LIBRARY_PATH
    OS/2LIBPATH
    OSXLD_LIBRARY_PATH

    적용된 Native Library는 톰캣의 콘솔 로그 "$CATALINA_HOME/logs/catalina.out(log)"에서 다음의 메시지를 확인 할 수 있습니다.

    Info: Loaded APR based Apache Tomcat Native library 1.1.31.
    +
    `,4);function V(X,Y){const l=r("ExternalLinkIcon"),c=r("Tabs"),p=r("CodeTabs");return h(),u("div",null,[_,g,b,v,A,f,a("p",null,[e("설치파일은 "),a("a",k,[e("톰캣 홈페이지"),s(l)]),e("에서 좌측의 "),w,e("에서 설치하고자 하는 버전을 선택하면 우측에 Binary와 Source Code 두가지 형태로 받을 수 있습니다. Binary는 OS플랫폼에 맞게 미리 준비된 설치파일로서 Core에 해당하는 파일을 받습니다. 종류는 여섯가지로 나뉘어 있습니다.")]),a("ul",null,[y,T,S,x,a("li",null,[e("64-bit Itanium Windows zip ("),a("a",L,[e("8.5.0부터 제외"),s(l)]),e(")")]),O]),H,s(c,{id:"115",data:[{id:"Unix/Linux/OSX"},{id:"Windows"}]},{title0:t(({value:n,isActive:o})=>[e("Unix/Linux/OSX")]),title1:t(({value:n,isActive:o})=>[e("Windows")]),tab0:t(({value:n,isActive:o})=>[N,J]),tab1:t(({value:n,isActive:o})=>[P,I]),_:1}),R,C,s(p,{id:"137",data:[{id:"Unix/Linux/OSX"},{id:"Windows"}]},{title0:t(({value:n,isActive:o})=>[e("Unix/Linux/OSX")]),title1:t(({value:n,isActive:o})=>[e("Windows")]),tab0:t(({value:n,isActive:o})=>[E]),tab1:t(({value:n,isActive:o})=>[$]),_:1}),z,a("p",null,[e("(Apache APR 홈페이지 : "),a("a",M,[e("http://apr.apache.org/"),s(l)]),e(")")]),W,a("p",null,[e('일반적인 경우 Tomcat이 설치되면 설치된 경로의 "$CATALINA_HOME/bin" 위치에 tomcat-native.tar.gz 형태로 설치 파일이 존재하나 없는 경우, 혹은 높은 버전을 설치하고 싶은 경우 별도로 다운로드 받아 진행합니다. Native의 다운로드는 톰켓 홈페이지의 좌측 '),B,e("의 하위에 "),a("a",j,[e("Tomcat Native"),s(l)]),e("로 존재합니다. 관련 링크를 클릭하면 우측에 OS플랫폼에 맞게 소스를 받을 수 있는 링크가 제공됩니다.")]),D,a("p",null,[e("Native Library가 해당 플랫폼 환경에 맞게 생성되었다면 Shared Library로 설정하여 적용하며 적용방법은 앞서 Java Home을 설정하는 방법을 이용합니다. Shared Library 환경변수명은 OS마다 상이함에 주의하며 "),a("a",q,[e("setenv.sh"),s(l)]),e("(bat)에 적용하는 방법은 다음과 같습니다.")]),U])}const Z=d(m,[["render",V],["__file","03-installation.html.vue"]]),F=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/03-installation.html","title":"3. Tomcat 설치","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/03-installation.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"3. Tomcat 설치"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://github.com/Great-Stone/great-stone.github.io/blob/master/assets/img/Tomcat_youtube/JavaHomeOnWindows.png?raw=true"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"3. Tomcat 설치"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"3. Tomcat 설치\\",\\"image\\":[\\"https://github.com/Great-Stone/great-stone.github.io/blob/master/assets/img/Tomcat_youtube/JavaHomeOnWindows.png?raw=true\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"3.1 설치파일 받기","slug":"_3-1-설치파일-받기","link":"#_3-1-설치파일-받기","children":[]},{"level":2,"title":"3.2 윈도우에 설치하기","slug":"_3-2-윈도우에-설치하기","link":"#_3-2-윈도우에-설치하기","children":[]},{"level":2,"title":"3.3 유닉스/리눅스/맥 에 설치하기","slug":"_3-3-유닉스-리눅스-맥-에-설치하기","link":"#_3-3-유닉스-리눅스-맥-에-설치하기","children":[]},{"level":2,"title":"3.4 설치 후 작업","slug":"_3-4-설치-후-작업","link":"#_3-4-설치-후-작업","children":[{"level":3,"title":"3.4.1 Java Home 설정하기","slug":"_3-4-1-java-home-설정하기","link":"#_3-4-1-java-home-설정하기","children":[]}]}],"git":{"createdTime":1640327880000,"updatedTime":1698218919000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":1.38,"words":414},"filePathRelative":"05-Software/Tomcat/tomcat101/03-installation.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n"}');export{Z as comp,F as data}; diff --git a/assets/03-terraform-in-Action.html-DJ9Tld7l.js b/assets/03-terraform-in-Action.html-DJ9Tld7l.js new file mode 100644 index 0000000000..b9a8402a6c --- /dev/null +++ b/assets/03-terraform-in-Action.html-DJ9Tld7l.js @@ -0,0 +1,116 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c as p,b as n,a as l,w as c,e as s,d as a}from"./app-Bzk8Nrll.js";const i="/assets/blast_radius_graph_1-DduPuAlv.png",u={},d=s(`

    03. 테라폼 실행

    리소스 분석

    모든 Terraform으로 구성되는 리소스는 정확히 동일한 방식으로 구성됩니다.

    resource type "name" {
    +  parameter = "foo"
    +  parameter2 = "bar"
    +  list = ["one", "two", "three"]
    +}
    +

    Terraform Provider 구성

    Terraform Core는 무엇이든 빌드하려면 하나 이상의 Provider가 필요합니다.

    사용하려는 Provider의 버전을 쑤동으로 구성 할 수 있습니다. 이 옵션을 비워두면 Terraform은 기본적으로 사용 가능한 최신 버전의 Provider를 사용합니다.

    `,8),m=n("div",{class:"language-hcl line-numbers-mode","data-ext":"hcl","data-title":"hcl"},[n("pre",{hcl:"",class:"language-hcl"},[n("code",null,[n("span",{class:"token keyword"},"terraform"),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token keyword"},"required_providers"),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token property"},"ncloud"),a(),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token property"},"source"),a(" "),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token string"},'"NaverCloudPlatform/ncloud"'),a(` + `),n("span",{class:"token property"},"version"),a(),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token string"},'">= 2.1.2"'),a(` + `),n("span",{class:"token punctuation"},"}"),a(` + `),n("span",{class:"token punctuation"},"}"),a(` +`),n("span",{class:"token punctuation"},"}"),a(` + +`),n("span",{class:"token keyword"},[a("provider"),n("span",{class:"token type variable"},' "ncloud" ')]),n("span",{class:"token punctuation"},"{"),a(),n("span",{class:"token punctuation"},"}"),a(` +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("br"),n("br"),n("br"),n("div",{class:"highlight-line"}," "),n("br"),n("br"),n("br"),n("br"),n("br")]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),k=s('

    버전관리 연산자

    Terraform Apply

    ',3),v=n("div",{class:"language-bash line-numbers-mode","data-ext":"sh","data-title":"sh"},[n("pre",{bash:"",class:"language-bash"},[n("code",null,[a(`$ terraform apply +`),n("span",{class:"token punctuation"},".."),a(`. +Terraform will perform the following actions: + + `),n("span",{class:"token comment"},"# ncloud_vpc.hashicat will be created"),a(` + + resource `),n("span",{class:"token string"},'"ncloud_vpc"'),a(),n("span",{class:"token string"},'"hashicat"'),a(),n("span",{class:"token punctuation"},"{"),a(` + + default_access_control_group_no `),n("span",{class:"token operator"},"="),a(),n("span",{class:"token punctuation"},"("),a("known after apply"),n("span",{class:"token punctuation"},")"),a(` + `),n("span",{class:"token punctuation"},".."),a(`. + + ipv4_cidr_block `),n("span",{class:"token operator"},"="),a(),n("span",{class:"token string"},'"10.0.0.0/16"'),a(` + + vpc_no `),n("span",{class:"token operator"},"="),a(),n("span",{class:"token punctuation"},"("),a("known after apply"),n("span",{class:"token punctuation"},")"),a(` + `),n("span",{class:"token punctuation"},"}"),a(` + +Plan: `),n("span",{class:"token number"},"1"),a(" to add, "),n("span",{class:"token number"},"0"),a(" to change, "),n("span",{class:"token number"},"0"),a(` to destroy. + +Do you want to perform these actions? + Terraform will perform the actions described above. + Only `),n("span",{class:"token string"},"'yes'"),a(` will be accepted to approve. + + Enter a value: +`)])]),n("div",{class:"highlight-lines"},[n("div",{class:"highlight-line"}," "),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br")]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),b=n("p",null,[n("code",null,"terraform apply"),a("는 우선 "),n("code",null,"plan"),a("을 실행하고, 승인하면 변경 사항을 적용합니다.")],-1),h=n("h2",{id:"terraform-destroy",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#terraform-destroy"},[n("span",null,"Terraform Destroy")])],-1),g=n("div",{class:"language-bash line-numbers-mode","data-ext":"sh","data-title":"sh"},[n("pre",{bash:"",class:"language-bash"},[n("code",null,[a(`$ terraform apply +`),n("span",{class:"token punctuation"},".."),a(`. +Terraform will perform the following actions: + + `),n("span",{class:"token comment"},"# ncloud_vpc.hashicat will be destoryed"),a(` + - resource `),n("span",{class:"token string"},'"ncloud_vpc"'),a(),n("span",{class:"token string"},'"hashicat"'),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token punctuation"},".."),a(`. + - ipv4_cidr_block `),n("span",{class:"token operator"},"="),a(),n("span",{class:"token string"},'"10.0.0.0/16"'),a(" -"),n("span",{class:"token operator"},">"),a(` null + `),n("span",{class:"token punctuation"},"}"),a(` + +Plan: `),n("span",{class:"token number"},"0"),a(" to add, "),n("span",{class:"token number"},"0"),a(" to change, "),n("span",{class:"token number"},"1"),a(` to destroy. + +Do you want to perform these actions? + Terraform will perform the actions described above. + Only `),n("span",{class:"token string"},"'yes'"),a(` will be accepted to approve. + + Enter a value: +`)])]),n("div",{class:"highlight-lines"},[n("div",{class:"highlight-line"}," "),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br")]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),f=s(`

    terraform destroyaction과 반대 입니다. 승인하면 인프라가 제거됩니다.

    Terraform Format

    Terraform은 내장 된 코드 포맷터/클리너와 함께 제공됩니다. 모든 여백과 목록 들여 쓰기를 깔끔하고 깔끔하게 만들 수 있습니다. 아름다운 코드가 더 잘 동작하는 것(?) 같습니다.

    terraform fmt
    +

    *.tf 파일이 포함 된 디렉토리에서 실행하기 만하면 코드가 정리됩니다.

    Terraform Data Sources

    data "ncloud_member_server_images" "prod" {
    + filter {
    +    name = "name"
    +    values = [data.terraform_remote_state.image_name.outputs.image_name]
    +  }
    +}
    +
    +resource "ncloud_server" "server" {
    +  name                      = "${var.server_name}${random_id.id.hex}"
    +  member_server_image_no    = data.ncloud_member_server_images.prod.member_server_images.0
    +  server_product_code       = "SPSVRGPUSSD00001" 
    +  login_key_name            = ncloud_login_key.key.key_name
    +  zone                      = var.zone
    +}
    +

    Data Source(data)는 Provider가 기존 리소스를 반환하도록 쿼리하는 방법입니다.

    생성되어있는 리소스나 Provider로 조회할 수 있는 리소스 정보를 다른 리소스 구성에서 접근할 수 있습니다.

    Terraform Dependency Mapping

    `,10),_=n("div",{class:"language-hcl line-numbers-mode","data-ext":"hcl","data-title":"hcl"},[n("pre",{hcl:"",class:"language-hcl"},[n("code",null,[n("span",{class:"token keyword"},[a("data "),n("span",{class:"token type variable"},'"ncloud_member_server_images"')]),a(),n("span",{class:"token string"},'"prod"'),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token keyword"},"filter"),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token property"},"name"),a(),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token string"},'"name"'),a(` + `),n("span",{class:"token property"},"values"),a(),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token punctuation"},"["),a("data.terraform_remote_state.image_name.outputs.image_name"),n("span",{class:"token punctuation"},"]"),a(` + `),n("span",{class:"token punctuation"},"}"),a(` +`),n("span",{class:"token punctuation"},"}"),a(` + +`),n("span",{class:"token keyword"},[a("resource "),n("span",{class:"token type variable"},'"ncloud_server"')]),a(),n("span",{class:"token string"},'"server"'),a(),n("span",{class:"token punctuation"},"{"),a(` + `),n("span",{class:"token property"},"name"),a(" "),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token string"},[a('"'),n("span",{class:"token interpolation"},[n("span",{class:"token punctuation"},"$"),n("span",{class:"token punctuation"},"{"),n("span",{class:"token keyword"},"var"),n("span",{class:"token punctuation"},"."),n("span",{class:"token type variable"},"server_name"),n("span",{class:"token punctuation"},"}")]),n("span",{class:"token interpolation"},[n("span",{class:"token punctuation"},"$"),n("span",{class:"token punctuation"},"{"),a("random_id"),n("span",{class:"token punctuation"},"."),a("id"),n("span",{class:"token punctuation"},"."),a("hex"),n("span",{class:"token punctuation"},"}")]),a('"')]),a(` + `),n("span",{class:"token property"},"member_server_image_no"),a(" "),n("span",{class:"token punctuation"},"="),a(" data.ncloud_member_server_images.prod.member_server_images."),n("span",{class:"token number"},"0"),a(` + `),n("span",{class:"token property"},"server_product_code"),a(" "),n("span",{class:"token punctuation"},"="),a(),n("span",{class:"token string"},'"SPSVRGPUSSD00001"'),a(` + `),n("span",{class:"token property"},"login_key_name"),a(" "),n("span",{class:"token punctuation"},"="),a(` ncloud_login_key.key.key_name + `),n("span",{class:"token property"},"zone"),a(" "),n("span",{class:"token punctuation"},"="),a(` var.zone +`),n("span",{class:"token punctuation"},"}"),a(` +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("br"),n("br"),n("div",{class:"highlight-line"}," "),n("br"),n("br"),n("br"),n("br"),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("br"),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("br")]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),y=s(`

    Terraform은 자동으로 종속성을 추적 할 수 있습니다. 앞서 설명된 리소스를 살펴보십시오. ncloud_server 리소스에서 강조 표시된 줄을 확인합니다. 이것이 테라 폼에서 한 리소스가 다른 리소스를 참조하도록하는 방법입니다.

    Terraform 코드 구성

    Terraform은 Workspace에서 .tf 확장자로 끝나는 모든 파일을 읽지만 대표적으로는 main.tf, variables.tf, outputs.tf를 갖는 것입니다. 원하는 경우 더 많은 tf 파일을 추가 할 수 있습니다.

    파일 구조

    Workspace
    +├── \`main.tf\`
    +├── \`outputs.tf\`
    +├── terraform.tfvars
    +└── \`variables.tf\`
    +

    이러한 각 파일을 자세히 살펴 보겠습니다.

    main.tf 파일

    첫 번째 파일은 main.tf입니다. 일반적으로 테라 폼 코드를 저장하는 곳입니다. 더 크고 복잡한 인프라를 사용하면이를 여러 파일로 나눌 수 있습니다.

    resource "ncloud_vpc" "main" {
    +  ipv4_cidr_block = var.address_space
    +  name            = lower("${var.prefix}-vpc-${var.region}")
    +}
    +
    +resource "ncloud_subnet" "main" {
    +  name           = "${var.name_scn02}-public"
    +  vpc_no         = ncloud_vpc.vpc_scn_02.id
    +  subnet         = cidrsubnet(ncloud_vpc.main.ipv4_cidr_block, 8, 0)
    +  zone           = "KR-2"
    +  network_acl_no = ncloud_network_acl.network_acl_02_public.id
    +  subnet_type    = "PUBLIC"
    +}
    +
    +...생략...
    +

    variable.tf 파일

    두 번째 파일은 variables.tf입니다. 여기에서 변수를 정의하고 선택적으로 일부 기본값을 설정합니다.

    variable "prefix" {
    +  description = "This prefix will be included in the name of most resources."
    +}
    +
    +variable "region" {
    +  description = "The region where the resources are created."
    +  default     = "KR"
    +}
    +

    output.tf 파일

    output.tf 파일은 테라 폼 적용이 끝날 때 표시 할 메시지 또는 데이터를 구성하는 곳입니다.

    output "acl_public_id" {
    +  value = ncloud_network_acl.network_acl_public.id
    +}
    +
    +output "public_addr" {
    +  value = "http://${ncloud_public_ip.main.public_ip}:8080"
    +}
    +

    Terraform Dependency Graph

    terraform 리소스 그래프는 리소스 간의 종속성을 시각적으로 보여줍니다.

    RegionPrefix 변수는 리소스 그룹을 만드는 데 필요하며 이는 가상 네트워크를 구축하는 데 필요합니다.


    실습을 위해 다음장으로 이동하세요.

    ',20);function q(w,T){const e=o("RouteLink");return r(),p("div",null,[d,m,k,v,b,h,g,f,_,y,n("p",null,[l(e,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html"},{default:c(()=>[a("💻 Lab - Terraform in Action")]),_:1})])])}const C=t(u,[["render",q],["__file","03-terraform-in-Action.html.vue"]]),D=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html","title":"03. 테라폼 실행","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"03. 테라폼 실행"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-19T11:31:31.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-19T11:31:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"03. 테라폼 실행\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-19T11:31:31.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"리소스 분석","slug":"리소스-분석","link":"#리소스-분석","children":[]},{"level":2,"title":"Terraform Provider 구성","slug":"terraform-provider-구성","link":"#terraform-provider-구성","children":[]},{"level":2,"title":"버전관리 연산자","slug":"버전관리-연산자","link":"#버전관리-연산자","children":[]},{"level":2,"title":"Terraform Apply","slug":"terraform-apply","link":"#terraform-apply","children":[]},{"level":2,"title":"Terraform Destroy","slug":"terraform-destroy","link":"#terraform-destroy","children":[]},{"level":2,"title":"Terraform Format","slug":"terraform-format","link":"#terraform-format","children":[]},{"level":2,"title":"Terraform Data Sources","slug":"terraform-data-sources","link":"#terraform-data-sources","children":[]},{"level":2,"title":"Terraform Dependency Mapping","slug":"terraform-dependency-mapping","link":"#terraform-dependency-mapping","children":[]},{"level":2,"title":"Terraform 코드 구성","slug":"terraform-코드-구성","link":"#terraform-코드-구성","children":[{"level":3,"title":"main.tf 파일","slug":"main-tf-파일","link":"#main-tf-파일","children":[]},{"level":3,"title":"variable.tf 파일","slug":"variable-tf-파일","link":"#variable-tf-파일","children":[]},{"level":3,"title":"output.tf 파일","slug":"output-tf-파일","link":"#output-tf-파일","children":[]}]},{"level":2,"title":"Terraform Dependency Graph","slug":"terraform-dependency-graph","link":"#terraform-dependency-graph","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695123091000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":1.35,"words":406},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    리소스 분석

    \\n

    모든 Terraform으로 구성되는 리소스는 정확히 동일한 방식으로 구성됩니다.

    \\n
    resource type \\"name\\" {\\n  parameter = \\"foo\\"\\n  parameter2 = \\"bar\\"\\n  list = [\\"one\\", \\"two\\", \\"three\\"]\\n}\\n
    "}');export{C as comp,D as data}; diff --git a/assets/03-traffic-metadata-C8cC4nAb.png b/assets/03-traffic-metadata-C8cC4nAb.png new file mode 100644 index 0000000000..8e06989bb7 Binary files /dev/null and b/assets/03-traffic-metadata-C8cC4nAb.png differ diff --git a/assets/03-traffic-methods-BH17zSFz.png b/assets/03-traffic-methods-BH17zSFz.png new file mode 100644 index 0000000000..c7b4d19825 Binary files /dev/null and b/assets/03-traffic-methods-BH17zSFz.png differ diff --git a/assets/03-traffic-routeui-Dm1u6sgH.png b/assets/03-traffic-routeui-Dm1u6sgH.png new file mode 100644 index 0000000000..d0af2edbfd Binary files /dev/null and b/assets/03-traffic-routeui-Dm1u6sgH.png differ diff --git a/assets/03-traffic-v1-BGAizMHc.png b/assets/03-traffic-v1-BGAizMHc.png new file mode 100644 index 0000000000..725cef3427 Binary files /dev/null and b/assets/03-traffic-v1-BGAizMHc.png differ diff --git a/assets/03-traffic-v2-CgPD7up9.png b/assets/03-traffic-v2-CgPD7up9.png new file mode 100644 index 0000000000..74779cba35 Binary files /dev/null and b/assets/03-traffic-v2-CgPD7up9.png differ diff --git a/assets/03-use-crd-error-BKh9b3mR.png b/assets/03-use-crd-error-BKh9b3mR.png new file mode 100644 index 0000000000..df0b8b7948 Binary files /dev/null and b/assets/03-use-crd-error-BKh9b3mR.png differ diff --git a/assets/03-use-crd-github-GQpHsMA9.png b/assets/03-use-crd-github-GQpHsMA9.png new file mode 100644 index 0000000000..c4dae82e8f Binary files /dev/null and b/assets/03-use-crd-github-GQpHsMA9.png differ diff --git a/assets/03-use-crd-hashicups-BHVZ8qNM.png b/assets/03-use-crd-hashicups-BHVZ8qNM.png new file mode 100644 index 0000000000..1a78270b1f Binary files /dev/null and b/assets/03-use-crd-hashicups-BHVZ8qNM.png differ diff --git a/assets/03-use-crd.html-oAT-nlvR.js b/assets/03-use-crd.html-oAT-nlvR.js new file mode 100644 index 0000000000..3c99003c24 --- /dev/null +++ b/assets/03-use-crd.html-oAT-nlvR.js @@ -0,0 +1,100 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c,b as e,d as s,a,e as t}from"./app-Bzk8Nrll.js";const l="/assets/03-use-crd-github-GQpHsMA9.png",d="/assets/03-use-crd-error-BKh9b3mR.png",p="/assets/02-intention-alldeny-CpsNCiFl.png",u="/assets/02-intention-crd-DDJBL_BF.png",m="/assets/03-use-crd-hashicups-BHVZ8qNM.png",v={},h=e("h1",{id:"_03-crd로-consul-serive-mesh-관리",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_03-crd로-consul-serive-mesh-관리"},[e("span",null,"03. CRD로 Consul Serive Mesh 관리")])],-1),b={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes",target:"_blank",rel:"noopener noreferrer"},g={href:"https://learn.hashicorp.com/tutorials/consul/service-mesh-zero-trust-network?in=consul/gs-consul-service-mesh",target:"_blank",rel:"noopener noreferrer"},k=e("p",null,"Consul 1.9 이전에는 Kubernetes에서 Consul과 함께 구성 항목을 사용할 때 운영자가 실행 중인 컨테이너에 들어가거나 로컬 Consul 바이너리를 사용하여 구성해야 했습니다. 1.9 이전 버전에서는 구성 항목을 Consul CLI, HTTP API로 관리하거나 시작하는 동안 구성 파일로 에이전트에 제공해야 합니다.",-1),f=e("p",null,[s("Consul 1.9부터 대부분의 구성 항목은 Kubernetes 사용자 지정 리소스 정의(CRD)로 관리할 수 있습니다. 이제 대부분의 구성 항목을 YAML로 정의하고 익숙한 "),e("code",null,"kubectl apply"),s("명령을 사용하여 Consul에 등록할 수 있습니다.")],-1),_=e("p",null,"현재 Kubernetes에서 Consul의 CRD로 사용할 수 있는 구성 항목은 다음과 같습니다.",-1),q={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes#proxy-defaults",target:"_blank",rel:"noopener noreferrer"},C=e("code",null,"proxy-defaults",-1),y={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes#service-defaults",target:"_blank",rel:"noopener noreferrer"},x=e("code",null,"service-defaults",-1),R={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes#service-resolver",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"service-resolver",-1),M={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes#service-router",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"service-router",-1),w={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes#service-splitter",target:"_blank",rel:"noopener noreferrer"},D=e("code",null,"service-splitter",-1),K={href:"https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes#service-intentions",target:"_blank",rel:"noopener noreferrer"},I=e("code",null,"service-intentions",-1),z=e("h2",{id:"샘플-애플리케이션-준비",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#샘플-애플리케이션-준비"},[e("span",null,"샘플 애플리케이션 준비")])],-1),E={href:"https://github.com/hashicorp/learn-consul-kubernetes",target:"_blank",rel:"noopener noreferrer"},A=t(`

    다운로드 후 learn-consul-kubernetes/service-mesh/deploy 경로로 이동하고 샘플 구성을 반영합니다.

    cd learn-consul-kubernetes/service-mesh/deploy
    +kubectl apply -f hashicups/
    +
    # 출력
    +service/frontend created
    +serviceaccount/frontend created
    +servicedefaults.consul.hashicorp.com/frontend created
    +configmap/nginx-configmap created
    +deployment.apps/frontend created
    +service/postgres created
    +serviceaccount/postgres created
    +servicedefaults.consul.hashicorp.com/postgres created
    +deployment.apps/postgres created
    +service/product-api created
    +serviceaccount/product-api created
    +servicedefaults.consul.hashicorp.com/product-api created
    +configmap/db-configmap created
    +deployment.apps/product-api created
    +service/public-api created
    +serviceaccount/public-api created
    +servicedefaults.consul.hashicorp.com/public-api created
    +deployment.apps/public-api created
    +

    서비스는 Consul이 각 서비스에 대한 프록시를 자동으로 삽입할 수 있도록 하는 annotation 을 사용합니다. 프록시 는 Consul의 구성을 기반으로 서비스 간의 요청을 처리하기 위해 데이터 플레인을 생성합니다. Consul이 주입되는 label을 선택하여 프록시가 있는 응용 프로그램을 확인할 수 있습니다.

    kubectl get pods --selector consul.hashicorp.com/connect-inject-status=injected
    +
    # 출력
    +NAME                           READY   STATUS    RESTARTS   AGE
    +frontend-98cb6859b-6ndvk       2/2     Running   0          3m10s
    +postgres-6ccb6d9968-hkbgz      2/2     Running   0          3m9s
    +product-api-6798bc4b4d-9ddv4   2/2     Running   2          3m9s
    +public-api-5bdf986897-tlxj2    2/2     Running   0          3m9s
    +

    배포된 앱에 접근하기 위해 port-forward를 구성합니다.

    kubectl port-forward service/frontend 18080:80 --address 0.0.0.0
    +
    # 출력
    +Forwarding from 0.0.0.0:18080 -> 80
    +
    `,10),H={href:"http://localhost:18080",target:"_blank",rel:"noopener noreferrer"},N=t('
    image-20220219202023073
    image-20220219202023073

    현재 Intention 규칙이 모두 deny로 구성되어있다면 에러 화면을 확인하게 됩니다.

    CRD 적용해보기

    UI에서가 아닌 CRD를 통해 Intention 을 정의하기위해 아래와 같이 구성합니다.

    cat > ./service-to-service.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: frontend-to-public-api
    +spec:
    +  destination:
    +    name: public-api
    +  sources:
    +    - name: frontend
    +      action: allow
    +---
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: public-api-to-product-api
    +spec:
    +  destination:
    +    name: product-api
    +  sources:
    +    - name: public-api
    +      action: allow
    +---
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: product-api-to-postgres
    +spec:
    +  destination:
    +    name: postgres
    +  sources:
    +    - name: product-api
    +      action: allow
    +EOF
    +

    규칙의 내용은 다음과 같습니다.

    규칙을 적용합니다.

    kubectl apply -f service-to-service.yaml
    +
    # 출력
    +serviceintentions.consul.hashicorp.com/frontend-to-public-api created
    +serviceintentions.consul.hashicorp.com/public-api-to-product-api created
    +serviceintentions.consul.hashicorp.com/product-api-to-postgres created
    +

    Consul UI에서 확인해보면 해당 Intention 규칙은 CRD로 적용되었기 때문에 Managed by CRD 표시가 붙는것을 확인할 수 있습니다.

    배포된 앱에 접근하기 위해 port-forward를 구성합니다.

    kubectl port-forward service/frontend 18080:80 --address 0.0.0.0
    +
    # 출력
    +Forwarding from 0.0.0.0:18080 -> 80
    +
    `,16),V={href:"http://localhost:18080",target:"_blank",rel:"noopener noreferrer"},B=t('
    image-20220219202943498
    image-20220219202943498

    서비스 간 연결이 허용되었으므로 페이지가 잘 표시됩니다.

    다음 과정을 위해 배포된 리소스를 정리합니다.

    kubectl delete -f service-to-service.yaml
    +
    # 출력
    +serviceintentions.consul.hashicorp.com "frontend-to-public-api" deleted
    +serviceintentions.consul.hashicorp.com "public-api-to-product-api" deleted
    +serviceintentions.consul.hashicorp.com "product-api-to-postgres" deleted
    +
    kubectl delete -f hashicups/
    +
    # 출력
    +service "frontend" deleted
    +serviceaccount "frontend" deleted
    +servicedefaults.consul.hashicorp.com "frontend" deleted
    +configmap "nginx-configmap" deleted
    +deployment.apps "frontend" deleted
    +service "postgres" deleted
    +serviceaccount "postgres" deleted
    +servicedefaults.consul.hashicorp.com "postgres" deleted
    +deployment.apps "postgres" deleted
    +service "product-api" deleted
    +serviceaccount "product-api" deleted
    +servicedefaults.consul.hashicorp.com "product-api" deleted
    +configmap "db-configmap" deleted
    +deployment.apps "product-api" deleted
    +service "public-api" deleted
    +serviceaccount "public-api" deleted
    +servicedefaults.consul.hashicorp.com "public-api" deleted
    +deployment.apps "public-api" deleted
    +
    `,7);function F(L,P){const n=o("ExternalLinkIcon");return r(),c("div",null,[h,e("blockquote",null,[e("p",null,[s("참고 : "),e("a",b,[s("https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes"),a(n)])]),e("p",null,[s("참고 : "),e("a",g,[s("https://learn.hashicorp.com/tutorials/consul/service-mesh-zero-trust-network?in=consul/gs-consul-service-mesh"),a(n)])])]),k,f,_,e("ul",null,[e("li",null,[e("a",q,[C,a(n)]),s("- 프록시 구성 제어")]),e("li",null,[e("a",y,[x,a(n)]),s("- 주어진 서비스의 모든 인스턴스에 대한 기본값을 구성합니다.")]),e("li",null,[e("a",R,[S,a(n)]),s("- 서비스 인스턴스를 특정 Connect 업스트림 검색 요청과 일치시킵니다.")]),e("li",null,[e("a",M,[T,a(n)]),s("- HTTP 경로를 기반으로 레이어 7 트래픽을 보낼 위치를 정의합니다.")]),e("li",null,[e("a",w,[D,a(n)]),s("- 백분율에 따라 단일 HTTP 경로에 대한 요청을 나누는 방법을 정의합니다.")]),e("li",null,[e("a",K,[I,a(n)]),s("- 특정 서비스 대 서비스 상호 작용에 대한 제한을 정의합니다.")])]),z,e("p",null,[s("필요한 애플리케이션을 다운로드 받습니다. git clone 또는 "),e("a",E,[s("https://github.com/hashicorp/learn-consul-kubernetes"),a(n)]),s(" 로 접속하여 Code를 다운로드 받습니다.")]),A,e("p",null,[s("브라우저에서 "),e("a",H,[s("http://localhost:18080"),a(n)]),s(" 로 접근합니다.")]),N,e("p",null,[s("브라우저에서 "),e("a",V,[s("http://localhost:18080"),a(n)]),s(" 로 접근합니다.")]),B])}const Z=i(v,[["render",F],["__file","03-use-crd.html.vue"]]),G=JSON.parse('{"path":"/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.html","title":"03. CRD로 Consul Serive Mesh 관리","lang":"ko-KR","frontmatter":{"description":"Consul Service Mesh on Kubernetes (Ent)","tag":["Consul","ServiceMesh","K8s","Kubernetes"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"03. CRD로 Consul Serive Mesh 관리"}],["meta",{"property":"og:description","content":"Consul Service Mesh on Kubernetes (Ent)"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"ServiceMesh"}],["meta",{"property":"article:tag","content":"K8s"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"03. CRD로 Consul Serive Mesh 관리\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"샘플 애플리케이션 준비","slug":"샘플-애플리케이션-준비","link":"#샘플-애플리케이션-준비","children":[]},{"level":2,"title":"CRD 적용해보기","slug":"crd-적용해보기","link":"#crd-적용해보기","children":[]}],"git":{"createdTime":1645936869000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.86,"words":559},"filePathRelative":"04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.md","localizedDate":"2022년 2월 27일","excerpt":"\\n
    \\n

    참고 : https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes

    \\n

    참고 : https://learn.hashicorp.com/tutorials/consul/service-mesh-zero-trust-network?in=consul/gs-consul-service-mesh

    \\n
    "}');export{Z as comp,G as data}; diff --git a/assets/03-z-lab_terraform_action.html-Cew6P_D8.js b/assets/03-z-lab_terraform_action.html-Cew6P_D8.js new file mode 100644 index 0000000000..c96c9f8c09 --- /dev/null +++ b/assets/03-z-lab_terraform_action.html-Cew6P_D8.js @@ -0,0 +1,56 @@ +import{_ as p}from"./lab1-02-By4gwc7V.js";import{_ as d}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as h,c as u,b as e,d as a,a as r,w as s,e as n}from"./app-Bzk8Nrll.js";const m="/assets/graphviz-1-CABq7Zn_.svg",f="/assets/lab2-01-CIFl3A_H.png",b="/assets/lab2-02-DGoHp863.png",k="/assets/lab2-03-CSmxmP8v.gif",g="/assets/graphviz-2-DN7qG3J4.svg",_={},v=n('

    💻 Lab - Terraform in Action

    편집기에서 열기


    📈 Terraform Graph

    @slidestart blood

    Terraform Graph는 모든 인프라에 대한 시각적 표현을 제공할 수 있습니다.


    이는 변경의 영향을 받을 종속성 문제 또는 리소스를 찾는 데 유용합니다.

    @slideend

    💻 다음 terraform graph명령을 실행해 보세요.

    새로운 Workspace 이므로, terraform init을 수행합니다.

    terraform init
    +

    terraform graph를 수행합니다.

    terraform graph
    +

    그러면 digraph로 시작하는 인프라의 시각적 맵을 만드는 데 사용할 수 있는 코드가 생성됩니다. 그래프 데이터는 DOT 그래프 설명 언어 형식 입니다. 무료 Blast Radius 도구를 포함하여 이 데이터를 시각화하는 데 사용할 수 있는 몇 가지 그래프 도구가 있습니다.

    `,17),y={href:"https://28mm.github.io/blast-radius-docs/",target:"_blank",rel:"noopener noreferrer"},x={href:"https://dreampuf.github.io/GraphvizOnline/",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"digraph",-1),C=e("li",null,[a("인프라에 대한 Terraform 그래프를 살펴보십시오. 종속성이 자동으로 매핑됩니다."),e("br"),e("img",{src:m,alt:"",loading:"lazy"})],-1),w=e("p",null,"온라인에 Terraform의 작업을 시각화해주는 여러가지 툴이 있습니다. 간혹 plan 파일을 요구하는 툴이 있다면 주의하십시오. 민감한 정보가 포한된 plan의 경우 보안적으로 위험할 수 있습니다. 주의하여 사용하세요.",-1),P={href:"https://hieven.github.io/terraform-visual/",target:"_blank",rel:"noopener noreferrer"},z=n(`

    경고

    plan 정보에는 인증키, 패스워드같은 노출하고 싶지 않은 정보가 포함될 수 있습니다.


    👨‍💻 Terraform Plan & Terraform Apply

    @slidestart blood

    기본적으로 terraform apply 명령은 Terraform Plan을 실행하여 원하는 변경 사항을 보여줍니다.


    이는 변경의 영향을 받을 종속성 문제 또는 리소스를 찾는 데 유용합니다.

    @slideend

    💻 필수 변수를 구성했으므로 변경 사항을 적용할 수 있습니다.

    어떤 일이 일어날지 보려면 먼저 terraform plan 명령을 실행하십시오 .

    terraform plan
    +

    계획 출력에 적절한 prefix, subnet cidr이 표시되는지 확인합니다. 원한다면 terraform.tfvars 혹은 variables.tf에 정의된 default값을 변경해보세요.

    그런 다음 terraform apply를 실행하고 리소스가 구축되는 것을 지켜보십시오.

    terraform apply
    +

    Terraform에서 "Do you want to perform these actions?"라는 메시지가 표시되면 yes 를 입력해야 합니다.

    Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 메시지를 확인하였습니까? 에러가 발생하였다면 무엇이 문제인지 찾아보세요.

    지금 우리 코드는 VPC만 정의합니다. 우리는 진행되는 실습에서 이 코드와 프로비저닝된 상태 기반으로 시작 할 것입니다.

    `,17),A={href:"https://console.ncloud.com/vpc-network/vpc",target:"_blank",rel:"noopener noreferrer"},D=e("figure",null,[e("img",{src:f,alt:"",tabindex:"0",loading:"lazy"}),e("figcaption")],-1),G=e("figure",null,[e("img",{src:b,alt:"",tabindex:"0",loading:"lazy"}),e("figcaption")],-1),R=e("hr",null,null,-1),V=e("h2",{id:"👩‍💻-test-and-repair",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#👩‍💻-test-and-repair"},[e("span",null,"👩‍💻 Test and Repair")])],-1),N=e("p",null,"@slidestart blood",-1),q=e("h3",{id:"terraform은-멱등성-idempotent-을-갖습니다",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#terraform은-멱등성-idempotent-을-갖습니다"},[e("span",null,"Terraform은 멱등성(idempotent)을 갖습니다.")])],-1),S=e("hr",null,null,-1),L=e("br",null,null,-1),O={href:"https://en.wikipedia.org/wiki/Idempotence",target:"_blank",rel:"noopener noreferrer"},B=n(`

    @slideend

    💻 멱등성의 검증을 해봅니다.

    어떤 일이 일어날지 보려면 먼저 terraform plan 명령을 실행하십시오.

    terraform plan
    +

    VPC가 이미 구축되었으므로 Terraform은 변경이 필요하지 않다고 보고합니다.

    이는 정상적이며 예상된 것입니다. 이제 다른 명령인 terraform apply를 실행하고 지켜보십시오.

    terraform apply
    +

    이미 올바르게 프로비저닝된 경우 VPC를 다시 생성하지 않습니다.

    Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

    🛫 Change Your Prefix

    @slidestart blood

    Terraform은 인프라를 Create, Destroy, Update, re-Create 합니다.


    리소스 내용 변경 시


    Terraform은 항상 현재 인프라를 코드에 정의된 것과 일치시키려고 합니다.

    @slideend

    💻 terraform.tfvars를 변경합니다.

    terraform.tfvars 파일을 편집하여 prefix를 기존과 다른 값으로 변경합니다.

    변경 후 terraform apply를 실행하고 지켜보십시오.

    terraform plan
    +

    VPC가 이미 구축되었으므로 Terraform은 변경이 필요하지 않다고 보고합니다.

    이는 정상적이며 예상된 것입니다. 이제 다른 명령인 terraform apply를 실행하고 지켜보십시오.

    terraform apply
    +

    Terraform에서 "Do you want to perform these actions?"라는 메시지가 표시되면 yes를 입력하고 완료되기를 기다립니다. 출력의 결과가 어떤가요?

    `,26),U=e("details",{class:"hint-container details"},[e("summary",null,"VPC Update"),e("p",null,[a("2021년 10월 20일 기준으로, VPC의 이름이 변경되면 NCP에서는 이 자원을 재생성 합니다."),e("br"),a(" 리소스에 대한 구성값의 변경이 유지된 채로 변경되기도 하지만, 때에 따라서는 삭제 후 재생성 합니다.")]),e("div",{class:"language-bash line-numbers-mode","data-ext":"sh","data-title":"sh"},[e("pre",{bash:"",class:"language-bash"},[e("code",null,[a(`$ terraform apply +ncloud_vpc.hashicat: Refreshing state`),e("span",{class:"token punctuation"},".."),a(". "),e("span",{class:"token punctuation"},"["),a("id"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"13888"),e("span",{class:"token punctuation"},"]"),a(` + +Terraform used the selected providers to generate the following execution plan. +Resource actions are indicated with the following symbols: +-/+ destroy and `),e("span",{class:"token keyword"},"then"),a(` create replacement + +Terraform will perform the following actions: + + `),e("span",{class:"token comment"},"# ncloud_vpc.hashicat must be replaced"),a(` +-/+ resource `),e("span",{class:"token string"},'"ncloud_vpc"'),a(),e("span",{class:"token string"},'"hashicat"'),a(),e("span",{class:"token punctuation"},"{"),a(` + ~ default_access_control_group_no `),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"26594"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token punctuation"},"("),a("known after apply"),e("span",{class:"token punctuation"},")"),a(` + ~ default_network_acl_no `),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"19325"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token punctuation"},"("),a("known after apply"),e("span",{class:"token punctuation"},")"),a(` + ~ default_private_route_table_no `),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"25834"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token punctuation"},"("),a("known after apply"),e("span",{class:"token punctuation"},")"),a(` + ~ default_public_route_table_no `),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"25833"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token punctuation"},"("),a("known after apply"),e("span",{class:"token punctuation"},")"),a(` + ~ `),e("span",{class:"token function"},"id"),a(" "),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"13888"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token punctuation"},"("),a("known after apply"),e("span",{class:"token punctuation"},")"),a(` + ~ name `),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"yourname-vpc-kr"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token string"},'"hashicat-vpc-kr"'),a(),e("span",{class:"token comment"},"# forces replacement"),a(` + ~ vpc_no `),e("span",{class:"token operator"},"="),a(),e("span",{class:"token string"},'"13888"'),a(" -"),e("span",{class:"token operator"},">"),a(),e("span",{class:"token punctuation"},"("),a("known after apply"),e("span",{class:"token punctuation"},")"),a(` + `),e("span",{class:"token comment"},"# (1 unchanged attribute hidden)"),a(` + `),e("span",{class:"token punctuation"},"}"),a(` + +Plan: `),e("span",{class:"token number"},"1"),a(" to add, "),e("span",{class:"token number"},"0"),a(" to change, "),e("span",{class:"token number"},"1"),a(` to destroy. + +Do you want to perform these actions? + Terraform will perform the actions described above. + Only `),e("span",{class:"token string"},"'yes'"),a(` will be accepted to approve. + + Enter a value: `),e("span",{class:"token function"},"yes"),a(` + +ncloud_vpc.hashicat: Destroying`),e("span",{class:"token punctuation"},".."),a(". "),e("span",{class:"token punctuation"},"["),a("id"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"13888"),e("span",{class:"token punctuation"},"]"),a(` +ncloud_vpc.hashicat: Still destroying`),e("span",{class:"token punctuation"},".."),a(". "),e("span",{class:"token punctuation"},"["),a("id"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"13888"),a(", 10s elapsed"),e("span",{class:"token punctuation"},"]"),a(` +ncloud_vpc.hashicat: Still destroying`),e("span",{class:"token punctuation"},".."),a(". "),e("span",{class:"token punctuation"},"["),a("id"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"13888"),a(", 20s elapsed"),e("span",{class:"token punctuation"},"]"),a(` +ncloud_vpc.hashicat: Destruction complete after 23s +ncloud_vpc.hashicat: Creating`),e("span",{class:"token punctuation"},".."),a(`. +ncloud_vpc.hashicat: Still creating`),e("span",{class:"token punctuation"},".."),a(". "),e("span",{class:"token punctuation"},"["),a("10s elapsed"),e("span",{class:"token punctuation"},"]"),a(` +ncloud_vpc.hashicat: Still creating`),e("span",{class:"token punctuation"},".."),a(". "),e("span",{class:"token punctuation"},"["),a("20s elapsed"),e("span",{class:"token punctuation"},"]"),a(` +ncloud_vpc.hashicat: Creation complete after 23s `),e("span",{class:"token punctuation"},"["),a("id"),e("span",{class:"token operator"},"="),e("span",{class:"token number"},"13902"),e("span",{class:"token punctuation"},"]"),a(` + +Apply complete`),e("span",{class:"token operator"},"!"),a(" Resources: "),e("span",{class:"token number"},"1"),a(" added, "),e("span",{class:"token number"},"0"),a(" changed, "),e("span",{class:"token number"},"1"),a(` destroyed. +`)])]),e("div",{class:"highlight-lines"},[e("div",{class:"highlight-line"}," "),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br")]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])])],-1),W=n('

    🛫 Create and Change ACL

    @slidestart blood

    Terraform은 인프라를 Create, Destroy, Update, re-Create 합니다.


    리소스 내용 변경 시


    Terraform은 항상 현재 인프라를 코드에 정의된 것과 일치시키려고 합니다.


    Terraform 코드는 한 번에 하나 또는 두 개의 리소스를 사용하여 점진적으로 빌드할 수 있습니다.

    @slideend

    💻 ncloud_network_acl을 추가합니다.

    main.tf 파일을 열고 리소스 블록의 주석처리를 제거하려고 합니다.
    리소스 유형은 ncloud_network_acl이고 이름은 public 입니다.

    변경 후 terraform apply를 실행하고 yes를 입력하여 추가된 리소스가 생성되는지 확인하세요.

    ncloud_network_acl리소스 내부의 vpc_no 파라메터를 확인합니다. 어떻게 가르키고 있나요?

    해당 리소스는 VPC의 설정을 상속 받습니다.

    Terraform은 수백개의 상호 연결되 리소스 간의 복잡한 종송석을 맵핑할 수 있습니다.

    💻 ncloud_network_acl을 설정을 변경합니다.

    ncloud_network_acl항목에 대해 description 의 내용을 수정해 보세요.

    ',21),F=e("div",{class:"language-hcl","data-ext":"hcl","data-title":"hcl"},[e("pre",{hcl:"",class:"language-hcl"},[e("code",null,[e("span",{class:"token keyword"},[a("resource "),e("span",{class:"token type variable"},'"ncloud_network_acl"')]),a(),e("span",{class:"token string"},'"public"'),a(),e("span",{class:"token punctuation"},"{"),a(` + `),e("span",{class:"token property"},"vpc_no"),a(" "),e("span",{class:"token punctuation"},"="),a(` ncloud_vpc.hashicat.id + `),e("span",{class:"token property"},"name"),a(" "),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token string"},[a('"'),e("span",{class:"token interpolation"},[e("span",{class:"token punctuation"},"$"),e("span",{class:"token punctuation"},"{"),e("span",{class:"token keyword"},"var"),e("span",{class:"token punctuation"},"."),e("span",{class:"token type variable"},"prefix"),e("span",{class:"token punctuation"},"}")]),a('-acl-public"')]),a(` + `),e("span",{class:"token property"},"description"),a(),e("span",{class:"token punctuation"},"="),a(),e("span",{class:"token string"},'"for Public"'),a(` +`),e("span",{class:"token punctuation"},"}"),a(` +`)])]),e("div",{class:"highlight-lines"},[e("br"),e("br"),e("br"),e("div",{class:"highlight-line"}," "),e("br")])],-1),I=n(`

    변경 후 terraform apply를 실행하고 yes를 입력하여 변경된 사항에 대해 리소스가 어떻게 되는지 확인하세요.

    올바르게 프로비저닝된 경우 ncloud_network_acl를 삭제 후 다시 생성하지 않습니다.

    Apply complete! Resources: 0 added, 1 changed, 0 destroyed.


    🏗️ Complete the Build

    @slidestart blood

    -auto-approve 플래그

    해당 플래그를 사용하여 "Do you want to perform these actions?" 에 한 질문을 오버라이드(Override) 할 수 있습니다.


    사용에 주의가 필요하니다.

    검토 단계인 Plan을 건너뛰고 바로 Create/Update/Destroy 합니다.

    @slideend

    💻 애플리케이션을 배포해보세요.

    terraform plan를 실행하여 구성할 리소스 항목을 확인합니다.

    terraform plan
    +

    이제 Apply를 실행하여 HashiCat 애플리케이션을 빌드합니다.

    terraform apply -auto-approve
    +

    애플리케이션이 배포를 완료하는데 5~10분이 소요될 수 있습니다. 실행이 끝날 때 애플리케이션 URL이 포함된 Terraform 출력을 보면 완료되었음을 알 수 있습니다.

    catapp_url 출력 에서 URL을 클릭하여 새 브라우저 탭에서 웹 애플리케이션을 엽니다.

    경고

    응용 프로그램이 로드되지 않으면 terraform apply 다시 실행 하십시오. 이렇게 하면 웹 서버를 다시 설치하고 실행 중이 아닌 경우 응용 프로그램을 시작하려고 합니다.

    💻 변경된 사항을 확인하기 위해 그래프를 다시 살펴봅니다.

    terraform graph를 수행합니다.

    terraform graph
    +
    `,24),Z={href:"https://dreampuf.github.io/GraphvizOnline/",target:"_blank",rel:"noopener noreferrer"},E=n('

    인프라에 대한 Terraform 그래프를 살펴보십시오. 종속성이 자동으로 매핑됩니다.

    Terraform은 이 그래프를 사용하여 최대 효율성을 위해 병렬로 구축할 수 있는 리소스를 결정합니다.


    😱 Quiz Time 3. Terraform Apply

    Q. Plan 파일을 지정하지 않고 terraform apply를 실행하면 어떻게 됩니까?


    이 장에서 우리는 :

    ',10),H=e("li",null,"Terraform 리소스에 대해 배웠습니다.",-1),M=e("li",null,"Terraform Plan, Graph, Apply, Destory",-1),Q=e("li",null,"종속성에 대해 배웠습니다.",-1),J=e("li",null,"실습에서 그래프를 확인해보았습니다.",-1),K={href:"http://main.tf",target:"_blank",rel:"noopener noreferrer"},Y={href:"http://variables.tf",target:"_blank",rel:"noopener noreferrer"},$=e("li",null,"Meow World 애플리케이션을 구축하였습니다.",-1);function j(X,ee){const t=i("ExternalLinkIcon"),c=i("Tabs");return h(),u("div",null,[v,e("ul",null,[e("li",null,[e("a",y,[a("Blast Radius"),r(t)]),a("는 설치형 툴입니다.")]),e("li",null,[a("Graphviz는 DOT 그래프 설명 언어를 표시해주는 툴입니다. "),e("ul",null,[e("li",null,[e("a",x,[a("https://dreampuf.github.io/GraphvizOnline/"),r(t)]),a(" 에 앞서 "),T,a("로 시작하는 내용을 복사하여 붙여넣고 어떤 그림이 나오는지 확인해 봅니다.")]),C])])]),w,e("ul",null,[e("li",null,[e("a",P,[a("https://hieven.github.io/terraform-visual/"),r(t)])])]),z,e("p",null,[e("a",A,[a("NCP Consul"),r(t)]),a(" 화면에 접속해 보세요. 구성한 자원이 생성된 것이 확인되나요?")]),r(c,{id:"134",data:[{id:"1. Products & Services"},{id:"2. VPC"}]},{title0:s(({value:o,isActive:l})=>[a("1. Products & Services")]),title1:s(({value:o,isActive:l})=>[a("2. VPC")]),tab0:s(({value:o,isActive:l})=>[D]),tab1:s(({value:o,isActive:l})=>[G]),_:1},8,["data"]),R,V,N,q,S,e("p",null,[a("멱등은 수학 및 컴퓨터 과학의 특정 연산의 속성으로, 초기 적용을 넘어 동일하다면 결과를 변경하지 않고 여러 번 적용할 수 있습니다."),L,a(" 참고 : "),e("a",O,[a("https://en.wikipedia.org/wiki/Idempotence"),r(t)])]),B,U,W,F,I,e("p",null,[e("a",Z,[a("https://dreampuf.github.io/GraphvizOnline/"),r(t)]),a("에 앞서 digraph로 시작하는 내용을 복사하여 붙여넣고 어떤 그림이 나오는지 확인해 봅니다.")]),E,e("ul",null,[H,M,Q,J,e("li",null,[e("a",K,[a("main.tf"),r(t)]),a(", "),e("a",Y,[a("variables.tf"),r(t)]),a(", outputs.tf를 살펴보았습니다.")]),$])])}const ne=d(_,[["render",j],["__file","03-z-lab_terraform_action.html.vue"]]),se=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html","title":"💻 Lab - Terraform in Action","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"💻 Lab - Terraform in Action"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"💻 Lab - Terraform in Action\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":3,"title":"편집기에서 열기","slug":"편집기에서-열기","link":"#편집기에서-열기","children":[]},{"level":2,"title":"📈 Terraform Graph","slug":"📈-terraform-graph","link":"#📈-terraform-graph","children":[{"level":3,"title":"Terraform Graph는 모든 인프라에 대한 시각적 표현을 제공할 수 있습니다.","slug":"terraform-graph는-모든-인프라에-대한-시각적-표현을-제공할-수-있습니다","link":"#terraform-graph는-모든-인프라에-대한-시각적-표현을-제공할-수-있습니다","children":[]},{"level":3,"title":"이는 변경의 영향을 받을 종속성 문제 또는 리소스를 찾는 데 유용합니다.","slug":"이는-변경의-영향을-받을-종속성-문제-또는-리소스를-찾는-데-유용합니다","link":"#이는-변경의-영향을-받을-종속성-문제-또는-리소스를-찾는-데-유용합니다","children":[]}]},{"level":2,"title":"👨‍💻 Terraform Plan & Terraform Apply","slug":"👨‍💻-terraform-plan-terraform-apply","link":"#👨‍💻-terraform-plan-terraform-apply","children":[{"level":3,"title":"기본적으로 terraform apply 명령은 Terraform Plan을 실행하여 원하는 변경 사항을 보여줍니다.","slug":"기본적으로-terraform-apply-명령은-terraform-plan을-실행하여-원하는-변경-사항을-보여줍니다","link":"#기본적으로-terraform-apply-명령은-terraform-plan을-실행하여-원하는-변경-사항을-보여줍니다","children":[]},{"level":3,"title":"이는 변경의 영향을 받을 종속성 문제 또는 리소스를 찾는 데 유용합니다.","slug":"이는-변경의-영향을-받을-종속성-문제-또는-리소스를-찾는-데-유용합니다-1","link":"#이는-변경의-영향을-받을-종속성-문제-또는-리소스를-찾는-데-유용합니다-1","children":[]}]},{"level":2,"title":"👩‍💻 Test and Repair","slug":"👩‍💻-test-and-repair","link":"#👩‍💻-test-and-repair","children":[{"level":3,"title":"Terraform은 멱등성(idempotent)을 갖습니다.","slug":"terraform은-멱등성-idempotent-을-갖습니다","link":"#terraform은-멱등성-idempotent-을-갖습니다","children":[]}]},{"level":2,"title":"🛫 Change Your Prefix","slug":"🛫-change-your-prefix","link":"#🛫-change-your-prefix","children":[{"level":3,"title":"Terraform은 인프라를 Create, Destroy, Update, re-Create 합니다.","slug":"terraform은-인프라를-create-destroy-update-re-create-합니다","link":"#terraform은-인프라를-create-destroy-update-re-create-합니다","children":[]},{"level":3,"title":"리소스 내용 변경 시","slug":"리소스-내용-변경-시","link":"#리소스-내용-변경-시","children":[]}]},{"level":2,"title":"🛫 Create and Change ACL","slug":"🛫-create-and-change-acl","link":"#🛫-create-and-change-acl","children":[{"level":3,"title":"Terraform은 인프라를 Create, Destroy, Update, re-Create 합니다.","slug":"terraform은-인프라를-create-destroy-update-re-create-합니다-1","link":"#terraform은-인프라를-create-destroy-update-re-create-합니다-1","children":[]},{"level":3,"title":"리소스 내용 변경 시","slug":"리소스-내용-변경-시-1","link":"#리소스-내용-변경-시-1","children":[]},{"level":3,"title":"Terraform은 항상 현재 인프라를 코드에 정의된 것과 일치시키려고 합니다.","slug":"terraform은-항상-현재-인프라를-코드에-정의된-것과-일치시키려고-합니다","link":"#terraform은-항상-현재-인프라를-코드에-정의된-것과-일치시키려고-합니다","children":[]},{"level":3,"title":"Terraform 코드는 한 번에 하나 또는 두 개의 리소스를 사용하여 점진적으로 빌드할 수 있습니다.","slug":"terraform-코드는-한-번에-하나-또는-두-개의-리소스를-사용하여-점진적으로-빌드할-수-있습니다","link":"#terraform-코드는-한-번에-하나-또는-두-개의-리소스를-사용하여-점진적으로-빌드할-수-있습니다","children":[]}]},{"level":2,"title":"🏗️ Complete the Build","slug":"🏗️-complete-the-build","link":"#🏗️-complete-the-build","children":[{"level":3,"title":"-auto-approve 플래그","slug":"auto-approve-플래그","link":"#auto-approve-플래그","children":[]},{"level":3,"title":"사용에 주의가 필요하니다.","slug":"사용에-주의가-필요하니다","link":"#사용에-주의가-필요하니다","children":[]}]},{"level":2,"title":"😱 Quiz Time 3. Terraform Apply","slug":"quiz-time-3-terraform-apply","link":"#quiz-time-3-terraform-apply","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1698218919000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":2.18,"words":653},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    편집기에서 열기

    \\n
    \\n\\n
    \\n

    📈 Terraform Graph

    \\n

    @slidestart blood

    \\n

    Terraform Graph는 모든 인프라에 대한 시각적 표현을 제공할 수 있습니다.

    "}');export{ne as comp,se as data}; diff --git a/assets/04-Template.html-D_srWqRi.js b/assets/04-Template.html-D_srWqRi.js new file mode 100644 index 0000000000..a59cdd24ed --- /dev/null +++ b/assets/04-Template.html-D_srWqRi.js @@ -0,0 +1,10 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as i,c as s,b as t,d as e,a as c,e as a}from"./app-Bzk8Nrll.js";const r={},p=a('

    Template

    docmoa에 문서 템플릿을 설명합니다.

    주의

    기본 템플릿 가이드를 잘 지켜주세요. 함께 만드는 문서 모음이므로, 기본적인 형식이 필요합니다.

    최소 Template

    ',4),m=t("div",{class:"language-markdown","data-ext":"md","data-title":"md"},[t("pre",{md:"",class:"language-markdown"},[t("code",null,[t("span",{class:"token front-matter-block"},[t("span",{class:"token punctuation"},"---"),e(` + +`),t("span",{class:"token punctuation"},"---")]),e(` + +`),t("span",{class:"token title important"},[t("span",{class:"token punctuation"},"#"),e(" h1 제목 = Title 입니다.")]),e(` +내용은 마크다운 형식으로 작성합니다. + +`),t("span",{class:"token title important"},[t("span",{class:"token punctuation"},"##"),e(" h2 제목")]),e(` + +`)])]),t("div",{class:"highlight-lines"},[t("div",{class:"highlight-line"}," "),t("div",{class:"highlight-line"}," "),t("br"),t("br"),t("br"),t("div",{class:"highlight-line"}," "),t("div",{class:"highlight-line"}," "),t("br"),t("br")])],-1),d=t("code",null,"---",-1),h={href:"https://vuepress.vuejs.org/guide/frontmatter.html",target:"_blank",rel:"noopener noreferrer"},u=a('',1),g=t("li",null,[t("code",null,"# 제목"),e(" : "),t("code",null,"Frontmatter"),e("에서 "),t("code",null,"title"),e("을 명시하지 않는 것이 문서 작성에 복잡함을 줄여줄 것으로 예상되어 둘 중에 "),t("code",null,"h1"),e(" 스타일의 제목을 넣는 것을 권장합니다.")],-1);function _(k,T){const n=l("ExternalLinkIcon");return i(),s("div",null,[p,m,t("ul",null,[t("li",null,[e("Frontmatter(서문) : Frontmatter는 "),d,e(" 로 구분되는 내용입니다. 자세한 내용은 "),t("a",h,[e("공식 가이드"),c(n)]),e("를 참고하세요. 여기서 주로 사용하는 내용은 다음과 같습니다. "),u]),g])])}const b=o(r,[["render",_],["__file","04-Template.html.vue"]]),x=JSON.parse('{"path":"/00-Howto/02-Guide/04-Template.html","title":"Template","lang":"ko-KR","frontmatter":{"description":"Template docmoa에 문서 템플릿을 설명합니다. 주의 기본 템플릿 가이드를 잘 지켜주세요. 함께 만드는 문서 모음이므로, 기본적인 형식이 필요합니다. 최소 Template Frontmatter(서문) : Frontmatter는 --- 로 구분되는 내용입니다. 자세한 내용은 공식 가이드를 참고하세요. 여기서 ...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/02-Guide/04-Template.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Template"}],["meta",{"property":"og:description","content":"Template docmoa에 문서 템플릿을 설명합니다. 주의 기본 템플릿 가이드를 잘 지켜주세요. 함께 만드는 문서 모음이므로, 기본적인 형식이 필요합니다. 최소 Template Frontmatter(서문) : Frontmatter는 --- 로 구분되는 내용입니다. 자세한 내용은 공식 가이드를 참고하세요. 여기서 ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2024-02-14T07:38:39.000Z"}],["meta",{"property":"article:modified_time","content":"2024-02-14T07:38:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Template\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-02-14T07:38:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"최소 Template","slug":"최소-template","link":"#최소-template","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1707896319000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":0.15,"words":44},"filePathRelative":"00-Howto/02-Guide/04-Template.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    docmoa에 문서 템플릿을 설명합니다.

    \\n
    \\n

    주의

    \\n

    기본 템플릿 가이드를 잘 지켜주세요. 함께 만드는 문서 모음이므로, 기본적인 형식이 필요합니다.

    \\n
    \\n

    최소 Template

    \\n
    ---\\n\\n---\\n\\n# h1 제목 = Title 입니다.\\n내용은 마크다운 형식으로 작성합니다.\\n\\n## h2 제목\\n\\n
    ","autoDesc":true}');export{b as comp,x as data}; diff --git a/assets/04-agent.html-DwW982Yy.js b/assets/04-agent.html-DwW982Yy.js new file mode 100644 index 0000000000..0ef04b065d --- /dev/null +++ b/assets/04-agent.html-DwW982Yy.js @@ -0,0 +1,99 @@ +import{_ as n,a as s,b as e,c as a}from"./1564449679656-jDgy1EGt.js";import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o,e as l}from"./app-Bzk8Nrll.js";const c={},p=l(`

    4. Agents and Distributing Builds

    빌드를 수행하기 위한 Worker로 다중 Jenkins를 컨트롤 할 수 있습니다. 이때 명령을 수행하는 Jenkins는 Master, 빌드를 수행하는 Jenkins는 Worker로 구분합니다. 여기서는 Worker의 연결을 원격 호스트의 Jenkins를 SSH를 통해 연결하는 방식과 컨테이너로 구성된 Jenkins를 연결하는 과정을 확인 합니다.

    Master-Slave 방식, 또는 Master-Agent 방식으로 표현합니다.

    ※ Slave 호스트에 Jenkins를 설치할 필요는 없습니다.

    4.1 Adding an SSH build agent to Jenkins

    Worker가 실행되는 Slave 호스트에 SSH key를 생성하고 Worker 호스트에 인증 키를 복사하는 과정은 다음과 같습니다.

    1. 키 생성 및 복사(jenkins 를 수행할 유저를 생성해야 합니다.)

      # User가 없는 경우 새로운 Jenkins slave 유저 추가
      +$ useradd jenkins
      +$ passwd jenkins
      +Changing password for user jenkins.
      +New password:
      +Retype new password:
      +
      +# Slave 호스트에서 ssh 키를 생성합니다.
      +$ ssh-keygen -t rsa
      +Generating public/private rsa key pair.
      +Enter file in which to save the key (/root/.ssh/id_rsa): <enter>
      +Created directory '/root/.ssh'.
      +Enter passphrase (empty for no passphrase): <enter>
      +Enter same passphrase again: <enter>
      +Your identification has been saved in /root/.ssh/id_rsa.
      +Your public key has been saved in /root/.ssh/id_rsa.pub.
      +The key fingerprint is: <enter>
      +SHA256:WFU7MRVViaU1mSmCA5K+5yHfx7X+aV3U6/QtMSUoxug root@jenkinsecho.gyulee.com
      +The key's randomart image is:
      ++---[RSA 2048]----+
      +|     .... o.+.=*O|
      +|     ..  + . *o=.|
      +|    .   .o. +o. .|
      +|     . o. + ... +|
      +|      o.S. .   +.|
      +|     o oE    .oo.|
      +|      = o . . +o=|
      +|       o . o ..o=|
      +|          . ..o+ |
      ++----[SHA256]-----+
      +
      +$ cd ~/.ssh
      +$ cat ./id_rsa.pub > ./authorized_keys
      +
    2. Jenkins 관리노드 관리를 선택합니다.

    3. 좌측 메뉴에서 신규 노드를 클릭합니다.

    4. 노드명에 고유한 이름을 입력하고 Permanent Agent 를 활성화 합니다.

    5. 새로운 노드에 대한 정보를 기입합니다.

    Label 지정한 Slave Worker에서 빌드가 수행되도록 기존 02-02.Jobs의 Pipeline 스크립트를 수정합니다. 기존 agent any를 다음과 같이 agent { label 'Metal' }로 변경합니다. 해당 pipeline은 label이 Metal로 지정된 Worker에서만 빌드를 수행합니다.

    pipeline {
    +    agent { label 'Metal' }
    +    parameters {
    +        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
    +    }
    +    stages {
    +        stage('Example') {
    +            steps {
    +                echo "\${params.Greeting} World!"
    +            }
    +        }
    +    }
    +}
    +
    1564378013035
    1564378013035

    4.2 Using Docker images for agents

    Master Jenkins 호스트에서 docker 서비스에 설정을 추가합니다. docker 설치가 되어있지 않은 경우 설치가 필요합니다.

    $ yum -y install docker
    +

    RHEL8 환경이 Master인 경우 위와 같은 방식으로 설치를 진행하면 변경된 패키지에 따라 podman-docker가 설치 됩니다. 아직 Jenkins에서는 2019년 7월 29일 기준 podman을 지원하지 않음으로 별도 yum repository를 추가하여 진행합니다. docker-ce 최신 버전에서는 containerd.io 의 필요 버전이 1.2.2-3 이상이나 RHEL8에서 지원하지 않음으로 별도로 버전을 지정하여 설치합니다.

    $ yum -y install yum-utils
    +$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    +$ sudo yum repolist -v
    +...
    +Repo-id      : docker-ce-stable
    +Repo-name    : Docker CE Stable - x86_64
    +Repo-revision: 1564098258
    +Repo-updated : Fri 26 Jul 2019 08:44:18 AM KST
    +Repo-pkgs    : 47
    +Repo-size    : 982 M
    +Repo-baseurl : https://download.docker.com/linux/centos/7/x86_64/stable
    +Repo-expire  : 172,800 second(s) (last: Thu 25 Jul 2019 07:33:33 AM KST)
    +Repo-filename: /etc/yum.repos.d/docker-ce.repo
    +...
    +
    +$ yum -y install docker-ce-3:18.09.1-3.el7
    +$ systemctl enable docker
    +$ systemctl start docker
    +

    Jenkins에 새로운 플러그인을 추가하고 설정합니다.

    Docker 실행을 위한 Item을 생성합니다. (e.g. 04-02.UsingDockerImagesForAgents)

    1. Pipeline 스크립트를 구성합니다.
    pipeline {
    +    agent {
    +        docker { image 'node:latest' }
    +    }
    +    stages {
    +        stage('Test'){
    +            steps {
    +                sh 'node --version'
    +            }
    +        }
    +    }
    +}
    +
    1. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

    2. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

      1564388703234
      1564388703234

    4.3 Configuring specific agents

    Freestyle project 형태의 Item을 생성합니다. (e.g. 04-03.ConfiguringSpecificAgents)

    Jenkins는 각 단계, 빌드, 그리고 빌드 후 작업일 지정할 수 있습니다. Freestyle project에서는 이같은 전체 빌드 단계를 구성하고 여러가지 플러그인을 사용할 수 있는 환경을 제공합니다.

    1564444282455
    1564444282455
    ',27),r=[p];function d(u,k){return t(),o("div",null,r)}const b=i(c,[["render",d],["__file","04-agent.html.vue"]]),h=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/04-agent.html","title":"4. Agents and Distributing Builds","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/04-agent.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"4. Agents and Distributing Builds"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"4. Agents and Distributing Builds\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"4.1 Adding an SSH build agent to Jenkins","slug":"_4-1-adding-an-ssh-build-agent-to-jenkins","link":"#_4-1-adding-an-ssh-build-agent-to-jenkins","children":[]},{"level":2,"title":"4.2 Using Docker images for agents","slug":"_4-2-using-docker-images-for-agents","link":"#_4-2-using-docker-images-for-agents","children":[]},{"level":2,"title":"4.3 Configuring specific agents","slug":"_4-3-configuring-specific-agents","link":"#_4-3-configuring-specific-agents","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":2.31,"words":693},"filePathRelative":"05-Software/Jenkins/pipeline101/04-agent.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    빌드를 수행하기 위한 Worker로 다중 Jenkins를 컨트롤 할 수 있습니다. 이때 명령을 수행하는 Jenkins는 Master, 빌드를 수행하는 Jenkins는 Worker로 구분합니다. 여기서는 Worker의 연결을 원격 호스트의 Jenkins를 SSH를 통해 연결하는 방식과 컨테이너로 구성된 Jenkins를 연결하는 과정을 확인 합니다.

    \\n

    Master-Slave 방식, 또는 Master-Agent 방식으로 표현합니다.

    \\n
    \\n

    \\n

    ※ Slave 호스트에 Jenkins를 설치할 필요는 없습니다.

    \\n
    "}');export{b as comp,h as data}; diff --git a/assets/04-configuration.html-DzqaMvOy.js b/assets/04-configuration.html-DzqaMvOy.js new file mode 100644 index 0000000000..54494f8a6b --- /dev/null +++ b/assets/04-configuration.html-DzqaMvOy.js @@ -0,0 +1,36 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as d,c as u,a as o,w as e,b as a,e as l,d as t}from"./app-Bzk8Nrll.js";const h={},m=a("h1",{id:"_4-tomcat-환경설정",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_4-tomcat-환경설정"},[a("span",null,"4. Tomcat 환경설정")])],-1),v=a("ul",null,[a("li",null,"리스너"),a("li",null,"자바옵션"),a("li",null,"클래스로더"),a("li",null,"setenv?"),a("li",null,"web.xml"),a("li",null,"로그")],-1),g=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/DFBJ7r1u0Jo",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),k=l(`

    4.1 리스너

    리스너는 Tcp로 활성화되는 HTTP 프로토콜을 상징하여 설명합니다. 톰캣은 기본적으로 HTTP, AJP, Shutdown 을 위한 port가 활성화 됩니다. 리스너는 우리 몸의 귀와 같은 역할을 합니다. 들려오는 요청을 받는 역할을 하지요. 톰캣 또한 요청을 받아들이기 위해 리스너를 활성화하여 요청을 받아 들입니다.

    이러한 리스너는 톰캣의 "Coyote" 컴포넌트가 담당하는데 톰캣의 Startup.sh(bat)을 수행하면 다음과 같은 메시지를 확인할 수 있습니다.

    7월 15, 2014 5:46:18 오후 org.apache.coyote.AbstractProtocol start 정보: Starting ProtocolHandler ["http-bio-8080"]
    +7월 15, 2014 5:46:18 오후 org.apache.coyote.AbstractProtocol start 정보: Starting ProtocolHandler ["ajp-bio-8009"]
    +7월 15, 2014 5:46:18 오후 org.apache.catalina.startup.Catalina start 정보: Server startup in 1002 ms
    +

    http에 8080포트가 할당되고 ajp에 8009포트가 할당됩니다. 이런 포트로 톰캣에 HTTP 혹은 AJP로 요청을 전달할 수 있습니다. 만약 톰캣이 기동된 서버의 IP가 "192.168.0.10"이고 사용되는 HTTP 포트가 8080 이라면

    http://192.168.0.10:8080

    이렇게 브라우저에서 호출이 가능합니다. 해당 IP가 DNS나 별도의 호스팅 서비스를 통해 www.tomcat-gm.com에 연결되어 있다면

    http://www.tomcat-gm.com:8080

    이렇게 호출이 가능합니다.

    일반적으로는 HTTP의 기본 포트로 80이 사용되고 HTTPS(SSL)의 기본 포트로 443이 사용됩니다. 이경우 별도의 포트 지정없이 url 요청이 가능합니다.

    http://www.tomcat-gm.com(:80)

    https://www.tomcat-gm.com(:443)

    이러한 리스너 설정은 톰캣의 설정에서 Connector로 정의됩니다. $CATALINA_HOME/conf 디렉토리의 server.xml을 열어보시면 다음과 같은 Connector 설정을 확인 할 수 있습니다.

    <Connector port="8080" protocol="HTTP/1.1"
    +    connectionTimeout="20000"
    +    minSpareThreads="10"
    +    maxSpareThreads="5"
    +    maxThreads="15"
    +    redirectPort="8443" />
    +

    프로토콜의 형태가 "HTTP/1.1"이고 포트는 "8080"으로 활성화 됩니다. 해당 플랫폼에 IP가 여러개 존재한다면 "address" 항목을 추가하여 별도의 IP를 지정 할 수도 있습니다. 이같은 설정은 AJP나 SSL 또한 마찬가지 입니다.

    4.2 Java Options

    Java의 장점이 무엇인가 물으면 그 대표적인 한가지는 OS 플랫폼에 종속적이지 않은 어플리케이션 개발이 가능하다 일 것입니다. 이런 개발 환경이 가능한 이유는 JVM(Java Virtual Machine)이 제공하는 환경 때문입니다. JVM이 동작하면 각 OS에 Java가 공통적으로 수행되기 위한 Runtime 환경을 만들고 이후 생성된 JVM 환경에서 어플리케이션이 수행되기 때문에 OS 플랫폼 마다 개발을 달리하지 않아도 됩니다. 하지만 각각의 플랫폼에서의 JVM은 그 동작의 목적은 같아도 어플리케이션에 따라 성능에 차이가 발생할 수 있습니다. 어떤 어플리케이션은 한번에 큰 메모리를 요구하는가 하면 때로는 계산을 주로 한다던가, IO 작업이 많다던가하여 CPU 자원을 많이 필요로 하는 식이죠. 따라서 JVM에서는 사용자가 조절할 수 있는 수많은 옵션을 제공합니다. 물론 아무것도 설정하지 않은 상태가 가장 일반적일 수는 있지만 성능이나 장애극복을 위해 Java Option이 추가되기도 합니다. 적용되는 Java Option의 예는 다음과 같습니다.

    옵션설명
    -Xms(ms)Heap 메모리의 최소값을 정의합니다.
    -Xmx(mx)Heap 메모리의 최대값을 정의합니다.
    -verbosegcJVM에서 수행하는 GC를 로그로 남깁니다.
    -XX:+AggressiveOpts소수점 컴파일을 최적화 합니다.
    -Djava.net.preferIPv4Stack=trueIPv4와 IPv6모두 사용할 수 있는 환경에서 IPv4를 우선하여 서비스 합니다.

    이 외에도 수많은 Java Option이 존재하기에 각 환경에 맞는 Java Option의 적용이 필요하겠습니다. 하지만 어플리케이션이 실제 수행되기 전에는 어떤 요구사항이 발생하는지는 알 수 없기 때문에 반드시 실 서비스를 하기 전 충분한 테스트가 필요합니다.

    톰캣에서 Java Options의 추가를 위해서는 setenv.sh(bat) 혹은 catalina.sh(bat)의 스크립트에 추가하는 방법과 Windows 서비스에 등록된 경우 관련 설정창에 추가하는 방법이 있습니다.

    TomcatServiceOptions
    TomcatServiceOptions

    (서비스로 등록된 톰캣에 Java Options 적용 예)

    4.3 ClassLoader

    Java 환경에서는 class를 호출하여 서비스를 수행합니다. 각 class는 단독으로, 혹은 여러개가 함께 각각의 Class에 정의된 역할을 수행합니다. 이런 Class를 사용하기 위해서는 ClassLoader가 Class를 읽어 Class를 나열하는 과정이 수행됩니다. 나열되는 Class들은 경로의 형태를 띄며 이를 ClassPath라고도 부릅니다.

    ...:class:class:class:class:...

    ClassLoader가 Class를 읽지 못한다면 JVM에서는 해당 Class에 들어있는 Method를 요청할 때 찾지 못하는 상황이 발생하며, 이경우 ClassNotFound 메시지를 발생시킵니다.
    또한 이렇게 정의되는 ClassPath에는 우선순위가 있습니다. 동일한 Class명의 동일한 Method이지만 다른 역할을 수행하는 Class가 로딩된다면 어떤것이 우선권을 갖을까요? ClassPath순서상 앞서있는 Class가 우선권을 갖습니다. 아래와 같은 ClassPath가 생성되었다면 classA가 우선권을 갖습니다.

    ...:classA:classB:classC:classE:...

    그렇다면 어플리케이션 개발자가 의도한 Class를 호출하기 위해서는 ClassLoader가 원하는 class를 앞서 설정하도록 해야합니다. 물론 겹치는 Class가 없다는 가정하에는 어떤 위치에 있던지 상관없이 읽히기만 하면 되겠지요.

    일반적으로 웹 어플리케이션을 위한 war형태의 애플리케이션 개발시 Class는 WEB-INF/classes jar형태의 라이브러리는 WEB-INF/lib에 위치시킵니다. 이렇게 위치된 Class들은 톰캣 혹은 WAS에 배치(deploy)되면 전체 JVM의 가장 뒤에 위치하게 됩니다. 간혹 웹 어플리케이션 형태가 아닌 Class나 라이브러리를 적용하기위해서는 CLASSPATH라는 변수를 사용하여 ClassLoader가 읽을 수 있도록 합니다. 해당 변수는 WAS마다 상이할 수 있습니다.

    실행되는 JVM에서의 ClassLoader 순서를 보면 다음과 같습니다.

    BootClassPath:ExtensionClassPath:ClassPath

    BootClassPath와 ExtensionClassPath는 Java의 기본 라이브러리를 로딩합니다. rt.jar와 같은 필수 라이브러리가 그 예입니다. 만약 기존 JVM을 hooking하는 식의 클래스를 사용하는 경우에는 이보다 앞서 클래스를 위치시킬 필요가 있습니다. HelloWorld라는 클래서를 BootClassPath앞에 위치하게 하려면 -Xbootclasspath/p:HelloWorld, 뒤에 적용하려면 -Xbotclasspath/a:HelloWorld 형태를 사용하여 적용합니다. p와 a에 주의합니다. 그리고 일반적인 Class가 위치하는 ClassPath에 위치하게 하기 위해서는 톰켓의 경우 스크립트에 'CLASSPATH'변수를 치환합니다. 그 예는 다음과 같습니다.

    export CLASSPATH=HelloWorld
    +export CLASSPATH=\${CLASSPATH}:HelloWorld
    +export CLASSPATH=HelloWorld:\${CLASSPATH}
    +

    기 적용된 CLASSPATH가 있는가에 따라 적용하고자하는 Class 혹은 라이브러리 앞, 뒤에 기존 CLASSPATH를 넣어줄 수도 있습니다.

    경고

    Windows의 경우 구분자가 세미콜론(;)이고 그 외에는 콜론(:)임에 주의합니다.

    4.4 setenv

    Windows 환경의 서비스 실행방법을 제외하고는 대부분 스크립트에 앞서 설명한 Java Option이나 ClassPath를 설정합니다. 일반적으로, 그리고 여러 운영환경에서 이러한 실행 환경 변수를 catalina.sh(bat)에 설정하여 사용하는 경우를 보았습니다. 하지만 한번이라도 해당 스크립트를 열어 읽어보셨다면 다음과 같은 메시지를 확인 할 수 있을 것입니다.

    # -----------------------------------------------------------------------------
    +# Control Script for the CATALINA Server
    +#
    +# Environment Variable Prerequisites
    +#
    +#   Do not set the variables in this script. Instead put them into a script
    +#   setenv.sh in CATALINA_BASE/bin to keep your customizations separate.
    +

    즉, catalina.sh는 건드리지 말고 setenv.sh(bat)에 추가적은 설정을 하라는 안내 문구 입니다. catalina.sh를 수정하는 경우 해당 톰캣을 이전하거나, 동일한 톰캣을 구성하거나, 또는 복구해야 하는 상황에서 추가로 설정되거나 수정된 내용의 확인이 힘들 수 있고, 또한 설정과 수정으로 인한 비정상 동작을 할 수 있기 때문입니다. 그렇다면 setenv.sh(bat)은 어떻게 작용할까요? catalina.sh(bat)에서 setenv를 찾아보면 다음과 같이 setenv스크립트에 적용된내용을 읽어오는 것을 확인 할 수 있습니다.

    `,40),b={href:"http://catalina.sh",target:"_blank",rel:"noopener noreferrer"},A=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token keyword"},"if"),t(),a("span",{class:"token punctuation"},"["),t(),a("span",{class:"token parameter variable"},"-r"),t(),a("span",{class:"token string"},[t('"'),a("span",{class:"token variable"},"$CATALINA_BASE"),t('/bin/setenv.sh"')]),t(),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},";"),t(),a("span",{class:"token keyword"},"then"),t(` + `),a("span",{class:"token builtin class-name"},"."),t(),a("span",{class:"token string"},[t('"'),a("span",{class:"token variable"},"$CATALINA_BASE"),t('/bin/setenv.sh"')]),t(` +`),a("span",{class:"token keyword"},"elif"),t(),a("span",{class:"token punctuation"},"["),t(),a("span",{class:"token parameter variable"},"-r"),t(),a("span",{class:"token string"},[t('"'),a("span",{class:"token variable"},"$CATALINA_HOME"),t('/bin/setenv.sh"')]),t(),a("span",{class:"token punctuation"},"]"),a("span",{class:"token punctuation"},";"),t(),a("span",{class:"token keyword"},"then"),t(` + `),a("span",{class:"token builtin class-name"},"."),t(),a("span",{class:"token string"},[t('"'),a("span",{class:"token variable"},"$CATALINA_HOME"),t('/bin/setenv.sh"')]),t(` +`),a("span",{class:"token keyword"},"fi"),t(` +`)])])],-1),_=a("div",{class:"language-powershell","data-ext":"powershell","data-title":"powershell"},[a("pre",{class:"language-powershell"},[a("code",null,[t(`rem Get standard environment variables +`),a("span",{class:"token keyword"},"if"),t(" not exist "),a("span",{class:"token string"},'"%CATALINA_BASE%\\bin\\setenv.bat"'),t(` goto checkSetenvHome +call `),a("span",{class:"token string"},'"%CATALINA_BASE%\\bin\\setenv.bat"'),t(` +goto setenvDone +:checkSetenvHome +`),a("span",{class:"token keyword"},"if"),t(" exist "),a("span",{class:"token string"},'"%CATALINA_HOME%\\bin\\setenv.bat"'),t(" call "),a("span",{class:"token string"},'"%CATALINA_HOME%\\bin\\setenv.bat"'),t(` +:setenvDone +`)])])],-1),S=l("

    이같이 톰캣에서는 추가/수정해야하는 환경변수나 설정값을 하나의 스크립트에서 관리할 수 있는 환경을 제공합니다. 다만 setenv.sh(bat)스크립트는 별도로 만들어야 합니다. 앞서 catalina.sh(bat)의 설명된 변수들을 보면 Java Options은 JAVA_OPTS로하지 말고 CATALINA_OPTS로 하라는 점도 주의해서 보시기 바랍니다. JAVA_OPTS의 경우 톰캣을 정지시키는 shutdown.sh(bat)에서도 호출되기 때문에 차후 소개되는 JMX 모니터링을 위한 옵션과 같이 별도의 포트를 활성화하는 옵션과 같은 성격의 설정 적용시 문제가 될 수 있습니다. setenv.sh(bat)스크립트에 다음과 같이 추가하면 해당 옵션을 별도로 export하지 않아도 톰캣 기동시 적용됩니다.

    ",1),C={href:"http://setenv.sh",target:"_blank",rel:"noopener noreferrer"},T=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token assign-left variable"},"CATALINA_OPTS"),a("span",{class:"token operator"},"="),a("span",{class:"token string"},'"-Xms1024m -Xmx2048m -XX:MaxPermSize=512m -verbosegc"'),t(` +`),a("span",{class:"token assign-left variable"},"CLASSPATH"),a("span",{class:"token operator"},"="),a("span",{class:"token string"},[t('"'),a("span",{class:"token variable"},"${CLASSPATH}"),t(':/app/libs/myapi.jar"')]),t(` +`)])])],-1),w=a("div",{class:"language-powershell","data-ext":"powershell","data-title":"powershell"},[a("pre",{class:"language-powershell"},[a("code",null,[a("span",{class:"token function"},"set"),t(" CATALINA_OPTS="),a("span",{class:"token operator"},"-"),t("Xms1024m "),a("span",{class:"token operator"},"-"),t("Xmx2048m "),a("span",{class:"token operator"},"-"),t("XX:MaxPermSize=512m "),a("span",{class:"token operator"},"-"),t(`verbosegc +`),a("span",{class:"token function"},"set"),t(" CLASSPATH="),a("span",{class:"token operator"},"%"),t("CLASSPATH%"),a("span",{class:"token punctuation"},";"),a("span",{class:"token operator"},"/"),t("app/libs/myapi"),a("span",{class:"token punctuation"},"."),t(`jar +`)])])],-1),x=l('

    4.5 web.xml

    웹 어플리케이션에서 web.xml은 서블릿을 정의하고 이어주는 역할을 수행합니다. 이와 마찬가지로 톰캣에 있는 $CATALINA_HOME/conf/web.xml 또한 톰캣에 있는 서블릿을 정의하고 이어주는 역할을 수행합니다. 다만 JSP/Servlet 엔진으로서의 최소한의 정의를 합니다.

    따라서 톰캣에 배치되는 모든 어플리케이션에서 공통으로 수정되어야 할 사항은 web.xml에도 정의할 수 있습니다. 하지만 앞서 catalina 스크립트와 마찬가지로 추가/수정시 부작용이 있을 수 있음에 중의합니다.

    4.6 Log

    톰캣의 로그는 다음과 같이 종류와 정의는 다음에서 정의합니다. 다만 Windows 서비스는 서비스 환경설정에서 정의힙니다.

    LogConfig File
    Catalina.outCATALINA_OUT 환경변수로 정의, catalina.sh에 정의되어 있고 setenv 스크립트에서 재정의
    access.logserver.xml
    *.loglogging.properties
    ',7);function P(f,L){const c=i("ExternalLinkIcon"),p=i("CodeTabs");return d(),u("div",null,[m,v,g,k,o(p,{id:"199",data:[{id:'catalina.sh (Unix/Linux/OSX)'},{id:"catalina.bat (Windows)"}]},{title0:e(({value:s,isActive:n})=>[a("a",b,[t("catalina.sh"),o(c)]),t(" (Unix/Linux/OSX)")]),title1:e(({value:s,isActive:n})=>[t("catalina.bat (Windows)")]),tab0:e(({value:s,isActive:n})=>[A]),tab1:e(({value:s,isActive:n})=>[_]),_:1},8,["data"]),S,o(p,{id:"210",data:[{id:'setenv.sh (Unix/Linux/OSX)'},{id:"setenv.bat (Windows)"}]},{title0:e(({value:s,isActive:n})=>[a("a",C,[t("setenv.sh"),o(c)]),t(" (Unix/Linux/OSX)")]),title1:e(({value:s,isActive:n})=>[t("setenv.bat (Windows)")]),tab0:e(({value:s,isActive:n})=>[T]),tab1:e(({value:s,isActive:n})=>[w]),_:1},8,["data"]),x])}const J=r(h,[["render",P],["__file","04-configuration.html.vue"]]),O=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/04-configuration.html","title":"4. Tomcat 환경설정","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/04-configuration.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"4. Tomcat 환경설정"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/tomcatService-20200628205826895.jpg?token=ADUAZXKZXYIDH75QYMNK75267EUOU"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"4. Tomcat 환경설정"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"4. Tomcat 환경설정\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/tomcatService-20200628205826895.jpg?token=ADUAZXKZXYIDH75QYMNK75267EUOU\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"4.1 리스너","slug":"_4-1-리스너","link":"#_4-1-리스너","children":[]},{"level":2,"title":"4.2 Java Options","slug":"_4-2-java-options","link":"#_4-2-java-options","children":[{"level":3,"title":"4.3 ClassLoader","slug":"_4-3-classloader","link":"#_4-3-classloader","children":[]},{"level":3,"title":"4.4 setenv","slug":"_4-4-setenv","link":"#_4-4-setenv","children":[]},{"level":3,"title":"4.5 web.xml","slug":"_4-5-web-xml","link":"#_4-5-web-xml","children":[]},{"level":3,"title":"4.6 Log","slug":"_4-6-log","link":"#_4-6-log","children":[]}]}],"git":{"createdTime":1640327880000,"updatedTime":1698218919000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":1.84,"words":552},"filePathRelative":"05-Software/Tomcat/tomcat101/04-configuration.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n"}');export{J as comp,O as data}; diff --git a/assets/04-ncp-provisioning-and-configuration.html-C77V6Qkn.js b/assets/04-ncp-provisioning-and-configuration.html-C77V6Qkn.js new file mode 100644 index 0000000000..d3b2e256c6 --- /dev/null +++ b/assets/04-ncp-provisioning-and-configuration.html-C77V6Qkn.js @@ -0,0 +1,19 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as p,c as i,b as n,d as e,a,w as l,e as c}from"./app-Bzk8Nrll.js";const u="/assets/cpa-DKh9yEJ1.jpg",d={},m=n("h1",{id:"_04-테라폼-프로비저닝-도구-사용-및-구성",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_04-테라폼-프로비저닝-도구-사용-및-구성"},[n("span",null,"04. 테라폼 프로비저닝 도구 사용 및 구성")])],-1),h=n("h2",{id:"terraform-프로비저닝-도구-사용",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#terraform-프로비저닝-도구-사용"},[n("span",null,"Terraform 프로비저닝 도구 사용")])],-1),k=n("p",null,"Terraform을 사용하여 가상 머신 또는 컨테이너를 세우고 나면 운영 체제와 애플리케이션을 구성 할 수 있습니다.",-1),f=n("p",null,[e("여기에서 "),n("mark",null,"Provisioner"),e(" 가 등장합니다.")],-1),v=n("p",null,"Terraform은 Bash, Powershell, Chef, Puppet, Ansible 등을 포함한 여러 유형의 Provisioner를 지원합니다.",-1),_={href:"https://www.terraform.io/docs/provisioners/index.html",target:"_blank",rel:"noopener noreferrer"},g=c(`

    File Provisioner

    Terraform 파일 프로비저닝 도구는 원격 시스템에 파일을 복사합니다.

    provisioner "file" {
    +  source        = "files/"
    +  destination   = "/home/${var.admin_username}/"
    +  connection {
    +    type        = "ssh"
    +    user        = var.username
    +    private_key = file(var.ssh_key)
    +    host        = ${self.ip}
    +  }
    +}
    +

    provisioner 블록 안에있는 코드의 connection 블록에 주목하세요. 파일 프로비저닝 도구는 SSH, WinRM 연결을 모두 지원합니다.

    Remote Exec Provisioner

    Remote Exec Provisioner를 사용하면 대상 호스트에서 스크립트 또는 기타 프로그램을 실행할 수 있습니다.

    자동으로 실행할 수있는 경우 (예 : 소프트웨어 설치 프로그램) remote-exec로 실행할 수 있습니다.

    provisioner "remote-exec" {
    +  inline = [
    +    "sudo chown -R ${var.admin_username}:${var.admin_username} /var/www/html",
    +    "chmod +x *.sh",
    +    "PLACEHOLDER=${var.placeholder} WIDTH=${var.width} HEIGHT=${var.height} PREFIX=${var.prefix} ./deploy_app.sh",
    +  ]
    +...
    +}
    +

    이 예에서는 일부 권한 및 소유권을 변경하고 일부 환경 변수가있는 스크립트를 실행하기 위해 몇 가지 명령을 실행합니다.

    Terraform & Config Management Tools

    Terraform은 Chef, Puppet, Ansible과 같은 일반적인 구성 관리 도구와 잘 작동합니다.

    ',12),b=n("br",null,null,-1),w={href:"https://www.terraform.io/docs/provisioners/chef.html",target:"_blank",rel:"noopener noreferrer"},y=n("br",null,null,-1),x={href:"https://www.terraform.io/docs/provisioners/local-exec.html",target:"_blank",rel:"noopener noreferrer"},T=n("br",null,null,-1),P={href:"https://github.com/scarolan/ansible-terraform",target:"_blank",rel:"noopener noreferrer"},C=n("h2",{id:"terraform-provisioner에-대한-도움말",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#terraform-provisioner에-대한-도움말"},[n("span",null,"Terraform Provisioner에 대한 도움말")])],-1),q=n("p",null,[n("code",null,"remote-exec"),e("와 같은 Terraform 프로비저닝 도구는 몇 가지 간단한 명령이나 스크립트를 실행해야 할 때 유용합니다. 더 복잡한 구성 관리의 경우 Chef 또는 Ansible과 같은 도구가 필요합니다.")],-1),R=n("p",null,[e("Provisioner는 Terraform 실행이 "),n("mark",null,"처음 실행될 때"),e(" 만 실행됩니다. 이러한 의미에서 그 동작들은 멱등성을 띄지 않습니다.")],-1),N=n("p",null,"수명이 긴 VM 또는 서버의 지속적인 상태 관리가 필요한 경우 이같은 구성 관리 도구를 활용할 수 있습니다.",-1),E={href:"https://www.packer.io/",target:"_blank",rel:"noopener noreferrer"},A=n("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/Dqwk7fYHhVQ",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),$=n("hr",null,null,-1),L=n("p",null,"실습을 위해 다음장으로 이동하세요.",-1);function V(B,H){const o=s("ExternalLinkIcon"),t=s("RouteLink");return p(),i("div",null,[m,h,k,f,v,n("p",null,[n("a",_,[e("https://www.terraform.io/docs/provisioners/index.html"),a(o)])]),g,n("ul",null,[n("li",null,[n("p",null,[e("Official Chef Terraform provisioner:"),b,n("a",w,[e("https://www.terraform.io/docs/provisioners/chef.html"),a(o)])])]),n("li",null,[n("p",null,[e("Run Puppet with 'local-exec':"),y,n("a",x,[e("https://www.terraform.io/docs/provisioners/local-exec.html"),a(o)])])]),n("li",null,[n("p",null,[e("Terraform and Ansible - Better Together:"),T,n("a",P,[e("https://github.com/scarolan/ansible-terraform"),a(o)])])])]),C,q,R,N,n("p",null,[e("반면에 변경 불가능한 인프라를 원하면 "),n("a",E,[e("Packer"),a(o)]),e(" 같은 이뮤터블을 위한 빌드 도구를 사용하는 것이 좋습니다.")]),A,$,L,n("p",null,[a(t,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioning_and_configuration.html"},{default:l(()=>[e("💻 Lab - Provisioners, Variables, Outputs")]),_:1})])])}const I=r(d,[["render",V],["__file","04-ncp-provisioning-and-configuration.html.vue"]]),M=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html","title":"04. 테라폼 프로비저닝 도구 사용 및 구성","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"04. 테라폼 프로비저닝 도구 사용 및 구성"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"04. 테라폼 프로비저닝 도구 사용 및 구성\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Terraform 프로비저닝 도구 사용","slug":"terraform-프로비저닝-도구-사용","link":"#terraform-프로비저닝-도구-사용","children":[]},{"level":2,"title":"File Provisioner","slug":"file-provisioner","link":"#file-provisioner","children":[]},{"level":2,"title":"Remote Exec Provisioner","slug":"remote-exec-provisioner","link":"#remote-exec-provisioner","children":[]},{"level":2,"title":"Terraform & Config Management Tools","slug":"terraform-config-management-tools","link":"#terraform-config-management-tools","children":[]},{"level":2,"title":"Terraform Provisioner에 대한 도움말","slug":"terraform-provisioner에-대한-도움말","link":"#terraform-provisioner에-대한-도움말","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.59,"words":177},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    Terraform 프로비저닝 도구 사용

    \\n

    Terraform을 사용하여 가상 머신 또는 컨테이너를 세우고 나면 운영 체제와 애플리케이션을 구성 할 수 있습니다.

    \\n

    여기에서 Provisioner 가 등장합니다.

    \\n

    Terraform은 Bash, Powershell, Chef, Puppet, Ansible 등을 포함한 여러 유형의 Provisioner를 지원합니다.

    \\n

    https://www.terraform.io/docs/provisioners/index.html

    "}');export{I as comp,M as data}; diff --git a/assets/04-traffic-management.html-D79x_2QV.js b/assets/04-traffic-management.html-D79x_2QV.js new file mode 100644 index 0000000000..e3753c2a28 --- /dev/null +++ b/assets/04-traffic-management.html-D79x_2QV.js @@ -0,0 +1,270 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as d,c as r,b as e,d as n,a,e as i}from"./app-Bzk8Nrll.js";const c="/assets/03-traffic-v1-BGAizMHc.png",v="/assets/03-traffic-v2-CgPD7up9.png",o="/assets/03-traffic-methods-BH17zSFz.png",u="/assets/03-traffic-metadata-C8cC4nAb.png",p="/assets/03-traffic-routeui-Dm1u6sgH.png",m={},b=i(`

    04. 트래픽 관리

    실습을 진행하기 위한 디렉토리를 생성합니다.

    mkdir ./traffic
    +

    Service Mesh는 HTTP 프로토콜 상에서 L7으로 동작하게 됩니다. 따라서 기본 프로토콜을 http로 변경합니다.

    cat > ./traffic/service-to-service.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ProxyDefaults
    +metadata:
    +  name: global
    +spec:
    +  config:
    +    protocol: http
    +EOF
    +
    kubectl apply -f ./traffic/service-to-service.yaml
    +
    # 출력
    +proxydefaults.consul.hashicorp.com/global created
    +

    샘플 앱 준비

    프론트엔드 서비스

    cat > ./traffic/gs-frontend.yaml <<EOF
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: gs-frontend
    +spec:
    +  selector:
    +    app: gs-frontend
    +  ports:
    +    - protocol: TCP
    +      port: 3000
    +      targetPort: 3000
    +---
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: gs-frontend
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: gs-frontend
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: gs-frontend
    +  template:
    +    metadata:
    +      labels:
    +        app: gs-frontend
    +      annotations:
    +        prometheus.io/scrape: "true"
    +        prometheus.io/port: "9901"
    +        consul.hashicorp.com/connect-inject: "true"
    +        consul.hashicorp.com/transparent-proxy: true
    +        consul.hashicorp.com/connect-service-upstreams: "gs-backend:8080"
    +    spec:
    +      serviceAccountName: gs-frontend
    +      containers:
    +        - name: gs-frontend
    +          image: hahohh/consul-frontend-nodejs:v1.5
    +          env:
    +            - name: PORT
    +              value: "3000"
    +            - name: UPSTREAM_URL
    +              value: "http://localhost:8080
    +          ports:
    +            - containerPort: 3000
    +EOF
    +

    적용하기

    kubectl apply -f ./traffic/gs-frontend.yaml
    +

    백엔드 서비스

    cat > ./traffic/gs-backend.yaml <<EOF
    +---
    +apiVersion: v1
    +kind: Service
    +metadata:
    +  name: gs-backend
    +spec:
    +  selector:
    +    app: gs-backend
    +  ports:
    +    - protocol: TCP
    +      port: 8080
    +      targetPort: 8080
    +---
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: gs-backend
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: gs-backend-v1
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: gs-backend
    +      version: v1
    +  template:
    +    metadata:
    +      labels:
    +        app: gs-backend
    +        version: v1
    +      annotations:
    +        consul.hashicorp.com/connect-inject: "true"
    +        consul.hashicorp.com/service-meta-version: v1
    +        consul.hashicorp.com/service-tags: v1
    +    spec:
    +      serviceAccountName: gs-backend
    +      containers:
    +        - name: gs-backend
    +          image: hahohh/consul-backend-go:v1.2
    +          env:
    +            - name: PORT
    +              value: "8080"
    +            - name: COLOR
    +              value: "green"
    +            - name: VERSION
    +              value: "v1"
    +          ports:
    +            - containerPort: 8080
    +---
    +apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: gs-backend-v2
    +spec:
    +  replicas: 1
    +  selector:
    +    matchLabels:
    +      app: gs-backend
    +      version: v2
    +  template:
    +    metadata:
    +      labels:
    +        app: gs-backend
    +        version: v2
    +      annotations:
    +        consul.hashicorp.com/connect-inject: "true"
    +        consul.hashicorp.com/service-meta-version: v2
    +        consul.hashicorp.com/service-tags: v2
    +    spec:
    +      serviceAccountName: gs-backend
    +      containers:
    +        - name: gs-backend
    +          image: hahohh/consul-backend-go:v1.2
    +          env:
    +            - name: PORT
    +              value: "8080"
    +            - name: COLOR
    +              value: "blue"
    +            - name: VERSION
    +              value: "v2"
    +            # - name: ISFAIL
    +            #   value: "yyyy"
    +          ports:
    +            - containerPort: 8080
    +EOF
    +

    적용하기

    kubectl apply -f ./traffic/gs-backend.yaml
    +

    서비스 Intention

    cat > ./traffic/service-to-service.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: gs-backend
    +spec:
    +  destination:
    +    name: gs-backend
    +  sources:
    +    - name: gs-frontend
    +      action: allow
    +EOF
    +

    적용하기

    kubectl apply -f ./traffic/service-to-service.yaml
    +

    port-forward를 통해 로컬에서 web 앱을 확인합니다.

    kubect l port-forward service/gs-frontend 3000:3000 --address 0.0.0.0
    +
    # 출력
    +Forwarding from 0.0.0.0:3000 -> 3000
    +
    `,23),h={href:"http://localhost:3000",target:"_blank",rel:"noopener noreferrer"},g=i('

    두개의 버전의 백엔드가 서로다른 갑을 리턴하여 때에 따라 v1, v2가 번갈아 나타납니다.

    v1
    v1
    v2
    v2

    트래픽 제어 요소

    traffic-method
    traffic-method

    Resolve

    cat > ./traffic/service-resolver.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceResolver
    +metadata:
    +  name: gs-backend
    +spec:
    +  defaultSubset: v1
    +  subsets:
    +    v1:
    +      filter: "Service.Meta.version == v1"
    +    v2:
    +      filter: "Service.Meta.version == v2"
    +EOF
    +

    적용하기

    kubectl apply -f ./traffic/service-resolver.yaml
    +

    앞서 배포된 gs-backendDeployment 에 선언된 annotation 내용을 확인하면 consul.hashicorp.com/service-meta-version: v2 을 확인할 수 있습니다. Consul UI에서도 해당 Meta 정보를 확인할 수 있습니다. 선언된 정보를 기반으로 서비스의 subset 을 정의합니다.

    service-meta
    service-meta

    Splitter

    cat > ./traffic/service-splitter.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceSplitter
    +metadata:
    +  name: gs-backend
    +spec:
    +  splits:
    +    - weight: 50
    +      serviceSubset: v1
    +    - weight: 50
    +      serviceSubset: v2
    +EOF
    +

    적용하기

    kubectl apply -f ./traffic/service-splitter.yaml
    +

    weight에 지정된 비율로 Resolve된 서비스 대상 subset 에 트래픽을 분산합니다. weight값을 0:100, 100:0 등으로 변경하여 요청의 결과가 어떻게 변화하는지 확인해 봅니다.

    `,17),f={href:"http://localhost:3000",target:"_blank",rel:"noopener noreferrer"},k=i(`

    Router

    cat > ./traffic/service-router.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceRouter
    +metadata:
    +  name: gs-backend
    +spec:
    +  routes:
    +    - match:
    +        http:
    +          pathPrefix: '/v1'
    +      destination:
    +        service: gs-backend
    +        serviceSubset: v1
    +    - match:
    +        http:
    +          pathPrefix: '/v2'
    +      destination:
    +        service: gs-backend
    +        serviceSubset: v2
    +    - match:
    +        http:
    +          queryParam:
    +            - name: version
    +              exact: 'v1'
    +      destination:
    +        service: gs-backend
    +        serviceSubset: v1
    +    - match:
    +        http:
    +          queryParam:
    +            - name: version
    +              exact: 'v2'
    +      destination:
    +        service: gs-backend
    +        serviceSubset: v2
    +EOF
    +

    적용하기

    kubectl apply -f ./traffic/service-router.yaml
    +

    예제에서는 url의 path, queryParam을 예로 트래픽을 컨트롤 합니다. 다음과같이 요청하여 트래픽이 조정되는 것을 확인합니다.

    `,5),y={href:"http://localhost:3000/v1",target:"_blank",rel:"noopener noreferrer"},x={href:"http://localhost:3000/v2",target:"_blank",rel:"noopener noreferrer"},_={href:"http://localhost:3000/?version=v1",target:"_blank",rel:"noopener noreferrer"},S={href:"http://localhost:3000/?version=v2",target:"_blank",rel:"noopener noreferrer"},E=i(`

    서비스 Intention (L7)

    service-to-service 허용 방식에도 Meshod, Path 등을 지정할 수 있습니다. 다음과 같이 변경하고 POST만 넣은 상태에서는 어떻게 동작하는지 확인합니다.

    cat > ./traffic/service-to-service.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: gs-backend
    +spec:
    +  destination:
    +    name: gs-backend
    +  sources:
    +    - name: gs-frontend
    +      permissions:
    +        - action: allow
    +          http:
    +            pathPrefix: /
    +            # methods: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']
    +            methods: ['POST']
    +

    적용하기

    kubectl apply -f ./traffic/service-to-service.yaml
    +

    다시 앱간의 요청인 GET으로 변경하고 트래픽 허용여부를 확인해봅니다.

    cat > ./traffic/service-to-service.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ServiceIntentions
    +metadata:
    +  name: gs-backend
    +spec:
    +  destination:
    +    name: gs-backend
    +  sources:
    +    - name: gs-frontend
    +      permissions:
    +        - action: allow
    +          http:
    +            pathPrefix: /
    +            # methods: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD']
    +            methods: ['GET']
    +

    적용하기

    kubectl apply -f ./traffic/service-to-service.yaml
    +

    Consul UI Routing table

    Consul UI에 접속하여 gs-backendRouting 탭을 클릭, 구성된 Resolver, Splitter, Router가 어떻게 구성되었는지, 각 서비스에는 어떤 조건으로 요청할 수 있는지 확인합니다.

    ui
    ui
    ',12);function P(q,O){const s=t("ExternalLinkIcon");return d(),r("div",null,[b,e("p",null,[e("a",h,[n("http://localhost:3000"),a(s)]),n(" 에 브라우저로 접속하여 상태를 확인합니다.")]),g,e("ul",null,[e("li",null,[e("a",f,[n("http://localhost:3000"),a(s)])])]),k,e("ul",null,[e("li",null,[e("a",y,[n("http://localhost:3000/v1"),a(s)])]),e("li",null,[e("a",x,[n("http://localhost:3000/v2"),a(s)])]),e("li",null,[e("a",_,[n("http://localhost:3000/?version=v1"),a(s)])]),e("li",null,[e("a",S,[n("http://localhost:3000/?version=v2"),a(s)])])]),E])}const C=l(m,[["render",P],["__file","04-traffic-management.html.vue"]]),F=JSON.parse('{"path":"/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.html","title":"04. 트래픽 관리","lang":"ko-KR","frontmatter":{"description":"Consul Service Mesh on Kubernetes (Ent)","tag":["Consul","ServiceMesh","K8s","Kubernetes"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"04. 트래픽 관리"}],["meta",{"property":"og:description","content":"Consul Service Mesh on Kubernetes (Ent)"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"ServiceMesh"}],["meta",{"property":"article:tag","content":"K8s"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"04. 트래픽 관리\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"샘플 앱 준비","slug":"샘플-앱-준비","link":"#샘플-앱-준비","children":[{"level":3,"title":"프론트엔드 서비스","slug":"프론트엔드-서비스","link":"#프론트엔드-서비스","children":[]},{"level":3,"title":"백엔드 서비스","slug":"백엔드-서비스","link":"#백엔드-서비스","children":[]},{"level":3,"title":"서비스 Intention","slug":"서비스-intention","link":"#서비스-intention","children":[]}]},{"level":2,"title":"트래픽 제어 요소","slug":"트래픽-제어-요소","link":"#트래픽-제어-요소","children":[{"level":3,"title":"Resolve","slug":"resolve","link":"#resolve","children":[]},{"level":3,"title":"Splitter","slug":"splitter","link":"#splitter","children":[]},{"level":3,"title":"Router","slug":"router","link":"#router","children":[]},{"level":3,"title":"서비스 Intention (L7)","slug":"서비스-intention-l7","link":"#서비스-intention-l7","children":[]}]},{"level":2,"title":"Consul UI Routing table","slug":"consul-ui-routing-table","link":"#consul-ui-routing-table","children":[]}],"git":{"createdTime":1645936869000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":2.45,"words":736},"filePathRelative":"04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.md","localizedDate":"2022년 2월 27일","excerpt":"\\n

    실습을 진행하기 위한 디렉토리를 생성합니다.

    \\n
    mkdir ./traffic\\n

    Service Mesh는 HTTP 프로토콜 상에서 L7으로 동작하게 됩니다. 따라서 기본 프로토콜을 http로 변경합니다.

    \\n
    cat > ./traffic/service-to-service.yaml <<EOF\\napiVersion: consul.hashicorp.com/v1alpha1\\nkind: ProxyDefaults\\nmetadata:\\n  name: global\\nspec:\\n  config:\\n    protocol: http\\nEOF\\n
    "}');export{C as comp,F as data}; diff --git a/assets/04-z-lab_provisioners_variables_outputs.html-H3qceguI.js b/assets/04-z-lab_provisioners_variables_outputs.html-H3qceguI.js new file mode 100644 index 0000000000..148abda73b --- /dev/null +++ b/assets/04-z-lab_provisioners_variables_outputs.html-H3qceguI.js @@ -0,0 +1,25 @@ +import{_ as o}from"./lab1-02-By4gwc7V.js";import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as p,c as i,b as a,d as e,a as r,e as s}from"./app-Bzk8Nrll.js";const c="/assets/lab4-01-BF_dIRYq.png",d={},u=s('

    💻 Lab - Provisioners, Variables, Outputs

    편집기에서 열기


    🛠️ Use a Provisioner

    @slidestart blood

    Terraform 프로비저닝 도구는 생성 시 한 번 실행됩니다.


    특별한 상황을 제외하고는 후속 적용에서 실행되지 않습니다. (이 실습실처럼...)


    terraform apply 를 입력할 때마다 프로비저닝 도구가 강제로 실행되도록 몇 가지 특별한 조정을 했습니다.

    이는 변경할 때마다 가상 머신을 파괴하고 다시 생성하지 않고 프로비저닝 도구를 사용하여 연습할 수 있도록 하기 위한 것입니다.

    triggers = {
    +    build_number = timestamp()
    +}
    +

    ______________________
    +< Cows love Terraform! >
    + ----------------------
    +         \\   ^__^
    +          \\  (oo)\\_______
    +             (__)\\       )\\/\\
    +                 ||----w |
    +                 ||     ||
    +=============================
    +

    @slideend

    💻 Provisioner를 수정합니다.

    main.tf 파일을 열어 remote-exec 항목이 있는 곳으로 이동합니다.

    inline 항목에 다음을 두줄 추가합니다.

    "sudo apt -y install cowsay",
    +"cowsay Mooooooooooo!",
    +

    terraform fmt명령을 사용하여 코드를 멋지게 정렬 할 수 있는 좋은 시간 입니다.

    이제 변경 사항을 적용하십시오.

    terraform apply -auto-approve
    +

    로그 출력을 뒤로 스크롤합니다. "Moooooooo!"라고 말하는 ASCII 아트 암소가 보일 것입니다.

    Result example

    🖨️ Add an Output

    @slidestart blood

    출력에서 Terraform 데이터와 함께 일반 텍스트를 혼합할 수 있습니다.

    출력은 실행이 끝날 때 사용자에게 유용한 정보를 전달하는 데 사용할 수 있습니다.


    terraform refresh 명령은 상태 파일을 인프라에 있는 파일과 동기화합니다.

    이 명령은 인프라를 변경하지 않습니다.


    Terraform 출력을 다시 보고 싶을 때 언제든지 terraform output 명령을 실행할 수 있습니다.

    단일 출력을 보려면 terraform output <output_name>을 실행합니다.

    @slideend

    💻 Output을 수정합니다.

    output.tf 파일을 열어 아래 항목을 추가합니다.

    output "ssh_info" {
    +  value = nonsensitive("sshpass -p '${data.ncloud_root_password.hashicat.root_password}' ssh root@${ncloud_public_ip.hashicat.public_ip} -oStrictHostKeyChecking=no")
    +}
    +

    해당 output의 이름은 ssh_info 입니다.

    어떤 유형의 출력이 유효한지 보려면 문서 페이지를 참조하세요.

    `,43),h={href:"https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/data-sources/root_password#argument-reference",target:"_blank",rel:"noopener noreferrer"},m={href:"https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/resources/public_ip#attributes-reference",target:"_blank",rel:"noopener noreferrer"},f=s(`

    output.tf에 새로운 내용을 저장하고 terraform refresh 명령을 실행하여 새로운 출력을 확인합니다.

    terraform refresh
    +

    terraform output 명령을 실행하여 모든 출력을 볼 수도 있습니다.

    terraform output
    +

    🐶 Fun With Variables

    @slidestart blood

    Terraform 변수에는 5가지 수준의 우선 순위가 있습니다. 1=최고 5=최저:

    1. 명령줄 플래그 - 명령줄 스위치로 실행
    2. 구성 파일 - terraform.tfvars 파일에 설정
    3. 환경 변수 - 쉘 환경의 일부
    4. 기본 구성 - variables.tf의 기본값
    5. 사용자 수동 입력 - 지정하지 않은 경우 사용자에게 입력을 요청합니다.

    다음은 placeholder 변수로 시도할 수 있는 다른 재미있는 사이트입니다.

    `,11),b={href:"http://placedog.net",target:"_blank",rel:"noopener noreferrer"},_={href:"http://placebear.com",target:"_blank",rel:"noopener noreferrer"},k={href:"http://www.fillmurray.com",target:"_blank",rel:"noopener noreferrer"},g={href:"http://www.placecage.com",target:"_blank",rel:"noopener noreferrer"},v={href:"http://placebeard.it",target:"_blank",rel:"noopener noreferrer"},y={href:"http://loremflickr.com",target:"_blank",rel:"noopener noreferrer"},x={href:"http://baconmockup.com",target:"_blank",rel:"noopener noreferrer"},w={href:"http://placeimg.com",target:"_blank",rel:"noopener noreferrer"},T=s(`

    @slideend

    💻 변수를 조정합니다.

    Terraform 변수를 구성하는 방법에는 여러 가지가 있습니다. 지금까지 terraform.tfvars 파일을 사용하여 변수를 설정했습니다.

    명령줄에서 기본값과 다른 height, width 변수를 사용 하여 애플리케이션을 다시 배포해 보십시오.

    변경 사항을 관찰하기 위해 적용할 때마다 웹 앱을 다시 로드합니다.

    terraform apply -auto-approve -var height=600 -var width=800
    +

    다음으로 Terraform이 읽을 수 있는 환경 변수를 설정해 보십시오. 다음 명령을 실행하여 placeholder 변수를 설정합니다.

    export TF_VAR_placeholder=placedog.net
    +

    환경 변수 적용 후 terraform apply -auto-approve를 실행하여 다시 적용해 봅니다.

    terraform apply -auto-approve
    +

    이제 명령줄에서 동일한 변수를 다르게 설정하여 다시 시도하십시오.

    terraform apply -auto-approve -var placeholder=placebear.com
    +

    어떤 변수가 우선시 되었습니까? 잘 이해 되셨나요?

    `,13),P={href:"https://www.terraform.io/docs/language/values/variables.html#variable-definition-precedence",target:"_blank",rel:"noopener noreferrer"},C=s('

    😱 Quiz Time 4. Terraform Variables

    Q. *.tfvars 파일과 환경 변수에 동일한 변수가 설정되어 있습니다. 어느 것이 우선합니까?


    이 장에서 우리는 :

    ',8);function q(V,N){const t=l("ExternalLinkIcon");return p(),i("div",null,[u,a("ul",null,[a("li",null,[e("ncloud_root_password : "),a("a",h,[e("https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/data-sources/root_password#argument-reference"),r(t)])]),a("li",null,[e("ncloud_public_ip :"),a("a",m,[e("https://registry.terraform.io/providers/NaverCloudPlatform/ncloud/latest/docs/resources/public_ip#attributes-reference"),r(t)])])]),f,a("ul",null,[a("li",null,[a("a",b,[e("placedog.net"),r(t)])]),a("li",null,[a("a",_,[e("placebear.com"),r(t)])]),a("li",null,[a("a",k,[e("www.fillmurray.com"),r(t)])]),a("li",null,[a("a",g,[e("www.placecage.com"),r(t)])]),a("li",null,[a("a",v,[e("placebeard.it"),r(t)])]),a("li",null,[a("a",y,[e("loremflickr.com"),r(t)])]),a("li",null,[a("a",x,[e("baconmockup.com"),r(t)])]),a("li",null,[a("a",w,[e("placeimg.com"),r(t)])])]),T,a("p",null,[e("다음 "),a("a",P,[e("공식문서"),r(t)]),e("를 참고할 수 있습니다.")]),C])}const L=n(d,[["render",q],["__file","04-z-lab_provisioners_variables_outputs.html.vue"]]),S=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.html","title":"💻 Lab - Provisioners, Variables, Outputs","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"💻 Lab - Provisioners, Variables, Outputs"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"💻 Lab - Provisioners, Variables, Outputs\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":3,"title":"편집기에서 열기","slug":"편집기에서-열기","link":"#편집기에서-열기","children":[]},{"level":2,"title":"🛠️ Use a Provisioner","slug":"🛠️-use-a-provisioner","link":"#🛠️-use-a-provisioner","children":[{"level":3,"title":"Terraform 프로비저닝 도구는 생성 시 한 번 실행됩니다.","slug":"terraform-프로비저닝-도구는-생성-시-한-번-실행됩니다","link":"#terraform-프로비저닝-도구는-생성-시-한-번-실행됩니다","children":[]},{"level":3,"title":"특별한 상황을 제외하고는 후속 적용에서 실행되지 않습니다. (이 실습실처럼...)","slug":"특별한-상황을-제외하고는-후속-적용에서-실행되지-않습니다-이-실습실처럼","link":"#특별한-상황을-제외하고는-후속-적용에서-실행되지-않습니다-이-실습실처럼","children":[]},{"level":3,"title":"terraform apply 를 입력할 때마다 프로비저닝 도구가 강제로 실행되도록 몇 가지 특별한 조정을 했습니다.","slug":"terraform-apply-를-입력할-때마다-프로비저닝-도구가-강제로-실행되도록-몇-가지-특별한-조정을-했습니다","link":"#terraform-apply-를-입력할-때마다-프로비저닝-도구가-강제로-실행되도록-몇-가지-특별한-조정을-했습니다","children":[]}]},{"level":2,"title":"🖨️ Add an Output","slug":"🖨️-add-an-output","link":"#🖨️-add-an-output","children":[{"level":3,"title":"출력에서 Terraform 데이터와 함께 일반 텍스트를 혼합할 수 있습니다.","slug":"출력에서-terraform-데이터와-함께-일반-텍스트를-혼합할-수-있습니다","link":"#출력에서-terraform-데이터와-함께-일반-텍스트를-혼합할-수-있습니다","children":[]},{"level":3,"title":"terraform refresh 명령은 상태 파일을 인프라에 있는 파일과 동기화합니다.","slug":"terraform-refresh-명령은-상태-파일을-인프라에-있는-파일과-동기화합니다","link":"#terraform-refresh-명령은-상태-파일을-인프라에-있는-파일과-동기화합니다","children":[]},{"level":3,"title":"Terraform 출력을 다시 보고 싶을 때 언제든지 terraform output 명령을 실행할 수 있습니다.","slug":"terraform-출력을-다시-보고-싶을-때-언제든지-terraform-output-명령을-실행할-수-있습니다","link":"#terraform-출력을-다시-보고-싶을-때-언제든지-terraform-output-명령을-실행할-수-있습니다","children":[]}]},{"level":2,"title":"🐶 Fun With Variables","slug":"🐶-fun-with-variables","link":"#🐶-fun-with-variables","children":[{"level":3,"title":"Terraform 변수에는 5가지 수준의 우선 순위가 있습니다. 1=최고 5=최저:","slug":"terraform-변수에는-5가지-수준의-우선-순위가-있습니다-1-최고-5-최저","link":"#terraform-변수에는-5가지-수준의-우선-순위가-있습니다-1-최고-5-최저","children":[]},{"level":3,"title":"다음은 placeholder 변수로 시도할 수 있는 다른 재미있는 사이트입니다.","slug":"다음은-placeholder-변수로-시도할-수-있는-다른-재미있는-사이트입니다","link":"#다음은-placeholder-변수로-시도할-수-있는-다른-재미있는-사이트입니다","children":[]}]},{"level":2,"title":"😱 Quiz Time 4. Terraform Variables","slug":"quiz-time-4-terraform-variables","link":"#quiz-time-4-terraform-variables","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.87,"words":260},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    편집기에서 열기

    \\n
    \\n\\n
    \\n

    🛠️ Use a Provisioner

    \\n

    @slidestart blood

    \\n

    Terraform 프로비저닝 도구는 생성 시 한 번 실행됩니다.

    \\n
    "}');export{L as comp,S as data}; diff --git a/assets/05-deployment.html-B4eAra3Y.js b/assets/05-deployment.html-B4eAra3Y.js new file mode 100644 index 0000000000..f1343867f3 --- /dev/null +++ b/assets/05-deployment.html-B4eAra3Y.js @@ -0,0 +1,58 @@ +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c,b as a,d as n,a as e,e as t}from"./app-Bzk8Nrll.js";const u={},i=a("h1",{id:"_5-tomcat-애플리케이션-배포",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_5-tomcat-애플리케이션-배포"},[a("span",null,"5. Tomcat 애플리케이션 배포")])],-1),r=a("ul",null,[a("li",null,"Web Application"),a("li",null,"by Manager"),a("li",null,"by webapps DIR"),a("li",null,"by context.xml"),a("li",null,"ROOT"),a("li",null,"Auto Deployment & Hot Deployment"),a("li",null,"Parallel Deployment")],-1),d=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/Hg-D7szI2t4",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),m=t(`

    5.1 Web Application

    톰캣에 배치되는 어플리케이션은 Java Web Application입니다. 간단히 웹 어플리케이션이라고도 합니다. 간략한 구조는 다음과 같습니다.

    파일 구조

    ./APPDIR
    +├── WEB-INF
    +│   ├── classes
    +│   │   └── class-files
    +│   ├── lib
    +│   │   └── jar-files
    +│   └── web.xml
    +├── index.html
    +└── index.jsp
    +

    APP 디렉토리 하위에는 웹어플리케이션의 정의를 넣을 WEB-INF 디렉토리가 필요합니다. 아주 간단한 어플리케이션은 web.xml에 다음의 태그만 넣어도 웹 어플리케이션으로 인지할 수 있습니다.

    <web-app/>
    +

    5.2 by Manager

    어플리케이션을 배치하는 방법에는 톰캣에서 제공하는 manager를 사용하는 방법이 있습니다. manager는 톰캣을 설치하면 제공되는 어플리케이션 관리 툴로 다음과 같이 확인할 수 있습니다.

    `,8),k={href:"http://ip",target:"_blank",rel:"noopener noreferrer"},g={href:"http://localhost:8080",target:"_blank",rel:"noopener noreferrer"},h=a("li",null,"ROOT 어플리케이션의 호출 확인 후 좌측의 'Manager App' 버튼 클릭",-1),x=a("li",null,"톰캣 유저의 설정이 되어있지 않으므로 로그인 창에서 '취소'버튼 클릭",-1),v=a("li",null,[a("code",null,"$CATALINA_HOME/conf/tomcat-user.xml"),n(" 에 설정하는 방법을 에러페이지에서 확인")],-1),b=a("li",null,[a("code",null,"tomcat-user.xml"),n("에 다음과 같이 설정 추가 (e.g. "),a("code",null,"user/passwd"),n("를 "),a("code",null,"admin/admin"),n("으로 설정)")],-1),y=t(`
    <tomcat-users>
    +  <role rolename="manager-gui"/>
    +  <user username="admin" password="admin" roles="manager-gui"/>
    +</tomcat-users>
    +

    manager의 중간에 Deploy에서 배치를 수행할 수 있으며 두가지 타입이 제공됩니다. 한가지는 Deploy directory or WAR file located on server로서 톰캣이 기동된 서버내의 어플리케이션을 지정하여 배치하는 방법과 WAR file to deploy는 현재 접속중인 로컬의 WAR파일을 업로드하여 배치하는 방법입니다. 두 방법 모두 수행 후 $CATALINA_HOME/webapps 에 해당 어플리케이션이 위치하게 됩니다.

    5.3 by webapps DIR

    톰캣에는 자동으로 어플리케이션을 인지하고 배치하는 디렉토리가 있습니다. 바로 $CATALINA_HOME/webapps입니다. 해당 경로는 앞서 manager 를 통한 배치시에도 어플리케이션이 위치하게되는 경로인데, manager를 사용하는 방법은 결국 webapps 디렉토리에 어플리케이션을 위치시키는 작업이라고 볼 수 있습니다. 따라서 직접 해당 경로에 어플리케이션을 위치시켜도 동일하게 배치 작업이 발생합니다.

    webapps 디렉토리가 자동으로 배치를 수행하는 설정은 server.xml에서 해당 경로가 배치를 수행하도록 설정되었기 때문입니다.

    <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
    +    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
    +           prefix="localhost_access_log." suffix=".txt"
    +           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
    +</Host>
    +

    server.xml에서 확인 할 수 있는 톰캣의 기본 host 환경인 localhost에서 webapps 디렉토리를 대상으로 autoDeploy를 수행한다는 설정 내용입니다. 이러한 설정으로 인해 자동으로 어플리케이션의 배치가 가능합니다.

    5.4 by context.xml

    앞서 두가지의 manager나 webapps 디렉토리를 통한 배치 방법은 모두 톰캣 내부의 webapps 디렉토리에 어플리케이션이 위치하게 된다는 특징이 있습니다. 이러한 점은 사용자가 원하는 임의의 위치에 어플리케이션은 배제 된다는 의미 일 수도 있습니다. 따라서 이경우 context.xml 형태의 xml을 통한 배치 방법을 사용 할 수 있겠습니다.

    배치를 설명하기에 앞서 context.xmlcontext 디스크립터는 본래 server.xml에 위치하는 디스크립터였습니다. 하지만 해당 디스크립터에 설정되는 내용들은 변경사항이 자주 발생하는 항목들이기에 별도의 xml에서도 정의할 수 있도록 변경되었으며, 톰캣 5.5.12 버전부터는 server.xml이 아닌 별도의 context.xml을 통하여 해당 설정들을 관리하도록 권고하고 있습니다.

    context.xml에서 설정하는 정보는 어플리케이션 뿐만이 아니기 때문에 어플리케이션 설정을 제외한 설정을 톰캣에 적용하는 경우에도 사용됩니다. context.xml<Context> 디스크립터로 시작되며 다음의 위치에서 적용되고, 위치에 따라 적용 범위가 달라집니다.

    1. $CATALINA_HOME/conf/context.xml : $CATALINA_HOME 내의 모든 톰켓 프로세스 내의 서비스
    2. $CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME]/context.xml.default : Host 범위 내의 모든 서비스
    3. $CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME]/[WEBAPPNAME].xml : 어플리케이션 범위 내의 모든 서비스
    4. $WEBAPP/META-INF/context.xml : 어플리케이션 범위 내의 모든 서비스

    이같은 위치에 따른 적용 범위는 context로 정의되는 대표적인 자원중 하나인 데이터소스(DataSource)의 경우 톰캣전체 또는 어플리케이션 별로 구분할 수 있는 기능을 사용할 수 있습니다.

    어플리케이션을 배포하는 경우 위의 4가지 방법 중 3, 4번 항목을 들 수 있으며, 특히 context.xml을 사용한 임의의 위치의 어플리케이션 배포는 3번 항목을 사용하게 됩니다.

    sample context-root를 갖는 어플리케이션은 다음과 같이 context.xml을 설정할 수 있습니다.

    <?xml version="1.0" encoding="UTF-8"?>
    +
    +<Context path="sample" docBase="/Users/GSLee/APP/sample" debug="0" reloadable="true" crossContext="true" privileged="true"/>
    +
    +<!-- path는 해당 설정을 server.xml에 하는 경우 의미가 있고 3번 방법의 경우 xml 파일 이름이 context-root로 설정됩니다. -->
    +

    5.5 ROOT

    `,18),_=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/kOp9ahbtE9Q",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),q=t(`

    일반적으로 어플리케이션을 배치하는 경우 해당 어플리케이션의 디렉토리 이름이나 context로 설정된 xml의 파일 이름이 context-root로 사용됩니다.

    http://www.mytomcat.co.kr/[WEBAPPNAME]/index.jsp

    하지만 대부분의 경우 다음과 같이 요청되기를 바라죠.

    http://www.mytomcat.co.kr/index.jsp

    이경우 배치 방식은 동일하지만 다음의 네가지 방법을 통해 어플리케이션 배치 시 context-root를 /로 설정할 수 있습니다.

    구성 위치설명
    $CATALINA_HOME/webapps/ROOTwebapps 기본 디렉토리에 ROOT인 디렉토리명으로 배치된 어플리케이션
    $CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME]/ROOT.xmlROOT를 이름으로 갖는 context 타입의 xml로 배치된 어플리케이션
    Tomcat ManagerAPP에서 context path 항목을 비워놓은 채로 배치하는 어플리케이션
    server.xml에 배치 어플리케이션을 설정context 디스크립터의 path항목을 비워놓음

    방법은 여러가지가 있지만 앞서 설명드린 context디스크립터로 설정한 별도의 xml을 사용한 배치 방식을 권장합니다.

    sample 어플리케이션을 ROOT로 배치한 로그는 다음과 같이 확인됩니다.

    정보: Starting Servlet Engine: Apache Tomcat/8.5.73
    +9월 06, 2014 8:30:52 오후 org.apache.catalina.startup.HostConfig deployDescriptor
    +정보: Deploying configuration descriptor /Users/GSLee/APP/tomcat/apache-tomcat-8.5.73/conf/Catalina/localhost/ROOT.xml
    +9월 06, 2014 8:30:52 오후 org.apache.catalina.core.StandardContext setPath
    +경고: A context path must either be an empty string or start with a '/'. The path [sample] does not meet these criteria and has been changed to [/sample]
    +9월 06, 2014 8:30:52 오후 org.apache.catalina.startup.SetContextPropertiesRule begin
    +경고: [SetContextPropertiesRule]{Context} Setting property 'debug' to '0' did not find a matching property.
    +9월 06, 2014 8:30:53 오후 org.rhq.helpers.rtfilter.filter.RtFilter init
    +정보: Initialized response-time filter for webapp with context root 'ROOT'.
    +9월 06, 2014 8:30:53 오후 org.apache.catalina.startup.HostConfig deployDescriptor
    +정보: Deployment of configuration descriptor /Users/GSLee/APP/tomcat/apache-tomcat-8.5.73/conf/Catalina/localhost/ROOT.xml has finished in 1,115 ms
    +

    5.6 Auto Deployment & Hot Deployment

    Auto Deploy와 Hot Deploy는 Auto와 Hot을 어떻게 해석하는가에 따라 다음과 같이 혼용되어 사용됩니다.

    의미가 어떻게 해석되던지 이런 용어를 사용함에 있어서 바라는점은 서비스가 실행중인 도중에도 변경사항을 사용자 모르게 바꾸고자 하는 의도가 대부분일 것입니다.

    5.6.1 webapps 'autoDeploy'

    webapps 디렉토리에 어플리케이션을 넣으면 자동으로 배치가 됩니다. 서버가 기동된 상태에서도 말이죠. 해당 설정은 다음의 'Host' 디스크립터에서 정의합니다.

    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
    +      ...
    +</Host>
    +

    Host 설정에 autoDeploy가 true인 경우 해당 디렉토리에 위치하는 어플리케이션을 감지하여 자동으로 톰캣에 배치를 수행합니다.

    5.6.2 jsp 'checkInterval'

    jsp를 사용자 뷰로 사용하는 경우 서비스의 컨텐츠, 또는 jsp에서 실행하는 코드상의 변경사항이 자주 발생하게 됩니다. 이경우 jsp를 새로 반영하기 위해 서버가 실행중임에도 자동으로 업데이트된 jsp를 컴파일하여 해당 소스를 반영하는 동작을 지원하는 설정이 있습니다. 해당 설정은 다음의 $CATAILNA_HOME/conf/web.xml의 jsp 서블릿에 정의되어 있습니다.

    <servlet>
    +    <servlet-name>jsp</servlet-name>
    +    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    +    ...
    +    <init-param>
    +        <param-name>development</param-name>
    +        <param-value>true</param-value>
    +    </init-param>
    +    <init-param>
    +        <param-name>checkInterval</param-name>
    +        <param-value>1</param-value>
    +    </init-param>
    +    <load-on-startup>3</load-on-startup>
    +</servlet>
    +

    jsp 서블릿에서는 두가지 경우에 대해 jsp의 업데이트를 수행합니다.

    관련 옵션에 대한 상세 내용이나 추가적인 jasper 컴포넌트의 옵션은 해당 설정의 위에 주석으로 설명이 되어있습니다.

    5.6.3 Class 'reloadable'

    클래스를 수정하여 컴파일한 뒤 어플리케이션에 업로드하면 얼마 후 해당 어플리케이션(컨텍스트)를 리로딩하는 과정을 수행합니다. 해당 설정은 Context 디스크립터가 설정된 xml에 정의합니다.

    <Context reloadable="true" ...>
    +    <Loader checkInterval="15"/>
    +</Context>
    +

    reloadabletrue로 설정하는 경우 해당 어플리케이션(컨텍스트)는 클래스에 변경이 발생하면 다시 리로딩하는 기능을 수행합니다. 간격은 기본 15초이기 때문에 더 빠른 방영을 원하시면 Context디스크립터 내에 LoadercheckInterval을 정의함으로 시간을 조절할 수 있습니다. 하지만 이런 리로드 현상을 싫어하시는 분도 있습니다. "Tomcat Context 수동 Reload"라는 블로그 글에서도 보이듯 자동 리로드를 비활성화 하고 Valve를 사용하는 별도의 방법을 사용할 수도 있습니다.


    5.7 Parallel Deployment

    `,29),w=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/Bp789a8MBWI",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),A=a("p",null,[n("어플리케이션의 클래스가 수정되어 리로드가 발생하거나 라이브러리나 xml등의 재기동 후 반영되는 변경사항을 적용하기 위해서는 톰캣을 재기동해야 하는 상황이 발생합니다. 이런 경우 기존 어플리케이션은 기존에 사용중인 사용자가 계속 이용 할 수 있도록 활성화된 상태에서 새로운 버전의 어플리케이션을 배치, 새로운 사용자는 새로운 어플리케이션을~~(새술은 새부대에?)~~ 사용하도록 하는 "),a("mark",null,"Parallel Deployment"),n(" 기능을 사용 할 수 있습니다.")],-1),f=a("figure",null,[a("img",{src:"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/appVersion.jpg?token=ADUAZXOCLTBA3NROCYL7SF267EUQK",alt:"parallelDeploy",tabindex:"0",loading:"lazy"}),a("figcaption",null,"parallelDeploy")],-1),T={href:"http://docs.oracle.com/middleware/1212/wls/DEPGD/redeploy.htm#DEPGD266",target:"_blank",rel:"noopener noreferrer"},O=t(`

    그 방법은 WEBAPP##[VersionNumber]입니다. sample 어플리케이션으로 예를 들면 sample##1.0으로 배치를 수행하는 것입니다. 기존 어플리케이션 뒤에 샵 기호 두개와 버전이름만 붙이면 되기 때문에 매우 간단하지만 단점으로는 거의 동일한 구성과 용량의 독립적인 어플리케이션이 필요하기 때문에 어느정도 변경사항이 많은 경우 활용도가 높겠습니다. 버전은 float 형태로 정의하며 상대적으로 높은 값이 신규 배치가 됩니다. Context에서 지정하는 경우에는 어플리케이션 경로를 설정한 대로 샵 기호가 추가된 어플리케이션 이름을 지정하면 서비스 컨텍스트는 샵 기호와 버전이 제외된 기존 경로를 사용하게 됩니다.

    <Context docBase="/app/sample##01" ... /></Context>
    +
    <Context docBase="/app/sample##02" ... /></Context>
    +

    배치된 어플리케이션은 톰캣 Manager에서도 확인할 수 있습니다.

    버전이 높은 어플리케이션이 배치되면 기존 사용자는 이전 버전의 어플리케이션 서비스를 이용하고 새로 접속하는 사용자는 신규 어플리케이션의 서비스를 이용하게 됩니다. 이전 버전의 어플리케이션은 톰캣 Manager에서 Session을 확인하여 사용자가 없는것을 확인 후 제거할 수 있습니다.

    `,5);function D(E,C){const s=o("ExternalLinkIcon");return l(),c("div",null,[i,r,d,m,a("ul",null,[a("li",null,[a("a",k,[n("http://ip"),e(s)]),n(":port 로 기본 톰캣 http 요청 (로컬에서 기본 설정으로 실행한 경우 "),a("a",g,[n("http://localhost:8080"),e(s)]),n(")")]),h,x,v,b]),y,_,q,w,A,f,a("p",null,[n("WebLogic Server 같은 타 WAS에서도 이와 유사한 '"),a("a",T,[n("production redeployment"),e(s)]),n("'기능이 있지만 톰캣이 좀더 쉬운 방법을 제공합니다.")]),O])}const I=p(u,[["render",D],["__file","05-deployment.html.vue"]]),R=JSON.parse(`{"path":"/05-Software/Tomcat/tomcat101/05-deployment.html","title":"5. Tomcat 애플리케이션 배포","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/05-deployment.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"5. Tomcat 애플리케이션 배포"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/appVersion.jpg?token=ADUAZXOCLTBA3NROCYL7SF267EUQK"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-19T11:31:31.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"5. Tomcat 애플리케이션 배포"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-19T11:31:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"5. Tomcat 애플리케이션 배포\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/appVersion.jpg?token=ADUAZXOCLTBA3NROCYL7SF267EUQK\\"],\\"dateModified\\":\\"2023-09-19T11:31:31.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"5.1 Web Application","slug":"_5-1-web-application","link":"#_5-1-web-application","children":[]},{"level":2,"title":"5.2 by Manager","slug":"_5-2-by-manager","link":"#_5-2-by-manager","children":[]},{"level":2,"title":"5.3 by webapps DIR","slug":"_5-3-by-webapps-dir","link":"#_5-3-by-webapps-dir","children":[]},{"level":2,"title":"5.4 by context.xml","slug":"_5-4-by-context-xml","link":"#_5-4-by-context-xml","children":[]},{"level":2,"title":"5.5 ROOT","slug":"_5-5-root","link":"#_5-5-root","children":[]},{"level":2,"title":"5.6 Auto Deployment & Hot Deployment","slug":"_5-6-auto-deployment-hot-deployment","link":"#_5-6-auto-deployment-hot-deployment","children":[{"level":3,"title":"5.6.1 webapps 'autoDeploy'","slug":"_5-6-1-webapps-autodeploy","link":"#_5-6-1-webapps-autodeploy","children":[]},{"level":3,"title":"5.6.2 jsp 'checkInterval'","slug":"_5-6-2-jsp-checkinterval","link":"#_5-6-2-jsp-checkinterval","children":[]},{"level":3,"title":"5.6.3 Class 'reloadable'","slug":"_5-6-3-class-reloadable","link":"#_5-6-3-class-reloadable","children":[]}]},{"level":2,"title":"5.7 Parallel Deployment","slug":"_5-7-parallel-deployment","link":"#_5-7-parallel-deployment","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695123091000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":2.35,"words":706},"filePathRelative":"05-Software/Tomcat/tomcat101/05-deployment.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n"}`);export{I as comp,R as data}; diff --git a/assets/05-plugins.html-C_L6Paff.js b/assets/05-plugins.html-C_L6Paff.js new file mode 100644 index 0000000000..fcd3ca71e9 --- /dev/null +++ b/assets/05-plugins.html-C_L6Paff.js @@ -0,0 +1,5 @@ +import{_ as a}from"./1564450122219-xixIqXDz.js";import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as l,c as d,b as e,d as n,a as o,e as t}from"./app-Bzk8Nrll.js";const c={},p=e("h1",{id:"_5-plugins",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_5-plugins"},[e("span",null,"5. Plugins")])],-1),g=e("p",null,"Jenkins가 유용한 툴인 이유중 하나는 방대한 양의 플러그인 입니다. Jenkins의 기능을 확장시키고, 관리, 빌드 정책 등을 확장 시켜주고, 타 서비스와의 연계를 쉽게 가능하도록 합니다.",-1),u={href:"https://plugins.jenkins.io/",target:"_blank",rel:"noopener noreferrer"},h=t('
    1564450122219
    1564450122219

    5.1 Adding plugins via plugin manager

    Jenkins는 온라인에 연결된 plugin을 검색, 설치할 수 있는 플러그인 관리기능을 갖고 있습니다. 좌측 메뉴에서 Jenkins 관리를 클릭하면 플러그인 관리 링크를 통하여 해당 기능에 접근할 수 있습니다.

    각 플러그인 이름을 클릭하면 플러그인 정보를 확인할 수 있는 plugins.jenkins.io 사이트로 이동하여 정보를 보여줍니다. 사용방법은 우측에 wiki링크를 클릭합니다. 대략적인 UI나 사용방법은 wiki.jenkins.io에서 제공합니다.

    5.2 Using shared libraries

    ',6),m={href:"https://jenkins.io/doc/book/pipeline/shared-libraries/",target:"_blank",rel:"noopener noreferrer"},_={href:"https://github.com/Great-Stone/evenOdd",target:"_blank",rel:"noopener noreferrer"},k=t("

    소스의 var 디렉토리에는 Pipeline에서 사용하는 Shared Library들이 들어있습니다. groovy 스크립트로 되어있으며 Pipeline을 구성한 jenkinsfile에서 이를 사용합니다.

    vars/evenOdd.groovy를 호출하고 값을 받아오는 형태를 갖고, evenOdd.groovy에서 사용하는 log.infolog.warningvars/log.groovy에 구현되어있습니다.

    다음과 같이 Jenkins에 설정을 수행합니다.

    1. Jenkins 관리클릭 후 시스템 설정을 선택합니다.
    2. Global Pipeline Libraries 의 추가 버튼을 클릭하여 새로운 구성을 추가합니다.

    Shared Libraries가 준비가 되면 Pipeline 타입의 Item을 생성하고 (e.g. 05-02.UsingSharedLibraries) Pipeline 설정을 추가합니다.

    ",5),v=e("li",null,"Definition : Pipeline script from SCM",-1),b=e("li",null,"SCM : Git",-1),f={href:"https://github.com/Great-Stone/evenOdd.git",target:"_blank",rel:"noopener noreferrer"},y=t(`

    저장 후 Build Now를 클릭하여 빌드를 수행합니다. 빌드의 결과로는 2 단계로 수행되는데 1단계는 Declarative: Checkout SCM으로 SCM으로부터 소스를 받아 준비하는 단계이고, 2단계는 jenkinsfile을 수행하는 단계입니다. vars/evenOdd.goovy 스크립트에는 stage가 두개 있으나 해당 Pipeline 을 호출하는 값에 따라 하나의 stage만을 수행하도록 되어있어서 하나의 stage가 수행되었습니다.

    // Jenkinsfile
    +//@Library('evenOdd') _
    +
    +evenOdd(currentBuild.getNumber())
    +

    currentBuild.getNumber()는 현재 생성된 Pipeline Item의 빌드 숫자에 따라 값을 evenOdd(빌드 숫자)형태로 호출하게 됩니다.

    Jenkins shared libraries를 사용하는 가장 좋은 예는 재사용성 있는 Groovy 함수를 타 작업자와 공유하는 것 입니다. 빌드의 상태는 다른 파이프 라인 단계로 계속할 것인지 결정하는 데 사용할 수도 있습니다.

    경고

    해당 설정은 모든 빌드에 영향을 주기 때문에 타 작업을 위해 추가된 Global Pipeline LibrariesLibrary를 삭제하여 진행합니다.

    `,5);function S(P,J){const i=s("ExternalLinkIcon");return l(),d("div",null,[p,g,e("p",null,[e("a",u,[n("Plugin Index"),o(i)])]),h,e("p",null,[n("Jenkins Pipeline의 Shared libraries에 대한 상세 내용은 다음 링크를 참고합니다. "),e("a",m,[n("https://jenkins.io/doc/book/pipeline/shared-libraries/"),o(i)])]),e("p",null,[n("이번 실습을 진행하기전에 GitHub에서 "),e("a",_,[n("https://github.com/Great-Stone/evenOdd"),o(i)]),n(" repository를 본인 계정의 GitHub에 Fork 하여 진행합니다.")]),k,e("ul",null,[v,b,e("li",null,[n("Repositories "),e("ul",null,[e("li",null,[n("Repository URL : "),e("a",f,[n("https://github.com/Great-Stone/evenOdd.git"),o(i)])])])])]),y])}const j=r(c,[["render",S],["__file","05-plugins.html.vue"]]),L=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/05-plugins.html","title":"5. Plugins","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/05-plugins.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"5. Plugins"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"5. Plugins\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"5.1 Adding plugins via plugin manager","slug":"_5-1-adding-plugins-via-plugin-manager","link":"#_5-1-adding-plugins-via-plugin-manager","children":[]},{"level":2,"title":"5.2 Using shared libraries","slug":"_5-2-using-shared-libraries","link":"#_5-2-using-shared-libraries","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.65,"words":196},"filePathRelative":"05-Software/Jenkins/pipeline101/05-plugins.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    Jenkins가 유용한 툴인 이유중 하나는 방대한 양의 플러그인 입니다. Jenkins의 기능을 확장시키고, 관리, 빌드 정책 등을 확장 시켜주고, 타 서비스와의 연계를 쉽게 가능하도록 합니다.

    \\n

    Plugin Index

    \\n
    1564450122219
    \\n

    5.1 Adding plugins via plugin manager

    "}');export{j as comp,L as data}; diff --git a/assets/05-terraform-state.html-RoEWncbE.js b/assets/05-terraform-state.html-RoEWncbE.js new file mode 100644 index 0000000000..dc3a08f3f3 --- /dev/null +++ b/assets/05-terraform-state.html-RoEWncbE.js @@ -0,0 +1,25 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a,e as n}from"./app-Bzk8Nrll.js";const r={},s=n(`

    05. 테라폼 상태파일(State)

    Terraform State

    Terraform은 stateful 애플리케이션입니다. 즉, state file 내부에서 빌드 한 모든 내용을 추적합니다.

    앞서의 실습에서 반복된 Apply 작업 간에 Workspace 디렉토리에 나타난 terraform.tfstateterraform.tfstate.backup 파일을 보셨을 것입니다.

    상태 파일은 Terraform이 알고있는 모든 것에 대한 기록 소스입니다.

    파일 구조

    WORKSPACE
    +├── files
    +│   └── deploy_app.sh
    +├── main.tf
    +├── outputs.tf
    +├── \`terraform.tfstate\`
    +├── \`terraform.tfstate.backup\`
    +├── terraform.tfvars
    +└── variables.tf
    +

    State 파일 내부는 JSON 형식으로 구성되어있습니다.

    {
    +  "version": 4,
    +  "terraform_version": "0.12.7",
    +  "serial": 14,
    +  "lineage": "452b4191-89f6-db17-a3b1-4470dcb00607",
    +  "outputs": {
    +    "catapp_url": {
    +      "value": "http://go-hashicat-5c0265179ccda553.workshop.aws.hashidemos.io",
    +      "type": "string"
    +    },
    +

    Terraform Refresh

    때때로 인프라는 Terraform이 통제하는 범위 밖에서 변경 될 수 있습니다. (수동으로 UI에서 변경 등)

    State 파일은 인프라의 마지막으로 갱신된 상태를 나타냅니다. 상태 파일이 빌드 한 파일과 여전히 일치하는지 확인하고 확인하려면 terraform refresh 명령을 사용할 수 있습니다.

    이것은 인프라를 업데이트하지 않는 상태 파일 만 업데이트합니다.

    terraform refresh
    +

    기존 인프라 변경

    계획을 실행하거나 적용 할 때마다 Terraform은 세 가지 데이터 소스를 조정합니다.

    1. 코드에 작성한 내용
    2. 상태 파일
    3. 실제로 존재하는 것

    Terraform은 *.tf 파일에있는 내용을 기반으로 기존 리소스를 추가, 삭제, 변경 또는 교체하기 위해 최선 을 다합니다. 다음은 Plan/Apply 중에 각 리소스에 발생할 수있는 네 가지 사항입니다.

    +   create
    +-   destroy
    +-/+ replace
    +~   update in-place
    +

    경고

    무엇인가 변경할때 -/+ replace가 발생하는지 확인하세요. 이것은 기존 리소스를 삭제하고 다시 생성합니다.

    😱 Terraform State Quiz :

    각 시나리오에서 어떤 일이 발생합니까? 논의해 볼까요?

    Configuration(.tf)StateRealityOperation
    ncloud_server???
    ncloud_serverncloud_server???
    ncloud_serverncloud_serverncloud_server???
    ncloud_serverncloud_server???
    ncloud_server???
    ncloud_server???
    Configuration(.tf)StateRealityOperation
    ncloud_servercreate
    ncloud_serverncloud_servercreate
    ncloud_serverncloud_serverncloud_server-
    ncloud_serverncloud_serverdelete
    ncloud_server-
    ncloud_serverupdate state
    `,23),l=[s];function o(c,d){return e(),a("div",null,l)}const u=t(r,[["render",o],["__file","05-terraform-state.html.vue"]]),m=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html","title":"05. 테라폼 상태파일(State)","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"05. 테라폼 상태파일(State)"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-19T11:31:31.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-19T11:31:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"05. 테라폼 상태파일(State)\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-19T11:31:31.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Terraform State","slug":"terraform-state","link":"#terraform-state","children":[]},{"level":2,"title":"Terraform Refresh","slug":"terraform-refresh","link":"#terraform-refresh","children":[]},{"level":2,"title":"기존 인프라 변경","slug":"기존-인프라-변경","link":"#기존-인프라-변경","children":[]},{"level":2,"title":"😱 Terraform State Quiz :","slug":"terraform-state-quiz","link":"#terraform-state-quiz","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695123091000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":0.48,"words":143},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    Terraform State

    \\n

    Terraform은 stateful 애플리케이션입니다. 즉, state file 내부에서 빌드 한 모든 내용을 추적합니다.

    \\n

    앞서의 실습에서 반복된 Apply 작업 간에 Workspace 디렉토리에 나타난 terraform.tfstateterraform.tfstate.backup 파일을 보셨을 것입니다.

    \\n

    상태 파일은 Terraform이 알고있는 모든 것에 대한 기록 소스입니다.

    "}');export{u as comp,m as data}; diff --git a/assets/06-dbconnection.html-Lm8jzNRi.js b/assets/06-dbconnection.html-Lm8jzNRi.js new file mode 100644 index 0000000000..71fdb7f75f --- /dev/null +++ b/assets/06-dbconnection.html-Lm8jzNRi.js @@ -0,0 +1,115 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c as o,b as n,d as c,a as l,e as a}from"./app-Bzk8Nrll.js";const u={},i=n("h1",{id:"_6-tomcat-database-연동",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_6-tomcat-database-연동"},[n("span",null,"6. Tomcat Database 연동")])],-1),r=n("ul",null,[n("li",null,"JDBC Connection Pool"),n("li",null,"DB 연동 예제"),n("li",null,"DB 연동 설정값"),n("li",null,"JNDI Lookup")],-1),k=n("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/odsWlmZfzag",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),d=a(`

    6.1 JDBC Connection Pool

    JDBC Connection Pool은 Java에서 DB(Data Base)의 Session 자원을 미리 확보함으로 재생성하는 비용을 줄이기 위한 기술 입니다. Java에서 사용되는 기술이기 때문에 각 DB 벤더사들은 Java로 구현되는 서비스에서 자사의 DB를 사용할 수 있도록 별도의 라이브러리를 제공하며 이를 사용하여 DB와의 Connection을 생성하고 DB를 사용할 수 있게 됩니다.

    JDBC는 여타 드라이버와는 다르게 미리 Connection을 확보하여 JVM상에 Object상태로 만들어두고 이를 요청하는 서비스에 빌려줍니다. 빌려준다는 표현을 사용한 이유는 반드시 반환되어야 하기 때문입니다. 앞서 미리 만든다는 표현은 만드는 개수가 제한되어 있다는 의미로 사용하였으며, 때문에 한정된 자원을 DB와의 연계 처리만을 하는 경우 잠시 사용하고 다시 반납하는 과정을 거칩니다.

    다음은 jsp에서 단일 Oracle DB와의 Connection Pool을 생성하고 반납하는 샘플 코드입니다.(테스트 용도로 입니다.)

    <%@ page import="java.sql.*" %>
    +<%
    +  StringBuffer sbError = new StringBuffer();
    +  DatabaseMetaData dbMetaData = null;
    +  Connection conn = null;
    +%>
    +<font size="-1"><p>
    +<%
    +  DriverManager.registerDriver (new oracle.jdbc.OracleDriver());
    +  try {
    +    conn = DriverManager.getConnection("jdbc:oracle:thin:@172.16.1.128:1521:TOSA1", "fmsvr", "fmsvr");
    +    dbMetaData = conn.getMetaData();
    +%>
    +<p>
    +Name of JDBC Driver: <%= dbMetaData.getDriverName() %><br>
    +Version: <%= dbMetaData.getDriverVersion() %><br>
    +Major: <%= dbMetaData.getDriverMajorVersion() %><br>
    +Minor: <%= dbMetaData.getDriverMinorVersion() %><br>
    +<p>
    +Database Name: <%= dbMetaData.getDatabaseProductName() %><br>a
    +Version: <%= dbMetaData.getDatabaseProductVersion() %><br>
    +<%
    +  } catch (SQLException e) {
    +    sbError.append(e.toString());
    +  } finally {
    +    if (conn != null) {
    +      try {
    +        conn.close();
    +      } catch (SQLException e) {
    +        sbError.append(e.toString());
    +      }
    +    }
    +  }
    +  if (sbError.length() != 0) {
    +    out.println(sbError.toString());
    +  } else {
    +%>
    +<p>No error</font>
    +<%
    +  }
    +%>
    +

    주어진 정보로 getConnection()을 수행하고 다시 close()를 수행하여 반납하는 과정이며, close()하지 않는 경우 해당 객체는 모두 사용하였음에도 불구하고 메모리상에 남아 차후 메모리 이슈를 발생시킬 수 있습니다.

    톰캣에서는 이런 일련의 Connection을 생성하는 작업을 어플리케이션 대신 생성할 수 있으며 내부적으로 생성하는 개수나 연결이 끊어졌을 때의 재시도, 사용하지 않는 Connection의 강제 반환 등의 설정이 가능합니다.

    다음은 Context 디스트립터 내에 설정하는 Resource에서 정의한 DataSource 예제 입니다.

    <Resource name="jdbc/test"
    +          auth="Container"
    +          type="javax.sql.DataSource"
    +          username="oracle"
    +          password="oracle"
    +          driverClassName="oracle.jdbc.driver.OracleDriver"
    +          url="jdbc:oracle:thin:@address:1521:SID"
    +          removeAbandoned="true"
    +          removeAbandonedTimeout="60"
    +          logAbandoned="true"
    +          maxActive="25"
    +          maxIdle="10"
    +          maxWait="-1"
    +/>
    +

    6.2 DB 연동 예제

    톰캣에서 DB를 연동하기 위해서는 우선 사용할 DB의 벤더사에서 제공하는 JDBC driver를 ClassLoader에서 읽도록 해야 합니다. 우선 JDBC driver를 받고 두가지 방법으로 적용이 가능합니다.

    #JDBC Driver Classpath
    +CLASSPATH=/app/lib/jdbc.jar
    +

    대표적인 DB의 Context 디스크립터에 설정하는 방법은 다음과 같습니다.

    <!-- MySQL - Connector/J -->
    +<Resource name="jdbc/test"
    +          auth="Container"
    +          type="javax.sql.DataSource"
    +          username="javauser"
    +          password="javadude" 
    +          driverClassName="com.mysql.jdbc.Driver" 
    +          url="jdbc:mysql://ipaddress:3306/javatest" 
    +          maxActive="25"
    +          maxIdle="10"
    +          maxWait="-1"
    +/>
    +
    <!-- Oracle - classes12.jar(jdk1.4.2), ojdbc#.jar(5+) -->
    +<Resource name="jdbc/test"
    +          auth="Container"
    +          type="javax.sql.DataSource"
    +          username="oracle"
    +          password="oracle" 
    +          driverClassName="oracle.jdbc.driver.OracleDriver" 
    +          url="jdbc:oracle:thin:@ipaddress:1521:SID" 
    +          maxActive="25"
    +          maxIdle="10"
    +          maxWait="-1"
    + />
    +
    <!-- PostgreSQL - JDBC # -->
    +<Resource name="jdbc/test"
    +          auth="Container" 
    +          type="javax.sql.DataSource" 
    +          username="myuser"
    +          password="mypasswd" 
    +          driverClassName="org.postgresql.Driver" 
    +          url="jdbc:postgresql://ipaddress:5432/mydb" 
    +          maxActive="25"
    +          maxIdle="10"
    +          maxWait="-1"
    +/>
    +

    6.3 DB 연동 설정값

    Resource에 정의되는 항목은 기본적인 url이나 DB접근 계정정도만 있어도 가능하지만 간혹 튜닝이나 문제해결을 위해 추가적인 옵션이 요구되는 경우가 있습니다. 톰캣에서는 다음의 설정값을 제공합니다.

    ATTRIBUTEDESCRIPTOINDEFAULT
    maxActive최대 Connection 값100
    maxIdleIdle Connection 최대 허용치=maxActive
    minIdleIdle Connection 최소 허용치=initialSize
    initialSizeConnection Pool의 최초 생성 개수10
    maxWaitConnection을 얻기위해 대기하는 최대 시간30000(ms)
    removeAbandoned특정시간 동안 사용하지 않는 Connection 반환false
    removeAbandonedTimeoutremoveAbandoned가 동작하는데 소요되는 시간60(s)
    logAbandonedConnection이 remove될 때 log에 기록false
    testOnBorrowgetConnection()이 수행될 때 유효성 테스트false
    validationQuery테스트를 위한 쿼리null

    validationQuery로는 다음과 같이 적용 가능합니다.

    `,25),m={href:"http://tomcat.apache.org/tomcat-10.0-doc/jdbc-pool.html",target:"_blank",rel:"noopener noreferrer"},v=a(`

    6.4 JNDI Lookup

    톰캣에서 생성한 JDBC Connection Pool을 DataSource로서 사용하기 위해서는 JNDI를 Lookup하는 방법을 사용합니다. JNDI를 사용하면 이를 지원하는 다른 프레임워크나 API에서도 톰캣의 자원을 사용할 수 있습니다. mybatis/ibatis의 경우도 JNDI 설정을 할 수 있습니다.

    Context디스크립터로 정의한 DataSource는 어플리케이션의 web.xml에서 정의하고 소스에서는 lookup을 이용하여 사용합니다. 일련의 설정방법의 예는 다음과 같습니다. Context의 정의의 위치에 따라 전체 어플리케이션에 적용될 수도 있고 host단위, 혹은 단일 어플리케이션 내에서만 자원을 생성하게 됩니다.

    <!-- context.xml -->
    +<Resource name="jdbc/test"
    +          auth="Container"
    +          type="javax.sql.DataSource"
    +          username="oracle"
    +          password="oracle" 
    +          driverClassName="oracle.jdbc.driver.OracleDriver" 
    +          url="jdbc:oracle:thin:@ipaddress:1521:SID" 
    + />
    +
    <!-- web.xml -->
    +<web-app>
    +    ...
    +    <resource-ref>
    +        <res-ref-name>jdbc/test</res-ref-name>
    +        <res-type>javax.sql.DataSource</res-type>
    +        <res-auth>Container</res-auth>
    +    </resource-ref>
    +    ...
    +</web-app>
    +
    //Source Code
    +ds = ctx.lookup("java:comp/env/jdbc/test");
    +
    `,6);function b(g,q){const s=e("ExternalLinkIcon");return p(),o("div",null,[i,r,k,d,n("p",null,[n("a",m,[c("http://tomcat.apache.org/tomcat-10.0-doc/jdbc-pool.html"),l(s)])]),v])}const x=t(u,[["render",b],["__file","06-dbconnection.html.vue"]]),f=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/06-dbconnection.html","title":"6. Tomcat Database 연동","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/06-dbconnection.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"6. Tomcat Database 연동"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"6. Tomcat Database 연동\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"6.1 JDBC Connection Pool","slug":"_6-1-jdbc-connection-pool","link":"#_6-1-jdbc-connection-pool","children":[]},{"level":2,"title":"6.2 DB 연동 예제","slug":"_6-2-db-연동-예제","link":"#_6-2-db-연동-예제","children":[]},{"level":2,"title":"6.3 DB 연동 설정값","slug":"_6-3-db-연동-설정값","link":"#_6-3-db-연동-설정값","children":[]},{"level":2,"title":"6.4 JNDI Lookup","slug":"_6-4-jndi-lookup","link":"#_6-4-jndi-lookup","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.56,"words":468},"filePathRelative":"05-Software/Tomcat/tomcat101/06-dbconnection.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n"}');export{x as comp,f as data}; diff --git a/assets/06-notifications.html-CawbIy-9.js b/assets/06-notifications.html-CawbIy-9.js new file mode 100644 index 0000000000..fe6c5e5f19 --- /dev/null +++ b/assets/06-notifications.html-CawbIy-9.js @@ -0,0 +1,13 @@ +import{_ as l,a as c,b as r,c as d}from"./1564465713857-Bb9GicK7.js";import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as m,c as u,b as e,d as i,a as t,w as h,e as n}from"./app-Bzk8Nrll.js";const f={},b=e("h1",{id:"_6-notifications",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_6-notifications"},[e("span",null,"6. Notifications")])],-1),g=e("p",null,"Jenkins빌드의 결과를 받아볼 수 있는 몇가지 방안에 대해 알아봅니다.",-1),_=e("h2",{id:"_6-1-notifications-of-build-state",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_6-1-notifications-of-build-state"},[e("span",null,"6.1 Notifications of build state")])],-1),k={href:"http://catlight.io",target:"_blank",rel:"noopener noreferrer"},v=e("figure",null,[e("img",{src:l,alt:"1564463655933",tabindex:"0",loading:"lazy"}),e("figcaption",null,"1564463655933")],-1),y=e("p",null,"여기서는 Chrome 확장 프로그램을 통한 알림을 받을 수 있는 설정을 설명합니다. 이 과정을 진행하기 위해서는 Chrome 웹브라우저가 필요합니다.",-1),S=n('
  • jenkins를 검색하여 Yet Another Jenkins Notifier를 확인합니다. Chrome에 추가버튼으로 확장 프로그램을 설치합니다.

  • 설치가 완료되면 브라우저 우측 상단에 Jenkins 아이콘이 나타납니다. 클릭합니다.

  • 각 Item(Job)의 url을 입력하여 +버튼을 클릭합니다.

  • 등록된 Item을 확인하고 해당 빌드를 Jenkins 콘솔에서 실행해봅니다. 결과에 대한 알림이 발생하는 것을 확인 할 수 있습니다.

    1564464197547
    1564464197547
  • ',4),J=n('

    6.2 Build state badges for SCM

    Jenkins에서 빌드가 수행된 결과를 SCM에 반영하는 기능도 플러그인을 통해 가능합니다. SCM에서 해당 Jenkins에 접근이 가능해야 하므로 Jenkins는 SCM에서 접근가능한 네트워크 상태여야 합니다.

    Jenkins에 새로운 플러그인을 추가하고 설정합니다.

    이제 기존의 외부 SCM이 연결된 Item을 선택합니다. 여기서는 05-02.UsingSharedLibraries에 설정합니다. 해당 Item을 선택하면 좌측에 Embeddable Build Status 항목이 새로 생긴것을 확인 할 수 있습니다.

    1564464880533
    1564464880533

    해당 항목을 클릭하고 Markdownunprotected의 항목을 복사합니다.

    [![Build Status](http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries)](http://myjenkins.com/job/05-02.UsingSharedLibraries/)
    +
    `,8),j={href:"http://README.md",target:"_blank",rel:"noopener noreferrer"},x=n(`
    # evenOdd
    +[![Build Status](http://myjenkins.com/buildStatus/icon?job=libraries)](http://myjenkins.com/job/libraries/)
    +
    +A Jenkins even/odd playbook from the Jenkins.io documentation
    +
    +Add this as a shared library called evenOdd in your jenkins
    +instance, and then instantiate the pipeline in your project Jenkinsfile
    +
    +This will also use an example of global variabls from the log.groovy
    +definitions
    +
    +

    이같이 반영하면 각 빌드에 대한 결과를 SCM에 동적으로 상태를 반영 할 수 있습니다.

    1564465713857
    1564465713857

    이같은 알림 설정은 코드의 빌드가 얼마나 잘 수행되는지 이해하고 추적할 수 있도록 도와줍니다.

    ',4);function w(N,C){const o=a("ExternalLinkIcon"),s=a("RouteLink");return m(),u("div",null,[b,g,_,e("p",null,[i("Jenkins에서는 플러그인이나 외부 툴에 의해 빌드에 대한 결과를 받아 볼 수 있습니다. 대표적으로는 Jenkins의 슬랙 플러그인을 사용하여 슬랙으로 빌드에 결과를 받아보거나, "),e("a",k,[i("catlight.io"),t(o)]),i(" 에서 데스크탑용 어플리케이션에 연동하는 방법도 있습니다.")]),v,y,e("ol",null,[e("li",null,[e("p",null,[t(s,{to:"/05-Software/Jenkins/pipeline101/chrome:/apps/"},{default:h(()=>[i("chrome://apps/")]),_:1}),i("에 접속하여 앱스토어를 클릭합니다.")])]),S]),J,e("p",null,[i("복사한 형식을 GitHub의 evenOdd repository의 "),e("a",j,[i("README.md"),t(o)]),i(" 파일 상단에 위치 시킵니다.")]),x])}const E=p(f,[["render",w],["__file","06-notifications.html.vue"]]),L=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/06-notifications.html","title":"6. Notifications","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/06-notifications.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"6. Notifications"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"6. Notifications"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"6. Notifications\\",\\"image\\":[\\"http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries\\",\\"http://myjenkins.com/buildStatus/icon?job=libraries\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"6.1 Notifications of build state","slug":"_6-1-notifications-of-build-state","link":"#_6-1-notifications-of-build-state","children":[]},{"level":2,"title":"6.2 Build state badges for SCM","slug":"_6-2-build-state-badges-for-scm","link":"#_6-2-build-state-badges-for-scm","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.62,"words":187},"filePathRelative":"05-Software/Jenkins/pipeline101/06-notifications.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    Jenkins빌드의 결과를 받아볼 수 있는 몇가지 방안에 대해 알아봅니다.

    \\n

    6.1 Notifications of build state

    \\n

    Jenkins에서는 플러그인이나 외부 툴에 의해 빌드에 대한 결과를 받아 볼 수 있습니다. 대표적으로는 Jenkins의 슬랙 플러그인을 사용하여 슬랙으로 빌드에 결과를 받아보거나, catlight.io 에서 데스크탑용 어플리케이션에 연동하는 방법도 있습니다.

    "}');export{E as comp,L as data}; diff --git a/assets/06-terraform-cloud.html-BOv2Y4DV.js b/assets/06-terraform-cloud.html-BOv2Y4DV.js new file mode 100644 index 0000000000..6cdcfb80b0 --- /dev/null +++ b/assets/06-terraform-cloud.html-BOv2Y4DV.js @@ -0,0 +1 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as t,c as a,b as l,a as i,w as n,e as m,d as c}from"./app-Bzk8Nrll.js";const p="/assets/automate-the-provisioning-lifecycle-h9sTQYF2.png",d="/assets/tfc_tfe_logo-BObqu3et.png",s={},f=m('

    06. Terraform Cloud

    Terraform Cloud

    Terraform Cloud는 Terraform을 사용하여 코드로 인프라를 작성하고 구축하기위한 최고의 워크 플로를 제공하는 무료 로 시작하는 SaaS 애플리케이션입니다.


    Terraform Cloud는 Remote State 관리, API 기반 실행, 정책 관리 등과 같은 기능을 제공하는 호스팅 된 애플리케이션입니다. 많은 사용자가 클라우드 기반 SaaS 솔루션을 선호하는 이유 중 한가지는 인프라를 유지하여 실행하는 것이 부담될 때 입니다.

    Terraform Enterprise는 동일한 애플리케이션이지만 클라우드 환경이나 데이터 센터에서 실행됩니다. 일부 사용자는 Terraform Enterprise 애플리케이션에 대한 더 많은 제어가 필요하거나 회사 방화벽 뒤의 제한된 네트워크에서 실행하려고합니다.

    이 두 제품의 기능 목록은 거의 동일합니다. 다음 실습에서는 Terraform Cloud 계정을 사용할 것입니다.

    Terraform Remote State

    기본적으로 Terraform은 랩톱 또는 워크스테이션의 Workspace 디렉토리에 State 파일을 저장합니다. 이것은 개발 및 테스트에는 괜찮지만 프로덕션 환경에서는 상태 파일을 안전하게 보호하고 저장해야합니다.

    Terraform에는 상태 파일을 원격으로 저장하고 보호하는 옵션이 있습니다. Terraform Cloud 계정은 이제 오픈 소스 사용자에게도 무제한 상태 파일 스토리지를 제공합니다.

    모든 상태 파일은 암호화되어 (HashiCorp Vault 사용) Terraform Cloud 계정에 안전하게 저장됩니다. 상태 파일을 다시 잃어 버리거나 삭제하는 것에 대해 걱정할 필요가 없습니다.

    Terraform Cloud Execution Modes


    실습을 위해 다음장으로 이동하세요.

    ',18);function u(h,T){const r=o("RouteLink");return t(),a("div",null,[f,l("p",null,[i(r,{to:"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html"},{default:n(()=>[c("💻 Lab - Terraform Cloud 연결")]),_:1})])])}const C=e(s,[["render",u],["__file","06-terraform-cloud.html.vue"]]),k=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html","title":"06. Terraform Cloud","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"06. Terraform Cloud"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"06. Terraform Cloud\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Terraform Cloud","slug":"terraform-cloud","link":"#terraform-cloud","children":[]},{"level":2,"title":"","slug":"","link":"#","children":[]},{"level":2,"title":"Terraform Remote State","slug":"terraform-remote-state","link":"#terraform-remote-state","children":[]},{"level":2,"title":"Terraform Cloud Execution Modes","slug":"terraform-cloud-execution-modes","link":"#terraform-cloud-execution-modes","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.38,"words":114},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    Terraform Cloud

    \\n

    Terraform Cloud는 Terraform을 사용하여 코드로 인프라를 작성하고 구축하기위한 최고의 워크 플로를 제공하는 무료 로 시작하는 SaaS 애플리케이션입니다.

    \\n
    \\n"}');export{C as comp,k as data}; diff --git a/assets/06-z-lab_terraform_cloud.html-BYSe2KEx.js b/assets/06-z-lab_terraform_cloud.html-BYSe2KEx.js new file mode 100644 index 0000000000..2d827e7c59 --- /dev/null +++ b/assets/06-z-lab_terraform_cloud.html-BYSe2KEx.js @@ -0,0 +1,35 @@ +import{_ as d}from"./lab1-02-By4gwc7V.js";import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as u,c as m,b as e,d as a,a as s,w as o,e as n}from"./app-Bzk8Nrll.js";const f="/assets/lab6-01-BR2XuCJW.png",h="/assets/lab6-02-BlWl7cfs.png",g="/assets/lab6-03-aJ3zR-en.png",b="/assets/lab6-04-DFO_B_SW.png",k="/assets/lab6-05-DW5jvJ6v.png",_="/assets/lab6-06-BGkbBT-2.png",v="/assets/intro-to-terraform-on-ncp-2-B4FWEybN.png",y={},T=n('

    💻 Lab - Terraform Cloud 연결

    편집기에서 열기


    ☁️ Terraform Configuration

    @slidestart blood

    Terraform Cloud

    Remote State 저장소는 모든 사용자에게 무료입니다.

    @slideend

    Terraform Cloud 계정

    Terraform Cloud는 다른 SaaS 서비스와 같이 개인을 위한 무료 플랜이 준비되어있습니다.

    아직 계정이 없는 경우 계성을 생성하고 다음 실습을 진행합니다.

    ',13),C={href:"https://app.terraform.io/signup/account",target:"_blank",rel:"noopener noreferrer"},x=e("li",null,[a("필요한 정보를 입력하고 확인하여 신규 계정을 생성합니다. "),e("button",{style:{"border-color":"#3322de","background-color":"#5c4ee5",color:"#fff","font-size":"1rem"}},"Cretea account")],-1),q=e("li",null,"가입한 이메일로 계정 생성 확인 메시지가 도착합니다. 링크를 확인하면 Terraform Cloud를 사용할 준비가 끝났습니다.",-1),w=n('

    💻 Terraform Cloud를 설정합니다.

    1. Terraform Cloud에 로그인하면 YOURNAME-training 이라는 새 조직을 만듭니다. YOURNAME을 자신의 이름이나 다른 텍스트로 바꾸십시오.

    2. 다음으로 Workspace를 생성하라는 메시지가 표시됩니다. CLI 기반 워크플로 패널을 클릭하여 VCS 통합 단계를 건너뛸 수 있습니다.

    3. 작업 공간의 이름을 hashicat-ncp 로 지정 하고 Create workspace를 클릭하여 새로운 Workspace를 생성합니다.

    4. 터미널에서 terraform version 을 실행하여 버전을 확인합니다.

    5. Terraform Cloud 상에 생성한 hashicat-ncpSettings > General 로 이동하여 Terraform Version을 동일한 버전으로 구성합니다. 그리고 Execution Mode를 Local로 설정합니다.

    ',2),S=e("figure",null,[e("img",{src:h,alt:"",tabindex:"0",loading:"lazy"}),e("figcaption")],-1),R=e("figure",null,[e("img",{src:g,alt:"",tabindex:"0",loading:"lazy"}),e("figcaption")],-1),z=n(`
    1. Settings 페이지 하단에 버튼을 클릭하여 저장합니다.

    🎛️ Configure Remote State

    @slidestart blood

    "Local" 실행 모드는 Terraform의 구성과 변수는 모두 워크스테이션에 남아있습니다.


    "Remote" 실행 모드로 전환하게 되면 Terraform Cloud 환경의 컨테이너 환경에서 실행됩니다.


    "Remote" 실행 모드로 전환 시, 변수구성은 Terraform Cloud 환경에 설정해야 합니다.

    @slideend

    이번 실습에서는 Terraform Cloud를 Remote State Backend로 구성하여 기존 State 파일을 Terraform Cloud 환경으로 마이그레이션 합니다.

    💻 Remote Backend 구성하기

    Workspace 디렉토리에 (main.tf와 같은 위치) 아래와 같은 내용으로 remote_backend.tf 파일을 생성합니다.

    # remote_backend.tf
    +terraform {
    +  backend "remote" {
    +    hostname = "app.terraform.io"
    +    organization = "YOURORGANIZATION"
    +    workspaces {
    +      name = "hashicat-ncp"
    +    }
    +  }
    +}
    +

    YOURORGANIZATION을 생성한 Organization 이름으로 수정합니다.

    이후 터미널에서 terraform login 을 입력합니다. 로컬 환경에 Terraform Cloud와 API 인증을 위한 Token을 생성하는 과정입니다. yes를 입력하면 Terraform Cloud의 토큰 생성화면이 열립니다.

    `,16),A=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{bash:"",class:"language-bash"},[e("code",null,[a(`$ terraform login +Terraform will request an API token `),e("span",{class:"token keyword"},"for"),a(` app.terraform.io using your browser. +`),e("span",{class:"token punctuation"},".."),a(`. +Do you want to proceed? + Only `),e("span",{class:"token string"},"'yes'"),a(` will be accepted to confirm. + + Enter a value: +`)])]),e("div",{class:"highlight-lines"},[e("div",{class:"highlight-line"}," "),e("br"),e("br"),e("br"),e("br"),e("br"),e("br")])],-1),N=e("p",null,[e("code",null,"Create API token"),a(" 화면이 나오면 Description에 적절한 값(예: ncp workshop)을 입력한 후 "),e("button",{style:{"border-color":"#3322de","background-color":"#5c4ee5",color:"#fff","font-size":"1rem"}},"Create API token"),a(" 버튼을 클릭하여 새로운 Token을 생성합니다.")],-1),I=e("figure",null,[e("img",{src:b,alt:"",tabindex:"0",loading:"lazy"}),e("figcaption")],-1),O=e("figure",null,[e("img",{src:k,alt:"",tabindex:"0",loading:"lazy"}),e("figcaption")],-1),P=n(`

    생성된 Token을 복사하여 앞서 터미널에 새로운 입력란인 Enter a value: 에 붙여넣고 (엔터)를 입력합니다. (입력된 값은 보이지 않습니다.)

    ...
    +Generate a token using your browser, and copy-paste it into this prompt.
    +
    +Terraform will store the token in plain text in the following file
    +for use by subsequent commands:
    +    /Users/yourname/.terraform.d/credentials.tfrc.json
    +
    +Token for app.terraform.io:
    +  Enter a value: ****************************************** 
    +

    해당 토큰은 터미널에 표기된 credentials.tfrc.json 파일에 저장됩니다.

    터미널에서 terraform init을 실행합니다.

    State를 Terraform Cloud로 마이그레이션하라는 메시지가 표시되면 "yes"를 입력합니다.

    backend가 remote로 구성됨이 성공함을 확인합니다.

    `,6),E=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{bash:"",class:"language-bash"},[e("code",null,[a(`$ terraform init +`),e("span",{class:"token punctuation"},".."),a(`. +Initializing the backend`),e("span",{class:"token punctuation"},".."),a(`. + +Successfully configured the backend `),e("span",{class:"token string"},'"remote"'),e("span",{class:"token operator"},"!"),a(` Terraform will automatically +use this backend unless the backend configuration changes. +`),e("span",{class:"token punctuation"},".."),a(`. +`)])]),e("div",{class:"highlight-lines"},[e("div",{class:"highlight-line"}," "),e("br"),e("br"),e("br"),e("br"),e("br"),e("br")])],-1),B=n('

    이제 상태가 Terraform Cloud에 안전하게 저장됩니다. TFC UI에서 작업 영역의 "State" 탭에서 이를 확인할 수 있습니다.

    변수들을 변경하면서 terraform apply -auto-approve를 실행하고, 상태 파일이 리소스가 변경될 때마다 변경되는 것을 지켜보십시오. Terraform Cloud UI를 사용하여 이전 상태 파일을 탐색할 수 있습니다.


    🔥 Terraform Destroy

    @slidestart blood

    Terraform은 인프라를 구축하는 것만큼 쉽게 인프라를 파괴할 수 있습니다.


    "terraform destroy"는 주의하여 사용하세요.

    @slideend

    💻 리소스 삭제하기

    다음 명령을 실행하여 인프라를 삭제하세요.

    terraform destroy
    +

    인프라를 삭제한다는 메시지가 표시되면 "yes"를 입력해야 합니다. 중요한 리소스가 실수로 삭제되는 것을 방지하기 위한 안전 기능입니다.

    확인 버튼을 클릭하기 전에 리소스 삭제 작업이 완전히 끝날 때까지 기다리십시오.


    Intro
    Intro
    ',17);function L(W,V){const c=i("ExternalLinkIcon"),l=i("Tabs");return u(),m("div",null,[T,e("ol",null,[e("li",null,[a("계정 생성을 위해 "),e("a",C,[a("https://app.terraform.io/signup/account"),s(c)]),a("로 접속합니다.")]),x,q]),w,s(l,{id:"93",data:[{id:"Setting 위치"},{id:"Version과 Execution Mode"}]},{title0:o(({value:r,isActive:t})=>[a("Setting 위치")]),title1:o(({value:r,isActive:t})=>[a("Version과 Execution Mode")]),tab0:o(({value:r,isActive:t})=>[S]),tab1:o(({value:r,isActive:t})=>[R]),_:1}),z,A,N,s(l,{id:"153",data:[{id:"Token 생성하기"},{id:"Token 생성 후 복사"}]},{title0:o(({value:r,isActive:t})=>[a("Token 생성하기")]),title1:o(({value:r,isActive:t})=>[a("Token 생성 후 복사")]),tab0:o(({value:r,isActive:t})=>[I]),tab1:o(({value:r,isActive:t})=>[O]),_:1}),P,E,B])}const G=p(y,[["render",L],["__file","06-z-lab_terraform_cloud.html.vue"]]),M=JSON.parse('{"path":"/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html","title":"💻 Lab - Terraform Cloud 연결","lang":"ko-KR","frontmatter":{"description":"Naver Cloud Platform에서의 Terraform 실습","tag":["ncloud","ncp","terraform","workshop"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"💻 Lab - Terraform Cloud 연결"}],["meta",{"property":"og:description","content":"Naver Cloud Platform에서의 Terraform 실습"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:28:39.000Z"}],["meta",{"property":"article:tag","content":"ncloud"}],["meta",{"property":"article:tag","content":"ncp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"workshop"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:28:39.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"💻 Lab - Terraform Cloud 연결\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-25T07:28:39.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":3,"title":"편집기에서 열기","slug":"편집기에서-열기","link":"#편집기에서-열기","children":[]},{"level":2,"title":"☁️ Terraform Configuration","slug":"☁️-terraform-configuration","link":"#☁️-terraform-configuration","children":[{"level":3,"title":"Terraform Cloud","slug":"terraform-cloud","link":"#terraform-cloud","children":[]},{"level":3,"title":"Remote State 저장소는 모든 사용자에게 무료입니다.","slug":"remote-state-저장소는-모든-사용자에게-무료입니다","link":"#remote-state-저장소는-모든-사용자에게-무료입니다","children":[]},{"level":3,"title":"Terraform Cloud 계정","slug":"terraform-cloud-계정","link":"#terraform-cloud-계정","children":[]}]},{"level":2,"title":"🎛️ Configure Remote State","slug":"🎛️-configure-remote-state","link":"#🎛️-configure-remote-state","children":[{"level":3,"title":"\\"Local\\" 실행 모드는 Terraform의 구성과 변수는 모두 워크스테이션에 남아있습니다.","slug":"local-실행-모드는-terraform의-구성과-변수는-모두-워크스테이션에-남아있습니다","link":"#local-실행-모드는-terraform의-구성과-변수는-모두-워크스테이션에-남아있습니다","children":[]},{"level":3,"title":"\\"Remote\\" 실행 모드로 전환하게 되면 Terraform Cloud 환경의 컨테이너 환경에서 실행됩니다.","slug":"remote-실행-모드로-전환하게-되면-terraform-cloud-환경의-컨테이너-환경에서-실행됩니다","link":"#remote-실행-모드로-전환하게-되면-terraform-cloud-환경의-컨테이너-환경에서-실행됩니다","children":[]},{"level":3,"title":"\\"Remote\\" 실행 모드로 전환 시, 변수구성은 Terraform Cloud 환경에 설정해야 합니다.","slug":"remote-실행-모드로-전환-시-변수구성은-terraform-cloud-환경에-설정해야-합니다","link":"#remote-실행-모드로-전환-시-변수구성은-terraform-cloud-환경에-설정해야-합니다","children":[]}]},{"level":2,"title":"🔥 Terraform Destroy","slug":"🔥-terraform-destroy","link":"#🔥-terraform-destroy","children":[{"level":3,"title":"Terraform은 인프라를 구축하는 것만큼 쉽게 인프라를 파괴할 수 있습니다.","slug":"terraform은-인프라를-구축하는-것만큼-쉽게-인프라를-파괴할-수-있습니다","link":"#terraform은-인프라를-구축하는-것만큼-쉽게-인프라를-파괴할-수-있습니다","children":[]},{"level":3,"title":"\\"terraform destroy\\"는 주의하여 사용하세요.","slug":"terraform-destroy-는-주의하여-사용하세요","link":"#terraform-destroy-는-주의하여-사용하세요","children":[]}]}],"git":{"createdTime":1695042774000,"updatedTime":1698218919000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":1.32,"words":397},"filePathRelative":"03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    편집기에서 열기

    \\n
    \\n\\n
    \\n

    ☁️ Terraform Configuration

    \\n

    @slidestart blood

    \\n

    Terraform Cloud

    \\n

    Remote State 저장소는 모든 사용자에게 무료입니다.

    "}');export{G as comp,M as data}; diff --git a/assets/07-host.html-C9EMg1b9.js b/assets/07-host.html-C9EMg1b9.js new file mode 100644 index 0000000000..a03f003acd --- /dev/null +++ b/assets/07-host.html-C9EMg1b9.js @@ -0,0 +1,19 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as c,c as l,b as a,d as t,a as s,e}from"./app-Bzk8Nrll.js";const u={},i=a("h1",{id:"_7-tomcat-host",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_7-tomcat-host"},[a("span",null,"7. Tomcat HOST")])],-1),r=a("ul",null,[a("li",null,"호스트 구성"),a("li",null,"호스트 특징"),a("li",null,"host manager")],-1),k=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/hnLzh_WE8R8",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),m=e(`

    7.1 호스트 구성

    톰캣에 정의된 바로는 Host로 정의되나 일반적인 기능으로 표현한다면 가상 호스트(Virtual Host)와 같은 기능 입니다. 특정 host 명, 즉 http url로 서비스를 분기하는 역할을 합니다. server.xml 기본으로 설정되어있는 localhost인 호스트의 내용은 다음과 같습니다.

    <Engine name="Catalina" defaultHost="localhost">
    +    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
    +    ...
    +    </Host>
    +</Engine>
    +

    설정된 내용을 분석해본다면 Catalina라는 톰캣의 엔진에서 처리하는 기본 호스트는 localhost이고, localhostwebapps 디렉토리를 기본 배치 디렉토리고 갖는다는 내용입니다. 기본 호스트로 지정된 호스트는 이외에 설정된 호스트 조건에 맞지 않은 모든 요청을 처리하게 됩니다. 이렇게 생성된 localhost$CATALINA_HOME/conf/[ENGINENAME]/[HOSTNAME] 과 같은 경로에 호스트만의 설정 값을 갖게 됩니다.

    별도의 호스트 추가는 'Engine' 디스크립터 하위에 'Host' 디스크립터로 정의합니다. 'myhost'라는 호스트는 다음과 같이 추가할 수 있습니다.

    <Engine name="Catalina" defaultHost="localhost">
    +    <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
    +    ...
    +    </Host>
    +    <Host name="myhost" appBase="webapps_myhost" unpackWARs="true" autoDeploy="true">
    +    ...
    +    </Host>
    +</Engine>
    +

    새롭게 추가되는 호스트는 기본 배치경로를 다르게 설정합니다. 때문에 동일한 컨텍스트의 요청이라도 어떤 호스트가 처리하는가에 따라 다른 어플리케이션의 서비스를 이용하게 됩니다. 설정 후 톰캣 프로세스를 재기동하면 호스트 설정 디렉토리가 생성됨을 확인 할 수 있습니다.

    7.2 호스트 특징

    호스트의 기능은 주로 웹서버에서 많이 사용되는 기능입니다. 특정 url로 호출되는 요청을 각 요청 전달 목적지에 맞게 분배하는 역할을 수행하지요. 호스트는 톰캣에도 구현되어 있으며 이를 통해 하나의 톰캣 내에서 같은 컨텍스트를 갖는 요청의 처리가 가능합니다.

    hostRoot
    hostRoot

    톰캣의 프로세스를 서비스 마다 생성하지 못하는 상황(e.g. 자원의 한계와 같은) 또는 서비스에서 소모하는 자원이 크지 않아 추가적인 프로세스를 기동하지 않아도 되는 상황 등 호스트의 기능을 활용 할수 있습니다.

    7.3 host manager

    호스트를 관리하기 위해 톰캣에서는 'host manager'를 제공합니다. 앞서 배치에서 보았던 'Manage APP'와 같이 호스트를 쉽게 구성할 수 있도록 UI환경을 제공합니다. 이를 통해 설정파일에 직접 수정하기보다 제공되는 UI로 정확하고 쉽게 구성하고 관리할 수 있도록 합니다.

    `,14),d={href:"http://ip",target:"_blank",rel:"noopener noreferrer"},g={href:"http://localhost:8080",target:"_blank",rel:"noopener noreferrer"},h=a("li",null,[t("ROOT 어플리케이션의 호출 확인 후 좌/우측의 "),a("code",null,"Host Manager"),t(" 클릭")],-1),q=a("li",null,"톰캣 유저의 설정이 되어있지 않으므로 로그인 창에서 '취소'버튼 클릭",-1),_=a("li",null,[a("code",null,"$CATALINA_HOME/conf/tomcat-user.xml"),t(" 에 설정하는 방법을 에러페이지에서 확인")],-1),f=a("li",null,[t("tomcat-user.xml에 다음과 같이 설정 추가 (e.g. "),a("code",null,"user/passwd"),t("를 "),a("code",null,"admin/admin"),t("으로 설정)")],-1),v=e(`
    <tomcat-users>
    +  <role rolename="manager-gui"/>
    +  <role rolename="admin-gui"/>
    +  <user username="admin" password="admin" roles="manager-gui,admin-gui"/>
    +</tomcat-users>\`\`\`
    +
    hostManager
    hostManager

    host manager에서는 기본 호스트 외에 추가적인 호스트에 대한 추가를 할 수 있도록 Add Virtual Host를 사용할 수 있습니다. Host 디스크립터에서 정의되는 내용을 각 항목에 맞게 입력 할 수 있고 이렇게 추가된 호스트는 Host name 테이블의 commands에서 개별적으로 시작과 정지가 가능합니다. 호스트가 정지되어 비활성화 된 상태에서는 해당 호스트의 요청 url에 맞게 들어오더라도 기본 호스트가 처리하게 됩니다. host manager는 웹페이지를 통한 호스트의 추가/삭제/컨트롤이 가능하므로 외부, 또는 관리자가 아닌 사용자가 접근하지 못하도록 해야 합니다.

    `,4);function x(y,w){const n=p("ExternalLinkIcon");return c(),l("div",null,[i,r,k,m,a("ul",null,[a("li",null,[a("a",d,[t("http://ip"),s(n)]),t(":port 로 기본 톰캣 http 요청 (로컬에서 기본 설정으로 실행한 경우 "),a("a",g,[t("http://localhost:8080"),s(n)]),t(")")]),h,q,_,f]),v])}const H=o(u,[["render",x],["__file","07-host.html.vue"]]),A=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/07-host.html","title":"7. Tomcat HOST","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/07-host.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"7. Tomcat HOST"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/hostRoot.jpg?token=ADUAZXPZNGFNIR7Z4S5S6MK67EUR6"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"7. Tomcat HOST"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"7. Tomcat HOST\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/hostRoot.jpg?token=ADUAZXPZNGFNIR7Z4S5S6MK67EUR6\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/hostManager.jpg?token=ADUAZXORX3KILKKDCM4YLOK67EUSE\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"7.1 호스트 구성","slug":"_7-1-호스트-구성","link":"#_7-1-호스트-구성","children":[]},{"level":2,"title":"7.2 호스트 특징","slug":"_7-2-호스트-특징","link":"#_7-2-호스트-특징","children":[]},{"level":2,"title":"7.3 host manager","slug":"_7-3-host-manager","link":"#_7-3-host-manager","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.68,"words":205},"filePathRelative":"05-Software/Tomcat/tomcat101/07-host.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n\\n
    \\n

    7.1 호스트 구성

    \\n

    톰캣에 정의된 바로는 Host로 정의되나 일반적인 기능으로 표현한다면 가상 호스트(Virtual Host)와 같은 기능 입니다. 특정 host 명, 즉 http url로 서비스를 분기하는 역할을 합니다. server.xml 기본으로 설정되어있는 localhost인 호스트의 내용은 다음과 같습니다.

    "}');export{H as comp,A as data}; diff --git a/assets/07-testing.html-DItX3hY7.js b/assets/07-testing.html-DItX3hY7.js new file mode 100644 index 0000000000..120f47c61f --- /dev/null +++ b/assets/07-testing.html-DItX3hY7.js @@ -0,0 +1,69 @@ +import{_ as n,a as s}from"./1564471729123-DnEyDLm_.js";import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as t,e as i}from"./app-Bzk8Nrll.js";const p={},o=i(`

    7. Testing

    7.1 Code coverage tests and reports

    테스트 Pipeline 구성시 테스트 과정을 지정할 수 있습니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-01.CodeCoverageTestsAndReports)

    설정은 다음과 같이 수행합니다.

    1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

      pipeline {
      +    agent any
      +    stages {
      +        stage('Build') {
      +            steps {
      +                sh '''
      +                  echo This > app.sh
      +                  echo That >> app.sh
      +                '''
      +            }
      +        }
      +        stage('Test') {
      +            steps {
      +                sh '''
      +                  grep This app.sh >> \${BUILD_ID}.cov
      +                  grep That app.sh >> \${BUILD_ID}.cov
      +                '''
      +            }
      +        }
      +        stage('Coverage'){
      +            steps {
      +                sh '''
      +                  app_lines=\`cat app.sh | wc -l\`
      +                  cov_lines=\`cat \${BUILD_ID}.cov | wc -l\`
      +                  echo The app has \`expr $app_lines - $cov_lines\` lines uncovered > \${BUILD_ID}.rpt
      +                  cat \${BUILD_ID}.rpt
      +                '''
      +                archiveArtifacts "\${env.BUILD_ID}.rpt"
      +            }
      +        }
      +    }
      +}
      +
    2. 빌드가 완료되면 해당 Job화면을 리로드 합니다. Pipeline에 archiveArtifacts가 추가되었으므로 해당 Job에서 이를 관리합니다.
      1564470826126

    3. 해당 아카이브에는 코드 검증 후의 결과가 저장 됩니다.

    7.2 Using test results to stop the build

    테스트 결과에 따라 빌드를 중지시키는 Pipeline 스크립트를 확인합니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-02.UsingTestResultsToStopTheBuild)

    설정은 다음과 같이 수행합니다.

    1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

      pipeline {
      +    agent any
      +    stages {
      +        stage('Build') {
      +            steps {
      +                sh '''
      +                  echo This > app.sh
      +                  echo That >> app.sh
      +                  echo The Other >> app.sh
      +                '''
      +            }
      +        }
      +        stage('Test') {
      +            steps {
      +                sh '''
      +                  for n in This That Those
      +                   do if grep $n app.sh >> \${BUILD_ID}.cov
      +                    then exit 1
      +                   fi
      +                  done
      +                '''
      +            }
      +        }
      +        stage('Coverage'){
      +            steps {
      +                sh '''
      +                  app_lines=\`cat app.sh | wc -l\`
      +                  cov_lines=\`cat \${BUILD_ID}.cov | wc -l\`
      +                  echo The app has \`expr $app_lines - $cov_lines\` lines uncovered > \${BUILD_ID}.rpt
      +                  cat \${BUILD_ID}.rpt
      +                '''
      +                archiveArtifacts "\${env.BUILD_ID}.rpt"
      +            }
      +        }
      +    }
      +}
      +
    2. 저장을 하고 빌드를 수행하면, Pipeline 스크립트 상 Test Stage에서 조건 만족 시 exit 1를 수행하므로 빌드는 중간에 멈추게 됩니다.

      1564471729123
      1564471729123
    ',9),c=[o];function l(u,r){return e(),t("div",null,c)}const m=a(p,[["render",l],["__file","07-testing.html.vue"]]),k=JSON.parse(`{"path":"/05-Software/Jenkins/pipeline101/07-testing.html","title":"7. Testing","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/07-testing.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"7. Testing"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"7. Testing\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"7.1 Code coverage tests and reports","slug":"_7-1-code-coverage-tests-and-reports","link":"#_7-1-code-coverage-tests-and-reports","children":[]},{"level":2,"title":"7.2 Using test results to stop the build","slug":"_7-2-using-test-results-to-stop-the-build","link":"#_7-2-using-test-results-to-stop-the-build","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.68,"words":204},"filePathRelative":"05-Software/Jenkins/pipeline101/07-testing.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    7.1 Code coverage tests and reports

    \\n

    테스트 Pipeline 구성시 테스트 과정을 지정할 수 있습니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-01.CodeCoverageTestsAndReports)

    \\n

    설정은 다음과 같이 수행합니다.

    \\n
      \\n
    1. \\n

      Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

      \\n
      pipeline {\\n    agent any\\n    stages {\\n        stage('Build') {\\n            steps {\\n                sh '''\\n                  echo This > app.sh\\n                  echo That >> app.sh\\n                '''\\n            }\\n        }\\n        stage('Test') {\\n            steps {\\n                sh '''\\n                  grep This app.sh >> \${BUILD_ID}.cov\\n                  grep That app.sh >> \${BUILD_ID}.cov\\n                '''\\n            }\\n        }\\n        stage('Coverage'){\\n            steps {\\n                sh '''\\n                  app_lines=\`cat app.sh | wc -l\`\\n                  cov_lines=\`cat \${BUILD_ID}.cov | wc -l\`\\n                  echo The app has \`expr $app_lines - $cov_lines\` lines uncovered > \${BUILD_ID}.rpt\\n                  cat \${BUILD_ID}.rpt\\n                '''\\n                archiveArtifacts \\"\${env.BUILD_ID}.rpt\\"\\n            }\\n        }\\n    }\\n}\\n
    2. \\n
    3. \\n

      빌드가 완료되면 해당 Job화면을 리로드 합니다. Pipeline에 archiveArtifacts가 추가되었으므로 해당 Job에서 이를 관리합니다.
      \\n

      \\n
    4. \\n
    5. \\n

      해당 아카이브에는 코드 검증 후의 결과가 저장 됩니다.

      \\n
    6. \\n
    "}`);export{m as comp,k as data}; diff --git a/assets/08-restapi.html-E0A3Day-.js b/assets/08-restapi.html-E0A3Day-.js new file mode 100644 index 0000000000..e7077fa3dc --- /dev/null +++ b/assets/08-restapi.html-E0A3Day-.js @@ -0,0 +1,34 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-Bzk8Nrll.js";const t={},i=e(`

    8. REST API

    Jenkins는 외부 서비스와의 연동이나 정보 조회를 위한 API를 제공합니다.

    8.1 Triggering builds via the REST API

    Jenkins REST API 테스트를 위해서는 Jenkins에 인증 가능한 Token을 취득하고 curl이나 Postman 같은 도구를 사용하여 확인 가능 합니다. 우선 Token을 얻는 방법은 다음과 같습니다.

    1. Jenkins에 로그인 합니다.

    2. 우측 상단의 로그인 아이디에 마우스를 호버하면 드롭박스 버튼이 나타납니다. 설정을 클릭합니다.

    3. API Token에서 Current token을 확인합니다. 등록된 Token이 없는 경우 다음과 같이 신규 Token을 발급 받습니다.

    4. 이름과 Token을 사용하여 다음과 같이 curl로 접속하면 Jenkins-Crumb 프롬프트가 나타납니다.

      $ curl --user "admin:TOKEN" 'http://myjenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
      +
      +Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605
      +
    5. Crumb를 확인했으면 다시 헤더 값에 Jenkins-Crumb:를 추가하여 02-04.MultiStep Job을 빌드하기 위해 다음과 같이 요청합니다.

      $ curl -X POST http://myjenkins.com/job/02-04.MultiStep/build --user gyulee:11479bdec9cada082d189938a3946348be --data-urlencode json='' -H "Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605"
      +

    API로 호출된 빌드가 수행되어 빌드 번호가 증가하는 것을 확인합니다.

    8.2 Retriving build status via the REST API

    빌드에 대한 결과를 REST API를 통해 요청하는 방법을 알아봅니다. 앞서 진행시의 Token값이 필요합니다. Json 형태로 출력되기 때문에 정렬을 위해 python이 설치 되어있다면 mjson.tool을 사용하여 보기 좋은 형태로 출력 가능합니다.

    # Python이 설치되어있지 않은 경우
    +$ yum -y install python2
    +
    +# Jenkins에 REST API로 마지막 빌드 상태 요청
    +$ curl  -s --user gyulee:11479bdec9cada082d189938a3946348be http://myjenkins.com/job/02-04.MultiStep/lastBuild/api/json | python2 -mjson.tool
    +
    +{
    +    "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
    +    "actions": [
    +        {
    +            "_class": "hudson.model.CauseAction",
    +            "causes": [
    +                {
    +                    "_class": "hudson.model.Cause$UserIdCause",
    +                    "shortDescription": "Started by user GyuSeok.Lee",
    +                    "userId": "gyulee",
    +                    "userName": "GyuSeok.Lee"
    +                }
    +            ]
    +        },
    +        {},
    +        {
    +            "_class": "hudson.plugins.git.util.BuildData",
    +            "buildsByBranchName": {
    +                "master": {
    +                    "_class": "hudson.plugins.git.util.Build",
    +                    "buildNumber": 5,
    +                    "buildResult": null,
    +...
    +
    `,9),l=[i];function o(p,c){return s(),a("div",null,l)}const d=n(t,[["render",o],["__file","08-restapi.html.vue"]]),m=JSON.parse(`{"path":"/05-Software/Jenkins/pipeline101/08-restapi.html","title":"8. REST API","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/08-restapi.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"8. REST API"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"8. REST API\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"8.1 Triggering builds via the REST API","slug":"_8-1-triggering-builds-via-the-rest-api","link":"#_8-1-triggering-builds-via-the-rest-api","children":[]},{"level":2,"title":"8.2 Retriving build status via the REST API","slug":"_8-2-retriving-build-status-via-the-rest-api","link":"#_8-2-retriving-build-status-via-the-rest-api","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.55,"words":166},"filePathRelative":"05-Software/Jenkins/pipeline101/08-restapi.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    Jenkins는 외부 서비스와의 연동이나 정보 조회를 위한 API를 제공합니다.

    \\n

    8.1 Triggering builds via the REST API

    \\n

    Jenkins REST API 테스트를 위해서는 Jenkins에 인증 가능한 Token을 취득하고 curl이나 Postman 같은 도구를 사용하여 확인 가능 합니다. 우선 Token을 얻는 방법은 다음과 같습니다.

    \\n
      \\n
    1. \\n

      Jenkins에 로그인 합니다.

      \\n
    2. \\n
    3. \\n

      우측 상단의 로그인 아이디에 마우스를 호버하면 드롭박스 버튼이 나타납니다. 설정을 클릭합니다.

      \\n
    4. \\n
    5. \\n

      API Token에서 Current token을 확인합니다. 등록된 Token이 없는 경우 다음과 같이 신규 Token을 발급 받습니다.

      \\n\\n
    6. \\n
    7. \\n

      이름과 Token을 사용하여 다음과 같이 curl로 접속하면 Jenkins-Crumb 프롬프트가 나타납니다.

      \\n
      $ curl --user \\"admin:TOKEN\\" 'http://myjenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\\":\\",//crumb)'\\n\\nJenkins-Crumb:89e1fd9c402824c89465f6b97f49b605\\n
    8. \\n
    9. \\n

      Crumb를 확인했으면 다시 헤더 값에 Jenkins-Crumb:를 추가하여 02-04.MultiStep Job을 빌드하기 위해 다음과 같이 요청합니다.

      \\n
      $ curl -X POST http://myjenkins.com/job/02-04.MultiStep/build --user gyulee:11479bdec9cada082d189938a3946348be --data-urlencode json='' -H \\"Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605\\"\\n
    10. \\n
    "}`);export{d as comp,m as data}; diff --git a/assets/08-webserver.html-CfNCoUcb.js b/assets/08-webserver.html-CfNCoUcb.js new file mode 100644 index 0000000000..6a8a0b17a2 --- /dev/null +++ b/assets/08-webserver.html-CfNCoUcb.js @@ -0,0 +1,69 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as c,c as l,b as a,d as n,a as t,e}from"./app-Bzk8Nrll.js";const r={},i=a("h1",{id:"_8-tomcat-웹서버-연동",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_8-tomcat-웹서버-연동"},[a("span",null,"8. Tomcat 웹서버 연동")])],-1),u=a("ul",null,[a("li",null,"웹서버 연동의 이유"),a("li",null,"mod_jk"),a("li",null,"클러스터")],-1),d=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/j6qeCBWM4YI",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),k=e('

    8.1 웹서버 연동의 이유

    톰캣 단일로 서비스하는 경우도 있지만 일반적으로 웹서버와 연동하여 사용하는 경우가 보다 더 많습니다. 그 이유를 다음과 같이 정리합니다.

    8.1.1 요청분산

    톰캣에서 처리하는 서비스 요청이 증가하면 단일 프로세스로 처리가 부족한 상황이 발생합니다. 처리에 필요한 힙 메모리를 추가해야 한다면 현재 적용된 메모리 설정보다 더 많은 값을 설정하고 CPU 자원이 부족하다면 장비의 교체도 고려해 봐야겠습니다. 이런 접근은 가용한 메모리가 없거나 더 나은 장비를 추가 구입/구성 해야하는 점이 있습니다. 따라서 단일 프로세스의 한계를 유연하게 대처하기 위해 복수의 프로세스에서 동일한 서비스를 구성하는 방안을 고려할 수 있습니다. 그리고 복수의 프로세스에 요청을 전달하기 위해 LB(LoadBalancer)가 필요합니다.

    LB 기능을 수행하는 대표적인 두가지는 네트워크 장비(스위치 장비: L2, L4, L7)를 사용하는 방법과 HTTP 요청을 받아 분산이 가능한 웹서버 입니다. 어떤 방법을 사용하던지 복수개의 톰캣을 사용하면 상황에 따라 프로세스를 추가하여 처리하는 용량을 증가시킬 수 있습니다. 물론 앞서 장비의 추가 상황을 제외한다면 단일 프로세스 보다는 복수의 프로세스를 사용하여 부하를 분산시킬 수 있습니다.

    8.1.2 소스분산

    톰캣은 웹서버의 역할을 함께 수행할 수 있는 기능을 동반하고 있습니다. 하지만 정적인 소스를 처리함에 있어서는 기존 웹서버의 처리 능력이 더 우월하기 때문에 소스 처리의 추체를 분산시켜 처리 속도를 증가시킬 수 있습니다. 대표적인 정적인 소스는 html, css, 이미지 파일 입니다. 앞서 요청의 분산으로 부하를 분산시키는 역할과 더불어 어플리케이션 소스 또한 처리추체를 분산시키고, 더불어 웹서버와 톰캣에서 좀더 빠르게 처리 할 수 있고, 처리 가능한 요청의 처리를 분담할 수 있습니다.

    8.1.3 장애극복

    일반적으로 Failover로 표현하는 장애처리 및 장애극복은 복수의 톰캣 프로세스를 사용함에 따른 장점입니다. 특정 톰캣 프로세스에 장애가 발생하더라도 다른 톰캣 프로세스에서 요청을 처리하게 됨으로 단일 프로세스로 운영할때보다 서비스 지속성에 장점을 갖습니다.


    8.2 mod_jk

    ',12),m=a("code",null,"Tomcat Connector",-1),g={href:"http://tomcat.apache.org",target:"_blank",rel:"noopener noreferrer"},h=a("ul",null,[a("li",null,"Apache HTTP Server : mod_jk.so, mod_jk.dll"),a("li",null,"IIS : isapi_redirect.dll"),a("li",null,"iPlanet : nsapi_redirector.so, nsapi_redirector.dll")],-1),v={href:"http://tomcat.apache.org/connectors-doc/",target:"_blank",rel:"noopener noreferrer"},b=e(`

    아파치가 설치되었다는 가정하에 톰캣을 연동하는 방법은 다음과 같습니다.

    8.2.1 mod_jk 다운로드

    유닉스/리눅스/맥의 경우 mod_jk의 소스를 다운받아 아파치의 apxs와 함께 컴파일하는 과정이 필요합니다. 윈도우에서도 컴파일하여 사용할 수 있으나 비쥬얼 스튜디오가 있어야 컴파일을 할 수 있기 때문에 별도의 바이너리 파일로 제공됩니다. 다운로드페이지에서 플랫폼에 맞는 모듈을 다운받습니다.

    8.2.2 모듈 컴파일

    유닉스/리눅스/맥의 경우 컴파일을 수행하기위해 아파치의 'apxs'가 필요합니다. 다운받은 소스 압축파일을 풀고 다음과 같이 컴파일 합니다.

    $ tar xvfz tomcat-connectors-1.2.40-src.tar.gz
    +$ cd ~/Downloads/tomcat-connectors-1.2.40-src/native
    +$ ./configure —with-apxs=$APACHE_HOME_DIR/bin/apxs
    +$ make
    +$ make install
    +

    컴파일이 완료된 모듈은 자동으로 $APACHE_HOME/modules/mod_jk.so로 생성됩니다. 윈도우에서는 다운 받은 바이너리 모듈의 압축을 풀어 동일한 디렉토리에 복사하면 됩니다.

    8.2.3 모듈 설정

    생성된 모듈을 아파치에서 사용할 수 있도록 설정하는 작업을 합니다. $APACHE_HOME/conf/httpd.conf에 설정하거나 별도의 conf파일을 생성하여 읽게 하여도 됩니다. httpd.conf에 설정하는 내용은 다음과 같습니다.

    LoadModule jk_module modules/mod_jk.so
    +
    +<IfModule jk_module>
    +    JkWorkersFile conf/workers.properties
    +    JkLogFile logs/mod_jk.log
    +    JkLogLevel info
    +    JkMountFile conf/uri.properties
    +</IfModule>
    +

    JkWorkersFile은 요청을 처리하는 워커, 즉 톰캣을 정의하는 파일을 지정합니다.

    JkMountFile은 워커와 워커가 처리할 요청을 맵핑하는 파일을 지정합니다. JkMount만으로도 설정이 가능하나 하나의 파일에서 별도로 관리하기 위해서는 해당 파일을 지정하는 것을 권장합니다. httpd.confJkMount를 사용하는 경우 다음과 같이 정의할 수 있습니다.

    #jsp 파일을 worker1 워커가 처리하는 경우
    +JkMount /*.jsp  worker1
    +
    +#server 경로의 요청을 worker2 워커가 처리하는 경우
    +JkMount /servlet/* worker2
    +

    8.2.4 워커 정의

    워커는 그 단어의 의미에서도 추측할 수 있듯이 mod_jk에서 지정하는 요청을 처리하는 대상, 즉 톰켓 프로세스를 의미합니다. 워커는 다음과 같은 설정 방식을 따릅니다.

    worker.[WORKER_NAME].[TYPE]=[VALUE]
    +

    설정의 예는 다음과 같습니다.

    worker.properties의 설정 예제는 다음과 같습니다.
    workersproperties

    해당 설정은 LB로 구성되는 워커를 정의합니다. LB로 구성될 worker1worker2를 정의합니다.

    8.2.5 처리할 요청의 정의

    워커의 정의로 요청을 처리할 워커가 준비되었다면 어떤 요청을 전달할지 정의해햐 합니다. 앞서 JkMount를 사용한 방식은 간단히 설명하였고 여기서는 uri.properties 파일에서 별도로 요청의 처리 맵핑을 관리하도록 하였습니다. JkMountFile로 지정되는 이 설정 파일은 다음과 같은 설정 방식을 따릅니다.

    [URL or FILE_EXTENSION]=[WORKER or WORKER_LIST]
    +

    이렇게 설정되는 설정 파일의 내용의 예는 다음과 같습니다.

    /*.do=worker1
    +/*.jsp=worker2
    +/servlet/*=loadbalancer
    +

    JkMount와 표현방식에 약간의 차이('='의 사용여부)가 있음에 주의하여 설정합니다.

    8.2.6 테스트

    설정이 완료되면 아파치 프로세스를 재기동 합니다. 이후 맵핑한 요청설정에 따라 아파치에 요청을 합니다. jsp파일을 톰캣이 처리하도록 설정하였다면 톰캣에서 요청해보고 url을 아파치로 변경하여 동일하게 요청되는지 확인합니다.


    8.3 클러스터

    웹서버와 연동하는 주요 기능중 한가지는 장애처리입니다. 일반적으로는 이런 장애처리 동작시 기존 처리중이던 HTTP Session 정보는 장애가 발생한 톰캣이 가지고 있었기 때문에 없어지게 됩니다. 이같은 현상은 기존에 로그인하여 작업을 하던 중 해당 톰캣 프로세스에 문제가 발생하여 다른 톰캣 프로세스로 요청이 넘어가면 로그인 하던 세션이 끊겨 다시금 작업을 수행하는 현상이 발생하는 것을 예로 들수 있습니다.

    톰캣에서는 장애처리시의 HTTP Session을 복구하여 지속적인 세션의 유지를 가능하게 하고자 '클러스터' 기능을 제공합니다. 클러스터는 Multicast로 톰캣 프로세스간에 클러스터를 형성하고 멤버로 구성된 톰캣간에 세션을 공유하는 방식입니다.

    기능의 활성화는 단순히 server.xmlCluster 디스크립터의 주석을 해제하는 것만으로도 가능합니다.

    tomcatCluster.png
    tomcatCluster.png

    하지만 동일 장비에서 기동되는 톰캣간이나 서비스가 다른 톰캣이 여럿 기동중인 경우에는 설정값들이 중복되어 톰캣 기동이나 서비스 처리시 문제가 발생할 수 있습니다. 따라서 기본적인 설정 값 외에 별도의 설정들을 적용해야 하는 경우 server.xml에서 클러스터를 사용하기 위한 디스크립터 위에 설명한 도큐먼트의 내용을 참고해야 합니다.

    만약 도큐먼트의 설정들이 너무 많거나 어떻게 적용해야 하는지 이해하기 힘든경우 톰캣 5.5버전의 server.xml을 참고하시기 바랍니다. 해당 버전에서는 6.0 이후 단순히 한줄로 적용된 Cluster 디스크립터와는 다르게 기본적인 설정과 값이 같이 적용되어 있습니다. 아래 예제는 도큐먼트의 기본 설정에서 가져온 내용입니다.

    `,37),_={href:"http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html",target:"_blank",rel:"noopener noreferrer"},q=e(`
    <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
    +         channelSendOptions="8">
    +
    +  <Manager className="org.apache.catalina.ha.session.DeltaManager"
    +           expireSessionsOnShutdown="false"
    +           notifyListenersOnReplication="true"/>
    +
    +  <Channel className="org.apache.catalina.tribes.group.GroupChannel">
    +    <Membership className="org.apache.catalina.tribes.membership.McastService"
    +                address="228.0.0.4"
    +                port="45564"
    +                frequency="500"
    +                dropTime="3000"/>
    +    <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
    +              address="auto"
    +              port="4000"
    +              autoBind="100"
    +              selectorTimeout="5000"
    +              maxThreads="6"/>
    +
    +    <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
    +      <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
    +    </Sender>
    +    <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
    +    <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
    +  </Channel>
    +
    +  <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
    +         filter=""/>
    +  <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
    +
    +    <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
    +              tempDir="/tmp/war-temp/"
    +              deployDir="/tmp/war-deploy/"
    +              watchDir="/tmp/war-listen/"
    +              watchEnabled="false"/>
    +
    +    <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
    +    <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
    +</Cluster>
    +

    앞서 여러개의 톰캣 클러스터를 사용하는 경우 MembershipReciver 디스크립터의 내용에 주의합니다. Membership은 동일한 설정을 갖는 톰캣 끼리 같은 클러스터 멤버 그룹으로 인지하는 내용으로 멀티캐스트 통신을 수행합니다. Membershopaddressport가 동일한 톰캣 프로세스 끼지 클러스터 기능을 수행합니다. Reciver는 클러스터간의 메시지를 주고받는 역할을 수행하며 TCP 통신을 수행합니다. 따라서 동일한 장비의 톰캣은 Reciver에서 설정되는 port에 차이가 있어야 합니다.

    설정된 톰캣 클러스터의 기능은 어플리케이션이 세션 복제를 허용하는가의 여부에 따라 동작하게 됩니다. 따라서 어플리케이션의 web.xml에 복제가능을 활성화하는 디스크립터를 추가합니다.

    <web-app>
    +  ...
    +  <distributeable/>
    +  ...
    +</web-app>
    +

    복제 설정이 추가된 어플리케이션이 배치된 톰캣은 기동시 클러스터를 활성화하고 멤버간에 통신을 수행하는 메시지가 로그에 나타납니다.
    distributable

    구성된 클러스터와 어플리케이션은 LB로 구성되어 요청하며 각 톰캣 프로세스는 세션을 공유하기 때문에 하나의 톰캣 프로세스가 종료되더라도 다른 톰캣 프로세스에서 세션을 받아 수행하는 것을 확인할 수 잇습니다.

    `,6);function w(x,f){const s=p("ExternalLinkIcon");return c(),l("div",null,[i,u,d,k,a("p",null,[n("웹서버는 프록시 기능만을 사용하여도 톰캣과의 연동이 가능하나 톰캣으로의 연동을 좀더 긴밀하게 하기 위해 별도의 모듈을 제공합니다. 이는 "),m,n("로 제공되는데 "),a("a",g,[n("http://tomcat.apache.org"),t(s)]),n("의 Document와 Download에서 확인할 수 있습니다. 연동가능한 대표적인 웹서버로는 다음의 웹서버와 모듈이 요구됩니다.")]),h,a("p",null,[n("아파치(Apache HTTP Server)는 가장 많이 사용되고 모든 플랫폼을 지원하는 대표적인 웹서버로서 이번 장에서 설명하고자하는 웹서버와의 연동에서 사용하고자 합니다. 기타 웹서버의 경우 톰캣의 "),a("a",v,[n("토큐먼트"),t(s)]),n(" 내용을 참고하시기 바랍니다.")]),b,a("p",null,[a("a",_,[n("http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html: 기본 설정"),t(s)])]),q])}const j=o(r,[["render",w],["__file","08-webserver.html.vue"]]),S=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/08-webserver.html","title":"8. Tomcat 웹서버 연동","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/08-webserver.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"8. Tomcat 웹서버 연동"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/workerproperties.jpg?token=ADUAZXKQI5JMPI7G5L7PVNS67EUS4"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"8. Tomcat 웹서버 연동"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"8. Tomcat 웹서버 연동\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/workerproperties.jpg?token=ADUAZXKQI5JMPI7G5L7PVNS67EUS4\\",\\"https://raw.githubusercontent.com/Great-Stone/great-stone.github.io/master/assets/img/Tomcat_youtube/tomcatCluster.png\\",\\"https://raw.githubusercontent.com/Great-Stone/great-stone.github.io/master/assets/img/Tomcat_youtube/distributeable.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"8.1 웹서버 연동의 이유","slug":"_8-1-웹서버-연동의-이유","link":"#_8-1-웹서버-연동의-이유","children":[{"level":3,"title":"8.1.1 요청분산","slug":"_8-1-1-요청분산","link":"#_8-1-1-요청분산","children":[]},{"level":3,"title":"8.1.2 소스분산","slug":"_8-1-2-소스분산","link":"#_8-1-2-소스분산","children":[]},{"level":3,"title":"8.1.3 장애극복","slug":"_8-1-3-장애극복","link":"#_8-1-3-장애극복","children":[]}]},{"level":2,"title":"8.2 mod_jk","slug":"_8-2-mod-jk","link":"#_8-2-mod-jk","children":[{"level":3,"title":"8.2.1 mod_jk 다운로드","slug":"_8-2-1-mod-jk-다운로드","link":"#_8-2-1-mod-jk-다운로드","children":[]},{"level":3,"title":"8.2.2 모듈 컴파일","slug":"_8-2-2-모듈-컴파일","link":"#_8-2-2-모듈-컴파일","children":[]},{"level":3,"title":"8.2.3 모듈 설정","slug":"_8-2-3-모듈-설정","link":"#_8-2-3-모듈-설정","children":[]},{"level":3,"title":"8.2.4 워커 정의","slug":"_8-2-4-워커-정의","link":"#_8-2-4-워커-정의","children":[]},{"level":3,"title":"8.2.5 처리할 요청의 정의","slug":"_8-2-5-처리할-요청의-정의","link":"#_8-2-5-처리할-요청의-정의","children":[]}]},{"level":2,"title":"8.2.6 테스트","slug":"_8-2-6-테스트","link":"#_8-2-6-테스트","children":[]},{"level":2,"title":"8.3 클러스터","slug":"_8-3-클러스터","link":"#_8-3-클러스터","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.51,"words":452},"filePathRelative":"05-Software/Tomcat/tomcat101/08-webserver.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n\\n
    \\n

    8.1 웹서버 연동의 이유

    \\n

    톰캣 단일로 서비스하는 경우도 있지만 일반적으로 웹서버와 연동하여 사용하는 경우가 보다 더 많습니다. 그 이유를 다음과 같이 정리합니다.

    "}');export{j as comp,S as data}; diff --git a/assets/09-security.html-CSGyCK8z.js b/assets/09-security.html-CSGyCK8z.js new file mode 100644 index 0000000000..898c19f438 --- /dev/null +++ b/assets/09-security.html-CSGyCK8z.js @@ -0,0 +1,18 @@ +import{_ as t,a as e,b as n,c as l,d as i,e as s,f as a}from"./1564543591394-BKf8t37i.js";import{_ as d}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as r,e as c}from"./app-Bzk8Nrll.js";const p={},u=c(`

    9. Security

    9.1 Securing your deployment with users

    사용자별 배포수행을 위한 사용자 설정을 설명합니다.

    Enable security는 보안 설정 여부를 설정하는 항목으로 기본적으로는 비활성화되어있습니다. 체크하여 활성화하면 다양한 보안 옵션을 설정할 수 있는 항목이 표기 됩니다.

    Security Realm 에서는 Jenkins에서 사용하는 사용자 관리 방식을 선택합니다.

    Authorization 에서는 사용자 권한에 대한 설정을 정의합니다.

    다음은 권한 매트릭스의 항목과 권한별 설명입니다.

    항목권한의미
    OverallAdminister시스템의 전역 설정을 변경할 수 있다. OS 에서 허용된 범위안에서 전체 시스템 엑세스드의 매우 민감한 설정을 수행
    Read젠킨스의 모든 페이지 확인 가능
    RunScripts그루비 콘솔이나 그루비 CLI 명령을 통해 그루비 스크립트를 실행
    UploadPlugins특정 플러그인을 업로드
    ConfigureUpdateCenter업데이트 사이트와 프록시 설정
    SlaveConfigure기존 슬레이브 설정 가능
    Delete기존 슬레이브 삭제
    Create신규 슬레이브 생성
    Disconnect슬레이브 연결을 끊거나 슬레이브를 임시로 오프라인으로 표시
    Connect슬레이브와 연결하거나 슬레이브를 온라인으로 표시
    JobCreate새로운 작업 생성
    Delete기존 작업 삭제
    Configure기존 작업의 설정 갱신
    Read프로젝트 설정에 읽기 전용 권한 부여
    Discover익명 사용자가 작업을 볼 권한이 없으면 에러 메시지 표시를 하지 않고 로그인 폼으로 전환
    Build새로운 빌드 시작
    Workspace젠킨스 빌드를 실행 하기 위해 체크아웃 한 작업 영역의 내용을 가져오기 가능
    Cancel실행중인 빌드 취소
    RunDelete빌드 내역에서 특정 빌드 삭제
    Update빌드의 설명과 기타 프로퍼티 수정(빌드 실패 사유등)
    ViewCreate새로운 뷰 생성
    Delete기존 뷰 삭제
    Configure기존 뷰 설정 갱신
    Read기존 뷰 보기
    SCMTag특정 빌드와 관련된 소스 관리 시스템에 태깅을 생성

    CSRF Protection 항목에 있는 Prevent Cross Site Request Forgery exploits 항목은 페이지마다 nonce 또는 crumb 이라 불리우는 임시 값을 삽입하여 사이트 간 요청 위조 공격을 막을 수 있게 해줍니다. 사용방법은 위에서 REST API 에 대한 설명 시 crumb 값을 얻고, 사용하는 방법을 참고합니다.

    9.2 Securing secret credentials and files

    Jenkins에서 Pipeline을 설정하는 경우 일부 보안적인 값이 필요한 경우가 있습니다. 예를 들면 UsernamePassword 같은 값입니다. 앞서의 과정에서 Credentials를 생성하는 작업을 일부 수행해 보았습니다. 여기서는 생성된 인증 값을 Pipeline에 적용하는 방법을 설명합니다.

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 09-02.SecuringSecretCredentialsAndFiles) 설정은 다음과 같이 수행합니다.

    1. Pipeline 스크립트에 다음과 같이 입력 합니다.

      pipeline {
      +    agent any
      +    environment {
      +       SECRET=credentials('jenkins-secret-text')
      +    }
      +    stages {
      +        stage('Build') {
      +            steps {
      +                echo "\${env.SECRET}"
      +            }
      +        }
      +    }
      +}
      +
    2. 저장 후 Build Now를 클릭하여 빌드를 수행하면 실패하게 되고 Console Output에서 진행사항을 보면, Pipeline 스크립트에서 선언한 jenkins-secret-text때문에 에러가 발생한 것을 확인할 수 있습니다.

    3. 좌측 상단의 Jenkins버튼을 클릭하여 최상위 메뉴로 이동합니다.

    4. 좌측 메뉴의 Credentials를 클릭하고 (global) 도메인을 클릭합니다.

      1564534571013
      1564534571013
    5. 좌측에 Add Credentials를 클릭하여 새로운 항목을 추가합니다.

    6. 저장 후 다시 빌드를 수행하면 정상적으로 수행됩니다. 해당 값은 숨기기 위한 값이므로 Pipeline 스크립트에서 echo로 호출하더라도 ****이란 값으로 표기 됩니다.

    이같은 방법은 Password같은 보안에 민감한 정보를 사용하기에 유용합니다.

    9.3 Auditing your environment

    Jenkins의 변화와 활동에 대한 감시를 위한 설정 방법을 설명합니다. Jenkins에 새로운 플러그인을 추가하고 설정합니다.

    저장 후 빌드나 Job의 설정 변경등의 작업을 수행하면, audit.log.0으로 지정된 파일 경로에 생성됨을 확인 할 수 있습니다.

    $ tail -f ./audit.log.0
    +Jul 31, 2019 10:47:32,727 AM job/02-02.Jobs/ #12 Started by user GyuSeok.Lee
    +Jul 31, 2019 10:47:42,738 AM /job/03-04.WebhookBuild Triggering/configSubmit by gyulee
    +Jul 31, 2019 10:48:09,001 AM /configSubmit by gyulee
    +

    9.4 Using forders to create security realms

    다양한 프로젝트를 관리하는 경우 관리상, 빌드 프로젝트를 관리해야할 필요성이 발생합니다. Jenkins에서 Forder 아이템을 생성하여 관리 편의성과 보안요소를 추가할 수 있습니다.

    우선 테스트를 위한 사용자를 추가합니다.

    다음으로 Forder 타임의 Item을 추가합니다.

    권한 설정을 하여 현재 Admin 권한의 사용자는 접근 가능하고 새로 생성한 tester는 접근불가하도록 설정합니다.

    Jenkins의 인증 기능을 사용하여 보안적 요소를 구성할 수 있습니다. Audit 로그를 활용하여 사용자별 활동을 기록할 수도 있고 Folder를 활용하면 간단히 사용자/그룹에 프로젝트를 구분하여 사용할 수 있도록 구성할 수 있습니다.

    ',32),g=[u];function y(m,f){return o(),r("div",null,g)}const b=d(p,[["render",y],["__file","09-security.html.vue"]]),v=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/09-security.html","title":"9. Security","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/09-security.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"9. Security"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"9. Security\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"9.1 Securing your deployment with users","slug":"_9-1-securing-your-deployment-with-users","link":"#_9-1-securing-your-deployment-with-users","children":[]},{"level":2,"title":"9.2 Securing secret credentials and files","slug":"_9-2-securing-secret-credentials-and-files","link":"#_9-2-securing-secret-credentials-and-files","children":[]},{"level":2,"title":"9.3 Auditing your environment","slug":"_9-3-auditing-your-environment","link":"#_9-3-auditing-your-environment","children":[]},{"level":2,"title":"9.4 Using forders to create security realms","slug":"_9-4-using-forders-to-create-security-realms","link":"#_9-4-using-forders-to-create-security-realms","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.44,"words":433},"filePathRelative":"05-Software/Jenkins/pipeline101/09-security.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    9.1 Securing your deployment with users

    \\n

    사용자별 배포수행을 위한 사용자 설정을 설명합니다.

    \\n\\n

    Enable security는 보안 설정 여부를 설정하는 항목으로 기본적으로는 비활성화되어있습니다. 체크하여 활성화하면 다양한 보안 옵션을 설정할 수 있는 항목이 표기 됩니다.

    "}');export{b as comp,v as data}; diff --git a/assets/09-thread.html-XImYqvQZ.js b/assets/09-thread.html-XImYqvQZ.js new file mode 100644 index 0000000000..085f7f0625 --- /dev/null +++ b/assets/09-thread.html-XImYqvQZ.js @@ -0,0 +1,30 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,b as a,e}from"./app-Bzk8Nrll.js";const o={},p=a("h1",{id:"_9-tomcat-쓰레드",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#_9-tomcat-쓰레드"},[a("span",null,"9. Tomcat 쓰레드")])],-1),c=a("ul",null,[a("li",null,"Thread?"),a("li",null,"설정"),a("li",null,"Thread Dump")],-1),l=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/sKiEidnV0nI",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),u=e(`

    9.1 Thread?

    Thread는 JVM내에 요청된 작업을 동시에 처리하기 위한 작은 cpu라고 볼 수 있습니다. 톰캣에 서비스 처리를 요청하는 경우 해당 요청은 Queue에 쌓여 FIFO로 Thread에 전달되고 Thread에 여유가 있는 경우 Queue에 들어온 요청은 바로 Thread로 전달되어 Queue Length는 0을 유지하지만 Thread가 모두 사용중이여서 더이상의 요청 처리를 하지 못하는 경우 새로 발생한 요청은 Queue에 쌓이면서 지연이 발생합니다.

    Thread가 많을수록 동시에 많은 요청을 처리하기 때문에 작은 Thread 수는 서비스를 지연시키지만 이에 반해 Thread도 자원을 소모하므로 필요이상의 큰 값은 불필요한 JVM의 자원을 소모하게 되고 하나의 프로세스 내의 Thread 수는 톰캣 기준으로 700개 이하로 설정할 것을 권장합니다.

    사실상 요청은 지연이 최소화 되어야 하며 지연이 길어질수록 Thread를 점유하여 동시간대에 사용가능한 Thread 수를 줄이므로 적정한 Thread 개수의 설정 상태에서 요청을 더 많이 받고자 한다면 지연에 대한 문제점을 찾는 것을 우선해야 합니다.

    9.2 설정

    쓰레드는 Connector 기준으로 생성됩니다. 따라서 HTTP나 AJP, SSL이 설정된 Connector마다 다른 쓰레드 수를 설정할 수 있습니다. 또는 하나의 쓰레드 풀을 생성하고 Connector에서 해당 쓰레드 풀의 쓰레드를 같이 사용하도록 설정할 수도 있습니다.

    9.2.1 Connector의 쓰레드 설정

    기본적인 'Connector'는 다음과 같이 설정되어있습니다.

    <Connector port="8080" protocol="HTTP/1.1"
    +           connectionTimeout="20000" redirectPort="8443" />
    +           
    +<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    +

    HTTP나 AJP 프로토콜이 정의된 Connector는 설정되어 있지는 않지만 기본값으로 최대 쓰레드 200개의 설정을 가지고 있습니다. 쓰레드 관련 설정값은 다음과 같습니다.

    AttributeDescription
    maxSpareThreadsIdle 상태로 유지할 max thread pool size
    maxThreads동시 요청에 의해 Connector가 생성 할 수 있는 최대 request 수
    minSpareThreadstomcat을 실행할때 최소로 유지할 Idle Thread 수
    maxIdleTimeThread를 유지하는 시간(ms)

    이런 설정 값들로 다시금 정의하면 기본 Connector를 다음과 같이 설정할 수 있습니다.

    <Connector port="8080" protocol="HTTP/1.1"
    +           connectionTimeout="20000" redirectPort="8443"
    +           maxSpareThreads="5"
    +           maxThreads="15"
    +           minSpareThreads="10" />
    +           
    +<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
    +           maxSpareThreads="5"
    +           maxThreads="15"
    +           minSpareThreads="10" />
    +

    9.2.2 Executor

    Executor 디스크립터는 Connector의 쓰레드 설정에 별도의 실행자로 설정하여 동일한 Executor를 사용하는 Connector는 같은 쓰레드 풀에서 쓰레드를 사용하도록 하는 기능입니다.

    별도의 Connector를 사용하여 서비스하지만 모두 같은 쓰레드 자원을 사용하기 위함이며 connectorexecutor라는 설정을 사용하여 공통의 쓰레드 풀을 이용할 수 있습니다. tomcatThreadPool이라는 이름을 갖는 Executor와 각 Connector에 설정하는 예는 다음과 같습니다.

    <Executor name="tomcatThreadPool"
    +          namePrefix="catalina-exec-"
    +          maxThreads="150"
    +          minSpareThreads="4"/>
    +
    +<Connector executor="tomcatThreadPool"
    +           port="8080" protocol="HTTP/1.1"
    +           connectionTimeout="20000" redirectPort="8443"/>
    +           
    +<Connector executor="tomcatThreadPool"
    +           port="8009" protocol="AJP/1.3" redirectPort="8443"/>
    +

    Executor에는 name을 정의하여 다른 Connector에서 해당 Executor를 정의할 수 있는 연결고리를 만듭니다. 그리고 쓰레드의 이름을 정의하는 namePrifix설정으로 다른 쓰레드와 구분할 수 있도록 합니다. 기존 Connector에 설정하던 쓰레드 관련 설정을 Excutor에 함으로서 Connector에 공통 쓰레드 풀을 제공합니다.


    9.3 Thread Dump

    쓰레드 덤프는 실행중인 Thread의 종류와 시작점, 실행한 클래스와 메소드 순서, 현재 상태등을 기록하는 JVM의 고유 기능입니다. 쓰레드 덤프로 서비스의 흐름과 서비스 지연시 수행중인 작업, 병목등을 확인할 수 있습니다.

    쓰레드 덤프의 시작에는 쓰레드 이름과 쓰레드의 정보가 기록되며 이후 쓰레드 상태에 대해 설명합니다.
    트레이스의 읽는 순서는 위가 최근 실행한 클래스와 메소드이기 때문에 아래서부터 위로 읽습니다.

    ThreadDump
    ThreadDump

    쓰레드 덤프를 발생시키는 법은 다음과 같습니다.

    1. 프로세스 ID를 확인

    2. 쓰레드 덤프 발생

    3. 쓰레드 덤프 확인

    `,26),r=[p,c,l,u];function i(d,m){return n(),s("div",null,r)}const g=t(o,[["render",i],["__file","09-thread.html.vue"]]),v=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/09-thread.html","title":"9. Tomcat 쓰레드","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/09-thread.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"9. Tomcat 쓰레드"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/great-stone.github.io/master/assets/img/Tomcat_youtube/ThreadDump.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"9. Tomcat 쓰레드"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"9. Tomcat 쓰레드\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/great-stone.github.io/master/assets/img/Tomcat_youtube/ThreadDump.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"9.1 Thread?","slug":"_9-1-thread","link":"#_9-1-thread","children":[]},{"level":2,"title":"9.2 설정","slug":"_9-2-설정","link":"#_9-2-설정","children":[{"level":3,"title":"9.2.1 Connector의 쓰레드 설정","slug":"_9-2-1-connector의-쓰레드-설정","link":"#_9-2-1-connector의-쓰레드-설정","children":[]},{"level":3,"title":"9.2.2 Executor","slug":"_9-2-2-executor","link":"#_9-2-2-executor","children":[]}]},{"level":2,"title":"9.3 Thread Dump","slug":"_9-3-thread-dump","link":"#_9-3-thread-dump","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.83,"words":250},"filePathRelative":"05-Software/Tomcat/tomcat101/09-thread.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n\\n
    \\n

    9.1 Thread?

    \\n

    Thread는 JVM내에 요청된 작업을 동시에 처리하기 위한 작은 cpu라고 볼 수 있습니다. 톰캣에 서비스 처리를 요청하는 경우 해당 요청은 Queue에 쌓여 FIFO로 Thread에 전달되고 Thread에 여유가 있는 경우 Queue에 들어온 요청은 바로 Thread로 전달되어 Queue Length는 0을 유지하지만 Thread가 모두 사용중이여서 더이상의 요청 처리를 하지 못하는 경우 새로 발생한 요청은 Queue에 쌓이면서 지연이 발생합니다.

    "}');export{g as comp,v as data}; diff --git a/assets/1-vso-overview.html-CsgoBSJ3.js b/assets/1-vso-overview.html-CsgoBSJ3.js new file mode 100644 index 0000000000..3715bbdc31 --- /dev/null +++ b/assets/1-vso-overview.html-CsgoBSJ3.js @@ -0,0 +1,103 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as i,c as o,b as n,d as e,a as s,e as t}from"./app-Bzk8Nrll.js";const p={},r=n("h1",{id:"vault-secrets-operator-개요",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#vault-secrets-operator-개요"},[n("span",null,"Vault Secrets Operator 개요")])],-1),u=n("br",null,null,-1),d={href:"https://github.com/hashicorp/vault-secrets-operator/issues",target:"_blank",rel:"noopener noreferrer"},m=t('
    img
    img

    Vault Secrets Operator(이하, VSO)를 사용하면 파드가 쿠버네티스 시크릿에서 기본적으로 볼트 시크릿을 사용할 수 있다.

    개요

    VSO는 지원되는 Custom Resource Definitions (CRD) 집합의 변경 사항을 감시하여 작동한다. 각 CRD는 오퍼레이터가 Vault Secrets을 Kubernetes Secret에 동기화할 수 있도록 하는 데 필요한 사양(specification)을 제공한다.

    오퍼레이터는 소스(source) 볼트 시크릿 데이터를 대상(destination) 쿠버네티스 시크릿에 직접 쓰며, 소스에 대한 변경 사항이 수명 기간 동안 대상에 복제되도록 한다. 이러한 방식으로 애플리케이션은 대상 시크릿에 대한 접근 권한만 있으면 그 안에 포함된 시크릿 데이터를 사용할 수 있다.

    특징

    VSO가 지원하는 기능은 다음과 같다:

    ',7),v=n("li",null,"모든 Vault 비밀 엔진 지원.",-1),k=n("li",null,"Vault와의 TLS/mTLS 통신.",-1),h={href:"https://developer.hashicorp.com/vault/docs/auth/kubernetes",target:"_blank",rel:"noopener noreferrer"},b=n("code",null,"Pod",-1),y=n("code",null,"ServiceAccount",-1),g=t("
  • Vault Secrets을 Kubernetes Secrets에 동기화.
  • Deployment, ReplicaSet, StatefulSet 쿠버네티스 리소스 유형에 대한 시크릿 로테이션.
  • 운영자 모니터링을 위한 Prometheus 계측(instrumentation) 제공.
  • 지원되는 설치 방법: Helm, Kustomize
  • ",4),f={href:"https://developer.hashicorp.com/vault/docs/platform/k8s/vso/installation",target:"_blank",rel:"noopener noreferrer"},_=n("h2",{id:"지원되는-쿠버네티스-버전",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#지원되는-쿠버네티스-버전"},[n("span",null,"지원되는 쿠버네티스 버전")])],-1),V={href:"https://kubernetes.io/releases/",target:"_blank",rel:"noopener noreferrer"},S=t(`

    Vault 접근 및 사용자 지정 리소스 정의

    참고:
    현재, 오퍼레이터는 쿠버네티스 인증 방법만 지원한다. 시간이 지남에 따라 더 많은 Vault 인증 방식에 대한 지원을 추가할 예정이다.

    Vault 연결 및 인증 구성은 VaultConnectionVaultAuth CRD에서 제공한다. 이는 모든 비밀 복제 유형 리소스가 참조하는 기본 사용자 지정 리소스로 간주할 수 있다.

    VaultConnection 커스텀 리소스

    오퍼레이터가 단일 Vault 서버 인스턴스에 연결하는 데 필요한 구성을 제공한다.

    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultConnection
    +metadata:
    +  namespace: vso-example
    +  name: example
    +spec:
    +  # 필수적인 구성정보
    +  # Vault 서버 주소
    +  address: http://vault.vault.svc.cluster.local:8200
    +
    +  # 선택적인 구성정보
    +  # 모든 Vault 요청에 포함될 HTTP headers
    +  # headers: []
    +  
    +  # TLS 연결의 SNI 호스트로 사용할 TLS 서버 이름
    +  # tlsServerName: ""
    +  
    +  # Vault에 대한 TLS 연결에 대한 TLS verification을 건너뜀
    +  # skipTLSVerify: false
    + 
    +  # Kubernetes Secret에 저장된 신뢰할 수 있는 PEM 인코딩된 CA 인증서 체인
    +  # caCertSecretRef: ""
    +

    VaultAuth 커스텀 리소스

    오퍼레이터가 VaultConnection 사용자 지정 리소스에 지정된 대로 단일 Vault 서버 인스턴스에 인증하는 데 필요한 구성을 제공한다.

    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultAuth
    +metadata:
    +  namespace: vso-example
    +  name: example
    +spec:
    +  # 필수적인 구성정보
    +  # 해당 VaultConnection 커스텀 리소스의 VaultConnectionRef
    +  # 값을 지정하지 않으면 오퍼레이터는 자체 쿠버네티스 네임스페이스에 구성된 
    +  # 'default' VaultConnection을 기본값으로 사용
    +  vaultConnectionRef: example
    +
    +  # Vault에 인증할 때 사용하는 방법.
    +  method: kubernetes
    +  # Auth methods로 인증할 때 사용할 마운트.
    +  mount: kubernetes
    +  # 쿠버네티스용 인증 구성을 사용하려면, Method를 쿠버네티스로 설정해야 함
    +  kubernetes:
    +    # Vault에 인증할 때 사용할 역할
    +    role: example
    +    # Vault에 인증할 때 사용할 서비스어카운트 파드/애플리케이션당 항상 고유한 서비스어카운트를 제공을 권장
    +    serviceAccount: default
    +
    +  # 선택적인 구성정보
    +  # 인증 백엔드가 마운트되는 Vault 네임스페이스(Vault Enterprise 전용기능)
    +  # namespace: ""
    +
    +  # Vault에 인증할 때 사용할 매개변수
    +  # params: []
    +
    +  # 모든 Vault 인증 요청에 포함할 HTTP 헤더
    +  # headers: []
    +

    볼트 시크릿 커스텀 리소스 정의

    오퍼레이터가 단일 볼트 시크릿을 단일 쿠버네티스 시크릿으로 복제하는 데 필요한 구성을 제공한다. 지원되는 각 CRD는 아래 문서에 설명된 Vault Secret의 Class에 특화되어 있다.

    VaultStaticSecret 사용자 지정 리소스

    오퍼레이터가 단일 볼트 정적 시크릿을 단일 Kubernetes Secret에 동기화하는 데 필요한 구성을 제공한다.

    `,14),x={href:"https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2",target:"_blank",rel:"noopener noreferrer"},C={href:"https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1",target:"_blank",rel:"noopener noreferrer"},w=t(`
    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultStaticSecret
    +metadata:
    +  namespace: vso-example
    +  name: example
    +spec:
    +  vaultAuthRef: example
    +  mount: kvv2
    +  type: kv-v2
    +  name: secret
    +  refreshAfter: 60s
    +  destination:
    +    create: true
    +    name: static-secret1
    +

    VaultPKISecret 사용자 지정 리소스

    운영자가 단일 볼트 PKI 시크릿을 단일 쿠버네티스 시크릿에 동기화하는 데 필요한 구성을 제공한다.

    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultPKISecret
    +metadata:
    +  namespace: vso-example
    +  name: example
    +spec:
    +  vaultAuthRef: example
    +  mount: pki
    +  name: default
    +  commonName: example.com
    +  format: pem
    +  expiryOffset: 1s
    +  ttl: 60s
    +  namespace: tenant-1
    +  destination:
    +    create: true
    +    name: pki1
    +

    VaultDynamicSecret Custom Resource

    오퍼레이터가 단일 볼트 동적 시크릿을 단일 쿠버네티스 시크릿에 동기화하는 데 필요한 구성을 제공한다.

    `,7),T={href:"https://developer.hashicorp.com/vault/docs/secrets/databases",target:"_blank",rel:"noopener noreferrer"},K={href:"https://developer.hashicorp.com/vault/docs/secrets/aws",target:"_blank",rel:"noopener noreferrer"},R={href:"https://developer.hashicorp.com/vault/docs/secrets/azure",target:"_blank",rel:"noopener noreferrer"},q={href:"https://developer.hashicorp.com/vault/docs/secrets/gcp",target:"_blank",rel:"noopener noreferrer"},O=t(`
    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultDynamicSecret
    +metadata:
    +  namespace: vso-example
    +  name: example
    +spec:
    +  vaultAuthRef: example
    +  mount: db
    +  role: postgres
    +  destination:
    +    create: true
    +    name: dynamic1
    +
    `,1);function A(I,E){const a=c("ExternalLinkIcon");return i(),o("div",null,[r,n("blockquote",null,[n("p",null,[e("참고:"),u,e(" 현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *"),n("a",d,[e("here"),s(a)]),e("*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.")])]),m,n("ul",null,[v,k,n("li",null,[n("a",h,[e("Kubernetes Auth Method"),s(a)]),e("을 통해 요청하는 "),b,e("의 "),y,e("를 사용한 인증.")]),g,n("li",null,[e("자세한 내용은 "),n("em",null,[n("a",f,[e("installation"),s(a)])]),e(" 문서를 참조.")])]),_,n("p",null,[e("현재 지원되는 "),n("a",V,[e("Kubernetes minor releases"),s(a)]),e("는 다음과 같다. 최신 버전은 각 쿠버네티스 버전에 대해 테스트되며, 다른 버전의 Kubernetes에서도 작동할 수 있지만 지원되지는 않는다.")]),S,n("ul",null,[n("li",null,[e("지원되는 시크릿 엔진: "),n("a",x,[e("kv-v2"),s(a)]),e(", "),n("a",C,[e("kv-v1"),s(a)])])]),w,n("ul",null,[n("li",null,[e("지원되는 시크릿 엔진은 전부가 아님 : "),n("a",T,[e("databases"),s(a)]),e(", "),n("a",K,[e("aws"),s(a)]),e(", "),n("a",R,[e("azure"),s(a)]),e(", "),n("a",q,[e("gcp"),s(a)]),e(", ...")])]),O])}const D=l(p,[["render",A],["__file","1-vso-overview.html.vue"]]),P=JSON.parse('{"path":"/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html","title":"Vault Secrets Operator 개요","lang":"ko-KR","frontmatter":{"description":"Vault Secrets Operator(이하, VSO)를 사용하면 파드가 쿠버네티스 시크릿에서 기본적으로 볼트 시크릿을 사용할 수 있다.","tag":["vault","operator"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Vault Secrets Operator 개요"}],["meta",{"property":"og:description","content":"Vault Secrets Operator(이하, VSO)를 사용하면 파드가 쿠버네티스 시크릿에서 기본적으로 볼트 시크릿을 사용할 수 있다."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://external-preview.redd.it/UWYqK9zEwREq18MnMbIC7T6W5mUJbF_i4C2K3T1cV6Y.jpg?width=640&crop=smart&auto=webp&s=927dad31a962359c0b61c5ae57ce1d57f6957cf7"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Vault Secrets Operator 개요"}],["meta",{"property":"article:tag","content":"vault"}],["meta",{"property":"article:tag","content":"operator"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vault Secrets Operator 개요\\",\\"image\\":[\\"https://external-preview.redd.it/UWYqK9zEwREq18MnMbIC7T6W5mUJbF_i4C2K3T1cV6Y.jpg?width=640&crop=smart&auto=webp&s=927dad31a962359c0b61c5ae57ce1d57f6957cf7\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"개요","slug":"개요","link":"#개요","children":[{"level":3,"title":"특징","slug":"특징","link":"#특징","children":[]}]},{"level":2,"title":"지원되는 쿠버네티스 버전","slug":"지원되는-쿠버네티스-버전","link":"#지원되는-쿠버네티스-버전","children":[]},{"level":2,"title":"Vault 접근 및 사용자 지정 리소스 정의","slug":"vault-접근-및-사용자-지정-리소스-정의","link":"#vault-접근-및-사용자-지정-리소스-정의","children":[{"level":3,"title":"VaultConnection 커스텀 리소스","slug":"vaultconnection-커스텀-리소스","link":"#vaultconnection-커스텀-리소스","children":[]},{"level":3,"title":"VaultAuth 커스텀 리소스","slug":"vaultauth-커스텀-리소스","link":"#vaultauth-커스텀-리소스","children":[]}]},{"level":2,"title":"볼트 시크릿 커스텀 리소스 정의","slug":"볼트-시크릿-커스텀-리소스-정의","link":"#볼트-시크릿-커스텀-리소스-정의","children":[{"level":3,"title":"VaultStaticSecret 사용자 지정 리소스","slug":"vaultstaticsecret-사용자-지정-리소스","link":"#vaultstaticsecret-사용자-지정-리소스","children":[]},{"level":3,"title":"VaultPKISecret 사용자 지정 리소스","slug":"vaultpkisecret-사용자-지정-리소스","link":"#vaultpkisecret-사용자-지정-리소스","children":[]},{"level":3,"title":"VaultDynamicSecret Custom Resource","slug":"vaultdynamicsecret-custom-resource","link":"#vaultdynamicsecret-custom-resource","children":[]}]}],"git":{"createdTime":1684599614000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Hyungwook Yu","email":"40632767+chosam2@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.08,"words":325},"filePathRelative":"04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.md","localizedDate":"2023년 5월 21일","excerpt":"\\n
    \\n

    참고:
    \\n현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *here*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

    \\n
    \\n
    \\"img\\"
    img
    "}');export{D as comp,P as data}; diff --git a/assets/10-artifacts.html-Bs2kQ5kz.js b/assets/10-artifacts.html-Bs2kQ5kz.js new file mode 100644 index 0000000000..1b0649097a --- /dev/null +++ b/assets/10-artifacts.html-Bs2kQ5kz.js @@ -0,0 +1,31 @@ +import{_ as n,a,b as t,c as s}from"./1564546697375-DhhAeZD5.js";import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as i,c,e as p}from"./app-Bzk8Nrll.js";const o={},l=p(`

    10. Artifacts

    빌드 이후 빌드의 결과를 기록하고 저장하는 방법을 설명합니다.

    10.1 Creating and storing artifacts

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-01.CreatingAndStoringArtifacts)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages{
    +        stage('Build') {
    +            steps{
    +                sh 'echo "Generating artifacts for \${BUILD_NUMBER}" > output.txt'
    +            }
    +        }
    +        stage('Archive') {
    +            steps {
    +                archiveArtifacts artifacts: 'output.txt', onlyIfSuccessful: true
    +            }
    +        }
    +    }
    +}
    +

    Archive Stage에 archiveArtifacts 스크립트가 동작하는 예제입니다. 이같은 Pipeline 스크립트 작성을 도와주는 툴을 추가로 확인해 봅니다.

    결과물을 확인하면 Pipeline 스크립트에 작성한 형태와 같은 것을 확인 할 수 있습니다.

    1564545470815
    1564545470815

    좌측 메뉴의 Build Now를 클릭하여 빌드 수행 후에 화면에 Artifacts 항목이 추가된 것을 확인할 수 있습니다. UI 상에는 마지막 빌드 결과가 강조되어 나오고 각 빌드에 대한 결과물은 각각의 빌드단계의 다운로드 버튼으로 확인하고 다운로드 할 수 있습니다.

    1564545639205
    1564545639205

    10.2. Fingerprinting for artifact tracking

    빌드 이후 보관되는 파일에 대해 어떤 프로젝트, 어떤 빌드 에서 발생한 결과물인지 확인할 수 있는 핑거프린팅 기능을 설명합니다.

    Step 1의 프로젝트를 그대로 사용하거나 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-02.FingerprintingForArtifactTracking)

    Step 1 Pipeline 스크립트의 archiveArtifactsfingerprint: true를 추가합니다.

    pipeline {
    +    agent any
    +    stages{
    +        stage('Build') {
    +            steps{
    +                sh 'echo "Generating text artifacts: Build:\${BUILD_NUMBER}" > output.txt'
    +            }
    +        }
    +        stage('Archive') {
    +            steps {
    +                archiveArtifacts artifacts: 'output.txt', fingerprint: true, onlyIfSuccessful: true
    +            }
    +        }
    +    }
    +}
    +

    파일의 지문을 확인합니다.

    ',19),r=[l];function u(d,g){return i(),c("div",null,r)}const f=e(o,[["render",u],["__file","10-artifacts.html.vue"]]),h=JSON.parse(`{"path":"/05-Software/Jenkins/pipeline101/10-artifacts.html","title":"10. Artifacts","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/10-artifacts.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"10. Artifacts"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"10. Artifacts\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"10.1 Creating and storing artifacts","slug":"_10-1-creating-and-storing-artifacts","link":"#_10-1-creating-and-storing-artifacts","children":[]},{"level":2,"title":"10.2. Fingerprinting for artifact tracking","slug":"_10-2-fingerprinting-for-artifact-tracking","link":"#_10-2-fingerprinting-for-artifact-tracking","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.57,"words":170},"filePathRelative":"05-Software/Jenkins/pipeline101/10-artifacts.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    빌드 이후 빌드의 결과를 기록하고 저장하는 방법을 설명합니다.

    \\n

    10.1 Creating and storing artifacts

    \\n

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-01.CreatingAndStoringArtifacts)

    \\n

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    \\n
    pipeline {\\n    agent any\\n    stages{\\n        stage('Build') {\\n            steps{\\n                sh 'echo \\"Generating artifacts for \${BUILD_NUMBER}\\" > output.txt'\\n            }\\n        }\\n        stage('Archive') {\\n            steps {\\n                archiveArtifacts artifacts: 'output.txt', onlyIfSuccessful: true\\n            }\\n        }\\n    }\\n}\\n
    "}`);export{f as comp,h as data}; diff --git a/assets/10-monitoring.html-B9YmIXXL.js b/assets/10-monitoring.html-B9YmIXXL.js new file mode 100644 index 0000000000..dc2557016e --- /dev/null +++ b/assets/10-monitoring.html-B9YmIXXL.js @@ -0,0 +1,18 @@ +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as i,c as r,b as e,d as a,a as n,e as o}from"./app-Bzk8Nrll.js";const c={},p=e("h1",{id:"_10-tomcat-모니터링",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_10-tomcat-모니터링"},[e("span",null,"10. Tomcat 모니터링")])],-1),m=e("ul",null,[e("li",null,"모니터링은 왜 하나?"),e("li",null,"톰캣 기본 모니터링 툴"),e("li",null,"psi-Probe"),e("li",null,"jkstatus"),e("li",null,"visualVM"),e("li",null,"JMC"),e("li",null,"APM")],-1),d=e("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/1IAghXNby-Y",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),u=o('

    10.1 모니터링은 왜 하나?

    무엇인가에 대한 모니터링은 그 대상의 상태를 확인하기 위함입니다. 문제가 있는지, 어떤 동작을 하고 있는지, 알아야 할 내용이 있다면 그 사항을 알수 있도록 하는, 즉 대상의 상태를 감시하는 것입니다.

    톰캣을 사용하여 서비스를 제공하는 입장에서는 톰캣의 상태를 감시할 수 있어야 합니다. 톰캣의 작업 상태나 자원의 상태, 특정 문제 상황이 발생하는 징조를 파악하는 것입니다. 모니터링을 잘 수행하면 더나은 서비스와 서비스 장애로 인한 손실을 예방할 수 있습니다.


    10.2 톰캣 기본 모니터링 툴

    톰캣에는 기본적으로 제공하는 모니터링 툴이 있습니다. 자세하지는 않더라도 필요한 만큼의 정보를 제공합니다.

    10.2.1 Manager APP

    앞서 어플리케이션의 배치를 통해 알아보았던 Manager APP에서는 다음의 정보를 확인 할 수 있습니다. 만약 호스트가 여러개라면 해당 Manager APP 어플리케이션을 별도로 배치하여 해당 호스트의 배치 정보를 확인 할 수 있습니다.

    그리고 상단의 Server Status링크를 통해 이동하면 다음의 정보를 확인 할 수 있습니다.

    배치된 어플리케이션의 Session의 숫자에 링크된 페이지에서는 현재 생성된 세션의 정보와 해당 세션을 강제 종료시킬 수 있는 Sessoin Administration을 제공합니다.

    10.2.2 host manager

    host manager에서는 다음의 정보를 확인 할 수 있습니다.

    10.2.3 admin (톰캣 5.5)

    톰캣 5.5 버전에서는 admin 어플리케이션이 제공됩니다. 다운로드의 아카이브 사이트에서 확인 가능하며 톰캣을 다운받기위한 버전하위에 apache-tomcat-x.x.xx-admin 이름을 갖는 파일이 있습니다. 여타 WAS에서 제공되는 만큼의 웹 콘솔 UI를 제공하는 관리 툴로서 톰캣 내에 설정과 각 항목의 정보를 파악할 수 있습니다. 다만 톰캣 5.5 이후로는 제공되지 않습니다.


    10.3 pis-Probe

    ',20),h={href:"http://code.google.com/p/psi-probe",target:"_blank",rel:"noopener noreferrer"},g=o("

    psi-probe는 예전에 lambda probe였으나 현재 구글에서 관리하기 시작한 후로 명칭이 변경되었습니다.

    psi-probe의 공식 웹 사이트의 다운로드 항목에서 파일을 받아 압축을 풀면 probe.war 웹 어플리케이션이 있습니다. 해당 어플리케이션을 톰캣에 배치하면 psi-probe를 실행할 수 있으며 tomcat-user.xmlmanager권한을 갖는 사용자로 접근하게 됩니다.

    ",2),_={href:"https://code.google.com/p/psi-probe/wiki/Features",target:"_blank",rel:"noopener noreferrer"},k=e("code",null,"psi-probe",-1),v=o(`

    앞서 텍스트로만 표현되던 정보들도 보다 보기좋게 제공하고 각 자원이나 설정을 파악하는데 있어서 기본 톰캣 모니터링 툴보다 나은 기능을 제공합니다. 다만 일부 모니터링 항목은 5.5까지를 지원하고 톰캣 8.0에 대한 지원이 불가능하며 2013년 3월 이후로 업데이트가 없다는 점이 단점입니다.


    10.4 jkstatus

    jkstatus는 mod_jk를 사용하여 연동한 경우 아파치에서 확인할 수 있는 톰켓 연동에 대한 모니터링 툴 입니다.

    사용을 위해서 worker.propertiesstatus 워커를 추가합니다.

    worker.list=tomcat1,tomcat2,loadbalancer,status
    +...
    +worker.status.type=status
    +

    그리고 uri.properties에 요청을 수행할 경로를 워커에 맵핑합니다.

    ...
    +/jkstatus=status
    +

    설정 후 아파치를 재기동하면 아파치 요청 url의 컨텍스트에 설정한 요청 경로를 입력하여 'jkstatus' 툴을 확인 할 수 있습니다. 앞서 80으로 요청하는 아파치의 경우 다음과 같이 요청 할 수 있습니다.

    `,9),b={href:"http://tomcat.apache.org/connectors-doc/reference/status.html",target:"_blank",rel:"noopener noreferrer"},f=o('
    jkstatus
    jkstatus

    jkstatus로 확인할 수 있는 정보는 다음과 같습니다.


    10.5 visualVM

    ',5),M={href:"http://visualvm.java.net",target:"_blank",rel:"noopener noreferrer"},j=o(`

    JDK에는 $JAVA_HOMe/bin/jvisualvm에 실행파일이 위치합니다.

    visualVM의 장점중 하나는 플러그인 입니다. 현재까지도 상당수의 플러그인이 제공되고 있으며 플러그인 API가 공개되어 있어 원하는 모니터링 플러그인을 생성할 수도 있습니다.

    자바 프로세스는 자체적으로 로컬환경에서는 visualVM에 자동으로 인지되게 됩니다. 따라서 수행중인 JVM 프로세스는 visualVM의 Local항목에 감지되어 목록에 나타납니다. 그리고 원격지의 JVM 프로세스 또한 JMX(Java Monitoring Extension)을 통해 로컬의 visualVM에서 모니터링 할 수 있습니다. 서비스로 등록된 로컬의 톰캣은 프로세스가 보이지 않기 때문에 리모트로 구성하는 방법을 따릅니다. 톰캣의 리모트 구성 방법은 두가지가 있습니다.

    1. Java 기본 JMX 설정
      Java에서는 옵션을 통해 JMX를 활성화하고 설정 할 수 있습니다. 스크립트에 다음의 JMX의 옵션을 설정합니다.

      #setenv.sh
      +CATALINA_OPTS="-Dcom.sun.management.jmxremote
      +-Dcom.sun.management.jmxremote.port=18080
      +-Dcom.sun.management.jmxremote.ssl=false
      +-Dcom.sun.management.jmxremote.authenticate=false"
      +

      이 후 visualVM의 'Remote'에 플랫폼 IP를 등록하고 우클릭을 하면 'Add JMX connection...'을 통해 원격지의 톰캣을 등록할 수 있습니다.

    2. jmx remote 모듈
      톰캣에서는 jmx를 위한 모듈을 제공합니다. 톰캣의 다운로드에 보면 Extra항목에 Remote JMX jar가 있습니다.

    Java에서는 옵션을 통해 JMX를 활성화하고 설정 할 수 있습니다. 스크립트에 다음의 JMX의 옵션을 설정합니다.

    #setenv.sh
    +CATALINA_OPTS="-Dcom.sun.management.jmxremote.ssl=false
    +-Dcom.sun.management.jmxremote.authenticate=false"
    +

    그리고 server.xmlListener 디스크립터로 JmxRemoteLifecycleListener를 추가합니다.

    <Server port="8005" shutdown=“SHUTDOWN">
    +
    +       <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
    +                 rmiRegistryPortPlatform="10001" rmiServerPortPlatform="10002" />
    +

    설정된 톰캣은 visualVM의 Remote 등록시 다음의 정보로 접근 정보를 생성합니다.
    service:jmx:rmi://192.168.56.101:10002/jndi/rmi://192.168.56.101:10001/jmxrmi

    로컬이나 리모트에 설정된 톰캣 프로세스는 좌측의 네비게이션에 나타나며 해당 항목을 더블클릭하여 우측 화면에서 모니터링 하게 됩니다.

    visualVM에서는 기본적으로 다음의 기능을 제공합니다.

    이외에도 플러그인에서 제공하는 기능을 활용한 다양한 모니터링이 가능합니다.


    10.6 JMC

    `,15),x={href:"http://www.oracle.com/technetwork/java/javase/2col/jmc-relnotes-2004763.html",target:"_blank",rel:"noopener noreferrer"},A=e("p",null,"JMC(Java Mision control)은 bea사에서 만든 별도의 JDK인 JRockit에서 제공하던 모니터링 툴입니다. 현재는 bea가 오라클사에서 인수하면서 관련 소프트웨어도 오라클이 관리하고 있으며 관련하여 Sun사도 인수하면서 기존 Sun Hotspot JDK와 JRockit의 장점을 합친 결과로 여러 기능이 추가되고 있습니다. 특히 JDK 7에서 많은 변화가 있었으며 여기에 JMC가 포함되었습니다.",-1),J=e("p",null,[a('JDK 7 이상의 버전에서 "$JAVA_HOME/bin/jmc"로 실행시키며, 수행된 툴은 다음과 같은 모습을 갖고 있습니다.'),e("br"),e("img",{src:"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/jmc.png?token=ADUAZXOHGPY4RMTZNBFBJYK67EUUS",alt:"jmc",loading:"lazy"})],-1),P=e("p",null,"visualVM과 거의 비슷한 정도의 모니터링 기능을 제공하는 JMC의 대표적인 특징은 GC 정책에 따른 모니터링 탭의 변화 입니다. GC 정책은 기본 Parellel GC외에도 필요에 따라 CMS(Concurrent Mark Sweep)이나 G1 정책이 사용될 수 있는데 이런 GC 정책에 따른 뷰가 변경됨이 큰 특징입니다.",-1),w=e("hr",null,null,-1),y=e("h2",{id:"_10-7-apm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_10-7-apm"},[e("span",null,"10.7 APM")])],-1),T={href:"https://github.com/scouter-project/scouter",target:"_blank",rel:"noopener noreferrer"};function S(V,C){const t=l("ExternalLinkIcon");return i(),r("div",null,[p,m,d,u,e("p",null,[e("a",h,[a("http://code.google.com/p/psi-probe"),n(t)])]),g,e("p",null,[e("a",_,[a("ip:port/probe"),n(t)]),a("'와 같이 톰캣에 요청하면 "),k,a("가 제공하는 톰캣의 모니터링과 관리를 수행하는 어플리케이션을 확인 할 수 있습니다.")]),v,e("p",null,[a("'"),e("a",b,[a("ip:port/jkstatus"),n(t)]),a("'")]),f,e("p",null,[a("visualVM은 톰캣만이 아닌 JVM 전반에 대해 모니터링을 제공하는 툴로서 제공됩니다. Oracle JDK 1.6.0_18 버전 이상부터는 기본으로 포함되어 있고 별도의 다운로드를 위해서는 관련 공식 페이지인 '"),e("a",M,[a("http://visualvm.java.net"),n(t)]),a("'을 통해 받을 수 있습니다.")]),j,e("p",null,[e("a",x,[a("http://www.oracle.com/technetwork/java/javase/2col/jmc-relnotes-2004763.html"),n(t)])]),A,J,P,w,y,e("p",null,[a("APM은 Application Performence Manager의 약자로 모니터링의 역할과 더불어 어플리케이션의 성능을 향샹시킬 목적으로 사용되는 별도의 어플리케이션입니다. WAS의 상용 APM 으로는 Jennifer, Pharos, Performizer 등이 있고 Opensource로 "),e("a",T,[a("Scouter"),n(t)]),a("가 대표적입니다.")])])}const U=s(c,[["render",S],["__file","10-monitoring.html.vue"]]),q=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/10-monitoring.html","title":"10. Tomcat 모니터링","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/10-monitoring.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"10. Tomcat 모니터링"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/jkstatus.png?token=ADUAZXOQFPMD3J65EB7NTBC67EUVU"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"10. Tomcat 모니터링"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"10. Tomcat 모니터링\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/jkstatus.png?token=ADUAZXOQFPMD3J65EB7NTBC67EUVU\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/jmc.png?token=ADUAZXOHGPY4RMTZNBFBJYK67EUUS\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"10.1 모니터링은 왜 하나?","slug":"_10-1-모니터링은-왜-하나","link":"#_10-1-모니터링은-왜-하나","children":[]},{"level":2,"title":"10.2 톰캣 기본 모니터링 툴","slug":"_10-2-톰캣-기본-모니터링-툴","link":"#_10-2-톰캣-기본-모니터링-툴","children":[{"level":3,"title":"10.2.1 Manager APP","slug":"_10-2-1-manager-app","link":"#_10-2-1-manager-app","children":[]},{"level":3,"title":"10.2.2 host manager","slug":"_10-2-2-host-manager","link":"#_10-2-2-host-manager","children":[]},{"level":3,"title":"10.2.3 admin (톰캣 5.5)","slug":"_10-2-3-admin-톰캣-5-5","link":"#_10-2-3-admin-톰캣-5-5","children":[]}]},{"level":2,"title":"10.3 pis-Probe","slug":"_10-3-pis-probe","link":"#_10-3-pis-probe","children":[]},{"level":2,"title":"10.4 jkstatus","slug":"_10-4-jkstatus","link":"#_10-4-jkstatus","children":[]},{"level":2,"title":"10.5 visualVM","slug":"_10-5-visualvm","link":"#_10-5-visualvm","children":[]},{"level":2,"title":"10.6 JMC","slug":"_10-6-jmc","link":"#_10-6-jmc","children":[]},{"level":2,"title":"10.7 APM","slug":"_10-7-apm","link":"#_10-7-apm","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.2,"words":360},"filePathRelative":"05-Software/Tomcat/tomcat101/10-monitoring.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n"}');export{U as comp,q as data}; diff --git a/assets/11-pipelines.html-C2Sm7x5b.js b/assets/11-pipelines.html-C2Sm7x5b.js new file mode 100644 index 0000000000..de94671b90 --- /dev/null +++ b/assets/11-pipelines.html-C2Sm7x5b.js @@ -0,0 +1,80 @@ +import{_ as e,a as i,b as t,c as p,d as o,e as l,f as c}from"./1564557613406-aoabhQ4f.js";import{_ as u}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as d,c as g,b as n,d as m,a as v,e as a}from"./app-Bzk8Nrll.js";const k={},b=a(`

    11. Pipelines

    11.1 Automating deployment with pipelines

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-01.AutomatingDeploymentWithPipelines)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh 'echo "Test Hello World!"'
    +            }
    +        }
    +    }
    +}
    +

    두개의 Stage를 갖는 Pipeline 스크립트입니다. Pipeline은 빌드 수행시의 각 단계를 구분하여 빌드의 과정을 확인하고 실패에 따른 단계별 확인이 가능합니다.

    좌측 Build Now를 클릭하여 빌드를 수행하면 빌드에 대한 결과는 Stage 별로 성공 실패의 여부와 로그를 확인할 수 있도록 Stage View가 UI로 제공됩니다. Stage 별로 Stage View는 기록되며, Stage에 변경이 있거나 이름이 변경되는 경우에는 해당 UI에 변경이 발생하여 기존 Pipeline 기록을 보지 못할 수 있습니다.

    1564547435978
    1564547435978

    11.2 Creating pipeline gates

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-02.CreatingPipelineGates)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('BuildMore'){
    +            steps {
    +                input message: "Shall we build more?"
    +                sh '''
    +                    echo "We are approved; continue!"
    +                    ls -lah
    +                '''
    +            }
    +        }
    +    }
    +}
    +

    개의 Stage를 갖는 Pipeline 스크립트입니다. 두번째 Stage에 input 스크립트가 있습니다. 이 스크립트가 추가되면 Pipeline을 진행하면서 해당하는 동작을 수행할 것인지, 마치 승인 작업과 같은 동작을 수행할 수 있습니다.

    좌측 Build Now를 클릭하여 빌드를 수행하면 두번째 Stage에서 해당 작업을 수행할 지에 대한 물음을 확인 할 수 있습니다.

    1564547694870
    1564547694870

    Abort를 선택하면 빌드 취소와 같은 동작으로 실패로 처리되지는 않습니다.

    11.3 Job promotion for long-running pipeline

    빌드 단계를 구현할 때 Pipeline 스크립트로 하나의 프로젝트 내에서 모든 동작을 정의 할 수도 있지만 서로다른 Job을 연계하고, 승인 절차를 따르도록 구성할 수 있습니다.

    Job promotion 기능을 사용하기 위한 플러그인을 설치합니다.

    FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-one)

    11-03.Job-one 빌드 후 승인에 대한 다음 빌드를 진행할 FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-two)

    11-03.Job-one에 대한 빌드를 수행합니다. 수행 완료 후 빌드 히스토리의 최근 빌드를 클릭(e.g. #1)하면 Promotion Status에 승인절차를 기다리고 있음을 확인할 수 있습니다. Parameters 항목의 approve를 체크하고 APPROVE버튼을 클릭합니다.

    1564554095622
    1564554095622

    승인이 완료되면 해당 프로젝트의 승인에 대한 이벤트를 통해 빌드를 수행하는 11-03.Job-two가 이어서 빌드됨을 확인 할 수 있습니다.

    11.4 Multibranch repository automation

    SCM의 Multibranch를 빌드하는 과정에 대해 설명합니다.

    다음의 GitHub repository를 fork 합니다.

    ',30),h={href:"https://github.com/Great-Stone/multibranch-demo",target:"_blank",rel:"noopener noreferrer"},y=a('

    Multibranch Pipeline 형태의 Item을 생성합니다. (e.g. 11-04.MultibranchRepositoryAutomation)

    저장 후에는 자동적으로 모든 브랜치의 소스를 빌드 수행합니다.

    1564555063361
    1564555063361
    1564554995103
    1564554995103

    SCM에서 브랜치를 여러개 관리하고 모두 빌드와 테스팅이 필요하다면 Multibranch 프로젝트를 생성하여 등록하고, 빌드 관리가 가능합니다.

    11.5 Creating pipeline with snippets

    Pipeline 을 스크립트를 작성하는 방법을 배워봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-05. CreatingPipelineWithSnippets)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage("Hello") {
    +            steps {
    +                echo 'Hello World'
    +            }
    +        }
    +    }
    +}
    +

    echo가 동작할때 시간을 기록하도록 스크립트를 수정해보겠습니다.

    빌드를 수행하고 로그를 확인해 봅니다. echo 동작이 수행 될때 시간이 함께 표기되는 것을 확인 할 수 있습니다.

    1564555730104
    1564555730104

    11.6 Discovering global pipeline variables

    Pipeline에서 사용할 수 있는 변수를 확인하고 사용하는 방법을 알아봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-06.DiscoveringGlobalPipelineVariables)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +               echo "We are in build \${currentBuild.number}"
    +               echo "Our current result is \${currentBuild.currentResult}"
    +            }
    +        }
    +        stage('BuildMore'){
    +            steps {
    +               echo "Name of the project is \${currentBuild.projectName}"
    +            }
    +        }
    +        stage('BuildEnv'){
    +            steps {
    +                echo "Jenkins Home : \${env.JENKINS_HOME}"
    +            }
    +        }
    +    }
    +}
    +

    Pipeline 스크립트에서 사용가능한 변수와 사용방법은 Pipeline Syntax 링크의 Global Variables Reference 항목에서 확인 가능합니다.

    1564557613406
    1564557613406
    ',20);function f(_,P){const s=r("ExternalLinkIcon");return d(),g("div",null,[b,n("ul",null,[n("li",null,[n("a",h,[m("https://github.com/Great-Stone/multibranch-demo"),v(s)])])]),y])}const q=u(k,[["render",f],["__file","11-pipelines.html.vue"]]),J=JSON.parse(`{"path":"/05-Software/Jenkins/pipeline101/11-pipelines.html","title":"11. Pipelines","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/11-pipelines.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"11. Pipelines"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"11. Pipelines\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"11.1 Automating deployment with pipelines","slug":"_11-1-automating-deployment-with-pipelines","link":"#_11-1-automating-deployment-with-pipelines","children":[]},{"level":2,"title":"11.2 Creating pipeline gates","slug":"_11-2-creating-pipeline-gates","link":"#_11-2-creating-pipeline-gates","children":[]},{"level":2,"title":"11.3 Job promotion for long-running pipeline","slug":"_11-3-job-promotion-for-long-running-pipeline","link":"#_11-3-job-promotion-for-long-running-pipeline","children":[]},{"level":2,"title":"11.4 Multibranch repository automation","slug":"_11-4-multibranch-repository-automation","link":"#_11-4-multibranch-repository-automation","children":[]},{"level":2,"title":"11.5 Creating pipeline with snippets","slug":"_11-5-creating-pipeline-with-snippets","link":"#_11-5-creating-pipeline-with-snippets","children":[]},{"level":2,"title":"11.6 Discovering global pipeline variables","slug":"_11-6-discovering-global-pipeline-variables","link":"#_11-6-discovering-global-pipeline-variables","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.43,"words":428},"filePathRelative":"05-Software/Jenkins/pipeline101/11-pipelines.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    11.1 Automating deployment with pipelines

    \\n

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-01.AutomatingDeploymentWithPipelines)

    \\n

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    \\n
    pipeline {\\n    agent any\\n    stages {\\n        stage('Build') {\\n            steps {\\n                sh 'echo \\"Hello World\\"'\\n            }\\n        }\\n        stage('Test') {\\n            steps {\\n                sh 'echo \\"Test Hello World!\\"'\\n            }\\n        }\\n    }\\n}\\n
    "}`);export{q as comp,J as data}; diff --git a/assets/11-tip.html-DaXXBiss.js b/assets/11-tip.html-DaXXBiss.js new file mode 100644 index 0000000000..56bf8171e9 --- /dev/null +++ b/assets/11-tip.html-DaXXBiss.js @@ -0,0 +1,5 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as n,b as t,e as o}from"./app-Bzk8Nrll.js";const s={},c=t("h1",{id:"_11-tomcat-팁",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_11-tomcat-팁"},[t("span",null,"11. Tomcat 팁")])],-1),p=t("ul",null,[t("li",null,"디렉토리"),t("li",null,"setenv"),t("li",null,"실행 유저"),t("li",null,"Connector")],-1),r=t("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/zY0pDLOZ_7U",frameborder:"0",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1),l=o(`

    11.1 디렉토리

    하나의 장비에는 둘 이상의 톰캣을 운영하는 경우가 발생합니다. 이경우 설정 파일을 별도로 생성하고 스크립트를 통해 해당 설정을 읽게 하는 식의 방법을 사용할 수도 있지만 기본 제공되는 스크립트가 수정되는 양이 많을수록 관리의 난이도가 증가합니다. 따라서 톰캣의 프로세스를 여러개 기동하기 위한 방법으로 권장하는 것을 $CATALINA_HOME을 단순히 여러개 만드는 것입니다. 톰캣은 그 자체 용량이 그리 크지 않기 때문에 여러개 복사한다하여도 큰 무리 없이 사용가능한 가벼운 엔진 입니다. 따라서 설정 파일을 추가로 구성하고 스크립트를 수정하는 방법 보다는 기본 톰캣 엔진 전체를 복사하는 것을 권장합니다.

    11.2 setenv

    앞서 옵션을 적용함에 있어 강조한 setenv를 이야기 하고자 합니다. 톰캣에 대한 서비스를 지원하다보면 주로 catalina.sh(bat)를 수정하는 경우가 대부분이고 현재가지는 운영중인 서비스에 setenv사용한 사례찾기는 힘든것 같습니다. 하지만 catalina.sh(bat) 스크립트에서도 명시하듯 해당 스크립트를 수정하는 것은 설정한다는 이유 외에는 단점이 더 많기 때문에 반드시 setenv를 통해 추가적인 스크립트 추가 설정을 권장합니다.

    11.3 실행 유저

    Unix/Linux/Mac 플랫폼에서 톰캣이 별도의 계정으로 구성되어 있지만 간혹 root 계정으로 실수로 기동하는 경우가 발생합니다. 톰캣에서는 server.xml에 다음의 설정으로 root 로의 기동을 방지 할 수 있습니다.

    <Server port="8005" shutdown="SHUTDOWN">
    +  <Listener className="org.apache.catalina.security.SecurityListener" checkedOsUsers="root" />
    +  ...
    +

    이러한 Listener 디스크립터의 설정으로 root 계정의 실행을 방지하며, 만약 root로 기동하는 경우 다음과 같은 메시지를 발생시킵니다.

    java.lang.Error: Start attempted while running as user [root]. Running Tomcat as this user has been blocked by the Lifecycle listener org.apache.catalina.security.SecurityListener (usually configured in CATALINA_BASE/conf/server.xml)
    +

    11.4 Connector

    Connector 디스크립터로 정의되는 프로토콜에 대한 정의는 톰캣이 요청을 받아들이는 통로를 설정하는 것이기 때문에 주요 설정 중 하나 입니다. 앞서 살펴본 쓰레드 설정외에도 도움이 될만한 옵션에 대한 내용은 다음과 같습니다.

    옵션기능 설명
    acceptCount="10"request Queue의 길이를 정의
    : idle thread가 없으면 queue에서 idle thread가 생길때 까지 요청을 대기하는 queue의 길이
    : 요청을 처리할 수 없는 상황이면 빨리 에러 코드를 클라이언트에게 보내서 에러처리 표시
    enableLookups="false"Servlet/JSP 코드 중에서 들어오는 http request에 대한 ip를 조회 하는 명령등이 있을 경우 DNS 이름을 IP주소로 바꾸기 위해서 DNS 서버에 look up 요청을 보냄
    : 서버간의 round trip 발생을 막을 수 있음
    compression="off"HTTP message body를 gzip 형태로 압축해서 리턴하지 않음
    maxConnection="8192"하나의 톰캣인스턴스가 유지할 수 있는 Connection의 수를 정의
    : 현재 연결되어 있는 실제 Connection의 수가 아니라 현재 사용중인 socket fd (file descriptor)의 수
    maxKeepAliveRequest="1"HTTP 1.1 Keep Alive Connection을 사용할 때, 최대 유지할 Connection 수를 결정하는 옵션
    : Keep Alive를 사용할 환경이 아닌 경우에 설정
    tcpNoDelay="true"TCP 프로토콜은 기본적으로 패킷을 보낼때 바로 보내지 않음
    : 버퍼사이즈에 데이터가 모두 담길때까지 패킷 전송을 보류함으로 대기 시간이 발생하는 것을 방지
    : 트래픽이 증가하지만 현 망 속도를 고려하였을 때 문제가 크지 않음
    `,13),i=[c,p,r,l];function u(d,m){return a(),n("div",null,i)}const g=e(s,[["render",u],["__file","11-tip.html.vue"]]),_=JSON.parse('{"path":"/05-Software/Tomcat/tomcat101/11-tip.html","title":"11. Tomcat 팁","lang":"ko-KR","frontmatter":{"description":"Tomcat","tag":["Tomcat","Java"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Tomcat/tomcat101/11-tip.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"11. Tomcat 팁"}],["meta",{"property":"og:description","content":"Tomcat"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Tomcat"}],["meta",{"property":"article:tag","content":"Java"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"11. Tomcat 팁\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":3,"title":"11.1 디렉토리","slug":"_11-1-디렉토리","link":"#_11-1-디렉토리","children":[]},{"level":3,"title":"11.2 setenv","slug":"_11-2-setenv","link":"#_11-2-setenv","children":[]},{"level":3,"title":"11.3 실행 유저","slug":"_11-3-실행-유저","link":"#_11-3-실행-유저","children":[]},{"level":3,"title":"11.4 Connector","slug":"_11-4-connector","link":"#_11-4-connector","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.54,"words":162},"filePathRelative":"05-Software/Tomcat/tomcat101/11-tip.md","localizedDate":"2021년 12월 24일","excerpt":"\\n\\n\\n
    \\n

    11.1 디렉토리

    "}');export{g as comp,_ as data}; diff --git a/assets/12-appendix.html-DYgAE8cJ.js b/assets/12-appendix.html-DYgAE8cJ.js new file mode 100644 index 0000000000..985a7ba2b2 --- /dev/null +++ b/assets/12-appendix.html-DYgAE8cJ.js @@ -0,0 +1,7 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o,c as s,b as e,d as t,a as r,e as l}from"./app-Bzk8Nrll.js";const c={},d=l(`

    Apendix

    GitHub SCM 연동 이슈

    GitHub를 SCM으로 사용하는 경우 다음과 같은 메시지가 출력되면서 진행되지 않는 경우가 있습니다.

    GitHub API Usage: Current quota has 5000 remaining (447380 over budget). Next quota of 5000 in 5 days 0 hr. Sleeping for 4 days 23 hr.
    +14:07:33 GitHub API Usage: The quota may have been refreshed earlier than expected, rechecking...
    +

    이 경우 서버 시간과 GitHub의 시간이 맞지 않아 발생할 수 있는 이슈 입니다. ntpdate를 재설정 합니다.

    `,5),p=e("li",null,[e("p",null,"RHEL7 : ntpd를 재시작 합니다."),e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,`$ systemctl restart ntpd +`)])])],-1),h=e("br",null,null,-1),m={href:"https://access.redhat.com/solutions/4130881",target:"_blank",rel:"noopener noreferrer"},u=e("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[e("pre",{class:"language-bash"},[e("code",null,[t(`$ systemctl stop chronyd +$ chronyd `),e("span",{class:"token parameter variable"},"-q"),t(` +$ systemctl start chronyd +`)])])],-1),g=e("h2",{id:"유용한-플러그인",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#유용한-플러그인"},[e("span",null,"유용한 플러그인")])],-1),x=e("ul",null,[e("li",null,"Restart Safely : Jenkins를 재기동해야하는 경우 빌드가 수행중이지 않을 때 자동으로 Restart 시켜줍니다. 설치 후에는 왼쪽 주 메뉴에 표시됩니다."),e("li",null,"ThinBackup : Jenkins의 구성을 백업, 복구할 수 있는 기능을 제공합니다. 백업 주기나 백업 개수등을 정의 할 수 있습니다.")],-1);function _(b,y){const a=i("ExternalLinkIcon");return o(),s("div",null,[d,e("ul",null,[p,e("li",null,[e("p",null,[t("RHEL8 : RHEL8에서는 ntpdate를 사용하지 않고 chronyd가 대신합니다."),h,e("a",m,[t("https://access.redhat.com/solutions/4130881"),r(a)])]),u])]),g,x])}const v=n(c,[["render",_],["__file","12-appendix.html.vue"]]),S=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/12-appendix.html","title":"Apendix","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/12-appendix.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Apendix"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Apendix\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"GitHub SCM 연동 이슈","slug":"github-scm-연동-이슈","link":"#github-scm-연동-이슈","children":[]},{"level":2,"title":"유용한 플러그인","slug":"유용한-플러그인","link":"#유용한-플러그인","children":[]}],"git":{"createdTime":1640327880000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.31,"words":93},"filePathRelative":"05-Software/Jenkins/pipeline101/12-appendix.md","localizedDate":"2021년 12월 24일","excerpt":"\\n

    GitHub SCM 연동 이슈

    \\n

    GitHub를 SCM으로 사용하는 경우 다음과 같은 메시지가 출력되면서 진행되지 않는 경우가 있습니다.

    \\n
    GitHub API Usage: Current quota has 5000 remaining (447380 over budget). Next quota of 5000 in 5 days 0 hr. Sleeping for 4 days 23 hr.\\n14:07:33 GitHub API Usage: The quota may have been refreshed earlier than expected, rechecking...\\n
    "}');export{v as comp,S as data}; diff --git a/assets/13-jenkins_101_single.html-Dgqr7ZGI.js b/assets/13-jenkins_101_single.html-Dgqr7ZGI.js new file mode 100644 index 0000000000..5ff08a4d03 --- /dev/null +++ b/assets/13-jenkins_101_single.html-Dgqr7ZGI.js @@ -0,0 +1,531 @@ +import{_ as p,a as c,b as d,c as r,d as u,e as v}from"./1563945539114-BeDUHaoS.js";import{_ as g,a as k,b as m,c as b,d as h,e as f,f as y,g as _}from"./1564365395583-BYzBww3x.js";import{_ as S,a as x,b as P,c as J}from"./1564449679656-jDgy1EGt.js";import{_ as C}from"./1564450122219-xixIqXDz.js";import{_ as T,a as I,b as A,c as w}from"./1564465713857-Bb9GicK7.js";import{_ as D,a as j}from"./1564471729123-DnEyDLm_.js";import{_ as q,a as E,b as R,c as B,d as M,e as U,f as G}from"./1564543591394-BKf8t37i.js";import{_ as H,a as L,b as N,c as O}from"./1564546697375-DhhAeZD5.js";import{_ as $,a as z,b as W,c as V,d as F,e as K,f as Z}from"./1564557613406-aoabhQ4f.js";import{_ as Y}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as X,c as Q,b as n,d as s,a as e,w as nn,e as a}from"./app-Bzk8Nrll.js";const sn={},en=a(`

    Pipeline on Jenkins 101 (Single Page)

    Update at 31 Jul, 2019

    Introduction

    Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

    CategoryNameVersion
    VMVirtualBox6.0.10
    OSRed Hat Enterprise Linux8.0.0
    JDKRed Hat OpenJDK1.8.222
    JenkinsJenkins rpm2.176.2

    Jenkins 실행 및 구성

    Jenkins를 실행 및 구성하기위한 OS와 JDK가 준비되었다는 가정 하에 진행합니다. 필요 JDK 버전 정보는 다음과 같습니다.

    필요 JDK를 설치합니다.

    $ subscription-manager repos --enable=rhel-8-for-x86_64-baseos-rpms --enable=rhel-8-for-x86_64-appstream-rpms
    +
    +### Java JDK 8 ###
    +$ yum -y install java-1.8.0-openjdk-devel
    +
    +### Check JDK version ###
    +$ java -version
    +openjdk version "1.8.0_222"
    +OpenJDK Runtime Environment (build 1.8.0_222-b10)
    +OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
    +

    Red Hatsu/Fedora/CentOS 환경에서의 Jenkins 다운로드 및 실행은 다음의 과정을 수행합니다.

    `,11),an={href:"https://pkg.jenkins.io/redhat-stable/",target:"_blank",rel:"noopener noreferrer"},tn=a(`

    패키지로 설치된 Jenkins의 설정파일은 /etc/sysconfig/jenkins에 있습니다. 해당 파일에서 실행시 활성화되는 포트 같은 설정을 변경할 수 있습니다.

    ## Type:        integer(0:65535)
    +## Default:     8080
    +## ServiceRestart: jenkins
    +#
    +# Port Jenkins is listening on.
    +# Set to -1 to disable
    +#
    +JENKINS_PORT="8080"
    +

    외부 접속을 위해 Jenkins에서 사용할 포트를 방화벽에서 열어줍니다.

    $ firewall-cmd --permanent --add-port=8080/tcp
    +$ firewall-cmd --reload
    +

    서비스를 부팅시 실행하도록 활성화하고 Jenkins를 시작합니다.

    $ systemctl enable jenkins 
    +$ systemctl start jenkins
    +

    실행 후 브라우저로 접속하면 Jenkins가 준비중입니다. 준비가 끝나면 Unlock Jenkins 페이지가 나오고 /var/lib/jenkins/secrets/initialAdminPassword의 값을 입력하는 과정을 설명합니다. 해당 파일에 있는 토큰 복사하여 붙여넣습니다.

    이후 과정은 Install suggested plugins를 클릭하여 기본 플러그인을 설치하여 진행합니다. 경우에 따라 Select plugins to install을 선택하여 플러그인을 지정하여 설치할 수 있습니다.

    플러그인 설치 과정을 선택하여 진행하면 Getting Started 화면으로 전환되어 플러그인 설치가 진행됩니다.

    설치 후 기본 Admin User 를 생성하고, 접속 Url을 확인 후 설치과정을 종료합니다.

    GitHub 계정생성

    진행되는 실습에서는 일부 GitHub를 SCM으로 연동합니다. 원활한 진행을 위해 GitHub계정을 생성해주세요. 또는 별개의 Git 서버를 구축하여 사용할 수도 있습니다.

    Jenkins Theme (Optional)

    Jenkins는 간단히 테마와 회사 CI를 적용할 수 있는 플러그인이 제공됩니다.

    기본 Jenkins 테마를 변경하기 위해서는 다음의 과정을 수행합니다.

    `,17),ln={href:"http://afonsof.com/jenkins-material-theme/",target:"_blank",rel:"noopener noreferrer"},on=a("
  • Build your own theme with a company logo! 에서 색상과 로고를 업로드 합니다.

  • DOWNLOAD YOUR THEME!버튼을 클릭하면 CSS파일이 다운됩니다.

  • Jenkins 관리로 이동하여 시스템 설정를 클릭합니다.

  • Theme항목의 Theme elements의 드롭다운 항목에서 Extra CSS를 클릭하고 앞서 다운받은 CSS파일의 내용을 붙여넣고 설정을 저장하면 적용된 테마를 확인할 수 있습니다.

  • ",4),pn=n("h2",{id:"_1-ci-cd",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-ci-cd"},[n("span",null,"1. CI/CD")])],-1),cn=n("p",null,[n("strong",null,"CI/CD Concept Definitions")],-1),dn=n("ul",null,[n("li",null,"Continuous integration"),n("li",null,"Continuous delivery"),n("li",null,"Continuous deployment"),n("li",null,"Source control management (SCM)")],-1),rn=a('

    Delivery vs Deployment

    Jenkins for CI/CD

    2. Jobs

    Job and Project

    프로젝트는 Job의 일부 입니다. 즉, 모든 프로젝트가 Job이지만 모든 Job이 프로젝트는 아닙니다. Job의 구조는 다음과 같습니다.

    ',7),un=a(`

    FreeStyleProejct, MatrixProject, ExternalJob만 New job에 표시됩니다.

    Step 1. New pipeline

    Step 1에서는 stage없이 기본 Pipeline을 실행하여 수행 테스트를 합니다.

    1. Jenkins 로그인

    2. 좌측 새로운 Item 클릭

    3. Enter an item name에 Job 이름 설정 (e.g. 2.Jobs)

    4. Pipeline 선택 후 OK 버튼 클릭

    5. Pipeline 항목 오른 쪽 Try sample Pipelie...클릭하여 Hello world 클릭 후 저장

      node {
      +   echo 'Hello World'
      +}
      +
    6. 좌측 Build now클릭

    7. 좌측 Build History의 최근 빌드된 항목(e.g. #1) 우측에 마우스를 가져가면 dropdown 버튼이 생깁니다. 해당 버튼을 클릭하여 Console Output 클릭

    8. 수행된 echo 동작 출력을 확인합니다.

      Started by user GyuSeok.Lee
      +Running in Durability level: MAX_SURVIVABILITY
      +[Pipeline] Start of Pipeline
      +[Pipeline] node
      +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
      +[Pipeline] {
      +[Pipeline] echo
      +Hello World
      +[Pipeline] }
      +[Pipeline] // node
      +[Pipeline] End of Pipeline
      +Finished: SUCCESS
      +

    Step 2. New pipeline

    Step 2에서는 stage 를 구성하여 실행합니다.

    1. 기존 생성한 Job 클릭 (e.g. 02-02.Jobs)

    2. 좌측 구성을 클릭하여 Pipeline 스크립트를수정합니다.

      pipeline{
      +    agent any
      +    stages {
      +        stage("Hello") {
      +            steps {
      +                echo 'Hello World'
      +            }
      +        }
      +    }
      +}
      +
    3. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

    4. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

      1563942302074
      1563942302074
    5. 수행된 빌드의 Console Output을 확인하면 앞서 Step 1에서는 없던 stage 항목이 추가되어 수행됨을 확인 할 수 있습니다.

      Started by user GyuSeok.Lee
      +Running in Durability level: MAX_SURVIVABILITY
      +[Pipeline] Start of Pipeline
      +[Pipeline] node
      +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
      +[Pipeline] {
      +[Pipeline] stage
      +[Pipeline] { (Hello)
      +[Pipeline] echo
      +Hello World
      +[Pipeline] }
      +[Pipeline] // stage
      +[Pipeline] }
      +[Pipeline] // node
      +[Pipeline] End of Pipeline
      +Finished: SUCCESS
      +

    Step 3. Parameterizing a job

    Pipeline 내에서 사용되는 매개변수 정의를 확인해 봅니다. Pipeline 스크립트는 다음과 같습니다.

    pipeline {
    +    agent any
    +    parameters {
    +        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
    +    }
    +    stages {
    +        stage('Example') {
    +            steps {
    +                echo "\${params.Greeting} World!"
    +            }
    +        }
    +    }
    +}
    +

    parameters항목내에 매개변수의 데이터 유형(e.g. string)을 정의합니다. name은 값을 담고있는 변수이고 defaultValue의 값을 반환합니다. Pipeline에 정의된 parametersparams내에 정의 되므로 \${params.매개변수이름}과 같은 형태로 호출 됩니다.

    저장 후 다시 구성을 확인하면 이 빌드는 매개변수가 있습니다가 활성화 되고 내부에 추가된 매개변수 항목을 확인 할 수 있습니다.

    1563944944350
    1563944944350

    이렇게 저장된 Pipeline Job은 매개변수를 외부로부터 받을 수 있습니다. 따라서 좌측의 기존 Build Nowbuild with Parameters로 변경되었고, 이를 클릭하면 Greeting을 정의할 수 있는 UI가 나타납니다. 해당 매개변수를 재정의 하여 빌드를 수행할 수 있습니다.

    1563944733249
    1563944733249
    1563944765637
    1563944765637

    Step 4. Creating multiple steps for a job

    다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-04.MultiStep)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +                sh '''
    +                    echo "Multiline shell steps works too"
    +                    ls -lah
    +                '''
    +            }
    +        }
    +    }
    +}
    +

    '''은 스크립트 정의 시 여러줄을 입력할 수 있도록 묶어주는 역할을 합니다. 해당 스크립트에서는 sh로 구분된 스크립트 명령줄이 두번 수행됩니다.

    1563945323777
    1563945323777

    실행되는 여러 스크립트의 수행을 stage로 구분하기위해 기존 Pipeline 스크립트를 다음과 같이 수정합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build-1') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('Build-2') {
    +            steps {
    +                sh '''
    +                    echo "Multiline shell steps works too"
    +                    ls -lah
    +                '''
    +            }
    +        }
    +    }
    +}
    +

    stage를 구분하였기 때문에 각 실행되는 sh 스크립트는 각 스테이지에서 한번씩 수행되며, 이는 빌드의 결과로 나타납니다.

    1563945539114
    1563945539114

    Step 5. Adding scripts as a job step

    Pipeline의 step을 추가하여 결과를 확인하는 과정을 설명합니다. 피보나치 수열을 수행하는 쉘 스크립트를 시간제한을 두어 수행하고 그 결과를 확인합니다.

    ',28),vn={href:"https://namu.wiki/w/%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98%20%EC%88%98%EC%97%B4",target:"_blank",rel:"noopener noreferrer"},gn={href:"https://namu.wiki/w/%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98",target:"_blank",rel:"noopener noreferrer"},kn=a(`
    $ mkdir -p /var/jenkins_home/scripts
    +$ cd /var/jenkins_home/scripts
    +$ vi ./fibonacci.sh
    +#!/bin/bash
    +N=\${1:-10}
    +
    +a=0
    +b=1
    +
    +echo "The Fibonacci series is : "
    +
    +for (( i=0; i<N; i++ ))
    +do
    +    echo "$a"
    +    sleep 2
    +    fn=$((a + b))
    +    a=$b
    +    b=$fn
    +done
    +# End of for loop
    +
    +$ chown -R jenkins /var/jenkins_home/
    +$ chmod +x /var/jenkins_home/scripts/fibonacci.sh
    +

    다중스텝을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 02-05.AddingStep)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Deploy') {
    +            steps {
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh '/var/jenkins_home/scripts/fibonacci.sh 5'
    +                }
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh '/var/jenkins_home/scripts/fibonacci.sh 32'
    +                }
    +            }
    +        }
    +    }
    +}
    +

    steps에 스크립트를 timeout이 감싸고 있으며, 각 스크립트의 제한시간은 1분입니다. 빌드를 수행하면 최종적으로는 aborted, 즉 중단됨 상태가 되는데 그 이유는 빌드 기록에서 해당 빌드를 클릭하면 확인 가능합니다.

    3. Builds

    Step 1. Tracking build state

    Pipeline이 수행되는 동작을 추적하는 과정을 확인합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-01.TrackingBuildState)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Deploy') {
    +            steps {
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh 'for n in \`seq 1 10\`; do echo $n; sleep 1; done'
    +                }
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh 'for n in \`seq 1 50\`; do echo $n; sleep 1; done'
    +                }
    +            }
    +        }
    +    }
    +}
    +

    Build Now를 클릭하여 빌드를 수행합니다. 그러면, 좌측의 Build History에 새로운 기록이 생성되면서 동작 중인것을 확인 할 수 있습니다.

    첫번째 방법은 앞서 확인한 Pipeline Steps를 확인하는 것입니다. 다시한번 확인하는 방법을 설명합니다.

    현재 수행중인 Pipeline이 어떤 단계가 수행중인지 각 스탭별로 확인할 수 있고 상태를 확인할 수 있습니다.

    1563948810815
    1563948810815

    두번째 방법은 출력되는 콘솔 로그를 확인하는 것입니다. Jenkins에서 빌드를 수행하면 빌드 수행 스크립트가 내부에 임시적으로 생성되어 작업을 실행합니다. 이때 발생되는 로그는 Console Output을 통해 거의 실시간으로 동작을 확인 할 수 있습니다.

    1563948863834
    1563948863834

    마지막으로는 Pipeline을 위한 UI인 BlueOcean 플러그인을 활용하는 방법입니다. Blue Ocean은 Pipeline에 알맞은 UI를 제공하며 수행 단계와 각 단게별 결과를 쉽게 확인할 수 있습니다.

    1563949823939
    1563949823939

    Step 2. Polling SCM for build triggering

    Git SCM을 기반으로 Pipeline을 설정하는 과정을 설명합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-02.PollingSCMforBuildTriggering)

    해당 과정을 수행하기 위해서는 다음의 구성이 필요합니다.

    Pipeline을 다음과 같이 설정합니다.

    `,27),mn=n("li",null,"Definition : Pipeline script from SCM",-1),bn=n("li",null,"SCM : Git",-1),hn={href:"https://github.com/Great-Stone/jenkins-git",target:"_blank",rel:"noopener noreferrer"},fn=a(`

    추가로 빌드 트리거를 위한 설정을 합니다.

    Polling으로 인한 빌드 트리거가 동작하면 좌측 메뉴의 Polling Log에서 상태 확인이 가능합니다.

    1563958295010
    1563958295010

    1분마다 확인 하도록 되어있기 때문에 다시 Polling을 시도하지만 변경사항이 없는 경우에는 Polling Log에 No changes 메시지가 나타나고 빌드는 수행되지 않습니다.

    1563958396611
    1563958396611

    Step 3. Connecting Jenkins to GitHub

    GitHub를 통한 CI 과정을 설명합니다. WebHook의 설정과 Jenkins에 관련 설정은 어떻게 하는지 알아봅니다.

    Jenkins에서 접속가능하도록 GitHub에서 Token을 생성합니다.

    ',9),yn={href:"http://github.com",target:"_blank",rel:"noopener noreferrer"},_n=a('
  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-integration) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

    1564115701058
    1564115701058
  • ',4),Sn=n("p",null,"우선 Jenkins에 Git연동을 위한 설정을 추가합니다.",-1),xn=n("li",null,[n("code",null,"Jenkins 관리"),s("에서 "),n("code",null,"시스템 설정"),s("을 클릭합니다.")],-1),Pn=n("li",null,[n("code",null,"GitHub"),s(" 항목의 "),n("code",null,"GitHub Servers"),s("의 "),n("code",null,"Add GitHub Server > GitHub Server"),s("를 선택합니다.")],-1),Jn=n("li",null,"Name : 설정이름을 입력합니다. (e.g. github)",-1),Cn={href:"https://api.github.com",target:"_blank",rel:"noopener noreferrer"},Tn=a("
  • Credentials : ADD트롭박스를 선택합니다.
  • TEST CONNECTION버튼을 클릭하여 정상적으로 연결이 되는지 확인합니다.
  • Manage hook 를 활성화 합니다.
  • ",3),In=n("li",null,"시스템 설정을 저장합니다.",-1),An=n("p",null,[n("strong",null,"Step 4. Webhook build triggering")],-1),wn=n("p",null,"git repo의 Webhook 을 통한 빌드를 수행합니다. GitHub에 다음과 같이 설정합니다.",-1),Dn={href:"https://github.com/Great-Stone/jenkins-git",target:"_blank",rel:"noopener noreferrer"},jn=n("code",null,"fork",-1),qn=n("figure",null,[n("img",{src:g,alt:"1564122799631",tabindex:"0",loading:"lazy"}),n("figcaption",null,"1564122799631")],-1),En=a("
  • 우측 상단의 드롭박스에서 Settings선택 후 좌측 메뉴 맨 아래의 Developer settings를 선택합니다.

  • Developer settings화면에서 좌측 메뉴 하단 Personal access tockes를 클릭하고, 화면이 해당 페이지로 변경되면 Generate new token버튼을 클릭합니다.

  • Token description에 Token설명을 입력하고 입니다. (e.g. jenkins-webhook) 생성합니다. 생성시 repo, admin:repo_hook, notifications항목은 활성화 합니다.

  • Generate token버튼을 클릭하여 Token 생성이 완료되면 발급된 Token을 확인 할 수 있습니다. 해당 값은 Jenkins에서 Git연동설정 시 필요합니다.

  • ",4),Rn=a('

    Webhook을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-04.WebhookBuild Triggering)

    설정은 다음과 같이 수행합니다.

    1. Pipeline 설정의 Definition의 드롭다운을 선택하여 Pipeline script from SCM을 선택합니다.

    2. SCM항목은 Git을 선택하고 하위 필드를 다음과 같이 정의합니다.

    저장 후 좌측 메뉴의 Build Now를 클릭하면 SCM에서 소스를 받고 Pipeline을 지정한 스크립트로 수행하는 것을 확인 할 수 있습니다.

    4. Agents and Distributing Builds

    빌드를 수행하기 위한 Worker로 다중 Jenkins를 컨트롤 할 수 있습니다. 이때 명령을 수행하는 Jenkins는 Master, 빌드를 수행하는 Jenkins는 Worker로 구분합니다. 여기서는 Worker의 연결을 원격 호스트의 Jenkins를 SSH를 통해 연결하는 방식과 컨테이너로 구성된 Jenkins를 연결하는 과정을 확인 합니다.

    Master-Slave 방식, 또는 Master-Agent 방식으로 표현합니다.

    ※ Slave 호스트에 Jenkins를 설치할 필요는 없습니다.

    Step 1. Adding an SSH build agent to Jenkins

    Worker가 실행되는 Slave 호스트에 SSH key를 생성하고 Worker 호스트에 인증 키를 복사하는 과정은 다음과 같습니다.

    1. 키 생성 및 복사(jenkins 를 수행할 유저를 생성해야 합니다.)

      # User가 없는 경우 새로운 Jenkins slave 유저 추가
      +$ useradd jenkins
      +$ passwd jenkins
      +Changing password for user jenkins.
      +New password:
      +Retype new password:
      +
      +# Slave 호스트에서 ssh 키를 생성합니다.
      +$ ssh-keygen -t rsa
      +Generating public/private rsa key pair.
      +Enter file in which to save the key (/root/.ssh/id_rsa): <enter>
      +Created directory '/root/.ssh'.
      +Enter passphrase (empty for no passphrase): <enter>
      +Enter same passphrase again: <enter>
      +Your identification has been saved in /root/.ssh/id_rsa.
      +Your public key has been saved in /root/.ssh/id_rsa.pub.
      +The key fingerprint is: <enter>
      +SHA256:WFU7MRVViaU1mSmCA5K+5yHfx7X+aV3U6/QtMSUoxug root@jenkinsecho.gyulee.com
      +The key's randomart image is:
      ++---[RSA 2048]----+
      +|     .... o.+.=*O|
      +|     ..  + . *o=.|
      +|    .   .o. +o. .|
      +|     . o. + ... +|
      +|      o.S. .   +.|
      +|     o oE    .oo.|
      +|      = o . . +o=|
      +|       o . o ..o=|
      +|          . ..o+ |
      ++----[SHA256]-----+
      +
      +$ cd ~/.ssh
      +$ cat ./id_rsa.pub > ./authorized_keys
      +
    2. Jenkins 관리노드 관리를 선택합니다.

    3. 좌측 메뉴에서 신규 노드를 클릭합니다.

    4. 노드명에 고유한 이름을 입력하고 Permanent Agent 를 활성화 합니다.

    5. 새로운 노드에 대한 정보를 기입합니다.

    Label 지정한 Slave Worker에서 빌드가 수행되도록 기존 02-02.Jobs의 Pipeline 스크립트를 수정합니다. 기존 agent any를 다음과 같이 agent { label 'Metal' }로 변경합니다. 해당 pipeline은 label이 Metal로 지정된 Worker에서만 빌드를 수행합니다.

    pipeline {
    +    agent { label 'Metal' }
    +    parameters {
    +        string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?')
    +    }
    +    stages {
    +        stage('Example') {
    +            steps {
    +                echo "\${params.Greeting} World!"
    +            }
    +        }
    +    }
    +}
    +
    1564378013035
    1564378013035

    Step 2. Using Docker images for agents

    Master Jenkins 호스트에서 docker 서비스에 설정을 추가합니다. docker 설치가 되어있지 않은 경우 설치가 필요합니다.

    $ yum -y install docker
    +

    RHEL8 환경이 Master인 경우 위와 같은 방식으로 설치를 진행하면 변경된 패키지에 따라 podman-docker가 설치 됩니다. 아직 Jenkins에서는 2019년 7월 29일 기준 podman을 지원하지 않음으로 별도 yum repository를 추가하여 진행합니다. docker-ce 최신 버전에서는 containerd.io 의 필요 버전이 1.2.2-3 이상이나 RHEL8에서 지원하지 않음으로 별도로 버전을 지정하여 설치합니다.

    $ yum -y install yum-utils
    +$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    +$ sudo yum repolist -v
    +...
    +Repo-id      : docker-ce-stable
    +Repo-name    : Docker CE Stable - x86_64
    +Repo-revision: 1564098258
    +Repo-updated : Fri 26 Jul 2019 08:44:18 AM KST
    +Repo-pkgs    : 47
    +Repo-size    : 982 M
    +Repo-baseurl : https://download.docker.com/linux/centos/7/x86_64/stable
    +Repo-expire  : 172,800 second(s) (last: Thu 25 Jul 2019 07:33:33 AM KST)
    +Repo-filename: /etc/yum.repos.d/docker-ce.repo
    +...
    +
    +$ yum -y install docker-ce-3:18.09.1-3.el7
    +$ systemctl enable docker
    +$ systemctl start docker
    +

    Jenkins에 새로운 플러그인을 추가하고 설정합니다.

    Docker 실행을 위한 Item을 생성합니다. (e.g. 04-02.UsingDockerImagesForAgents)

    1. Pipeline 스크립트를 구성합니다.
    pipeline {
    +    agent {
    +        docker { image 'node:latest' }
    +    }
    +    stages {
    +        stage('Test'){
    +            steps {
    +                sh 'node --version'
    +            }
    +        }
    +    }
    +}
    +
    1. 수정 후 좌측 Build Now를 클릭하여 빌드 수행 후 결과를 확인합니다.

    2. Step 1에서의 결과와는 달리 Stage View항목과 Pipeline stage가 수행된 결과를 확인할 수 있는 UI가 생성됩니다.

      1564388703234
      1564388703234

    Step3. Configuring specific agents

    Freestyle project 형태의 Item을 생성합니다. (e.g. 04-03.ConfiguringSpecificAgents)

    Jenkins는 각 단계, 빌드, 그리고 빌드 후 작업일 지정할 수 있습니다. Freestyle project에서는 이같은 전체 빌드 단계를 구성하고 여러가지 플러그인을 사용할 수 있는 환경을 제공합니다.

    1564444282455
    1564444282455

    5. Plugins

    Jenkins가 유용한 툴인 이유중 하나는 방대한 양의 플러그인 입니다. Jenkins의 기능을 확장시키고, 관리, 빌드 정책 등을 확장 시켜주고, 타 서비스와의 연계를 쉽게 가능하도록 합니다.

    ',33),Bn={href:"https://plugins.jenkins.io/",target:"_blank",rel:"noopener noreferrer"},Mn=a('
    1564450122219
    1564450122219

    Step 1. Adding plugins via plugin manager

    Jenkins는 온라인에 연결된 plugin을 검색, 설치할 수 있는 플러그인 관리기능을 갖고 있습니다. 좌측 메뉴에서 Jenkins 관리를 클릭하면 플러그인 관리 링크를 통하여 해당 기능에 접근할 수 있습니다.

    각 플러그인 이름을 클릭하면 플러그인 정보를 확인할 수 있는 plugins.jenkins.io 사이트로 이동하여 정보를 보여줍니다. 사용방법은 우측에 wiki링크를 클릭합니다. 대략적인 UI나 사용방법은 wiki.jenkins.io에서 제공합니다.

    Step 2. Using shared libraries

    ',6),Un={href:"https://jenkins.io/doc/book/pipeline/shared-libraries/",target:"_blank",rel:"noopener noreferrer"},Gn={href:"https://github.com/Great-Stone/evenOdd",target:"_blank",rel:"noopener noreferrer"},Hn=a("

    소스의 var 디렉토리에는 Pipeline에서 사용하는 Shared Library들이 들어있습니다. groovy 스크립트로 되어있으며 Pipeline을 구성한 jenkinsfile에서 이를 사용합니다.

    vars/evenOdd.groovy를 호출하고 값을 받아오는 형태를 갖고, evenOdd.groovy에서 사용하는 log.infolog.warningvars/log.groovy에 구현되어있습니다.

    다음과 같이 Jenkins에 설정을 수행합니다.

    1. Jenkins 관리클릭 후 시스템 설정을 선택합니다.
    2. Global Pipeline Libraries 의 추가 버튼을 클릭하여 새로운 구성을 추가합니다.

    Shared Libraries가 준비가 되면 Pipeline 타입의 Item을 생성하고 (e.g. 05-02.UsingSharedLibraries) Pipeline 설정을 추가합니다.

    ",5),Ln=n("li",null,"Definition : Pipeline script from SCM",-1),Nn=n("li",null,"SCM : Git",-1),On={href:"https://github.com/Great-Stone/evenOdd.git",target:"_blank",rel:"noopener noreferrer"},$n=a(`

    저장 후 Build Now를 클릭하여 빌드를 수행합니다. 빌드의 결과로는 2 단계로 수행되는데 1단계는 Declarative: Checkout SCM으로 SCM으로부터 소스를 받아 준비하는 단계이고, 2단계는 jenkinsfile을 수행하는 단계입니다. vars/evenOdd.goovy 스크립트에는 stage가 두개 있으나 해당 Pipeline 을 호출하는 값에 따라 하나의 stage만을 수행하도록 되어있어서 하나의 stage가 수행되었습니다.

    // Jenkinsfile
    +//@Library('evenOdd') _
    +
    +evenOdd(currentBuild.getNumber())
    +

    currentBuild.getNumber()는 현재 생성된 Pipeline Item의 빌드 숫자에 따라 값을 evenOdd(빌드 숫자)형태로 호출하게 됩니다.

    Jenkins shared libraries를 사용하는 가장 좋은 예는 재사용성 있는 Groovy 함수를 타 작업자와 공유하는 것 입니다. 빌드의 상태는 다른 파이프 라인 단계로 계속할 것인지 결정하는 데 사용할 수도 있습니다.

    주의

    해당 설정은 모든 빌드에 영향을 주기 때문에 타 작업을 위해 추가된 Global Pipeline LibrariesLibrary를 삭제하여 진행합니다.

    6. Notifications

    Jenkins빌드의 결과를 받아볼 수 있는 몇가지 방안에 대해 알아봅니다.

    Step 1. Notifications of build state

    `,9),zn={href:"http://catlight.io",target:"_blank",rel:"noopener noreferrer"},Wn=n("figure",null,[n("img",{src:T,alt:"1564463655933",tabindex:"0",loading:"lazy"}),n("figcaption",null,"1564463655933")],-1),Vn=n("p",null,"여기서는 Chrome 확장 프로그램을 통한 알림을 받을 수 있는 설정을 설명합니다. 이 과정을 진행하기 위해서는 Chrome 웹브라우저가 필요합니다.",-1),Fn=a('
  • jenkins를 검색하여 Yet Another Jenkins Notifier를 확인합니다. Chrome에 추가버튼으로 확장 프로그램을 설치합니다.

  • 설치가 완료되면 브라우저 우측 상단에 Jenkins 아이콘이 나타납니다. 클릭합니다.

  • 각 Item(Job)의 url을 입력하여 +버튼을 클릭합니다.

  • 등록된 Item을 확인하고 해당 빌드를 Jenkins 콘솔에서 실행해봅니다. 결과에 대한 알림이 발생하는 것을 확인 할 수 있습니다.

    1564464197547
    1564464197547
  • ',4),Kn=a('

    Step 2. Build state badges for SCM

    Jenkins에서 빌드가 수행된 결과를 SCM에 반영하는 기능도 플러그인을 통해 가능합니다. SCM에서 해당 Jenkins에 접근이 가능해야 하므로 Jenkins는 SCM에서 접근가능한 네트워크 상태여야 합니다.

    Jenkins에 새로운 플러그인을 추가하고 설정합니다.

    이제 기존의 외부 SCM이 연결된 Item을 선택합니다. 여기서는 05-02.UsingSharedLibraries에 설정합니다. 해당 Item을 선택하면 좌측에 Embeddable Build Status 항목이 새로 생긴것을 확인 할 수 있습니다.

    1564464880533
    1564464880533

    해당 항목을 클릭하고 Markdownunprotected의 항목을 복사합니다.

    [![Build Status](http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries)](http://myjenkins.com/job/05-02.UsingSharedLibraries/)
    +
    `,8),Zn={href:"http://README.md",target:"_blank",rel:"noopener noreferrer"},Yn=a(`
    # evenOdd
    +[![Build Status](http://myjenkins.com/buildStatus/icon?job=libraries)](http://myjenkins.com/job/libraries/)
    +
    +A Jenkins even/odd playbook from the Jenkins.io documentation
    +
    +Add this as a shared library called evenOdd in your jenkins
    +instance, and then instantiate the pipeline in your project Jenkinsfile
    +
    +This will also use an example of global variabls from the log.groovy
    +definitions
    +
    +

    이같이 반영하면 각 빌드에 대한 결과를 SCM에 동적으로 상태를 반영 할 수 있습니다.

    1564465713857
    1564465713857

    이같은 알림 설정은 코드의 빌드가 얼마나 잘 수행되는지 이해하고 추적할 수 있도록 도와줍니다.

    7. Testing

    Step 1. Code coverage tests and reports

    테스트 Pipeline 구성시 테스트 과정을 지정할 수 있습니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-01.CodeCoverageTestsAndReports)

    설정은 다음과 같이 수행합니다.

    1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

      pipeline {
      +    agent any
      +    stages {
      +        stage('Build') {
      +            steps {
      +                sh '''
      +                  echo This > app.sh
      +                  echo That >> app.sh
      +                '''
      +            }
      +        }
      +        stage('Test') {
      +            steps {
      +                sh '''
      +                  grep This app.sh >> \${BUILD_ID}.cov
      +                  grep That app.sh >> \${BUILD_ID}.cov
      +                '''
      +            }
      +        }
      +        stage('Coverage'){
      +            steps {
      +                sh '''
      +                  app_lines=\`cat app.sh | wc -l\`
      +                  cov_lines=\`cat \${BUILD_ID}.cov | wc -l\`
      +                  echo The app has \`expr $app_lines - $cov_lines\` lines uncovered > \${BUILD_ID}.rpt
      +                  cat \${BUILD_ID}.rpt
      +                '''
      +                archiveArtifacts "\${env.BUILD_ID}.rpt"
      +            }
      +        }
      +    }
      +}
      +
    2. 빌드가 완료되면 해당 Job화면을 리로드 합니다. Pipeline에 archiveArtifacts가 추가되었으므로 해당 Job에서 이를 관리합니다.
      1564470826126

    3. 해당 아카이브에는 코드 검증 후의 결과가 저장 됩니다.

    Step 2. Using test results to stop the build

    테스트 결과에 따라 빌드를 중지시키는 Pipeline 스크립트를 확인합니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-02.UsingTestResultsToStopTheBuild)

    설정은 다음과 같이 수행합니다.

    1. Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

      pipeline {
      +    agent any
      +    stages {
      +        stage('Build') {
      +            steps {
      +                sh '''
      +                  echo This > app.sh
      +                  echo That >> app.sh
      +                  echo The Other >> app.sh
      +                '''
      +            }
      +        }
      +        stage('Test') {
      +            steps {
      +                sh '''
      +                  for n in This That Those
      +                   do if grep $n app.sh >> \${BUILD_ID}.cov
      +                    then exit 1
      +                   fi
      +                  done
      +                '''
      +            }
      +        }
      +        stage('Coverage'){
      +            steps {
      +                sh '''
      +                  app_lines=\`cat app.sh | wc -l\`
      +                  cov_lines=\`cat \${BUILD_ID}.cov | wc -l\`
      +                  echo The app has \`expr $app_lines - $cov_lines\` lines uncovered > \${BUILD_ID}.rpt
      +                  cat \${BUILD_ID}.rpt
      +                '''
      +                archiveArtifacts "\${env.BUILD_ID}.rpt"
      +            }
      +        }
      +    }
      +}
      +
    2. 저장을 하고 빌드를 수행하면, Pipeline 스크립트 상 Test Stage에서 조건 만족 시 exit 1를 수행하므로 빌드는 중간에 멈추게 됩니다.

      1564471729123
      1564471729123

    8. REST API

    Jenkins는 외부 서비스와의 연동이나 정보 조회를 위한 API를 제공합니다.

    Step 1. Triggering builds via the REST API

    Jenkins REST API 테스트를 위해서는 Jenkins에 인증 가능한 Token을 취득하고 curl이나 Postman 같은 도구를 사용하여 확인 가능 합니다. 우선 Token을 얻는 방법은 다음과 같습니다.

    1. Jenkins에 로그인 합니다.

    2. 우측 상단의 로그인 아이디에 마우스를 호버하면 드롭박스 버튼이 나타납니다. 설정을 클릭합니다.

    3. API Token에서 Current token을 확인합니다. 등록된 Token이 없는 경우 다음과 같이 신규 Token을 발급 받습니다.

    4. 이름과 Token을 사용하여 다음과 같이 curl로 접속하면 Jenkins-Crumb 프롬프트가 나타납니다.

      $ curl --user "admin:TOKEN" 'http://myjenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
      +
      +Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605
      +
    5. Crumb를 확인했으면 다시 헤더 값에 Jenkins-Crumb:를 추가하여 02-04.MultiStep Job을 빌드하기 위해 다음과 같이 요청합니다.

      $ curl -X POST http://myjenkins.com/job/02-04.MultiStep/build --user gyulee:11479bdec9cada082d189938a3946348be --data-urlencode json='' -H "Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605"
      +

    API로 호출된 빌드가 수행되어 빌드 번호가 증가하는 것을 확인합니다.

    Step 2. Retriving build status via the REST API

    빌드에 대한 결과를 REST API를 통해 요청하는 방법을 알아봅니다. 앞서 진행시의 Token값이 필요합니다. Json 형태로 출력되기 때문에 정렬을 위해 python이 설치 되어있다면 mjson.tool을 사용하여 보기 좋은 형태로 출력 가능합니다.

    # Python이 설치되어있지 않은 경우
    +$ yum -y install python2
    +
    +# Jenkins에 REST API로 마지막 빌드 상태 요청
    +$ curl  -s --user gyulee:11479bdec9cada082d189938a3946348be http://myjenkins.com/job/02-04.MultiStep/lastBuild/api/json | python2 -mjson.tool
    +
    +{
    +    "_class": "org.jenkinsci.plugins.workflow.job.WorkflowRun",
    +    "actions": [
    +        {
    +            "_class": "hudson.model.CauseAction",
    +            "causes": [
    +                {
    +                    "_class": "hudson.model.Cause$UserIdCause",
    +                    "shortDescription": "Started by user GyuSeok.Lee",
    +                    "userId": "gyulee",
    +                    "userName": "GyuSeok.Lee"
    +                }
    +            ]
    +        },
    +        {},
    +        {
    +            "_class": "hudson.plugins.git.util.BuildData",
    +            "buildsByBranchName": {
    +                "master": {
    +                    "_class": "hudson.plugins.git.util.Build",
    +                    "buildNumber": 5,
    +                    "buildResult": null,
    +...
    +

    9. Security

    Step 1. Securing your deployment with users

    사용자별 배포수행을 위한 사용자 설정을 설명합니다.

    Enable security는 보안 설정 여부를 설정하는 항목으로 기본적으로는 비활성화되어있습니다. 체크하여 활성화하면 다양한 보안 옵션을 설정할 수 있는 항목이 표기 됩니다.

    Security Realm 에서는 Jenkins에서 사용하는 사용자 관리 방식을 선택합니다.

    Authorization 에서는 사용자 권한에 대한 설정을 정의합니다.

    다음은 권한 매트릭스의 항목과 권한별 설명입니다.

    항목권한의미
    OverallAdminister시스템의 전역 설정을 변경할 수 있다. OS 에서 허용된 범위안에서 전체 시스템 엑세스드의 매우 민감한 설정을 수행
    Read젠킨스의 모든 페이지 확인 가능
    RunScripts그루비 콘솔이나 그루비 CLI 명령을 통해 그루비 스크립트를 실행
    UploadPlugins특정 플러그인을 업로드
    ConfigureUpdateCenter업데이트 사이트와 프록시 설정
    SlaveConfigure기존 슬레이브 설정 가능
    Delete기존 슬레이브 삭제
    Create신규 슬레이브 생성
    Disconnect슬레이브 연결을 끊거나 슬레이브를 임시로 오프라인으로 표시
    Connect슬레이브와 연결하거나 슬레이브를 온라인으로 표시
    JobCreate새로운 작업 생성
    Delete기존 작업 삭제
    Configure기존 작업의 설정 갱신
    Read프로젝트 설정에 읽기 전용 권한 부여
    Discover익명 사용자가 작업을 볼 권한이 없으면 에러 메시지 표시를 하지 않고 로그인 폼으로 전환
    Build새로운 빌드 시작
    Workspace젠킨스 빌드를 실행 하기 위해 체크아웃 한 작업 영역의 내용을 가져오기 가능
    Cancel실행중인 빌드 취소
    RunDelete빌드 내역에서 특정 빌드 삭제
    Update빌드의 설명과 기타 프로퍼티 수정(빌드 실패 사유등)
    ViewCreate새로운 뷰 생성
    Delete기존 뷰 삭제
    Configure기존 뷰 설정 갱신
    Read기존 뷰 보기
    SCMTag특정 빌드와 관련된 소스 관리 시스템에 태깅을 생성

    CSRF Protection 항목에 있는 Prevent Cross Site Request Forgery exploits 항목은 페이지마다 nonce 또는 crumb 이라 불리우는 임시 값을 삽입하여 사이트 간 요청 위조 공격을 막을 수 있게 해줍니다. 사용방법은 위에서 REST API 에 대한 설명 시 crumb 값을 얻고, 사용하는 방법을 참고합니다.

    Step 2. Securing secret credentials and files

    Jenkins에서 Pipeline을 설정하는 경우 일부 보안적인 값이 필요한 경우가 있습니다. 예를 들면 UsernamePassword 같은 값입니다. 앞서의 과정에서 Credentials를 생성하는 작업을 일부 수행해 보았습니다. 여기서는 생성된 인증 값을 Pipeline에 적용하는 방법을 설명합니다.

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 09-02.SecuringSecretCredentialsAndFiles) 설정은 다음과 같이 수행합니다.

    1. Pipeline 스크립트에 다음과 같이 입력 합니다.

      pipeline {
      +    agent any
      +    environment {
      +       SECRET=credentials('jenkins-secret-text')
      +    }
      +    stages {
      +        stage('Build') {
      +            steps {
      +                echo "\${env.SECRET}"
      +            }
      +        }
      +    }
      +}
      +
    2. 저장 후 Build Now를 클릭하여 빌드를 수행하면 실패하게 되고 Console Output에서 진행사항을 보면, Pipeline 스크립트에서 선언한 jenkins-secret-text때문에 에러가 발생한 것을 확인할 수 있습니다.

    3. 좌측 상단의 Jenkins버튼을 클릭하여 최상위 메뉴로 이동합니다.

    4. 좌측 메뉴의 Credentials를 클릭하고 (global) 도메인을 클릭합니다.

      1564534571013
      1564534571013
    5. 좌측에 Add Credentials를 클릭하여 새로운 항목을 추가합니다.

    6. 저장 후 다시 빌드를 수행하면 정상적으로 수행됩니다. 해당 값은 숨기기 위한 값이므로 Pipeline 스크립트에서 echo로 호출하더라도 ****이란 값으로 표기 됩니다.

    이같은 방법은 Password같은 보안에 민감한 정보를 사용하기에 유용합니다.

    Step 3. Auditing your environment

    Jenkins의 변화와 활동에 대한 감시를 위한 설정 방법을 설명합니다. Jenkins에 새로운 플러그인을 추가하고 설정합니다.

    저장 후 빌드나 Job의 설정 변경등의 작업을 수행하면, audit.log.0으로 지정된 파일 경로에 생성됨을 확인 할 수 있습니다.

    $ tail -f ./audit.log.0
    +Jul 31, 2019 10:47:32,727 AM job/02-02.Jobs/ #12 Started by user GyuSeok.Lee
    +Jul 31, 2019 10:47:42,738 AM /job/03-04.WebhookBuild Triggering/configSubmit by gyulee
    +Jul 31, 2019 10:48:09,001 AM /configSubmit by gyulee
    +

    Step 4. Using forders to create security realms

    다양한 프로젝트를 관리하는 경우 관리상, 빌드 프로젝트를 관리해야할 필요성이 발생합니다. Jenkins에서 Forder 아이템을 생성하여 관리 편의성과 보안요소를 추가할 수 있습니다.

    우선 테스트를 위한 사용자를 추가합니다.

    다음으로 Forder 타임의 Item을 추가합니다.

    권한 설정을 하여 현재 Admin 권한의 사용자는 접근 가능하고 새로 생성한 tester는 접근불가하도록 설정합니다.

    Jenkins의 인증 기능을 사용하여 보안적 요소를 구성할 수 있습니다. Audit 로그를 활용하여 사용자별 활동을 기록할 수도 있고 Folder를 활용하면 간단히 사용자/그룹에 프로젝트를 구분하여 사용할 수 있도록 구성할 수 있습니다.

    10. Artifacts

    빌드 이후 빌드의 결과를 기록하고 저장하는 방법을 설명합니다.

    Step 1. Creating and storing artifacts

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-01.CreatingAndStoringArtifacts)

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    pipeline {
    +    agent any
    +    stages{
    +        stage('Build') {
    +            steps{
    +                sh 'echo "Generating artifacts for \${BUILD_NUMBER}" > output.txt'
    +            }
    +        }
    +        stage('Archive') {
    +            steps {
    +                archiveArtifacts artifacts: 'output.txt', onlyIfSuccessful: true
    +            }
    +        }
    +    }
    +}
    +

    Archive Stage에 archiveArtifacts 스크립트가 동작하는 예제입니다. 이같은 Pipeline 스크립트 작성을 도와주는 툴을 추가로 확인해 봅니다.

    결과물을 확인하면 Pipeline 스크립트에 작성한 형태와 같은 것을 확인 할 수 있습니다.

    1564545470815
    1564545470815

    좌측 메뉴의 Build Now를 클릭하여 빌드 수행 후에 화면에 Artifacts 항목이 추가된 것을 확인할 수 있습니다. UI 상에는 마지막 빌드 결과가 강조되어 나오고 각 빌드에 대한 결과물은 각각의 빌드단계의 다운로드 버튼으로 확인하고 다운로드 할 수 있습니다.

    1564545639205
    1564545639205

    Step 2. Fingerprinting for artifact tracking

    빌드 이후 보관되는 파일에 대해 어떤 프로젝트, 어떤 빌드 에서 발생한 결과물인지 확인할 수 있는 핑거프린팅 기능을 설명합니다.

    Step 1의 프로젝트를 그대로 사용하거나 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-02.FingerprintingForArtifactTracking)

    Step 1 Pipeline 스크립트의 archiveArtifactsfingerprint: true를 추가합니다.

    pipeline {
    +    agent any
    +    stages{
    +        stage('Build') {
    +            steps{
    +                sh 'echo "Generating text artifacts: Build:\${BUILD_NUMBER}" > output.txt'
    +            }
    +        }
    +        stage('Archive') {
    +            steps {
    +                archiveArtifacts artifacts: 'output.txt', fingerprint: true, onlyIfSuccessful: true
    +            }
    +        }
    +    }
    +}
    +

    파일의 지문을 확인합니다.

    11. Pipelines

    Pipeline에 대해 설명합니다.

    Step 1. Automating deployment with pipelines

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-01.AutomatingDeploymentWithPipelines)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh 'echo "Test Hello World!"'
    +            }
    +        }
    +    }
    +}
    +

    두개의 Stage를 갖는 Pipeline 스크립트입니다. Pipeline은 빌드 수행시의 각 단계를 구분하여 빌드의 과정을 확인하고 실패에 따른 단계별 확인이 가능합니다.

    좌측 Build Now를 클릭하여 빌드를 수행하면 빌드에 대한 결과는 Stage 별로 성공 실패의 여부와 로그를 확인할 수 있도록 Stage View가 UI로 제공됩니다. Stage 별로 Stage View는 기록되며, Stage에 변경이 있거나 이름이 변경되는 경우에는 해당 UI에 변경이 발생하여 기존 Pipeline 기록을 보지 못할 수 있습니다.

    1564547435978
    1564547435978

    Step 2. Creating pipeline gates

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-02.CreatingPipelineGates)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('BuildMore'){
    +            steps {
    +                input message: "Shall we build more?"
    +                sh '''
    +                    echo "We are approved; continue!"
    +                    ls -lah
    +                '''
    +            }
    +        }
    +    }
    +}
    +

    개의 Stage를 갖는 Pipeline 스크립트입니다. 두번째 Stage에 input 스크립트가 있습니다. 이 스크립트가 추가되면 Pipeline을 진행하면서 해당하는 동작을 수행할 것인지, 마치 승인 작업과 같은 동작을 수행할 수 있습니다.

    좌측 Build Now를 클릭하여 빌드를 수행하면 두번째 Stage에서 해당 작업을 수행할 지에 대한 물음을 확인 할 수 있습니다.

    1564547694870
    1564547694870

    Abort를 선택하면 빌드 취소와 같은 동작으로 실패로 처리되지는 않습니다.

    Step 3. Job promotion for long-running pipeline

    빌드 단계를 구현할 때 Pipeline 스크립트로 하나의 프로젝트 내에서 모든 동작을 정의 할 수도 있지만 서로다른 Job을 연계하고, 승인 절차를 따르도록 구성할 수 있습니다.

    Job promotion 기능을 사용하기 위한 플러그인을 설치합니다.

    FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-one)

    11-03.Job-one 빌드 후 승인에 대한 다음 빌드를 진행할 FreeStyle 타입의 Item을 생성합니다. (e.g. 11-03.Job-two)

    11-03.Job-one에 대한 빌드를 수행합니다. 수행 완료 후 빌드 히스토리의 최근 빌드를 클릭(e.g. #1)하면 Promotion Status에 승인절차를 기다리고 있음을 확인할 수 있습니다. Parameters 항목의 approve를 체크하고 APPROVE버튼을 클릭합니다.

    1564554095622
    1564554095622

    승인이 완료되면 해당 프로젝트의 승인에 대한 이벤트를 통해 빌드를 수행하는 11-03.Job-two가 이어서 빌드됨을 확인 할 수 있습니다.

    Step 4. Multibranch repository automation

    SCM의 Multibranch를 빌드하는 과정에 대해 설명합니다.

    다음의 GitHub repository를 fork 합니다.

    ',104),Xn={href:"https://github.com/Great-Stone/multibranch-demo",target:"_blank",rel:"noopener noreferrer"},Qn=a('

    Multibranch Pipeline 형태의 Item을 생성합니다. (e.g. 11-04.MultibranchRepositoryAutomation)

    저장 후에는 자동적으로 모든 브랜치의 소스를 빌드 수행합니다.

    1564555063361
    1564555063361
    1564554995103
    1564554995103

    SCM에서 브랜치를 여러개 관리하고 모두 빌드와 테스팅이 필요하다면 Multibranch 프로젝트를 생성하여 등록하고, 빌드 관리가 가능합니다.

    Step 5. Creating pipeline with snippets

    Pipeline 을 스크립트를 작성하는 방법을 배워봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-05. CreatingPipelineWithSnippets)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage("Hello") {
    +            steps {
    +                echo 'Hello World'
    +            }
    +        }
    +    }
    +}
    +

    echo가 동작할때 시간을 기록하도록 스크립트를 수정해보겠습니다.

    빌드를 수행하고 로그를 확인해 봅니다. echo 동작이 수행 될때 시간이 함께 표기되는 것을 확인 할 수 있습니다.

    1564555730104
    1564555730104

    Step 6. Discovering global pipeline variables

    Pipeline에서 사용할 수 있는 변수를 확인하고 사용하는 방법을 알아봅니다. Pipeline 타입의 Item을 생성합니다. (e.g. 11-06.DiscoveringGlobalPipelineVariables)

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +               echo "We are in build \${currentBuild.number}"
    +               echo "Our current result is \${currentBuild.currentResult}"
    +            }
    +        }
    +        stage('BuildMore'){
    +            steps {
    +               echo "Name of the project is \${currentBuild.projectName}"
    +            }
    +        }
    +        stage('BuildEnv'){
    +            steps {
    +                echo "Jenkins Home : \${env.JENKINS_HOME}"
    +            }
    +        }
    +    }
    +}
    +

    Pipeline 스크립트에서 사용가능한 변수와 사용방법은 Pipeline Syntax 링크의 Global Variables Reference 항목에서 확인 가능합니다.

    1564557613406
    1564557613406

    Apendix

    GitHub SCM 연동 이슈

    GitHub를 SCM으로 사용하는 경우 다음과 같은 메시지가 출력되면서 진행되지 않는 경우가 있습니다.

    GitHub API Usage: Current quota has 5000 remaining (447380 over budget). Next quota of 5000 in 5 days 0 hr. Sleeping for 4 days 23 hr.
    +14:07:33 GitHub API Usage: The quota may have been refreshed earlier than expected, rechecking...
    +

    이 경우 서버 시간과 GitHub의 시간이 맞지 않아 발생할 수 있는 이슈 입니다. ntpdate를 재설정 합니다.

    `,25),ns=n("li",null,[n("p",null,"RHEL7 : ntpd를 재시작 합니다."),n("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[n("pre",{class:"language-bash"},[n("code",null,`$ systemctl restart ntpd +`)])])],-1),ss=n("br",null,null,-1),es={href:"https://access.redhat.com/solutions/4130881",target:"_blank",rel:"noopener noreferrer"},as=n("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[n("pre",{class:"language-bash"},[n("code",null,[s(`$ systemctl stop chronyd +$ chronyd `),n("span",{class:"token parameter variable"},"-q"),s(` +$ systemctl start chronyd +`)])])],-1),is=n("p",null,[n("strong",null,"유용한 플러그인")],-1),ts=n("ul",null,[n("li",null,"Restart Safely : Jenkins를 재기동해야하는 경우 빌드가 수행중이지 않을 때 자동으로 Restart 시켜줍니다. 설치 후에는 왼쪽 주 메뉴에 표시됩니다."),n("li",null,"ThinBackup : Jenkins의 구성을 백업, 복구할 수 있는 기능을 제공합니다. 백업 주기나 백업 개수등을 정의 할 수 있습니다.")],-1);function ls(os,ps){const i=t("ExternalLinkIcon"),l=t("Mermaid"),o=t("RouteLink");return X(),Q("div",null,[en,n("blockquote",null,[n("p",null,[s("참고 url : "),n("a",an,[s("https://pkg.jenkins.io/redhat-stable/"),e(i)])])]),tn,n("ul",null,[n("li",null,[n("p",null,[n("a",ln,[s("http://afonsof.com/jenkins-material-theme/"),e(i)]),s(" 에 접속합니다.")])]),on]),pn,cn,dn,e(l,{id:"mermaid-235",code:"eJxtjcEJwzAQBN9RFWogDeThT1xAIGngkBdzIN8p0sng7mOHECPjfQ6zuwXvCgnomcZMk7skysaBE4n5HjOipha+UKwlT6MRLXpkHWowVnF+zW/o2nVb++bvKsZStRbPYlifT9Tv7N+1TR4QeUZeDub+dtRT1GWCmPsA04VS8A=="}),rn,e(l,{id:"mermaid-292",code:"eJxtjk0KwkAMRveeYpbpolcQpr8qCmLBfabEOlJmJKZYb68NIhaa5XuP8HWM96vZn1bmcxZ20SUmTdcZWPcQxlaOHG/USqJBNjlTwQGF/bigapjBWuEGKiZq5NXTgt1+v+UxXHw3MIqPYdZZ7XJoZHDTwD9YwNnT8wcLhSWUoxAH7FW8AUDhPw0="}),un,n("p",null,[s("Jenkins가 설치된 서버에 [피보나치 수열](["),n("a",vn,[s("https://namu.wiki/w/피보나치 수열"),e(i)]),s("]("),n("a",gn,[s("https://namu.wiki/w/피보나치"),e(i)]),s(" 수열))을 수행하는 스크립트를 작성합니다. Sleep이 있기 때문에 일정 시간 이상 소요 됩니다.")]),kn,n("ul",null,[mn,bn,n("li",null,[s("Repositories "),n("ul",null,[n("li",null,[s("Repository URL : "),n("a",hn,[s("https://github.com/Great-Stone/jenkins-git"),e(i)])])])])]),fn,n("ul",null,[n("li",null,[n("p",null,[n("a",yn,[s("github.com"),e(i)]),s("에 접속하여 로그인합니다.")])]),_n]),Sn,n("ul",null,[xn,Pn,n("li",null,[s("항목의 입력정보는 다음과 같습니다. "),n("ul",null,[Jn,n("li",null,[s("API URL : "),n("a",Cn,[s("https://api.github.com"),e(i)])]),Tn])]),In]),An,wn,n("ul",null,[n("li",null,[n("p",null,[n("a",Dn,[s("https://github.com/Great-Stone/jenkins-git"),e(i)]),s(" 를 "),jn,s("합니다.")]),qn]),En]),Rn,n("p",null,[n("a",Bn,[s("Plugin Index"),e(i)])]),Mn,n("p",null,[s("Jenkins Pipeline의 Shared libraries에 대한 상세 내용은 다음 링크를 참고합니다. "),n("a",Un,[s("https://jenkins.io/doc/book/pipeline/shared-libraries/"),e(i)])]),n("p",null,[s("이번 실습을 진행하기전에 GitHub에서 "),n("a",Gn,[s("https://github.com/Great-Stone/evenOdd"),e(i)]),s(" repository를 본인 계정의 GitHub에 Fork 하여 진행합니다.")]),Hn,n("ul",null,[Ln,Nn,n("li",null,[s("Repositories "),n("ul",null,[n("li",null,[s("Repository URL : "),n("a",On,[s("https://github.com/Great-Stone/evenOdd.git"),e(i)])])])])]),$n,n("p",null,[s("Jenkins에서는 플러그인이나 외부 툴에 의해 빌드에 대한 결과를 받아 볼 수 있습니다. 대표적으로는 Jenkins의 슬랙 플러그인을 사용하여 슬랙으로 빌드에 결과를 받아보거나, "),n("a",zn,[s("catlight.io"),e(i)]),s(" 에서 데스크탑용 어플리케이션에 연동하는 방법도 있습니다.")]),Wn,Vn,n("ol",null,[n("li",null,[n("p",null,[e(o,{to:"/05-Software/Jenkins/pipeline101/chrome:/apps/"},{default:nn(()=>[s("chrome://apps/")]),_:1}),s("에 접속하여 앱스토어를 클릭합니다.")])]),Fn]),Kn,n("p",null,[s("복사한 형식을 GitHub의 evenOdd repository의 "),n("a",Zn,[s("README.md"),e(i)]),s(" 파일 상단에 위치 시킵니다.")]),Yn,n("ul",null,[n("li",null,[n("a",Xn,[s("https://github.com/Great-Stone/multibranch-demo"),e(i)])])]),Qn,n("ul",null,[ns,n("li",null,[n("p",null,[s("RHEL8 : RHEL8에서는 ntpdate를 사용하지 않고 chronyd가 대신합니다."),ss,n("a",es,[s("https://access.redhat.com/solutions/4130881"),e(i)])]),as])]),is,ts])}const ys=Y(sn,[["render",ls],["__file","13-jenkins_101_single.html.vue"]]),_s=JSON.parse('{"path":"/05-Software/Jenkins/pipeline101/13-jenkins_101_single.html","title":"Pipeline on Jenkins 101 (Single Page)","lang":"ko-KR","frontmatter":{"description":"jenkins 101","tag":["cicd","jenkins"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/05-Software/Jenkins/pipeline101/13-jenkins_101_single.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Pipeline on Jenkins 101 (Single Page)"}],["meta",{"property":"og:description","content":"jenkins 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Pipeline on Jenkins 101 (Single Page)"}],["meta",{"property":"article:tag","content":"cicd"}],["meta",{"property":"article:tag","content":"jenkins"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Pipeline on Jenkins 101 (Single Page)\\",\\"image\\":[\\"http://myjenkins.com/buildStatus/icon?job=05-02.UsingSharedLibraries\\",\\"http://myjenkins.com/buildStatus/icon?job=libraries\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Introduction","slug":"introduction","link":"#introduction","children":[]},{"level":2,"title":"1. CI/CD","slug":"_1-ci-cd","link":"#_1-ci-cd","children":[]},{"level":2,"title":"2. Jobs","slug":"_2-jobs","link":"#_2-jobs","children":[]},{"level":2,"title":"3. Builds","slug":"_3-builds","link":"#_3-builds","children":[]},{"level":2,"title":"4. Agents and Distributing Builds","slug":"_4-agents-and-distributing-builds","link":"#_4-agents-and-distributing-builds","children":[]},{"level":2,"title":"5. Plugins","slug":"_5-plugins","link":"#_5-plugins","children":[]},{"level":2,"title":"6. Notifications","slug":"_6-notifications","link":"#_6-notifications","children":[]},{"level":2,"title":"7. Testing","slug":"_7-testing","link":"#_7-testing","children":[]},{"level":2,"title":"8. REST API","slug":"_8-rest-api","link":"#_8-rest-api","children":[]},{"level":2,"title":"9. Security","slug":"_9-security","link":"#_9-security","children":[]},{"level":2,"title":"10. Artifacts","slug":"_10-artifacts","link":"#_10-artifacts","children":[]},{"level":2,"title":"11. Pipelines","slug":"_11-pipelines","link":"#_11-pipelines","children":[]},{"level":2,"title":"Apendix","slug":"apendix","link":"#apendix","children":[]}],"git":{"createdTime":1640328154000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":13.08,"words":3923},"filePathRelative":"05-Software/Jenkins/pipeline101/13-jenkins_101_single.md","localizedDate":"2021년 12월 24일","excerpt":"\\n
    \\n

    Update at 31 Jul, 2019

    \\n
    \\n

    Introduction

    \\n

    Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

    \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
    CategoryNameVersion
    VMVirtualBox6.0.10
    OSRed Hat Enterprise Linux8.0.0
    JDKRed Hat OpenJDK1.8.222
    JenkinsJenkins rpm2.176.2
    "}');export{ys as comp,_s as data}; diff --git a/assets/1563942302074-BFYw0S2R.png b/assets/1563942302074-BFYw0S2R.png new file mode 100644 index 0000000000..e3ebf9ce63 Binary files /dev/null and b/assets/1563942302074-BFYw0S2R.png differ diff --git a/assets/1563944733249-Bx9TTz2q.png b/assets/1563944733249-Bx9TTz2q.png new file mode 100644 index 0000000000..c3f685f8cd Binary files /dev/null and b/assets/1563944733249-Bx9TTz2q.png differ diff --git a/assets/1563944765637--ZLPfxm8.png b/assets/1563944765637--ZLPfxm8.png new file mode 100644 index 0000000000..cc605e7760 Binary files /dev/null and b/assets/1563944765637--ZLPfxm8.png differ diff --git a/assets/1563944944350-CqOzkSoe.png b/assets/1563944944350-CqOzkSoe.png new file mode 100644 index 0000000000..3508e7b89c Binary files /dev/null and b/assets/1563944944350-CqOzkSoe.png differ diff --git a/assets/1563945323777-BJl9i_bk.png b/assets/1563945323777-BJl9i_bk.png new file mode 100644 index 0000000000..974870a71e Binary files /dev/null and b/assets/1563945323777-BJl9i_bk.png differ diff --git a/assets/1563945539114-BeDUHaoS.js b/assets/1563945539114-BeDUHaoS.js new file mode 100644 index 0000000000..b18bc4ba7a --- /dev/null +++ b/assets/1563945539114-BeDUHaoS.js @@ -0,0 +1 @@ +const s="/assets/1563942302074-BFYw0S2R.png",t="/assets/1563944944350-CqOzkSoe.png",o="/assets/1563944733249-Bx9TTz2q.png",_="/assets/1563944765637--ZLPfxm8.png",a="/assets/1563945323777-BJl9i_bk.png",n="/assets/1563945539114-Z8AnYPt1.png";export{s as _,t as a,o as b,_ as c,a as d,n as e}; diff --git a/assets/1563945539114-Z8AnYPt1.png b/assets/1563945539114-Z8AnYPt1.png new file mode 100644 index 0000000000..60c4f073f6 Binary files /dev/null and b/assets/1563945539114-Z8AnYPt1.png differ diff --git a/assets/1563948810815-Dy4NLvPN.png b/assets/1563948810815-Dy4NLvPN.png new file mode 100644 index 0000000000..fe4f390afd Binary files /dev/null and b/assets/1563948810815-Dy4NLvPN.png differ diff --git a/assets/1563948863834-C44mQZaj.png b/assets/1563948863834-C44mQZaj.png new file mode 100644 index 0000000000..417a57a390 Binary files /dev/null and b/assets/1563948863834-C44mQZaj.png differ diff --git a/assets/1563949823939-DTtxMLwY.png b/assets/1563949823939-DTtxMLwY.png new file mode 100644 index 0000000000..bacad8eef7 Binary files /dev/null and b/assets/1563949823939-DTtxMLwY.png differ diff --git a/assets/1563958295010-B7UwU6QW.png b/assets/1563958295010-B7UwU6QW.png new file mode 100644 index 0000000000..dcf03e67e2 Binary files /dev/null and b/assets/1563958295010-B7UwU6QW.png differ diff --git a/assets/1563958396611-Che1gI68.png b/assets/1563958396611-Che1gI68.png new file mode 100644 index 0000000000..737873e820 Binary files /dev/null and b/assets/1563958396611-Che1gI68.png differ diff --git a/assets/1564115701058-DUx_TG2O.png b/assets/1564115701058-DUx_TG2O.png new file mode 100644 index 0000000000..b03feabd54 Binary files /dev/null and b/assets/1564115701058-DUx_TG2O.png differ diff --git a/assets/1564122799631-DVgcdHQo.png b/assets/1564122799631-DVgcdHQo.png new file mode 100644 index 0000000000..bb54da149b Binary files /dev/null and b/assets/1564122799631-DVgcdHQo.png differ diff --git a/assets/1564365395583-B6nSrg1b.png b/assets/1564365395583-B6nSrg1b.png new file mode 100644 index 0000000000..48d52a4440 Binary files /dev/null and b/assets/1564365395583-B6nSrg1b.png differ diff --git a/assets/1564365395583-BYzBww3x.js b/assets/1564365395583-BYzBww3x.js new file mode 100644 index 0000000000..b6189aacbf --- /dev/null +++ b/assets/1564365395583-BYzBww3x.js @@ -0,0 +1 @@ +const s="/assets/1563948810815-Dy4NLvPN.png",t="/assets/1563948863834-C44mQZaj.png",a="/assets/1563949823939-DTtxMLwY.png",o="/assets/1563958295010-B7UwU6QW.png",_="/assets/1563958396611-Che1gI68.png",n="/assets/1564115701058-DUx_TG2O.png",p="/assets/1564122799631-DVgcdHQo.png",g="/assets/1564365395583-B6nSrg1b.png";export{p as _,s as a,t as b,a as c,o as d,_ as e,n as f,g}; diff --git a/assets/1564378013035-eu7TrSmT.png b/assets/1564378013035-eu7TrSmT.png new file mode 100644 index 0000000000..30e63204af Binary files /dev/null and b/assets/1564378013035-eu7TrSmT.png differ diff --git a/assets/1564388703234-C6HVMeKj.png b/assets/1564388703234-C6HVMeKj.png new file mode 100644 index 0000000000..f60c454409 Binary files /dev/null and b/assets/1564388703234-C6HVMeKj.png differ diff --git a/assets/1564444282455-fMGiiEuS.png b/assets/1564444282455-fMGiiEuS.png new file mode 100644 index 0000000000..3f0f04094a Binary files /dev/null and b/assets/1564444282455-fMGiiEuS.png differ diff --git a/assets/1564449679656-Q7hUWdlx.png b/assets/1564449679656-Q7hUWdlx.png new file mode 100644 index 0000000000..8f90cb866e Binary files /dev/null and b/assets/1564449679656-Q7hUWdlx.png differ diff --git a/assets/1564449679656-jDgy1EGt.js b/assets/1564449679656-jDgy1EGt.js new file mode 100644 index 0000000000..32cfe6f1e1 --- /dev/null +++ b/assets/1564449679656-jDgy1EGt.js @@ -0,0 +1 @@ +const s="/assets/1564378013035-eu7TrSmT.png",t="/assets/1564388703234-C6HVMeKj.png",a="/assets/1564444282455-fMGiiEuS.png",o="/assets/1564449679656-Q7hUWdlx.png";export{s as _,t as a,a as b,o as c}; diff --git a/assets/1564450122219-CKg9ry-H.png b/assets/1564450122219-CKg9ry-H.png new file mode 100644 index 0000000000..4caa9af68c Binary files /dev/null and b/assets/1564450122219-CKg9ry-H.png differ diff --git a/assets/1564450122219-xixIqXDz.js b/assets/1564450122219-xixIqXDz.js new file mode 100644 index 0000000000..08c95b89b5 --- /dev/null +++ b/assets/1564450122219-xixIqXDz.js @@ -0,0 +1 @@ +const s="/assets/1564450122219-CKg9ry-H.png";export{s as _}; diff --git a/assets/1564463655933-CfGF9PtM.png b/assets/1564463655933-CfGF9PtM.png new file mode 100644 index 0000000000..7e28338b05 Binary files /dev/null and b/assets/1564463655933-CfGF9PtM.png differ diff --git a/assets/1564464197547-kzKVLGCb.png b/assets/1564464197547-kzKVLGCb.png new file mode 100644 index 0000000000..2ea0a14648 Binary files /dev/null and b/assets/1564464197547-kzKVLGCb.png differ diff --git a/assets/1564464880533-CnEtAD-2.png b/assets/1564464880533-CnEtAD-2.png new file mode 100644 index 0000000000..290432e233 Binary files /dev/null and b/assets/1564464880533-CnEtAD-2.png differ diff --git a/assets/1564465713857-Bb9GicK7.js b/assets/1564465713857-Bb9GicK7.js new file mode 100644 index 0000000000..ebe19063a2 --- /dev/null +++ b/assets/1564465713857-Bb9GicK7.js @@ -0,0 +1 @@ +const s="/assets/1564463655933-CfGF9PtM.png",t="/assets/1564464197547-kzKVLGCb.png",n="/assets/1564464880533-CnEtAD-2.png",a="/assets/1564465713857-CN3n8BVB.png";export{s as _,t as a,n as b,a as c}; diff --git a/assets/1564465713857-CN3n8BVB.png b/assets/1564465713857-CN3n8BVB.png new file mode 100644 index 0000000000..983ca95cce Binary files /dev/null and b/assets/1564465713857-CN3n8BVB.png differ diff --git a/assets/1564470826126-Did1V3RY.png b/assets/1564470826126-Did1V3RY.png new file mode 100644 index 0000000000..ebf583133e Binary files /dev/null and b/assets/1564470826126-Did1V3RY.png differ diff --git a/assets/1564471729123-DLD1jWZQ.png b/assets/1564471729123-DLD1jWZQ.png new file mode 100644 index 0000000000..604f8b6afc Binary files /dev/null and b/assets/1564471729123-DLD1jWZQ.png differ diff --git a/assets/1564471729123-DnEyDLm_.js b/assets/1564471729123-DnEyDLm_.js new file mode 100644 index 0000000000..f5bf69a334 --- /dev/null +++ b/assets/1564471729123-DnEyDLm_.js @@ -0,0 +1 @@ +const s="/assets/1564470826126-Did1V3RY.png",t="/assets/1564471729123-DLD1jWZQ.png";export{s as _,t as a}; diff --git a/assets/1564534571013-CEnvOb2w.png b/assets/1564534571013-CEnvOb2w.png new file mode 100644 index 0000000000..aeea0c9b40 Binary files /dev/null and b/assets/1564534571013-CEnvOb2w.png differ diff --git a/assets/1564537956019-_PlpZ-pE.png b/assets/1564537956019-_PlpZ-pE.png new file mode 100644 index 0000000000..0f6a85b7e5 Binary files /dev/null and b/assets/1564537956019-_PlpZ-pE.png differ diff --git a/assets/1564538201169-BBhK-FwW.png b/assets/1564538201169-BBhK-FwW.png new file mode 100644 index 0000000000..16c2b12a3a Binary files /dev/null and b/assets/1564538201169-BBhK-FwW.png differ diff --git a/assets/1564538419183-D4MhbkaU.png b/assets/1564538419183-D4MhbkaU.png new file mode 100644 index 0000000000..4be5b76136 Binary files /dev/null and b/assets/1564538419183-D4MhbkaU.png differ diff --git a/assets/1564540010695-B2yTNzZi.png b/assets/1564540010695-B2yTNzZi.png new file mode 100644 index 0000000000..af502b6bdc Binary files /dev/null and b/assets/1564540010695-B2yTNzZi.png differ diff --git a/assets/1564543546039-CpSD_dvM.png b/assets/1564543546039-CpSD_dvM.png new file mode 100644 index 0000000000..9e4041669c Binary files /dev/null and b/assets/1564543546039-CpSD_dvM.png differ diff --git a/assets/1564543591394-BKf8t37i.js b/assets/1564543591394-BKf8t37i.js new file mode 100644 index 0000000000..e39eb810eb --- /dev/null +++ b/assets/1564543591394-BKf8t37i.js @@ -0,0 +1 @@ +const s="/assets/1564534571013-CEnvOb2w.png",t="/assets/1564537956019-_PlpZ-pE.png",p="/assets/1564538201169-BBhK-FwW.png",_="/assets/1564538419183-D4MhbkaU.png",a="/assets/1564543546039-CpSD_dvM.png",n="/assets/1564540010695-B2yTNzZi.png",o="/assets/1564543591394-BqJ3SkmW.png";export{s as _,t as a,p as b,_ as c,a as d,n as e,o as f}; diff --git a/assets/1564543591394-BqJ3SkmW.png b/assets/1564543591394-BqJ3SkmW.png new file mode 100644 index 0000000000..7f47ddf097 Binary files /dev/null and b/assets/1564543591394-BqJ3SkmW.png differ diff --git a/assets/1564545470815-C-ib1VQu.png b/assets/1564545470815-C-ib1VQu.png new file mode 100644 index 0000000000..a991989e4a Binary files /dev/null and b/assets/1564545470815-C-ib1VQu.png differ diff --git a/assets/1564545639205-CgvhxLKo.png b/assets/1564545639205-CgvhxLKo.png new file mode 100644 index 0000000000..584382aa66 Binary files /dev/null and b/assets/1564545639205-CgvhxLKo.png differ diff --git a/assets/1564546308113-DwdkfHW2.png b/assets/1564546308113-DwdkfHW2.png new file mode 100644 index 0000000000..140de805fc Binary files /dev/null and b/assets/1564546308113-DwdkfHW2.png differ diff --git a/assets/1564546697375-Be8ZCm1Z.png b/assets/1564546697375-Be8ZCm1Z.png new file mode 100644 index 0000000000..9f0d0a4b57 Binary files /dev/null and b/assets/1564546697375-Be8ZCm1Z.png differ diff --git a/assets/1564546697375-DhhAeZD5.js b/assets/1564546697375-DhhAeZD5.js new file mode 100644 index 0000000000..466678b358 --- /dev/null +++ b/assets/1564546697375-DhhAeZD5.js @@ -0,0 +1 @@ +const s="/assets/1564546308113-DwdkfHW2.png",t="/assets/1564545470815-C-ib1VQu.png",o="/assets/1564545639205-CgvhxLKo.png",a="/assets/1564546697375-Be8ZCm1Z.png";export{s as _,t as a,o as b,a as c}; diff --git a/assets/1564547435978-BGhj8vjn.png b/assets/1564547435978-BGhj8vjn.png new file mode 100644 index 0000000000..425380e457 Binary files /dev/null and b/assets/1564547435978-BGhj8vjn.png differ diff --git a/assets/1564547694870-C4Z693td.png b/assets/1564547694870-C4Z693td.png new file mode 100644 index 0000000000..4edd75a1cc Binary files /dev/null and b/assets/1564547694870-C4Z693td.png differ diff --git a/assets/1564554095622-BniayC3t.png b/assets/1564554095622-BniayC3t.png new file mode 100644 index 0000000000..4ca713ab37 Binary files /dev/null and b/assets/1564554095622-BniayC3t.png differ diff --git a/assets/1564554995103-C2ctDU4I.png b/assets/1564554995103-C2ctDU4I.png new file mode 100644 index 0000000000..f0abea3d0e Binary files /dev/null and b/assets/1564554995103-C2ctDU4I.png differ diff --git a/assets/1564555063361-B9I5lMqm.png b/assets/1564555063361-B9I5lMqm.png new file mode 100644 index 0000000000..2388fdc415 Binary files /dev/null and b/assets/1564555063361-B9I5lMqm.png differ diff --git a/assets/1564555730104-DSwgnGhf.png b/assets/1564555730104-DSwgnGhf.png new file mode 100644 index 0000000000..de79aa2408 Binary files /dev/null and b/assets/1564555730104-DSwgnGhf.png differ diff --git a/assets/1564557613406-Du8U8roR.png b/assets/1564557613406-Du8U8roR.png new file mode 100644 index 0000000000..563876acf5 Binary files /dev/null and b/assets/1564557613406-Du8U8roR.png differ diff --git a/assets/1564557613406-aoabhQ4f.js b/assets/1564557613406-aoabhQ4f.js new file mode 100644 index 0000000000..6f37bc8e8a --- /dev/null +++ b/assets/1564557613406-aoabhQ4f.js @@ -0,0 +1 @@ +const s="/assets/1564547435978-BGhj8vjn.png",t="/assets/1564547694870-C4Z693td.png",n="/assets/1564554095622-BniayC3t.png",a="/assets/1564555063361-B9I5lMqm.png",o="/assets/1564554995103-C2ctDU4I.png",p="/assets/1564555730104-DSwgnGhf.png",_="/assets/1564557613406-Du8U8roR.png";export{s as _,t as a,n as b,a as c,o as d,p as e,_ as f}; diff --git a/assets/1691011664-k8s-vault-sidecar-workflow-copy-2x-CSoO09IP.webp b/assets/1691011664-k8s-vault-sidecar-workflow-copy-2x-CSoO09IP.webp new file mode 100644 index 0000000000..dce2edd2a2 Binary files /dev/null and b/assets/1691011664-k8s-vault-sidecar-workflow-copy-2x-CSoO09IP.webp differ diff --git a/assets/2-vso-install.html-DEpsuV_3.js b/assets/2-vso-install.html-DEpsuV_3.js new file mode 100644 index 0000000000..026aabd9aa --- /dev/null +++ b/assets/2-vso-install.html-DEpsuV_3.js @@ -0,0 +1,8 @@ +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as n,b as e,d as t,a as r,e as c}from"./app-Bzk8Nrll.js";const p={},i=e("h1",{id:"vault-secrets-operator-설치",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vault-secrets-operator-설치"},[e("span",null,"Vault Secrets Operator 설치")])],-1),h=e("br",null,null,-1),m={href:"https://github.com/hashicorp/vault-secrets-operator/issues",target:"_blank",rel:"noopener noreferrer"},u=e("h2",{id:"사전-요구사항",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#사전-요구사항"},[e("span",null,"사전 요구사항")])],-1),d=e("ul",null,[e("li",null,"Kubernetes 1.22+"),e("li",null,"Vault OSS/Enterprise 1.11+")],-1),v=e("h2",{id:"helm을-활용한-설치",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#helm을-활용한-설치"},[e("span",null,"Helm을 활용한 설치")])],-1),g={href:"https://developer.hashicorp.com/vault/docs/platform/k8s/vso/helm",target:"_blank",rel:"noopener noreferrer"},b=c(`

    VSO의 새 인스턴스를 설치하려면 먼저 HashiCorp Helm Repo를 추가하고 Chart에 액세스할 수 있는지 확인한다:

    $helm repo add hashicorp https://helm.releases.hashicorp.com
    +"hashicorp" has been added to your repositories
    +
    +$ helm search repo hashicorp/vault-secrets-operator --devel
    +NAME            CHART VERSION   APP VERSION DESCRIPTION
    +hashicorp/vault-secrets-operator    0.1.0-beta          0.1.0-beta      Official HashiCorp Vault Secrets Operator Chart
    +

    그런다음 Operator를 설치한다:

    $ helm install --create-namespace --namespace vault-secrets-operator vault-secrets-operator hashicorp/vault-secrets-operator --version 0.1.0-beta
    +

    Helm을 사용한 업그레이드

    업그레이드는 기존 설치에서 helm upgrade로 수행할 수 있다. 설치 또는 업그레이드 전에 항상 --dry-run으로 헬름을 실행하여 변경 사항을 확인한다.

    Helm Chart Values

    `,7),_={href:"https://developer.hashicorp.com/vault/docs/platform/k8s/vso/helm",target:"_blank",rel:"noopener noreferrer"};function f(k,V){const a=o("ExternalLinkIcon");return l(),n("div",null,[i,e("blockquote",null,[e("p",null,[t("참고:"),h,t(" 현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *"),e("a",m,[t("here"),r(a)]),t("*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.")])]),u,d,v,e("p",null,[e("a",g,[t("Vault Secrets Operator Helm chart"),r(a)]),t("는 Vault Secrets Operator(이하, VSO)를 설치하고 구성하는 권고 방안이다.")]),b,e("p",null,[t("지원되는 모든 헬름 차트 값은 "),e("a",_,[t("here"),r(a)]),t("에서 확인할 수 있다.")])])}const y=s(p,[["render",f],["__file","2-vso-install.html.vue"]]),H=JSON.parse('{"path":"/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html","title":"Vault Secrets Operator 설치","lang":"ko-KR","frontmatter":{"description":"Vault Secrets Operator(이하, VSO)를 설치하고 구성하는 권고 방안이다.","tag":["vault","operator"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Vault Secrets Operator 설치"}],["meta",{"property":"og:description","content":"Vault Secrets Operator(이하, VSO)를 설치하고 구성하는 권고 방안이다."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"vault"}],["meta",{"property":"article:tag","content":"operator"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vault Secrets Operator 설치\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"사전 요구사항","slug":"사전-요구사항","link":"#사전-요구사항","children":[]},{"level":2,"title":"Helm을 활용한 설치","slug":"helm을-활용한-설치","link":"#helm을-활용한-설치","children":[]},{"level":2,"title":"Helm을 사용한 업그레이드","slug":"helm을-사용한-업그레이드","link":"#helm을-사용한-업그레이드","children":[{"level":3,"title":"Helm Chart Values","slug":"helm-chart-values","link":"#helm-chart-values","children":[]}]}],"git":{"createdTime":1684599614000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Hyungwook Yu","email":"40632767+chosam2@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.39,"words":118},"filePathRelative":"04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.md","localizedDate":"2023년 5월 21일","excerpt":"\\n
    \\n

    참고:
    \\n현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *here*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

    \\n
    \\n

    사전 요구사항

    \\n"}');export{y as comp,H as data}; diff --git a/assets/2022-07.html-DG6lpmIo.js b/assets/2022-07.html-DG6lpmIo.js new file mode 100644 index 0000000000..44d453acf1 --- /dev/null +++ b/assets/2022-07.html-DG6lpmIo.js @@ -0,0 +1 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as i,b as e,d as r,a as o}from"./app-Bzk8Nrll.js";const s={},c=e("h1",{id:"_2022년-7월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2022년-7월"},[e("span",null,"2022년 7월")])],-1),u=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p=e("p",null,"HCP Boundary 출시 (Public Beta)",-1),h={href:"https://www.hashicorp.com/blog/announcing-launch-and-free-public-beta-of-hcp-boundary",target:"_blank",rel:"noopener noreferrer"},d={href:"https://www.youtube.com/watch?v=p_Wbi12xliE",target:"_blank",rel:"noopener noreferrer"},_={href:"https://learn.hashicorp.com/collections/boundary/hcp-getting-started",target:"_blank",rel:"noopener noreferrer"},m=e("li",null,"Hashicorp 는 모든 솔루션에 대해 사용자가 직접 설치하는 설치형 을 비롯해 이와 동일한 경험을 기반으로 SaaS 형태의 Cloud 서비스를 제공하고 있습니다. 지난 6월 21일, HCP Boundary 의 Public Beta 가 공개되어 무료 제공 중입니다.",-1),g=e("li",null,"AWS Platform 에 One click 으로 Cluster 생성 및 이용 가능하며 간단한 Network Peering 과정을 거쳐 AWS Platform 에 구성된 HVN (Hashicorp Virtual Network) 및 Cluster 와 사용자의 AWS 환경을 연결하여 미리 구성한 서비스들을 연동합니다. (AWS 지원 Region 확장 및 타 Cloud Platform 지원 예정)",-1),f=e("li",null,[r("HCP 계정 생성 시, "),e("strong",null,"USD 50불이 기본 Credit 으로 제공"),r("되며 이를 활용하여 Boundary Public Beta 외에도 다양한 Vault, Consul 과 같은 HCP Service 을 약 1개월간 체험해보실 수 있습니다.")],-1),b=e("p",null,"Hashicorp Developer Site 출시 (Public Beta)",-1),w={href:"https://www.hashicorp.com/blog/new-hashicorp-developer-site-is-now-in-public-beta?fbclid=IwAR1_Snhw3Yiqai2J5qcvWUlE82yaslu1nlC3O2iWDe8-wCFFWNPbPr-dxx4",target:"_blank",rel:"noopener noreferrer"},S={href:"https://developer.hashicorp.com/",target:"_blank",rel:"noopener noreferrer"},C={href:"http://learn.hashicorp.com",target:"_blank",rel:"noopener noreferrer"},k=e("li",null,"Public Beta 기간에는 Hashicorp Solution 중 Vault 와 Waypoint 에 대해 이용 가능하고, 추후 모든 Solution 에 대해 제공 예정입니다.",-1),v=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),P={href:"https://www.hashicorp.com/blog/terraform-cloud-adds-drift-detection-for-infrastructure-management",target:"_blank",rel:"noopener noreferrer"},y=e("ul",null,[e("li",null,"대상 환경의 상태를 지속적으로 확인하여Cloud Console 등을 이용한 수동 자원 생성 및 변경으로 인해 Code 및 State 와 불일치하는 변경사항에 대해 감지하고 운영자에게 알림으로써 변경사항에 대한 적용 여부를 결정할 수 있도록 하여 시스템 운영 상에 위해가 될 수 있는 위험요소와 비용을 최소화하도록 지원하는 기능")],-1),H={href:"https://github.com/hashicorp/terraform/blob/v1.2.3/CHANGELOG.md#123-june-15-2022",target:"_blank",rel:"noopener noreferrer"},B=e("ul",null,[e("li",null,"Remote State Backend 지원 종료: artifactory, etcd, etcdv3, manta, swift")],-1),N={href:"https://www.terraform.io/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},A={href:"https://www.terraform.io/enterprise/releases/2022/v202206-1",target:"_blank",rel:"noopener noreferrer"},T={href:"https://www.terraform.io/cloud-docs/workspaces/settings/run-tasks",target:"_blank",rel:"noopener noreferrer"},x={href:"https://www.terraform.io/enterprise/admin/infrastructure/backup-restore",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,"CLI 1.2.1 지원",-1),M=e("li",null,"SAML Performance 문제 개선",-1),W=e("li",null,"VCS 설정시 Automatic speculative plans 활성화 관련 오류 수정",-1),z=e("li",null,"보안취약점 개선",-1),E={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.20.0",target:"_blank",rel:"noopener noreferrer"},R=e("ul",null,[e("li",null,"EC2 Instance 관련 신규 argument 추가"),e("li",null,"VPC Endpoint 관련 argument 추가"),e("li",null,"VPN Connection 지원 위한 argument 추가"),e("li",null,"ISO-partition tagging 관련 오류 수정")],-1),j={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.11.0",target:"_blank",rel:"noopener noreferrer"},V=e("ul",null,[e("li",null,"azurerm_resource_group_template_deployment 에 대한 data source 추가"),e("li",null,"azurerm_managed_disk 관련 export 지원 (disk_access_id 및 network_access_policy)"),e("li",null,"azurerm_mysql_flexible_server 에 할당되는 resource ID 관련 오류 수정")],-1),U={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.27.0",target:"_blank",rel:"noopener noreferrer"},I=e("ul",null,[e("li",null,"Compute 에 대한 NAT 설정 관련 argument 추가 (maxPortsPerVm)"),e("li",null,"Compute 에 대한 forwarding rule 설정 관련 argument 추가 (psc_connection_id, psc_connection_status)"),e("li",null,"Compute 에 대한 dynamic port allocation 관련 오류 수정")],-1),O={href:"https://www.hashicorp.com/blog/vault-1-11",target:"_blank",rel:"noopener noreferrer"},K=e("li",null,"K8S Secret Engine: K8S Cluster 에서 사용되는 Service Account Token 에 대한 Dynamic Secret 발행 및 관리",-1),L={href:"https://www.vaultproject.io/docs/enterprise/automated-upgrades?_gl=1*1pfp1zr*_ga*MTA4MTgxMjU2NC4xNjM4ODI3NDM4*_ga_P7S46ZYEKW*MTY1NjM5MzQ0NS43LjAuMTY1NjM5MzQ0NS4w&_ga=2.18692156.469231660.1656292338-1081812564.1638827438#automated-upgrades",target:"_blank",rel:"noopener noreferrer"},Y={href:"https://www.vaultproject.io/docs/enterprise/redundancy-zones?_gl=1*1pfp1zr*_ga*MTA4MTgxMjU2NC4xNjM4ODI3NDM4*_ga_P7S46ZYEKW*MTY1NjM5MzQ0NS43LjAuMTY1NjM5MzQ0NS4w&_ga=2.18692156.469231660.1656292338-1081812564.1638827438#redundancy-zones",target:"_blank",rel:"noopener noreferrer"},Z=e("li",null,"Transit Secret Engine: Transit 이용 시 외부키 import 및 활용 추가",-1),q=e("li",null,"Transform Secret Engine: 동일 data 에 대한 convergent tokenization 기능 추가",-1),J=e("li",null,"Snowflake DB Secret Engine: RSA key pair credential 기반 접속정보 관리",-1),F={href:"https://www.hashicorp.com/blog/hcp-consul-on-azure-to-go-ga-plus-more-consul-news",target:"_blank",rel:"noopener noreferrer"},G=e("ul",null,[e("li",null,"기존 AWS 환경 기반 제공중인 HCP SaaS 서비스 중 Consul 에 대해 Azure 환경 기반 제공 (2022년 7월 예정)")],-1),Q={href:"https://www.hashicorp.com/blog/nomad-1-3-adds-native-service-discovery-and-edge-workload-support",target:"_blank",rel:"noopener noreferrer"},X=e("ul",null,[e("li",null,"Service Discovery 내장: Consul 연동 없이 자체 Service Discovery 기능 지원"),e("li",null,"Edge compute improvements: Network 단절 된 Client 가 재연결 시도 시 재시작 되는 문제 관련 개선")],-1);function $(ee,re){const t=n("ExternalLinkIcon");return a(),i("div",null,[c,u,e("ul",null,[e("li",null,[p,e("ul",null,[e("li",null,[e("a",h,[r("HCP Boundary 소개 Blog"),o(t)])]),e("li",null,[e("a",d,[r("Hashicorp Korea Snapshot"),o(t)])]),e("li",null,[e("a",_,[r("HCP Boundary 시작하기"),o(t)])]),m,g,f])]),e("li",null,[b,e("ul",null,[e("li",null,[e("a",w,[r("Hashicorp Developer Site 소개 Blog"),o(t)])]),e("li",null,[e("a",S,[r("Hashicorp Developer Site"),o(t)])]),e("li",null,[r("Tutorial 과 Reference Architecture 정보가 "),e("a",C,[r("learn.hashicorp.com"),o(t)]),r(" 을 비롯, 각 solution 별 website 에 파편화 되어 있어 Hashicorp Solution 을 보다 더 쉽게 이해하고 업무에 적용하는데에 어려움이 있었습니다. 새롭게 출시된 Hashicorp Developer Site 에서는 이러한 그동안 축적된 유용한 자료와 이를 테스트 해볼 수 있는 환경을 한 곳에 모아 통합 제공함으로써 보다 더 쉽게 Hashicorp Solution 을 경험할 수 있습니다.")]),k])])]),v,e("ul",null,[e("li",null,[r("Terraform "),e("ul",null,[e("li",null,[e("a",P,[r("Drift Detection for Terraform Cloud"),o(t)]),r(" (Public Beta) "),y]),e("li",null,[r("CLI "),e("ul",null,[e("li",null,[e("a",H,[r("1.23 Release"),o(t)]),B]),e("li",null,[e("a",N,[r("Enterprise Release"),o(t)]),e("ul",null,[e("li",null,[e("a",A,[r("6월 Release"),o(t)]),r(" 출시 (v202206-1)")]),e("li",null,[e("a",T,[r("Run Task"),o(t)]),r(" 기능 추가")]),e("li",null,[e("a",x,[r("Backup/Restore API"),o(t)]),r(" 내 Backup Skip 기능 추가")]),D,M,W,z])])])]),e("li",null,[r("Provider "),e("ul",null,[e("li",null,[e("a",E,[r("AWS v4.20.0 주요 수정사항"),o(t)]),R]),e("li",null,[e("a",j,[r("Azure v3.11.0 주요 수정사항"),o(t)]),V]),e("li",null,[e("a",U,[r("GCP v4.27.0 주요 수정사항"),o(t)]),I])])])])]),e("li",null,[r("Vault "),e("ul",null,[e("li",null,[e("a",O,[r("1.11 주요 수정사항"),o(t)]),e("ul",null,[K,e("li",null,[r("Integrated Storage Autopilot: "),e("a",L,[r("Cluster 자동 업그레이드"),o(t)]),r(" 및 "),e("a",Y,[r("Redundancy Zones"),o(t)]),r(" 기능 기반 가용성 및 확장성 확보")]),Z,q,J])])])]),e("li",null,[r("Consul "),e("ul",null,[e("li",null,[e("a",F,[r("HCP Consul on Azure"),o(t)]),G])])]),e("li",null,[r("Nomad "),e("ul",null,[e("li",null,[e("a",Q,[r("1.13 주요 수정사항"),o(t)]),X])])])])])}const le=l(s,[["render",$],["__file","2022-07.html.vue"]]),ne=JSON.parse('{"path":"/04-HashiCorp/08-Updates/99-2022/2022-07.html","title":"2022년 7월","lang":"ko-KR","frontmatter":{"description":"2022년 7월 Update","tag":["Hashicorp","Update","July"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/99-2022/2022-07.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2022년 7월"}],["meta",{"property":"og:description","content":"2022년 7월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"July"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2022년 7월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1656980314000,"updatedTime":1695042774000,"contributors":[{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":4},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":1.27,"words":382},"filePathRelative":"04-HashiCorp/08-Updates/99-2022/2022-07.md","localizedDate":"2022년 7월 5일","excerpt":"\\n

    Product 소개

    \\n"}');export{le as comp,ne as data}; diff --git a/assets/2022-08.html-4-PzckqW.js b/assets/2022-08.html-4-PzckqW.js new file mode 100644 index 0000000000..86549eee82 --- /dev/null +++ b/assets/2022-08.html-4-PzckqW.js @@ -0,0 +1 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as i,b as e,d as r,a as t}from"./app-Bzk8Nrll.js";const s={},c=e("h1",{id:"_2022년-8월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2022년-8월"},[e("span",null,"2022년 8월")])],-1),u=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p=e("p",null,"Consul Service Mesh 에 대한 AWS Lambda 지원 (Public Beta)",-1),h={href:"https://www.hashicorp.com/blog/consul-service-mesh-support-for-aws-lambda-now-in-public-beta",target:"_blank",rel:"noopener noreferrer"},d=e("li",null,"Service Mesh 내 구성된 Service 가 AWS Lambda 를 호출 할 수 있도록 지원함으로써, 기존의 K8S, VM, Nomad 혹은 Amazon ECS 등의 다양한 환경과 더불어 Serverless 환경까지 통합 지원하여 Service Mesh 구성의 범위를 확장하고 Workflow 일원화가 가능합니다.",-1),_={href:"https://www.consul.io/docs/lambda/registration",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.consul.io/docs/lambda/invocation",target:"_blank",rel:"noopener noreferrer"},g={href:"https://registry.terraform.io/modules/hashicorp/consul-lambda-registrator/aws/0.1.0-alpha2",target:"_blank",rel:"noopener noreferrer"},f=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),b={href:"https://github.com/hashicorp/terraform/releases/tag/v1.2.5",target:"_blank",rel:"noopener noreferrer"},v=e("ul",null,[e("li",null,"Minor Bug Fix")],-1),w={href:"https://www.terraform.io/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},k={href:"https://www.terraform.io/enterprise/releases/2022/v202207-2",target:"_blank",rel:"noopener noreferrer"},y=e("li",null,"필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202204-2)",-1),P=e("li",null,"Default 값 없는 변수 표시: VCS 연동 시 Default 값 없는 변수 표시 및 Default 값 설정 지",-1),S=e("li",null,"Workspace 최대개수 제한: ORG 당 생성 가능한 최대 Workspace 수 설정",-1),A=e("li",null,"exclude-tags 추가: Workspace 조회 시 특정 tag 가 붙은 Workspace 제외",-1),L=e("li",null,"API Rate Limit Logic 변경: 기존 IP 주소 기반 -> Token 유형 기반",-1),C={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.23.0",target:"_blank",rel:"noopener noreferrer"},T=e("ul",null,[e("li",null,"신규 Datasource 추가: Firewall, S3 Public Access"),e("li",null,"aws_connect_ 관련 오류 수정"),e("li",null,"aws_iam_role 관련 오류 수정"),e("li",null,"aws_rds_cluster 관련 오류 수정")],-1),R={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.15.0",target:"_blank",rel:"noopener noreferrer"},x=e("ul",null,[e("li",null,"azure_cdn resource 및 datasource 추가"),e("li",null,"azure_route_server 및 bgp_connection resource 추가")],-1),H={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.29.0",target:"_blank",rel:"noopener noreferrer"},I=e("ul",null,[e("li",null,"google_compute_snapshot 에 대한 iam 권한 설정 resource 추가"),e("li",null,"google_artifact_registry_repository 관련 resource 추가"),e("li",null,"google_sql_database_instance 에 대한 encryption_key_name 설정 추가"),e("li",null,"BigQuery 에 대한 user_by_email 및 group_by_email 에 대한 대소문자 구분 오류 수정"),e("li",null,"Compute 에 대한 ipv6 output 생성 오류 수정")],-1),M={href:"https://github.com/hashicorp/vault/blob/main/CHANGELOG.md#1111",target:"_blank",rel:"noopener noreferrer"},U=e("ul",null,[e("li",null,"Log Verbosity API 추가: Log 출력 level 변경 지원하는 API")],-1),V={href:"https://github.com/hashicorp/consul/releases/tag/v1.12.3",target:"_blank",rel:"noopener noreferrer"},W=e("ul",null,[e("li",null,"Envoy Proxy 의 최신 보안 Patch 에 대한 지원"),e("li",null,"HTTP Handler의 URL Decoding 오류 개선"),e("li",null,"GRPC 통신 관련 Memory Leak 오류 개선"),e("li",null,"GRPC 혹은 HTTP2 에 대한 Ingress Gateway 구성 시 발생하는 오류 개선")],-1),B={href:"https://github.com/hashicorp/nomad/releases/tag/v1.3.2",target:"_blank",rel:"noopener noreferrer"},N=e("ul",null,[e("li",null,"API 호출 시 Authorization Bearer Header 지원"),e("li",null,"Template Configuration 에 대한 Fault Tolerance 강화"),e("li",null,"예기치 못한 ACL Permission 거부 오류에 대한 수정")],-1);function z(E,G){const l=n("ExternalLinkIcon");return a(),i("div",null,[c,u,e("ul",null,[e("li",null,[p,e("ul",null,[e("li",null,[e("a",h,[r("Hashicorp Blog"),t(l)])]),d,e("li",null,[r("참고문서 1: "),e("a",_,[r("Register Lambda Functions"),t(l)])]),e("li",null,[r("참고문서 2: "),e("a",m,[r("Invoke Lambda Fuctions"),t(l)])]),e("li",null,[r("참고문서 3: "),e("a",g,[r("Terraform Registry: consul-lambda-registrator"),t(l)])])])])]),f,e("ul",null,[e("li",null,[r("Terraform "),e("ul",null,[e("li",null,[r("CLI "),e("ul",null,[e("li",null,[e("a",b,[r("1.25 Release"),t(l)]),v]),e("li",null,[e("a",w,[r("Enterprise Release"),t(l)]),e("ul",null,[e("li",null,[e("a",k,[r("7월 Release"),t(l)]),r(" 출시 (v202206-1)")]),y,P,S,A,L])])])]),e("li",null,[r("Provider "),e("ul",null,[e("li",null,[e("a",C,[r("AWS v4.23.0 주요 수정사항"),t(l)]),T]),e("li",null,[e("a",R,[r("Azure v3.15.0 주요 수정사항"),t(l)]),x]),e("li",null,[e("a",H,[r("GCP v4.29.0 주요 수정사항"),t(l)]),I])])])])]),e("li",null,[r("Vault "),e("ul",null,[e("li",null,[e("a",M,[r("1.11.1 주요 수정사항"),t(l)]),U])])]),e("li",null,[r("Consul "),e("ul",null,[e("li",null,[e("a",V,[r("1.12.3 주요 수정사항"),t(l)]),W])])]),e("li",null,[r("Nomad "),e("ul",null,[e("li",null,[e("a",B,[r("1.3.2 주요 수정사항"),t(l)]),N])])])])])}const K=o(s,[["render",z],["__file","2022-08.html.vue"]]),j=JSON.parse('{"path":"/04-HashiCorp/08-Updates/99-2022/2022-08.html","title":"2022년 8월","lang":"ko-KR","frontmatter":{"description":"2022년 8월 Update","tag":["Hashicorp","Update","Aug"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/99-2022/2022-08.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2022년 8월"}],["meta",{"property":"og:description","content":"2022년 8월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Aug"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2022년 8월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1659510375000,"updatedTime":1695042774000,"contributors":[{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":5},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.69,"words":207},"filePathRelative":"04-HashiCorp/08-Updates/99-2022/2022-08.md","localizedDate":"2022년 8월 3일","excerpt":"\\n

    Product 소개

    \\n"}');export{K as comp,j as data}; diff --git a/assets/2022-09.html-DEZ_nh_3.js b/assets/2022-09.html-DEZ_nh_3.js new file mode 100644 index 0000000000..ca306c2840 --- /dev/null +++ b/assets/2022-09.html-DEZ_nh_3.js @@ -0,0 +1 @@ +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as i,b as e,d as r,a as t}from"./app-Bzk8Nrll.js";const s={},u=e("h1",{id:"_2022년-9월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2022년-9월"},[e("span",null,"2022년 9월")])],-1),c=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p=e("p",null,"CDKTF (Cloud Development Kit for Terraform) General Available",-1),_={href:"https://www.hashicorp.com/blog/cdk-for-terraform-now-generally-available",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"Python, Go 등 프로그래밍 언어 기반으로 Terraform 을 활용하실 수 있도록 지원하는 CDKTF 가 정식 출시 되었습니다. CDKTF를 사용하면 개발자는 익숙한 프로그래밍 언어에서 컨텍스트 전환 없이 코드로 인프라를 설정할 수 있으며, 애플리케이션 비즈니스 로직을 정의하는 데 사용하는 인프라 리소스를 프로비저닝하기 위해 동일한 도구와 구문을 사용할 수 있습니다. 팀은 익숙한 구문으로 협업하면서 Terraform 에코시스템의 기능을 계속 활용하고 확립된 Terraform 배포 파이프라인을 통해 인프라 구성을 배포할 수 있습니다.",-1),d={href:"https://github.com/hashicorp/terraform-cdk/blob/main/CHANGELOG.md#0120",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.terraform.io/cdktf?_gl=1*1sc3uq2*_ga*MjA4NTc1MTMyNy4xNjM4OTUwNzQ3*_ga_P7S46ZYEKW*MTY2MTMxOTcxNS4xMzQuMS4xNjYxMzIxMjUxLjAuMC4w",target:"_blank",rel:"noopener noreferrer"},g={href:"https://learn.hashicorp.com/collections/terraform/cdktf?_gl=1*1wv13qn*_ga*MjA4NTc1MTMyNy4xNjM4OTUwNzQ3*_ga_P7S46ZYEKW*MTY2MTMxOTcxNS4xMzQuMS4xNjYxMzIxMTcxLjAuMC4w",target:"_blank",rel:"noopener noreferrer"},f=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),T=e("p",null,"Terraform",-1),b=e("p",null,"CLI",-1),M={href:"https://github.com/hashicorp/terraform/releases/tag/v1.2.8",target:"_blank",rel:"noopener noreferrer"},v=e("ul",null,[e("li",null,"tolist, tomap, toset 등 type 변환 작업 시 변환 대상 type 유추로 인한 panic 이 발생하는 오류 개선")],-1),w={href:"https://www.terraform.io/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},x={href:"https://www.terraform.io/enterprise/releases/2022/v202208-3",target:"_blank",rel:"noopener noreferrer"},k=e("li",null,[e("p",null,"필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)")],-1),y=e("li",null,[e("p",null,"VCS 기반 Workspace 생성 시 정의된 variable 에 대한 type 검증 및 오류 안내 기능 추가")],-1),N=e("li",null,[e("p",null,"Known Issue: TFE 내부 모듈 중 하나인 Postgres - ver 10 또는 11 에 대한 Migration 실패 오류 개선 (v202208-2)")],-1),C=e("li",null,[e("p",null,"Run Pipeline 에 대한 Metric 정보가 Prometheus 에 표시 되지 않는 이슈 개선")],-1),j=e("li",null,[e("p",null,"Module Registry Protocol endpoint 인 '/v1/modules/{namespace}/{name}/{provider}/versions' 에서 version 갯수가 많은 Module 처리시 발생하는 오류 개선")],-1),K=e("p",null,"Provider",-1),S={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.27.0",target:"_blank",rel:"noopener noreferrer"},P=e("ul",null,[e("li",null,"networkmanager 관련 resource 추가"),e("li",null,"firewall data source에 대한 capacity_usage_summary, firewall_status 등 argument 추가"),e("li",null,"lb_target_group resource 에 대한 ip_address_type argument 추가"),e("li",null,"aws_db_instance resource 에 대한 특정 argument 수정 시 발생하는 'InvalidParameterCombination: No modifications were requested' 오류 개선"),e("li",null,"aws_opsworks resource 에 대한 region 정보 및 tag 적용 관련 오류 개선")],-1),A={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.19.1",target:"_blank",rel:"noopener noreferrer"},z=e("ul",null,[e("li",null,"azurerm_dns resource 에 대해 resource ID parsing 시 발생하는 대소문자 관련 오류 개선")],-1),U={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.33.0",target:"_blank",rel:"noopener noreferrer"},D=e("ul",null,[e("li",null,"google_cloudfunctions2_function resource 추가"),e("li",null,"google_container_cluster 에 대해 authenticator_groups_config 지원"),e("li",null,"google_cloud_sql resource 에 대해 password_validation_policy argument 추가"),e("li",null,"google_dns_managed_zone data source 에 대해 managed_zone_id argument 추가"),e("li",null,"google_bigquery_data_transfer_config 에 대해 display_name 강제 변경 오류 개선")],-1),G=e("p",null,"Vault",-1),O={href:"https://github.com/hashicorp/vault/blob/main/CHANGELOG.md#1112",target:"_blank",rel:"noopener noreferrer"},E=e("ul",null,[e("li",null,"Vaunt Agent 사용 시 keep_alives 비활성화 설정 지원"),e("li",null,"잘못된 Server Side Consistent Token 에 대해 500 이 아닌 403 에러 출력"),e("li",null,"UI 에서 제공 되는 JWT auth method 관련 log 정보 개선")],-1),I=e("p",null,"Consul",-1),H={href:"https://github.com/hashicorp/consul/releases/tag/v1.13.1",target:"_blank",rel:"noopener noreferrer"},L=e("ul",null,[e("li",null,"snapshot 관련 호환성 이슈 개선"),e("li",null,"grpc peering 관련 오류 개선")],-1),Y=e("p",null,"Nomad",-1),F={href:"https://github.com/hashicorp/nomad/releases/tag/v1.3.3",target:"_blank",rel:"noopener noreferrer"},R=e("ul",null,[e("li",null,"CSI Plugin 에 대한 stage_publish_base_dir 통해 특정 stage / publishing directory mount 지원"),e("li",null,"qemu 에 대한 socket file 명 축약 기능 지원"),e("li",null,"만료된 일회성 token 의 timestamp 관련 오류 개선"),e("li",null,"UI 의 Evaluation tab 에서 일부 누락된 정보 출력 개선"),e("li",null,"task 가 사용하는 메모리가 0 으로 출력되는 오류 개선")],-1);function V(q,Q){const l=n("ExternalLinkIcon");return a(),i("div",null,[u,c,e("ul",null,[e("li",null,[p,e("ul",null,[e("li",null,[e("a",_,[r("Hashicorp Blog"),t(l)])]),h,e("li",null,[r("참고문서 1: "),e("a",d,[r("CDK for Terraform v0.12: CHANGELOG"),t(l)])]),e("li",null,[r("참고문서 2: "),e("a",m,[r("CDKTF Overview"),t(l)])]),e("li",null,[r("참고문서 3: "),e("a",g,[r("CDKTF Tutorials"),t(l)])])])])]),f,e("ul",null,[e("li",null,[T,e("ul",null,[e("li",null,[b,e("ul",null,[e("li",null,[e("p",null,[e("a",M,[r("1.28 Release"),t(l)])]),v]),e("li",null,[e("p",null,[e("a",w,[r("Enterprise Release"),t(l)])]),e("ul",null,[e("li",null,[e("p",null,[e("a",x,[r("8월 Release"),t(l)]),r(" 출시 (v202208-3)")])]),k,y,N,C,j])])])]),e("li",null,[K,e("ul",null,[e("li",null,[e("a",S,[r("AWS v4.27.0 주요 수정사항"),t(l)]),P]),e("li",null,[e("a",A,[r("Azure v3.19.1 주요 수정사항"),t(l)]),z]),e("li",null,[e("a",U,[r("GCP v4.33.0 주요 수정사항"),t(l)]),D])])])])]),e("li",null,[G,e("ul",null,[e("li",null,[e("a",O,[r("1.11.2 주요 수정사항"),t(l)]),E])])]),e("li",null,[I,e("ul",null,[e("li",null,[e("a",H,[r("1.13.1 주요 수정사항"),t(l)]),L])])]),e("li",null,[Y,e("ul",null,[e("li",null,[e("a",F,[r("1.3.3 주요 수정사항"),t(l)]),R])])])])])}const B=o(s,[["render",V],["__file","2022-09.html.vue"]]),J=JSON.parse('{"path":"/04-HashiCorp/08-Updates/99-2022/2022-09.html","title":"2022년 9월","lang":"ko-KR","frontmatter":{"description":"2022년 9월 Update","tag":["Hashicorp","Update","Sep"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/99-2022/2022-09.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2022년 9월"}],["meta",{"property":"og:description","content":"2022년 9월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Sep"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2022년 9월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1662203164000,"updatedTime":1695042774000,"contributors":[{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.83,"words":249},"filePathRelative":"04-HashiCorp/08-Updates/99-2022/2022-09.md","localizedDate":"2022년 9월 3일","excerpt":"\\n

    Product 소개

    \\n"}');export{B as comp,J as data}; diff --git a/assets/2022-10.html-y6mQjU3f.js b/assets/2022-10.html-y6mQjU3f.js new file mode 100644 index 0000000000..8ce6c241e7 --- /dev/null +++ b/assets/2022-10.html-y6mQjU3f.js @@ -0,0 +1 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as i,b as e,d as t,a as l}from"./app-Bzk8Nrll.js";const s={},c=e("h1",{id:"_2022년-10월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2022년-10월"},[e("span",null,"2022년 10월")])],-1),u=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p=e("p",null,"Nomad: Nomad Variables and Service Discovery",-1),d={href:"https://www.hashicorp.com/blog/nomad-1-4-adds-nomad-variables-and-updates-service-discovery",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"Hashicorp Nomad는 Container 뿐만 아니라 Container 화 하기 어려운 Legacy Application 에 대해 배포하고 관리하는 데 사용되는 간단하고 유연한 오케스트레이터입니다. Nomad는 On-prem 및 Cloud 환경을 가리지 않고 작동합니다. Cloudflare, Roblox, Q2 및 Pandora와 같은 조직의 프로덕션에서 널리 채택되고 사용됩니다. 새롭게 출시된 HashiCorp Nomad 1.4 Beta Release 에서는 상태 확인을 통해 Service Discovery 지원을 강화하고 사용자가 구성 값을 저장할 수 있도록 하는 Nomad Variable 기능이 도입 되었습니다.",-1),_=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),m={href:"https://github.com/hashicorp/terraform/releases/tag/v1.3.0",target:"_blank",rel:"noopener noreferrer"},g=e("ul",null,[e("li",null,"Optional attributes for object type constraints: 변수 사용 시 Type 지정에 대한 'Optional' 지원"),e("li",null,"'moved' block 기능개선: resource 에 대한 refactor 기능 개선 (예: aws_instance 를 instance module 로 refactoring)")],-1),f={href:"https://www.terraform.io/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},v={href:"https://www.terraform.io/enterprise/releases/2022/v202209-2",target:"_blank",rel:"noopener noreferrer"},b=e("li",null,[e("p",null,"필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)")],-1),y=e("li",null,[e("p",null,"TFE 엔진 내 Vault 에 대한 정책 수정하여 간헐적으로 TFE 구동 시 발생하는 403 오류 해결")],-1),C=e("li",null,[e("p",null,"TFE 엔진 내 Data Migration Logic 에 대해 Postgres 11 이상에서만 지원")],-1),w={href:"https://www.terraform.io/enterprise/run/remote-operations#speculative-plans",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.32.0",target:"_blank",rel:"noopener noreferrer"},N=e("ul",null,[e("li",null,"resource/aws_eks_cluster 에 대한 outpost_config 설정 추가 (Outpost 에서 EKS 사용 지원)"),e("li",null,"resource/aws_ec2_managed_prefix_list 에 대해 MaxEntries and Entry 반영 오류 개선")],-1),x={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.24.0",target:"_blank",rel:"noopener noreferrer"},T=e("ul",null,[e("li",null,"azurerm_linux(windows)_virtual_machine 에 대한 patch_assessment_mode 설정 추가"),e("li",null,"azurerm_managed_disk 에 대한 PremiumV2_LRS type 지원"),e("li",null,"azurerm_private_endpoint 에 대한 custom_network_interface_name 설정 추가"),e("li",null,"azurerm_storage_account 에 대한 azure_files_identity_based_auth 정보 export 지원")],-1),P={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.38.0",target:"_blank",rel:"noopener noreferrer"},R=e("ul",null,[e("li",null,"appengine 에 대한 egress_setting 설정 추가"),e("li",null,"bigquery 에 대한 json_extension 설정 추가"),e("li",null,"compute 에 대한 json_custom_config 설정 추가"),e("li",null,"compute 에 대한 most_disruptive_allowed_action update 불가 오류 개선"),e("li",null,"storage 에 대한 lifecycle_rule.condition.age 설정 오류 개선")],-1),S={href:"https://github.com/hashicorp/vault/blob/main/CHANGELOG.md#1113",target:"_blank",rel:"noopener noreferrer"},V=e("ul",null,[e("li",null,"identity/oidc 관련 오류 개선"),e("li",null,"integrated storage (raft) 방식 사용 시 retry join 관련 오류 개선")],-1),E={href:"https://github.com/hashicorp/consul/releases/tag/v1.13.2",target:"_blank",rel:"noopener noreferrer"},H={href:"https://www.consul.io/commands/peering",target:"_blank",rel:"noopener noreferrer"},L=e("li",null,"Metric 관련 Label 기능 추가",-1),U=e("li",null,"Envoy 관련 Outlier (이상감지) 에 대한 추가 Parameter 지원",-1),O=e("li",null,"Consul Connect 에 대해 ConnectCA CSR requests 의 URI Length Check 개선",-1),z=e("li",null,"auto-config JWT authorization 에 대한 input 값 검증 개선",-1),j=e("li",null,"Consul Connect (Service Mesh) 의 TLS 인증서 관련 오류 개선",-1),A={href:"https://github.com/hashicorp/nomad/releases/tag/v1.3.5",target:"_blank",rel:"noopener noreferrer"},B=e("ul",null,[e("li",null,"Consul 연동 시 Namespace 정보 조회 관련 개선"),e("li",null,'template 사용시 change_mode = "script" 명시로 인한 오류 개선')],-1);function D(M,F){const o=n("ExternalLinkIcon");return a(),i("div",null,[c,u,e("ul",null,[e("li",null,[p,e("ul",null,[e("li",null,[e("a",d,[t("Hashicorp Blog"),l(o)])]),h])])]),_,e("ul",null,[e("li",null,[t("Terraform "),e("ul",null,[e("li",null,[t("CLI "),e("ul",null,[e("li",null,[e("a",m,[t("1.30 Release"),l(o)]),g]),e("li",null,[e("a",f,[t("Enterprise Release"),l(o)]),e("ul",null,[e("li",null,[e("p",null,[e("a",v,[t("9월 Release"),l(o)]),t(" 출시 (v202209-2)")])]),b,y,C,e("li",null,[e("p",null,[t("TF Code 변경에 대한 Test 등을 지원하는 "),e("a",w,[t("예측 계획 (Speculative Plans)"),l(o)]),t(" 기능 지원")])])])])])]),e("li",null,[t("Provider "),e("ul",null,[e("li",null,[e("a",k,[t("AWS v4.32.0 주요 수정사항"),l(o)]),N]),e("li",null,[e("a",x,[t("Azure v3.24.0 주요 수정사항"),l(o)]),T]),e("li",null,[e("a",P,[t("GCP v4.38.0 주요 수정사항"),l(o)]),R])])])])]),e("li",null,[t("Vault "),e("ul",null,[e("li",null,[e("a",S,[t("1.11.3 주요 수정사항"),l(o)]),V])])]),e("li",null,[t("Consul "),e("ul",null,[e("li",null,[e("a",E,[t("1.13.2 주요 수정사항"),l(o)]),e("ul",null,[e("li",null,[t("신규 consul cli command: "),e("a",H,[t("peering"),l(o)])]),L,U,O,z,j])])])]),e("li",null,[t("Nomad "),e("ul",null,[e("li",null,[e("a",A,[t("1.3.5 주요 수정사항"),l(o)]),B])])])])])}const K=r(s,[["render",D],["__file","2022-10.html.vue"]]),Z=JSON.parse('{"path":"/04-HashiCorp/08-Updates/99-2022/2022-10.html","title":"2022년 10월","lang":"ko-KR","frontmatter":{"description":"2022년 10월 Update","tag":["Hashicorp","Update","Oct"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/99-2022/2022-10.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2022년 10월"}],["meta",{"property":"og:description","content":"2022년 10월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Oct"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2022년 10월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1664864834000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.75,"words":225},"filePathRelative":"04-HashiCorp/08-Updates/99-2022/2022-10.md","localizedDate":"2022년 10월 4일","excerpt":"\\n

    Product 소개

    \\n"}');export{K as comp,Z as data}; diff --git a/assets/2022-11.html-m34nQJRW.js b/assets/2022-11.html-m34nQJRW.js new file mode 100644 index 0000000000..f52c863b39 --- /dev/null +++ b/assets/2022-11.html-m34nQJRW.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as a,c as i,b as e,d as r,a as l,e as s}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2022년-11월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2022년-11월"},[e("span",null,"2022년 11월")])],-1),p=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),h=e("p",null,"Hashiconf Global",-1),d={href:"https://www.hashicorp.com/blog/hashiconf-global-2022-from-zero-trust-to-no-code",target:"_blank",rel:"noopener noreferrer"},_=e("p",null,"Day 1: ZTS (Zero Trust Security) 와 Cloud Service Networking 을 메인 주제로 새로운 기능과 HCP 서비스에 대한 소개",-1),g={href:"https://www.hashicorp.com/blog/hcp-boundary-now-ga-bolsters-hashicorp-s-zero-trust-security-solution",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.hashicorp.com/blog/hcp-vault-on-microsoft-azure-now-in-public-beta",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.hashicorp.com/blog/consul-1-14-beta-announcing-simplified-service-mesh-deployments",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.hashicorp.com/blog/hashicorp-developer-your-new-experience-for-docs-and-tutorials",target:"_blank",rel:"noopener noreferrer"},w=e("p",null,"Day 2: Infrastructure 및 Application 자동화 관련 제품군을 메인 주제로 새로운 기능 소개",-1),v={href:"https://www.hashicorp.com/blog/terraform-gains-visibility-self-service-and-compliance-upgrades",target:"_blank",rel:"noopener noreferrer"},y={href:"https://www.hashicorp.com/blog/nomad-1-4-adds-nomad-variables-and-updates-service-discovery",target:"_blank",rel:"noopener noreferrer"},P={href:"https://www.hashicorp.com/blog/announcing-hcp-waypoint-public-beta",target:"_blank",rel:"noopener noreferrer"},S=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),k={href:"https://www.hashicorp.com/blog/terraform-gains-visibility-self-service-and-compliance-upgrades",target:"_blank",rel:"noopener noreferrer"},C={href:"https://developer.hashicorp.com/terraform/cloud-docs/workspaces/health?_gl=1*cm2dqn*_ga*MjA4NTc1MTMyNy4xNjM4OTUwNzQ3*_ga_P7S46ZYEKW*MTY2NjYxMjg0NS4yMTkuMS4xNjY2NjEzMjM1LjAuMC4w#continuous-validation",target:"_blank",rel:"noopener noreferrer"},T={href:"https://www.hashicorp.com/blog/drift-detection-for-terraform-cloud-is-now-generally-available",target:"_blank",rel:"noopener noreferrer"},M={href:"https://developer.hashicorp.com/terraform/cloud-docs/no-code-provisioning/module-design?_gl=1*ilib11*_ga*MjA4NTc1MTMyNy4xNjM4OTUwNzQ3*_ga_P7S46ZYEKW*MTY2NjYxMjg0NS4yMTkuMS4xNjY2NjEzNDQwLjAuMC4w",target:"_blank",rel:"noopener noreferrer"},A={href:"https://developer.hashicorp.com/terraform/cloud-docs/policy-enforcement/opa",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,"CLI",-1),H={href:"https://github.com/hashicorp/terraform/releases/tag/v1.3.3",target:"_blank",rel:"noopener noreferrer"},z=e("ul",null,[e("li",null,"이미 삭제됐으나, Code 상에 존재하는 Resource 삭제 시 발생하는 오류 개선")],-1),x={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},V={href:"https://www.terraform.io/enterprise/releases/2022/v202208-3",target:"_blank",rel:"noopener noreferrer"},B=s("
  • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)

  • PostgresSQL 버전 10 지원종료: TFE 에 대해 External PostgresSQL 사용하는 경우 최소 버전 12 이상으로 Upgrade 필요

  • 구버전 OS 지원종료: 2023년 2월 release (v202302-1) 을 기점으로 아래 OS 목록에 대해 지원 종료

  • ",3),j=e("p",null,"Provider",-1),G={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.36.0",target:"_blank",rel:"noopener noreferrer"},D=e("ul",null,[e("li",null,"aws_lightsail 관련 resource 추가"),e("li",null,"aws_route53_zone 에 대해 primary_name_server 설정 추가"),e("li",null,"aws_resourcegroups_group 에 대해 configuration 설정 추가"),e("li",null,"aws_sqs_queue 관련 오류 개선")],-1),O={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.28.0",target:"_blank",rel:"noopener noreferrer"},R=e("ul",null,[e("li",null,"azurerm_sentinel_data_connector 관련 resource 추가"),e("li",null,"azurerm_linux(windows)_web_app, azurerm_linux(windows)function_app 에 대해 certificate 관련 설정 추가"),e("li",null,"azurerm_storage_account 관련 account_tier 설정 추가"),e("li",null,"azurerm_linux(windows)_web_app, azurerm_linux(windows)function_app 에 대해 오류 개선")],-1),U={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.41.0",target:"_blank",rel:"noopener noreferrer"},W=e("ul",null,[e("li",null,"google_sql_user.sql_server_user_details 에 대해 read-only 만 가능하도록 수정"),e("li",null,"google_bigquery_table 에 대해 avro_options 설정 추가"),e("li",null,"google_container_node_pool 에 대해 node_config.0.guest_accelerator.0.gpu_sharing_config 설정 추가"),e("li",null,"google_filestore_instance 이 여러개 연속 생성 되도록 강제하는 조건 삭제")],-1),E={href:"https://www.hashicorp.com/blog/vault-1-12",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/hashicorp/vault/releases/tag/v1.12.0",target:"_blank",rel:"noopener noreferrer"},L=e("li",null,"PKI Secret Engine 관련 개선: RSA-PSS signature, telemetry, Google Cloud Key Manager 지원 등",-1),I=e("li",null,"PKCS #11 Provider 지원: HSM 연동 관련 기능 및 Oracle TDE 지원",-1),Y=e("li",null,"Transform 관련 개선: BYOK, MSSQL 지원, Key 자동 Rotation 등",-1),Z={href:"https://www.hashicorp.com/blog/hcp-vault-on-microsoft-azure-now-in-public-beta",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/hashicorp/consul/releases/tag/v1.13.3",target:"_blank",rel:"noopener noreferrer"},q=e("ul",null,[e("li",null,"Ingress Gateway (Service Mesh) 에 대한 Upstream Max Connection 설정 개선"),e("li",null,"Terminating Gateway 및 Mesh Gateway (Service Mesh) 에 대한 TCP Keepalives 설정 추가"),e("li",null,"Agent Cache 관련 goroutine leak 발생 bug 수정"),e("li",null,"Snapshot Agent 관련 Session 조회 불가로 인한 Panic 발생 bug 수정")],-1),F={href:"https://www.hashicorp.com/blog/consul-1-14-beta-announcing-simplified-service-mesh-deployments",target:"_blank",rel:"noopener noreferrer"},J=e("ul",null,[e("li",null,"Consul Client on K8S 에 대한 구조변화: Daemonset 에서 Sidecar 방식으로 변경"),e("li",null,"Cluster Peering 기반 Service Failover 포함 Mesh Traffic Management 기능 개선"),e("li",null,"Windows 에 대한 Service Mesh 지원"),e("li",null,"AWS Lambda 에 대한 Service Mesh 지원")],-1),X={href:"https://www.hashicorp.com/blog/nomad-1-4-adds-nomad-variables-and-updates-service-discovery",target:"_blank",rel:"noopener noreferrer"},$={href:"https://github.com/hashicorp/nomad/releases/tag/v1.4.0",target:"_blank",rel:"noopener noreferrer"},ee=e("li",null,"기밀 정보 활용 위한 자체 KV, Variable 기능 추가",-1),re=e("li",null,"Service Discovery 기능 추가",-1),oe=e("li",null,"ACL Token 기반 Policy 기능 추가",-1),le=e("li",null,"Web UI 에서 Task 단위 정보 출력 개선",-1),ne=e("li",null,"Raft Version 2 지원 종료 (Upgrade 시 참고)",-1);function te(ae,ie){const o=t("ExternalLinkIcon");return a(),i("div",null,[u,p,e("ul",null,[e("li",null,[h,e("ul",null,[e("li",null,[e("p",null,[e("a",d,[r("Hashicorp Blog"),l(o)])])]),e("li",null,[_,e("ul",null,[e("li",null,[e("a",g,[r("HCP Boundary GA"),l(o)]),r(": Opensource 버전만 지원하던 Boundary 에 대해 HCP Version 공식 출시")]),e("li",null,[e("a",m,[r("HCP Vault on Microsoft Azure Public Beta"),l(o)]),r(": AWS 뿐만 아니라 Azure 사용자들도 HCP Vault 사용 가능")]),e("li",null,[e("a",f,[r("Consul Breaking Changes"),l(o)]),r(": K8S 에서의 Component 간소화 및 기능강화, Windows 에 대한 Service Mesh 지원")]),e("li",null,[e("a",b,[r("Hashicorp Developer Portal"),l(o)]),r(" 개편: 8가지 전 제품군에 대한 공식 문서 및 Tutorial 총망라")])])]),e("li",null,[w,e("ul",null,[e("li",null,[e("a",v,[r("Terraform Cloud"),l(o)]),r(": Self Service 및 Compliance 관련 기능 강화")]),e("li",null,[e("a",y,[r("Nomad 1.4 GA"),l(o)]),r(": 단독 제품으로서의 Service Discovery, Variable 등 기능 강화")]),e("li",null,[e("a",P,[r("HCP Waypoint Public Beta"),l(o)]),r(" 출시: Opensource 버전만 지원하던 Waypoint 의 HCP Version Public Beta 출시")])])])])])]),S,e("ul",null,[e("li",null,[r("Terraform "),e("ul",null,[e("li",null,[e("p",null,[r("Terraform Cloud 신규 기능 (현재 모두 Beta, "),e("a",k,[r("Hashiconf Global 발표"),l(o)]),r(" 참고)")]),e("ul",null,[e("li",null,[e("p",null,[e("a",C,[r("Continous Validation"),l(o)]),r(": Terraform 으로 Provisioning 한 (Day 0) Resource 에 대한 수동 변화를 감지하는 "),e("a",T,[r("Drift Detection"),l(o)]),r(" 와 더불어 장기적 관리 및 유지보수 관점에서 필요한 사전 (Precondition) 및 사후 (Postcondition) 조건을 기반으로 Resource 의 상태를 점검하고 관리하는 기능")])]),e("li",null,[e("p",null,[e("a",M,[r("No-Code Provisioning"),l(o)]),r(": Terraform 에 대해 Code 작성과 같은 기본 지식 또는 Module 과 같은 고급 지식에 대한 이해 없이 최소한의 변수 정보 입력만으로 Terraform 기반의 Workspace 생성 부터 Resource Provisioning 까지 사용할 수 있게 지원하는 Self-Service 특화 기능")])]),e("li",null,[e("p",null,[e("a",A,[r("OPA (Open Policy Agent)"),l(o)]),r(" 기반 정책 관리: Rego 정책 언어 기반 OPA 릴 지원하여 기존에 OPA 기반 표준 정책 수립한 사용자도 손쉽게 Terraform Cloud 에 Import 하여 정책 기반 Resource Provisioning 을 지원하는 기능")])])])]),e("li",null,[N,e("ul",null,[e("li",null,[e("a",H,[r("1.33 Release"),l(o)]),z]),e("li",null,[e("a",x,[r("Enterprise Release"),l(o)]),e("ul",null,[e("li",null,[e("p",null,[e("a",V,[r("10월 Release"),l(o)]),r(" 출시 (v202210-1)")])]),B])])])]),e("li",null,[j,e("ul",null,[e("li",null,[e("a",G,[r("AWS v4.36.0 주요 수정사항"),l(o)]),D]),e("li",null,[e("a",O,[r("Azure v3.28.0 주요 수정사항"),l(o)]),R]),e("li",null,[e("a",U,[r("GCP v4.41.0 주요 수정사항"),l(o)]),W])])])])]),e("li",null,[r("Vault "),e("ul",null,[e("li",null,[e("a",E,[r("1.12 주요 수정사항"),l(o)]),e("ul",null,[e("li",null,[r("상세 내용: "),e("a",K,[r("CHANGELOG"),l(o)]),r(" 참고")]),L,I,Y])]),e("li",null,[e("a",Z,[r("HCP Vault on Azure (Public Beta)"),l(o)]),r(": Azure 유럽 Region 에서 사용 가능")])])]),e("li",null,[r("Consul "),e("ul",null,[e("li",null,[e("a",Q,[r("1.13.3 주요 수정사항"),l(o)]),q]),e("li",null,[e("a",F,[r("1.14 Beta"),l(o)]),r(" (Hashiconf Global Announcement) "),J])])]),e("li",null,[r("Nomad "),e("ul",null,[e("li",null,[e("a",X,[r("1.4 주요 수정사항"),l(o)]),e("ul",null,[e("li",null,[r("상세 내용: "),e("a",$,[r("CHANGELOG"),l(o)]),r(" 참고")]),ee,re,oe,le,ne])])])])])])}const ue=n(c,[["render",te],["__file","2022-11.html.vue"]]),pe=JSON.parse('{"path":"/04-HashiCorp/08-Updates/99-2022/2022-11.html","title":"2022년 11월","lang":"ko-KR","frontmatter":{"description":"2022년 11월 Update","tag":["Hashicorp","Update","Nov"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/99-2022/2022-11.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2022년 11월"}],["meta",{"property":"og:description","content":"2022년 11월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Nov"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2022년 11월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1667293310000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.7,"words":511},"filePathRelative":"04-HashiCorp/08-Updates/99-2022/2022-11.md","localizedDate":"2022년 11월 1일","excerpt":"\\n

    Product 소개

    \\n"}');export{ue as comp,pe as data}; diff --git a/assets/2022-12.html-C2Kpn3zh.js b/assets/2022-12.html-C2Kpn3zh.js new file mode 100644 index 0000000000..e02c5a9193 --- /dev/null +++ b/assets/2022-12.html-C2Kpn3zh.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as i,b as e,d as l,a as t,e as s}from"./app-Bzk8Nrll.js";const u={},c=e("h1",{id:"_2022년-12월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2022년-12월"},[e("span",null,"2022년 12월")])],-1),p=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),h=e("p",null,"Terraform Run Tasks in Public Registry",-1),d={href:"https://www.hashicorp.com/blog/run-tasks-are-now-available-in-the-terraform-registry",target:"_blank",rel:"noopener noreferrer"},_={href:"https://registry.terraform.io/",target:"_blank",rel:"noopener noreferrer"},m=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),g=e("p",null,"Terraform",-1),f={href:"https://github.com/hashicorp/terraform/releases/tag/v1.3.5",target:"_blank",rel:"noopener noreferrer"},b=e("ul",null,[e("li",null,"Size 확인 불가한 Temp File 에 대한 처리 오류 개선"),e("li",null,"Empty 및 Null Collection 에 대한 처리 오류 개선"),e("li",null,"Optional object 에 대핸 처리 오류 개선")],-1),v={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},y={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2022/v202211-1",target:"_blank",rel:"noopener noreferrer"},k=s("
  • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)
  • 2023년 2월 Release (v202302-1) 을 기점으로 구버전 OS 및 Postrgres DB 에 대한 지원 종료
  • Workspaces API 관련 workspace 삭제 기능을 safe / force 로 세분화
  • ",3),T={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.41.0",target:"_blank",rel:"noopener noreferrer"},C=e("ul",null,[e("li",null,"aws_rds_clusters resource 추가"),e("li",null,"aws_nat_gateway 에 대해 private_ip 설정 지원"),e("li",null,"aws_resourcegroups_group 에 대해 configuration.parameters 설정을 optional 로 변경")],-1),P={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.33.0",target:"_blank",rel:"noopener noreferrer"},w=e("ul",null,[e("li",null,"azurerm_mssql_managed_instance_transparent_data_encryption resource 추가"),e("li",null,"azurerm_mssql_managed_instance 에 대해 customer_managed_key_id 와 user-assigned identity 설정 지원"),e("li",null,"azurerm_mysql_flexible_server 에 대해 iops 설정 오류 개선"),e("li",null,"azurerm_storage_account 에 대해 multichannel checking 과정 관련 오류 개선")],-1),R={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.44.1",target:"_blank",rel:"noopener noreferrer"},S=e("ul",null,[e("li",null,"google_compute_instance_template 에 대해 advanced_machine_features 설정으로 인한 오류 개선")],-1),U=e("p",null,"Vault",-1),x={href:"https://github.com/hashicorp/vault/releases/tag/v1.12.1",target:"_blank",rel:"noopener noreferrer"},A=e("ul",null,[e("li",null,"DB Secret Enging - Snowflake 에 대해 병렬 요청 허용"),e("li",null,"SDK/LDAP 에 대해 group filter 사용 시 paging 지원"),e("li",null,"MFA (Multi Factor Authentication) 수행 시 발생 가능한 namespace 관련 오류 개선"),e("li",null,"Vault Agent 가 Certificate Template 렌더링 하는 과정에서 발생 가능한 오류 개선")],-1),E=e("p",null,"Consul",-1),N={href:"https://www.hashicorp.com/blog/consul-1-14-ga-announcing-simplified-service-mesh-deployments",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/hashicorp/consul/blob/main/CHANGELOG.md#1140-november-15-2022",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,[e("p",null,"Consul Client on K8S 에 대한 구조변화: Daemonset 에서 Sidecar 방식으로 변경")],-1),H=e("li",null,[e("p",null,"Cluster Peering 기반 Service Failover 포함 Mesh Traffic Management 기능 개선")],-1),L=e("li",null,[e("p",null,"Windows 에 대한 Service Mesh 지원")],-1),M=e("li",null,[e("p",null,"AWS Lambda 에 대한 Service Mesh 지원")],-1),V=e("p",null,"Nomad",-1),B={href:"https://github.com/hashicorp/nomad/releases/tag/v1.4.3",target:"_blank",rel:"noopener noreferrer"},F=e("ul",null,[e("li",null,"동일 Job 에 대해 여러 evaluation 이 pending 되어 있는 경우, 가장 최근 성공한 evaluation 에 대해서만 반영"),e("li",null,"File Upload, Consul Upstream 설정 등 UI 개선"),e("li",null,"Consul 연동 시 gRPC fingerprint 관련 발생 오류 개선 (Consul 1.14 연동부터 적용)")],-1);function G(O,I){const r=o("ExternalLinkIcon");return a(),i("div",null,[c,p,e("ul",null,[e("li",null,[h,e("ul",null,[e("li",null,[e("a",d,[l("Hashicorp Blog"),t(r)])]),e("li",null,[l("활발하게 활용되고 있는 Terraform Run Tasks (3rd party 연동 및 통합) 기능이 강화되었습니다. 이제 "),e("a",_,[l("Terraform Public Registry"),t(r)]),l(" 에서 Run Tasks 를 검색하여 필요한 3rd party service 와 연동하여 자원 관리에 필요한 Cost Management, Policy Compliance 와 같은 다양한 기능들을 적용하여 보다 효율적인 자원 관리가 가능합니다.")])])])]),m,e("ul",null,[e("li",null,[g,e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",f,[l("1.35 Release"),t(r)]),b])])]),e("li",null,[e("a",v,[l("Enterprise Release"),t(r)]),e("ul",null,[e("li",null,[e("a",y,[l("11월 Release"),t(r)]),l(" 출시 (v202211-1)")]),k])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",T,[l("AWS v4.41.0 주요 수정사항"),t(r)]),C]),e("li",null,[e("a",P,[l("Azure v3.33.0 주요 수정사항"),t(r)]),w]),e("li",null,[e("a",R,[l("GCP v4.41.1 주요 수정사항"),t(r)]),S])])])])]),e("li",null,[U,e("ul",null,[e("li",null,[e("a",x,[l("1.12.1 주요 수정사항"),t(r)]),A])])]),e("li",null,[E,e("ul",null,[e("li",null,[e("p",null,[e("a",N,[l("1.14"),t(r)])]),e("ul",null,[e("li",null,[e("p",null,[l("상세 "),e("a",z,[l("CHANGELOG"),t(r)])])]),D,H,L,M])])])]),e("li",null,[V,e("ul",null,[e("li",null,[e("a",B,[l("1.4.3 주요 수정사항"),t(r)]),F])])])])])}const W=n(u,[["render",G],["__file","2022-12.html.vue"]]),q=JSON.parse('{"path":"/04-HashiCorp/08-Updates/99-2022/2022-12.html","title":"2022년 12월","lang":"ko-KR","frontmatter":{"description":"2022년 12월 Update","tag":["Hashicorp","Update","Dec"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/99-2022/2022-12.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2022년 12월"}],["meta",{"property":"og:description","content":"2022년 12월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Dec"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2022년 12월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1670381763000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.82,"words":247},"filePathRelative":"04-HashiCorp/08-Updates/99-2022/2022-12.md","localizedDate":"2022년 12월 7일","excerpt":"\\n

    Product 소개

    \\n"}');export{W as comp,q as data}; diff --git a/assets/2023-01.html-ggQMyTJT.js b/assets/2023-01.html-ggQMyTJT.js new file mode 100644 index 0000000000..5df425d943 --- /dev/null +++ b/assets/2023-01.html-ggQMyTJT.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as a,c as i,b as e,d as t,a as o,e as s}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-1월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-1월"},[e("span",null,"2023년 1월")])],-1),p=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),_=e("p",null,"Dynamic Secrets for Waypoint with Vault",-1),d={href:"https://www.hashicorp.com/blog/dynamic-secrets-for-waypoint-with-vault",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"Application 에 대한 Build, Deployment 및 Monitoring 을 간소화하고 쉽게 접근할 수 있도록 개발자를 지원하는 Waypoint 에서도 이제 Vault 와 연동하여 Hashicorp Vault config sourcer plugin 을 통해 Application Config 에 포함되는 보안 정보를 관리할 수 있습니다.",-1),m=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),g={href:"https://github.com/hashicorp/terraform/releases/tag/v1.3.",target:"_blank",rel:"noopener noreferrer"},f=e("ul",null,[e("li",null,"Module Output 관련 오류 개선")],-1),b={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},v={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2022/v202212-2",target:"_blank",rel:"noopener noreferrer"},k=s("
  • 필수 Upgrade Version: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)
  • 2023년 2월 Release (v202302-1) 을 기점으로 구버전 OS 및 Postrgres DB 에 대한 지원 종료
  • CA Bundle 관련 이슈로 인해 발생하는 HTTPS endpoint 접속 관련 Sentinel 정책 오류 수정
  • sentinel, cost-estimation, plan-exporter 등을 처리하는 tfe-nomad container 에 대해 성능 및 안정성 제고를 위해 tfe-task-worker 로 대체.
  • ",4),y={href:"https://developer.hashicorp.com/terraform/cloud-docs/workspaces/settings/run-tasks#associating-run-tasks-with-a-workspace",target:"_blank",rel:"noopener noreferrer"},w={href:"https://developer.hashicorp.com/terraform/enterprise/api-docs/workspaces#list-workspaces",target:"_blank",rel:"noopener noreferrer"},C=e("li",null,"terraform apply 진행 도중 취소 시 해당 resource 의 상태 정보에 대해 'unknown' 으로 출력하도록 개선",-1),A={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.48.0",target:"_blank",rel:"noopener noreferrer"},P=e("ul",null,[e("li",null,"aws_eks_node_group 에 대해 WINDOWS_CORE_2019_x86_64, WINDOWS_CORE_2022_x86_64 등 Windows OS 기반 Node 사용 지원"),e("li",null,"aws_networkfirewall_rule_group 에 대해 reference_sets 지원"),e("li",null,"aws_networkmanager_vpc_attachment 에 대해 options.appliance_mode_support 지원")],-1),S={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.37.0",target:"_blank",rel:"noopener noreferrer"},x=e("ul",null,[e("li",null,"신규 resource 추가: azurerm_coginitive_deployment, azurerm_billing_account_cost_management_export, azurerm_key_vault_certificate_contacts, azurerm_lab_service_plan, azurerm_resource_deployment_script, azurerm_spring_cloud_customized_accelerator 등"),e("li",null,"azurerm_netapp_volume 에 대해 zone 설정 추가"),e("li",null,"azurerm_virtual_network_gateway_connection 에 대해 다른 resource group 에서 생성 안되는 오류 개선")],-1),N={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.47.0",target:"_blank",rel:"noopener noreferrer"},H=e("ul",null,[e("li",null,"신규 resource: google_alloydb_backup, google_filestore_backup"),e("li",null,"compute 에 대해 ipv6_access_type 설정 지원"),e("li",null,"container 에 대해 autoscaling 에 대한 default 설정 지원 추가"),e("li",null,"container 에 대해 gke gateway api controller 지원을 위한 gateway_api_config 설정 추가"),e("li",null,'google_sql_database 에 대해 삭제 정책이 기본 "abandon" 처리 되어 있음으로 인해 발생 가능한 오류 개선')],-1),O={href:"https://github.com/hashicorp/vault/blob/main/CHANGELOG.md#1122",target:"_blank",rel:"noopener noreferrer"},E=e("li",null,"PKI Secret Engine: 기본 issuer 에 대한 변경을 위해 default_follows_latest_issuer 설정 추가",-1),G=e("li",null,"Raft Storage: retry_join_as_non_voter 설정 추가",-1),R=e("li",null,"Okta Auth Method 관련 AuthRenew 로 인한 Panic 오류 수정",-1),U=e("li",null,"ciphertext 가 4 byte 이하인 경우 발생 가능한 deadlock 오류 수정",-1),V=e("li",null,"Performance Standby Node 에 대한 start up race condition 수정",-1),z={href:"https://github.com/hashicorp/consul/blob/main/CHANGELOG.md#1143-december-13-2022",target:"_blank",rel:"noopener noreferrer"},L=e("li",null,[e("p",null,"consul connect: vault connect CA test 에 대해 제한된 권한의 token 사용하도록 수정")],-1),T=e("li",null,[e("p",null,"consul connect 관련 Mesh Gateway 의 Failover peering 에 대한 오류 개선")],-1),W=e("li",null,[e("p",null,"consul connect 관련 Vault 1.11+ 를 CA Provider 로 사용 시 발생하는 Intermediate CA 관련 오류 개선")],-1),M=e("li",null,[e("p",null,"Cluster Peering 기반 Service Failover 포함 Mesh Traffic Management 기능 개선")],-1),B=e("li",null,[e("p",null,"Windows 에 대한 Service Mesh 지원")],-1),D=e("li",null,[e("p",null,"AWS Lambda 에 대한 Service Mesh 지원")],-1),I={href:"https://github.com/hashicorp/nomad/blob/main/CHANGELOG.md#143-november-21-2022",target:"_blank",rel:"noopener noreferrer"},j=e("li",null,"동일 Job 에 대해 여러 evaluation 이 pending 되어 있는 경우, 가장 최근 성공한 evaluation 에 대해서만 반영",-1),F=e("li",null,"File Upload, Consul Upstream 설정 등 UI 개선",-1),J=e("li",null,"Consul 연동 시 gRPC fingerprint 관련 발생 오류 개선 (Consul 1.14 연동부터 적용)",-1);function K(Z,q){const l=r("ExternalLinkIcon");return a(),i("div",null,[u,p,e("ul",null,[e("li",null,[_,e("ul",null,[e("li",null,[e("a",d,[t("Hashicorp Blog"),o(l)])]),h])])]),m,e("ul",null,[e("li",null,[t("Terraform "),e("ul",null,[e("li",null,[t("CLI "),e("ul",null,[e("li",null,[e("a",g,[t("1.3.6 Release"),o(l)]),f])])]),e("li",null,[e("a",b,[t("Enterprise Release"),o(l)]),e("ul",null,[e("li",null,[e("a",v,[t("12월 Release"),o(l)]),t(" 출시 (v202212-2)")]),k,e("li",null,[e("a",y,[t("Pre-plan 및 Pre-Apply 를 위한 Run Task"),o(l)]),t(" 지원")]),e("li",null,[e("a",w,[t("List Workspaces API endpoint"),o(l)]),t(" 사용 시 wildcard 매칭 지원")]),C])]),e("li",null,[t("Provider "),e("ul",null,[e("li",null,[e("a",A,[t("AWS v4.48.0 주요 수정사항"),o(l)]),P]),e("li",null,[e("a",S,[t("Azure v3.37.0 주요 수정사항"),o(l)]),x]),e("li",null,[e("a",N,[t("GCP v4.47.0 주요 수정사항"),o(l)]),H])])])])]),e("li",null,[t("Vault "),e("ul",null,[e("li",null,[t("1.12.2 "),e("ul",null,[e("li",null,[t("상세 "),e("a",O,[t("CHANGELOG"),o(l)])]),E,G,R,U,V])])])]),e("li",null,[t("Consul "),e("ul",null,[e("li",null,[t("1.14.3 "),e("ul",null,[e("li",null,[e("p",null,[t("상세 "),e("a",z,[t("CHANGELOG"),o(l)])])]),L,T,W,M,B,D])])])]),e("li",null,[t("Nomad "),e("ul",null,[e("li",null,[t("1.4.3 "),e("ul",null,[e("li",null,[t("상세 "),e("a",I,[t("CHANGELOG"),o(l)])]),j,F,J])])])])])])}const Y=n(c,[["render",K],["__file","2023-01.html.vue"]]),$=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-01.html","title":"2023년 1월","lang":"ko-KR","frontmatter":{"description":"2023년 1월 Update","tag":["Hashicorp","Update","Jan"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-01.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 1월"}],["meta",{"property":"og:description","content":"2023년 1월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Jan"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 1월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1672798178000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.03,"words":308},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-01.md","localizedDate":"2023년 1월 4일","excerpt":"\\n

    Product 소개

    \\n"}');export{Y as comp,$ as data}; diff --git a/assets/2023-02.html-ClDBGKm0.js b/assets/2023-02.html-ClDBGKm0.js new file mode 100644 index 0000000000..967f46f941 --- /dev/null +++ b/assets/2023-02.html-ClDBGKm0.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as s,b as e,d as o,a as t,e as r}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-2월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-2월"},[e("span",null,"2023년 2월")])],-1),d=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p=e("p",null,"Terraform Cloud Adds ‘Projects’ to Organize Workspaces at Scale",-1),_={href:"https://www.hashicorp.com/blog/terraform-cloud-adds-projects-to-organize-workspaces-at-scale",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"기존의 Terraform Cloud 에서는 연관되는 Workspace 간 그룹화가 불가능하고 Organization 및 Workspace 단위로만 권한 할당이 가능함으로 인해 사용자들은 자원 활용에 대한 제약사항을 고려하여 Organization 및 Workspace 를 분할하는 불편함을 감수해야 했습니다. 'Projects' 는 Workspace 를 그룹화하고 Project 단위로 권한 설정을 지원함으로써 앞서 소개한 제약사항을 해소하도록 지원합니다.",-1),m=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),g={href:"https://github.com/hashicorp/terraform/releases/tag/v1.3.7",target:"_blank",rel:"noopener noreferrer"},f=e("ul",null,[e("li",null,"Module 사용 시 Version Constraint 에 대한 Parsing 관련 오류 개선"),e("li",null,"ignore_changes 설정된 resource 가 있을 경우 panic 이 발생하는 오류 개선")],-1),b={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},k={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202301-2",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,"v202301-2",-1),y=r("
  • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2, v202204-2)
  • 2023년 2월 Release (v202302-1) 을 기점으로 구버전 OS 및 Postrgres DB 에 대한 지원 종료
  • Log Level (debug) 로 설정 시 tfe-atlas, tfe-atlas-ui, tfe-sidekiq 등 일부 구성요소로 인한 대량 log 수집되는 이슈 해결
  • Policy Manager 에 대한 Resource 접근 권한 제한, 필요 시 필요한 만큼의 추가 권한 필요
  • Private Module 처럼 Organization 간 Provider 공유 가능
  • State Rollback 기능 지원
  • ",6),P={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.52.0",target:"_blank",rel:"noopener noreferrer"},C=e("ul",null,[e("li",null,[o("신규 resource 추가: "),e("code",null,"aws_auditmanager resource"),o(" 추가")]),e("li",null,[o("Bug Fix "),e("ul",null,[e("li",null,[o("aws_dynamodb_table 에 대해 "),e("code",null,"kms_key_arn"),o(" 관련 오류 개선")]),e("li",null,[o("aws_api_gateway_integration 에 대해 "),e("code",null,"connection type"),o(" 이 변경되는 오류 개선")])])])],-1),x={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.41.0",target:"_blank",rel:"noopener noreferrer"},w=r("",1),z={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.51.0",target:"_blank",rel:"noopener noreferrer"},O=r("",1),A={href:"https://github.com/hashicorp/vault/blob/main/CHANGELOG.md#1122",target:"_blank",rel:"noopener noreferrer"},R=e("li",null,[o("PKI Secret Engine: 기본 issuer 에 대한 변경을 위해 "),e("code",null,"default_follows_latest_issuer"),o(" 설정 추가")],-1),U=e("li",null,[o("Raft Storage: "),e("code",null,"retry_join_as_non_voter"),o("설정 추가")],-1),E=e("li",null,"Okta Auth Method 관련 AuthRenew 로 인한 Panic 오류 수정",-1),G=e("li",null,"ciphertext 가 4 byte 이하인 경우 발생 가능한 deadlock 오류 수정",-1),N=e("li",null,"Performance Standby Node 에 대한 start up race condition 수정",-1),S={href:"https://github.com/hashicorp/consul/blob/main/CHANGELOG.md#1144-january-26-2023",target:"_blank",rel:"noopener noreferrer"},j=r("
  • connect 관련 transparent proxy upstreams 에 필요한 세부 구성 (proxy-defaults, service-default) 수정
  • peering 구성 시 이름은 반드시 소문자로 구성 필요. 향후 호환성 등을 고려하여 기존 구성된 peering 에 대해서 이름에 대문자 사용한 경우에 대해 소문자로 재구성 권고
  • consul connect envoy 명령어 사용 시 envoy-ready-bind-portenvoy-ready-bind-address 설정 지원
  • connect 관련 filter expression 이 처리 되도록 ConsulResolver 지원
  • 인증서 auto-reloading 에 대한 오류 개선
  • ",5),H={href:"https://github.com/hashicorp/nomad/blob/main/CHANGELOG.md#143-november-21-2022",target:"_blank",rel:"noopener noreferrer"},L=e("li",null,"동일 Job 에 대해 여러 evaluation 이 pending 되어 있는 경우, 가장 최근 성공한 evaluation 에 대해서만 반영",-1),T=e("li",null,"File Upload, Consul Upstream 설정 등 UI 개선",-1),W=e("li",null,"Consul 연동 시 gRPC fingerprint 관련 발생 오류 개선 (Consul 1.14 연동부터 적용)",-1);function B(V,F){const l=a("ExternalLinkIcon");return i(),s("div",null,[u,d,e("ul",null,[e("li",null,[p,e("ul",null,[e("li",null,[e("a",_,[o("Hashicorp Blog"),t(l)])]),h])])]),m,e("ul",null,[e("li",null,[o("Terraform "),e("ul",null,[e("li",null,[o("CLI "),e("ul",null,[e("li",null,[e("a",g,[o("1.3.7 Release"),t(l)]),f])])]),e("li",null,[e("a",b,[o("Enterprise Release"),t(l)]),e("ul",null,[e("li",null,[e("a",k,[o("1월 Release"),t(l)]),o(" 출시 ("),v,o(")")]),y])]),e("li",null,[o("Provider "),e("ul",null,[e("li",null,[e("a",P,[o("AWS v4.52.0 주요 수정사항"),t(l)]),C]),e("li",null,[e("a",x,[o("Azure v3.41.0 주요 수정사항"),t(l)]),w]),e("li",null,[e("a",z,[o("GCP v4.51.0 주요 수정사항"),t(l)]),O])])])])]),e("li",null,[o("Vault "),e("ul",null,[e("li",null,[o("1.12.2 "),e("ul",null,[e("li",null,[o("상세 "),e("a",A,[o("CHANGELOG"),t(l)])]),R,U,E,G,N])])])]),e("li",null,[o("Consul "),e("ul",null,[e("li",null,[o("1.14.4 "),e("ul",null,[e("li",null,[o("상세 "),e("a",S,[o("CHANGELOG"),t(l)])]),j])])])]),e("li",null,[o("Nomad "),e("ul",null,[e("li",null,[o("1.4.3 "),e("ul",null,[e("li",null,[o("상세 "),e("a",H,[o("CHANGELOG"),t(l)])]),L,T,W])])])])])])}const D=n(c,[["render",B],["__file","2023-02.html.vue"]]),K=JSON.parse(`{"path":"/04-HashiCorp/08-Updates/98-2023/2023-02.html","title":"2023년 2월","lang":"ko-KR","frontmatter":{"description":"2023년 2월 Update","tag":["Hashicorp","Update","Feb"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-02.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 2월"}],["meta",{"property":"og:description","content":"2023년 2월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Feb"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 2월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1675423442000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.95,"words":285},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-02.md","localizedDate":"2023년 2월 3일","excerpt":"\\n

    Product 소개

    \\n"}`);export{D as comp,K as data}; diff --git a/assets/2023-03.html-C303hGPW.js b/assets/2023-03.html-C303hGPW.js new file mode 100644 index 0000000000..f89a94ebad --- /dev/null +++ b/assets/2023-03.html-C303hGPW.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as s,c as i,b as e,d as o,a as l,e as t}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-3월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-3월"},[e("span",null,"2023년 3월")])],-1),d=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p=e("p",null,"Writing Terraform for unsupported resources",-1),h={href:"https://www.hashicorp.com/blog/writing-terraform-for-unsupported-resources",target:"_blank",rel:"noopener noreferrer"},_=e("li",null,"Terraform 과 대상 환경 간 연동을 위해 Provider 를 활용할 때 대상 환경이 API 을 통해서는 지원하는 기능이지만 Provider 에는 구현되어 있지 않아 사용할 수 없는 기능이 종종 있습니다 (예: Vault Provider 기반 Vault 운영 시 Unseal 기능 사용 불가). Terracurl 을 통해 API 을 통해서만 지원되는 기능들을 Terraform Code 로 구성하여 하나의 Resource 로 관리할 수 있습니다.",-1),m=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),g={href:"https://github.com/hashicorp/terraform/releases/tag/v1.3.9",target:"_blank",rel:"noopener noreferrer"},f=e("ul",null,[e("li",null,"이미 폐기된 Resource 에 대해 제거 및 plan 할 때 발생하는 이슈 해결")],-1),v={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},b={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202302-1",target:"_blank",rel:"noopener noreferrer"},k=e("code",null,"v202302-1 (681)",-1),w=t("
  • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
  • 구버전 OS 및 Postrgres DB 에 대한 지원 종료
  • Known Issue 1: 관리자 설정에서 TFC Agent 에 대해 사용 여부를 선택하지 않은 상태에서 agent run pipeline mode 사용 시 실행 계획이 무기한 Queue 에 대기하는 문제 발생. 관련하여 tfe-task-worker 를 통해 [ERROR] core: Unexpected HTTP response code: method=POST url=https://terraform.example.com/api/agent/register status=404 라는 Error Log 출력되며 2023년 3월 Release (v202303-1) 에서 해결 예정
  • Known Issue 2: API 를 통해 team 에게 manage-workspaces 권한 부여 시, read-workspaces 도 부여되는 문제 발생. 더불어 manage-workspaces 에 대한 권한 제거 시 read-workspaces 권한은 제거 되지 않고 부여된 채로 유지 되는 문제 발생. 이후 출시 예정인 Release 에서 해결 예정
  • Breaking Changes: 여러 Workspace 를 그룹으로 엮는 Project 기능이 추가됨에 따라
  • ",5),y={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.57.1",target:"_blank",rel:"noopener noreferrer"},P=e("ul",null,[e("li",null,[o("Bug Fix "),e("ul",null,[e("li",null,[o("resource/aws_lambda_function 에 대해 "),e("code",null,"skip_destroy"),o(" 를 Null 값 처리 시 발생하는 "),e("code",null,"Provider produced inconsitent final plan"),o(" 오류 개선")])])])],-1),x={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.46.0",target:"_blank",rel:"noopener noreferrer"},T=e("ul",null,[e("li",null,[o("신규 resource 추가: "),e("code",null,"azurerm_mobile_network"),o(" 및 "),e("code",null,"azurerm_sentinel_alert_rule")]),e("li",null,[o("기능 개선 "),e("ul",null,[e("li",null,"resource 에 대해 AuthV2(EasyAuthV2) 지원")])]),e("li",null,[o("Bug Fix "),e("ul",null,[e("li",null,"azurerm_storage_object_replication 에 대해 cross tenant replication 이 비활성화 되어 있는 경우 동작하지 않는 오류 개선")])])],-1),R={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.56.0",target:"_blank",rel:"noopener noreferrer"},C=t("",1),V={href:"https://www.hashicorp.com/blog/vault-1-13-adds-kubernetes-operator-mfa-improvements-and-more",target:"_blank",rel:"noopener noreferrer"},A={href:"https://github.com/hashicorp/vault/releases/tag/v1.13.0",target:"_blank",rel:"noopener noreferrer"},I=e("li",null,"Multi-namespace access: namespace 간 권한 부여를 통한 secret 공유 기능 추가",-1),U=e("li",null,"Multi Vault Agent: 여러 config 파일 연동하여 다양한 namespace 및 path 에 agent 접근 기능 추가",-1),B=e("li",null,"Certificate revocation for cross-cluster management: PR 구성과 같은 Multi Cluster 구조에서 Cluster 간 PKI 인증서 만료 처리에 대한 동기화 지원",-1),E=e("li",null,"Managed transit keys: Transit 암호화 처리 시 사용자 key 사용 지원",-1),S={href:"https://www.hashicorp.com/blog/consul-1-15-adds-envoy-extensions-and-enhances-access-logging",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/hashicorp/consul/releases/tag/v1.15.0",target:"_blank",rel:"noopener noreferrer"},N=e("li",null,"Envoy access logging: service mesh 구성 시 config entries 와 CRD 를 기반한 손쉬운 envoy log 활성화 지원",-1),L=e("li",null,[o("Consul Envoy extensions: "),e("code",null,"EnvoyExtensions"),o(" 설정을 통해 lua 및 lambda 등에 대한 service mesh 연동 지원")],-1),M=e("li",null,[o("Service-to-service troubleshooting: "),e("code",null,"consul troubleshoot"),o(" 명령어 지원을 통해 서비스 간 통신 오류에 대한 진단 지원")],-1),O=e("li",null,"Linux VM runtime support in Consul-native API Gateway (beta): Linux VM 환경 기반 Application 의 service mesh 구성에 필요한 API Gateway 지원",-1),j={href:"https://www.hashicorp.com/blog/nomad-1-5-adds-single-sign-on-and-dynamic-node-metadata",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/hashicorp/nomad/releases/tag/v1.5.0",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,"Single sign-on and OIDC support: OIDC 기반 SSO 지원 및 Login 기능 추가",-1),F=e("li",null,"Dynamic node metadata: client node 에 대해 constraints, affinity, spread 등 job scheduling 결정에 영향을 주는 meta 정보 동적 설정 지원",-1),K=e("li",null,"Job Templates: Job 명세 작성 지원을 위한 Template 기능 지원",-1);function G(W,J){const r=a("ExternalLinkIcon");return s(),i("div",null,[u,d,e("ul",null,[e("li",null,[p,e("ul",null,[e("li",null,[e("a",h,[o("Hashicorp Blog"),l(r)])]),_])])]),m,e("ul",null,[e("li",null,[o("Terraform "),e("ul",null,[e("li",null,[o("CLI "),e("ul",null,[e("li",null,[e("a",g,[o("1.3.9 Release"),l(r)]),f])])]),e("li",null,[e("a",v,[o("Enterprise Release"),l(r)]),e("ul",null,[e("li",null,[e("a",b,[o("2월 Release"),l(r)]),o(" 출시 ("),k,o(")")]),w])]),e("li",null,[o("Provider "),e("ul",null,[e("li",null,[e("a",y,[o("AWS v4.57.1 주요 수정사항"),l(r)]),P]),e("li",null,[e("a",x,[o("Azure v3.46.0 주요 수정사항"),l(r)]),T]),e("li",null,[e("a",R,[o("GCP v4.56.0 주요 수정사항"),l(r)]),C])])])])]),e("li",null,[o("Vault "),e("ul",null,[e("li",null,[o("1.13.0 "),e("ul",null,[e("li",null,[e("a",V,[o("Hashicorp Blog"),l(r)])]),e("li",null,[o("상세 "),e("a",A,[o("Release Note"),l(r)])]),I,U,B,E])])])]),e("li",null,[o("Consul "),e("ul",null,[e("li",null,[o("1.15.0 "),e("ul",null,[e("li",null,[e("a",S,[o("Hashicorp Blog"),l(r)])]),e("li",null,[o("상세 "),e("a",H,[o("Release Note"),l(r)])]),N,L,M,O])])])]),e("li",null,[o("Nomad "),e("ul",null,[e("li",null,[o("1.5.0 "),e("ul",null,[e("li",null,[e("a",j,[o("Hashicorp Blog"),l(r)])]),e("li",null,[o("상세 "),e("a",z,[o("Release Note"),l(r)])]),D,F,K])])])])])])}const q=n(c,[["render",G],["__file","2023-03.html.vue"]]),X=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-03.html","title":"2023년 3월","lang":"ko-KR","frontmatter":{"description":"2023년 3월 Update","tag":["Hashicorp","Update","Mar"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-03.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 3월"}],["meta",{"property":"og:description","content":"2023년 3월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Mar"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 3월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1678198450000,"updatedTime":1695042774000,"contributors":[{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.23,"words":370},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-03.md","localizedDate":"2023년 3월 7일","excerpt":"\\n

    Product 소개

    \\n"}');export{q as comp,X as data}; diff --git a/assets/2023-04.html-sQgt4Mf_.js b/assets/2023-04.html-sQgt4Mf_.js new file mode 100644 index 0000000000..0e36fec2b8 --- /dev/null +++ b/assets/2023-04.html-sQgt4Mf_.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as n,c,b as e,d as o,a as t,e as l}from"./app-Bzk8Nrll.js";const d={},s=e("h1",{id:"_2023년-4월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-4월"},[e("span",null,"2023년 4월")])],-1),u=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),_=e("p",null,"Dynamic provider credentials now generally available for Terraform Cloud",-1),p={href:"https://www.hashicorp.com/blog/dynamic-provider-credentials-now-ga-for-terraform-cloud",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,[o("AWS, MS Azure, GCP 등 Cloud 환경을 Terraform 과 연동 및 인증하기 위해 Workspace Variable 또는 Variable Set 을 활용하여 Credential 정보를 설정해서 사용했습니다. 해당 Credential 정보는 장기간의 TTL 을 설정하여 사용하거나 혹은 보안취약점을 보완하기 위해 관리자가 수동으로 갱신 및 설정하는 등의 번거로움을 동반하고 있었습니다. Dynamic Provider Credential 은 각 Cloud Service 의 OIDC Provider 를 기반으로 Terraform 에 대한 TLS 인증 확인을 수행함으로써 매 Apply 마다 Terraform 에 대한 인증 처리 후 Resource Provisioning 등을 수행하는 "),e("code",null,"동적인증처리"),o(" 를 지원합니다.")],-1),m={href:"https://developer.hashicorp.com/terraform/tutorials/cloud/dynamic-credentials?product_intent=terraform",target:"_blank",rel:"noopener noreferrer"},g=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),f={href:"https://github.com/hashicorp/terraform/releases/tag/v1.4.2",target:"_blank",rel:"noopener noreferrer"},v=e("ul",null,[e("li",null,[o("일부 Provider 에서 발생하는 "),e("code",null,"invalid plan"),o(" 오류 개선")])],-1),b={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},y={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202303-1",target:"_blank",rel:"noopener noreferrer"},k=e("code",null,"v202303-1 (688)",-1),w=e("li",null,"필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)",-1),T=e("li",null,"OPA Support: OPA (Open Policy Agent) 기반 정책 설정 지원",-1),C=e("li",null,"Dynamic Provider Credential: Terraform 과 Provider 간 동적 인증처리 지원 (지원대상 Provider: AWS, AzureRM, AzureAD, GCP, Vault)",-1),P=e("li",null,"Drift Detection: State 파일 내 상태정보와 실제 대상환경 간 상이한 부분 체크 및 알림 설정, Workspace 또는 Organization 단위로 활성화",-1),A={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.60.0",target:"_blank",rel:"noopener noreferrer"},S=l("",1),z={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.49.0",target:"_blank",rel:"noopener noreferrer"},x=l("",1),R={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.59.0",target:"_blank",rel:"noopener noreferrer"},U=l("",1),V={href:"https://github.com/hashicorp/vault/releases/tag/v1.13.1",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,[o("Github Auth Method 에 대해 환경변수 "),e("code",null,"VAULT_AUTH_CONFIG_GITHUB_TOKEN"),o(" 설정 지원")],-1),N=e("li",null,"DB Secrets Engine (Elasticsearch) 에 대해 API 오류 시 알림 메세지 강화",-1),H=e("li",null,[e("code",null,"CKR_FUNCTION_FAILED"),o(" 오류 발생 시 PKCS#11 HSM 에 대한 재연결 시도 개선")],-1),O=e("li",null,"UI 개선",-1),I={href:"https://github.com/hashicorp/consul/releases/tag/v1.15.1",target:"_blank",rel:"noopener noreferrer"},L=l("
  • consul token update 명령어 수행 시 -append-policy-id, -append-policy-name, -append-role-id, -append-service-identity, -append-node-identity 매개변수 설정 지원
  • 호환 Envoy Version: 1.22.5 ~ 1.22.7, 1.23.2 ~ 1.23.4, 1.24.0 ~ 1.24.2 지원 (1.21.5 제외)
  • Service Mesh 에 사용되는 Gateway 에서 발생하는 HTTPRoute 관련 오류 개선
  • 존재하지 않는 ACL 정책 호출 시 발생하는 오류 개선
  • ",4),B={href:"https://github.com/hashicorp/nomad/releases/tag/v1.5.2",target:"_blank",rel:"noopener noreferrer"},E=l("
  • namespace status, quota status, server members 명령어에 대해 -json-t 매개변수 설정 지원
  • 등록된 서비스 제거 시 두번 제거 되는 오류 개선
  • Consul Discovery 사용하는 서비스에 대해 발생하는 Cluster Join 권한 오류 개선
  • ",3);function G(M,W){const r=a("ExternalLinkIcon");return n(),c("div",null,[s,u,e("ul",null,[e("li",null,[_,e("ul",null,[e("li",null,[e("a",p,[o("Hashicorp Blog"),t(r)])]),h,e("li",null,[e("a",m,[o("Hashicorp Officlal Tutorial"),t(r)]),o(" 을 참고하여 테스트 해보실 수 있습니다.")])])])]),g,e("ul",null,[e("li",null,[o("Terraform "),e("ul",null,[e("li",null,[o("CLI "),e("ul",null,[e("li",null,[e("a",f,[o("1.4.2 Release"),t(r)]),v])])]),e("li",null,[e("a",b,[o("Enterprise Release"),t(r)]),e("ul",null,[e("li",null,[e("a",y,[o("3월 Release"),t(r)]),o(" 출시 ("),k,o(")")]),w,T,C,P])]),e("li",null,[o("Provider "),e("ul",null,[e("li",null,[e("a",A,[o("AWS v4.60.0 주요 수정사항"),t(r)]),S]),e("li",null,[e("a",z,[o("Azure v3.49.0 주요 수정사항"),t(r)]),x]),e("li",null,[e("a",R,[o("GCP v4.59.0 주요 수정사항"),t(r)]),U])])])])]),e("li",null,[o("Vault "),e("ul",null,[e("li",null,[o("1.13.1 "),e("ul",null,[e("li",null,[o("상세 "),e("a",V,[o("Release Note"),t(r)])]),D,N,H,O])])])]),e("li",null,[o("Consul "),e("ul",null,[e("li",null,[o("1.15.1 "),e("ul",null,[e("li",null,[o("상세 "),e("a",I,[o("Release Note"),t(r)])]),L])])])]),e("li",null,[o("Nomad "),e("ul",null,[e("li",null,[o("1.5.2 "),e("ul",null,[e("li",null,[o("상세 "),e("a",B,[o("Release Note"),t(r)])]),E])])])])])])}const j=i(d,[["render",G],["__file","2023-04.html.vue"]]),q=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-04.html","title":"2023년 4월","lang":"ko-KR","frontmatter":{"description":"2023년 4월 Update","tag":["Hashicorp","Update","Apr"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-04.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 4월"}],["meta",{"property":"og:description","content":"2023년 4월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Apr"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 4월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1680766775000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.96,"words":287},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-04.md","localizedDate":"2023년 4월 6일","excerpt":"\\n

    Product 소개

    \\n"}');export{j as comp,q as data}; diff --git a/assets/2023-05.html-iXkPu4qP.js b/assets/2023-05.html-iXkPu4qP.js new file mode 100644 index 0000000000..f95dfb9d37 --- /dev/null +++ b/assets/2023-05.html-iXkPu4qP.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as s,b as e,d as t,a as l,e as r}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-5월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-5월"},[e("span",null,"2023년 5월")])],-1),_=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),d=e("p",null,"Vault Secrets Operator: A new method for Kubernetes integration",-1),p={href:"https://www.hashicorp.com/blog/vault-secrets-operator-a-new-method-for-kubernetes-integration",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"K8S Cluster 의 Secret 을 CRD (custom resource definitions) 를 기반으로 Vault 와 연동함으로써 K8S 를 이용하는 개발자 및 다양한 사용자들은 새로운 Tool 을 배울 필요 없이 기존의 경험을 바탕으로 K8S Secret 을 생명주기가 더해진 동적인 데이터로써 사용 가능합니다 .",-1),m={href:"https://www.hashicorp.com/blog/kubernetes-vault-integration-via-sidecar-agent-injector-vs-csi-provider",target:"_blank",rel:"noopener noreferrer"},g={href:"https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator",target:"_blank",rel:"noopener noreferrer"},f=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),v=e("p",null,"CLI",-1),b={href:"https://github.com/hashicorp/terraform/releases/tag/v1.4.5",target:"_blank",rel:"noopener noreferrer"},k=e("ul",null,[e("li",null,[t("terraform destroy 시 "),e("code",null,"create_before_destroy"),t(" 참조 오류 등 개선")])],-1),S={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},w={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202304-1",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"v202304-1 (692)",-1),I=e("li",null,"필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)",-1),P=e("li",null,"Project List API 사용 시 Project 이름 기반 필터 처리 지원",-1),T=e("li",null,"신규 생성된 User, Team, Org 정보에 대한 강조 표시 등 UI 개선",-1),C=e("li",null,"Sentinel 0.21 Upgrade",-1),A=e("p",null,"Provider",-1),R={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v4.64.0",target:"_blank",rel:"noopener noreferrer"},U=r("",1),x={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.53.0",target:"_blank",rel:"noopener noreferrer"},V=r("",1),H={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.63.0",target:"_blank",rel:"noopener noreferrer"},N=r("",1),j={href:"https://github.com/hashicorp/vault/releases/tag/v1.13.1",target:"_blank",rel:"noopener noreferrer"},K=e("li",null,[t("Github Auth Method 에 대해 환경변수 "),e("code",null,"VAULT_AUTH_CONFIG_GITHUB_TOKEN"),t(" 설정 지원")],-1),B=e("li",null,"DB Secrets Engine (Elasticsearch) 에 대해 API 오류 시 알림 메세지 강화",-1),O=e("li",null,[e("code",null,"CKR_FUNCTION_FAILED"),t(" 오류 발생 시 PKCS#11 HSM 에 대한 재연결 시도 개선")],-1),E=e("li",null,"UI 개선",-1),z={href:"https://github.com/hashicorp/consul/releases/tag/v1.15.2",target:"_blank",rel:"noopener noreferrer"},L=e("li",null,"connect proxy 설정 기반 service mesh 관련 telemetry 를 HCP metrics collection service 로 전달하는 기능 추가",-1),D=e("li",null,"Vault CA Provider 와 연동 시 발생하는 Service Mesh TLS 설정 관련 오류 개선",-1),G=e("li",null,[t("Gateway 에 대해 Namespace 및 Partition 관련 "),e("code",null,"unmarshal"),t(" 되는 오류 개선")],-1),M=e("li",null,"Default Namespace 또는 Partition 을 사용하지 않는 서비스에 대한 Resolver, Router, Splitter 사용 시 Peering 이 올바르게 수행되지 않는 오류 개선",-1),F=e("li",null,[t("Audit Log 이용 시 "),e("code",null,"Streaming not supported"),t(" 발생하는 오류 개선")],-1),q={href:"https://github.com/hashicorp/nomad/releases/tag/v1.5.3",target:"_blank",rel:"noopener noreferrer"},Z=e("li",null,"인증되지 않은 HTTP API 호출이 ACL 검사를 우회 통과하는 오류 개선",-1);function J(W,Q){const o=n("ExternalLinkIcon");return a(),s("div",null,[u,_,e("ul",null,[e("li",null,[d,e("ul",null,[e("li",null,[e("a",p,[t("Hashicorp Blog"),l(o)])]),h,e("li",null,[t("기존에 제공되고 있던 Sidecar Injector 및 CSI Provider 방식과의 비교 분석은 "),e("a",m,[t("Hashicorp Blog: Kubernetes Vault Integration via Sidecar Agent Injector vs. Vault Secrets Operator vs. CSI Provider"),l(o)]),t(" 를 참고하세요.")]),e("li",null,[e("a",g,[t("Hashicorp Officlal Tutorial"),l(o)]),t(" 을 참고하여 테스트 해보실 수 있습니다.")])])])]),f,e("ul",null,[e("li",null,[t("Terraform "),e("ul",null,[e("li",null,[v,e("ul",null,[e("li",null,[e("a",b,[t("1.4.5 Release"),l(o)]),k])])]),e("li",null,[e("p",null,[e("a",S,[t("Enterprise Release"),l(o)])]),e("ul",null,[e("li",null,[e("a",w,[t("4월 Release"),l(o)]),t(" 출시 ("),y,t(")")]),I,P,T,C])]),e("li",null,[A,e("ul",null,[e("li",null,[e("p",null,[e("a",R,[t("AWS v4.64.0 주요 수정사항"),l(o)])]),U]),e("li",null,[e("p",null,[e("a",x,[t("Azure v3.53.0 주요 수정사항"),l(o)])]),V]),e("li",null,[e("p",null,[e("a",H,[t("GCP v4.63.0 주요 수정사항"),l(o)])]),N])])])])]),e("li",null,[t("Vault "),e("ul",null,[e("li",null,[t("1.13.1 "),e("ul",null,[e("li",null,[t("상세 "),e("a",j,[t("Release Note"),l(o)])]),K,B,O,E])])])]),e("li",null,[t("Consul "),e("ul",null,[e("li",null,[t("1.15.2 "),e("ul",null,[e("li",null,[t("상세 "),e("a",z,[t("Release Note"),l(o)])]),L,D,G,M,F])])])]),e("li",null,[t("Nomad "),e("ul",null,[e("li",null,[t("1.5.3 "),e("ul",null,[e("li",null,[t("상세 "),e("a",q,[t("Release Note"),l(o)])]),Z])])])])])])}const $=i(c,[["render",J],["__file","2023-05.html.vue"]]),ee=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-05.html","title":"2023년 5월","lang":"ko-KR","frontmatter":{"description":"2023년 5월 Update","tag":["Hashicorp","Update","May"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-05.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 5월"}],["meta",{"property":"og:description","content":"2023년 5월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"May"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 5월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1682489034000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.91,"words":272},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-05.md","localizedDate":"2023년 4월 26일","excerpt":"\\n

    Product 소개

    \\n"}');export{$ as comp,ee as data}; diff --git a/assets/2023-06.html-BjObLeBp.js b/assets/2023-06.html-BjObLeBp.js new file mode 100644 index 0000000000..3f29773418 --- /dev/null +++ b/assets/2023-06.html-BjObLeBp.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as a,c as s,b as e,d as l,a as o,e as t}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-6월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-6월"},[e("span",null,"2023년 6월")])],-1),d=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),_=e("p",null,"Terraform Cloud updates plans with an enhanced Free tier and more flexibility",-1),p={href:"https://www.hashicorp.com/blog/terraform-cloud-updates-plans-with-an-enhanced-free-tier-and-more-flexibility",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"Terraform Cloud 에 대한 요금제가 개편됩니다. 요금제 개편과 함께 각 요금제에서 사용가능한 기능들도 추가되었습니다. (예를 들어 기존 Free Tier 에서는 사용불가 했던 Sentinel/OPA, SSO, Terraform Agent, Run Tasks 등)",-1),m=e("p",null,"Terraform Cloud adds Vault-backed dynamic credentials",-1),g={href:"https://www.hashicorp.com/blog/terraform-cloud-adds-vault-backed-dynamic-credentials",target:"_blank",rel:"noopener noreferrer"},f=e("li",null,"지난 4월 소개된 Dynamic provider credentials now generally available for Terraform Cloud 에 이어 Vault의 Cloud Secrets Engine (AWS, Azure, GCP) 를 연계한 동적 자격증명 발급 기능이 출시되었습니다. 이제, Terraform Apply 수행 시 마다 Vault 로 부터 자격증명을 발급받아 사용함으로써 보다 보안 강화된 워크플로우를 구성할 수 있습니다.",-1),b=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),v={href:"https://github.com/hashicorp/terraform/releases/tag/v1.4.6",target:"_blank",rel:"noopener noreferrer"},k=e("ul",null,[e("li",null,[e("code",null,"terraform plan"),l(" 시 null string 또는 잘못 정의된 map 으로 인한 오류 개선")]),e("li",null,[e("code",null,"terraform plan"),l(" 시 구버전 TFE 에서 plan 두번 되는 이슈 개선")]),e("li",null,[e("code",null,"forces replacement"),l(" 설정된 자원 관련 오류 개선")])],-1),y={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},C={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202305-1",target:"_blank",rel:"noopener noreferrer"},T=e("code",null,"v202305-1 (703)",-1),w=e("li",null,"필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)",-1),A=e("li",null,"Terraform run 시 구동되는 Build Worker 관련 지원 종료 (v202306-1 부터 적용, Terraform run 시 TFC Agent 로 대체)",-1),x=e("li",null,"Project 단위 Variable Set 사용 가능",-1),E=e("li",null,[l("Vault 와 연계한 Dynamic Credentials 사용 시 base64 encoded PEM format Cert 지정시 "),e("code",null,"TFC_VAULT_ENCODED_CACERT"),l(" 환경변수 사용 지원")],-1),P=e("li",null,"Authentication Token 발행 시 폐기 시점 (Expire date) 설정지원",-1),S=e("li",null,"OPA Version 자동 업그레이드 지원",-1),V={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.0.0",target:"_blank",rel:"noopener noreferrer"},z={href:"https://www.hashicorp.com/blog/terraform-aws-provider-5-0-adds-updates-to-default-tags",target:"_blank",rel:"noopener noreferrer"},H=t("
  • Provider 단위별 생성/관리 되는 자원에 대한 Default Tag 설정 지원 및 기존 발생하던 Tag 관련 이슈에 대한 개선
  • EC2 Classic 관련 기능 지원 종료
  • 기존에 사용자가 고지 받던 Warning 알림 (지원 종료된 자원 사용 등) 에 대한 개선
  • 신규 자원 관련 지원 추가
  • ",4),R={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.58.0",target:"_blank",rel:"noopener noreferrer"},B=t("",1),U={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.67.0",target:"_blank",rel:"noopener noreferrer"},N=t("",1),O={href:"https://github.com/hashicorp/vault/releases/tag/v1.13.2",target:"_blank",rel:"noopener noreferrer"},j=e("li",null,"namespace 에 대한 output 정보 상세화",-1),F=e("li",null,"pki secret engine 에 대해 OCSP 에 대한 사이즈 조정 및 호환성 제고",-1),D=e("li",null,"ldap auth method 에 대해 max_page_size 설정 지원",-1),G=e("li",null,[l("신규 생성된 Token 이 인지되지 않음으로 인해 간헐적으로 발생되는 "),e("code",null,"error performing token check: no lease entry found for token that ought to have one, possible eventual consistency issue"),l(" 오류 개선")],-1),L=e("li",null,"Perf Standby Cluster 에 대해 Leader 변경, Unseal 등의 작업 이후 발생하는 412 오류 개선",-1),W=e("li",null,"PR Cluster 에 filter 된 정보 replication 작업 시 발생하는 caching 오류 개선",-1),J=e("li",null,"UI 개선",-1),M={href:"https://github.com/hashicorp/consul/releases/tag/v1.15.3",target:"_blank",rel:"noopener noreferrer"},I={href:"https://github.com/advisories/GHSA-9f7g-gqwh-jpf5",target:"_blank",rel:"noopener noreferrer"},q=e("code",null,"go/scanner",-1),Z={href:"https://github.com/advisories/GHSA-v4m2-x4rp-hv22",target:"_blank",rel:"noopener noreferrer"},K=e("code",null,"html/template",-1),Q={href:"https://github.com/advisories/GHSA-8v5j-pwr7-w5f8",target:"_blank",rel:"noopener noreferrer"},X=e("code",null,"net/textproto",-1),Y={href:"https://github.com/advisories/GHSA-9f7g-gqwh-jpf5",target:"_blank",rel:"noopener noreferrer"},$=e("code",null,"mime/multipart",-1),ee=t("
  • HCP Consul 에 대해 OTEL 형태의 Metric 수집 지원
  • Raft Storage 관련 성능 개선
  • partition/region 간 peering 관련 기능 개선
  • Envoy Version 지원추가: 1.22.11, 1.23.8, 1.24.6, 1.25.4
  • Envoy Proxy 관련 Passive Health Check 에 대해 MaxEjectionPercentBaseEjectionTime 설정 지원
  • ",5),le={href:"https://github.com/hashicorp/nomad/releases/tag/v1.5.6",target:"_blank",rel:"noopener noreferrer"},re=e("li",null,"Job allocation 이 재시작 되었을 때 발생하는 group service 가 등록 해제 되는 오류 개선",-1),oe=e("li",null,"종료되었음에도 삭제 되지 않은 Job allocation 에 대한 오류 개선",-1),te=e("li",null,"Job Evaluation 이 잘못된 type 으로 생성되는 오류 개선",-1);function ne(ie,ae){const r=i("ExternalLinkIcon");return a(),s("div",null,[u,d,e("ul",null,[e("li",null,[_,e("ul",null,[e("li",null,[e("a",p,[l("Hashicorp Blog"),o(r)])]),h])]),e("li",null,[m,e("ul",null,[e("li",null,[e("a",g,[l("Hashicorp Blog"),o(r)])]),f])])]),b,e("ul",null,[e("li",null,[l("Terraform "),e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",v,[l("1.4.6 Release"),o(r)]),k])])]),e("li",null,[e("a",y,[l("Enterprise Release"),o(r)]),e("ul",null,[e("li",null,[e("a",C,[l("5월 Release"),o(r)]),l(" 출시 ("),T,l(")")]),w,A,x,E,P,S])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",V,[l("AWS v5.0 주요 수정사항"),o(r)]),e("ul",null,[e("li",null,[l("신버전 출시 관련 "),e("a",z,[l("Hashicorp Blog"),o(r)])]),H])]),e("li",null,[e("a",R,[l("Azure v3.58.0 주요 수정사항"),o(r)]),B]),e("li",null,[e("a",U,[l("GCP v4.67.0 주요 수정사항"),o(r)]),N])])])])]),e("li",null,[l("Vault "),e("ul",null,[e("li",null,[l("1.13.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",O,[l("Release Note"),o(r)])]),j,F,D,G,L,W,J])])])]),e("li",null,[l("Consul "),e("ul",null,[e("li",null,[l("1.15.3 "),e("ul",null,[e("li",null,[l("상세 "),e("a",M,[l("Release Note"),o(r)])]),e("li",null,[l("보안 취약점 관련 패치: "),e("a",I,[l("CVE-2023-24537 ("),q,l(")"),o(r)]),l(", "),e("a",Z,[l("CVE-2023-24538("),K,l(")"),o(r)]),l(", "),e("a",Q,[l("CVE-2023-24534("),X,l(")"),o(r)]),l(", "),e("a",Y,[l("CVE-2023-24536("),$,l(")"),o(r)])]),ee])])])]),e("li",null,[l("Nomad "),e("ul",null,[e("li",null,[l("1.5.6 "),e("ul",null,[e("li",null,[l("상세 "),e("a",le,[l("Release Note"),o(r)])]),re,oe,te])])])])])])}const ue=n(c,[["render",ne],["__file","2023-06.html.vue"]]),de=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-06.html","title":"2023년 6월","lang":"ko-KR","frontmatter":{"description":"2023년 6월 Update","tag":["Hashicorp","Update","Jun"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-06.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 6월"}],["meta",{"property":"og:description","content":"2023년 6월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-05T06:52:52.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Jun"}],["meta",{"property":"article:modified_time","content":"2023-10-05T06:52:52.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 6월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-05T06:52:52.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1686116077000,"updatedTime":1696488772000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.29,"words":388},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-06.md","localizedDate":"2023년 6월 7일","excerpt":"\\n

    Product 소개

    \\n"}');export{ue as comp,de as data}; diff --git a/assets/2023-07.html-CSUKPa-P.js b/assets/2023-07.html-CSUKPa-P.js new file mode 100644 index 0000000000..2a3118a85b --- /dev/null +++ b/assets/2023-07.html-CSUKPa-P.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as s,b as e,d as r,a as t,e as l}from"./app-Bzk8Nrll.js";const c={},d=e("h1",{id:"_2023년-7월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-7월"},[e("span",null,"2023년 7월")])],-1),u=e("h2",{id:"product-소개-hashidays-2023",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개-hashidays-2023"},[e("span",null,"Product 소개 (Hashidays 2023)")])],-1),h={href:"https://www.hashicorp.com/blog/a-hashicorp-ambassador-at-hashidays-london",target:"_blank",rel:"noopener noreferrer"},p={href:"https://www.hashicorp.com/blog/announcing-hcp-vault-secrets-public-beta",target:"_blank",rel:"noopener noreferrer"},_={href:"https://www.hashicorp.com/blog/vault-secrets-operator-for-kubernetes-now-ga",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.hashicorp.com/blog/boundary-0-13-introduces-ssh-session-recording-boundary-enterprise-and-more",target:"_blank",rel:"noopener noreferrer"},g={href:"https://www.hashicorp.com/blog/new-terraform-cloud-capabilities-to-import-view-and-manage-infrastructure",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.hashicorp.com/blog/terraform-cloud-adds-vault-backed-dynamic-credentials",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.hashicorp.com/blog/terraform-1-5-brings-config-driven-import-and-checks",target:"_blank",rel:"noopener noreferrer"},w={href:"https://www.hashicorp.com/blog/consul-1-16-enhances-service-mesh-reliability-user-experience-and-security",target:"_blank",rel:"noopener noreferrer"},v=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),y={href:"https://github.com/hashicorp/terraform/releases/tag/v1.5.0",target:"_blank",rel:"noopener noreferrer"},k={href:"https://developer.hashicorp.com/terraform/language/checks",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"check",-1),x=e("code",null,"assert",-1),C=e("code",null,"assert",-1),E=e("code",null,"condition",-1),P=e("code",null,"error_message",-1),V=e("code",null,"resource",-1),A=e("code",null,"data",-1),H=e("li",null,[e("code",null,"import"),r(" Block 추가, 기존 terraform import 명령어 기반 import 작업을 hcl code 로 작성하여 보다 손쉽게 import 작업 수행하도록 지원")],-1),B={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},T={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202306-1",target:"_blank",rel:"noopener noreferrer"},I=e("code",null,"v202306-1 (713)",-1),R=l("
  • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
  • Terraform run 시 구동되는 Build Worker 관련 지원 종료 (v202306-1 부터 적용, Terraform run 시 TFC Agent 로 대체)
  • v202308-1 부터 TFE 구동을 위한 Container 구성이 terraform-enterprise 라는 단일 Container 로 통합 (terraform plan 또는 terraform apply 는 기존 방식과 동일하게 수행 때마다 agent container 생성)
  • v202308-1 부터 Docker 19.03 지원 종료 예정
  • ",4),z={href:"https://developer.hashicorp.com/terraform/enterprise/no-code-provisioning/module-design",target:"_blank",rel:"noopener noreferrer"},U={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.7.0",target:"_blank",rel:"noopener noreferrer"},N=l("",1),K={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.64.0",target:"_blank",rel:"noopener noreferrer"},M=l("",1),W={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.73.0",target:"_blank",rel:"noopener noreferrer"},F=l("",1),O={href:"https://github.com/hashicorp/vault/releases/tag/v1.14.0",target:"_blank",rel:"noopener noreferrer"},G=e("li",null,"PKI Engine 에 대해 사용 중인 유효 인증서 정보 관리에 대한 기본 설정을 off 로 변경",-1),J=e("li",null,"AWS Secret Engine: Static Rotation 지원 (기존 생성된 IAM User 에 대해 Access Key / Secret Key Rotation)",-1),D=e("li",null,"Auth Method 추가: OCI (Oracle Cloud Infrastructure)",-1),L=e("li",null,"Vault Agent Supervisor Mode: Vault 에 저장된 Secret 을 환경변수로 등록",-1),j=e("li",null,"PKI: ACME 지원",-1),Z=e("li",null,"UI: Terraform 과 같이 좌측 Sidebar 형태로 UI 변경",-1),q=e("li",null,"Vault Proxy: Vault Agent API Proxy 와 동일 기능 제공, 향후 기능 추가 및 Vault Agent API Proxy 사용 종료 예정",-1),Y={href:"https://github.com/hashicorp/consul/releases/tag/v1.16.0",target:"_blank",rel:"noopener noreferrer"},Q=e("li",null,[e("code",null,"/v1/health/connect/"),r(" 와 "),e("code",null,"/v1/health/ingress/"),r(" api 에 대해 적절한 권한 없는 token 사용 시 403 오류 출력")],-1),X=e("li",null,"cluster peering: 지원 종료된 version 을 사용하는 cluster 에 대한 하위 호환성 제거",-1),$=e("li",null,[e("code",null,"consul services export"),r(": peer cluster 또는 타 partition 에 서비스 노출을 위한 명령어 추가")],-1),ee=e("li",null,"Permissive mTLS: Service Mesh 에 대해 mTLS 활성화 유무 선택",-1),re=e("li",null,"audit hash: audit log hash function 기반 data hash 값 검증 지원",-1),oe={href:"https://github.com/hashicorp/nomad/releases/tag/v1.5.6",target:"_blank",rel:"noopener noreferrer"},te=e("li",null,"Job allocation 이 재시작 되었을 때 발생하는 group service 가 등록 해제 되는 오류 개선",-1),le=e("li",null,"종료되었음에도 삭제 되지 않은 Job allocation 에 대한 오류 개선",-1),ne=e("li",null,"Job Evaluation 이 잘못된 type 으로 생성되는 오류 개선",-1);function ae(ie,se){const o=a("ExternalLinkIcon");return i(),s("div",null,[d,u,e("ul",null,[e("li",null,[r("매년 유럽 네덜란드에서 이틀간 진행되던 Hashiconf 가 올해는 한단계 더 성장하여 영국 런던, 프랑스 파리 그리고 독일 뮌헨에서 동시에 진행되는 Hashidays 2023 으로 개최되었습니다. 새롭게 펼쳐진 Hashidays 에서는 Terraform, Vault, Consul 그리고 Boundary 에 대해 그동안 사용자들이 사용하면서 느꼈던 불편함을 해소할 신기능을 출시했습니다. "),e("ul",null,[e("li",null,[e("a",h,[r("Hashicorp Blog"),t(o)])]),e("li",null,[e("a",p,[r("HCP Vault Secrets"),t(o)]),r(" (Public Beta): Vault 에서 가장 많이 활용되고 있는 Secret Engine 중 하나인 KV Engine 기반 시크릿 관리에 특화된 HCP SaaS 서비스로써 애플리케이션 개발 및 운영에 사용되는 시크릿에 대한 저장, 접근 그리고 자동 동기화 기능을 더욱 쉽게 구성 가능하도록 지원합니다. 특히 기존 사용 중인 AWS Secret Manager 등 클라우드 서비스와도 원클릭 동기화를 지원함으로써 기존 워크플로우에 대해 최소한의 변경으로 시크릿 동적관리가 가능합니다.")]),e("li",null,[e("a",_,[r("Vault Secrets Operator"),t(o)]),r(": CRD 기반으로 K8S의 Secrets 와 연계하여 Secrets 를 동적으로 관리함으로써 최소한의 추가 구성으로 기존 구성하여 사용중인 Secrets 에 대해 보안을 강화합니다.")]),e("li",null,[e("a",m,[r("Boundary Enterprise"),t(o)]),r(": 기존에 Opensource 그리고 HCP SaaS 로만 제공되던 Boundary 가 드디어 Enterprise Edition 이 출시되었습니다. Enterprise Edition 에서는 그동안 많은 사용자들이 필요로 했던 Session Recording 과 더불어 그외 다양한 신규 기능들이 추가되었습니다.")]),e("li",null,[e("a",g,[r("Terraform"),t(o)]),r(": 지난 6월호에서 소개한 "),e("a",f,[r("Vault-backed dynamic credentials"),t(o)]),r(" 의 정식 GA 출시, 그동안 너무 불편하고 어려웠던 Terraform import 를 보완해줄 "),e("a",b,[r("Config-driven Import"),t(o)]),r(", 생성 및 관리 중인 자원에 대한 보다 효과적인 관리를 위한 Explorer (Beta) 등 다양한 기능이 추가되었습니다.")]),e("li",null,[e("a",w,[r("Consul 1.16"),t(o)]),r(": Envoy Proxy 에 Extension 이 출시되어 WASM (Web Assembly) Code 기반 추가 기능을 활용하거나 외부 보안 서비스와 연계하여 인증 기반 기능 활용합니다, 또한 여러 Cluster 에 걸쳐 동일 서비스에 대해 동일 서비스 이름을 사용하도록 하는 Sameness Groups 를 통해 서비스 관리 뿐만 아니라 장애 발생 시 수행하는 Failover 를 보다 간단하게 처리할 수 있습니다.")])])])]),v,e("ul",null,[e("li",null,[r("Terraform "),e("ul",null,[e("li",null,[r("CLI "),e("ul",null,[e("li",null,[e("a",y,[r("1.5.0 Release"),t(o)]),e("ul",null,[e("li",null,[e("a",k,[S,t(o)]),r(" Block 추가, 최소 1개 이상의 "),x,r(" 구문을 요구하며 "),C,r(" 구문 내 1개 혹은 여러 개의 "),E,r(" 과 "),P,r(" 를 지정하여 "),V,r(" 또는 "),A,r(" 자원에 대한 검증 지원")]),H])])])]),e("li",null,[e("a",B,[r("Enterprise Release"),t(o)]),e("ul",null,[e("li",null,[e("a",T,[r("6월 Release"),t(o)]),r(" 출시 ("),I,r(")")]),R,e("li",null,[e("a",z,[r("No-code Provisioning"),t(o)]),r(" 지원")])])]),e("li",null,[r("Provider "),e("ul",null,[e("li",null,[e("a",U,[r("AWS v5.7.0 주요 수정사항"),t(o)]),N]),e("li",null,[e("a",K,[r("Azure v3.64.0 주요 수정사항"),t(o)]),M]),e("li",null,[e("a",W,[r("GCP v4.73.0 주요 수정사항"),t(o)]),F])])])])]),e("li",null,[r("Vault "),e("ul",null,[e("li",null,[r("1.14.0 "),e("ul",null,[e("li",null,[r("상세 "),e("a",O,[r("Release Note"),t(o)])]),G,J,D,L,j,Z,q])])])]),e("li",null,[r("Consul "),e("ul",null,[e("li",null,[r("1.16 "),e("ul",null,[e("li",null,[r("상세 "),e("a",Y,[r("Release Note"),t(o)])]),Q,X,$,ee,re])])])]),e("li",null,[r("Nomad "),e("ul",null,[e("li",null,[r("1.5.6 "),e("ul",null,[e("li",null,[r("상세 "),e("a",oe,[r("Release Note"),t(o)])]),te,le,ne])])])])])])}const ue=n(c,[["render",ae],["__file","2023-07.html.vue"]]),he=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-07.html","title":"2023년 7월","lang":"ko-KR","frontmatter":{"description":"2023년 7월 Update","tag":["Hashicorp","Update","Jul"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-07.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 7월"}],["meta",{"property":"og:description","content":"2023년 7월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-05T06:52:52.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Jul"}],["meta",{"property":"article:modified_time","content":"2023-10-05T06:52:52.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 7월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-05T06:52:52.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개 (Hashidays 2023)","slug":"product-소개-hashidays-2023","link":"#product-소개-hashidays-2023","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1689171954000,"updatedTime":1696488772000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.49,"words":448},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-07.md","localizedDate":"2023년 7월 12일","excerpt":"\\n

    Product 소개 (Hashidays 2023)

    \\n"}');export{ue as comp,he as data}; diff --git a/assets/2023-08.html-Bq8h3G2V.js b/assets/2023-08.html-Bq8h3G2V.js new file mode 100644 index 0000000000..fdcb9b6603 --- /dev/null +++ b/assets/2023-08.html-Bq8h3G2V.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as s,b as e,d as l,a as r,e as t}from"./app-Bzk8Nrll.js";const c={},_=e("h1",{id:"_2023년-8월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-8월"},[e("span",null,"2023년 8월")])],-1),u=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),d={href:"https://www.hashicorp.com/blog/using-terraform-dynamic-provider-credentials-in-your-aws-landing-zones",target:"_blank",rel:"noopener noreferrer"},p=e("li",null,"지난 4월 소개된 Terraform 사용 시 필요한 대상 환경에 대한 클라우드 자격증명을 Vault 와 연동하여 동적으로 사용 및 관리하는 Dynamic Provider Credentials 기능을 AWS Landing Zone 에서도 사용하실 수 있습니다. Terraform 과 함께 Landing Zone 으로 시작하는 AWS 의 여정에서 더 보안 강화된 IaC 를 경험해보세요!",-1),h=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),m={href:"https://github.com/hashicorp/terraform/releases/tag/v1.5.4",target:"_blank",rel:"noopener noreferrer"},g={href:"https://developer.hashicorp.com/terraform/language/checks",target:"_blank",rel:"noopener noreferrer"},f=e("code",null,"check",-1),v={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},b={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202307-1",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"v202307-1 (722)",-1),k=t("
  • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
  • TFE 구동을 위한 Container 구성이 terraform-enterprise 라는 단일 Container 로 통합. terraform plan 또는 terraform apply 는 기존 방식과 동일하게 수행 때마다 agent container 생성 (적용시점: v202309-1 부터 적용)
  • Docker 19.03, 20.10 미지원 (적용시점: v202308-1 부터 적용)
  • Redis v5 지원 종료
  • Manage Policy Overrides 에 대해 기본 부여된 정책 수정 (기존 Read -> List)
  • 신규 Org 에 대해 Cost Estimation 기본 비활성화
  • ",6),w={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.10.0",target:"_blank",rel:"noopener noreferrer"},T=t("",1),x={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.67.0",target:"_blank",rel:"noopener noreferrer"},N=t("",1),z={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.76.0",target:"_blank",rel:"noopener noreferrer"},P=t("",1),S={href:"https://github.com/hashicorp/vault/releases/tag/v1.14.1",target:"_blank",rel:"noopener noreferrer"},C=e("li",null,"Administrative Namespace 도입. 기존 Root Namespace 에서만 접근 가능한 System Backend Path 에 대한 접근 권한 등을 기반으로 Cluster 관리용 Namespace 로 활용",-1),R=e("li",null,"Transform Secrets Engine 사용 시 Encoding 요청에 대한 Max TTL 적용",-1),U=e("li",null,"Performance Standby (Read Replica) Node 에서 발생하는 요청에 대한 Logging 알람 방지",-1),L={href:"https://github.com/hashicorp/consul/releases/tag/v1.16.0",target:"_blank",rel:"noopener noreferrer"},A=e("li",null,[e("code",null,"/v1/health/connect/"),l(" 와 "),e("code",null,"/v1/health/ingress/"),l(" api 에 대해 적절한 권한 없는 token 사용 시 403 오류 출력")],-1),B=e("li",null,"cluster peering: 지원 종료된 version 을 사용하는 cluster 에 대한 하위 호환성 제거",-1),V=e("li",null,[e("code",null,"consul services export"),l(": peer cluster 또는 타 partition 에 서비스 노출을 위한 명령어 추가")],-1),E=e("li",null,"Permissive mTLS: Service Mesh 에 대해 mTLS 활성화 유무 선택",-1),H=e("li",null,"audit hash: audit log hash function 기반 data hash 값 검증 지원",-1),I={href:"https://www.hashicorp.com/blog/nomad-1-6-adds-node-pools-ux-updates-and-more",target:"_blank",rel:"noopener noreferrer"},J={href:"https://github.com/hashicorp/nomad/releases/tag/v1.6.1",target:"_blank",rel:"noopener noreferrer"},W=e("li",null,"Datacenter 내 Client Node 에 대해 논리적 파티션으로 구분지어 Job 배포 시 해당하는 Pool 에만 배포되도록 하는 Node Pool 기능 추가",-1),Z=e("li",null,"Namespace 를 생성하고 각 Namespace 별 기본 Node Pool 지정 및 배포 허용 여부를 제어할 수 있는 Node Pool Governance 기능 추가",-1),D=e("li",null,"Job Status 및 Job Deployments 에 대한 UI 개선",-1),F=e("li",null,"Job Spec 소스코드에 대한 기존 Raw JSON 지원했던 부분에 대해 소스코드 원문을 UI 상에서 확인 및 수정할 수 있도록 개선",-1),M=e("li",null,"Podman 정식 지원",-1),O=e("li",null,"Job restart 명령어 지원 (기존에는 stop 후 start)",-1);function j(G,q){const o=n("ExternalLinkIcon");return a(),s("div",null,[_,u,e("ul",null,[e("li",null,[l("Using Terraform dynamic provider credentials in your AWS landing zones "),e("ul",null,[e("li",null,[e("a",d,[l("Hashicorp Blog"),r(o)])]),p])])]),h,e("ul",null,[e("li",null,[l("Terraform "),e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",m,[l("1.5.4 Release"),r(o)]),e("ul",null,[e("li",null,[e("a",g,[f,r(o)]),l(" Block 관련 오류 개선")])])])])]),e("li",null,[e("a",v,[l("Enterprise Release"),r(o)]),e("ul",null,[e("li",null,[e("a",b,[l("7월 Release"),r(o)]),l(" 출시 ("),y,l(")")]),k])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",w,[l("AWS v5.10.0 주요 수정사항"),r(o)]),T]),e("li",null,[e("a",x,[l("Azure v3.67.0 주요 수정사항"),r(o)]),N]),e("li",null,[e("a",z,[l("GCP v4.76.0 주요 수정사항"),r(o)]),P])])])])]),e("li",null,[l("Vault "),e("ul",null,[e("li",null,[l("1.14.1 "),e("ul",null,[e("li",null,[l("상세 "),e("a",S,[l("Release Note"),r(o)])]),C,R,U])])])]),e("li",null,[l("Consul "),e("ul",null,[e("li",null,[l("1.16 "),e("ul",null,[e("li",null,[l("상세 "),e("a",L,[l("Release Note"),r(o)])]),A,B,V,E,H])])])]),e("li",null,[l("Nomad "),e("ul",null,[e("li",null,[l("1.6.0 "),e("ul",null,[e("li",null,[e("a",I,[l("Hashicorp Blog"),r(o)])]),e("li",null,[l("상세 "),e("a",J,[l("Release Note"),r(o)])]),W,Z,D,F,M,O])])])])])])}const X=i(c,[["render",j],["__file","2023-08.html.vue"]]),Y=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-08.html","title":"2023년 8월","lang":"ko-KR","frontmatter":{"description":"2023년 8월 Update","tag":["Hashicorp","Update","Aug"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-08.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 8월"}],["meta",{"property":"og:description","content":"2023년 8월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-05T06:52:52.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Aug"}],["meta",{"property":"article:modified_time","content":"2023-10-05T06:52:52.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 8월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-05T06:52:52.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1690862751000,"updatedTime":1696488772000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.01,"words":302},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-08.md","localizedDate":"2023년 8월 1일","excerpt":"\\n

    Product 소개

    \\n"}');export{X as comp,Y as data}; diff --git a/assets/2023-09.html-D8Ie08jE.js b/assets/2023-09.html-D8Ie08jE.js new file mode 100644 index 0000000000..9c1e53c1a5 --- /dev/null +++ b/assets/2023-09.html-D8Ie08jE.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as n,c as s,b as e,d as l,a as t,e as r}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-9월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-9월"},[e("span",null,"2023년 9월")])],-1),_=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p={href:"https://www.hashicorp.com/blog/terraform-ephemeral-workspaces-public-beta-now-available",target:"_blank",rel:"noopener noreferrer"},d=e("li",null,"개발 및 테스트 등을 위해 임시로 사용하는 Workspace 에 대해 사용 완료 후 방치함으로 인해 발생되는 자원 낭비 또는 보안 유출 위험성을 방지하고자 Workspace 사용에 대해 미리 시간 설정을 할 수 있는 기능이 베타로 출시되었습니다. 이제, 정해놓은 타이머가 도래하면 Workspace 와 해당 Workspace 를 통해 생성한 자원을 자동 폐기 및 정리함으로써 자원 관리의 효율성을 높이고 미사용 자원에 대한 보안 유출 등을 방지할 수 있습니다. 베타 버전은 Terraform Cloud (Plus 요금제 이상) 에서 체험 및 사용 가능합니다.",-1),h=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),m={href:"https://github.com/hashicorp/terraform/releases/tag/v1.5.6",target:"_blank",rel:"noopener noreferrer"},g=e("ul",null,[e("li",null,[l("여러 개의 "),e("code",null,"terraform_remote_state"),l(" 를 읽어 오는 중 발생 가능한 오류에 대한 개선")])],-1),f={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202308-1",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"v202308-1 (725)",-1),y=r("
  • 필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)
  • Postgres DB v14.0, v14.1, v14.2, v14.3 에 대한 지원 종료
  • TFE 구동을 위한 Container 구성이 terraform-enterprise 라는 단일 Container 로 통합. terraform plan 또는 terraform apply 는 기존 방식과 동일하게 수행 때마다 agent container 생성 (적용시점: v202309-1 부터 적용)
  • Docker 19.03, 20.10 지원 종료
  • dynamic provider credentials 에 대해 Workspace 내 Provider 당 설정 지원
  • UI 출력 및 Cloud Provider 사용 위한 Key 검증 처리 등 성능 개선
  • ",6),v={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.15.0",target:"_blank",rel:"noopener noreferrer"},k=r("",1),w={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.71.0",target:"_blank",rel:"noopener noreferrer"},x=r("",1),P={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v4.80.0",target:"_blank",rel:"noopener noreferrer"},T=r("",1),N={href:"https://github.com/hashicorp/vault/releases/tag/v1.14.2",target:"_blank",rel:"noopener noreferrer"},U=e("li",null,"Azure Auth Method 에 대해 Auto auth 위한 Azure Workload Identity Federation 설정 지원",-1),C=e("li",null,"KMIP 에 대해 Namespace Lock/Unlock 설정 지원",-1),z=e("li",null,"Replication 관련 Flush 단계에서 쓰기 지원하여 Index 재생성 작업에 영향 주지 않도록 개선",-1),S=e("li",null,"Vault Agent Template 에 대해 VAULT_CACERT_BYTES 환경변수 사용 지원",-1),R=e("li",null,"Transform Engine 관련 Standby Node 에서 발생하는 인코딩 오류 개선",-1),I={href:"https://github.com/hashicorp/consul/releases/tag/v1.16.1",target:"_blank",rel:"noopener noreferrer"},A=e("li",null,[e("code",null,"consul members"),l(" 명령어에 대해 -filter 설정 추가")],-1),V=e("li",null,"operator/usage api 에 대해 node 수 출력 지원 (CLI 사용 시 consul operator usage 명령어 사용)",-1),W=e("li",null,"Service Mesh 관련 Virtual Service 및 Failover 에 대한 Transparent Proxy 성능 개선",-1),B=e("li",null,"Topology 관점 UI 출력 개선",-1),E=e("li",null,"Envoy Proxy 관련 xDS configuration 완료되기 전 구동 되어 발생하는 오류 개선",-1),F=e("li",null,"RSA Key 에 대해 사이즈가 2048 bits 이하여서 발생하는 오류를 검증 단계에서 미리 확인하도록 개선",-1),H={href:"https://github.com/hashicorp/nomad/releases/tag/v1.6.1",target:"_blank",rel:"noopener noreferrer"},J=e("li",null,"Datacenter 내 Client Node 에 대해 논리적 파티션으로 구분지어 Job 배포 시 해당하는 Pool 에만 배포되도록 하는 Node Pool 기능 추가",-1),D=e("li",null,"Namespace 를 생성하고 각 Namespace 별 기본 Node Pool 지정 및 배포 허용 여부를 제어할 수 있는 Node Pool Governance 기능 추가",-1),L=e("li",null,"Job Status 및 Job Deployments 에 대한 UI 개선",-1),K=e("li",null,"Job Spec 소스코드에 대한 기존 Raw JSON 지원했던 부분에 대해 소스코드 원문을 UI 상에서 확인 및 수정할 수 있도록 개선",-1),M=e("li",null,"Podman 정식 지원",-1),j=e("li",null,"Job restart 명령어 지원 (기존에는 stop 후 start)",-1);function q(G,Z){const o=a("ExternalLinkIcon");return n(),s("div",null,[u,_,e("ul",null,[e("li",null,[l("Terraform ephemeral workspaces public beta now available "),e("ul",null,[e("li",null,[e("a",p,[l("Hashicorp Blog"),t(o)])]),d])])]),h,e("ul",null,[e("li",null,[l("Terraform "),e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",m,[l("1.5.6 Release"),t(o)]),g])])]),e("li",null,[e("ul",null,[e("li",null,[e("a",f,[l("8월 Release"),t(o)]),l(" 출시 ("),b,l(")")]),y])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",v,[l("AWS v5.15.0 주요 수정사항"),t(o)]),k]),e("li",null,[e("a",w,[l("Azure v3.71.0 주요 수정사항"),t(o)]),x]),e("li",null,[e("a",P,[l("GCP v4.80.0 주요 수정사항"),t(o)]),T])])])])]),e("li",null,[l("Vault "),e("ul",null,[e("li",null,[l("1.14.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",N,[l("Release Note"),t(o)])]),U,C,z,S,R])])])]),e("li",null,[l("Consul "),e("ul",null,[e("li",null,[l("1.16.1 "),e("ul",null,[e("li",null,[l("상세 "),e("a",I,[l("Release Note"),t(o)])]),A,V,W,B,E,F])])])]),e("li",null,[l("Nomad "),e("ul",null,[e("li",null,[l("1.6.1 "),e("ul",null,[e("li",null,[l("상세 "),e("a",H,[l("Release Note"),t(o)])]),J,D,L,K,M,j])])])])])])}const Q=i(c,[["render",q],["__file","2023-09.html.vue"]]),X=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-09.html","title":"2023년 9월","lang":"ko-KR","frontmatter":{"description":"2023년 9월 Update","tag":["Hashicorp","Update","Sep"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-09.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 9월"}],["meta",{"property":"og:description","content":"2023년 9월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-05T06:52:52.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Sep"}],["meta",{"property":"article:modified_time","content":"2023-10-05T06:52:52.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 9월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-05T06:52:52.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1693797134000,"updatedTime":1696488772000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.95,"words":284},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-09.md","localizedDate":"2023년 9월 4일","excerpt":"\\n

    Product 소개

    \\n"}');export{Q as comp,X as data}; diff --git a/assets/2023-10.html-DCZIi768.js b/assets/2023-10.html-DCZIi768.js new file mode 100644 index 0000000000..c936ea4a27 --- /dev/null +++ b/assets/2023-10.html-DCZIi768.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as n,c as s,b as e,d as l,a as r,e as o}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-10월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-10월"},[e("span",null,"2023년 10월")])],-1),p=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),d={href:"https://www.hashicorp.com/blog/multicloud-golden-image-pipeline-terraform-cloud-hcp-packer",target:"_blank",rel:"noopener noreferrer"},_=e("li",null,'조직 내 클라우드 사용환경에서 "표준화" 되지 않은 VM OS Image 로 인해 장애 발생 시 케이스를 표준화 하지 못하고 대응에 미진한 경우를 종종 접하곤 합니다. Hashicorp Packer 와 Terraform 을 연동하여 조직 내 사용중인 각 클라우드 환경 마다 Golden OS Image 를 구성하고 이를 활용하여 인스턴스 자원 배포하는 과정까지의 라이프사이클을 "표준화" 함으로써 보다 더 효율적이고 안정적인 시스템 환경을 구성해보세요.',-1),h=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),m={href:"https://github.com/hashicorp/terraform/releases/tag/v1.5.7",target:"_blank",rel:"noopener noreferrer"},g=e("ul",null,[e("li",null,[e("code",null,"terraform init"),l(" 수행 시 잘못된 경로로 모듈을 다운로드 하는 경우에 대한 오류 개선")]),e("li",null,[e("code",null,"terraform remote state"),l(" 관리 시 발생 가능한 state 간 호환성 불일치에 대한 이슈 방지")])],-1),f={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},b={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202309-1",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,"v202309-1 (733)",-1),k=e("li",null,"필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)",-1),y={href:"https://developer.hashicorp.com/terraform/enterprise/flexible-deployments",target:"_blank",rel:"noopener noreferrer"},w=e("li",null,[l("Azure DevOps VCS 연동 시 발생하는 Known Issue 에 대해 Azure DevOps Server 의 버전을 "),e("code",null,"2020 Update 1.2 Patch 7"),l(" 로 업그레이드 하여 해결")],-1),P=e("li",null,[l("Terraform Enterprise 로그인 진행 후 발생하는 "),e("code",null,"step-up"),l(" 추가 인증 이슈에 10월 Release 에서 개선 예정")],-1),S={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.19.0",target:"_blank",rel:"noopener noreferrer"},x=o("",1),C={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.75.0",target:"_blank",rel:"noopener noreferrer"},z=o("",1),T={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v5.0.0",target:"_blank",rel:"noopener noreferrer"},U=o("",1),V={href:"https://github.com/hashicorp/vault/releases/tag/v1.15.0",target:"_blank",rel:"noopener noreferrer"},I=e("li",null,"Auth Method (alicloud, azure, cf, gcp, jet, k8s, oci 등) plugin 버전 업그레이드",-1),R=e("li",null,"RGP (Role Governing Policies) 설정 시 Namespace 단위로 적용",-1),A=e("li",null,"System Backend Path에 접근 가능한 Administrative Namespace 도입 (기존에는 root 만 가능)",-1),N=e("li",null,"UI 상에서 KV v2 의 Path 정보에 대한 복사 기능 지원하여 CLI 또는 API 에서의 활용 편의성 증가",-1),H=e("li",null,"UI 첫 페이지에 Dashbaord 도입",-1),B=e("li",null,"SAML Auth Method 지원",-1),O={href:"https://github.com/hashicorp/consul/releases/tag/v1.16.2",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,"Vault 와 연동하여 사용하는 인증서 관련 Leaf Cert 생성 시 발생하는 오류 개선",-1),G=e("li",null,"Snapshot 기반 복원 후 Envoy Endpoint 올바르게 생성되지 않는 이슈 해결",-1),M={href:"https://github.com/hashicorp/nomad/releases/tag/v1.6.2",target:"_blank",rel:"noopener noreferrer"},j=e("li",null,"Service Mesh 위해 Consul 연동 시 upstream 설정 관련 DestinationPeer, DestinationType, LocalBindSocketPath 등 설정 지원",-1),E=e("li",null,"jobspec 관련 여러개의 cron 정의를 위해 crons 지원",-1),K=e("li",null,"UI 개선",-1);function L(F,W){const t=a("ExternalLinkIcon");return n(),s("div",null,[u,p,e("ul",null,[e("li",null,[l("Creating a multi-cloud golden image pipeline with Terraform Cloud and HCP Packer "),e("ul",null,[e("li",null,[e("a",d,[l("Hashicorp Blog"),r(t)])]),_])])]),h,e("ul",null,[e("li",null,[l("Terraform "),e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",m,[l("1.5.7 Release"),r(t)]),g])])]),e("li",null,[e("a",f,[l("Enterprise Release"),r(t)]),e("ul",null,[e("li",null,[e("a",b,[l("9월 Release"),r(t)]),l(" 출시 ("),v,l(")")]),k,e("li",null,[l("기존에 수십 가지의 container 로 분산 구성되어 있던 서비스 구성을 단일 container 로 통합 (terraform-enterprise). 이를 기반으로 "),e("a",y,[l("flexible deployment options"),r(t)]),l(" 로써 VM 또는 K8S 등 다양한 플랫폼에 설치 및 구동방식 제공")]),w,P])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",S,[l("AWS v5.19.0 주요 수정사항"),r(t)]),x]),e("li",null,[e("a",C,[l("Azure v3.75.0 주요 수정사항"),r(t)]),z]),e("li",null,[e("a",T,[l("GCP v5.0.0 주요 수정사항"),r(t)]),U])])])])]),e("li",null,[l("Vault "),e("ul",null,[e("li",null,[l("1.15 "),e("ul",null,[e("li",null,[l("상세 "),e("a",V,[l("Release Note"),r(t)])]),I,R,A,N,H,B])])])]),e("li",null,[l("Consul "),e("ul",null,[e("li",null,[l("1.16.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",O,[l("Release Note"),r(t)])]),D,G])])])]),e("li",null,[l("Nomad "),e("ul",null,[e("li",null,[l("1.6.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",M,[l("Release Note"),r(t)])]),j,E,K])])])])])])}const J=i(c,[["render",L],["__file","2023-10.html.vue"]]),Q=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-10.html","title":"2023년 10월","lang":"ko-KR","frontmatter":{"description":"2023년 10월 Update","tag":["Hashicorp","Update","Oct"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-10.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 10월"}],["meta",{"property":"og:description","content":"2023년 10월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-05T07:29:41.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Oct"}],["meta",{"property":"article:modified_time","content":"2023-10-05T07:29:41.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 10월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-05T07:29:41.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1696476441000,"updatedTime":1696490981000,"contributors":[{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":3},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.9,"words":271},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-10.md","localizedDate":"2023년 10월 5일","excerpt":"\\n

    Product 소개

    \\n"}');export{J as comp,Q as data}; diff --git a/assets/2023-11.html-CTNmw6DG.js b/assets/2023-11.html-CTNmw6DG.js new file mode 100644 index 0000000000..0e05c57af3 --- /dev/null +++ b/assets/2023-11.html-CTNmw6DG.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as s,b as e,d as l,a as o,e as r}from"./app-Bzk8Nrll.js";const c={},u=e("h1",{id:"_2023년-11월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2023년-11월"},[e("span",null,"2023년 11월")])],-1),d=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),_={href:"https://www.hashicorp.com/blog/infrastructure-security-lifecycle-releases-open-hashiconf-2023",target:"_blank",rel:"noopener noreferrer"},p={href:"https://www.linkedin.com/search/results/content/?fromMember=%5B%22ACoAABFOyc0BZBke7ai9SSnSjeoYufUuYByUFqo%22%5D&heroEntityKey=urn%3Ali%3Afsd_profile%3AACoAABFOyc0BZBke7ai9SSnSjeoYufUuYByUFqo&keywords=jin%20sol%20kim&position=0&searchId=ccb5024f-0353-4b04-a8e3-41e3bace4159&sid=_%40V&update=urn%3Ali%3Afs_updateV2%3A(urn%3Ali%3Aactivity%3A7126034287874699264%2CBLENDED_SEARCH_FEED%2CEMPTY%2CDEFAULT%2Cfalse)",target:"_blank",rel:"noopener noreferrer"},h=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),m={href:"https://github.com/hashicorp/terraform/releases/tag/v1.6.0",target:"_blank",rel:"noopener noreferrer"},f=r("",1),g={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},y={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202310-1",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"v202310-1 (741)",-1),v=e("li",null,"필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202207-2)",-1),A={href:"https://developer.hashicorp.com/terraform/enterprise/flexible-deployments",target:"_blank",rel:"noopener noreferrer"},S=e("code",null,"consolidated_services_enabled",-1),k=e("li",null,[l("Known Issue 1. Azure DevOps VCS 연동 시 발생하는 Known Issue 에 대해 Azure DevOps Server 의 버전을 "),e("code",null,"2020 Update 1.2 Patch 7"),l(" 로 업그레이드 하여 해결")],-1),B=e("li",null,[l("Known Issue 2. GCP 환경에서 "),e("code",null,"consolidated_services_enabled"),l(" 설정 활성화하고 설치 시 발생하는 Object Storage Issue 는 v202311-1 에서 개선 예정")],-1),C={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.23.1",target:"_blank",rel:"noopener noreferrer"},E=e("ul",null,[e("li",null,[l("Bug Fix "),e("ul",null,[e("li",null,"data-source/aws_lambda_function 에 대해 vpc_config.ipv6_allowed_for_dual_stack 설정 시 발생하는 오류 개선")])])],-1),U={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.78.0",target:"_blank",rel:"noopener noreferrer"},w=e("ul",null,[e("li",null,[l("신규 resource 추가 "),e("ul",null,[e("li",null,"azurerm_resource_management_private_link_association")])]),e("li",null,[l("기능 개선 "),e("ul",null,[e("li",null,"azurerm_redis_enterprise_cluster 에 대한 Japan East 리전 설정 지원")])]),e("li",null,[l("Bug Fix "),e("ul",null,[e("li",null,"azurerm_backup_policy_vm 에 대해 현재 시간정보 사용 관련 오류 개선")])])],-1),D={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v5.4.0",target:"_blank",rel:"noopener noreferrer"},N=r("",1),I={href:"https://github.com/hashicorp/vault/releases/tag/v1.15.1",target:"_blank",rel:"noopener noreferrer"},T=e("li",null,"Secret Sync 관련 telemetry 추가",-1),P=e("li",null,"UI 개선",-1),R=e("li",null,[l("Audit Device 설정 관련 "),e("code",null,"file"),l(" 사용 시 SIGHUP 에서 발생하는 log file 재열기 오류 개선")],-1),H=e("li",null,"AWS Auth Method 관련 Client Config 가 존재하지 않는 경우 발생하는 패닉 오류 개선",-1),V=e("li",null,"Transit Engine 관련 managed key 사용 시 sign/verify 과정 추가 key auto rotation 불가하도록 개선",-1),x=e("li",null,"DB Secret Engine 관련 Mongo DB 에 대해 admin 이 아닌 계정에 대한 root rotate 지원",-1),F={href:"https://github.com/hashicorp/consul/releases/tag/v1.16.2",target:"_blank",rel:"noopener noreferrer"},O=e("li",null,"Vault 와 연동하여 사용하는 인증서 관련 Leaf Cert 생성 시 발생하는 오류 개선",-1),L=e("li",null,"Snapshot 기반 복원 후 Envoy Endpoint 올바르게 생성되지 않는 이슈 해결",-1),j={href:"https://github.com/hashicorp/nomad/releases/tag/v1.6.2",target:"_blank",rel:"noopener noreferrer"},M=e("li",null,"Service Mesh 위해 Consul 연동 시 upstream 설정 관련 DestinationPeer, DestinationType, LocalBindSocketPath 등 설정 지원",-1),Y=e("li",null,"jobspec 관련 여러개의 cron 정의를 위해 crons 지원",-1),W=e("li",null,"UI 개선",-1);function K(q,z){const t=a("ExternalLinkIcon");return i(),s("div",null,[u,d,e("ul",null,[e("li",null,[l("Infrastructure and security releases open HashiConf 2023 "),e("ul",null,[e("li",null,[e("a",_,[l("Hashicorp Blog"),o(t)])]),e("li",null,[l("샌프란시스코에서 개최된 Hashiconf 2023 에서 8가지의 솔루션에 대해 그룹을 크게 Infrastructure 와 Security 로 구분지어 앞으로의 솔루션 포트폴리오 및 업데이트를 진행하며, Terraform test framework, Vault Secret Sync, Vault Radar 등 워크플로우 개선을 위한 새로운 기능이 공개했습니다. 자세한 사항은 행사 현장을 직접 다녀온 이들이 전해주는 "),e("a",p,[l("Hashicorp Korea: Hashiconf 2023"),o(t)]),l(" 에서 확인하세요!")])])])]),h,e("ul",null,[e("li",null,[l("Terraform "),e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",m,[l("1.6 Release"),o(t)]),f])])]),e("li",null,[e("a",g,[l("Enterprise Release"),o(t)]),e("ul",null,[e("li",null,[e("a",y,[l("10월 Release"),o(t)]),l(" 출시 ("),b,l(")")]),v,e("li",null,[l("v202309-1 release 부터 FDO ("),e("a",A,[l("flexible deployment options"),o(t)]),l(") 지원하며,"),S,l(" 설정 여부에 따라 기존 replicated 설치 방식 사용 지원.")]),k,B])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",C,[l("AWS v5.23.1 주요 수정사항"),o(t)]),E]),e("li",null,[e("a",U,[l("Azure v3.78.0 주요 수정사항"),o(t)]),w]),e("li",null,[e("a",D,[l("GCP v5.4.0 주요 수정사항"),o(t)]),N])])])])]),e("li",null,[l("Vault "),e("ul",null,[e("li",null,[l("1.15.1 "),e("ul",null,[e("li",null,[l("상세 "),e("a",I,[l("Release Note"),o(t)])]),T,P,R,H,V,x])])])]),e("li",null,[l("Consul "),e("ul",null,[e("li",null,[l("1.16.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",F,[l("Release Note"),o(t)])]),O,L])])])]),e("li",null,[l("Nomad "),e("ul",null,[e("li",null,[l("1.6.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",j,[l("Release Note"),o(t)])]),M,Y,W])])])])])])}const J=n(c,[["render",K],["__file","2023-11.html.vue"]]),Q=JSON.parse('{"path":"/04-HashiCorp/08-Updates/98-2023/2023-11.html","title":"2023년 11월","lang":"ko-KR","frontmatter":{"description":"2023년 11월 Update","tag":["Hashicorp","Update","Nov"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/98-2023/2023-11.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2023년 11월"}],["meta",{"property":"og:description","content":"2023년 11월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2024-02-14T07:38:50.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Nov"}],["meta",{"property":"article:modified_time","content":"2024-02-14T07:38:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2023년 11월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-02-14T07:38:50.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1698990531000,"updatedTime":1707896330000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":0.99,"words":298},"filePathRelative":"04-HashiCorp/08-Updates/98-2023/2023-11.md","localizedDate":"2023년 11월 3일","excerpt":"\\n

    Product 소개

    \\n"}');export{J as comp,Q as data}; diff --git a/assets/2024-01.html-BqO2nuKk.js b/assets/2024-01.html-BqO2nuKk.js new file mode 100644 index 0000000000..d8de85253a --- /dev/null +++ b/assets/2024-01.html-BqO2nuKk.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as a,c as s,b as e,d as l,a as o,e as r}from"./app-Bzk8Nrll.js";const c={},_=e("h1",{id:"_2024년-1월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2024년-1월"},[e("span",null,"2024년 1월")])],-1),u=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),p={href:"https://www.hashicorp.com/blog/hashicorp-2023-year-in-review-community",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,"작년 2023년 한 해동안 있었던 Hashicorp 관련 이야기: 개최된 컨퍼런스 및 이벤트부터 새로 출시된 솔루션 별 트레이닝 및 자격증 관련, 그리고 창업자 Mitchell Hashimoto 의 퇴사 소식 등을 한 번에 확인하실 수 있습니다.",-1),d=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),g=e("p",null,"Terraform",-1),m={href:"https://github.com/hashicorp/terraform/releases/tag/v1.6.6",target:"_blank",rel:"noopener noreferrer"},f=e("ul",null,[e("li",null,[e("code",null,"terraform test"),l(" 관련 실제 존재하지 않는 자원에 대해 destroy 를 수행하려는 오류에 대한 개선")])],-1),v={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},b={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2023/v202312-1",target:"_blank",rel:"noopener noreferrer"},y=e("code",null,"v202312-1 (745)",-1),w=e("li",null,"필수 Upgrade Versison: Release Note 에서 * 표기된 Version 은 필수로 거쳐야 하는 Version (예: v202312-1)",-1),k={href:"https://developer.hashicorp.com/terraform/enterprise/workspaces/variables/managing-variables#priority-variable-sets",target:"_blank",rel:"noopener noreferrer"},C=e("li",null,[e("code",null,"terraform plan -out "),l(" 또는 API 호출 시 "),e("code",null,"save-plan"),l(" 을 활용하여 plan 정보를 저장 후 추후 apply 시 활용하도록 지원")],-1),x=e("li",null,[l("Workspace 의 상태 정보를 확인하고 "),e("code",null,"inactive"),l(" 인 Workspace 에 대해 자동 destroy 수행 지원")],-1),V=e("li",null,[l("Known Issue: working directory 설정 관련하여 "),e("code",null,"Operation failed: failed packing filesystem: illegal slug error: invalid symlink"),l(" 에러 발생하는 부분에 대해 2024 1월 release 에서 patch 예정")],-1),E={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.31.0",target:"_blank",rel:"noopener noreferrer"},z=r("",1),H={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.85.0",target:"_blank",rel:"noopener noreferrer"},U=r("",1),P={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v5.10.0",target:"_blank",rel:"noopener noreferrer"},R=r("",1),N=e("p",null,"Vault",-1),T={href:"https://github.com/hashicorp/vault/releases/tag/v1.15.4",target:"_blank",rel:"noopener noreferrer"},B={href:"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-6337",target:"_blank",rel:"noopener noreferrer"},I={href:"https://discuss.hashicorp.com/t/hcsec-2023-34-vault-vulnerable-to-denial-of-service-through-memory-exhaustion-when-handling-large-http-requests/60741",target:"_blank",rel:"noopener noreferrer"},S=e("li",null,"sys/leader api 호출 시 발생 가능한 deadlock 오류 개ㅓㅅㄴ",-1),j=e("li",null,"ui 관련 버그 개선",-1),A=e("p",null,"Consul",-1),F=e("p",null,"1.17.1",-1),L={href:"https://github.com/hashicorp/consul/releases/tag/v1.17.1",target:"_blank",rel:"noopener noreferrer"},D=e("p",null,"소스코드 Go 1.20.12 기반으로 변경 하여",-1),G={href:"https://nvd.nist.gov/vuln/detail/CVE-2023-45283",target:"_blank",rel:"noopener noreferrer"},K={href:"https://nvd.nist.gov/vuln/detail/CVE-2023-45285",target:"_blank",rel:"noopener noreferrer"},M={href:"https://nvd.nist.gov/vuln/detail/CVE-2023-39326",target:"_blank",rel:"noopener noreferrer"},W={href:"https://nvd.nist.gov/vuln/detail/CVE-2023-45285",target:"_blank",rel:"noopener noreferrer"},J=e("li",null,[e("p",null,"ACL 관련 nomad client templated policy와 api-gateway templated policy 지원")],-1),Z=e("li",null,[e("p",null,[e("code",null,"peering exported-services"),l(" 명령어 추가하여 peer cluster 에게 export 한 service 목록 확인 지원")])],-1),q=e("li",null,[e("p",null,[l("Consul Telemetry Collector 의 "),e("code",null,"stats_flush_interval"),l(" 에 대한 기본값을 60초로 지정")])],-1),O=e("li",null,[e("p",null,"Wan Federation 관련 Secondary DC 에 대해 Replication 을 위한 불필요한 쓰기 작업을 방지하기 위해 hash 적용")],-1),Q=e("p",null,"Nomad",-1),X={href:"https://github.com/hashicorp/nomad/releases/tag/v1.7.2",target:"_blank",rel:"noopener noreferrer"},Y=e("li",null,[e("strong",null,"Reschedule on Lost"),l(": Node Down 등으로 중지된 job 에 대해 "),e("code",null,"nomad job stop"),l(" 등의 수동 개입이 필요했던 부분에 대해 자동으로 reschedule 방지 되도록 지원")],-1),$=e("li",null,"UI 에서 Task 에 대한 예제 template 추가",-1),ee=e("li",null,"잘못된 CPU 정보 기반 CPU Topology 작성 시 발생하는 패닉 오류 개선",-1);function le(te,oe){const t=i("ExternalLinkIcon");return a(),s("div",null,[_,u,e("ul",null,[e("li",null,[l("HashiCorp 2023 year in review: Community "),e("ul",null,[e("li",null,[e("a",p,[l("Hashicorp Blog"),o(t)])]),h])])]),d,e("ul",null,[e("li",null,[g,e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",m,[l("1.6.6 Release"),o(t)]),f])])]),e("li",null,[e("a",v,[l("Enterprise Release"),o(t)]),e("ul",null,[e("li",null,[e("a",b,[l("12월 Release"),o(t)]),l(" 출시 ("),y,l(")")]),w,e("li",null,[e("a",k,[l("Priorty Variable Set"),o(t)]),l(" 를 통해 변수에 지정되는 값에 대한 우선순위 설정 지원")]),C,x,V])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",E,[l("AWS v5.31.0 주요 수정사항"),o(t)]),z]),e("li",null,[e("a",H,[l("Azure v3.85.0 주요 수정사항"),o(t)]),U]),e("li",null,[e("a",P,[l("GCP v5.10.0 주요 수정사항"),o(t)]),R])])])])]),e("li",null,[N,e("ul",null,[e("li",null,[l("1.15.4 "),e("ul",null,[e("li",null,[l("상세 "),e("a",T,[l("Release Note"),o(t)])]),e("li",null,[e("a",B,[l("CVE-2023-6337"),o(t)]),l(" & "),e("a",I,[l("HCSEC-2023-34"),o(t)]),l(" 관련 오류 개선")]),S,j])])])]),e("li",null,[A,e("ul",null,[e("li",null,[F,e("ul",null,[e("li",null,[e("p",null,[l("상세 "),e("a",L,[l("Release Note"),o(t)])])]),e("li",null,[D,e("p",null,[e("a",G,[l("CVE-2023-45283"),o(t)]),l(", "),e("a",K,[l("CVE-2023-45284"),o(t)]),l(", "),e("a",M,[l("CVE-2023-39326"),o(t)]),l(", "),e("a",W,[l("CVE-2023-45285"),o(t)]),l(" 관련 이슈 해결")])]),J,Z,q,O])])])]),e("li",null,[Q,e("ul",null,[e("li",null,[l("1.7.2 "),e("ul",null,[e("li",null,[l("상세 "),e("a",X,[l("Release Note"),o(t)])]),Y,$,ee])])])])])])}const ie=n(c,[["render",le],["__file","2024-01.html.vue"]]),ae=JSON.parse('{"path":"/04-HashiCorp/08-Updates/97-2024/2024-01.html","title":"2024년 1월","lang":"ko-KR","frontmatter":{"description":"2024년 1월 Update","tag":["Hashicorp","Update","Jan"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/97-2024/2024-01.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2024년 1월"}],["meta",{"property":"og:description","content":"2024년 1월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2024-02-14T07:38:50.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Jan"}],["meta",{"property":"article:modified_time","content":"2024-02-14T07:38:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2024년 1월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-02-14T07:38:50.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1704238435000,"updatedTime":1707896330000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":1.12,"words":336},"filePathRelative":"04-HashiCorp/08-Updates/97-2024/2024-01.md","localizedDate":"2024년 1월 3일","excerpt":"\\n

    Product 소개

    \\n"}');export{ie as comp,ae as data}; diff --git a/assets/2024-02.html-C7mJntCJ.js b/assets/2024-02.html-C7mJntCJ.js new file mode 100644 index 0000000000..36173711c9 --- /dev/null +++ b/assets/2024-02.html-C7mJntCJ.js @@ -0,0 +1 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as n,c as s,b as e,d as l,a as r,e as o}from"./app-Bzk8Nrll.js";const u={},c=e("h1",{id:"_2024년-2월",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2024년-2월"},[e("span",null,"2024년 2월")])],-1),p=e("h2",{id:"product-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-소개"},[e("span",null,"Product 소개")])],-1),_={href:"https://www.hashicorp.com/blog/hcp-vault-radar-begins-limited-beta",target:"_blank",rel:"noopener noreferrer"},d=e("li",null,"작년 2023년 10월 Hashiconf 에서 공개된 HCP Vault Radar 가 Alpha 를 거쳐 Beta 가 출시되었습니다. Beta 에서는 RBAC/ABAC 을 지원하며 스캔할 수 있는 새로운 데이터 소스도 선보입니다",-1),h=e("h2",{id:"product-update",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#product-update"},[e("span",null,"Product Update")])],-1),g={href:"https://github.com/hashicorp/terraform/releases/tag/v1.7.2",target:"_blank",rel:"noopener noreferrer"},m=e("ul",null,[e("li",null,"terraform cloud 사용 시 발생하는 module 다운로드 재시도 등 오류 개선")],-1),f={href:"https://developer.hashicorp.com/terraform/enterprise/releases",target:"_blank",rel:"noopener noreferrer"},b={href:"https://developer.hashicorp.com/terraform/enterprise/releases/2024/v202401-1",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,"v202401-1 (751)",-1),k=e("li",null,[l("2023년 9월 (v202309-1) 에 출시된 FDO 설치방식과 기존 Replicated 설치방식을 선택할 수 있게 하는 "),e("code",null,"consolidated_services_enabled"),l(" 설정 지원 종료")],-1),w=e("li",null,"Org 내 연결된 VCS 에 대한 Status Check 개선",-1),y={href:"https://developer.hashicorp.com/terraform/cloud-docs/registry/publish-modules#branch-based-publishing-considerations",target:"_blank",rel:"noopener noreferrer"},R={href:"https://developer.hashicorp.com/terraform/enterprise/registry/test",target:"_blank",rel:"noopener noreferrer"},C=e("li",null,"Plan and Apply 수행 시 추가 옵션을 제공하여 선택한 자원에 대해 교체 되도록 지원",-1),P=e("li",null,"Workspace 설정에 Auto-apply run triggers 를 도입하여 연결된 다른 Workspace 에 변경 사항 발생 시 자동 apply 되도록 설정 지원",-1),x={href:"https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.35.0",target:"_blank",rel:"noopener noreferrer"},B=o("",1),T={href:"https://github.com/hashicorp/terraform-provider-azurerm/releases/tag/v3.90.0",target:"_blank",rel:"noopener noreferrer"},A=o("",1),N={href:"https://github.com/hashicorp/terraform-provider-google/releases/tag/v5.15.0",target:"_blank",rel:"noopener noreferrer"},H=o("",1),S={href:"https://github.com/hashicorp/vault/releases/tag/v1.15.5",target:"_blank",rel:"noopener noreferrer"},U=e("li",null,"audit log 관련 log_raw 설정 사용 시 발생하는 버그 개선",-1),V=e("li",null,"ui 개선",-1),z={href:"https://github.com/hashicorp/consul/releases/tag/v1.17.2",target:"_blank",rel:"noopener noreferrer"},E=e("li",null,[e("p",null,"Known Issue: 1.17.2 및 1.16.5 에서 Terminating Gatteway 가 TLS SAN 검증을 엄격하게 수행함으로 인해 Service Mesh 외부 Upstream 서비스 연결 방지")],-1),F=e("li",null,[e("p",null,"sameness-group 기반 failover 수행 시 해당 group이 속해있는 partition 이 아닌 기본 partition 으로 질의 하는 오류 개선")],-1),I={href:"https://github.com/hashicorp/nomad/releases/tag/v1.7.3",target:"_blank",rel:"noopener noreferrer"},O=e("li",null,"exec driver 에 대해 OOM 감지 관련 지원",-1),M=e("li",null,"Consul 에 대해 admin partition 지원",-1),D=e("li",null,"1.5 및 1.6 client 에서 발생하는 template 기반 Variable 및 Service 사용 오류 개선",-1),L=e("li",null,"UI 개선",-1);function j(G,K){const t=a("ExternalLinkIcon");return n(),s("div",null,[c,p,e("ul",null,[e("li",null,[l("HCP Vault Radar begins limited beta "),e("ul",null,[e("li",null,[e("a",_,[l("Hashicorp Blog"),r(t)])]),d])])]),h,e("ul",null,[e("li",null,[l("Terraform "),e("ul",null,[e("li",null,[l("CLI "),e("ul",null,[e("li",null,[e("a",g,[l("1.7.2 Release"),r(t)]),m])])]),e("li",null,[e("a",f,[l("Enterprise Release"),r(t)]),e("ul",null,[e("li",null,[e("a",b,[l("1월 Release"),r(t)]),l(" 출시 ("),v,l(")")]),k,w,e("li",null,[l("Private Registry 관련 "),e("a",y,[l("branch-based publishing workflow"),r(t)]),l(" 와 "),e("a",R,[l("automatically run tests for modules"),r(t)]),l(" 기능 지원")]),C,P])]),e("li",null,[l("Provider "),e("ul",null,[e("li",null,[e("a",x,[l("AWS v5.35.0 주요 수정사항"),r(t)]),B]),e("li",null,[e("a",T,[l("Azure v3.90.0 주요 수정사항"),r(t)]),A]),e("li",null,[e("a",N,[l("GCP v5.15.0 주요 수정사항"),r(t)]),H])])])])]),e("li",null,[l("Vault "),e("ul",null,[e("li",null,[l("1.15.5 "),e("ul",null,[e("li",null,[l("상세 "),e("a",S,[l("Release Note"),r(t)])]),U,V])])])]),e("li",null,[l("Consul "),e("ul",null,[e("li",null,[l("1.17.2 "),e("ul",null,[e("li",null,[e("p",null,[l("상세 "),e("a",z,[l("Release Note"),r(t)])])]),E,F])])])]),e("li",null,[l("Nomad "),e("ul",null,[e("li",null,[l("1.7.3 "),e("ul",null,[e("li",null,[l("상세 "),e("a",I,[l("Release Note"),r(t)])]),O,M,D,L])])])])])])}const q=i(u,[["render",j],["__file","2024-02.html.vue"]]),J=JSON.parse('{"path":"/04-HashiCorp/08-Updates/97-2024/2024-02.html","title":"2024년 2월","lang":"ko-KR","frontmatter":{"description":"2024년 2월 Update","tag":["Hashicorp","Update","Feb"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/08-Updates/97-2024/2024-02.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"2024년 2월"}],["meta",{"property":"og:description","content":"2024년 2월 Update"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2024-02-14T07:38:50.000Z"}],["meta",{"property":"article:tag","content":"Hashicorp"}],["meta",{"property":"article:tag","content":"Update"}],["meta",{"property":"article:tag","content":"Feb"}],["meta",{"property":"article:modified_time","content":"2024-02-14T07:38:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"2024년 2월\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-02-14T07:38:50.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Product 소개","slug":"product-소개","link":"#product-소개","children":[]},{"level":2,"title":"Product Update","slug":"product-update","link":"#product-update","children":[]}],"git":{"createdTime":1707291700000,"updatedTime":1707896330000,"contributors":[{"name":"najihun","email":"51940925+najihun@users.noreply.github.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.75,"words":226},"filePathRelative":"04-HashiCorp/08-Updates/97-2024/2024-02.md","localizedDate":"2024년 2월 7일","excerpt":"\\n

    Product 소개

    \\n"}');export{q as comp,J as data}; diff --git a/assets/3-vso-samples.html-BFhpQZdK.js b/assets/3-vso-samples.html-BFhpQZdK.js new file mode 100644 index 0000000000..b0c84dedbf --- /dev/null +++ b/assets/3-vso-samples.html-BFhpQZdK.js @@ -0,0 +1,465 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as i,c,b as n,d as s,a as e,e as t}from"./app-Bzk8Nrll.js";const o={},u=n("h1",{id:"vault-secrets-operator-예제실습",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#vault-secrets-operator-예제실습"},[n("span",null,"Vault Secrets Operator 예제실습")])],-1),r=n("br",null,null,-1),k={href:"https://github.com/hashicorp/vault-secrets-operator/issues",target:"_blank",rel:"noopener noreferrer"},d=t('

    본 문서는 HashiCorp 공식 GitHub의 Vault Secret Operator 저장소 에서 제공하는 코드를 활용하여 환경구성 및 샘플 애플리케이션 배포/연동에 대한 상세 분석을 제공한다.

    img
    img

    0. 사전 요구사항

    1) 패키지 및 바이너리 정보

    2) 저장소 복제

    ',6),v={href:"https://github.com/hashicorp/vault-secrets-operator",target:"_blank",rel:"noopener noreferrer"},m=t(`
    # 저장소 복제
    +$ git clone https://github.com/hashicorp/vault-secrets-operator.git
    +
    +# 작업 디렉토리 이동
    +$ cd vault-secrets-operator
    +

    1. K8s 환경구성 및 샘플배포

    `,2),b=n("br",null,null,-1),g={href:"https://gist.githubusercontent.com/hyungwook0221/85ec45d06a8c7643bcfe4afcd8843856/raw/b40096f6bbfad75636e3657f6c0827f8b7a0b436/vso-demo-1.sh",target:"_blank",rel:"noopener noreferrer"},h=t(`
    $ make setup-kind
    +

    vault-secrets-operator-control-plane 가 단일노드로 배포된 것을 확인할 수 있다.

    $ kubectl get nodes -o wide
    +NAME                                   STATUS   ROLES           AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
    +vault-secrets-operator-control-plane   Ready    control-plane   3m18s   v1.25.3   172.18.0.2    <none>        Ubuntu 22.04.1 LTS   5.15.49-linuxkit   containerd://1.6.9
    +
    +$ kubectl get pods -A
    +NAMESPACE            NAME                                                           READY   STATUS    RESTARTS   AGE
    +kube-system          coredns-565d847f94-42vpm                                       1/1     Running   0          3m5s
    +kube-system          coredns-565d847f94-6fsv9                                       1/1     Running   0          3m5s
    +kube-system          etcd-vault-secrets-operator-control-plane                      1/1     Running   0          3m18s
    +kube-system          kindnet-9j486                                                  1/1     Running   0          3m6s
    +kube-system          kube-apiserver-vault-secrets-operator-control-plane            1/1     Running   0          3m18s
    +kube-system          kube-controller-manager-vault-secrets-operator-control-plane   1/1     Running   0          3m18s
    +kube-system          kube-proxy-tfqc8                                               1/1     Running   0          3m6s
    +kube-system          kube-scheduler-vault-secrets-operator-control-plane            1/1     Running   0          3m17s
    +local-path-storage   local-path-provisioner-684f458cdd-2dzfn                        1/1     Running   0          3m5s
    +

    2. Vault 클러스터 배포

    `,6),y=n("br",null,null,-1),f={href:"https://gist.githubusercontent.com/hyungwook0221/3bf0603ca179f367492fefab9574595f/raw/53b33e501082193999c1f5dca9ed73c70507d42f/vso-demo-2.sh",target:"_blank",rel:"noopener noreferrer"},_=t(`

    앞서 생성된 KinD 클러스터 내부에 Vault 클러스터를 배포한다. 이때, 필요한 사전 환경을 Terraform 코드를 통해 자동으로 구성한다.

    make setup-integration-test
    +
    # Pod 확인
    +$ kubectl get pods -n vault
    +NAME      READY   STATUS    RESTARTS   AGE
    +vault-0   1/1     Running   0          73s
    +
    +# vault 상태확인
    +$ kubectl exec -n vault -it vault-0 -- vault status
    +Key             Value
    +---             -----
    +Seal Type       shamir
    +Initialized     true
    +Sealed          false
    +Total Shares    1
    +Threshold       1
    +Version         1.13.2
    +Build Date      2023-04-25T13:02:50Z
    +Storage Type    inmem
    +Cluster Name    vault-cluster-199af322
    +Cluster ID      23b647d5-f067-ba94-b359-2fca26af9ff9
    +HA Enabled      false
    +

    Terraform의 kubernetes, helm 프로바이더를 사용하여 다음과 같은 리소스를 자동으로 배포한다.

    `,5),q=n("br",null,null,-1),w={href:"https://raw.githubusercontent.com/hashicorp/vault-secrets-operator/main/test/integration/infra/main.tf",target:"_blank",rel:"noopener noreferrer"},x=t('

    3. Vault 설정

    ',2),P=n("br",null,null,-1),V={href:"https://raw.githubusercontent.com/hashicorp/vault-secrets-operator/main/config/samples/setup.sh",target:"_blank",rel:"noopener noreferrer"},S=n("br",null,null,-1),T={href:"https://gist.githubusercontent.com/hyungwook0221/1750fa348e95141018e83ed16835281f/raw/71c967722244b175cf1ceabba828b038cfe1bf8c/vso-demo-3.sh",target:"_blank",rel:"noopener noreferrer"},A=t(`

    setup.sh 스크립트를 실행하여 다음 3가지 시크릿 엔진에 대한 실습 환경을 구성한다.

    $ ./config/samples/setup.sh
    +
    img
    img

    1) Secret Engine

    (1) KV 시크릿엔진 활성화 : kvv1, kvv2

    KV 시크릿엔진 Version 1, Version2를 활성화 하고 샘플 데이터를 주입한다.

    vault secrets disable kvv2/
    +vault secrets enable -path=kvv2 kv-v2
    +vault kv put kvv2/secret username="db-readonly-username" password="db-secret-password"
    +
    +vault secrets disable kvv1/
    +vault secrets enable -path=kvv1 -version=1 kv
    +vault kv put kvv1/secret username="v1-user" password="v1-password"
    +
    img
    img
    img
    img

    (2) PKI 시크릿 엔진 활성화 : pki

    PKI 시크릿 엔진을 활성화하고 다음 설정을 진행한다.

    # PKI Secret 엔진 활성화
    +vault secrets disable pki
    +vault secrets enable pki
    +
    +# PKI 인증서 생성
    +vault write pki/root/generate/internal \\
    +    common_name=example.com \\
    +    ttl=768h
    +
    +# 설정
    +vault write pki/config/urls \\
    +    issuing_certificates="http://127.0.0.1:8200/v1/pki/ca" \\
    +    crl_distribution_points="http://127.0.0.1:8200/v1/pki/crl"
    +
    +# 역할구성
    +vault write pki/roles/default \\
    +    allowed_domains=example.com \\
    +    allowed_domains=localhost \\
    +    allow_subdomains=true \\
    +    max_ttl=72h
    +
    img
    img

    2) ACL Policy

    (1) 정책 정의 : demo

    각 시크릿 엔진에 대한 ACL Policy를 정의하기 위해 다음 hcl 을 작성하고 적용한다.

    # policy.hcl 작성
    +cat <<EOT > /tmp/policy.hcl
    +path "kvv2/*" {
    +  capabilities = ["read"]
    +}
    +path "kvv1/*" {
    +  capabilities = ["read"]
    +}
    +path "pki/*" {
    +  capabilities = ["read", "create", "update"]
    +}
    +EOT
    +
    +# demo 정책 생성
    +vault policy write demo /tmp/policy.hcl
    +

    vault policy write 명령으로 정책을 생성하고 확인한다.

    img
    img
    img
    img

    3) Auth Methods

    (1) 인증방식 정의: kubernetes

    Vault와 연동을 위해 kubernetes 인증방식을 설정한다.

    참고:
    Beta 버전에서는 Kubernetes 인증 방식만 제공

    # Kubernetes 인증방식 활성화
    +vault auth disable kubernetes
    +vault auth enable kubernetes
    +
    +vault write auth/kubernetes/config \\
    +    kubernetes_host=https://kubernetes.default.svc
    +
    +vault write auth/kubernetes/role/demo \\
    +    bound_service_account_names=default \\
    +    bound_service_account_namespaces=tenant-1,tenant-2 \\
    +    policies=demo \\
    +    ttl=1h
    +

    VSO에서는 현재 Kubernetes 인증 방식만을 제공하고 있으므로 Kubernetes 인증 방식을 통해 실습을 진행한다.

    img
    img

    kubernetes 인증방식 구성을 위해 Roles, Config를 정의한다.

    img
    img
    img
    img
    img
    img
    img
    img

    (참고) Entity 확인

    img
    img

    4) Kubernetes 네임스페이스 생성

    K8s 인증방식의 역할(Role)에서 사용할 네임스페이스 확인

    kubectl get ns | grep tenant
    +tenant-1                        Active   5h2m
    +tenant-2                        Active   5h2m
    +

    4. 오퍼레이터 빌드 및 배포

    `,48),K=n("br",null,null,-1),R={href:"https://gist.githubusercontent.com/hyungwook0221/37612418122e9154a497236c75bf3a5e/raw/9ce3a49318921f5928ec7bb96cd5149af3612713/vso-demo-4.sh",target:"_blank",rel:"noopener noreferrer"},C=t(`

    Vault 설정이 완료되었으므로 실제 Kubernetes Cluster에서 Operator를 배포한다.

    $ make build docker-build deploy-kind
    +

    1) 배포된 리소스 확인

    $ kubectl get pods -n vault-secrets-operator-system
    +NAME                                                         READY   STATUS    RESTARTS   AGE
    +vault-secrets-operator-controller-manager-6f8b6b8f49-5lt97   2/2     Running   0          3h59m
    +
    +$ k get crd -A
    +NAME                                        CREATED AT
    +vaultauths.secrets.hashicorp.com            2023-05-12T08:37:15Z
    +vaultconnections.secrets.hashicorp.com      2023-05-12T08:37:15Z
    +vaultdynamicsecrets.secrets.hashicorp.com   2023-05-12T08:37:15Z
    +vaultpkisecrets.secrets.hashicorp.com       2023-05-12T08:37:15Z
    +vaultstaticsecrets.secrets.hashicorp.com    2023-05-12T08:37:15Z
    +

    5. 샘플 K8s 리소스 배포

    $ kubectl apply -k config/samples
    +
    +secret/pki1 created
    +secret/secret1 created
    +secret/secret1 created
    +service/tls-app-service created
    +ingress.networking.k8s.io/tls-example-ingress created
    +vaultauth.secrets.hashicorp.com/vaultauth-sample created
    +vaultauth.secrets.hashicorp.com/vaultauth-sample created
    +vaultconnection.secrets.hashicorp.com/vaultconnection-sample created
    +vaultconnection.secrets.hashicorp.com/vaultconnection-sample created
    +vaultdynamicsecret.secrets.hashicorp.com/vaultdynamicsecret-sample created
    +vaultpkisecret.secrets.hashicorp.com/vaultpkisecret-sample-tenant-1 created
    +vaultpkisecret.secrets.hashicorp.com/vaultpkisecret-tls created
    +vaultstaticsecret.secrets.hashicorp.com/vaultstaticsecret-sample-tenant-1 created
    +vaultstaticsecret.secrets.hashicorp.com/vaultstaticsecret-sample-tenant-2 created
    +pod/app1 created
    +pod/tls-app created
    +pod/app1 created
    +
    $ kubectl get secrets -n tenant-1 secret1 -o yaml
    +$ kubectl get secrets -n tenant-1 pki1 -o yaml
    +$ kubectl get secrets -n tenant-2 secret1 -o yaml
    +

    1) 연결 및 인증방식 설정

    설명추가

    (1) VaultConnection 커스텀 리소스

    Vault Operator가 연결할 Vault Cluster 정보를 구성한다.

    `,12),I=n("code",null,".spec.address",-1),j={href:"http://vault.vault.svc.cluster.local:8200",target:"_blank",rel:"noopener noreferrer"},E=t(`
    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultConnection
    +metadata:
    +  labels:
    +    app.kubernetes.io/name: vaultconnection
    +    app.kubernetes.io/instance: vaultconnection-sample
    +    app.kubernetes.io/part-of: vault-secrets-operator
    +    app.kubernetes.io/managed-by: kustomize
    +    app.kubernetes.io/created-by: vault-secrets-operator
    +  name: vaultconnection-sample
    +  namespace: tenant-1
    +spec:
    +  address: http://vault.vault.svc.cluster.local:8200
    +---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultConnection
    +metadata:
    +  labels:
    +    app.kubernetes.io/name: vaultconnection
    +    app.kubernetes.io/instance: vaultconnection-sample
    +    app.kubernetes.io/part-of: vault-secrets-operator
    +    app.kubernetes.io/managed-by: kustomize
    +    app.kubernetes.io/created-by: vault-secrets-operator
    +  name: vaultconnection-sample
    +  namespace: tenant-2
    +spec:
    +  address: http://vault.vault.svc.cluster.local:8200
    +

    (2) VaultAuth 커스텀 리소스

    사전에 정의된 VaultConnection 을 통해 Operator가 Vault Server와 연결할 때, 어떤 인증방식을 사용할지 구성한다.

    참고 : Beta 버전에서는 K8s 인증방식만 제공

    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultAuth
    +metadata:
    +  labels:
    +    app.kubernetes.io/name: vaultauth
    +    app.kubernetes.io/instance: vaultauth-sample
    +    app.kubernetes.io/part-of: vault-secrets-operator
    +    app.kubernetes.io/managed-by: kustomize
    +    app.kubernetes.io/created-by: vault-secrets-operator
    +  name: vaultauth-sample
    +  namespace: tenant-1
    +spec:
    +  vaultConnectionRef: vaultconnection-sample
    +  method: kubernetes
    +  mount: kubernetes
    +  kubernetes:
    +    role: demo
    +    serviceAccount: default
    +---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultAuth
    +metadata:
    +  labels:
    +    app.kubernetes.io/name: vaultauth
    +    app.kubernetes.io/instance: vaultauth-sample
    +    app.kubernetes.io/part-of: vault-secrets-operator
    +    app.kubernetes.io/managed-by: kustomize
    +    app.kubernetes.io/created-by: vault-secrets-operator
    +  name: vaultauth-sample
    +  namespace: tenant-2
    +spec:
    +  vaultConnectionRef: vaultconnection-sample
    +  method: kubernetes
    +  mount: kubernetes
    +  kubernetes:
    +    role: demo
    +    serviceAccount: default
    +

    2) Vault CRD 예제

    VSO에서 제공하는 3가지 CRD를 사용하여 Kubernetes 오브젝트와 연동하여 사용하는 방법을 알아본다.

    (1) VaultPKISecret : Pod + PKI Secret

    다음은 PKI 인증서를 생성하고 Nginx 웹 서버에 적용하는 실습 예제이다. Nginx 파드를 생성할 때 secret 타입의 볼륨을 마운트한다.

    ---
    +apiVersion: v1
    +kind: Secret
    +metadata:
    +  name: pki1
    +  namespace: tenant-1
    +type: Opaque
    +---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultPKISecret
    +metadata:
    +  namespace: tenant-1
    +  name: vaultpkisecret-sample-tenant-1
    +spec:
    +  vaultAuthRef: vaultauth-sample
    +  namespace: tenant-1
    +  mount: pki
    +  name: default
    +  destination:
    +    name: pki1
    +  commonName: consul.example.com
    +  format: pem
    +  revoke: true
    +  clear: true
    +  expiryOffset: 5s
    +  ttl: 15s
    +
    ---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: app1
    +  namespace: tenant-1
    +spec:
    +  containers:
    +    - name: nginx
    +      image: nginx
    +      volumeMounts:
    +        - name: secrets
    +          mountPath: "/etc/secrets"
    +          readOnly: true
    +  volumes:
    +    - name: secrets
    +      secret:
    +        # created in Terraform
    +        secretName: pki1
    +        optional: false # default setting; "mysecret" must exist
    +

    실제 PKI 인증서가 정상적으로 생성되는 확인해본다.

    $ ls -lrt /etc/secrets
    +
    +total 0
    +lrwxrwxrwx 1 root root 20 May 14 08:33 serial_number -> ..data/serial_number
    +lrwxrwxrwx 1 root root 23 May 14 08:33 private_key_type -> ..data/private_key_type
    +lrwxrwxrwx 1 root root 18 May 14 08:33 private_key -> ..data/private_key
    +lrwxrwxrwx 1 root root 17 May 14 08:33 issuing_ca -> ..data/issuing_ca
    +lrwxrwxrwx 1 root root 17 May 14 08:33 expiration -> ..data/expiration
    +lrwxrwxrwx 1 root root 18 May 14 08:33 certificate -> ..data/certificate
    +lrwxrwxrwx 1 root root 15 May 14 08:33 ca_chain -> ..data/ca_chain
    +lrwxrwxrwx 1 root root 11 May 14 08:33 _raw -> ..data/_raw
    +

    본 실습에서는 실제 nginx 파드의 구성파일에 PKI 인증서를 적용하는 시나리오가 아닌 단순 파일생성 및 갱신해보았다.

    (2) VaultPKISecret 예제2 : Ingress + Pod + PKI Secret

    이번 실습에서는 앞서 확인한 PKI 인증서를 활용하여 K8s Ingress 오브젝트에 적용하고 주기적으로 교체되는 시나리오를 확인해본다.

    `,21),N={href:"https://github.com/hashicorp/vault-secrets-operator/tree/main#ingress-tls-with-vaultpkisecret",target:"_blank",rel:"noopener noreferrer"},M=t(`
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
    +
    +kubectl wait --namespace ingress-nginx \\
    +  --for=condition=ready pod \\
    +  --selector=app.kubernetes.io/component=controller \\
    +  --timeout=90s
    +
    ---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultPKISecret
    +metadata:
    +  name: vaultpkisecret-tls
    +  namespace: tenant-1
    +spec:
    +  vaultAuthRef: vaultauth-sample
    +  namespace: tenant-1
    +  mount: pki
    +  name: default
    +  destination:
    +    create: true
    +    name: pki-tls
    +    type: kubernetes.io/tls
    +  commonName: localhost
    +  format: pem
    +  revoke: true
    +  clear: true
    +  expiryOffset: 15s
    +  ttl: 1m
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: tls-app
    +  namespace: tenant-1
    +  labels:
    +    app: tls-app
    +spec:
    +  containers:
    +  - command:
    +    - /agnhost
    +    - netexec
    +    - --http-port
    +    - "8080"
    +    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    +    name: tls-app
    +---
    +kind: Service
    +apiVersion: v1
    +metadata:
    +  name: tls-app-service
    +  namespace: tenant-1
    +spec:
    +  selector:
    +    app: tls-app
    +  ports:
    +    - port: 443
    +      targetPort: 8080
    +---
    +apiVersion: networking.k8s.io/v1
    +kind: Ingress
    +metadata:
    +  name: tls-example-ingress
    +  namespace: tenant-1
    +  annotations:
    +    nginx.ingress.kubernetes.io/rewrite-target: /$2
    +spec:
    +  tls:
    +  - hosts:
    +    - localhost
    +    secretName: pki-tls
    +  rules:
    +  - host: localhost
    +    http:
    +      paths:
    +      - path: /tls-app(/|$)(.*)
    +        pathType: Prefix
    +        backend:
    +          service:
    +            name: tls-app-service
    +            port:
    +              number: 443
    +
    $ curl -k https://localhost:38443/tls-app/hostname
    +tls-app%
    +$ curl -kvI https://localhost:38443/tls-app/hostname
    +*   Trying 127.0.0.1:38443...
    +* Connected to localhost (127.0.0.1) port 38443 (#0)
    +# 중략
    +* Server certificate:
    +*  subject: CN=localhost
    +*  start date: May 14 08:04:00 2023 GMT
    +*  expire date: May 14 08:05:30 2023 GMT
    +*  issuer: CN=example.com
    +
    kubectl logs -f -n ingress-nginx -l app.kubernetes.io/instance=ingress-nginx
    +W0514 07:51:58.673604       1 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
    +{"level":"info","msg":"patching webhook configurations 'ingress-nginx-admission' mutating=false, validating=true, failurePolicy=Fail","source":"k8s/k8s.go:118","time":"2023-05-14T07:51:58Z"}
    +{"level":"info","msg":"Patched hook(s)","source":"k8s/k8s.go:138","time":"2023-05-14T07:51:58Z"}
    +I0514 08:19:30.110926       9 store.go:619] "secret was updated and it is used in ingress annotations. Parsing" secret="tenant-1/pki-tls"
    +I0514 08:19:30.113988       9 backend_ssl.go:59] "Updating secret in local store" name="tenant-1/pki-tls"
    +W0514 08:19:30.114178       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:20:30 +0000 UTC)
    +I0514 08:20:15.208102       9 store.go:619] "secret was updated and it is used in ingress annotations. Parsing" secret="tenant-1/pki-tls"
    +I0514 08:20:15.208539       9 backend_ssl.go:59] "Updating secret in local store" name="tenant-1/pki-tls"
    +W0514 08:20:15.208801       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:21:15 +0000 UTC)
    +W0514 08:20:18.543113       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:21:15 +0000 UTC)
    +I0514 08:21:00.107794       9 store.go:619] "secret was updated and it is used in ingress annotations. Parsing" secret="tenant-1/pki-tls"
    +I0514 08:21:00.108127       9 backend_ssl.go:59] "Updating secret in local store" name="tenant-1/pki-tls"
    +W0514 08:21:00.108295       9 controller.go:1406] SSL certificate for server "localhost" is about to expire (2023-05-14 08:22:00 +0000 UTC)
    +W0514 07:51:58.418022       1 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
    +{"err":"secrets \\"ingress-nginx-admission\\" not found","level":"info","msg":"no secret found","source":"k8s/k8s.go:229","time":"2023-05-14T07:51:58Z"}
    +{"level":"info","msg":"creating new secret","source":"cmd/create.go:28","time":"2023-05-14T07:51:58Z"}
    +
    img
    img
    img
    img
    img
    img
    img
    img

    (3) VaultStaticSecret 예제 :

    ---
    +apiVersion: v1
    +kind: Secret
    +metadata:
    +  name: secret1
    +  namespace: tenant-1
    +type: Opaque
    +---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultStaticSecret
    +metadata:
    +  namespace: tenant-1
    +  name: vaultstaticsecret-sample-tenant-1
    +spec:
    +  # namespace: cluster1/tenant-1
    +  vaultAuthRef: vaultauth-sample
    +  mount: kvv2
    +  type: kv-v2
    +  name: secret
    +  refreshAfter: 5s
    +  destination:
    +    name: secret1
    +---
    +apiVersion: v1
    +kind: Secret
    +metadata:
    +  name: secret1
    +  namespace: tenant-2
    +type: Opaque
    +---
    +apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultStaticSecret
    +metadata:
    +  namespace: tenant-2
    +  name: vaultstaticsecret-sample-tenant-2
    +spec:
    +  # namespace: cluster1/tenant-2
    +  vaultAuthRef: vaultauth-sample
    +  mount: kvv1
    +  type: kv-v1
    +  name: secret
    +  refreshAfter: 5s
    +  destination:
    +    name: secret1
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: app1
    +  namespace: tenant-1
    +spec:
    +  containers:
    +  - name: nginx
    +    image: nginx
    +    volumeMounts:
    +    - name: secrets
    +      mountPath: "/etc/secrets"
    +      readOnly: true
    +  volumes:
    +  - name: secrets
    +    secret:
    +      secretName: secret1
    +      optional: false # default setting; "mysecret" must exist
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: app1
    +  namespace: tenant-2
    +spec:
    +  containers:
    +  - name: nginx
    +    image: nginx
    +    volumeMounts:
    +    - name: secrets
    +      mountPath: "/etc/secrets"
    +      readOnly: true
    +  volumes:
    +  - name: secrets
    +    secret:
    +      secretName: secret1
    +      optional: false # default setting; "mysecret" must exist
    +

    (5) VaultDynamicSecret

    🔥 업데이트 예정

    apiVersion: secrets.hashicorp.com/v1alpha1
    +kind: VaultDynamicSecret
    +metadata:
    +  labels:
    +    app.kubernetes.io/name: vaultdynamicsecret
    +    app.kubernetes.io/instance: vaultdynamicsecret-sample
    +    app.kubernetes.io/part-of: vault-secrets-operator
    +    app.kubernetes.io/managed-by: kustomize
    +    app.kubernetes.io/created-by: vault-secrets-operator
    +  name: vaultdynamicsecret-sample
    +spec:
    +  # TODO(user): Add fields here
    +

    6. 리소스 삭제

    샘플 삭제:

    # K8s 리소스 삭제
    +$ kubectl delete -k config/samples
    +
    +# kind 클러스터 삭제
    +$ kind delete clusters vault-secrets-operator
    +

    2. [테라폼 기반 데모](

    `,24);function O(D,z){const a=p("ExternalLinkIcon");return i(),c("div",null,[u,n("blockquote",null,[n("p",null,[s("📌 참고:"),r,s(" 현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *"),n("a",k,[s("here"),e(a)]),s("*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.")])]),d,n("p",null,[s("실습을 위해 "),n("a",v,[s("vault-secrets-operator"),e(a)]),s(" 저장소를 복제한다.")]),m,n("blockquote",null,[n("p",null,[s("📌 참고:"),b,s(" 실행결과 : "),n("a",g,[s("vso-demo-1.sh"),e(a)])])]),h,n("blockquote",null,[n("p",null,[s("📌 참고"),y,s(" 실행결과 : "),n("a",f,[s("vso-demo-2.sh"),e(a)])])]),_,n("blockquote",null,[n("p",null,[s("📌 참고 :"),q,s(" 원본코드 : "),n("a",w,[s("main.tf"),e(a)])])]),x,n("blockquote",null,[n("p",null,[s("📌 참고:"),P,s(" 원본소스 : "),n("a",V,[s("setup.sh"),e(a)]),S,s(" 실행결과 : "),n("a",T,[s("vso-demo-3.sh"),e(a)])])]),A,n("blockquote",null,[n("p",null,[s("참고"),K,n("a",R,[s("vso-demo-4.sh"),e(a)])])]),C,n("ul",null,[n("li",null,[I,s(" : "),n("a",j,[s("http://vault.vault.svc.cluster.local:8200"),e(a)])])]),E,n("blockquote",null,[n("p",null,[s("참고 : Ingress 실습을 위해서는 Nginx Ingress Controller를 설치 후 진행해야 한다. ["),n("a",N,[s("참고"),e(a)]),s("]")])]),M])}const U=l(o,[["render",O],["__file","3-vso-samples.html.vue"]]),H=JSON.parse('{"path":"/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html","title":"Vault Secrets Operator 예제실습","lang":"ko-KR","frontmatter":{"description":"Vault Secret Operator 저장소에서 제공하는 코드를 활용하여 환경구성 및 샘플 애플리케이션 배포/연동에 대한 상세 분석을 제공한다.","tag":["vault","operator"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Vault Secrets Operator 예제실습"}],["meta",{"property":"og:description","content":"Vault Secret Operator 저장소에서 제공하는 코드를 활용하여 환경구성 및 샘플 애플리케이션 배포/연동에 대한 상세 분석을 제공한다."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/vso_repo.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Vault Secrets Operator 예제실습"}],["meta",{"property":"article:tag","content":"vault"}],["meta",{"property":"article:tag","content":"operator"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vault Secrets Operator 예제실습\\",\\"image\\":[\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/vso_repo.png\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/H7fG1P.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/fLg3mc.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/S7cDQl.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/6Ht11e.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/P7EuMl.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/jjaKW7.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/447eDQ.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/Jvvuyn.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/gd4mOV.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/7wjcUA.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/m5o4il.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/QlpQMb.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/sD28Mg.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/8bhJgt.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/MicUWH.jpg\\",\\"https://raw.githubusercontent.com/hyungwook0221/img/main/uPic/ljNFGC.jpg\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"0. 사전 요구사항","slug":"_0-사전-요구사항","link":"#_0-사전-요구사항","children":[{"level":3,"title":"1) 패키지 및 바이너리 정보","slug":"_1-패키지-및-바이너리-정보","link":"#_1-패키지-및-바이너리-정보","children":[]},{"level":3,"title":"2) 저장소 복제","slug":"_2-저장소-복제","link":"#_2-저장소-복제","children":[]}]},{"level":2,"title":"1. K8s 환경구성 및 샘플배포","slug":"_1-k8s-환경구성-및-샘플배포","link":"#_1-k8s-환경구성-및-샘플배포","children":[]},{"level":2,"title":"2. Vault 클러스터 배포","slug":"_2-vault-클러스터-배포","link":"#_2-vault-클러스터-배포","children":[]},{"level":2,"title":"3. Vault 설정","slug":"_3-vault-설정","link":"#_3-vault-설정","children":[{"level":3,"title":"1) Secret Engine","slug":"_1-secret-engine","link":"#_1-secret-engine","children":[]},{"level":3,"title":"2) ACL Policy","slug":"_2-acl-policy","link":"#_2-acl-policy","children":[]},{"level":3,"title":"3) Auth Methods","slug":"_3-auth-methods","link":"#_3-auth-methods","children":[]},{"level":3,"title":"4) Kubernetes 네임스페이스 생성","slug":"_4-kubernetes-네임스페이스-생성","link":"#_4-kubernetes-네임스페이스-생성","children":[]}]},{"level":2,"title":"4. 오퍼레이터 빌드 및 배포","slug":"_4-오퍼레이터-빌드-및-배포","link":"#_4-오퍼레이터-빌드-및-배포","children":[{"level":3,"title":"1) 배포된 리소스 확인","slug":"_1-배포된-리소스-확인","link":"#_1-배포된-리소스-확인","children":[]}]},{"level":2,"title":"5. 샘플 K8s 리소스 배포","slug":"_5-샘플-k8s-리소스-배포","link":"#_5-샘플-k8s-리소스-배포","children":[{"level":3,"title":"1) 연결 및 인증방식 설정","slug":"_1-연결-및-인증방식-설정","link":"#_1-연결-및-인증방식-설정","children":[]},{"level":3,"title":"2) Vault CRD 예제","slug":"_2-vault-crd-예제","link":"#_2-vault-crd-예제","children":[]}]},{"level":2,"title":"6. 리소스 삭제","slug":"_6-리소스-삭제","link":"#_6-리소스-삭제","children":[]},{"level":2,"title":"2. [테라폼 기반 데모](","slug":"_2-테라폼-기반-데모","link":"#_2-테라폼-기반-데모","children":[]}],"git":{"createdTime":1684599614000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Hyungwook Yu","email":"40632767+chosam2@users.noreply.github.com","commits":1}]},"readingTime":{"minutes":6.82,"words":2045},"filePathRelative":"04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.md","localizedDate":"2023년 5월 21일","excerpt":"\\n
    \\n

    📌 참고:
    \\n현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *here*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

    \\n
    \\n

    본 문서는 HashiCorp 공식 GitHub의 Vault Secret Operator 저장소 에서 제공하는 코드를 활용하여 환경구성 및 샘플 애플리케이션 배포/연동에 대한 상세 분석을 제공한다.

    "}');export{U as comp,H as data}; diff --git a/assets/400-error.html-De4cfBHo.js b/assets/400-error.html-De4cfBHo.js new file mode 100644 index 0000000000..5bedaa50fe --- /dev/null +++ b/assets/400-error.html-De4cfBHo.js @@ -0,0 +1,45 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as e,c as p,b as n,d as s,a as r,e as c}from"./app-Bzk8Nrll.js";const l={},i=n("h1",{id:"vault-400-error",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#vault-400-error"},[n("span",null,"Vault 400 Error")])],-1),u={href:"https://www.vaultproject.io/api#http-status-codes",target:"_blank",rel:"noopener noreferrer"},d=c(`

    Vault에 API 요청시 400에러가 발생하는 경우 Vault로 전달된 데이터 형태가 올바른지 확인이 필요하다.

    예를들어 아래와 같이 Transit의 복호화 요청을 하는 경우 데이터가 비어있다면 응답과 Audit로그에서 400 에러관련 메세지를 확인할 수 있다.

    curl \\
    +  -H "X-Vault-Token: s.HeeRXjkW1KJhF8ofQsglI9yw" \\
    +  -X POST \\
    +  -d "{}" \\
    +  http://192.168.60.103:8200/v1/transit/decrypt/my-key
    +
    {
    +  "time": "2022-03-04T08:02:37.596190958Z",
    +  "type": "response",
    +  "auth": {
    +    "client_token": "hmac-sha256:17bc16e3346dd6c398646cb7da8e0bd71ae720f608a8c447b8942b8283388600",
    +    "accessor": "hmac-sha256:798bb09d10dc2ac18533acb3d049c4185af3328fbc88fedd23081f63caa13b44",
    +    "display_name": "root",
    +    "policies": [
    +      "root"
    +    ],
    +    "token_policies": [
    +      "root"
    +    ],
    +    "token_type": "service",
    +    "token_issue_time": "2021-09-15T13:45:47+09:00"
    +  },
    +  "request": {
    +    "id": "c39906c5-f48d-c177-37c2-4c1635db78e7",
    +    "operation": "update",
    +    "mount_type": "transit",
    +    "client_token": "hmac-sha256:17bc16e3346dd6c398646cb7da8e0bd71ae720f608a8c447b8942b8283388600",
    +    "client_token_accessor": "hmac-sha256:798bb09d10dc2ac18533acb3d049c4185af3328fbc88fedd23081f63caa13b44",
    +    "namespace": {
    +      "id": "root"
    +    },
    +    "path": "kbhealth-transit/prod/decrypt/aes256",
    +    "data": {
    +      "ciphertext": "hmac-sha256:34c2966e2ef36e2dcdb24f05fd4442b8f85c0d2fbf0887977636c7592e2cef3b"
    +    },
    +    "remote_address": "10.100.0.85"
    +  },
    +  "response": {
    +    "mount_type": "transit",
    +    "data": {
    +      "error": "hmac-sha256:bf7d730e400653f79b134c3bdb593f8220f6f1588a26048a6e1272a01ad47384"
    +    }
    +  },
    +  "error": "1 error occurred:\\n\\t* invalid request\\n\\n"
    +}
    +
    `,6);function k(m,q){const a=o("ExternalLinkIcon");return e(),p("div",null,[i,n("blockquote",null,[n("p",null,[s("Vault HTTP Status Codes : "),n("a",u,[s("https://www.vaultproject.io/api#http-status-codes"),r(a)])])]),d])}const h=t(l,[["render",k],["__file","400-error.html.vue"]]),g=JSON.parse('{"path":"/04-HashiCorp/06-Vault/05-TroubleShooting/400-error.html","title":"Vault 400 Error","lang":"ko-KR","frontmatter":{"description":"Vault 400 Error 발생원인 및 확인","tag":["vault","error","400"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/06-Vault/05-TroubleShooting/400-error.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Vault 400 Error"}],["meta",{"property":"og:description","content":"Vault 400 Error 발생원인 및 확인"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"vault"}],["meta",{"property":"article:tag","content":"error"}],["meta",{"property":"article:tag","content":"400"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vault 400 Error\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1647319815000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.46,"words":139},"filePathRelative":"04-HashiCorp/06-Vault/05-TroubleShooting/400-error.md","localizedDate":"2022년 3월 15일","excerpt":"\\n
    \\n

    Vault HTTP Status Codes : https://www.vaultproject.io/api#http-status-codes

    \\n
    \\n

    Vault에 API 요청시 400에러가 발생하는 경우 Vault로 전달된 데이터 형태가 올바른지 확인이 필요하다.

    \\n"}');export{h as comp,g as data}; diff --git a/assets/404.html-32aGe-oF.js b/assets/404.html-32aGe-oF.js new file mode 100644 index 0000000000..c1b9ef7d91 --- /dev/null +++ b/assets/404.html-32aGe-oF.js @@ -0,0 +1 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,b as n}from"./app-Bzk8Nrll.js";const r={},a=n("p",null,"404 Not Found",-1),c=[a];function p(s,i){return e(),o("div",null,c)}const d=t(r,[["render",p],["__file","404.html.vue"]]),u=JSON.parse('{"path":"/404.html","title":"","lang":"ko-KR","frontmatter":{"layout":"NotFound","description":"404 Not Found ","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/404.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:description","content":"404 Not Found "}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"ko-KR"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found \\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"

    404 Not Found

    \\n","autoDesc":true}');export{d as comp,u as data}; diff --git a/assets/AlibabaCloud.html-Ddvy_KKt.js b/assets/AlibabaCloud.html-Ddvy_KKt.js new file mode 100644 index 0000000000..66647aa135 --- /dev/null +++ b/assets/AlibabaCloud.html-Ddvy_KKt.js @@ -0,0 +1,93 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c as o,b as n,d as s,a as l,e as c}from"./app-Bzk8Nrll.js";const i={},r=n("h1",{id:"alibaba-cloud-packer-sample",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#alibaba-cloud-packer-sample"},[n("span",null,"Alibaba Cloud Packer Sample")])],-1),u=n("h2",{id:"packer-pkr-hcl",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#packer-pkr-hcl"},[n("span",null,"packer.pkr.hcl")])],-1),d=n("code",null,"vault()",-1),k={href:"https://www.packer.io/docs/templates/hcl_templates/functions/contextual/vault",target:"_blank",rel:"noopener noreferrer"},m=c(`
    # packer build -force .
    +
    +locals {
    +  access_key = vault("/kv-v2/data/alicloud", "access_key")
    +  secret_key = vault("/kv-v2/data/alicloud", "secret_key")
    +}
    +
    +variable "region" {
    +  default     = "ap-southeast-1"
    +  description = "https://www.alibabacloud.com/help/doc-detail/40654.htm"
    +}
    +
    +source "alicloud-ecs" "basic-example" {
    +  access_key           = local.access_key
    +  secret_key           = local.secret_key
    +  region               = var.region
    +  image_name           = "ssh_otp_image_1_5"
    +  source_image         = "centos_7_9_x64_20G_alibase_20210623.vhd"
    +  ssh_username         = "root"
    +  instance_type        = "ecs.n1.tiny"
    +  io_optimized         = true
    +  internet_charge_type = "PayByTraffic"
    +  image_force_delete   = true
    +}
    +
    +build {
    +  sources = ["sources.alicloud-ecs.basic-example"]
    +
    +  provisioner "file" {
    +    source      = "./files/"
    +    destination = "/tmp"
    +  }
    +
    +# Vault OTP
    +  provisioner "shell" {
    +    inline = [
    +      "cp /tmp/sshd /etc/pam.d/sshd",
    +      "cp /tmp/sshd_config /etc/ssh/sshd_config",
    +      "mkdir -p /etc/vault.d",
    +      "cp /tmp/vault.hcl /etc/vault.d/vault.hcl",
    +      "cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper",
    +      "/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev",
    +      "sudo adduser test",
    +      "echo password | passwd --stdin test",
    +      "echo 'test ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers",
    +      "sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config"
    +    ]
    +  }
    +
    +# Apache
    +  provisioner "shell" {
    +    inline = [
    +      "sudo yum -y update",
    +      "sleep 15",
    +      "sudo yum -y update",
    +      "sudo yum -y install httpd",
    +      "sudo systemctl enable httpd",
    +      "sudo systemctl start httpd",
    +      "chmod +x /tmp/deploy_app.sh",
    +      "PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh",
    +      # "sudo firewall-cmd --zone=public --permanent --add-port=80/tcp",
    +      # "sudo firewall-cmd --reload",
    +    ]
    +  }
    +}
    +
    +variable "placeholder" {
    +  default     = "placekitten.com"
    +  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
    +}
    +

    deploy_app.sh (option)

    #!/bin/bash
    +# Script to deploy a very simple web application.
    +# The web app has a customizable image and some text.
    +
    +cat << EOM > /var/www/html/index.html
    +<html>
    +  <head><title>Meow!</title></head>
    +  <body>
    +  <div style="width:800px;margin: 0 auto">
    +
    +  <!-- BEGIN -->
    +  <center><img src="http://\${PLACEHOLDER}/\${WIDTH}/\${HEIGHT}"></img></center>
    +  <center><h2>Meow World!</h2></center>
    +  Welcome to \${PREFIX}'s app. Replace this text with your own.
    +  <!-- END -->
    +
    +  </div>
    +  </body>
    +</html>
    +EOM
    +
    +echo "Script complete."
    +
    `,3);function v(b,g){const a=e("ExternalLinkIcon");return p(),o("div",null,[r,u,n("ul",null,[n("li",null,[d,s("는 vault 연동시 사용가능 : "),n("a",k,[s("https://www.packer.io/docs/templates/hcl_templates/functions/contextual/vault"),l(a)])])]),m])}const _=t(i,[["render",v],["__file","AlibabaCloud.html.vue"]]),q=JSON.parse(`{"path":"/04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.html","title":"Alibaba Cloud Packer Sample","lang":"ko-KR","frontmatter":{"description":"Packer Sample","tag":["Packer","Sample","Alibaba"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Alibaba Cloud Packer Sample"}],["meta",{"property":"og:description","content":"Packer Sample"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Packer"}],["meta",{"property":"article:tag","content":"Sample"}],["meta",{"property":"article:tag","content":"Alibaba"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Alibaba Cloud Packer Sample\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"packer.pkr.hcl","slug":"packer-pkr-hcl","link":"#packer-pkr-hcl","children":[]},{"level":2,"title":"deploy_app.sh (option)","slug":"deploy-app-sh-option","link":"#deploy-app-sh-option","children":[]}],"git":{"createdTime":1632808034000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.96,"words":289},"filePathRelative":"04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.md","localizedDate":"2021년 9월 28일","excerpt":"\\n

    packer.pkr.hcl

    \\n\\n
    # packer build -force .\\n\\nlocals {\\n  access_key = vault(\\"/kv-v2/data/alicloud\\", \\"access_key\\")\\n  secret_key = vault(\\"/kv-v2/data/alicloud\\", \\"secret_key\\")\\n}\\n\\nvariable \\"region\\" {\\n  default     = \\"ap-southeast-1\\"\\n  description = \\"https://www.alibabacloud.com/help/doc-detail/40654.htm\\"\\n}\\n\\nsource \\"alicloud-ecs\\" \\"basic-example\\" {\\n  access_key           = local.access_key\\n  secret_key           = local.secret_key\\n  region               = var.region\\n  image_name           = \\"ssh_otp_image_1_5\\"\\n  source_image         = \\"centos_7_9_x64_20G_alibase_20210623.vhd\\"\\n  ssh_username         = \\"root\\"\\n  instance_type        = \\"ecs.n1.tiny\\"\\n  io_optimized         = true\\n  internet_charge_type = \\"PayByTraffic\\"\\n  image_force_delete   = true\\n}\\n\\nbuild {\\n  sources = [\\"sources.alicloud-ecs.basic-example\\"]\\n\\n  provisioner \\"file\\" {\\n    source      = \\"./files/\\"\\n    destination = \\"/tmp\\"\\n  }\\n\\n# Vault OTP\\n  provisioner \\"shell\\" {\\n    inline = [\\n      \\"cp /tmp/sshd /etc/pam.d/sshd\\",\\n      \\"cp /tmp/sshd_config /etc/ssh/sshd_config\\",\\n      \\"mkdir -p /etc/vault.d\\",\\n      \\"cp /tmp/vault.hcl /etc/vault.d/vault.hcl\\",\\n      \\"cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper\\",\\n      \\"/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev\\",\\n      \\"sudo adduser test\\",\\n      \\"echo password | passwd --stdin test\\",\\n      \\"echo 'test ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers\\",\\n      \\"sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config\\"\\n    ]\\n  }\\n\\n# Apache\\n  provisioner \\"shell\\" {\\n    inline = [\\n      \\"sudo yum -y update\\",\\n      \\"sleep 15\\",\\n      \\"sudo yum -y update\\",\\n      \\"sudo yum -y install httpd\\",\\n      \\"sudo systemctl enable httpd\\",\\n      \\"sudo systemctl start httpd\\",\\n      \\"chmod +x /tmp/deploy_app.sh\\",\\n      \\"PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh\\",\\n      # \\"sudo firewall-cmd --zone=public --permanent --add-port=80/tcp\\",\\n      # \\"sudo firewall-cmd --reload\\",\\n    ]\\n  }\\n}\\n\\nvariable \\"placeholder\\" {\\n  default     = \\"placekitten.com\\"\\n  description = \\"Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net\\"\\n}\\n
    "}`);export{_ as comp,q as data}; diff --git a/assets/Azure.html-DPMhwAjI.js b/assets/Azure.html-DPMhwAjI.js new file mode 100644 index 0000000000..b55da3eea2 --- /dev/null +++ b/assets/Azure.html-DPMhwAjI.js @@ -0,0 +1,131 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c as o,b as n,d as s,a as l,e as c}from"./app-Bzk8Nrll.js";const i={},r=n("h1",{id:"azure-packer-sample",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#azure-packer-sample"},[n("span",null,"Azure Packer Sample")])],-1),u=n("h2",{id:"packer-pkr-hcl",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#packer-pkr-hcl"},[n("span",null,"packer.pkr.hcl")])],-1),d=n("code",null,"vault()",-1),k={href:"https://www.packer.io/docs/templates/hcl_templates/functions/contextual/vault",target:"_blank",rel:"noopener noreferrer"},m=c(`
    # packer init -upgrade .
    +# packer build -force .
    +
    +locals {
    +  client_id = vault("/kv/data/azure", "client_id")
    +  client_secret = vault("/kv/data/azure", "client_secret")
    +  tenant_id = vault("/kv/data/azure", "tenant_id")
    +  subscription_id = vault("/kv/data/azure", "subscription_id")
    +  resource_group_name = var.resource_name
    +  virtual_network_name = "kbid-d-krc-vnet-002"
    +  virtual_network_subnet_name  = "d-mgmt-snet-001"
    +  virtual_network_resource_group_name  = "kbid-d-krc-mgmt-rg"
    +  timestamp = formatdate("YYYYMMDD_hhmmss", timeadd(timestamp(), "9h")) #생성되는 이미지 이름을 time 기반으로 생성
    +}
    +
    +variable "placeholder" {
    +  default     = "placekitten.com"
    +  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
    +}
    +
    +# Basic example : https://www.packer.io/docs/builders/azure/arm#basic-example
    +# MS Guide : https://docs.microsoft.com/ko-kr/azure/virtual-machines/linux/build-image-with-packer
    +source "azure-arm" "basic-example" {
    +  client_id = local.client_id
    +  client_secret = local.client_secret
    +  subscription_id = local.subscription_id
    +  tenant_id = local.tenant_id
    +
    +  # shared_image_gallery {
    +  #   subscription = local.subscription_id
    +  #   resource_group = "myrg"
    +  #   gallery_name = "GalleryName"
    +  #   image_name = "gs_pkr_\${local.timestamp}"
    +  #   image_version = "1.0.0"
    +  # }
    +  managed_image_resource_group_name = local.resource_group_name
    +  managed_image_name = "${var.image_name}-${local.timestamp}"
    +
    +  os_type = "Linux"
    +  # az vm image list-publishers --location koreacentral --output table
    +  image_publisher = "RedHat"
    +  # az vm image list-offers --location koreacentral --publisher RedHat --output table
    +  image_offer = "RHEL"
    +  # az vm image list-skus --location koreacentral --publisher RedHat --offer RHEL --output table
    +  image_sku = "8_4"
    +
    +  azure_tags = {
    +    dept = "KBHC Terraform POC"
    +  }
    +  
    +  # az vm list-skus --location koreacentral --all --output table
    +  build_resource_group_name = local.resource_group_name
    +
    +  #########################################
    +  # 기존 생성되어있는 network 를 사용하기 위한 항목 #
    +  #########################################
    +  virtual_network_name = local.virtual_network_name
    +  virtual_network_subnet_name = local.virtual_network_subnet_name
    +  virtual_network_resource_group_name = local.virtual_network_resource_group_name
    +  
    +  # location = "koreacentral"
    +  vm_size = "Standard_A2_v2"
    +}
    +
    +build {
    +  sources = ["sources.azure-arm.basic-example"]
    +
    +  provisioner "file" {
    +    source      = "./files/"
    +    destination = "/tmp"
    +  }
    +
    +# Vault OTP
    +  provisioner "shell" {
    +    inline = [
    +      "sudo cp /tmp/sshd /etc/pam.d/sshd",
    +      "sudo cp /tmp/sshd_config /etc/ssh/sshd_config",
    +      "sudo mkdir -p /etc/vault.d",
    +      "sudo cp /tmp/vault.hcl /etc/vault.d/vault.hcl",
    +      "sudo cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper",
    +      "echo \\"=== Vault_Check ===\\"",
    +      "curl http://10.0.9.10:8200",
    +      "/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev",
    +      "echo \\"=== Add User ===\\"",
    +      "sudo adduser jboss",
    +      "echo password | sudo passwd --stdin jboss",
    +      "echo 'jboss ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /etc/sudoers",
    +      "echo \\"=== SELINUX DISABLE ===\\"",
    +      "sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config"
    +    ]
    +  }
    +
    +# Apache
    +  provisioner "shell" {
    +    inline = [
    +      "sudo yum -y update",
    +      "sleep 15",
    +      "sudo yum -y update",
    +      "sudo yum -y install httpd",
    +      "sudo systemctl enable httpd",
    +      "sudo systemctl start httpd",
    +      "chmod +x /tmp/deploy_app.sh",
    +      "sudo PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh",
    +      "sudo firewall-cmd --zone=public --permanent --add-port=80/tcp",
    +      "sudo firewall-cmd --reload",
    +    ]
    +  }
    +}
    +

    deploy_app.sh (option)

    #!/bin/bash
    +# Script to deploy a very simple web application.
    +# The web app has a customizable image and some text.
    +
    +cat << EOM > /var/www/html/index.html
    +<html>
    +  <head><title>Meow!</title></head>
    +  <body>
    +  <div style="width:800px;margin: 0 auto">
    +
    +  <!-- BEGIN -->
    +  <center><img src="http://\${PLACEHOLDER}/\${WIDTH}/\${HEIGHT}"></img></center>
    +  <center><h2>Meow World!</h2></center>
    +  Welcome to \${PREFIX}'s app. Replace this text with your own.
    +  <!-- END -->
    +
    +  </div>
    +  </body>
    +</html>
    +EOM
    +
    +echo "Script complete."
    +
    `,3);function v(g,b){const a=e("ExternalLinkIcon");return p(),o("div",null,[r,u,n("ul",null,[n("li",null,[d,s("는 vault 연동시 사용가능 : "),n("a",k,[s("https://www.packer.io/docs/templates/hcl_templates/functions/contextual/vault"),l(a)])])]),m])}const y=t(i,[["render",v],["__file","Azure.html.vue"]]),q=JSON.parse(`{"path":"/04-HashiCorp/01-Packer/05-SamplePkr/Azure.html","title":"Azure Packer Sample","lang":"ko-KR","frontmatter":{"description":"Packer Sample","tag":["Packer","Sample","Azure"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/01-Packer/05-SamplePkr/Azure.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Azure Packer Sample"}],["meta",{"property":"og:description","content":"Packer Sample"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Packer"}],["meta",{"property":"article:tag","content":"Sample"}],["meta",{"property":"article:tag","content":"Azure"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Azure Packer Sample\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"packer.pkr.hcl","slug":"packer-pkr-hcl","link":"#packer-pkr-hcl","children":[]},{"level":2,"title":"deploy_app.sh (option)","slug":"deploy-app-sh-option","link":"#deploy-app-sh-option","children":[]}],"git":{"createdTime":1632808034000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.41,"words":424},"filePathRelative":"04-HashiCorp/01-Packer/05-SamplePkr/Azure.md","localizedDate":"2021년 9월 28일","excerpt":"\\n

    packer.pkr.hcl

    \\n\\n
    # packer init -upgrade .\\n# packer build -force .\\n\\nlocals {\\n  client_id = vault(\\"/kv/data/azure\\", \\"client_id\\")\\n  client_secret = vault(\\"/kv/data/azure\\", \\"client_secret\\")\\n  tenant_id = vault(\\"/kv/data/azure\\", \\"tenant_id\\")\\n  subscription_id = vault(\\"/kv/data/azure\\", \\"subscription_id\\")\\n  resource_group_name = var.resource_name\\n  virtual_network_name = \\"kbid-d-krc-vnet-002\\"\\n  virtual_network_subnet_name  = \\"d-mgmt-snet-001\\"\\n  virtual_network_resource_group_name  = \\"kbid-d-krc-mgmt-rg\\"\\n  timestamp = formatdate(\\"YYYYMMDD_hhmmss\\", timeadd(timestamp(), \\"9h\\")) #생성되는 이미지 이름을 time 기반으로 생성\\n}\\n\\nvariable \\"placeholder\\" {\\n  default     = \\"placekitten.com\\"\\n  description = \\"Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net\\"\\n}\\n\\n# Basic example : https://www.packer.io/docs/builders/azure/arm#basic-example\\n# MS Guide : https://docs.microsoft.com/ko-kr/azure/virtual-machines/linux/build-image-with-packer\\nsource \\"azure-arm\\" \\"basic-example\\" {\\n  client_id = local.client_id\\n  client_secret = local.client_secret\\n  subscription_id = local.subscription_id\\n  tenant_id = local.tenant_id\\n\\n  # shared_image_gallery {\\n  #   subscription = local.subscription_id\\n  #   resource_group = \\"myrg\\"\\n  #   gallery_name = \\"GalleryName\\"\\n  #   image_name = \\"gs_pkr_\${local.timestamp}\\"\\n  #   image_version = \\"1.0.0\\"\\n  # }\\n  managed_image_resource_group_name = local.resource_group_name\\n  managed_image_name = \\"${var.image_name}-${local.timestamp}\\"\\n\\n  os_type = \\"Linux\\"\\n  # az vm image list-publishers --location koreacentral --output table\\n  image_publisher = \\"RedHat\\"\\n  # az vm image list-offers --location koreacentral --publisher RedHat --output table\\n  image_offer = \\"RHEL\\"\\n  # az vm image list-skus --location koreacentral --publisher RedHat --offer RHEL --output table\\n  image_sku = \\"8_4\\"\\n\\n  azure_tags = {\\n    dept = \\"KBHC Terraform POC\\"\\n  }\\n  \\n  # az vm list-skus --location koreacentral --all --output table\\n  build_resource_group_name = local.resource_group_name\\n\\n  #########################################\\n  # 기존 생성되어있는 network 를 사용하기 위한 항목 #\\n  #########################################\\n  virtual_network_name = local.virtual_network_name\\n  virtual_network_subnet_name = local.virtual_network_subnet_name\\n  virtual_network_resource_group_name = local.virtual_network_resource_group_name\\n  \\n  # location = \\"koreacentral\\"\\n  vm_size = \\"Standard_A2_v2\\"\\n}\\n\\nbuild {\\n  sources = [\\"sources.azure-arm.basic-example\\"]\\n\\n  provisioner \\"file\\" {\\n    source      = \\"./files/\\"\\n    destination = \\"/tmp\\"\\n  }\\n\\n# Vault OTP\\n  provisioner \\"shell\\" {\\n    inline = [\\n      \\"sudo cp /tmp/sshd /etc/pam.d/sshd\\",\\n      \\"sudo cp /tmp/sshd_config /etc/ssh/sshd_config\\",\\n      \\"sudo mkdir -p /etc/vault.d\\",\\n      \\"sudo cp /tmp/vault.hcl /etc/vault.d/vault.hcl\\",\\n      \\"sudo cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper\\",\\n      \\"echo \\\\\\"=== Vault_Check ===\\\\\\"\\",\\n      \\"curl http://10.0.9.10:8200\\",\\n      \\"/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev\\",\\n      \\"echo \\\\\\"=== Add User ===\\\\\\"\\",\\n      \\"sudo adduser jboss\\",\\n      \\"echo password | sudo passwd --stdin jboss\\",\\n      \\"echo 'jboss ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /etc/sudoers\\",\\n      \\"echo \\\\\\"=== SELINUX DISABLE ===\\\\\\"\\",\\n      \\"sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config\\"\\n    ]\\n  }\\n\\n# Apache\\n  provisioner \\"shell\\" {\\n    inline = [\\n      \\"sudo yum -y update\\",\\n      \\"sleep 15\\",\\n      \\"sudo yum -y update\\",\\n      \\"sudo yum -y install httpd\\",\\n      \\"sudo systemctl enable httpd\\",\\n      \\"sudo systemctl start httpd\\",\\n      \\"chmod +x /tmp/deploy_app.sh\\",\\n      \\"sudo PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh\\",\\n      \\"sudo firewall-cmd --zone=public --permanent --add-port=80/tcp\\",\\n      \\"sudo firewall-cmd --reload\\",\\n    ]\\n  }\\n}\\n
    "}`);export{y as comp,q as data}; diff --git a/assets/BoundaryTerraformSample01.html-68Tij93V.js b/assets/BoundaryTerraformSample01.html-68Tij93V.js new file mode 100644 index 0000000000..92620d4f2b --- /dev/null +++ b/assets/BoundaryTerraformSample01.html-68Tij93V.js @@ -0,0 +1,236 @@ +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c,b as n,d as s,a as t,e}from"./app-Bzk8Nrll.js";const r={},l=n("h1",{id:"configure-boudary-using-terraform",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#configure-boudary-using-terraform"},[n("span",null,"Configure Boudary using Terraform")])],-1),u={href:"https://registry.terraform.io/providers/hashicorp/boundary/latest/docs",target:"_blank",rel:"noopener noreferrer"},d={href:"https://learn.hashicorp.com/tutorials/boundary/getting-started-config",target:"_blank",rel:"noopener noreferrer"},k={id:"main-tf",tabindex:"-1"},v={class:"header-anchor",href:"#main-tf"},m={href:"http://main.tf",target:"_blank",rel:"noopener noreferrer"},b=e(`
    provider "boundary" {
    +  addr             = "http://172.28.128.11:9200"
    +//   recovery_kms_hcl = <<EOT
    +// kms "aead" {
    +//     purpose   = "recovery"
    +//     aead_type = "aes-gcm"
    +//     key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +//     key_id    = "global_recovery"
    +// }
    +// EOT
    +    auth_method_id = "ampw_U6FXouWRDK"
    +    password_auth_method_login_name = "admin"
    +    password_auth_method_password = "POByMKtvabYS1wtRHLgZ"
    +}
    +
    +resource "boundary_scope" "global" {
    +  global_scope = true
    +  scope_id     = "global"
    +  description  = "Global scope"
    +}
    +
    +// Scope HashiStack
    +resource "boundary_scope" "corp" {
    +  name                     = "hashistack"
    +  description              = "hashistack scope"
    +  scope_id                 = boundary_scope.global.id
    +  auto_create_admin_role   = true
    +  auto_create_default_role = true
    +}
    +
    +resource "boundary_auth_method" "corp_password" {
    +  name        = "corp_password_auth_method"
    +  description = "Password auth method"
    +  type        = "password"
    +  scope_id    = boundary_scope.corp.id
    +}
    +
    +resource "boundary_account" "user" {
    +  for_each       = var.users
    +  name           = each.key
    +  description    = "User account for my user"
    +  type           = "password"
    +  login_name     = lower(each.key)
    +  password       = "password"
    +  auth_method_id = boundary_auth_method.corp_password.id
    +}
    +
    +resource "boundary_user" "users" {
    +  for_each    = var.users
    +  name        = each.key
    +  description = "User resource for ${each.key}"
    +  account_ids = ["${boundary_account.user[each.key].id}"]
    +  scope_id = boundary_scope.corp.id
    +}
    +
    +resource "boundary_group" "admin" {
    +  name        = "admin"
    +  description = "Organization group for readonly users"
    +  member_ids  = [for user in boundary_user.users : user.id]
    +  scope_id    = boundary_scope.corp.id
    +}
    +
    +resource "boundary_user" "readonly_users" {
    +  for_each    = var.readonly_users
    +  name        = each.key
    +  description = "User resource for ${each.key}"
    +  scope_id    = boundary_scope.corp.id
    +}
    +
    +resource "boundary_group" "readonly" {
    +  name        = "read-only"
    +  description = "Organization group for readonly users"
    +  member_ids  = [for user in boundary_user.readonly_users : user.id]
    +  scope_id    = boundary_scope.corp.id
    +}
    +
    +resource "boundary_role" "corp_admin" {
    +  name        = "corp_admin"
    +  description = "Corp Administrator role"
    +  principal_ids = concat(
    +    [for user in boundary_user.users: user.id]
    +  )
    +  grant_strings   = ["id=*;type=*;actions=create,read,update,delete"]
    +  scope_id = boundary_scope.corp.id
    +}
    +
    +resource "boundary_role" "organization_readonly" {
    +  name        = "Read-only"
    +  description = "Read-only role"
    +  principal_ids = [boundary_group.readonly.id]
    +  grant_strings = ["id=*;type=*;actions=read"]
    +  scope_id    = boundary_scope.corp.id
    +}
    +
    +resource "boundary_scope" "core_infra" {
    +  name                   = "core_infra"
    +  description            = "My first project!"
    +  scope_id               = boundary_scope.corp.id
    +  auto_create_admin_role = true
    +}
    +
    +resource "boundary_host_catalog" "backend_servers" {
    +  name        = "backend_servers"
    +  description = "Backend servers host catalog"
    +  type        = "static"
    +  scope_id    = boundary_scope.core_infra.id
    +}
    +
    +resource "boundary_host" "ssh_servers" {
    +  for_each        = var.ssh_server_ips
    +  type            = "static"
    +  name            = "ssh_server_service_${each.value}"
    +  description     = "ssh server host"
    +  address         = each.key
    +  host_catalog_id = boundary_host_catalog.backend_servers.id
    +}
    +
    +resource "boundary_host" "backend_servers" {
    +  for_each        = var.backend_server_ips
    +  type            = "static"
    +  name            = "backend_server_service_${each.value}"
    +  description     = "Backend server host"
    +  address         = each.key
    +  host_catalog_id = boundary_host_catalog.backend_servers.id
    +}
    +
    +resource "boundary_host_set" "ssh_servers" {
    +  type            = "static"
    +  name            = "ssh_servers"
    +  description     = "Host set for ssh servers"
    +  host_catalog_id = boundary_host_catalog.backend_servers.id
    +  host_ids        = [for host in boundary_host.ssh_servers : host.id]
    +}
    +
    +resource "boundary_host_set" "backend_servers" {
    +  type            = "static"
    +  name            = "backend_servers"
    +  description     = "Host set for backend servers"
    +  host_catalog_id = boundary_host_catalog.backend_servers.id
    +  host_ids        = [for host in boundary_host.backend_servers : host.id]
    +}
    +
    +# create target for accessing backend servers on port :8000
    +resource "boundary_target" "backend_servers_service" {
    +  type         = "tcp"
    +  name         = "backend_server"
    +  description  = "Backend service target"
    +  scope_id     = boundary_scope.core_infra.id
    +  default_port = "8080"
    +
    +  host_set_ids = [
    +    boundary_host_set.backend_servers .id
    +  ]
    +}
    +
    +# create target for accessing backend servers on port :22
    +resource "boundary_target" "backend_servers_ssh" {
    +  type         = "tcp"
    +  name         = "ssh_server"
    +  description  = "Backend SSH target"
    +  scope_id     = boundary_scope.core_infra.id
    +  // default_port = "22"
    +
    +  host_set_ids = [
    +    boundary_host_set.ssh_servers.id
    +  ]
    +}
    +
    +// anonymous
    +resource "boundary_role" "global_anon_listing" {
    +  scope_id = boundary_scope.global.id
    +  grant_strings = [
    +    "id=*;type=auth-method;actions=list,authenticate",
    +    "type=scope;actions=list",
    +    "id={{account.id}};actions=read,change-password"
    +  ]
    +  principal_ids = ["u_anon"]
    +}
    +
    +resource "boundary_role" "org_anon_listing" {
    +  scope_id = boundary_scope.corp.id
    +  grant_strings = [
    +    "id=*;type=auth-method;actions=list,authenticate",
    +    "type=scope;actions=list",
    +    "id={{account.id}};actions=read,change-password"
    +  ]
    +  principal_ids = ["u_anon"]
    +}
    +
    +output "corp_auth_method_id" {
    +    value = "boundary authenticate password -auth-method-id ${boundary_auth_method.corp_password.id} -login-name ${boundary_account.user["gslee"].login_name} -password ${boundary_account.user["gslee"].password}"
    +}
    +
    `,1),y={id:"variable-tf",tabindex:"-1"},q={class:"header-anchor",href:"#variable-tf"},_={href:"http://variable.tf",target:"_blank",rel:"noopener noreferrer"},g=e(`
    variable "addr" {
    +  default = "http://172.28.128.11:9200"
    +}
    +
    +variable "users" {
    +  type = set(string)
    +  default = [
    +    "gslee",
    +    "Jim",
    +    "Mike",
    +    "Todd",
    +    "Jeff",
    +    "Randy",
    +    "Susmitha"
    +  ]
    +}
    +
    +variable "readonly_users" {
    +  type = set(string)
    +  default = [
    +    "Chris",
    +    "Pete",
    +    "Justin"
    +  ]
    +}
    +
    +variable "ssh_server_ips" {
    +  type = set(string)
    +  default = [
    +    "172.28.128.11"
    +  ]
    +}
    +
    +variable "backend_server_ips" {
    +  type = set(string)
    +  default = [
    +    "172.28.128.11",
    +    "172.28.128.50",
    +    "172.28.128.60",
    +    "172.28.128.61",
    +    "172.28.128.70",
    +  ]
    +}
    +
    `,1);function h(f,w){const a=o("ExternalLinkIcon");return i(),c("div",null,[l,n("ul",null,[n("li",null,[s("Terraform provider : "),n("a",u,[s("https://registry.terraform.io/providers/hashicorp/boundary/latest/docs"),t(a)])]),n("li",null,[s("learn site : "),n("a",d,[s("https://learn.hashicorp.com/tutorials/boundary/getting-started-config"),t(a)])])]),n("h2",k,[n("a",v,[n("span",null,[n("a",m,[s("main.tf"),t(a)])])])]),b,n("h2",y,[n("a",q,[n("span",null,[n("a",_,[s("variable.tf"),t(a)])])])]),g])}const C=p(r,[["render",h],["__file","BoundaryTerraformSample01.html.vue"]]),S=JSON.parse('{"path":"/04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.html","title":"Configure Boudary using Terraform","lang":"ko-KR","frontmatter":{"description":"Boundary Terraform Setup","tag":["Boundary","Terraform","Config"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Configure Boudary using Terraform"}],["meta",{"property":"og:description","content":"Boundary Terraform Setup"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Boundary"}],["meta",{"property":"article:tag","content":"Terraform"}],["meta",{"property":"article:tag","content":"Config"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Configure Boudary using Terraform\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"main.tf","slug":"main-tf","link":"#main-tf","children":[]},{"level":2,"title":"variable.tf","slug":"variable-tf","link":"#variable-tf","children":[]}],"git":{"createdTime":1634219407000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.72,"words":516},"filePathRelative":"04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.md","localizedDate":"2021년 10월 14일","excerpt":"\\n"}');export{C as comp,S as data}; diff --git a/assets/Chart.html-Dir6u0uC.js b/assets/Chart.html-Dir6u0uC.js new file mode 100644 index 0000000000..10272b4828 --- /dev/null +++ b/assets/Chart.html-Dir6u0uC.js @@ -0,0 +1,65 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as r,c as d,b as e,d as n,a as t,e as o}from"./app-Bzk8Nrll.js";const u={},c=e("h1",{id:"chart",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#chart"},[e("span",null,"Chart")])],-1),v=e("p",null,"문서 작성시 차트를 추가하는 방법을 안내합니다.",-1),b={href:"https://theme-hope.vuejs.press/guide/markdown/chart.html",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.chartjs.org/docs/latest/",target:"_blank",rel:"noopener noreferrer"},h=e("p",null,[e("code",null,"::: chart"),n(" 와 "),e("code",null,":::"),n("로 처리합니다.")],-1),q=e("h2",{id:"기본-사용법-bar",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#기본-사용법-bar"},[e("span",null,"기본 사용법 - Bar")])],-1),p=o(`
    CODE
    ::: chart A bar chart
    +
    +\`\`\`json
    +{
    +  "type": "bar",
    +  "data": {
    +    "labels": ["Red", "Orange", "Yellow", "Green", "Blue", "Purple"],
    +    "datasets": [{
    +      "label": "My First Dataset",
    +      "data": [12, 19, 3, 5, 2, 3],
    +      "backgroundColor": [
    +        "rgba(255, 99, 132, 0.2)",
    +        "rgba(255, 159, 64, 0.2)",
    +        "rgba(255, 205, 86, 0.2)",
    +        "rgba(75, 192, 192, 0.2)",
    +        "rgba(54, 162, 235, 0.2)",
    +        "rgba(153, 102, 255, 0.2)"
    +      ],
    +      "borderColor": [
    +        "rgb(255, 99, 132)",
    +        "rgb(255, 159, 64)",
    +        "rgb(255, 205, 86)",
    +        "rgb(75, 192, 192)",
    +        "rgb(54, 162, 235)",
    +        "rgb(153, 102, 255)"
    +      ],
    +      "borderWidth": 1
    +    }]
    +  },
    +  "options": {
    +    "scales": {
    +      "y": {
    +        "ticks": {
    +          "beginAtZero": true,
    +          "callback": "function(value){ return '$' + value + 'k'; }"
    +        },
    +        "beginAtZero": true
    +      }
    +    }
    +  }
    +}
    +\`\`\`
    +
    +
    +

    기본 사용법 - Bubble

    `,2),g=o(`
    CODE
    ::: chart A Bubble Chart
    +
    +\`\`\`json
    +{
    +  "type": "bubble",
    +  "data": {
    +    "datasets": [
    +      {
    +        "label": "First Dataset",
    +        "data": [
    +          { "x": 20, "y": 30, "r": 15 },
    +          { "x": 40, "y": 10, "r": 10 }
    +        ],
    +        "backgroundColor": "rgb(255, 99, 132)"
    +      }
    +    ]
    +  }
    +}
    +\`\`\`
    +
    +
    `,1);function k(C,_){const a=s("ExternalLinkIcon"),i=s("ChartJS");return r(),d("div",null,[c,v,e("ul",null,[e("li",null,[e("a",b,[n("공식 문서"),t(a)])])]),e("p",null,[n("차트 구성 방식은 "),e("a",m,[n("ChartJS"),t(a)]),n("를 따릅니다.")]),h,q,t(i,{id:"chart-22",config:"eJx9UstOwzAQvPcrLAspVESoTkihcOIhOCEQFwSoBycxIaplV44Dqqr8O7uO2ya04ZJ4d2Znd6RZjwihdrUU9JLQlBsaYiPnlkMDQagkT4WsoP6gLyKnIaFPhqtC4OtNSKl/8PVghFD4uJG1g55rs5SCzlHRa1bCOp1WeCONqx9X5L40lSV3Lc3dsZ3DGRaFhM1CEockCQkUsVcGTsqzRWF0rfJbLbVBuocANEXKj6MEhmYwzmIYnZxG4+2GHoUlwJme/UeJJvC5mA5QzlFk5m4dWpSAPJsCGsVAPkhhCdhkE+TgSsfxlI5rbXJhDjvuGf6r37N6GPQm98CuvT2wa2wP7FkadPNa5vYL3DDXbubwa1wk9dKWWmF6fCqrjEuxq6Gz6hSY6jJbdPF2iyhKdW3fhdEAWVOL3ZkAg6bEMGEkP2uV4c7jbw6JHq+JEbY2igRHATkhrgn/YBFckWZjx187uMxjTesOzY2a0S+eGcjH",title:"A%20bar%20chart",type:"json"}),p,t(i,{id:"chart-31",config:"eJxNjUEOwiAURPc9xc9facKiUFnUrcZLmC7AksZIxABNbAx3FyhFN2SYeTP/0wCgX14Kj4ByllIrJMkbhRfRS3n5OeVddK7ZgZLkVAupdBq43K3zcF7hvFOIsrZ1cx/wHS3WEsAlii4JGwXlEH7VCh42kFawhVC54e+aFLfHZM38HE9Gm4SineSOcU6g7wnQju2x4OvCEN/QhOYLM1k72g==",title:"A%20Bubble%20Chart",type:"json"}),g])}const x=l(u,[["render",k],["__file","Chart.html.vue"]]),y=JSON.parse('{"path":"/00-Howto/03-Tips/Chart.html","title":"Chart","lang":"ko-KR","frontmatter":{"description":"Chart 문서 작성시 차트를 추가하는 방법을 안내합니다. 공식 문서 차트 구성 방식은 ChartJS를 따릅니다. ::: chart 와 :::로 처리합니다. 기본 사용법 - Bar CODE 기본 사용법 - Bubble CODE ","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/03-Tips/Chart.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Chart"}],["meta",{"property":"og:description","content":"Chart 문서 작성시 차트를 추가하는 방법을 안내합니다. 공식 문서 차트 구성 방식은 ChartJS를 따릅니다. ::: chart 와 :::로 처리합니다. 기본 사용법 - Bar CODE 기본 사용법 - Bubble CODE "}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2024-02-15T08:16:17.000Z"}],["meta",{"property":"article:modified_time","content":"2024-02-15T08:16:17.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Chart\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-02-15T08:16:17.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"기본 사용법 - Bar","slug":"기본-사용법-bar","link":"#기본-사용법-bar","children":[]},{"level":2,"title":"기본 사용법 - Bubble","slug":"기본-사용법-bubble","link":"#기본-사용법-bubble","children":[]}],"git":{"createdTime":1663987184000,"updatedTime":1707984977000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":3}]},"readingTime":{"minutes":0.92,"words":277},"filePathRelative":"00-Howto/03-Tips/Chart.md","localizedDate":"2022년 9월 24일","excerpt":"\\n

    문서 작성시 차트를 추가하는 방법을 안내합니다.

    \\n\\n

    차트 구성 방식은 ChartJS를 따릅니다.

    \\n

    ::: chart:::로 처리합니다.

    ","autoDesc":true}');export{x as comp,y as data}; diff --git a/assets/Cloudwatch-Logging.html-B1APZMkF.js b/assets/Cloudwatch-Logging.html-B1APZMkF.js new file mode 100644 index 0000000000..5a67825995 --- /dev/null +++ b/assets/Cloudwatch-Logging.html-B1APZMkF.js @@ -0,0 +1,94 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c,b as n,d as s,a,e as i}from"./app-Bzk8Nrll.js";const r="/assets/Cloudwatch-logging-Nomad-B1kkD22-.png",p="/assets/Cloudwatch-logging-aws-DrZcdlMF.png",u={},d=n("h1",{id:"docker-log-driver-and-cloudwatch-on-nomad",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#docker-log-driver-and-cloudwatch-on-nomad"},[n("span",null,"Docker log driver and Cloudwatch on Nomad")])],-1),k=n("br",null,null,-1),g={href:"https://docs.docker.com/config/containers/logging/awslogs/",target:"_blank",rel:"noopener noreferrer"},m=i('

    Nomad에서 docker 자체의 로깅을 사용하므로서, Nomad에서 실행되는 docker 기반 컨테이너의 로깅이 특정 환경에 락인되는것을 방지합니다.

    경고

    AWS 환경이 아닌 외부 구성 시, 해당 노드에 Cloudwath 기록을 위한 Policy를 갖는 IAM의 credential 정보가 환경변수 또는 ~/.aws/credential 구성이 필요합니다.

    EC2 Instance Role 구성

    Nomad 구성 시 Cloudwatch에 대한 EC2 Instance의 IAM 구성이 필요합니다. 아래 Terraform 구성의 예를 참고하세요.

    ',4),b=n("li",null,[s("loging driver의 구성에 따라 "),n("code",null,"aws_iam_role_policy"),s("에 설정하는 필요한 권한에 차이가 있을 수 있습니다.")],-1),h=n("li",null,[s("예를 들어 docker loging 구성에서 "),n("code",null,"awslogs-create-group = true"),s(" 옵션을 추가하려는 경우 "),n("code",null,"logs:CreateLogGroup"),s(" 정책이 필요합니다.")],-1),v={href:"https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/logs/permissions-reference-cwl.html",target:"_blank",rel:"noopener noreferrer"},_=n("div",{class:"language-hcl line-numbers-mode","data-ext":"hcl","data-title":"hcl"},[n("pre",{hcl:"",class:"language-hcl"},[n("code",null,[n("span",{class:"token comment"},"## 생략 ##"),s(` + +`),n("span",{class:"token keyword"},[s("resource "),n("span",{class:"token type variable"},'"aws_iam_instance_profile"')]),s(),n("span",{class:"token string"},'"ec2_profile"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"name"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"ec2_profile"'),s(` + `),n("span",{class:"token property"},"role"),s(),n("span",{class:"token punctuation"},"="),s(` aws_iam_role.role.name +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token keyword"},[s("resource "),n("span",{class:"token type variable"},'"aws_iam_role"')]),s(),n("span",{class:"token string"},'"role"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"name"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"my_role"'),s(` + + `),n("span",{class:"token property"},"assume_role_policy"),s(),n("span",{class:"token punctuation"},"="),s(" jsonencode("),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"Version"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"2012-10-17"'),s(` + `),n("span",{class:"token property"},"Statement"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"["),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"Action"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"sts:AssumeRole"'),s(` + `),n("span",{class:"token property"},"Effect"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"Allow"'),s(` + `),n("span",{class:"token property"},"Sid"),s(" "),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'""'),s(` + `),n("span",{class:"token property"},"Principal"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"Service"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"ec2.amazonaws.com"'),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(`, + `),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token punctuation"},"}"),s(`) +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token keyword"},[s("resource "),n("span",{class:"token type variable"},'"aws_iam_role_policy"')]),s(),n("span",{class:"token string"},'"cloudwatch_policy"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"name"),s(" "),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"cloudwatch_policy"'),s(` + `),n("span",{class:"token property"},"role"),s(" "),n("span",{class:"token punctuation"},"="),s(` aws_iam_role.role.id + + `),n("span",{class:"token property"},"policy"),s(),n("span",{class:"token punctuation"},"="),s(" jsonencode("),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"Version"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"2012-10-17"'),s(` + `),n("span",{class:"token property"},"Statement"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"["),s(` + `),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"Action"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"["),s(` + `),n("span",{class:"token string"},'"logs:CreateLogStream"'),s(`, + `),n("span",{class:"token string"},'"logs:PutLogEvents"'),s(`, + `),n("span",{class:"token string"},'"logs:CreateLogGroup"'),s(` + `),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token property"},"Effect"),s(" "),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"Allow"'),s(` + `),n("span",{class:"token property"},"Resource"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"*"'),s(` + `),n("span",{class:"token punctuation"},"}"),s(`, + `),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token punctuation"},"}"),s(`) +`),n("span",{class:"token punctuation"},"}"),s(` + +`),n("span",{class:"token keyword"},[s("resource "),n("span",{class:"token type variable"},'"aws_instance"')]),s(),n("span",{class:"token string"},'"example"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"ami"),s(" "),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"ami-04e6fcf8cfe3b09ea"'),s(` + `),n("span",{class:"token property"},"instance_type"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"t2.micro"'),s(` + `),n("span",{class:"token property"},"key_name"),s(" "),n("span",{class:"token punctuation"},"="),s(` aws_key_pair.web_admin.key_name + `),n("span",{class:"token property"},"vpc_security_group_ids"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"["),s(` + aws_security_group.ssh.id + `),n("span",{class:"token punctuation"},"]"),s(` + + `),n("span",{class:"token property"},"iam_instance_profile"),s(),n("span",{class:"token punctuation"},"="),s(` aws_iam_instance_profile.ec2_profile.name +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("div",{class:"highlight-line"}," "),n("br")]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),y=n("h2",{id:"nomad-job의-docker-driver에-logging-설정",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#nomad-job의-docker-driver에-logging-설정"},[n("span",null,"Nomad Job의 Docker Driver에 Logging 설정")])],-1),w=n("code",null,"json-file",-1),f={href:"https://www.nomadproject.io/docs/drivers/docker#logging",target:"_blank",rel:"noopener noreferrer"},C={href:"https://docs.docker.com/config/containers/logging/configure/",target:"_blank",rel:"noopener noreferrer"},N=n("li",null,[s("기존 docker cli 상에 구성했던 "),n("code",null,"--log-driver"),s(" 같은 옵션의 정의가 HCL형태로 정의됩니다.")],-1),L=n("li",null,[s("HCL 문법을 따르므로, 몇몇 상이한 표현방식이 있을 수 있습니다. 예를들어 로그 날짜 구성에 사용되는 "),n("code",null,'"\\[%Y-%m-%d\\]"'),s(" 에서 "),n("code",null,"["),s(" 같은 특수문자 표기를 위해 "),n("code",null,"\\"),s("를 한번만 넣었다면, "),n("code",null,'"\\\\[%Y-%m-%d\\\\]"'),s(" 같이 두번 넣어야 할수도 있습니다.")],-1),x=n("p",null,"구성 예제는 아래와 같습니다.",-1),A=n("div",{class:"language-hcl line-numbers-mode","data-ext":"hcl","data-title":"hcl"},[n("pre",{hcl:"",class:"language-hcl"},[n("code",null,[s("job "),n("span",{class:"token string"},'"api"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"datacenters"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token string"},'"dc1"'),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token property"},"type"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"service"'),s(` + + group `),n("span",{class:"token string"},'"api"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"network"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"mode"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"bridge"'),s(` + port `),n("span",{class:"token string"},'"api"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"to"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token number"},"9001"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + `),n("span",{class:"token keyword"},"service"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"name"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"count-api"'),s(` + `),n("span",{class:"token property"},"port"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"api"'),s(` + `),n("span",{class:"token keyword"},"connect"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token keyword"},"sidecar_service"),s(),n("span",{class:"token punctuation"},"{"),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + + task `),n("span",{class:"token string"},'"web"'),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"driver"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"docker"'),s(` + `),n("span",{class:"token keyword"},"config"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"image"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"hashicorpnomad/counter-api:v1"'),s(` + `),n("span",{class:"token property"},"ports"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token punctuation"},"["),n("span",{class:"token string"},'"api"'),n("span",{class:"token punctuation"},"]"),s(` + `),n("span",{class:"token keyword"},"logging"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"type"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"awslogs"'),s(` + `),n("span",{class:"token keyword"},"config"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"awslogs-region"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"ap-northeast-2"'),s(` + `),n("span",{class:"token property"},"awslogs-group"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"myGroup"'),s(` + `),n("span",{class:"token property"},"awslogs-create-group"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token boolean"},"true"),s(` + `),n("span",{class:"token property"},"awslogs-datetime-format"),s(),n("span",{class:"token punctuation"},"="),s(),n("span",{class:"token string"},'"\\\\[%Y-%m-%dT%H:%M:%S\\\\+09:00\\\\]"'),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` + `),n("span",{class:"token punctuation"},"}"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("br"),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," "),n("br"),n("br"),n("br"),n("br")]),n("div",{class:"line-numbers","aria-hidden":"true"},[n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"}),n("div",{class:"line-number"})])],-1),D=n("h2",{id:"log-확인",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#log-확인"},[n("span",null,"Log 확인")])],-1),j=n("p",null,[s("Nomad의 로그 출력을 확인합니다."),n("br"),n("img",{src:r,alt:"NomadLog",loading:"lazy"})],-1),S=n("p",null,[s("Cloudwatch에 로그 출력을 확인합니다."),n("br"),n("img",{src:p,alt:"NomadLog",loading:"lazy"})],-1);function E(T,z){const e=o("ExternalLinkIcon");return l(),c("div",null,[d,n("p",null,[s('docker 런타임에는 log driver로 "awslogs"를 지원합니다.'),k,n("a",g,[s("https://docs.docker.com/config/containers/logging/awslogs/"),a(e)])]),m,n("ul",null,[b,h,n("li",null,[s("권한에 대한 상세 설명은 다음 링크를 참고합니다. "),n("a",v,[s("https://docs.aws.amazon.com/ko_kr/AmazonCloudWatch/latest/logs/permissions-reference-cwl.html"),a(e)])])]),_,y,n("p",null,[s("Nomad에서 docker 드라이버 사용시 적용되는 기본 log driver는 "),w,s(" 입니다. 추가 설정을 통해 docker가 지원하는 다양한 log driver를 사용할 수 있습니다. ("),n("a",f,[s("FluentD 샘플"),a(e)]),s(")")]),n("ul",null,[n("li",null,[s("구성에 필요한 정보는 Docker의 공식 문서를 참고 합니다. : "),n("a",C,[s("https://docs.docker.com/config/containers/logging/configure/"),a(e)])]),N,L]),x,A,D,j,S])}const H=t(u,[["render",E],["__file","Cloudwatch-Logging.html.vue"]]),V=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.html","title":"Docker log driver and Cloudwatch on Nomad","lang":"ko-KR","frontmatter":{"description":"Nomad docker job logging into Cloudwatch","tag":["Nomad","AWS","Cloudwatch","log"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Docker log driver and Cloudwatch on Nomad"}],["meta",{"property":"og:description","content":"Nomad docker job logging into Cloudwatch"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"AWS"}],["meta",{"property":"article:tag","content":"Cloudwatch"}],["meta",{"property":"article:tag","content":"log"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Docker log driver and Cloudwatch on Nomad\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"EC2 Instance Role 구성","slug":"ec2-instance-role-구성","link":"#ec2-instance-role-구성","children":[]},{"level":2,"title":"Nomad Job의 Docker Driver에 Logging 설정","slug":"nomad-job의-docker-driver에-logging-설정","link":"#nomad-job의-docker-driver에-logging-설정","children":[]},{"level":2,"title":"Log 확인","slug":"log-확인","link":"#log-확인","children":[]}],"git":{"createdTime":1639533195000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":3},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.85,"words":256},"filePathRelative":"04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.md","localizedDate":"2021년 12월 15일","excerpt":"\\n

    docker 런타임에는 log driver로 \\"awslogs\\"를 지원합니다.
    \\nhttps://docs.docker.com/config/containers/logging/awslogs/

    \\n
    \\n

    \\n

    Nomad에서 docker 자체의 로깅을 사용하므로서, Nomad에서 실행되는 docker 기반 컨테이너의 로깅이 특정 환경에 락인되는것을 방지합니다.

    \\n
    "}');export{H as comp,V as data}; diff --git a/assets/Cloudwatch-logging-Nomad-B1kkD22-.png b/assets/Cloudwatch-logging-Nomad-B1kkD22-.png new file mode 100644 index 0000000000..d5b11edfe1 Binary files /dev/null and b/assets/Cloudwatch-logging-Nomad-B1kkD22-.png differ diff --git a/assets/Cloudwatch-logging-aws-DrZcdlMF.png b/assets/Cloudwatch-logging-aws-DrZcdlMF.png new file mode 100644 index 0000000000..741751eb2a Binary files /dev/null and b/assets/Cloudwatch-logging-aws-DrZcdlMF.png differ diff --git a/assets/CodeBlock.html-2_d1c3y7.js b/assets/CodeBlock.html-2_d1c3y7.js new file mode 100644 index 0000000000..4d0aa15e8f --- /dev/null +++ b/assets/CodeBlock.html-2_d1c3y7.js @@ -0,0 +1,52 @@ +import{_ as d}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as k,c as r,b as n,d as s,a as t,w as a,e}from"./app-Bzk8Nrll.js";const g={},h=e('

    Code Block

    마크다운 기본 사용 법과 거의 동일합니다.

    기본 사용법

    코드블록은 ``` 과 ``` 사이에 코드를 넣어 로 표기합니다. 아래와 같이 md 파일 내에 작성하면

    ```\n# Code block e.g.\nThis is my code\n```\n

    다음과 같이 표기됩니다.

    # Code block e.g.\nThis is my code\n

    ``` 대신 ~~~ 도 사용 가능합니다.

    코드 구문 강조

    ',9),m={href:"https://github.com/PrismJS/prism/blob/master/components.json",target:"_blank",rel:"noopener noreferrer"},v=n("code",null,"languages",-1),b=e('
    ```python\nprint("hello, world.")\n```\n
    print("hello, world.")\n

    코드 강조

    문서 작성 시 코드블록에서 강조하고 싶은 경우 코드블럭의 스타일 에 강조할 라인 번호를 명시합니다.

    ```python {2,4-5}\nprint("nomal")\nprint("highlight!")\nprint("nomal")\nprint("highlight!")\nprint("highlight!")\n```\n
    ',5),y=n("div",{class:"language-python","data-ext":"py","data-title":"py"},[n("pre",{python:"",class:"language-python"},[n("code",null,[n("span",{class:"token keyword"},"print"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"nomal"'),n("span",{class:"token punctuation"},")"),s(` +`),n("span",{class:"token keyword"},"print"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"highlight!"'),n("span",{class:"token punctuation"},")"),s(` +`),n("span",{class:"token keyword"},"print"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"nomal"'),n("span",{class:"token punctuation"},")"),s(` +`),n("span",{class:"token keyword"},"print"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"highlight!"'),n("span",{class:"token punctuation"},")"),s(` +`),n("span",{class:"token keyword"},"print"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"highlight!"'),n("span",{class:"token punctuation"},")"),s(` +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("div",{class:"highlight-line"}," "),n("br"),n("div",{class:"highlight-line"}," "),n("div",{class:"highlight-line"}," ")])],-1),_=e(`

    다중 코드블록

    상황에 따라 동일한 구성 및 동작을 위한 코드블록을 옵션을 주어 선택적으로 표기하고 싶은 경우 <code-group><code-block> 을 활용 합니다.

    ::: code-tabs#shell
    +@tab Option1
    +\`\`\`bash {2}
    +# This is Option 1
    +chmod 755 ./file.txt
    +\`\`\`
    +
    +@tab Option2
    +\`\`\`bash {2}
    +# This is Option 2
    +chmod +x ./file.txt
    +\`\`\`
    +:::
    +
    `,3),x=n("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[n("pre",{bash:"",class:"language-bash"},[n("code",null,[n("span",{class:"token comment"},"# This is Option 1"),s(` +`),n("span",{class:"token function"},"chmod"),s(),n("span",{class:"token number"},"755"),s(` ./file.txt +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("div",{class:"highlight-line"}," ")])],-1),f=n("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[n("pre",{bash:"",class:"language-bash"},[n("code",null,[n("span",{class:"token comment"},"# This is Option 2"),s(` +`),n("span",{class:"token function"},"chmod"),s(` +x ./file.txt +`)])]),n("div",{class:"highlight-lines"},[n("br"),n("div",{class:"highlight-line"}," ")])],-1),w=n("h2",{id:"code-demo",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#code-demo"},[n("span",null,"Code Demo")])],-1),q={href:"https://vuepress-theme-hope.github.io/md-enhance/guide/demo/#",target:"_blank",rel:"noopener noreferrer"},C=e(`
    ::: normal-demo Demo
    +
    +\`\`\`html
    +<h1>Mr.Hope</h1>
    +<p>is <span id="very">very</span> handsome</p>
    +\`\`\`
    +
    +\`\`\`js
    +document.querySelector("#very").addEventListener("click", () => {
    +  alert("Very handsome");
    +});
    +\`\`\`
    +
    +\`\`\`css
    +span {
    +  color: red;
    +}
    +\`\`\`
    +
    +:::
    +
    `,1),T=n("div",{class:"language-html","data-ext":"html","data-title":"html"},[n("pre",{class:"language-html"},[n("code",null,[n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"<"),s("h1")]),n("span",{class:"token punctuation"},">")]),s("Mr.Hope"),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"")]),s(` +`),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"<"),s("p")]),n("span",{class:"token punctuation"},">")]),s("is "),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"<"),s("span")]),s(),n("span",{class:"token attr-name"},"id"),n("span",{class:"token attr-value"},[n("span",{class:"token punctuation attr-equals"},"="),n("span",{class:"token punctuation"},'"'),s("very"),n("span",{class:"token punctuation"},'"')]),n("span",{class:"token punctuation"},">")]),s("very"),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"")]),s(" handsome"),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"")]),s(` +`),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"<"),s("button")]),n("span",{class:"token punctuation"},">")]),s("hello"),n("span",{class:"token tag"},[n("span",{class:"token tag"},[n("span",{class:"token punctuation"},"")]),s(` +`)])])],-1),B=n("div",{class:"language-javascript","data-ext":"js","data-title":"js"},[n("pre",{class:"language-javascript"},[n("code",null,[s("document"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"querySelector"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"#very"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},"."),n("span",{class:"token function"},"addEventListener"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"click"'),n("span",{class:"token punctuation"},","),s(),n("span",{class:"token punctuation"},"("),n("span",{class:"token punctuation"},")"),s(),n("span",{class:"token operator"},"=>"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token function"},"alert"),n("span",{class:"token punctuation"},"("),n("span",{class:"token string"},'"Very handsome"'),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),n("span",{class:"token punctuation"},")"),n("span",{class:"token punctuation"},";"),s(` +`)])])],-1),j=n("div",{class:"language-css","data-ext":"css","data-title":"css"},[n("pre",{class:"language-css"},[n("code",null,[n("span",{class:"token selector"},"span"),s(),n("span",{class:"token punctuation"},"{"),s(` + `),n("span",{class:"token property"},"color"),n("span",{class:"token punctuation"},":"),s(" red"),n("span",{class:"token punctuation"},";"),s(` +`),n("span",{class:"token punctuation"},"}"),s(` +`)])])],-1);function S(P,D){const l=p("ExternalLinkIcon"),i=p("CodeTabs"),u=p("CodeDemo");return k(),r("div",null,[h,n("p",null,[s("VuePress는 Prism Javascript 라이브러리를 통해 키워드 강조 표시를 지원 합니다. 목록에 대한 확인은 "),n("a",m,[s("components.json"),t(l)]),s(" 의 "),v,s("를 참고할 수 있습니다.")]),b,y,_,t(i,{id:"43",data:[{id:"Option1"},{id:"Option2"}],"tab-id":"shell"},{title0:a(({value:o,isActive:c})=>[s("Option1")]),title1:a(({value:o,isActive:c})=>[s("Option2")]),tab0:a(({value:o,isActive:c})=>[x]),tab1:a(({value:o,isActive:c})=>[f]),_:1}),w,n("p",null,[n("a",q,[s("https://vuepress-theme-hope.github.io/md-enhance/guide/demo/#"),t(l)])]),C,t(u,{id:"code-demo-58",type:"normal",title:"Demo",code:"eJw9jj0LwjAQhv/KcS4K0uKqMZvgoJPglKUmB62ml5qkgoj/3UsVl/t8n3vvhW3uPa5RtSt9jNU+DKRqqQ2rQXcJVBoahs5tDT4oPg3qklRdxhrahl0KvSBDIS5jzoF1S94HVf86w7jEaxIPF+zYE+fqPsqNE3myOcS5wdn39KJqnNs9RHHoUiamsrO+szeDS5gvYKvhZRig8RSz7M6C/X8QfmP4XYIY2lQcp+cnxAYf4hoiuSISyfsDETZVFQ=="},{default:a(()=>[T,B,j]),_:1})])}const K=d(g,[["render",S],["__file","CodeBlock.html.vue"]]),E=JSON.parse('{"path":"/00-Howto/03-Tips/CodeBlock.html","title":"Code Block","lang":"ko-KR","frontmatter":{"description":"Code Block 마크다운 기본 사용 법과 거의 동일합니다. 기본 사용법 코드블록은 ``` 과 ``` 사이에 코드를 넣어 로 표기합니다. 아래와 같이 md 파일 내에 작성하면 다음과 같이 표기됩니다. ``` 대신 ~~~ 도 사용 가능합니다. 코드 구문 강조 VuePress는 Prism Javascript 라이브러리...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/03-Tips/CodeBlock.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Code Block"}],["meta",{"property":"og:description","content":"Code Block 마크다운 기본 사용 법과 거의 동일합니다. 기본 사용법 코드블록은 ``` 과 ``` 사이에 코드를 넣어 로 표기합니다. 아래와 같이 md 파일 내에 작성하면 다음과 같이 표기됩니다. ``` 대신 ~~~ 도 사용 가능합니다. 코드 구문 강조 VuePress는 Prism Javascript 라이브러리..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Code Block\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"기본 사용법","slug":"기본-사용법","link":"#기본-사용법","children":[]},{"level":2,"title":"코드 구문 강조","slug":"코드-구문-강조","link":"#코드-구문-강조","children":[]},{"level":2,"title":"코드 강조","slug":"코드-강조","link":"#코드-강조","children":[]},{"level":2,"title":"다중 코드블록","slug":"다중-코드블록","link":"#다중-코드블록","children":[]},{"level":2,"title":"Code Demo","slug":"code-demo","link":"#code-demo","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.66,"words":197},"filePathRelative":"00-Howto/03-Tips/CodeBlock.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    마크다운 기본 사용 법과 거의 동일합니다.

    \\n

    기본 사용법

    \\n

    코드블록은 ``` 과 ``` 사이에 코드를 넣어 로 표기합니다. 아래와 같이 md 파일 내에 작성하면

    \\n
    ```\\n# Code block e.g.\\nThis is my code\\n```\\n
    ","autoDesc":true}');export{K as comp,E as data}; diff --git a/assets/Consul Enterprise Feature.html-BQm8HMfI.js b/assets/Consul Enterprise Feature.html-BQm8HMfI.js new file mode 100644 index 0000000000..7b0f96e3b2 --- /dev/null +++ b/assets/Consul Enterprise Feature.html-BQm8HMfI.js @@ -0,0 +1 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c as r,b as e,d as n,a as l,e as s}from"./app-Bzk8Nrll.js";const c={},d=s('

    Consul Enterprise Feature

    Enterprise Global Visibility & Scale

    1. Network Segments

    하나의 Consul 서버에서 서로다른 Agent 들의 묶음을 관리할 수 있도록 하는 개념입니다. 여러개의 Consul 클러스터를 만드는 것이 아닌, 하나의 클러스터 내에서 Agent 들을 분류합니다.

    ',5),p={href:"https://learn.hashicorp.com/tutorials/consul/network-partition-datacenters",target:"_blank",rel:"noopener noreferrer"},h=e("h3",{id:"_2-advanced-federation",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2-advanced-federation"},[e("span",null,"2. Advanced Federation")])],-1),u=e("p",null,"페더레이션을 위해서는 RPC(8300/tcp), SerfWAN(8302/tcp, 8302/udp)를 통해야하므로 여러 테이터센터, 혹은 클러스터를 관리하기에 어려워집니다. 이를 보완하기 위해 지역간 트래픽을 모두 RPC(8300/tcp)를 통해 수행하여 TLS만으로 보안을 유지하도록 합니다.",-1),_={href:"https://www.consul.io/docs/enterprise/federation",target:"_blank",rel:"noopener noreferrer"},g=e("h3",{id:"_3-redendancy-zones",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_3-redendancy-zones"},[e("span",null,"3. Redendancy Zones")])],-1),m=e("p",null,'일반적으로 클러스터의 관리 서버는 3중화하여 구성하며 Raft 알고리즘을 통해 리더 선출을 하는 방식을 취합니다. HA를 위해 해당 투표에 참여하지 않은 추가 잉여 서버 노드를 "상시 대기" 시킬 수 있으며 장애 발생 시 해당 노드는 투표 구성원으로 승격 됩니다. 이는 서버 노드에 대한 추가 구성원임과 동시에 복구 기능을 제공합니다.',-1),w={href:"https://learn.hashicorp.com/tutorials/consul/autopilot-datacenter-operations#redundancy-zones",target:"_blank",rel:"noopener noreferrer"},f=e("h3",{id:"_4-enganced-read-scalability",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_4-enganced-read-scalability"},[e("span",null,"4. Enganced Read Scalability")])],-1),b=e("p",null,"앞서의 Redendancy Zone의 비투표 노드는 투표에는 참여하지 않지만 데이터를 복제하고 데이터를 읽을 수 있는 동작을 지원합니다. 이를 통해 Consul 클러스터를 확장할 수 있고 읽기/쓰기 지연시간을 줄이고 용량을 늘일 수 있습니다.",-1),y={href:"https://www.consul.io/docs/agent/options#_non_voting_server",target:"_blank",rel:"noopener noreferrer"},v=e("h2",{id:"governance-policy",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#governance-policy"},[e("span",null,"Governance & Policy")])],-1),k={href:"https://www.hashicorp.com/blog/enterprise-compliance-and-governance-with-hashicorp-consul-1-8/",target:"_blank",rel:"noopener noreferrer"},S=e("h3",{id:"_1-namespaces",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-namespaces"},[e("span",null,"1. Namespaces")])],-1),C=e("p",null,"Namespaces 기능은 사용자나 팀간의 데이터 격리를 제공합니다. 각각의 Namespace로 구분된 서비스와 정보는 서로 다른 구성원같에 조회가 불가능합니다. 하나의 클러스터로 논리적 분할을 가능하게 합니다.",-1),E={href:"https://www.consul.io/docs/enterprise/namespaces",target:"_blank",rel:"noopener noreferrer"},x=e("h3",{id:"_2-single-sign-on",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2-single-sign-on"},[e("span",null,"2. Single Sign On")])],-1),N=e("p",null,"Open ID Connect를 사용하여 인증을 처리합니다.",-1),F={href:"https://www.consul.io/docs/acl/auth-methods/oidc",target:"_blank",rel:"noopener noreferrer"},R=e("h3",{id:"_3-audit-logging",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_3-audit-logging"},[e("span",null,"3. Audit Logging")])],-1),A=e("p",null,"사용자의 이벤트 시도와 처리에 대한 로그를 캡쳐하고 기록하는 것을 지원합니다. 1.8.0 기준으로 'file'을 지원하며, 향후 추가 타입이 지원될 예정입니다.",-1),G={href:"https://www.consul.io/docs/agent/options#audit",target:"_blank",rel:"noopener noreferrer"},V=e("h3",{id:"_4-sentinel",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_4-sentinel"},[e("span",null,"4. Sentinel")])],-1),T=e("p",null,"Consul에 반영되는 서비스나 KV에 대한 정책을 정의할 수 있습니다. 예를 들면 서비스 등록이나 업데이트에 대한 시간을 강제하거나 Key에 이름 패턴을 강제화 할 수 있습니다.",-1),U={href:"https://www.consul.io/docs/agent/sentinel",target:"_blank",rel:"noopener noreferrer"};function Z(z,L){const t=o("ExternalLinkIcon");return i(),r("div",null,[d,e("p",null,[n("참고 Url : "),e("a",p,[n("https://learn.hashicorp.com/tutorials/consul/network-partition-datacenters"),l(t)])]),h,u,e("p",null,[n("참고 Url : "),e("a",_,[n("https://www.consul.io/docs/enterprise/federation"),l(t)])]),g,m,e("p",null,[n("참고 Url : "),e("a",w,[n("https://learn.hashicorp.com/tutorials/consul/autopilot-datacenter-operations#redundancy-zones"),l(t)])]),f,b,e("p",null,[n("참고 Url : "),e("a",y,[n("https://www.consul.io/docs/agent/options#_non_voting_server"),l(t)])]),v,e("p",null,[n("관련 블로그 : "),e("a",k,[n("https://www.hashicorp.com/blog/enterprise-compliance-and-governance-with-hashicorp-consul-1-8/"),l(t)])]),S,C,e("p",null,[n("참고 Url : "),e("a",E,[n("https://www.consul.io/docs/enterprise/namespaces"),l(t)])]),x,N,e("p",null,[n("참고 Url : "),e("a",F,[n("https://www.consul.io/docs/acl/auth-methods/oidc"),l(t)])]),R,A,e("p",null,[n("참고 Url : "),e("a",G,[n("https://www.consul.io/docs/agent/options#audit"),l(t)])]),V,T,e("p",null,[n("참고 Url : "),e("a",U,[n("https://www.consul.io/docs/agent/sentinel"),l(t)])])])}const O=a(c,[["render",Z],["__file","Consul Enterprise Feature.html.vue"]]),H=JSON.parse('{"path":"/04-HashiCorp/04-Consul/01-Information/Consul%20Enterprise%20Feature.html","title":"Consul Enterprise Feature","lang":"ko-KR","frontmatter":{"description":"Consul Enterprise Feature","tag":["Consul","Enterprise"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/01-Information/Consul%20Enterprise%20Feature.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Consul Enterprise Feature"}],["meta",{"property":"og:description","content":"Consul Enterprise Feature"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"Enterprise"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Consul Enterprise Feature\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Enterprise Global Visibility & Scale","slug":"enterprise-global-visibility-scale","link":"#enterprise-global-visibility-scale","children":[{"level":3,"title":"1. Network Segments","slug":"_1-network-segments","link":"#_1-network-segments","children":[]},{"level":3,"title":"2. Advanced Federation","slug":"_2-advanced-federation","link":"#_2-advanced-federation","children":[]},{"level":3,"title":"3. Redendancy Zones","slug":"_3-redendancy-zones","link":"#_3-redendancy-zones","children":[]},{"level":3,"title":"4. Enganced Read Scalability","slug":"_4-enganced-read-scalability","link":"#_4-enganced-read-scalability","children":[]}]},{"level":2,"title":"Governance & Policy","slug":"governance-policy","link":"#governance-policy","children":[{"level":3,"title":"1. Namespaces","slug":"_1-namespaces","link":"#_1-namespaces","children":[]},{"level":3,"title":"2. Single Sign On","slug":"_2-single-sign-on","link":"#_2-single-sign-on","children":[]},{"level":3,"title":"3. Audit Logging","slug":"_3-audit-logging","link":"#_3-audit-logging","children":[]},{"level":3,"title":"4. Sentinel","slug":"_4-sentinel","link":"#_4-sentinel","children":[]}]}],"git":{"createdTime":1628557352000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":0.5,"words":151},"filePathRelative":"04-HashiCorp/04-Consul/01-Information/Consul Enterprise Feature.md","localizedDate":"2021년 8월 10일","excerpt":"\\n"}');export{O as comp,H as data}; diff --git a/assets/Consul Enterprise Feature.html-CWApFHx1.js b/assets/Consul Enterprise Feature.html-CWApFHx1.js new file mode 100644 index 0000000000..25d7c58c7e --- /dev/null +++ b/assets/Consul Enterprise Feature.html-CWApFHx1.js @@ -0,0 +1,15 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,b as e,d as i,a as s,e as l}from"./app-Bzk8Nrll.js";const c={},d=e("h1",{id:"identifying-consul-split-brain",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#identifying-consul-split-brain"},[e("span",null,"Identifying consul split-brain")])],-1),p={href:"https://support.hashicorp.com/hc/en-us/articles/360058026733-Identifying-and-Recovering-from-a-Consul-Split-Brain",target:"_blank",rel:"noopener noreferrer"},u=l(`
    1. 다음을 실행하여 각 서버에서 Consul 서버 로그를 확인
    consul monitor -log-level=debug
    +
    1. Server들이 투표하는 로그 라인을 확인합니다. 다음과 같이 보여야 합니다.:

    ==정상적인 경우

    2020/10/19 16:21:23 [INFO] raft: Node at 10.90.168.42:8300 [Candidate] entering Candidate state in term 3732
    +2020/10/19 16:21:23 [DEBUG] raft: Votes needed: 2
    +2020/10/19 16:21:23 [DEBUG] raft: Vote granted from foobar in term 3732. Tally: 1
    +

    == 비 정상적인 경우

    2020/10/19 16:28:53 [WARN] raft: Election timeout reached, restarting election
    +2020/10/19 16:28:53 [INFO] raft: Node at 00.00.000.00:8300 [Candidate] entering Candidate state in term 992
    +2020/10/19 16:28:53 [DEBUG] raft: Votes needed: 2
    +2020/10/19 16:28:53 [DEBUG] raft: Vote granted from foobar2 in term 992. Tally: 1
    +2020/10/19 16:28:53 [ERR] raft: Failed to make RequestVote RPC to {Voter <Voter ID>)
    +
    복구 시 정상화된 로그
    +020/10/19 16:29:04 [WARN] raft: Election timeout reached, restarting election
    +2020/10/19 16:29:04 [INFO] raft: Node at 00.00.000.00:8300 [Candidate] entering Candidate state in term 989
    +2020/10/19 16:29:04 [DEBUG] raft: Votes needed: 2
    +2020/10/19 16:29:04 [DEBUG] raft: Vote granted from <ID> in term 989. Tally: 1
    +
    `,8);function g(m,h){const t=a("ExternalLinkIcon");return o(),r("div",null,[d,e("blockquote",null,[e("p",null,[e("a",p,[i("https://support.hashicorp.com/hc/en-us/articles/360058026733-Identifying-and-Recovering-from-a-Consul-Split-Brain"),s(t)])])]),u])}const _=n(c,[["render",g],["__file","Consul Enterprise Feature.html.vue"]]),C=JSON.parse('{"path":"/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Enterprise%20Feature.html","title":"Identifying consul split-brain","lang":"ko-KR","frontmatter":{"description":"Identifying consul split-brain","tag":["Consul"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Enterprise%20Feature.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Identifying consul split-brain"}],["meta",{"property":"og:description","content":"Identifying consul split-brain"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Identifying consul split-brain\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1628557352000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":0.65,"words":194},"filePathRelative":"04-HashiCorp/04-Consul/04-TroubleShooting/Consul Enterprise Feature.md","localizedDate":"2021년 8월 10일","excerpt":"\\n
    \\n

    https://support.hashicorp.com/hc/en-us/articles/360058026733-Identifying-and-Recovering-from-a-Consul-Split-Brain

    \\n
    "}');export{_ as comp,C as data}; diff --git a/assets/Consul Enterprise Feature.html-I88Lp2Lj.js b/assets/Consul Enterprise Feature.html-I88Lp2Lj.js new file mode 100644 index 0000000000..f24553071b --- /dev/null +++ b/assets/Consul Enterprise Feature.html-I88Lp2Lj.js @@ -0,0 +1,385 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c,b as n,d as s,a as e,e as t}from"./app-Bzk8Nrll.js";const p={},r=t('

    Consul Mesh Gateway - K8S x BMs/VMs

    이 문서에서는 Consul을 사용하여 상이한 두 Consul로 구성된 클러스터(마스터가 별개)의 서비스를 연계하는 방법을 설명합니다.

    1. 개요

    1.1 아키텍처

    네트워크 영역이 분리되어있는 두 환경의 애플리케이션 서비스들을 Service Mesh로 구성하는 방법을 알아 봅니다. 이번 구성 예에서는 Kubernetes와 Baremetal(BM)이나 VirtualMachine(VM)에 Consul Cluster(Datacenter)를 구성하고 각 환경의 애플리케이션 서비스를 Mesh Gateway로 연계합니다.

    Mesh Gateway를 사용하면 서로다른 클러스터간에 mTLS 환경의 통신과 서비스 간의 트래픽 통로를 단일화 하여 구성할 수 있습니다. 또한 mTLS내의 데이터가 Gateway에서 해동되지 않기 때문에 두 클러스터간 안전하게 데이터를 송수신 합니다.

    Consul의 각 Cluster는 Datacenter라는 명칭으로 구분됩니다. 이번 구성에서는 Kubernetes의 Consul Datacenter가 Primary의 역할을 합니다.

    HashiCorp General Presentation Template (KR) - Apr 2020 - Google Slides 2020-11-12 15-58-54
    HashiCorp General Presentation Template (KR) - Apr 2020 - Google Slides 2020-11-12 15-58-54

    1.2 Port 구성 참고

    ',10),u=n("br",null,null,-1),d={href:"https://www.consul.io/docs/install/ports",target:"_blank",rel:"noopener noreferrer"},k=t('
    UseDefault PortsCLI
    DNS: The DNS server (TCP and UDP)8600-dns-port
    HTTP: The HTTP API (TCP Only)8500-http-port
    HTTPS: The HTTPs APIdisabled (8501)*-https-port
    gRPC: The gRPC APIdisabled (8502)*-grpc-port
    LAN Serf: The Serf LAN port (TCP and UDP)8301-serf-lan-port
    Wan Serf: The Serf WAN port (TCP and UDP)8302-sert-wan-port
    server: Server RPC address (TCP Only)8300-server-port
    Sidecar Proxy Min: Sidecar 서비스 등록에 사용되는 범위의 최소 포트21000Configration file
    Sidecar Proxy Max: Sidecar 서비스 등록에 사용되는 범위의 최대 포트21255Configration file

    Federation을 위한 포트로는

    ',3),m={href:"https://www.consul.io/docs/agent/options#ports",target:"_blank",rel:"noopener noreferrer"},v=t(`
    ports {
    +  dns = 8600
    +  http = 8500
    +  https = -1
    +  grpc = -1
    +  serf_lan = 8301
    +  serf_wan = 8302
    +  server = 8300
    +  sidecar_min_port = 21000
    +  sidecar_max_port = 21255
    +  expose_min_port = 21500
    +  expose_max_port = 21755
    +}
    +

    2. Kubernetes상에 Consul설치

    2.1 사전 준비 사항

    `,3),b=n("li",null,"Kubernetes가 설치되어있어야 하며, 작업을 수행할 환경에서 접속 가능한 상태여야 합니다.",-1),h={href:"https://helm.sh/docs/intro/install",target:"_blank",rel:"noopener noreferrer"},g=n("h3",{id:"_2-2-설치",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_2-2-설치"},[n("span",null,"2.2 설치")])],-1),y={href:"https://www.consul.io/docs/k8s/helm",target:"_blank",rel:"noopener noreferrer"},f={href:"https://v2.helm.sh/docs/using_helm/#quickstart-guide",target:"_blank",rel:"noopener noreferrer"},_=t(`

    2.2.1 helm Repository 추가

    HashiCorp helm Repository를 추가합니다.

    $ helm repo add hashicorp https://helm.releases.hashicorp.com
    +"hashicorp" has been added to your repositories
    +

    Consul 차트에 접근가능한지 확인합니다.

    $ helm search repo hashicorp/consul
    +NAME                CHART VERSION   APP VERSION DESCRIPTION
    +hashicorp/consul    0.32.1          1.10.0       Official HashiCorp Consul Chart
    +
    CHART LIST

    Consul 차트마다의 기본 매칭되는 버전정보는 다음과 같이 리스트로 확인 가능합니다.

    $ helm search repo hashicorp/consul -l
    +NAME            	CHART VERSION	APP VERSION	DESCRIPTION
    +hashicorp/consul	0.32.1       	1.10.0     	Official HashiCorp Consul Chart
    +hashicorp/consul	0.32.0       	1.10.0     	Official HashiCorp Consul Chart
    +hashicorp/consul	0.31.1       	1.9.4      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.31.0       	1.9.4      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.30.0       	1.9.3      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.29.0       	1.9.2      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.28.0       	1.9.1      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.27.0       	1.9.0      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.26.0       	1.8.5      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.25.0       	1.8.4      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.24.1       	1.8.2      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.24.0       	1.8.1      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.23.1       	1.8.0      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.23.0       	1.8.0      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.22.0       	1.8.0      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.21.0       	1.7.3      	Official HashiCorp Consul Chart
    +hashicorp/consul	0.20.1       	1.7.2      	Official HashiCorp Consul Chart
    +

    2.2.2 Gossip 프토토콜을 위한 Kubernetes Secret 생성

    Kubernetes상에서 Consul Datacenter의 Gossip 프로토콜에서 사용할 키를 생성합니다. 미리 생성하여 값을 넣어도 되고, 생성시 값이 생성되도록 하여도 관계 없습니다.

    $ kubectl create secret generic consul-gossip-encryption-key --from-literal=key=$(consul keygen)
    +
    +--- or ---
    +
    +$ consul keygen
    +h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o=
    +$ kubectl create secret generic consul-gossip-encryption-key --from-literal=key=h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o=
    +

    2.2.3 설치를 위한 사용자 지정 yaml 작성

    `,10),C={href:"https://www.consul.io/docs/k8s/helm",target:"_blank",rel:"noopener noreferrer"},x=t(`
    # consul.yaml
    +global:
    +  name: consul
    +  # 기본이미지(OSS 최신 버전)가 아닌 다른 버전의 컨테이너 이미지 또는 별도의 레지스트리를 사용하는 경우 명시합니다.
    +  image: 'hashicorp/consul-enterprise:1.8.5-ent'
    +  datacenter: 'tsis-k8s'
    +  tls:
    +    # Federation 구성을 위해서는 TLS가 반드시 활성화되어야 합니다.
    +    enabled: true
    +    verify: false
    +    httpsOnly: false
    +
    +  federation:
    +    enabled: true
    +    # Kubernetes가 Primary Datacenter이기 때문에 이 환경에서 Federation을 위한 시크릿을 생성하도록 합니다.
    +    # https://www.consul.io/docs/k8s/installation/multi-cluster/kubernetes#primary-datacenter
    +    createFederationSecret: true
    +  gossipEncryption:
    +    # gossip프로토콜은 암호화되어야 하며, 해당 키는 미리 Kubernetes에 Secret으로 구성합니다.
    +    secretName: consul-gossip-encryption-key
    +    secretKey: key
    +  enableConsulNamespaces: true
    +server:
    +  enterpriseLicense:
    +    secretName: consul-enterprise-license-key
    +    secretKey: key
    +connectInject:
    +  enabled: true
    +  centralConfig:
    +    enabled: true
    +ui:
    +  service:
    +	  # UI에 접속을 위한 타입을 정의합니다.
    +	  # 보안상의 이유로 LoadBalancer기본적으로 서비스를 통해 노출되지 않으므로 kubectl port-forward를 사용하거나
    +    # NodePort로 UI에 접속하는 데 사용해야 합니다.
    +    type: NodePort
    +dns:
    +  enabled: true
    +meshGateway:
    +  # 메시 게이트웨이는 데이터 센터 간의 게이트웨이입니다.
    +  # 데이터 센터 간의 통신이 메시 게이트웨이를 통과하므로 Kubernetes에서 페더레이션을 위해 활성화되어야합니다.
    +  enabled: true
    +  service:
    +    type: NodePort
    +    nodePort: 31001
    +
    +# Ingress Gateway는 Kubernets로 요청되는 주요 관문을 Consul에서 설정하고 Service Mesh기능을 활성화 합니다.
    +# 이번 시나리오에서는 필수 설정이 아닙니다.
    +ingressGateways:
    +  enabled: true
    +  gateways:
    +    - name: ingress-gateway
    +      service:
    +        type: NodePort
    +        ports:
    +          - port: 31000
    +            nodePort: 31000
    +

    2.2.4 Consul Helm Chart 실행

    Helm3을 사용하여 사용자 구성으로 Consul을 설치하려면 다음을 실행합니다. (사용자 구성 파일 : consul.yaml)

    $ helm install consul hashicorp/consul -f consul.yaml --debug
    +

    설치가 완료되고 얼마안있어 Pod를 확인해보면 다음과 같이 확인 가능합니다.

    $ kubectl get pods
    +consul-consul-mesh-gateway-754fbc5575-d8dgt                       2/2     Running   0          2m
    +consul-consul-mesh-gateway-754fbc5575-wkvjh                       2/2     Running   0          2m
    +consul-consul-mh5h6                                               1/1     Running   0          2m
    +consul-consul-mx4mn                                               1/1     Running   0          2m
    +consul-consul-rlb5x                                               1/1     Running   0          2m
    +consul-consul-server-0                                            1/1     Running   0          2m
    +consul-consul-server-1                                            1/1     Running   0          2m
    +consul-consul-server-2                                            1/1     Running   0          2m
    +consul-consul-tbngg                                               1/1     Running   1          2m
    +consul-consul-tz9ct                                               1/1     Running   0          2m
    +

    2.2.5 설치 후 추가 가이드(Option)

    2.2.6 테스트를 위한 Pod 생성(Option)

    Kubertnetes상에서 Mesh Gateway를 사용하기 위한 설정을 확인할 수 있도록 테스트를 위한 Pod를 생성합니다.

    APP SAMPLE
    # k8s-consul-app.yaml
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: counting
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: counting
    +  annotations:
    +    'consul.hashicorp.com/service-tags': servicemesh, consul, counting, v1
    +    'consul.hashicorp.com/connect-inject': 'true'
    +spec:
    +  containers:
    +    - name: counting
    +      image: hashicorp/counting-service:0.0.2
    +      ports:
    +        - containerPort: 9001
    +          name: http
    +  serviceAccountName: counting
    +---
    +apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: dashboard
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: dashboard
    +  labels:
    +    app: 'dashboard'
    +  annotations:
    +    'consul.hashicorp.com/service-tags': servicemesh, consul, dashiboard, v1
    +    'consul.hashicorp.com/connect-inject': 'true'
    +    'consul.hashicorp.com/connect-service-upstreams': 'counting:9001'
    +spec:
    +  containers:
    +    - name: dashboard
    +      image: hashicorp/dashboard-service:0.0.4
    +      ports:
    +        - containerPort: 9002
    +          name: http
    +      env:
    +        - name: COUNTING_SERVICE_URL
    +          value: 'http://localhost:9001'
    +  serviceAccountName: dashboard
    +---
    +apiVersion: 'v1'
    +kind: 'Service'
    +metadata:
    +  name: 'dashboard-service-nodeport'
    +  namespace: 'default'
    +  labels:
    +    app: 'dashboard'
    +spec:
    +  ports:
    +    - protocol: 'TCP'
    +      port: 80
    +      targetPort: 9002
    +  selector:
    +    app: 'dashboard'
    +  type: 'NodePort'
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: dns
    +spec:
    +  containers:
    +    - name: dns
    +      image: anubhavmishra/tiny-tools
    +      command:
    +        - sleep
    +        - infinity
    +

    3. VM/BM상에 Consul설치

    3.1 사전 준비 사항

    `,14),w={href:"https://www.consul.io/docs/install",target:"_blank",rel:"noopener noreferrer"},S={href:"https://www.consul.io/docs/install#precompiled-binaries",target:"_blank",rel:"noopener noreferrer"},P={href:"https://www.consul.io/docs/install#compiling-from-source",target:"_blank",rel:"noopener noreferrer"},q=t(`

    3.2 Consul 바이너리 다운로드와 PATH 설정

    각 환경(Linux/Windows/Mac/FreeBSC/Solaris)에 맞는 바이너리를 받고 압축을 풉니다. consul 혹은 Windows의 경우 consul.exe를 시스템의 적절한 위치에 이동시키고 PATH에 추가시키면 어느곳에서든 접근할 수 있습니다.

    3.2.1 Linux 기반 또는 Mac

    쉘 설정 파일을 편집하여 PATH에 영구적으로 추가할 수 있습니다. 일반적으로 . + 쉘이름 + rc 로 구성되며 bash쉘을 사용하는 경우 ~/.bashrc 가 해당 파일입니다. 해당 파일에서 export PATH=으로 시작하는 :(콜론)으로 구분된 위치에 consul 바이너리 파일 위치를 넣어주거나 없는 경우 기존 PATH에 추가로 기입할 수 있습니다. /tools/consul_dir 디렉토리인경우 다음의 예와 같습니다.

    ...
    +export PATH=$PATH:/tools/consul_dir
    +

    root 권한이 있다면 시스템의 기본 PATH로 지정되어있는 /usr/local/bin디렉토리에 consul을 복사하는 것도 하나의 방법이 될 수 있습니다.

    3.2.2

    Windows

    시스템 설정에서 GUI를 통해 PATH를 추가합니다. 마우스 클릭으로 진행하는 경우 Windows 설정 > 시스템 > 정보 > 시스템 정보 > 고급 시스템 설정 > 고급 탭 > 환경 변수의 단계로 진행합니다. 작업표시줄의 검색창에서 고급 시스템 설정 을 검색하여 고급 탭 > 환경변수 로 이동할 수도 있습니다. 환경 변수 GUI에서 USER 또는 시스템 변수의 Path에 Consul 디렉토리 경로를 추가합니다.

    DESKTOP-LenovoMini 2020-11-15 20-36-36
    DESKTOP-LenovoMini 2020-11-15 20-36-36

    3.3 Primary(k8s) 환경에서 인증서 가져오기

    `,11),T={href:"https://www.consul.io/docs/k8s/installation/multi-cluster/vms-and-kubernetes",target:"_blank",rel:"noopener noreferrer"},M=t(`

    Kubernetes에 구성된 Consul Datacenter가 Primary이기 때문에 해당 환경에서 TLS 인증서를 가져옵니다. 앞서 구성된 Kubernetes 환경에서 CA(Certificate authority cert)와 서명 키(Certificate Authority signing key)를 가져옵니다.

    $ kubectl get secrets/consul-ca-cert \\
    +  --template='{{index .data "tls.crt" }}' | base64 -d > consul-agent-ca.pem
    +$ kubectl get secrets/consul-ca-key \\
    +  --template='{{index .data "tls.key" }}' | base64 -d > consul-agent-ca-key.pem
    +

    두 파일이 생성된 위치에서 consul tls명령을 사용하여 서버에서 사용할 인증서를 생성합니다.

    $ consul tls cert create -server -dc=vm-dc
    +==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
    +==> Saved vm-dc-server-consul-0.pem
    +==> Saved vm-dc-server-consul-0-key.pem
    +

    동일한 위치에서 Client를 위한 인증서를 생성합니다.

    $ consul tls cert create -client -dc=vm-dc
    +==> Using consul-agent-ca.pem and consul-agent-ca-key.pem
    +==> Saved vm-dc-client-consul-0.pem
    +==> Saved vm-dc-client-consul-0-key.pem
    +

    CA 파일과 새로 생성한 파일을 Server와 Client 각 환경에 복사합니다. (e.g. /home/consul/consul-cert/vm-dc-server-consul-0.pem)

    앞서 생성한 파일 이름을 기준으로 복사 대상은 다음과 같습니다.

    3.4 Consul 구성 파일 작성

    CLI를 활용하여 Consul을 구동할 때 구성 옵션을 사용하는 것도 가능하나 여기서는 구성 파일을 작성하여 Consul Server나 Consul Client가 기동할 수 있도록 합니다. Server와 Client에 대한 설정에 약간의 차이가 있을 뿐 대부분이 동일합니다.

    3.4.1 Server

    server = true
    +ui = true
    +bootstrap_expect = 3
    +node_name = "consul_server_01"
    +datacenter = "vm-dc"
    +client_addr = "0.0.0.0"
    +bind_addr = "192.168.100.51"
    +encrypt = "h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o="
    +data_dir = "/var/lib/consul"
    +retry_join = ["192.168.100.51","192.168.100.52","192.168.100.83"]
    +ports {
    +  https = 8501
    +  http = 8500
    +  grpc = 8502
    +}
    +enable_central_service_config = true
    +connect {
    +  enabled = true
    +  enable_mesh_gateway_wan_federation = true
    +}
    +primary_datacenter = "k8s-dc"
    +primary_gateways = ["172.16.1.111:31001","172.16.1.116:31001"]
    +cert_file = "/root/consul-cert/vm-dc-server-consul-0.pem"
    +key_file = "/root/consul-cert/vm-dc-server-consul-0-key.pem"
    +ca_file = "/root/consul-cert/consul-agent-ca.pem"
    +

    3.4.2 Client

    node_name = "consul_client_01"
    +datacenter = "vm-dc"
    +client_addr = "0.0.0.0"
    +bind_addr = "192.168.100.54"
    +encrypt = "h65lqS3w4x42KP+n4Hn9RtK84Rx7zP3WSahZSyD5i1o="
    +data_dir = "/var/lib/consul"
    +retry_join = ["192.168.100.51","192.168.100.52","192.168.100.53"]
    +cert_file = "/root/consul-cert/vm-dc-client-consul-0.pem"
    +key_file = "/root/consul-cert/vm-dc-client-consul-0-key.pem"
    +ca_file = "/root/consul-cert/consul-agent-ca.pem"
    +

    3.4.3 Consul 서비스 구성 (Option)

    Linux 환경이나 Windows환경에서 서비스로 구성하면 시스템 부팅 시 자동으로 시작할 수 있기 때문에 선호되는 설치 방식 중 하나입니다. 이미 설치된 상태라면 앞서 구성을 변경하고 consul reload를 사용하여 구성을 다시 읽어오거나 리스타트 합니다.

    LINUX

    /etc/systemd/system/consul.service에 다음의 서비스 파일을 작성합니다. 필요에 따라 User와 Group을 추가하여 구성하는 것도 가능합니다. 여기서는 consul User를 구성하여 사용하였습니다.

    [Unit]
    +Description=Consul Service Discovery Agent
    +Documentation=https://www.consul.io/
    +After=network-online.target
    +Wants=network-online.target
    +
    +[Service]
    +Type=simple
    +User=consul
    +Group=consul
    +ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d
    +
    +ExecReload=/bin/kill -HUP $MAINPID
    +KillSignal=SIGINT
    +TimeoutStopSec=5
    +Restart=on-failure
    +SyslogIdentifier=consul
    +
    +[Install]
    +WantedBy=multi-user.target
    +

    등록된 서비스를 활성화 하고 시작하여 상대를 확인합니다.

    $ systemctl enable consul
    +$ systemctl start consul
    +$ systemctl status consul
    +● consul.service - Consul Service Discovery Agent
    +   Loaded: loaded (/etc/systemd/system/consul.service; enabled; vendor preset: disabled)
    +   Active: active (running) since 토 2020-11-14 19:05:39 UTC; 17h ago
    +     Docs: https://www.consul.io/
    + Main PID: 1020 (consul)
    +   CGroup: /system.slice/consul.service
    +           └─1020 /usr/local/bin/consul agent -config-dir=/etc/consul.d
    +           
    +$ journalctl -u consul -f
    +1115 13:06:12 cl01-consul-vault-0 consul[1020]: 2020-11-15T13:06:12.265Z [INFO]  agent.server: Handled event for server in area: event=member-join server=consul-consul-server-1.tsis-k8s area=wan
    +1115 13:06:18 cl01-consul-vault-0 consul[1020]: 2020-11-15T13:06:18.119Z [INFO]  agent.server.memberlist.wan: memberlist: Suspect consul-consul-server-0.tsis-k8s has failed, no acks received
    +
    WINDOWS

    Powershell을 활용하여 서비스를 구성합니다.

    > sc.exe create "Consul" binPath= "consul agent -config-dir=C:\\ProgramData\\consul\\config" start= auto
    +[SC] CreateService SUCCESS
    +
    +> sc.exe start "Consul"
    +
    +SERVICE_NAME: Consul
    +        TYPE               : 10  WIN32_OWN_PROCESS
    +        STATE              : 4  RUNNING (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
    +        WIN32_EXIT_CODE    : 0  (0x0)
    +        SERVICE_EXIT_CODE  : 0  (0x0)
    +        CHECKPOINT         : 0x0
    +        WAIT_HINT          : 0x0
    +        PID                : 8008
    +        FLAGS              :
    +

    3.4.4 Federation 확인 (Option)

    Secondary Datacenter인 BM/VM 환경에서 primary_datacenter를 지정하였기 때문에 기동 후 Kubernetes의 Consul과 Join되어 Federation이 구성됩니다.

    Consul-Federation
    Consul-Federation

    4. BM/VM 환경의 Mesh Gateway 구성

    Mesh Gateway를 구성하여 Service Mesh 환경이 멀티/하이브리드 Datacenter 환경을 지원하도록 합니다. Mesh Gateway는 Consul의 내장 Proxy로는 동작하지 못하므로 Envoy 를 설치하여 이를 활용합니다.

    4.1 Envoy 설치

    Consul의 각 버전별 지원하는 Envoy 버전은 다음 표와 같습니다.

    Consul VersionCompatible Envoy Versions
    1.10.x1.18.3, 1.17.3, 1.16.4, 1.15.5
    1.9.x1.16.0, 1.15.2, 1.14.5‡, 1.13.6‡
    1.8.x1.14.5, 1.13.6, 1.12.7, 1.11.2
    1.7.x1.13.6, 1.12.7, 1.11.2, 1.10.0*
    1.6.x, 1.5.3, 1.5.21.11.1, 1.10.0, 1.9.1, 1.8.0†
    1.5.1, 1.5.01.9.1, 1.8.0†
    1.4.x, 1.3.x1.9.1, 1.8.0†, 1.7.0†
    `,29),E={class:"hint-container warning"},G=n("p",{class:"hint-container-title"},"경고",-1),A=n("br",null,null,-1),I={href:"https://github.com/envoyproxy/envoy/issues/6434",target:"_blank",rel:"noopener noreferrer"},H={href:"https://github.com/envoyproxy/envoy/issues/6435",target:"_blank",rel:"noopener noreferrer"},O=n("br",null,null,-1),R=n("code",null,"consul connect envoy",-1),D=n("code",null,"-envoy-version",-1),N={href:"https://www.envoyproxy.io/docs/envoy/latest/start/install",target:"_blank",rel:"noopener noreferrer"},K={href:"https://func-e.io/",target:"_blank",rel:"noopener noreferrer"},V=t(`

    다음 명령을 실행하여 Envoy를 가져와 설치하는 func-e 유틸리티를 다운로드하고 설치합니다.

    curl -L https://func-e.io/install.sh | bash -s -- -b /usr/local/bin
    +

    다음과 같이 대상 환경을 지정할 수 있습니다.

    export FUNC_E_PLATFORM=darwin/amd64
    +
    Go (Golang) GOOS and GOARCH

    A list of GOOS/GOARCH supported by go out of the box

    A list of 32-bit GOOS/GOARCH supported by go out of the box

    A list of 64-bit GOOS/GOARCH supported by go out of the box

    특정 버전을 명시하여 다운로드 하려면 다음 명령을 실행합니다.

    func-e use 1.18.3
    +

    Envoy 바이너리를 $PATH의 위치에 복사합니다. 이를 통해 Consul은 바이너리 위치를 지정하지 않고 Envoy를 자동으로 시작할 수 있습니다.

    sudo cp ~/.func-e/versions/1.18.3/bin/envoy /usr/local/bin/
    +

    다음 명령을 실행하여 Envoy가 $PATH에 있는지 확인합니다.

    envoy --version
    +

    4.2 Mesh Gateway 실행

    Mesh Gateway는 TLS를 필요로하며 Consul과도 TLS로 통신 합니다. 따라서 Consul로의 기본 접속 방식과 포트를 SSL기준으로 설정하여 실행합니다. 또한 앞서 Consul Client를 위해 생성한 인증서를 활용합니다.

    $ export CONSUL_HTTP_SSL=true
    +$ export CONSUL_HTTP_ADDR=https://127.0.0.1:8501
    +$ consul connect envoy -gateway=mesh -register -expose-servers \\
    +  -service "mesh-gateway-secondary" \\
    +  -ca-file=/root/consul-cert/consul-agent-ca.pem \\
    +  -client-cert=/root/consul-cert/vm-dc-client-consul-0.pem \\
    +  -client-key=/root/consul-cert/vm-dc-client-consul-0-key.pem \\
    +  -address '{{ GetInterfaceIP "lo" }}:9100' \\
    +  -wan-address '{{ GetInterfaceIP "eth0" }}:9100' -admin-bind=127.0.0.1:19001 &
    +

    실행 후에는 Consul UI에서도 해당 Mesh Gateway를 확인할 수 있습니다.

    5 TEST (Option)

    `,17),L=n("br",null,null,-1),U={href:"https://github.com/hashicorp/demo-consul-101",target:"_blank",rel:"noopener noreferrer"},B={class:"hint-container details"},$=t(`FOR TEST

    5.1 Counting Service(Backend) on Kubernetes

    앞서 2.2.6 테스트를 위한 Pod 생성의 counting 서비스를 활용합니다.

    apiVersion: v1
    +kind: ServiceAccount
    +metadata:
    +  name: counting
    +---
    +apiVersion: v1
    +kind: Pod
    +metadata:
    +  name: counting
    +  annotations:
    +    'consul.hashicorp.com/service-tags': servicemesh, consul, counting, v1
    +    'consul.hashicorp.com/connect-inject': 'true'
    +spec:
    +  containers:
    +    - name: counting
    +      image: hashicorp/counting-service:0.0.2
    +      ports:
    +        - containerPort: 9001
    +          name: http
    +  serviceAccountName: counting
    +

    5.2 DashBoard Service(Frontend) on BM/VM

    BM/VM 환경에서 Frontend 애플리케이션을 구성합니다. Envoy Proxy를 Sidecar로 구성하여 Service Mesh를 위한 구성을 하고, 다른 Consul 데이터센터에 있는 서비스를 찾을 수 있도록 합니다.

    `,6),F={href:"https://github.com/hashicorp/demo-consul-101/tree/master/services/dashboard-service",target:"_blank",rel:"noopener noreferrer"},W=n("p",null,"애플리케이션 실행을 위해서는 golang이 설치되어야 합니다.",-1),j=t(`

    Dashboard 애플리케이션을 실행합니다.

    $ PORT=9003 COUNTING_SERVICE_URL="http://localhost:5000" go run main.go &
    +
    `,2),z=n("li",null,"PORT : Dashboard 애플리케이션에서 사용하는 포트를 정의합니다.",-1),Z={href:"http://localhost:9001",target:"_blank",rel:"noopener noreferrer"},X=t(`

    9003으로 실행된 Dashboard 애플리케이션을 Consul에 서비스로 등록합니다. Consul의 기본 Configuration 디렉토리 위치에 해당 서비스 구성을 작성하고 읽어올 수 있습니다. (e.g. /etc/consul.d)

    # /etc/consul.d/dashboard.hcl
    +service {
    +  name = "dashboard-vm"
    +  port = 9003
    +
    +  connect {
    +    sidecar_service {
    +      proxy {
    +        upstreams = [
    +          {
    +            destination_name = "counting"
    +            datacenter = "k8s-dc"
    +            local_bind_port = 5000
    +            mesh_gateway {
    +              mode = "local"
    +            }
    +          }
    +        ]
    +      }
    +    }
    +  }
    +
    +  check {
    +    id       = "dashboard-check"
    +    http     = "http://localhost:9003/health"
    +    method   = "GET"
    +    interval = "1s"
    +    timeout  = "1s"
    +  }
    +}
    +

    Dashboard 애플리케이션에서 COUNTING_SERVICE_URL의 대상을 5000번 포트로 지정하였기 때문에 upstream에서 바인딩되는 포트를 맞춰줍니다. 구성이 완료되면 consul reload 명령을 통해 구성 디렉토리의 파일을 반영하고 추가된 서비스를 확인합니다.

    $ consul reload
    +Configuration reload triggered
    +
    +$ consul catalog services
    +consul
    +dashboard-vm
    +

    다음으로 Dashboard 서비스를 위한 Sidecar를 실행하고 추가된 서비스를 확인합니다.

    $ consul connect envoy -sidecar-for dashboard-vm \\
    +  -ca-file=/root/consul-cert/consul-agent-ca.pem \\
    +  -client-cert=/root/consul-cert/vm-dc-client-consul-0.pem \\
    +  -client-key=/root/consul-cert/vm-dc-client-consul-0-key.pem &
    +  
    +$ consul catalog services
    +consul
    +dashboard-vm
    +dashboard-vm-sidecar-proxy
    +

    이제 구성된 9003번 포트를 통해 Frontend에서 외부 데이터센터의 Backend로 요청이 되는지 확인합니다.

    Dashiboard
    Dashiboard

    5.3 Intention

    Sidecar기능이 활성화 되면서 Consul의 Intention기능을 사용할 수 있습니다. Intention을 통해 동적으로 서비스에 대한 트래픽을 통제할 수 있습니다.

    UI 또는 CLI를 통해 dashboard-vmcounting에 접근할 수 없도록 정의합니다.

    $ consul intention create -deny -replace dashboard-vm counting
    +Created: dashboard-vm => counting (deny)
    +
    Edit Intention - Consul 2020-11-16 00-27-50
    Edit Intention - Consul 2020-11-16 00-27-50

    접근할 수 없게 설정되었기 때문에 Sidecar에 주입된 설정으로 Dashboard에서는 Counting서비스에 접근할 수 없다는 메시지를 출력합니다.

    Dashboard-intention
    Dashboard-intention
    `,16);function J(Y,Q){const a=o("ExternalLinkIcon");return i(),c("div",null,[r,n("blockquote",null,[n("p",null,[s("Port 구성에 대한 문서는 다음을 참고합니다."),u,n("a",d,[s("https://www.consul.io/docs/install/ports"),e(a)])])]),k,n("p",null,[s("각 "),n("a",m,[s("포트 구성설정 가이드"),e(a)]),s("는 다음과 같습니다.")]),v,n("ul",null,[b,n("li",null,[s("Helm3 혹은 Helm2를 설치합니다. "),n("ul",null,[n("li",null,[s("Installing Helm : "),n("a",h,[s("https://helm.sh/docs/intro/install"),e(a)])])])])]),g,n("p",null,[s("Kubernetes에서 Consul을 실행하는 권장 방법은 "),n("a",y,[s("Helm 차트를 사용하는 것"),e(a)]),s(" 입니다. Consul을 실행하는 데 필요한 모든 구성 요소를 설치하고 구성합니다. Helm 2를 사용하는 경우 "),n("a",f,[s("Helm 2 설치 가이드"),e(a)]),s(" 에 따라 Tiller를 설치해야합니다.")]),_,n("p",null,[s("Helm 차트로 설치할 때 기본 설정을 엎어쓰는 파일을 생성하여 원하는 구성으로 설치되도록 준비합니다. 각 구성에 대한 설정은 "),n("a",C,[s("Helm Chart Configuration"),e(a)]),s(" 를 참고합니다.")]),x,n("ul",null,[n("li",null,[s("Consul 을 실행할 수 있도록 준비합니다. "),n("ul",null,[n("li",null,[s("Install Consul : "),n("a",w,[s("https://www.consul.io/docs/install"),e(a)])]),n("li",null,[s("BM이나 VM 환경에 Consul을 설치하는 방법은 "),n("a",S,[s("미리 컴파일 된 바이너리"),e(a)]),s("를 사용하여 구성하거나 "),n("a",P,[s("소스"),e(a)]),s("로부터 컴파일하여 사용하는 두가지 방법이 있습니다. 사전 컴파일 된 바이너리를 다운로드하여 사용하는 방법이 가장 쉽습니다. 이번 환경에서는 컴파일된 바이너리를 기준으로 설명합니다.")])])])]),q,n("blockquote",null,[n("p",null,[s("Federation Between VMs and Kubernetes : "),n("a",T,[s("https://www.consul.io/docs/k8s/installation/multi-cluster/vms-and-kubernetes"),e(a)])])]),M,n("div",E,[G,n("p",null,[s("‡ Consul 1.9.x는 1.15.0+의 Envoy를 권장합니다."),A,s(" † 1.9.1 버전 이하의 Envoy는 "),n("a",I,[s("CVE-2019-9900"),e(a)]),s(", "),n("a",H,[s("CVE-2019-9901"),e(a)]),s(" 취약점이 보고되었습니다."),O,s(" * Consul 1.7.x에서 Envoy 1.10.0을 사용하는 경우 "),R,s(" 커맨드 사용시 "),D,s(" 옵션을 포함해야합니다.")])]),n("p",null,[n("a",N,[s("Envoy 웹사이트"),e(a)]),s(" 에서 직접 Envoy의 컨테이너 기반 빌드를 얻거나 "),n("a",K,[s("func-e.io"),e(a)]),s(" 와 같은 3rd party 프로젝트에서 Envoy 바이너리 빌드 패키지를 얻을 수 있습니다.")]),V,n("blockquote",null,[n("p",null,[s("Frontend 애플리케이션을 BM/VM 환경에 구성하고 Backend를 Kubernetes에 구성하는 예제입니다."),L,n("a",U,[s("https://github.com/hashicorp/demo-consul-101"),e(a)])])]),n("details",B,[$,n("blockquote",null,[n("p",null,[n("a",F,[s("https://github.com/hashicorp/demo-consul-101/tree/master/services/dashboard-service"),e(a)])]),W]),j,n("ul",null,[z,n("li",null,[s('COUNTING_SERVICE_URL : Backend 애플리케이션인 Counting Service에 대한 정보입니다. 없는 경우 기본 값은 "'),n("a",Z,[s("http://localhost:9001"),e(a)]),s('" 입니다.')])]),X])])}const an=l(p,[["render",J],["__file","Consul Enterprise Feature.html.vue"]]),en=JSON.parse('{"path":"/04-HashiCorp/04-Consul/03-UseCase/Consul%20Enterprise%20Feature.html","title":"Consul Mesh Gateway - K8S x BMs/VMs","lang":"ko-KR","frontmatter":{"description":"Mesh Gateway k8s and VM","tag":["Consul","Hybrid","Kubetenetes","k8s","VM"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/03-UseCase/Consul%20Enterprise%20Feature.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Consul Mesh Gateway - K8S x BMs/VMs"}],["meta",{"property":"og:description","content":"Mesh Gateway k8s and VM"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/HashiCorp%20General%20Presentation%20Template%20%28KR%29%20-%20Apr%202020%20-%20Google%20Slides%202020-11-12%2015-58-54.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Consul Mesh Gateway - K8S x BMs/VMs"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"Hybrid"}],["meta",{"property":"article:tag","content":"Kubetenetes"}],["meta",{"property":"article:tag","content":"k8s"}],["meta",{"property":"article:tag","content":"VM"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Consul Mesh Gateway - K8S x BMs/VMs\\",\\"image\\":[\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/HashiCorp%20General%20Presentation%20Template%20%28KR%29%20-%20Apr%202020%20-%20Google%20Slides%202020-11-12%2015-58-54.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/DESKTOP-LenovoMini%202020-11-15%2020-36-36.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/consul-datacenter-dropdown.png\\",\\"https://learn.hashicorp.com/img/consul/connect-getting-started/screenshot1.png\\",\\"https://raw.githubusercontent.com/Great-Stone/images/master/uPic/Edit%20Intention%20-%20Consul%202020-11-16%2000-27-50.png\\",\\"https://learn.hashicorp.com/img/consul/connect-getting-started/screenshot2.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1. 개요","slug":"_1-개요","link":"#_1-개요","children":[{"level":3,"title":"1.1 아키텍처","slug":"_1-1-아키텍처","link":"#_1-1-아키텍처","children":[]},{"level":3,"title":"1.2 Port 구성 참고","slug":"_1-2-port-구성-참고","link":"#_1-2-port-구성-참고","children":[]}]},{"level":2,"title":"2. Kubernetes상에 Consul설치","slug":"_2-kubernetes상에-consul설치","link":"#_2-kubernetes상에-consul설치","children":[{"level":3,"title":"2.1 사전 준비 사항","slug":"_2-1-사전-준비-사항","link":"#_2-1-사전-준비-사항","children":[]},{"level":3,"title":"2.2 설치","slug":"_2-2-설치","link":"#_2-2-설치","children":[]}]},{"level":2,"title":"3. VM/BM상에 Consul설치","slug":"_3-vm-bm상에-consul설치","link":"#_3-vm-bm상에-consul설치","children":[{"level":3,"title":"3.1 사전 준비 사항","slug":"_3-1-사전-준비-사항","link":"#_3-1-사전-준비-사항","children":[]},{"level":3,"title":"3.2 Consul 바이너리 다운로드와 PATH 설정","slug":"_3-2-consul-바이너리-다운로드와-path-설정","link":"#_3-2-consul-바이너리-다운로드와-path-설정","children":[]},{"level":3,"title":"3.3 Primary(k8s) 환경에서 인증서 가져오기","slug":"_3-3-primary-k8s-환경에서-인증서-가져오기","link":"#_3-3-primary-k8s-환경에서-인증서-가져오기","children":[]},{"level":3,"title":"3.4 Consul 구성 파일 작성","slug":"_3-4-consul-구성-파일-작성","link":"#_3-4-consul-구성-파일-작성","children":[]}]},{"level":2,"title":"4. BM/VM 환경의 Mesh Gateway 구성","slug":"_4-bm-vm-환경의-mesh-gateway-구성","link":"#_4-bm-vm-환경의-mesh-gateway-구성","children":[{"level":3,"title":"4.1 Envoy 설치","slug":"_4-1-envoy-설치","link":"#_4-1-envoy-설치","children":[]},{"level":3,"title":"4.2 Mesh Gateway 실행","slug":"_4-2-mesh-gateway-실행","link":"#_4-2-mesh-gateway-실행","children":[]}]},{"level":2,"title":"5 TEST (Option)","slug":"_5-test-option","link":"#_5-test-option","children":[]}],"git":{"createdTime":1628557352000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":8.05,"words":2415},"filePathRelative":"04-HashiCorp/04-Consul/03-UseCase/Consul Enterprise Feature.md","localizedDate":"2021년 8월 10일","excerpt":"\\n
    \\n

    이 문서에서는 Consul을 사용하여 상이한 두 Consul로 구성된 클러스터(마스터가 별개)의 서비스를 연계하는 방법을 설명합니다.

    \\n
    \\n

    1. 개요

    \\n

    1.1 아키텍처

    \\n

    네트워크 영역이 분리되어있는 두 환경의 애플리케이션 서비스들을 Service Mesh로 구성하는 방법을 알아 봅니다. 이번 구성 예에서는 Kubernetes와 Baremetal(BM)이나 VirtualMachine(VM)에 Consul Cluster(Datacenter)를 구성하고 각 환경의 애플리케이션 서비스를 Mesh Gateway로 연계합니다.

    "}');export{an as comp,en as data}; diff --git a/assets/Consul Health Check.html-DGZ9Ux0W.js b/assets/Consul Health Check.html-DGZ9Ux0W.js new file mode 100644 index 0000000000..cfbf09623c --- /dev/null +++ b/assets/Consul Health Check.html-DGZ9Ux0W.js @@ -0,0 +1,18 @@ +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as a,c as l,b as e,d as t,a as n,e as r}from"./app-Bzk8Nrll.js";const i={},h=e("h1",{id:"consul-health-check-on-vms",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#consul-health-check-on-vms"},[e("span",null,"Consul Health Check on VMs")])],-1),u={href:"https://www.consul.io/docs/discovery/services",target:"_blank",rel:"noopener noreferrer"},d=e("br",null,null,-1),p={href:"https://learn.hashicorp.com/tutorials/consul/service-registration-health-checks?in=consul/developer-discovery#tuning-scripts-to-be-compatible-with-consul",target:"_blank",rel:"noopener noreferrer"},m=r(`

    1. 스트립트 Health Check를 위한 설정

    Consul config 디렉토리 하위에 monitor.hcl파일을 만듭니다.

    services {
    +  id = "web-service"
    +  namd = "web-service"
    +  address = "10.10.10.201"
    +  port = 8080
    +  checks = [
    +    {
    +      script = "/opt/consul/script/ps-check.sh"
    +      interval = "180s"
    +    }
    +  ]
    +}
    +

    2. 스크립트 Health Check시 Consul설정

    Consul에서 스크립트 기반의 설정시 Config파일 내에 하기와 같은 옵션이 추가되어야 합니다.

    (기존설정)
    +.....
    +enable_script_checks = "true" 또는 
    +enable_local_script_checks = "true"
    +
    +
    `,6),v={href:"https://www.consul.io/docs/agent/options#_enable_script_checks",target:"_blank",rel:"noopener noreferrer"};function k(_,g){const o=c("ExternalLinkIcon");return a(),l("div",null,[h,e("blockquote",null,[e("p",null,[e("a",u,[t("https://www.consul.io/docs/discovery/services"),n(o)]),d,e("a",p,[t("https://learn.hashicorp.com/tutorials/consul/service-registration-health-checks?in=consul/developer-discovery#tuning-scripts-to-be-compatible-with-consul"),n(o)])])]),m,e("p",null,[t("참고 Url : "),e("a",v,[t("https://www.consul.io/docs/agent/options#_enable_script_checks"),n(o)])])])}const f=s(i,[["render",k],["__file","Consul Health Check.html.vue"]]),w=JSON.parse('{"path":"/04-HashiCorp/04-Consul/03-UseCase/Consul%20Health%20Check.html","title":"Consul Health Check on VMs","lang":"ko-KR","frontmatter":{"description":"Consul Health Check","author":"euimokna","tag":["Consul"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/03-UseCase/Consul%20Health%20Check.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Consul Health Check on VMs"}],["meta",{"property":"og:description","content":"Consul Health Check"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:author","content":"euimokna"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Consul Health Check on VMs\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"euimokna\\"}]}"]]},"headers":[{"level":3,"title":"1. 스트립트 Health Check를 위한 설정","slug":"_1-스트립트-health-check를-위한-설정","link":"#_1-스트립트-health-check를-위한-설정","children":[]},{"level":3,"title":"2. 스크립트 Health Check시 Consul설정","slug":"_2-스크립트-health-check시-consul설정","link":"#_2-스크립트-health-check시-consul설정","children":[]}],"git":{"createdTime":1629704782000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"euimokna","email":"euimok@gmail.com","commits":1}]},"readingTime":{"minutes":0.23,"words":70},"filePathRelative":"04-HashiCorp/04-Consul/03-UseCase/Consul Health Check.md","localizedDate":"2021년 8월 23일","excerpt":"\\n
    \\n

    https://www.consul.io/docs/discovery/services
    \\nhttps://learn.hashicorp.com/tutorials/consul/service-registration-health-checks?in=consul/developer-discovery#tuning-scripts-to-be-compatible-with-consul

    \\n
    ","copyright":{"author":"euimokna"}}');export{f as comp,w as data}; diff --git a/assets/Consul Install.html-CqnmzIZZ.js b/assets/Consul Install.html-CqnmzIZZ.js new file mode 100644 index 0000000000..974a4852db --- /dev/null +++ b/assets/Consul Install.html-CqnmzIZZ.js @@ -0,0 +1,5 @@ +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,e}from"./app-Bzk8Nrll.js";const t={},o=e(`

    Consul yum install issue

    AmazonLinux 환경에서 하기와 같은 명령어로 consul 설치 후 systemd 를 통한 Consul 시작시 오류 발생

    sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
    +sudo yum -y install consul
    +

    Consul 시작시 오류로그

    [ec2-user@ip-10-0-10-35 ~]$ sudo systemctl start consul
    +Job for consul.service failed because a configured resource limit was exceeded. See "systemctl status consul.service" and "journalctl -xe" for details.
    +

    원인 및 해결방안

    yum 명령어로 consul설치시 /etc/consul.d/ 경로에 기본적으로 consul.env 파일이 자동으로 생성되는데 해당 파일이 생성되지 않아 수동으로 생성함.

    `,7),l=[o];function c(i,r){return n(),a("div",null,l)}const d=s(t,[["render",c],["__file","Consul Install.html.vue"]]),m=JSON.parse('{"path":"/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Install.html","title":"Consul yum install issue","lang":"ko-KR","frontmatter":{"description":"Identifying consul split-brain","tag":["Consul","install"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Install.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Consul yum install issue"}],["meta",{"property":"og:description","content":"Identifying consul split-brain"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"install"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Consul yum install issue\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Consul 시작시 오류로그","slug":"consul-시작시-오류로그","link":"#consul-시작시-오류로그","children":[]},{"level":2,"title":"원인 및 해결방안","slug":"원인-및-해결방안","link":"#원인-및-해결방안","children":[]}],"git":{"createdTime":1645588712000,"updatedTime":1695042774000,"contributors":[{"name":"Euimokna-cn","email":"myneck@cnmail.co.kr","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.22,"words":67},"filePathRelative":"04-HashiCorp/04-Consul/04-TroubleShooting/Consul Install.md","localizedDate":"2022년 2월 23일","excerpt":"\\n

    AmazonLinux 환경에서 하기와 같은 명령어로 consul 설치 후 systemd 를 통한 Consul 시작시 오류 발생

    \\n
    sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo\\nsudo yum -y install consul\\n
    "}');export{d as comp,m as data}; diff --git a/assets/Consul Sidecar Inject not working on k8s.html-CDhmBQ6y.js b/assets/Consul Sidecar Inject not working on k8s.html-CDhmBQ6y.js new file mode 100644 index 0000000000..4f1c33785b --- /dev/null +++ b/assets/Consul Sidecar Inject not working on k8s.html-CDhmBQ6y.js @@ -0,0 +1,49 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o,c as l,b as n,d as s,a as i,e as c}from"./app-Bzk8Nrll.js";const r={},p=n("h1",{id:"consul-sidecar-inject-not-working-on-k8s",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#consul-sidecar-inject-not-working-on-k8s"},[n("span",null,"Consul Sidecar Inject not working on K8S")])],-1),d=n("blockquote",null,[n("p",null,[s("Consul Version : 1.9.x"),n("br"),s(" Helm Chart : 0.30.0")])],-1),u=n("p",null,[s("Consul을 쿠버네티스 상에 구성하게 되면 "),n("code",null,"annotation"),s(" 구성만으로도 쉽게 Sidecar를 애플리케이션과 함께 배포 가능하다.")],-1),m={href:"https://www.consul.io/docs/k8s/connect#controlling-injection-via-annotation",target:"_blank",rel:"noopener noreferrer"},v=c(`
    annotations:
    +  'consul.hashicorp.com/connect-inject': 'true'
    +

    Consul Sidecar가 Pod 배포시 함께 구성되야 하는 것이 정상이나, Sidecar의 생성 실패나 이미지 가져오기 실패라는 언급도 없이 Sidecar의 injection이 동작하지 않는 경우가 있다.

    Log 확인

    쿠버네티스 상의 Consul을 구성하게 되면 injector가 Sidecar를 함께 배포하는 작업을 수행하므로 먼저 해당 컴포넌트의 로그를 확인한다.

    kubectl logs -n consul -l component=connect-injector -f
    +

    Kubernetes API 확인

    annotation의 동작은 쿠버네티스 컨트롤 플래인, 즉, 쿠버네티스의 API를 통해 요청되므로 해당 API를 통해 Consul에 접근이 가능한지 확인이 필요하다.
    consul-inject에서 kubernetest api 접속이 불가하다면 500에러가 발생한다.

    1. api 접속을 위한 proxy를 활성화
      $ kubectl proxy
      +Starting to serve on 127.0.0.1:8001
      +
    2. 다른 터미널에서 API로 확인
      • 정상인 경우
      $ curl -vv localhost:8001/api/v1/namespaces/consul/services/https:consul-connect-injector-svc:443/proxy/health/ready
      +*   Trying 127.0.0.1...
      +* TCP_NODELAY set
      +* Connected to localhost (127.0.0.1) port 8001 (#0)
      +> GET /api/v1/namespaces/consul/services/https:consul-connect-injector-svc:443/proxy/health/ready HTTP/1.1
      +> Host: localhost:8001
      +> User-Agent: curl/7.61.1
      +> Accept: */*
      +>
      +< HTTP/1.1 204 No Content
      +< Audit-Id: 52947d1d-0c90-47eb-8dc2-6c2efa0193fa
      +< Cache-Control: no-cache, private
      +< Date: Fri, 06 Aug 2021 10:15:21 GMT
      +<
      +* Connection #0 to host localhost left intact
      +
      • 쿠버네티스 API 접근이 안되는 경우
      *   Trying 127.0.0.1...                                      
      +* TCP_NODELAY set                                            
      +* Connected to localhost (127.0.0.1) port 8001 (#0)          
      +> GET /api/v1/namespaces/consul/services/https:consul-connect
      +-injector-svc:443/proxy/health/ready HTTP/1.1                
      +> Host: localhost:8001                                       
      +> User-Agent: curl/7.61.1                                    
      +> Accept: */*                                                
      +>                                                            
      +< HTTP/1.1 500 Internal Server Error                         
      +< Audit-Id: acb30d91-d8db-463e-a91e-1e2a5382329e             
      +< Cache-Control: no-cache, private                           
      +< Content-Length: 178                                        
      +< Content-Type: application/json
      +< Date: Fri, 06 Aug 2021 11:04:38 GMT                        
      +<                                                            
      +{                                                            
      +  "kind": "Status",
      +  "apiVersion": "v1",                                        
      +  "metadata": {                                              
      +                                                            
      +  },                                                         
      +  "status": "Failure",                                       
      +  "message": "error trying to reach service: Address is not a
      +  llowed",                                                     
      +  "code": 500
      +}
      +* Connection #0 to host localhost left intact
      +
    `,8);function k(b,g){const a=t("ExternalLinkIcon");return o(),l("div",null,[p,d,u,n("p",null,[s("참고 : "),n("a",m,[s("Controlling Injection Via Annotation"),i(a)])]),v])}const S=e(r,[["render",k],["__file","Consul Sidecar Inject not working on k8s.html.vue"]]),_=JSON.parse('{"path":"/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Sidecar%20Inject%20not%20working%20on%20k8s.html","title":"Consul Sidecar Inject not working on K8S","lang":"ko-KR","frontmatter":{"description":"SSH Too many authentication failures","tag":["Consul","ServiceMesh","SideCar","Kubernetes","K8S"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Sidecar%20Inject%20not%20working%20on%20k8s.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Consul Sidecar Inject not working on K8S"}],["meta",{"property":"og:description","content":"SSH Too many authentication failures"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"ServiceMesh"}],["meta",{"property":"article:tag","content":"SideCar"}],["meta",{"property":"article:tag","content":"Kubernetes"}],["meta",{"property":"article:tag","content":"K8S"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Consul Sidecar Inject not working on K8S\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Log 확인","slug":"log-확인","link":"#log-확인","children":[]},{"level":2,"title":"Kubernetes API 확인","slug":"kubernetes-api-확인","link":"#kubernetes-api-확인","children":[]}],"git":{"createdTime":1628557352000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":0.83,"words":250},"filePathRelative":"04-HashiCorp/04-Consul/04-TroubleShooting/Consul Sidecar Inject not working on k8s.md","localizedDate":"2021년 8월 10일","excerpt":"\\n
    \\n

    Consul Version : 1.9.x
    \\nHelm Chart : 0.30.0

    \\n
    \\n

    Consul을 쿠버네티스 상에 구성하게 되면 annotation 구성만으로도 쉽게 Sidecar를 애플리케이션과 함께 배포 가능하다.

    \\n

    참고 : Controlling Injection Via Annotation

    "}');export{S as comp,_ as data}; diff --git a/assets/CredentialConfig.html-B_-qDT_l.js b/assets/CredentialConfig.html-B_-qDT_l.js new file mode 100644 index 0000000000..0dc05872c6 --- /dev/null +++ b/assets/CredentialConfig.html-B_-qDT_l.js @@ -0,0 +1,47 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c,b as n,d as e,a as s,e as t}from"./app-Bzk8Nrll.js";const r={},p=t('

    Alibaba CLI 설정

    1. CLI 설치

    1.1 Download 방식

    ',3),u={href:"https://partners-intl.aliyun.com/help/doc-detail/139508.htm",target:"_blank",rel:"noopener noreferrer"},d={href:"https://github.com/aliyun/aliyun-cli/releases",target:"_blank",rel:"noopener noreferrer"},h=n("ul",null,[n("li",null,"CLI 릴리즈 페이지에서 OS에 맞는 파일을 다운로드하여 사용")],-1),m=n("h3",{id:"_1-2-mac-brew-설치",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-2-mac-brew-설치"},[n("span",null,"1.2 MAC - brew 설치")])],-1),g={href:"https://formulae.brew.sh/formula/aliyun-cli",target:"_blank",rel:"noopener noreferrer"},k=n("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[n("pre",{class:"language-bash"},[n("code",null,[e("brew "),n("span",{class:"token function"},"install"),e(` aliyun-cli +`)])])],-1),b=n("h2",{id:"_2-구성-설정",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_2-구성-설정"},[n("span",null,"2. 구성 설정")])],-1),_=n("p",null,[e("인증 메커니즘이 4가지이며 구성 설정시 "),n("code",null,"--mode"),e(" 에 Credential type을 넣어서 구성하게 됨")],-1),v=n("thead",null,[n("tr",null,[n("th",{style:{"text-align":"left"}},"Credential type"),n("th",{style:{"text-align":"left"}},"Limit"),n("th",{style:{"text-align":"left"}},"Interactive credential configuration (fast)"),n("th",{style:{"text-align":"left"}},"Non-interactive credential configuration")])],-1),f=n("td",{style:{"text-align":"left"}},"AK",-1),y=n("td",{style:{"text-align":"left"}},"Use an AccessKey ID and an AccessKey secret to authorize access.",-1),q={style:{"text-align":"left"}},C={href:"https://partners-intl.aliyun.com/help/doc-detail/121258.htm#section-5pj-p7j-06z",target:"_blank",rel:"noopener noreferrer"},x={style:{"text-align":"left"}},A={href:"https://partners-intl.aliyun.com/help/doc-detail/121259.htm#section-hhx-jpx-95g",target:"_blank",rel:"noopener noreferrer"},R=n("td",{style:{"text-align":"left"}},"StsToken",-1),I=n("td",{style:{"text-align":"left"}},"Use a Security Token Service (STS) token to authorize access.",-1),L={style:{"text-align":"left"}},w={href:"https://partners-intl.aliyun.com/help/doc-detail/121258.htm#section-bdk-377-tnm",target:"_blank",rel:"noopener noreferrer"},K={style:{"text-align":"left"}},S={href:"https://partners-intl.aliyun.com/help/doc-detail/121259.htm#section-mek-l1j-xib",target:"_blank",rel:"noopener noreferrer"},j=n("td",{style:{"text-align":"left"}},"RamRoleArn",-1),D=n("td",{style:{"text-align":"left"}},"Use the role of a Resource Access Management (RAM) user to authorize access.",-1),T={style:{"text-align":"left"}},E={href:"https://partners-intl.aliyun.com/help/doc-detail/121258.htm#section-h4x-fnh-5yj",target:"_blank",rel:"noopener noreferrer"},N={style:{"text-align":"left"}},P={href:"https://partners-intl.aliyun.com/help/doc-detail/121259.htm#section-uyo-8pk-uow",target:"_blank",rel:"noopener noreferrer"},z=n("td",{style:{"text-align":"left"}},"EcsRamRole",-1),M=n("td",{style:{"text-align":"left"}},"Use the RAM role of an Elastic Compute Service (ECS) instance to authorize password-free access.",-1),F={style:{"text-align":"left"}},V={href:"https://partners-intl.aliyun.com/help/doc-detail/121258.htm#section-pq4-04b-7an",target:"_blank",rel:"noopener noreferrer"},B={style:{"text-align":"left"}},O={href:"https://partners-intl.aliyun.com/help/doc-detail/121259.htm#section-874-dbh-9k0",target:"_blank",rel:"noopener noreferrer"},U=t(`

    2.1 AK 생성

    AccessKey 방식의 인증 정보가 없는 경우 아래와 같이 생성

    2.2 CLI 구성

    aliyun cli의 configure 를 실행

    $ aliyun configure --mode AK --profile myprofile
    +Configuring profile 'myprofile' in 'AK' authenticate mode...
    +Access Key Id []: LTAI5**********************t88V
    +Access Key Secret []: *******************************
    +Default Region Id []: ap-southeast-1
    +Default Output Format [json]: json (Only support json)
    +Default Language [zh|en] en: en
    +Saving profile[gslee] ...Done.
    +
    +Configure Done!!!
    +

    구성 완료 후 home 디렉토리의 .aliyun 디렉토리 내의 config.json에서 구성된 정보를 확인 가능

    {
    +    "current": "myprofile",
    +    "profiles": [
    +        {
    +            "name": "default",
    +        },
    +        {
    +            "name": "gslee",
    +            "mode": "AK",
    +            "access_key_id": "LTAI5**********************t88V",
    +            "region_id": "ap-southeast-1",
    +            "output_format": "json",
    +            "language": "en",
    +        }
    +    ],
    +    "meta_path": ""
    +}
    +

    2.3 CLI 사용 예제

    Region 목록 확인

    profile이 default인 경우 --profile or -p 생략 가능

    $ aliyun -p myprofile ecs DescribeRegions 
    +{
    +    "Regions": {
    +        "Region": [
    +            {
    +                "LocalName": "华北1(青岛)",
    +                "RegionEndpoint": "ecs.aliyuncs.com",
    +                "RegionId": "cn-qingdao"
    +            },
    +            {
    +                "LocalName": "华北2(北京)",
    +                "RegionEndpoint": "ecs.aliyuncs.com",
    +                "RegionId": "cn-beijing"
    +            },
    +        ]
    +    },
    +    "RequestId": "2304DF19-CABF-54DF-BDC6-F889C3A73E4F"
    +}
    +
    `,13);function Z($,G){const a=o("ExternalLinkIcon");return i(),c("div",null,[p,n("ul",null,[n("li",null,[e("Install guide : "),n("a",u,[e("https://partners-intl.aliyun.com/help/doc-detail/139508.htm"),s(a)])]),n("li",null,[e("Release Download Page : "),n("a",d,[e("https://github.com/aliyun/aliyun-cli/releases"),s(a)]),h])]),m,n("ul",null,[n("li",null,[e("Install guide : "),n("a",g,[e("https://formulae.brew.sh/formula/aliyun-cli"),s(a)]),k])]),b,_,n("table",null,[v,n("tbody",null,[n("tr",null,[f,y,n("td",q,[n("a",C,[e("Configure AccessKey credential"),s(a)])]),n("td",x,[n("a",A,[e("Configure AccessKey credential"),s(a)])])]),n("tr",null,[R,I,n("td",L,[n("a",w,[e("Configure STS token credential"),s(a)])]),n("td",K,[n("a",S,[e("Configure STS token credential"),s(a)])])]),n("tr",null,[j,D,n("td",T,[n("a",E,[e("Configure RamRoleArn credential"),s(a)])]),n("td",N,[n("a",P,[e("Configure RamRoleArn credential"),s(a)])])]),n("tr",null,[z,M,n("td",F,[n("a",V,[e("Configure EcsRamRole credential"),s(a)])]),n("td",B,[n("a",O,[e("Configure EcsRamRole credential"),s(a)])])])])]),U])}const Q=l(r,[["render",Z],["__file","CredentialConfig.html.vue"]]),W=JSON.parse('{"path":"/03-PublicCloud/AlibabaCloud/CredentialConfig.html","title":"Alibaba CLI 설정","lang":"ko-KR","frontmatter":{"description":"Alibaba Cloud CLI","tag":["alibaba","aliyun"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/03-PublicCloud/AlibabaCloud/CredentialConfig.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Alibaba CLI 설정"}],["meta",{"property":"og:description","content":"Alibaba Cloud CLI"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"alibaba"}],["meta",{"property":"article:tag","content":"aliyun"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Alibaba CLI 설정\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1. CLI 설치","slug":"_1-cli-설치","link":"#_1-cli-설치","children":[{"level":3,"title":"1.1 Download 방식","slug":"_1-1-download-방식","link":"#_1-1-download-방식","children":[]},{"level":3,"title":"1.2 MAC - brew 설치","slug":"_1-2-mac-brew-설치","link":"#_1-2-mac-brew-설치","children":[]}]},{"level":2,"title":"2. 구성 설정","slug":"_2-구성-설정","link":"#_2-구성-설정","children":[{"level":3,"title":"2.1 AK 생성","slug":"_2-1-ak-생성","link":"#_2-1-ak-생성","children":[]},{"level":3,"title":"2.2 CLI 구성","slug":"_2-2-cli-구성","link":"#_2-2-cli-구성","children":[]},{"level":3,"title":"2.3 CLI 사용 예제","slug":"_2-3-cli-사용-예제","link":"#_2-3-cli-사용-예제","children":[]}]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.14,"words":342},"filePathRelative":"03-PublicCloud/AlibabaCloud/CredentialConfig.md","localizedDate":"2023년 9월 18일","excerpt":"\\n

    1. CLI 설치

    \\n

    1.1 Download 방식

    \\n"}');export{Q as comp,W as data}; diff --git a/assets/DAS.html-BoEa47a0.js b/assets/DAS.html-BoEa47a0.js new file mode 100644 index 0000000000..a05d0a254f --- /dev/null +++ b/assets/DAS.html-BoEa47a0.js @@ -0,0 +1,205 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as t,c as i,b as n,a as l,w as o,e as p,d as c}from"./app-Bzk8Nrll.js";const r="/assets/nomad_das-DbYtaXiA.png",u={},d=n("h1",{id:"dynamic-application-sizing",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#dynamic-application-sizing"},[n("span",null,"Dynamic application sizing")])],-1),v=n("ul",null,[n("li",null,"Nomad autoscaler 배포 후 사용할 수 있는 기능 중에 하나"),n("li",null,"Dynamic application sizing(DAS)의 기능이 설정되어 있는 job을 배포 한 이후 autoscaler job에서 resource의 권고를 받아올 수 있음"),n("li",null,"권고 받은 값을 사용자가 확인 후 허용할 경우 job의 resource의 변화가 정상적으로 적용됨")],-1),m=n("h2",{id:"autoscaler-job은-기존에-사용하던-job을-사용",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#autoscaler-job은-기존에-사용하던-job을-사용"},[n("span",null,"autoscaler job은 기존에 사용하던 job을 사용")])],-1),k=p(`

    테스트 및 사용전 확인해야 할 사항은 Nomad의 enterprise, 즉 라이선스가 필요하며, nomad-autosclaer의 경우에도 enterprise여야만 합니다.

    Demo job의 배포

    job "example" {
    +  datacenters = ["dc1"]
    +
    +  group "cache-lb" {
    +    count = 1
    +
    +    network {
    +      port "lb" {}
    +    }
    +
    +    task "nginx" {
    +      driver = "docker"
    +
    +      config {
    +        image = "nginx"
    +        ports = ["lb"]
    +        volumes = [
    +          # It's safe to mount this path as a file because it won't re-render.
    +          "local/nginx.conf:/etc/nginx/nginx.conf",
    +          # This path hosts files that will re-render with Consul Template.
    +          "local/nginx:/etc/nginx/conf.d"
    +        ]
    +      }
    +
    +      # This template overwrites the embedded nginx.conf file so it loads
    +      # conf.d/*.conf files outside of the \`http\` block.
    +      template {
    +        data        = <<EOF
    +user  nginx;
    +worker_processes  1;
    +error_log  /var/log/nginx/error.log warn;
    +pid        /var/run/nginx.pid;
    +events {
    +    worker_connections  1024;
    +}
    +include /etc/nginx/conf.d/*.conf;
    +EOF
    +        destination = "local/nginx.conf"
    +      }
    +
    +      # This template creates a TCP proxy to Redis.
    +      template {
    +        data          = <<EOF
    +stream {
    +  server {
    +    listen {{ env "NOMAD_PORT_lb" }};
    +    proxy_pass backend;
    +  }
    +  upstream backend {
    +  {{ range nomadService "redis" }}
    +    server {{ .Address }}:{{ .Port }};
    +  {{ else }}server 127.0.0.1:65535; # force a 502
    +  {{ end }}
    +  }
    +}
    +EOF
    +        destination   = "local/nginx/nginx.conf"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +
    +      resources {
    +        cpu    = 50
    +        memory = 10
    +      }
    +
    +      scaling "cpu" {
    +        policy {
    +          cooldown            = "1m"
    +          evaluation_interval = "10s"
    +
    +          check "95pct" {
    +            strategy "app-sizing-percentile" {
    +              percentile = "95"
    +            }
    +          }
    +        }
    +      }
    +
    +      scaling "mem" {
    +        policy {
    +          cooldown            = "1m"
    +          evaluation_interval = "10s"
    +
    +          check "max" {
    +            strategy "app-sizing-max" {}
    +          }
    +        }
    +      }
    +    }
    +
    +    service {
    +      name         = "redis-lb"
    +      port         = "lb"
    +      address_mode = "host"
    +      provider     = "nomad"
    +    }
    +  }
    +
    +  group "cache" {
    +    count = 3
    +
    +    network {
    +      port "db" {
    +        to = 6379
    +      }
    +    }
    +
    +    task "redis" {
    +      driver = "docker"
    +
    +      config {
    +        image = "redis:6.0"
    +        ports = ["db"]
    +      }
    +
    +      resources {
    +        cpu    = 500
    +        memory = 256
    +      }
    +
    +      scaling "cpu" {
    +        policy {
    +          cooldown            = "1m"
    +          evaluation_interval = "10s"
    +
    +          check "95pct" {
    +            strategy "app-sizing-percentile" {
    +              percentile = "95"
    +            }
    +          }
    +        }
    +      }
    +
    +      scaling "mem" {
    +        policy {
    +          cooldown            = "1m"
    +          evaluation_interval = "10s"
    +
    +          check "max" {
    +            strategy "app-sizing-max" {}
    +          }
    +        }
    +      }
    +
    +      service {
    +        name         = "redis"
    +        port         = "db"
    +        address_mode = "host"
    +        provider     = "nomad"
    +      }
    +    }
    +  }
    +}
    +

    부하 테스트용 job 배포

    job "das-load-test" {
    +  datacenters = ["dc1"]
    +  type        = "batch"
    +
    +  parameterized {
    +    payload       = "optional"
    +    meta_optional = ["requests", "clients"]
    +  }
    +
    +  group "redis-benchmark" {
    +    task "redis-benchmark" {
    +      driver = "docker"
    +
    +      config {
    +        image   = "redis:6.0"
    +        command = "redis-benchmark"
    +
    +        args = [
    +          "-h",
    +          "\${HOST}",
    +          "-p",
    +          "\${PORT}",
    +          "-n",
    +          "\${REQUESTS}",
    +          "-c",
    +          "\${CLIENTS}",
    +        ]
    +      }
    +
    +      template {
    +        destination = "secrets/env.txt"
    +        env         = true
    +
    +        data = <<EOF
    +{{ with nomadService "redis-lb" }}{{ with index . 0 -}}
    +HOST={{.Address}}
    +PORT={{.Port}}
    +{{- end }}{{ end }}
    +REQUESTS={{ or (env "NOMAD_META_requests") "100000" }}
    +CLIENTS={{  or (env "NOMAD_META_clients") "50" }}
    +EOF
    +      }
    +
    +      resources {
    +        cpu    = 100
    +        memory = 128
    +      }
    +    }
    +  }
    +}
    +

    부하테스트를 진행하고 nomad ui에서 Optimize의 Recommended의 값이 변화하였고, Accept를 눌러 줄 경우 target job의 스펙이 변화합니다.

    nomad Optimize
    nomad Optimize
    ',7);function b(g,q){const s=e("RouteLink");return t(),i("div",null,[d,v,m,n("ul",null,[n("li",null,[l(s,{to:"/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html"},{default:o(()=>[c("AutoScaler job")]),_:1})])]),k])}const _=a(u,[["render",b],["__file","DAS.html.vue"]]),j=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html","title":"Dynamic application sizing","lang":"ko-KR","frontmatter":{"description":"Nomad Dynamic application sizing","tag":["Nomad","sample","job","autoscaler","das"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Dynamic application sizing"}],["meta",{"property":"og:description","content":"Nomad Dynamic application sizing"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"sample"}],["meta",{"property":"article:tag","content":"job"}],["meta",{"property":"article:tag","content":"autoscaler"}],["meta",{"property":"article:tag","content":"das"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Dynamic application sizing\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"autoscaler job은 기존에 사용하던 job을 사용","slug":"autoscaler-job은-기존에-사용하던-job을-사용","link":"#autoscaler-job은-기존에-사용하던-job을-사용","children":[{"level":3,"title":"Demo job의 배포","slug":"demo-job의-배포","link":"#demo-job의-배포","children":[]},{"level":3,"title":"부하 테스트용 job 배포","slug":"부하-테스트용-job-배포","link":"#부하-테스트용-job-배포","children":[]},{"level":3,"title":"부하테스트를 진행하고 nomad ui에서 Optimize의 Recommended의 값이 변화하였고, Accept를 눌러 줄 경우 target job의 스펙이 변화합니다.","slug":"부하테스트를-진행하고-nomad-ui에서-optimize의-recommended의-값이-변화하였고-accept를-눌러-줄-경우-target-job의-스펙이-변화합니다","link":"#부하테스트를-진행하고-nomad-ui에서-optimize의-recommended의-값이-변화하였고-accept를-눌러-줄-경우-target-job의-스펙이-변화합니다","children":[]}]}],"git":{"createdTime":1664763179000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"swbs90","email":"swbs90@naver.com","commits":1}]},"readingTime":{"minutes":1.23,"words":370},"filePathRelative":"04-HashiCorp/07-Nomad/05-SampleJob/DAS.md","localizedDate":"2022년 10월 3일","excerpt":"\\n\\n

    autoscaler job은 기존에 사용하던 job을 사용

    \\n"}');export{_ as comp,j as data}; diff --git a/assets/ForwardDns.html-w11anbby.js b/assets/ForwardDns.html-w11anbby.js new file mode 100644 index 0000000000..eb43fdde4e --- /dev/null +++ b/assets/ForwardDns.html-w11anbby.js @@ -0,0 +1,35 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-Bzk8Nrll.js";const t={},o=e(`

    ForwardDns

    Consul dns를 local에서도 사용해야 할 경우에는 dns forward를 해줘야한다. 아래는 ubuntu 환경에서 진행하였음

    설정 명령어

    #systemd-resolved 설정파일 추가 및 변경
    +mkdir -p /etc/systemd/resolved.conf.d
    +(
    +cat <<-EOF
    +[Resolve]
    +DNS=127.0.0.1
    +DNSSEC=false
    +Domains=~consul
    +EOF
    +) | sudo tee /etc/systemd/resolved.conf.d/consul.conf
    +(
    +cat <<-EOF
    +nameserver 127.0.0.1
    +options edns0 trust-ad
    +EOF
    +) | sudo tee /etc/resolv.conf
    +#iptables에 consul dns port 추가
    +iptables --table nat --append OUTPUT --destination localhost --protocol udp --match udp --dport 53 --jump REDIRECT --to-ports 8600
    +iptables --table nat --append OUTPUT --destination localhost --protocol tcp --match tcp --dport 53 --jump REDIRECT --to-ports 8600
    +#service 재시작
    +systemctl restart systemd-resolved
    +

    확인 명령어

    #Global domain에 consul 확인 
    +$ resolvectl domain
    +Global: ~consul
    +Link 5 (docker0):
    +Link 4 (eth2):
    +Link 3 (eth1):
    +Link 2 (eth0):
    +#consul service확인, 해당 클러스터에는 consul server가 3대임
    +$ resolvectl query consul.service.consul
    +consul.service.consul: 172.30.1.100
    +                       172.30.1.101
    +                       172.30.1.102
    +
    +
    `,6),l=[o];function r(p,c){return a(),s("div",null,l)}const u=n(t,[["render",r],["__file","ForwardDns.html.vue"]]),m=JSON.parse('{"path":"/04-HashiCorp/04-Consul/02-Configuration/ForwardDns.html","title":"ForwardDns","lang":"ko-KR","frontmatter":{"description":"Consul ForwardDNS","tag":["Consul","Enterprise","Configuration","ForwardDns"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/02-Configuration/ForwardDns.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"ForwardDns"}],["meta",{"property":"og:description","content":"Consul ForwardDNS"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"Enterprise"}],["meta",{"property":"article:tag","content":"Configuration"}],["meta",{"property":"article:tag","content":"ForwardDns"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ForwardDns\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Consul dns를 local에서도 사용해야 할 경우에는 dns forward를 해줘야한다. 아래는 ubuntu 환경에서 진행하였음","slug":"consul-dns를-local에서도-사용해야-할-경우에는-dns-forward를-해줘야한다-아래는-ubuntu-환경에서-진행하였음","link":"#consul-dns를-local에서도-사용해야-할-경우에는-dns-forward를-해줘야한다-아래는-ubuntu-환경에서-진행하였음","children":[]},{"level":2,"title":"설정 명령어","slug":"설정-명령어","link":"#설정-명령어","children":[]},{"level":2,"title":"확인 명령어","slug":"확인-명령어","link":"#확인-명령어","children":[]}],"git":{"createdTime":1642495573000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"ung","email":"swbs90@naver.com","commits":1}]},"readingTime":{"minutes":0.41,"words":124},"filePathRelative":"04-HashiCorp/04-Consul/02-Configuration/ForwardDns.md","localizedDate":"2022년 1월 18일","excerpt":"\\n

    Consul dns를 local에서도 사용해야 할 경우에는 dns forward를 해줘야한다. 아래는 ubuntu 환경에서 진행하였음

    \\n

    설정 명령어

    \\n
    #systemd-resolved 설정파일 추가 및 변경\\nmkdir -p /etc/systemd/resolved.conf.d\\n(\\ncat <<-EOF\\n[Resolve]\\nDNS=127.0.0.1\\nDNSSEC=false\\nDomains=~consul\\nEOF\\n) | sudo tee /etc/systemd/resolved.conf.d/consul.conf\\n(\\ncat <<-EOF\\nnameserver 127.0.0.1\\noptions edns0 trust-ad\\nEOF\\n) | sudo tee /etc/resolv.conf\\n#iptables에 consul dns port 추가\\niptables --table nat --append OUTPUT --destination localhost --protocol udp --match udp --dport 53 --jump REDIRECT --to-ports 8600\\niptables --table nat --append OUTPUT --destination localhost --protocol tcp --match tcp --dport 53 --jump REDIRECT --to-ports 8600\\n#service 재시작\\nsystemctl restart systemd-resolved\\n
    "}');export{u as comp,m as data}; diff --git a/assets/GCP.html-BxxzWfAX.js b/assets/GCP.html-BxxzWfAX.js new file mode 100644 index 0000000000..5619614e57 --- /dev/null +++ b/assets/GCP.html-BxxzWfAX.js @@ -0,0 +1,56 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-Bzk8Nrll.js";const e={},p=t(`

    Google Cloud Platform Packer Sample

    packer.pkr.hcl

    variable "base_image" {
    +  default = "ubuntu-1804-bionic-v20210415"
    +}
    +variable "project" {
    +  default = "gs-test-282101"
    +}
    +variable "region" {
    +  default = "asia-northeast2"
    +}
    +variable "zone" {
    +  default = "asia-northeast2-a"
    +}
    +variable "image_name" {
    +  
    +}
    +variable "placeholder" {
    +  default     = "placekitten.com"
    +  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
    +}
    +
    +source "googlecompute" "basic-example" {
    +  project_id = var.project
    +  source_image = var.base_image
    +  ssh_username = "ubuntu"
    +  zone = var.zone
    +  disk_size = 10
    +  disk_type = "pd-ssd"
    +  image_name = var.image_name
    +}
    +
    +build {
    +  name = "packer"
    +  source "sources.googlecompute.basic-example" {
    +      name = "packer"
    +  }
    +
    +  provisioner "file"{
    +    source = "./files"
    +    destination = "/tmp/"
    +  }
    +
    +  provisioner "shell" {
    +    inline = [
    +      "sudo apt-get -y update",
    +      "sleep 15",
    +      "sudo apt-get -y update",
    +      "sudo apt-get -y install apache2",
    +      "sudo systemctl enable apache2",
    +      "sudo systemctl start apache2",
    +      "sudo chown -R ubuntu:ubuntu /var/www/html",
    +      "chmod +x /tmp/files/*.sh",
    +      "PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/files/deploy_app.sh",
    +    ]
    +  }
    +}
    +
    `,3),o=[p];function c(l,i){return s(),a("div",null,o)}const k=n(e,[["render",c],["__file","GCP.html.vue"]]),d=JSON.parse('{"path":"/04-HashiCorp/01-Packer/05-SamplePkr/GCP.html","title":"Google Cloud Platform Packer Sample","lang":"ko-KR","frontmatter":{"description":"Packer Sample","tag":["Packer","Sample","GCP"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/01-Packer/05-SamplePkr/GCP.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Google Cloud Platform Packer Sample"}],["meta",{"property":"og:description","content":"Packer Sample"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Packer"}],["meta",{"property":"article:tag","content":"Sample"}],["meta",{"property":"article:tag","content":"GCP"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Google Cloud Platform Packer Sample\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"packer.pkr.hcl","slug":"packer-pkr-hcl","link":"#packer-pkr-hcl","children":[]}],"git":{"createdTime":1632809937000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.49,"words":148},"filePathRelative":"04-HashiCorp/01-Packer/05-SamplePkr/GCP.md","localizedDate":"2021년 9월 28일","excerpt":"\\n

    packer.pkr.hcl

    \\n
    variable \\"base_image\\" {\\n  default = \\"ubuntu-1804-bionic-v20210415\\"\\n}\\nvariable \\"project\\" {\\n  default = \\"gs-test-282101\\"\\n}\\nvariable \\"region\\" {\\n  default = \\"asia-northeast2\\"\\n}\\nvariable \\"zone\\" {\\n  default = \\"asia-northeast2-a\\"\\n}\\nvariable \\"image_name\\" {\\n  \\n}\\nvariable \\"placeholder\\" {\\n  default     = \\"placekitten.com\\"\\n  description = \\"Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net\\"\\n}\\n\\nsource \\"googlecompute\\" \\"basic-example\\" {\\n  project_id = var.project\\n  source_image = var.base_image\\n  ssh_username = \\"ubuntu\\"\\n  zone = var.zone\\n  disk_size = 10\\n  disk_type = \\"pd-ssd\\"\\n  image_name = var.image_name\\n}\\n\\nbuild {\\n  name = \\"packer\\"\\n  source \\"sources.googlecompute.basic-example\\" {\\n      name = \\"packer\\"\\n  }\\n\\n  provisioner \\"file\\"{\\n    source = \\"./files\\"\\n    destination = \\"/tmp/\\"\\n  }\\n\\n  provisioner \\"shell\\" {\\n    inline = [\\n      \\"sudo apt-get -y update\\",\\n      \\"sleep 15\\",\\n      \\"sudo apt-get -y update\\",\\n      \\"sudo apt-get -y install apache2\\",\\n      \\"sudo systemctl enable apache2\\",\\n      \\"sudo systemctl start apache2\\",\\n      \\"sudo chown -R ubuntu:ubuntu /var/www/html\\",\\n      \\"chmod +x /tmp/files/*.sh\\",\\n      \\"PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/files/deploy_app.sh\\",\\n    ]\\n  }\\n}\\n
    "}');export{k as comp,d as data}; diff --git a/assets/GUI01-BXuH5Jcw.png b/assets/GUI01-BXuH5Jcw.png new file mode 100644 index 0000000000..4b5a55c97d Binary files /dev/null and b/assets/GUI01-BXuH5Jcw.png differ diff --git a/assets/GUI02-D7PXN0N9.png b/assets/GUI02-D7PXN0N9.png new file mode 100644 index 0000000000..b35deaef77 Binary files /dev/null and b/assets/GUI02-D7PXN0N9.png differ diff --git a/assets/GUI03-DrpUzYVO.png b/assets/GUI03-DrpUzYVO.png new file mode 100644 index 0000000000..ce7b6ef567 Binary files /dev/null and b/assets/GUI03-DrpUzYVO.png differ diff --git a/assets/GUI04-BArdWfmu.png b/assets/GUI04-BArdWfmu.png new file mode 100644 index 0000000000..f3b5ef9146 Binary files /dev/null and b/assets/GUI04-BArdWfmu.png differ diff --git a/assets/GUI05-D_nSi561.png b/assets/GUI05-D_nSi561.png new file mode 100644 index 0000000000..7aa8ffe973 Binary files /dev/null and b/assets/GUI05-D_nSi561.png differ diff --git a/assets/GUI06-CRJWTx6F.png b/assets/GUI06-CRJWTx6F.png new file mode 100644 index 0000000000..c20349f12a Binary files /dev/null and b/assets/GUI06-CRJWTx6F.png differ diff --git a/assets/HCP_Packer_Intro.html-BtpDpHuz.js b/assets/HCP_Packer_Intro.html-BtpDpHuz.js new file mode 100644 index 0000000000..54182a70fa --- /dev/null +++ b/assets/HCP_Packer_Intro.html-BtpDpHuz.js @@ -0,0 +1,45 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as i,b as e,d as a,a as s,e as t}from"./app-Bzk8Nrll.js";const p={},l=e("h1",{id:"hcp-packer-소개",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#hcp-packer-소개"},[e("span",null,"HCP Packer 소개")])],-1),u=e("p",null,"HashiCorp의 제품은 설치형과 더불어 SaaS 모델로도 사용가능한 모델이 제공됩니다. 여기에는 지금까지 Terraform Cloud, HCP Vault, HCP Consul 이 제공되었습니다. HCP는 HashiCorp Cloud Platform의 약자 입니다.",-1),d={href:"https://cloud.hashicorp.com/",target:"_blank",rel:"noopener noreferrer"},h=e("p",null,"여기에 최근 HCP Packer가 공식적으로 GA(General Available)되었습니다. HashiCorp의 솔루션들에 대해서 우선 OSS(Open Source Software)로 떠올려 볼 수 있지만 기업을 위해 기능이 차별화된 설치형 엔터프라이즈와 더불어 클라우드형 서비스도 제공되고 있으며 향후 새로운 솔루션들이 추가될 전망입니다.",-1),k=e("h2",{id:"packer",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#packer"},[e("span",null,"Packer")])],-1),m=e("p",null,"Packer는 HashiCorp 에서 Vagrant에 이어 두번째로 릴리즈된 OSS 제품 입니다. 기존에는 표준 이미지(골든 이미지) 생성을 위해 사용자가 OS 설치 이후 접속하여 패키지, 파일, 설정 들을 수행 후 빠져나와 이미지를 생성하였다면, Packer는 미리 정의된 구성파일로 이미지를 생성하고 여러 플랫폼에 동시적으로 생성할 수 있습니다.",-1),g=e("p",null,"Amazone EC2(AMI), AZure VM, GCP GCE(Image)와 같은 주요 클라우드 밴더는 물론 국내 클라우드 밴더인 Naver Cloud Platform을 지원하고, 프라이빗 환경의 OpenStack과 VMware, 컨테이너인 Docker를 지원합니다.",-1),_={href:"https://www.packer.io/plugins",target:"_blank",rel:"noopener noreferrer"},v=t('

    플러그인이 다양하게 준비되어 있어 빌드 작업시 스크립트는 당연하고, Ansible 같은 구성관리 코드 툴과도 조합하여 이미지를 생성하는 동작을 코드화하고 자동화 합니다.

    HCP Packer

    HCP Packer가 제공하는 기능은 Packer가 생성한 이미지 Metadata에 대한 Registry 기능입니다. 개념적인 이해가 필요한 부분은 Packer로 생성되는 이미지 자체는 해당 플랫폼에 저장되며 HCP Packer는 해당 이미지에 대한 정보를 저장한다는 것으로 기존 Packer OSS와 함께 사용된다는 점입니다.

    HCP Packer Registry의 활용

    이미지 Metadata에 대한 Registry로서의 기능이 서비스로 제공된다는 것이 어떤 문제를 해결하기 위함인지에 대해 이해가 필요합니다.

    기업 환경에서 표준 이미지에 대한 관리 및 관련하여 Packer를 이용하면 이미지는 자동화되어 쉽게 발생하지만 작성된 이미지를 활용하는데에 어려움이 발생합니다. 몇가지 예를 들면 다음과 같은 문제점이 있습니다.

    여러 문제를 해결하기 위해 Packer에서 빌드 시 HCP Packer Registry에 Metadata를 동시에 등록하고 이미지의 속성 정보를 확인할 수 있게 되어 관리성을 높이고 외부 도구에서 명확한 이미지 ID를 쉽게 얻는 인터페이스를 제공할 수 있습니다.

    Iterations / Channels

    HCP Packer Registry의 주요 개념은 이미지 순환(Iterations)과 이미지 채널(Channels)입니다.

    Packer의 빌드마다 Iterations에 작성된 이미지의 정보가 추가됩니다.

    이렇게 추가된 Iteration 정보는 빌드시마다 기록되어 기존 Packer OSS 대비 이미지 생성에 대한 기록을 확인할 수 있습니다.

    각 Iteration 항목을 클릭하면 빌드의 세부적보를 확인 할 수 있고 아래 이미지에서는 AWS와 Azure에 대한 각 멀티 클라우드, 멀티 리전에 대한 생성 정보를 확인 할 수 있습니다.

    Channels는 특정 Channel에 대해 기존 작성된 Iteration을 할당할 수 있는 객체 입니다. Channel을 통해 Terraform을 포함한 외부 툴은 Iteration의 버전을 신경쓰지 않고 원하는 Channel의 이름만 알고 있으면 항상 유효한 이미지 정보를 취득할 수 있습니다. 아래 이미지에서는 Channel을 사용자가 알기 쉬운 이름으로 구성하고 작성된 Iteration 의 버전을 맵핑하는 것을 확인 할 수 있습니다.

    HCP Packer Template

    HCP Packer에 이미지 Metadata를 등록하는 방법은 기존 Packer로 작성된 선언의 build 블록에 hip_packer_registry 속성을 정의하는 것입니다. 관련 수행을 위한 안내는 learn.hashicorp.com의 내용을 확인할 수 있습니다.

    ',21),b={href:"https://learn.hashicorp.com/collections/packer/hcp-get-started",target:"_blank",rel:"noopener noreferrer"},f=t(`
    build {
    +  hcp_packer_registry {
    +    bucket_name = "learn-packer-ubuntu"
    +    description = <<EOT
    +Some nice description about the image being published to HCP Packer Registry.
    +    EOT
    +    bucket_labels = {
    +      "owner"          = "platform-team"
    +      "os"             = "Ubuntu",
    +      "ubuntu-version" = "Focal 20.04",
    +    }
    +
    +    build_labels = {
    +      {/* "build-time"   = timestamp()
    +      "build-source" = basename(path.cwd) */}
    +    }
    +  }
    +  sources = [
    +    "source.amazon-ebs.basic-example-east",
    +    "source.amazon-ebs.basic-example-west"
    +  ]
    +}
    +

    현재 모든 Packer Plugin이 HCP Packer를 지원하는 것은 아니므로 Plugin 페이지에서 HCP Packer Ready 표시가 되어있는지 확인이 필요합니다. 예를들어 Docker Plugin의 페이지를 확인해보면 지원되고 있는 표시를 확인 할 수 있습니다.

    `,3),P={href:"https://www.packer.io/plugins/builders/docker",target:"_blank",rel:"noopener noreferrer"},C=t('

    기업의 Governance/Policy를 준수

    기업내에서는 이미지에 대한 보안 규정 준수를 위해 Image의 revoke(취소)를 지원합니다. revoke된 Iteration은 관리자에 의해 완전 삭제되지 않는다면 복구하는 것도 가능합니다. 예를 들어 작성된 이미지이용을 중단하고 싶은 경우 Revoke Immediately 요청과 관련 설명을 추가할 수 있습니다.

    Terraform 연계

    HCP Packer의 정보는 외부 솔루션에서도 활용 가능합니다. Terraform과의 워크플로우에서 사용시에도 hcp 프로바이더가 추가되어 저장된 정보를 데이터 소스로 활용 가능합니다.

    ',5),y={href:"https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/packer_image",target:"_blank",rel:"noopener noreferrer"},H={href:"https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/packer_iteration",target:"_blank",rel:"noopener noreferrer"},q=t(`
    # This assumes HCP_CLIENT_ID and HCP_CLIENT_SECRET env variables are set
    +provider "hcp" { }
    +
    +data "hcp_packer_iteration" "ubuntu" {
    +  bucket_name = "learn-packer-ubuntu"
    +  channel     = "development"
    +}
    +
    +data "hcp_packer_image" "ubuntu_us_west_1" {
    +  bucket_name    = "learn-packer-ubuntu"
    +  cloud_provider = "aws"
    +  iteration_id   = data.hcp_packer_iteration.ubuntu.ulid
    +  region         = "us-west-1"
    +}
    +
    +output "ubuntu_iteration" {
    +  value = data.hcp_packer_iteration.ubuntu
    +}
    +
    +output "ubuntu_us_west_1" {
    +  value = data.hcp_packer_image.ubuntu_us_west_1
    +}
    +

    Terraform Cloud Run Task

    Terraform Cloud Business를 사용하는 경우 HCP Packer에서 제공하는 Terraform Cloud Run Tasks기능과 통합시킬 수 있습니다. Terraform Apply시 HCP Packer에서 제공하는 Run Tasks 정책이 적용되면 Plan과 Apply 단계 중간에 명확한 이미지에 대한 확인 및 오류 메시지를 발견할 수 있습니다.

    `,3),w={href:"https://cloud.hashicorp.com/docs/packer/manage-image-use/terraform-cloud-run-tasks",target:"_blank",rel:"noopener noreferrer"},x=e("p",null,"또한 이미지사 사용되는 AWS, Azure, GCP의 리소스에서 하드코딩되는 이미지 ID를 검색하거나 사용하지 못하게 경고 또는 실패하는 동작을 수행 할 수 있습니다.",-1),T={href:"https://cloud.hashicorp.com/docs/packer/manage-image-use/terraform-cloud-run-tasks#supported-resources",target:"_blank",rel:"noopener noreferrer"},I=e("h2",{id:"사용-요금",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#사용-요금"},[e("span",null,"사용 요금")])],-1),S=e("p",null,"무료 플랜이 제공되며 최대 10개의 이미지와 월 250건의 API 요청을 지원합니다. Standard 플랜 부터는 이미지 제한은 없고 시간 당 추적되는 이미지 총 개수와 요청 건에 대해 부과 되며 기술지원이 포함됩니다.",-1),A={href:"https://cloud.hashicorp.com/products/packer/pricing",target:"_blank",rel:"noopener noreferrer"};function R(z,G){const n=o("ExternalLinkIcon");return c(),i("div",null,[l,u,e("ul",null,[e("li",null,[a("HCP : "),e("a",d,[a("https://cloud.hashicorp.com/"),s(n)])])]),h,k,m,g,e("ul",null,[e("li",null,[a("Packer Plugins : "),e("a",_,[a("https://www.packer.io/plugins"),s(n)])])]),v,e("ul",null,[e("li",null,[a("Packer Get Started : "),e("a",b,[a("https://learn.hashicorp.com/collections/packer/hcp-get-started"),s(n)])])]),f,e("ul",null,[e("li",null,[a("Packer - Docker Plugin : "),e("a",P,[a("https://www.packer.io/plugins/builders/docker"),s(n)])])]),C,e("ul",null,[e("li",null,[a("HCP Packer image data source : "),e("a",y,[a("https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/packer_image"),s(n)])]),e("li",null,[a("HCP Packer iteration data source : "),e("a",H,[a("https://registry.terraform.io/providers/hashicorp/hcp/latest/docs/data-sources/packer_iteration"),s(n)])])]),q,e("ul",null,[e("li",null,[a("Terraform Cloud Run Tasks : "),e("a",w,[a("https://cloud.hashicorp.com/docs/packer/manage-image-use/terraform-cloud-run-tasks"),s(n)])])]),x,e("ul",null,[e("li",null,[a("지원되는 리소스 목록 : "),e("a",T,[a("https://cloud.hashicorp.com/docs/packer/manage-image-use/terraform-cloud-run-tasks#supported-resources"),s(n)])])]),I,S,e("ul",null,[e("li",null,[a("Pricing : "),e("a",A,[a("https://cloud.hashicorp.com/products/packer/pricing"),s(n)])])])])}const V=r(p,[["render",R],["__file","HCP_Packer_Intro.html.vue"]]),M=JSON.parse('{"path":"/04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.html","title":"HCP Packer 소개","lang":"ko-KR","frontmatter":{"description":"HCP Packer GA 및 소개","tag":["Packer","HCP","Terraform"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"HCP Packer 소개"}],["meta",{"property":"og:description","content":"HCP Packer GA 및 소개"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://cloud.hashicorp.com/img/docs/packer/hcp_packer_overview.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"HCP Packer 소개"}],["meta",{"property":"article:tag","content":"Packer"}],["meta",{"property":"article:tag","content":"HCP"}],["meta",{"property":"article:tag","content":"Terraform"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"HCP Packer 소개\\",\\"image\\":[\\"https://cloud.hashicorp.com/img/docs/packer/hcp_packer_overview.png\\",\\"https://cloud.hashicorp.com/img/docs/packer/iteration_basic_config.png\\",\\"https://cloud.hashicorp.com/img/docs/packer/multiple_iteration_example.png\\",\\"https://cloud.hashicorp.com/img/docs/packer/multi_cloud_bucket.png\\",\\"https://cloud.hashicorp.com/img/docs/packer/create_new_channel_box.png\\",\\"https://storage.googleapis.com/zenn-user-upload/ef7706869a8f-20220319.png\\",\\"https://storage.googleapis.com/zenn-user-upload/de0b443f013f-20220319.png\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Packer","slug":"packer","link":"#packer","children":[]},{"level":2,"title":"HCP Packer","slug":"hcp-packer","link":"#hcp-packer","children":[]},{"level":2,"title":"HCP Packer Registry의 활용","slug":"hcp-packer-registry의-활용","link":"#hcp-packer-registry의-활용","children":[]},{"level":2,"title":"Iterations / Channels","slug":"iterations-channels","link":"#iterations-channels","children":[]},{"level":2,"title":"HCP Packer Template","slug":"hcp-packer-template","link":"#hcp-packer-template","children":[]},{"level":2,"title":"기업의 Governance/Policy를 준수","slug":"기업의-governance-policy를-준수","link":"#기업의-governance-policy를-준수","children":[]},{"level":2,"title":"Terraform 연계","slug":"terraform-연계","link":"#terraform-연계","children":[]},{"level":2,"title":"Terraform Cloud Run Task","slug":"terraform-cloud-run-task","link":"#terraform-cloud-run-task","children":[]},{"level":2,"title":"사용 요금","slug":"사용-요금","link":"#사용-요금","children":[]}],"git":{"createdTime":1647827749000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.25,"words":376},"filePathRelative":"04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.md","localizedDate":"2022년 3월 21일","excerpt":"\\n

    HashiCorp의 제품은 설치형과 더불어 SaaS 모델로도 사용가능한 모델이 제공됩니다. 여기에는 지금까지 Terraform Cloud, HCP Vault, HCP Consul 이 제공되었습니다. HCP는 HashiCorp Cloud Platform의 약자 입니다.

    \\n\\n

    여기에 최근 HCP Packer가 공식적으로 GA(General Available)되었습니다. HashiCorp의 솔루션들에 대해서 우선 OSS(Open Source Software)로 떠올려 볼 수 있지만 기업을 위해 기능이 차별화된 설치형 엔터프라이즈와 더불어 클라우드형 서비스도 제공되고 있으며 향후 새로운 솔루션들이 추가될 전망입니다.

    "}');export{V as comp,M as data}; diff --git a/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 b/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 new file mode 100644 index 0000000000..0acaaff03d Binary files /dev/null and b/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 differ diff --git a/assets/KaTeX_AMS-Regular-DMm9YOAa.woff b/assets/KaTeX_AMS-Regular-DMm9YOAa.woff new file mode 100644 index 0000000000..b804d7b33a Binary files /dev/null and b/assets/KaTeX_AMS-Regular-DMm9YOAa.woff differ diff --git a/assets/KaTeX_AMS-Regular-DRggAlZN.ttf b/assets/KaTeX_AMS-Regular-DRggAlZN.ttf new file mode 100644 index 0000000000..c6f9a5e7c0 Binary files /dev/null and b/assets/KaTeX_AMS-Regular-DRggAlZN.ttf differ diff --git a/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf b/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf new file mode 100644 index 0000000000..9ff4a5e044 Binary files /dev/null and b/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf differ diff --git a/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff b/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff new file mode 100644 index 0000000000..9759710d1d Binary files /dev/null and b/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff differ diff --git a/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 b/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 new file mode 100644 index 0000000000..f390922ece Binary files /dev/null and b/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 differ diff --git a/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff b/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff new file mode 100644 index 0000000000..9bdd534fd2 Binary files /dev/null and b/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff differ diff --git a/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 b/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 new file mode 100644 index 0000000000..75344a1f98 Binary files /dev/null and b/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 differ diff --git a/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf b/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf new file mode 100644 index 0000000000..f522294ff0 Binary files /dev/null and b/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf differ diff --git a/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf b/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf new file mode 100644 index 0000000000..4e98259c3b Binary files /dev/null and b/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf differ diff --git a/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff b/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff new file mode 100644 index 0000000000..e7730f6627 Binary files /dev/null and b/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff differ diff --git a/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 b/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 new file mode 100644 index 0000000000..395f28beac Binary files /dev/null and b/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 differ diff --git a/assets/KaTeX_Fraktur-Regular-CB_wures.ttf b/assets/KaTeX_Fraktur-Regular-CB_wures.ttf new file mode 100644 index 0000000000..b8461b275f Binary files /dev/null and b/assets/KaTeX_Fraktur-Regular-CB_wures.ttf differ diff --git a/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 b/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 new file mode 100644 index 0000000000..735f6948d6 Binary files /dev/null and b/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 differ diff --git a/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff b/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff new file mode 100644 index 0000000000..acab069f90 Binary files /dev/null and b/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff differ diff --git a/assets/KaTeX_Main-Bold-Cx986IdX.woff2 b/assets/KaTeX_Main-Bold-Cx986IdX.woff2 new file mode 100644 index 0000000000..ab2ad21da6 Binary files /dev/null and b/assets/KaTeX_Main-Bold-Cx986IdX.woff2 differ diff --git a/assets/KaTeX_Main-Bold-Jm3AIy58.woff b/assets/KaTeX_Main-Bold-Jm3AIy58.woff new file mode 100644 index 0000000000..f38136ac1c Binary files /dev/null and b/assets/KaTeX_Main-Bold-Jm3AIy58.woff differ diff --git a/assets/KaTeX_Main-Bold-waoOVXN0.ttf b/assets/KaTeX_Main-Bold-waoOVXN0.ttf new file mode 100644 index 0000000000..4060e627dc Binary files /dev/null and b/assets/KaTeX_Main-Bold-waoOVXN0.ttf differ diff --git a/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 b/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 new file mode 100644 index 0000000000..5931794de4 Binary files /dev/null and b/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 differ diff --git a/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf b/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf new file mode 100644 index 0000000000..dc007977ee Binary files /dev/null and b/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf differ diff --git a/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff b/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff new file mode 100644 index 0000000000..67807b0bd4 Binary files /dev/null and b/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff differ diff --git a/assets/KaTeX_Main-Italic-3WenGoN9.ttf b/assets/KaTeX_Main-Italic-3WenGoN9.ttf new file mode 100644 index 0000000000..0e9b0f354a Binary files /dev/null and b/assets/KaTeX_Main-Italic-3WenGoN9.ttf differ diff --git a/assets/KaTeX_Main-Italic-BMLOBm91.woff b/assets/KaTeX_Main-Italic-BMLOBm91.woff new file mode 100644 index 0000000000..6f43b594b6 Binary files /dev/null and b/assets/KaTeX_Main-Italic-BMLOBm91.woff differ diff --git a/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 b/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 new file mode 100644 index 0000000000..b50920e138 Binary files /dev/null and b/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 differ diff --git a/assets/KaTeX_Main-Regular-B22Nviop.woff2 b/assets/KaTeX_Main-Regular-B22Nviop.woff2 new file mode 100644 index 0000000000..eb24a7ba28 Binary files /dev/null and b/assets/KaTeX_Main-Regular-B22Nviop.woff2 differ diff --git a/assets/KaTeX_Main-Regular-Dr94JaBh.woff b/assets/KaTeX_Main-Regular-Dr94JaBh.woff new file mode 100644 index 0000000000..21f5812968 Binary files /dev/null and b/assets/KaTeX_Main-Regular-Dr94JaBh.woff differ diff --git a/assets/KaTeX_Main-Regular-ypZvNtVU.ttf b/assets/KaTeX_Main-Regular-ypZvNtVU.ttf new file mode 100644 index 0000000000..dd45e1ed2e Binary files /dev/null and b/assets/KaTeX_Main-Regular-ypZvNtVU.ttf differ diff --git a/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf b/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf new file mode 100644 index 0000000000..728ce7a1e2 Binary files /dev/null and b/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf differ diff --git a/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 b/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 new file mode 100644 index 0000000000..29657023ad Binary files /dev/null and b/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 differ diff --git a/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff b/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff new file mode 100644 index 0000000000..0ae390d74c Binary files /dev/null and b/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff differ diff --git a/assets/KaTeX_Math-Italic-DA0__PXp.woff b/assets/KaTeX_Math-Italic-DA0__PXp.woff new file mode 100644 index 0000000000..eb5159d4c1 Binary files /dev/null and b/assets/KaTeX_Math-Italic-DA0__PXp.woff differ diff --git a/assets/KaTeX_Math-Italic-flOr_0UB.ttf b/assets/KaTeX_Math-Italic-flOr_0UB.ttf new file mode 100644 index 0000000000..70d559b4e9 Binary files /dev/null and b/assets/KaTeX_Math-Italic-flOr_0UB.ttf differ diff --git a/assets/KaTeX_Math-Italic-t53AETM-.woff2 b/assets/KaTeX_Math-Italic-t53AETM-.woff2 new file mode 100644 index 0000000000..215c143fd7 Binary files /dev/null and b/assets/KaTeX_Math-Italic-t53AETM-.woff2 differ diff --git a/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf b/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf new file mode 100644 index 0000000000..2f65a8a3a6 Binary files /dev/null and b/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf differ diff --git a/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 b/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 new file mode 100644 index 0000000000..cfaa3bda59 Binary files /dev/null and b/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 differ diff --git a/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff b/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff new file mode 100644 index 0000000000..8d47c02d94 Binary files /dev/null and b/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff differ diff --git a/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 b/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 new file mode 100644 index 0000000000..349c06dc60 Binary files /dev/null and b/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 differ diff --git a/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff b/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff new file mode 100644 index 0000000000..7e02df9636 Binary files /dev/null and b/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff differ diff --git a/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf b/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf new file mode 100644 index 0000000000..d5850df98e Binary files /dev/null and b/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf differ diff --git a/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf b/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf new file mode 100644 index 0000000000..537279f6bd Binary files /dev/null and b/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf differ diff --git a/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff b/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff new file mode 100644 index 0000000000..31b84829b4 Binary files /dev/null and b/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff differ diff --git a/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 b/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 new file mode 100644 index 0000000000..a90eea85f6 Binary files /dev/null and b/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 differ diff --git a/assets/KaTeX_Script-Regular-C5JkGWo-.ttf b/assets/KaTeX_Script-Regular-C5JkGWo-.ttf new file mode 100644 index 0000000000..fd679bf374 Binary files /dev/null and b/assets/KaTeX_Script-Regular-C5JkGWo-.ttf differ diff --git a/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 b/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 new file mode 100644 index 0000000000..b3048fc115 Binary files /dev/null and b/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 differ diff --git a/assets/KaTeX_Script-Regular-D5yQViql.woff b/assets/KaTeX_Script-Regular-D5yQViql.woff new file mode 100644 index 0000000000..0e7da821ee Binary files /dev/null and b/assets/KaTeX_Script-Regular-D5yQViql.woff differ diff --git a/assets/KaTeX_Size1-Regular-C195tn64.woff b/assets/KaTeX_Size1-Regular-C195tn64.woff new file mode 100644 index 0000000000..7f292d9118 Binary files /dev/null and b/assets/KaTeX_Size1-Regular-C195tn64.woff differ diff --git a/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf b/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf new file mode 100644 index 0000000000..871fd7d19d Binary files /dev/null and b/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf differ diff --git a/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 b/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 new file mode 100644 index 0000000000..c5a8462fbf Binary files /dev/null and b/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 differ diff --git a/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf b/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf new file mode 100644 index 0000000000..7a212caf91 Binary files /dev/null and b/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf differ diff --git a/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 b/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 new file mode 100644 index 0000000000..e1bccfe240 Binary files /dev/null and b/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 differ diff --git a/assets/KaTeX_Size2-Regular-oD1tc_U0.woff b/assets/KaTeX_Size2-Regular-oD1tc_U0.woff new file mode 100644 index 0000000000..d241d9be2d Binary files /dev/null and b/assets/KaTeX_Size2-Regular-oD1tc_U0.woff differ diff --git a/assets/KaTeX_Size3-Regular-CTq5MqoE.woff b/assets/KaTeX_Size3-Regular-CTq5MqoE.woff new file mode 100644 index 0000000000..e6e9b658dc Binary files /dev/null and b/assets/KaTeX_Size3-Regular-CTq5MqoE.woff differ diff --git a/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf b/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf new file mode 100644 index 0000000000..00bff3495f Binary files /dev/null and b/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf differ diff --git a/assets/KaTeX_Size4-Regular-BF-4gkZK.woff b/assets/KaTeX_Size4-Regular-BF-4gkZK.woff new file mode 100644 index 0000000000..e1ec545766 Binary files /dev/null and b/assets/KaTeX_Size4-Regular-BF-4gkZK.woff differ diff --git a/assets/KaTeX_Size4-Regular-DWFBv043.ttf b/assets/KaTeX_Size4-Regular-DWFBv043.ttf new file mode 100644 index 0000000000..74f08921f0 Binary files /dev/null and b/assets/KaTeX_Size4-Regular-DWFBv043.ttf differ diff --git a/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 b/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 new file mode 100644 index 0000000000..680c130850 Binary files /dev/null and b/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 differ diff --git a/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff b/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff new file mode 100644 index 0000000000..2432419f28 Binary files /dev/null and b/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff differ diff --git a/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 b/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 new file mode 100644 index 0000000000..771f1af705 Binary files /dev/null and b/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 differ diff --git a/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf b/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf new file mode 100644 index 0000000000..c83252c571 Binary files /dev/null and b/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf differ diff --git a/assets/Keyboard-Eng.html-CK_we2gb.js b/assets/Keyboard-Eng.html-CK_we2gb.js new file mode 100644 index 0000000000..59434f76be --- /dev/null +++ b/assets/Keyboard-Eng.html-CK_we2gb.js @@ -0,0 +1 @@ +import{_ as d}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as n,b as t,d as e,a as c,e as i}from"./app-Bzk8Nrll.js";const s={},p=t("h1",{id:"키보드의-특수기호-영어-명칭",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#키보드의-특수기호-영어-명칭"},[t("span",null,"키보드의 특수기호 영어 명칭")])],-1),l={href:"https://www.facebook.com/DoppioLover/posts/10225430970749092?__cft__%5B0%5D=AZVQVje3HpQ-XcR1aulTomjrKkwP3dgMkwtxvqSrRed0-yZn5vMd_fFoawk9FqeqSj7bIGYg4Ui1zUDYaE2anZDkndlmTgjhFCEnIkTD__lyGfjDt8Kf8od2Ayz3ZPT4PSo&__tn__=%2CO%2CP-R",target:"_blank",rel:"noopener noreferrer"},m=i("
    기호영어 명칭
    ~tilde. 이건 미국에서는 "틸다" 쯤 발음한다. 그런데 이게 뭐라 부르는지 모르는 분들 꽤 많음. 그냥 wavy thingy under escape하는 사람도 본 적이 있다.
    `backtick 또는 backquote
    !exclamation 또는 exclamation point. IT업계 종사자는 bang이라고도 한다.
    @at sign
    #pound 또는 crosshatch 또는 number sign 또는 hash. 한국에서는 흔히 sharp라고 불리는데 의외로 미국에서는 그렇게 부르는 사람을 거의 못봤다. 아마도 한국에서는 음악교육에서 악보일기를 가르쳐서 그런 것 아닐까 싶다.
    $dollar sign
    %percent sign
    ^caret 또는 circumflex. 과학용 계산기에 익숙한 사람들은 exponential power sign이라고 하기도 한다. 그런데 미국사람들도 이걸 뭐라는지 모르는 사람이 의외로 많다. 그냥 6키 위에 있는 것(that thing above 6 key)라고 하는 사람들 간혹 만난다.
    &ampersand 또는 and sign
    *asterisk 또는 star symbol.
    (opening parenthesis. 줄여서 open paren만 하는 경우가 흔하다.
    )closing parenthesis. 줄여서 close paren...
    _underscore. 아주 드물게 underbar라고 하는 사람도 있다.
    -minus sign 또는 hyphen 또는 dash
    +plus sign
    =equal sign
    {opening brace . 흔히 left curly brace라고불린다.
    }closing brace 또는 right curly brace
    [opening bracket. 흔히 left square bracket이라고도 불린다.
    ]closing bracket 또는 right square bracket
    |pipe 또는 vertical bar
    \\back slash 또는 backward slash
    :colon
    ;semicolon 발음은 세미콜론 또는 세마이콜론
    quotation mark 또는 double quote
    apostrophe 또는 single quote
    <less than sign. 가끔 left angle bracket이라고 부르는 경우가 있다.
    ,comma
    >greater than sign. 가끔 right angle bracket이라고 하는 사람들이 있다.
    .period 또는 dot. 일반적으로 period는 영어 언어적 용도로, dot은 컴퓨터 프로그래밍 관련으로 쓰인다.
    ?question mark
    /slash 또는 forward slash.
    ",1);function _(h,g){const r=o("ExternalLinkIcon");return a(),n("div",null,[p,t("blockquote",null,[t("p",null,[e("원본링크 : "),t("a",l,[e("https://www.facebook.com/DoppioLover/posts/10225430970749092?__cft__[0]=AZVQVje3HpQ-XcR1aulTomjrKkwP3dgMkwtxvqSrRed0-yZn5vMd_fFoawk9FqeqSj7bIGYg4Ui1zUDYaE2anZDkndlmTgjhFCEnIkTD__lyGfjDt8Kf8od2Ayz3ZPT4PSo&__tn__=%2CO%2CP-R"),c(r)])])]),m])}const y=d(s,[["render",_],["__file","Keyboard-Eng.html.vue"]]),f=JSON.parse('{"path":"/06-etc/infomation/Keyboard-Eng.html","title":"키보드의 특수기호 영어 명칭","lang":"ko-KR","frontmatter":{"description":"Keyboard key names","tag":["keyboard","tip"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/06-etc/infomation/Keyboard-Eng.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"키보드의 특수기호 영어 명칭"}],["meta",{"property":"og:description","content":"Keyboard key names"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"keyboard"}],["meta",{"property":"article:tag","content":"tip"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"키보드의 특수기호 영어 명칭\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.52,"words":156},"filePathRelative":"06-etc/infomation/Keyboard-Eng.md","localizedDate":"2023년 9월 18일","excerpt":"\\n
    \\n

    원본링크 : https://www.facebook.com/DoppioLover/posts/10225430970749092?__cft__[0]=AZVQVje3HpQ-XcR1aulTomjrKkwP3dgMkwtxvqSrRed0-yZn5vMd_fFoawk9FqeqSj7bIGYg4Ui1zUDYaE2anZDkndlmTgjhFCEnIkTD__lyGfjDt8Kf8od2Ayz3ZPT4PSo&__tn__=%2CO%2CP-R

    \\n
    "}');export{y as comp,f as data}; diff --git a/assets/Kubernetes_scheduler.html-DMN8z5Cq.js b/assets/Kubernetes_scheduler.html-DMN8z5Cq.js new file mode 100644 index 0000000000..e9b3a4f977 --- /dev/null +++ b/assets/Kubernetes_scheduler.html-DMN8z5Cq.js @@ -0,0 +1,3 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as i,c as s,b as e,d as t,a as o,e as a}from"./app-Bzk8Nrll.js";const u={},c=e("h1",{id:"kubernetes-스케쥴러-알고리즘",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#kubernetes-스케쥴러-알고리즘"},[e("span",null,"kubernetes 스케쥴러 알고리즘")])],-1),d={href:"https://github.com/kubernetes/community/blob/master/contributors/devel/sig-scheduling/scheduler_algorithm.md",target:"_blank",rel:"noopener noreferrer"},h={href:"http://scheduler.md",target:"_blank",rel:"noopener noreferrer"},m=a(`

    노드 필터링

    노드 순위 지정

    finalScoreNodeA = (weight1 * priorityFunc1) + (weight2 * priorityFunc2)
    +
    +
    `,5),p=e("li",null,[e("p",null,"모든 노드의 점수가 계산 된 후 점수가 가장 높은 노드가 포드의 호스트로 선택됩니다. 동일한 최고 점수를 가진 노드가 두 개 이상있는 경우 그중에서 임의의 노드가 선택됩니다.")],-1),b=e("p",null,"현재 Kubernetes 스케줄러는 다음과 같은 실용적인 우선 순위 기능을 제공합니다.",-1),g=e("li",null,"LeastRequestedPriority: 노드는 새 Pod가 노드에 예약 된 경우 무료가 될 노드 비율을 기준으로 우선 순위가 지정됩니다. (즉, (용량-노드에 이미있는 모든 포드의 요청 합계-예약중인 포드의 요청) / 용량). CPU와 메모리의 가중치는 동일합니다. 자유 비율이 가장 높은 노드가 가장 선호됩니다. 이 우선 순위 함수는 리소스 소비와 관련하여 노드에 Pod를 분산시키는 효과가 있습니다.",-1),_=e("li",null,"BalancedResourceAllocation:이 우선 순위 함수는 Pod가 배포 된 후 CPU 및 메모리 사용률이 균형을 이루도록 노드에 Pod를 배치하려고합니다.",-1),f=e("li",null,"SelectorSpreadPriority: 동일한 노드에서 동일한 서비스, 복제 컨트롤러 또는 복제본 세트에 속하는 Pod 수를 최소화하여 Pod를 분산합니다. 노드에 영역 정보가있는 경우 포드가 영역과 노드에 분산되도록 우선 순위가 조정됩니다.",-1),P=e("li",null,"CalculateAntiAffinityPriority: 특정 라벨에 대해 동일한 값을 가진 노드에서 동일한 서비스에 속하는 Pod 수를 최소화하여 Pod를 분산합니다.",-1),k=e("li",null,"ImageLocalityPriority: 포드에서 요청한 이미지의 위치에 따라 노드의 우선 순위가 지정됩니다. 팟 (Pod)에 필요한 이미 설치된 패키지의 크기가 더 큰 노드는 팟 (Pod)에 필요한 이미 설치된 패키지가없는 노드 또는 팟 (Pod)에 필요한 이미 설치된 패키지의 작은 총 크기보다 선호됩니다.",-1),y=e("br",null,null,-1),K={href:"http://scheduler.md",target:"_blank",rel:"noopener noreferrer"};function S(v,x){const r=n("ExternalLinkIcon");return i(),s("div",null,[c,e("ul",null,[e("li",null,[t("원본: "),e("a",d,[t("https://github.com/kubernetes/community/blob/master/contributors/devel/sig-scheduling/scheduler_algorithm.md"),o(r)])]),e("li",null,[t("예약되지 않은 각 포드에 대해 Kubernetes 스케줄러는 규칙 집합에 따라 클러스터에서 노드를 찾으려고합니다. Kubernetes 스케줄러에 대한 일반적인 소개는 "),e("a",h,[t("scheduler.md"),o(r)]),t(" 에서 찾을 수 있습니다 . 이 문서에서는 포드의 노드를 선택하는 방법에 대한 알고리즘을 설명합니다. 포드의 대상 노드를 선택하기 전에 두 단계가 있습니다. 첫 번째 단계는 모든 노드를 필터링하고 두 번째 단계는 나머지 노드의 순위를 매겨 포드에 가장 적합한 것을 찾는 것입니다.")])]),m,e("ul",null,[p,e("li",null,[b,e("ul",null,[g,_,f,P,k,e("li",null,[t("NodeAffinityPriority: (Kubernetes v1.2) preferredDuringSchedulingIgnoredDuringExecution노드 선호도를 구현 합니다. 자세한 내용 은 여기 를 참조하십시오."),y,t(" 위의 우선 순위 기능에 대한 자세한 내용은 pkg / scheduler / algorithm / priorities 에서 찾을 수 있습니다 . Kubernetes는 기본적으로 이러한 우선 순위 기능 중 일부를 사용하지만 전부는 아닙니다. pkg / scheduler / algorithmprovider / defaults / defaults.go 에서 기본적으로 사용되는 항목을 확인할 수 있습니다 . 술어와 유사하게, 위의 우선 순위 기능을 결합하고 원하는대로 가중치 요소 (양수)를 할당 할 수 있습니다 ( 사용자 정의 방법은 "),e("a",K,[t("scheduler.md"),o(r)]),t(" 확인 ).")])])])])])}const E=l(u,[["render",S],["__file","Kubernetes_scheduler.html.vue"]]),A=JSON.parse('{"path":"/02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.html","title":"kubernetes 스케쥴러 알고리즘","lang":"ko-KR","frontmatter":{"description":"kubernetes_scheduler","tag":["kubernetes","scheduler","알고리즘"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"kubernetes 스케쥴러 알고리즘"}],["meta",{"property":"og:description","content":"kubernetes_scheduler"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"kubernetes"}],["meta",{"property":"article:tag","content":"scheduler"}],["meta",{"property":"article:tag","content":"알고리즘"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"kubernetes 스케쥴러 알고리즘\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"노드 필터링","slug":"노드-필터링","link":"#노드-필터링","children":[]},{"level":2,"title":"노드 순위 지정","slug":"노드-순위-지정","link":"#노드-순위-지정","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.65,"words":196},"filePathRelative":"02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.md","localizedDate":"2023년 9월 18일","excerpt":"\\n"}');export{E as comp,A as data}; diff --git a/assets/Link.html-Bu_HIehI.js b/assets/Link.html-Bu_HIehI.js new file mode 100644 index 0000000000..60b9decc11 --- /dev/null +++ b/assets/Link.html-Bu_HIehI.js @@ -0,0 +1,11 @@ +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as p,b as a,d as n,a as t,w as r,e}from"./app-Bzk8Nrll.js";const u="/assets/youtube_share-gxLvh877.gif",d={},m=a("h1",{id:"link",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#link"},[a("span",null,"Link")])],-1),h={href:"https://www.markdownguide.org/basic-syntax/#links",target:"_blank",rel:"noopener noreferrer"},k=e(`

    텍스트에 링크 달기

    설명하던 글의 특정 단어에 대해 외부 링크를 추가하고자 하는 경우 브라킷[ ]과 괄호를 사용합니다. domain을 같이 기입하는 경우 새창에서 열기로 표기됩니다.

    새창으로 이동하는 [링크 달기](http://docmoa.github.io/00-Howto/03-Tips/Link.html)
    +현재 창에서 이동하는 [링크 달기](/00-Howto/03-Tips/Link.html)
    +

    다음과 같이 표기됩니다.

    `,4),g={href:"http://docmoa.github.io/00-Howto/03-Tips/Link.html",target:"_blank",rel:"noopener noreferrer"},f=e(`

    링크 자체를 표기

    별도의 연결된 단어 없이 링크 자체를 넣는 경우 < >를 사용합니다.

    <https://docmoa.github.io>
    +<docmoa@gmail.com>
    +

    다음과 같이 표기됩니다.

    `,4),w={href:"https://docmoa.github.io",target:"_blank",rel:"noopener noreferrer"},_=a("li",null,[a("em",null,[a("strong",null,[a("a",{href:"mailto:docmoa@gmail.com"},"docmoa@gmail.com")])])],-1),v=e(`

    이미지

    이미지는 !를 기존 링크 문법 앞에 추가합니다.

    ![대체 텍스트](이미지 링크 "이미지 설명")
    +
    +![](https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/layout-link-icon.png)
    +![대체 텍스트](https://img.icons8.com/ios/2x/깨진링크)
    +![대체 텍스트](https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/report-link-icon.png "이미지 설명")
    +

    다음과 같이 표기됩니다.

    동영상

    `,7),b={href:"https://vuepress-examples.netlify.app/demos/video/",target:"_blank",rel:"noopener noreferrer"},y=e('
    <iframe width="560" height="315" src="https://www.youtube.com/embed/StTqXEQ2l-Y" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
    +

    다음과 같이 표기됩니다.

    `,3),x=a("iframe",{width:"560",height:"315",src:"https://www.youtube.com/embed/StTqXEQ2l-Y",title:"YouTube video player",frameborder:"0",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,-1);function q(L,T){const s=o("ExternalLinkIcon"),c=o("RouteLink");return l(),p("div",null,[m,a("p",null,[n("문서 작성시 외부 링크를 포함하는 예를 설명합니다. "),a("a",h,[n("참고 문서"),t(s)])]),k,a("ul",null,[a("li",null,[n("새창으로 이동하는 "),a("a",g,[n("링크 달기"),t(s)])]),a("li",null,[n("현재 창에서 이동하는 "),t(c,{to:"/00-Howto/03-Tips/Link.html"},{default:r(()=>[n("링크 달기")]),_:1})])]),f,a("ul",null,[a("li",null,[a("em",null,[a("strong",null,[a("a",w,[n("https://docmoa.github.io"),t(s)])])])]),_]),v,a("p",null,[n("외부 동영상은 html 문법을 활용하여 추가할 수 있습니다. 여기서는 유튜브를 예를 들어 설명합니다. 다른 여러가지 방식은 "),a("a",b,[n("참고 링크"),t(s)]),n("를 확인해주세요.")]),y,x])}const z=i(d,[["render",q],["__file","Link.html.vue"]]),E=JSON.parse('{"path":"/00-Howto/03-Tips/Link.html","title":"Link","lang":"ko-KR","frontmatter":{"description":"Link 문서 작성시 외부 링크를 포함하는 예를 설명합니다. 참고 문서 텍스트에 링크 달기 설명하던 글의 특정 단어에 대해 외부 링크를 추가하고자 하는 경우 브라킷[ ]과 괄호를 사용합니다. domain을 같이 기입하는 경우 새창에서 열기로 표기됩니다. 다음과 같이 표기됩니다. 새창으로 이동하는 링크 달기 현재 창에...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/03-Tips/Link.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Link"}],["meta",{"property":"og:description","content":"Link 문서 작성시 외부 링크를 포함하는 예를 설명합니다. 참고 문서 텍스트에 링크 달기 설명하던 글의 특정 단어에 대해 외부 링크를 추가하고자 하는 경우 브라킷[ ]과 괄호를 사용합니다. domain을 같이 기입하는 경우 새창에서 열기로 표기됩니다. 다음과 같이 표기됩니다. 새창으로 이동하는 링크 달기 현재 창에..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/layout-link-icon.png"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:image:alt","content":"Link"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Link\\",\\"image\\":[\\"https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/layout-link-icon.png\\",\\"https://img.icons8.com/ios/2x/깨진링크\\",\\"https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/report-link-icon.png \\\\\\"이미지 설명\\\\\\"\\",\\"https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/layout-link-icon.png\\",\\"https://img.icons8.com/ios/2x/깨진링크\\",\\"https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/report-link-icon.png \\\\\\"이미지 설명\\\\\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"텍스트에 링크 달기","slug":"텍스트에-링크-달기","link":"#텍스트에-링크-달기","children":[]},{"level":2,"title":"링크 자체를 표기","slug":"링크-자체를-표기","link":"#링크-자체를-표기","children":[]},{"level":2,"title":"이미지","slug":"이미지","link":"#이미지","children":[]},{"level":2,"title":"동영상","slug":"동영상","link":"#동영상","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.44,"words":133},"filePathRelative":"00-Howto/03-Tips/Link.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    문서 작성시 외부 링크를 포함하는 예를 설명합니다. 참고 문서

    \\n

    텍스트에 링크 달기

    \\n

    설명하던 글의 특정 단어에 대해 외부 링크를 추가하고자 하는 경우 브라킷[ ]과 괄호를 사용합니다. domain을 같이 기입하는 경우 새창에서 열기로 표기됩니다.

    \\n
    새창으로 이동하는 [링크 달기](http://docmoa.github.io/00-Howto/03-Tips/Link.html)\\n현재 창에서 이동하는 [링크 달기](/00-Howto/03-Tips/Link.html)\\n
    ","autoDesc":true}');export{z as comp,E as data}; diff --git a/assets/Namespace.html-Ch8o5mNT.js b/assets/Namespace.html-Ch8o5mNT.js new file mode 100644 index 0000000000..58d2e05171 --- /dev/null +++ b/assets/Namespace.html-Ch8o5mNT.js @@ -0,0 +1,30 @@ +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o,c as p,b as a,d as e,a as c,e as l}from"./app-Bzk8Nrll.js";const r={},i=a("h1",{id:"nomad-namespace",tabindex:"-1"},[a("a",{class:"header-anchor",href:"#nomad-namespace"},[a("span",null,"Nomad Namespace")])],-1),m=a("br",null,null,-1),d=a("br",null,null,-1),h={href:"https://learn.hashicorp.com/tutorials/nomad/namespaces",target:"_blank",rel:"noopener noreferrer"},u=l(`

    Namespace 생성

    $ nomad namespace apply -description "PoC Application" apps
    +

    Namespace 삭제

    $ nomad namespace delete apps
    +

    Namespace 리스트 확인

    $ nomad namespace list
    +Name      Description
    +default   Default shared namespace
    +

    Job에 Namesapce 지정

    job "rails-www" {
    +
    +    ## Run in the QA environments
    +    namespace = "web-qa"
    +
    +    ## Only run in one datacenter when QAing
    +    datacenters = ["us-west1"]
    +    # ...
    +}
    +

    CLI 사용시 flag 추가하거나 ENV로 생략 가능

    # flag 설정
    +nomad job status -namespace=web-qa
    +
    +# ENV 설정
    +export NOMAD_NAMESPACE=web-qa
    +nomad job status
    +

    ACL 구성시의 예

    # Allow read only access to the production namespace
    +namespace "web-prod" {
    +    policy = "read"
    +}
    +
    +# Allow writing to the QA namespace
    +namespace "web-qa" {
    +    policy = "write"
    +}
    +
    `,12);function g(k,b){const n=t("ExternalLinkIcon");return o(),p("div",null,[i,a("blockquote",null,[a("p",null,[e("Nomad Version : >= 1.0.0"),m,e(" Nomad Ent. Version : >= 0.7.0"),d,a("a",h,[e("https://learn.hashicorp.com/tutorials/nomad/namespaces"),c(n)])])]),u])}const v=s(r,[["render",g],["__file","Namespace.html.vue"]]),_=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/02-Config/Namespace.html","title":"Nomad Namespace","lang":"ko-KR","frontmatter":{"description":"Nomad Namespace","tag":["Nomad","Namespace"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/02-Config/Namespace.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Nomad Namespace"}],["meta",{"property":"og:description","content":"Nomad Namespace"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"Namespace"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Nomad Namespace\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Namespace 생성","slug":"namespace-생성","link":"#namespace-생성","children":[]},{"level":2,"title":"Namespace 삭제","slug":"namespace-삭제","link":"#namespace-삭제","children":[]},{"level":2,"title":"Namespace 리스트 확인","slug":"namespace-리스트-확인","link":"#namespace-리스트-확인","children":[]},{"level":2,"title":"Job에 Namesapce 지정","slug":"job에-namesapce-지정","link":"#job에-namesapce-지정","children":[]},{"level":2,"title":"CLI 사용시 flag 추가하거나 ENV로 생략 가능","slug":"cli-사용시-flag-추가하거나-env로-생략-가능","link":"#cli-사용시-flag-추가하거나-env로-생략-가능","children":[]},{"level":2,"title":"ACL 구성시의 예","slug":"acl-구성시의-예","link":"#acl-구성시의-예","children":[]}],"git":{"createdTime":1628557352000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":3}]},"readingTime":{"minutes":0.38,"words":113},"filePathRelative":"04-HashiCorp/07-Nomad/02-Config/Namespace.md","localizedDate":"2021년 8월 10일","excerpt":"\\n
    \\n

    Nomad Version : >= 1.0.0
    \\nNomad Ent. Version : >= 0.7.0
    \\nhttps://learn.hashicorp.com/tutorials/nomad/namespaces

    \\n
    \\n

    Namespace 생성

    \\n
    $ nomad namespace apply -description \\"PoC Application\\" apps\\n
    "}');export{v as comp,_ as data}; diff --git a/assets/Nomad-Ui-Token.html-DDo04qBT.js b/assets/Nomad-Ui-Token.html-DDo04qBT.js new file mode 100644 index 0000000000..7f81777693 --- /dev/null +++ b/assets/Nomad-Ui-Token.html-DDo04qBT.js @@ -0,0 +1,23 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as o}from"./app-Bzk8Nrll.js";const t="/assets/token_login-CBaI7EPH.png",e="/assets/not_exec-CEumU8bl.png",p={},i=o(`

    Nomad UI Token

    해당 Token의 policy는 특정인이 원하여 만들었으며, 더 다양한 제약과 허용을 할 수 있습니다. 해당 policy는 아래와 같은 제약과 허용을 합니다.

    1. UI에서 exec(job에 접근) 제한
    2. 그 외에 job, node, volume, server등의 모든 화면 읽어오기

    Nomad cli

    #원하는 권한이 있는 policy file
    +$ cat nomad-ui-policy.hcl
    +namespace "*" {
    +  policy       = "read"
    +  capabilities = ["submit-job", "dispatch-job", "read-logs", "list-jobs", "parse-job", "read-job", "csi-list-volume", "csi-read-volume", "list-scaling-policies", "read-scaling-policy", "read-job-scaling", "read-fs"]
    +}
    +node {
    +  policy = "read"
    +}
    +host_volume "*" {
    +  policy = "write"
    +}
    +plugin {
    +  policy = "read"
    +}
    +
    +#위에서 만든 policy 파일을 nomad cluster에 적용
    +$ nomad acl policy apply -description "Production UI policy" prod-ui nomad-ui-policy.hcl
    +
    +#해당 policy로 token생성(policy는 여러개를 넣을 수도 있음)
    +$ nomad acl token create -name="prod ui token" -policy=prod-ui -type=client | tee ui-prod.token
    +#웹 브라우저 로그인을 위해 Secret ID 복사
    +

    Nomad UI

    아래는 위에서 만들어진 토큰으로 로그인한 화면입니다.
    TokenLogin

    아래 그림과 같이 exec버튼이 비활성화되어 있는 걸 볼 수 있습니다.
    exec비활성화

    ',7),c=[i];function l(r,d){return s(),a("div",null,c)}const k=n(p,[["render",l],["__file","Nomad-Ui-Token.html.vue"]]),g=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.html","title":"Nomad UI Token","lang":"ko-KR","frontmatter":{"description":"Nomad ACL","tag":["Nomad","ACL"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Nomad UI Token"}],["meta",{"property":"og:description","content":"Nomad ACL"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"ACL"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Nomad UI Token\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Nomad cli","slug":"nomad-cli","link":"#nomad-cli","children":[]},{"level":2,"title":"Nomad UI","slug":"nomad-ui","link":"#nomad-ui","children":[]}],"git":{"createdTime":1648772616000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"ung","email":"swbs90@naver.com","commits":1}]},"readingTime":{"minutes":0.4,"words":121},"filePathRelative":"04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.md","localizedDate":"2022년 4월 1일","excerpt":"\\n
    \\n

    \\n

    해당 Token의 policy는 특정인이 원하여 만들었으며, 더 다양한 제약과 허용을 할 수 있습니다. 해당 policy는 아래와 같은 제약과 허용을 합니다.

    \\n
      \\n
    1. UI에서 exec(job에 접근) 제한
    2. \\n
    3. 그 외에 job, node, volume, server등의 모든 화면 읽어오기
    4. \\n
    \\n
    \\n

    Nomad cli

    \\n
    #원하는 권한이 있는 policy file\\n$ cat nomad-ui-policy.hcl\\nnamespace \\"*\\" {\\n  policy       = \\"read\\"\\n  capabilities = [\\"submit-job\\", \\"dispatch-job\\", \\"read-logs\\", \\"list-jobs\\", \\"parse-job\\", \\"read-job\\", \\"csi-list-volume\\", \\"csi-read-volume\\", \\"list-scaling-policies\\", \\"read-scaling-policy\\", \\"read-job-scaling\\", \\"read-fs\\"]\\n}\\nnode {\\n  policy = \\"read\\"\\n}\\nhost_volume \\"*\\" {\\n  policy = \\"write\\"\\n}\\nplugin {\\n  policy = \\"read\\"\\n}\\n\\n#위에서 만든 policy 파일을 nomad cluster에 적용\\n$ nomad acl policy apply -description \\"Production UI policy\\" prod-ui nomad-ui-policy.hcl\\n\\n#해당 policy로 token생성(policy는 여러개를 넣을 수도 있음)\\n$ nomad acl token create -name=\\"prod ui token\\" -policy=prod-ui -type=client | tee ui-prod.token\\n#웹 브라우저 로그인을 위해 Secret ID 복사\\n
    "}');export{k as comp,g as data}; diff --git a/assets/Nomad-sslkey-create.html-DXTi_C5p.js b/assets/Nomad-sslkey-create.html-DXTi_C5p.js new file mode 100644 index 0000000000..fd26b7c39b --- /dev/null +++ b/assets/Nomad-sslkey-create.html-DXTi_C5p.js @@ -0,0 +1,15 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a,e as o}from"./app-Bzk8Nrll.js";const n={},d=o(`

    Nomad 인증서 생성

    공식 사이트에 consul 인증서 생성 가이드는 있는데 Nomad 인증서 생성가이드는
    Show Terminal을 들어가야 볼 수 있기때문에 귀찮음을 해결하기 위해 공유합니다.

    Nomad 인증서 생성

    consul tls ca create -domain=nomad -days 3650
    +
    +consul tls cert create -domain=nomad -dc=global  -server -days 3650
    +
    +consul tls cert create -domain=nomad -dc=global  -client -days 3650
    +
    +consul tls cert create -domain=nomad -dc=global  -cli -days 3650
    +

    Nomad env 설정

    export NOMAD_CACERT="\${HOME}/tls/nomad-agent-ca.pem"
    +
    +export NOMAD_CLIENT_CERT="\${HOME}/tls/global-cli-nomad-0.pem"
    +
    +export NOMAD_CLIENT_KEY="\${HOME}/tls/global-cli-nomad-0-key.pem"
    +
    +export NOMAD_ADDR="https://127.0.0.1:4646"
    +
    `,6),c=[d];function l(s,r){return t(),a("div",null,c)}const p=e(n,[["render",l],["__file","Nomad-sslkey-create.html.vue"]]),h=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.html","title":"Nomad 인증서 생성","lang":"ko-KR","frontmatter":{"description":"Nomad Namespace","tag":["Nomad","SSL"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Nomad 인증서 생성"}],["meta",{"property":"og:description","content":"Nomad Namespace"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"SSL"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Nomad 인증서 생성\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Nomad 인증서 생성","slug":"nomad-인증서-생성-1","link":"#nomad-인증서-생성-1","children":[]},{"level":2,"title":"Nomad env 설정","slug":"nomad-env-설정","link":"#nomad-env-설정","children":[]}],"git":{"createdTime":1629555140000,"updatedTime":1695042774000,"contributors":[{"name":"swbs90","email":"swbs90@naver.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.28,"words":83},"filePathRelative":"04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.md","localizedDate":"2021년 8월 21일","excerpt":"\\n
    \\n

    \\n

    공식 사이트에 consul 인증서 생성 가이드는 있는데 Nomad 인증서 생성가이드는
    \\nShow Terminal을 들어가야 볼 수 있기때문에 귀찮음을 해결하기 위해 공유합니다.

    \\n
    \\n

    Nomad 인증서 생성

    \\n
    consul tls ca create -domain=nomad -days 3650\\n\\nconsul tls cert create -domain=nomad -dc=global  -server -days 3650\\n\\nconsul tls cert create -domain=nomad -dc=global  -client -days 3650\\n\\nconsul tls cert create -domain=nomad -dc=global  -cli -days 3650\\n
    "}');export{p as comp,h as data}; diff --git a/assets/NotAllowAdminUsername.html-BUoNYKC5.js b/assets/NotAllowAdminUsername.html-BUoNYKC5.js new file mode 100644 index 0000000000..a8aa4d5e0f --- /dev/null +++ b/assets/NotAllowAdminUsername.html-BUoNYKC5.js @@ -0,0 +1,32 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c as l,b as e,d as n,a as s,e as t}from"./app-Bzk8Nrll.js";const c={},p=t('

    The Admin Username specified is not allowed.

    Log
    Error : compute.VirtualMachinesClient#CreateOrUpdate: Failure sending request: StatusCode=400 – Original Error: Code=“InvalidParameter” Message=“The Admin Username specified is not allowed.” Target="adminUsername"

    Azure(azurerm) 프로바이더를 사용하여 Virtual Machine을 프로비저닝하는 경우 OSProfile에서 Admin User Name을 잘못된 조건으로 구성하는 경우 발생 할 수 있음

    Azure API - OSProfile.AdminUsername Property

    ',4),u={href:"https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.models.osprofile.adminusername?view=azure-dotnet",target:"_blank",rel:"noopener noreferrer"},m=t('

    Azure의 API에서 정의하는 OSProfile 내의 AdminUsername은 온라인 문서에서처럼 몇가지 룰이 있다.

    Terraform Error Code Sample

    ',3),d={href:"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine",target:"_blank",rel:"noopener noreferrer"},k=e("div",{class:"language-hcl line-numbers-mode","data-ext":"hcl","data-title":"hcl"},[e("pre",{hcl:"",class:"language-hcl"},[e("code",null,[e("span",{class:"token keyword"},[n("resource "),e("span",{class:"token type variable"},'"azurerm_virtual_machine"')]),n(),e("span",{class:"token string"},'"main"'),n(),e("span",{class:"token punctuation"},"{"),n(` + `),e("span",{class:"token property"},"name"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},[n('"'),e("span",{class:"token interpolation"},[e("span",{class:"token punctuation"},"$"),e("span",{class:"token punctuation"},"{"),e("span",{class:"token keyword"},"var"),e("span",{class:"token punctuation"},"."),e("span",{class:"token type variable"},"prefix"),e("span",{class:"token punctuation"},"}")]),n('-vm"')]),n(` + `),e("span",{class:"token property"},"location"),n(" "),e("span",{class:"token punctuation"},"="),n(` azurerm_resource_group.main.location + `),e("span",{class:"token property"},"resource_group_name"),n(" "),e("span",{class:"token punctuation"},"="),n(` azurerm_resource_group.main.name + `),e("span",{class:"token property"},"network_interface_ids"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token punctuation"},"["),n("azurerm_network_interface.main.id"),e("span",{class:"token punctuation"},"]"),n(` + `),e("span",{class:"token property"},"vm_size"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"Standard_DS1_v2"'),n(` + + `),e("span",{class:"token keyword"},"storage_image_reference"),n(),e("span",{class:"token punctuation"},"{"),n(` + `),e("span",{class:"token property"},"publisher"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"Canonical"'),n(` + `),e("span",{class:"token property"},"offer"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"UbuntuServer"'),n(` + `),e("span",{class:"token property"},"sku"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"16.04-LTS"'),n(` + `),e("span",{class:"token property"},"version"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"latest"'),n(` + `),e("span",{class:"token punctuation"},"}"),n(` + `),e("span",{class:"token keyword"},"storage_os_disk"),n(),e("span",{class:"token punctuation"},"{"),n(` + `),e("span",{class:"token property"},"name"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"myosdisk1"'),n(` + `),e("span",{class:"token property"},"caching"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"ReadWrite"'),n(` + `),e("span",{class:"token property"},"create_option"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"FromImage"'),n(` + `),e("span",{class:"token property"},"managed_disk_type"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"Standard_LRS"'),n(` + `),e("span",{class:"token punctuation"},"}"),n(` + `),e("span",{class:"token keyword"},"os_profile"),n(),e("span",{class:"token punctuation"},"{"),n(` + `),e("span",{class:"token property"},"computer_name"),n(" "),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"hostname"'),n(` + `),e("span",{class:"token property"},"admin_username"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"test"'),n(` + `),e("span",{class:"token property"},"admin_password"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"Password1234!"'),n(` + `),e("span",{class:"token punctuation"},"}"),n(` + `),e("span",{class:"token keyword"},"os_profile_linux_config"),n(),e("span",{class:"token punctuation"},"{"),n(` + `),e("span",{class:"token property"},"disable_password_authentication"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token boolean"},"false"),n(` + `),e("span",{class:"token punctuation"},"}"),n(` + `),e("span",{class:"token property"},"tags"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token punctuation"},"{"),n(` + `),e("span",{class:"token property"},"environment"),n(),e("span",{class:"token punctuation"},"="),n(),e("span",{class:"token string"},'"staging"'),n(` + `),e("span",{class:"token punctuation"},"}"),n(` +`),e("span",{class:"token punctuation"},"}"),n(` +`)])]),e("div",{class:"highlight-lines"},[e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("div",{class:"highlight-line"}," "),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br"),e("br")]),e("div",{class:"line-numbers","aria-hidden":"true"},[e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"}),e("div",{class:"line-number"})])],-1);function h(b,g){const a=o("ExternalLinkIcon");return i(),l("div",null,[p,e("p",null,[e("a",u,[n("https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.models.osprofile.adminusername?view=azure-dotnet"),s(a)])]),m,e("p",null,[e("a",d,[n("https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine"),s(a)])]),k])}const v=r(c,[["render",h],["__file","NotAllowAdminUsername.html.vue"]]),y=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.html","title":"The Admin Username specified is not allowed.","lang":"ko-KR","frontmatter":{"description":"Terraform Azure AdminUsername Error","tag":["Terraform","Azure"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"The Admin Username specified is not allowed."}],["meta",{"property":"og:description","content":"Terraform Azure AdminUsername Error"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Terraform"}],["meta",{"property":"article:tag","content":"Azure"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"The Admin Username specified is not allowed.\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Azure API - OSProfile.AdminUsername Property","slug":"azure-api-osprofile-adminusername-property","link":"#azure-api-osprofile-adminusername-property","children":[]},{"level":2,"title":"Terraform Error Code Sample","slug":"terraform-error-code-sample","link":"#terraform-error-code-sample","children":[]}],"git":{"createdTime":1632444868000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":3},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.52,"words":156},"filePathRelative":"04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.md","localizedDate":"2021년 9월 24일","excerpt":"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
    Log
    Error : compute.VirtualMachinesClient#CreateOrUpdate: Failure sending request: StatusCode=400 – Original Error: Code=“InvalidParameter” Message=“The Admin Username specified is not allowed.” Target=\\"adminUsername\\"
    "}');export{v as comp,y as data}; diff --git a/assets/OnConsulNomad.html-PufX9nh_.js b/assets/OnConsulNomad.html-PufX9nh_.js new file mode 100644 index 0000000000..883090829c --- /dev/null +++ b/assets/OnConsulNomad.html-PufX9nh_.js @@ -0,0 +1,384 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-Bzk8Nrll.js";const t={},o=e(`

    Boundary Install on Consul-Nomad

    1. Nomad namespace create

    nomad namespace apply -description "Boundary" boundary
    +

    2. Postgresql setup

    2.1 Postgresql job run

    postgresql.nomad
    job "postgresql" {
    +  type = "service"
    +  datacenters = ["hashistack"]
    +  namespace = "boundary"
    +
    +  group "postgres" {
    +    count = 1
    +
    +    volume "postgres-vol" {
    +      type = "host"
    +      read_only = false
    +      source = "postgres-vol"
    +    }
    +
    +    task "db" {
    +      driver = "docker"
    +
    +      volume_mount {
    +        volume = "postgres-vol"
    +        destination = "/var/lib/postgresql/data"
    +        read_only = false
    +      }
    +
    +      config {
    +        image = "postgres:13.2"
    +        port_map {
    +          pg = 5432
    +        }
    +      }
    +      
    +      env {
    +        POSTGRES_PASSWORD = "postgres"
    +        POSTGRES_USER = "postgres"
    +        PGDATA = "/var/lib/postgresql/data/pgdata"
    +      }
    +
    +      resources {
    +        memory = 1024
    +
    +        network {
    +          port "pg" {
    +            static = 5432
    +          }
    +        }
    +      }
    +
    +      service {
    +        name = "postgresql"
    +        tags = ["db", "boundary"]
    +
    +        port = "pg"
    +
    +        check {
    +          type  = "tcp"
    +          interval = "10s"
    +          timeout  = "2s"
    +          port  = "pg"
    +        }
    +      }
    +    }
    +  }
    +}
    +
    nomad job run -namespace="boundary" postgresql.nomad
    +

    2.2 Posgresql init

    # Login
    +psql -h 172.28.128.11 -U postgres postgres
    +
    # <enter the password> postgres
    +CREATE ROLE boundary WITH LOGIN PASSWORD 'PASSWORD';
    +CREATE DATABASE boundary OWNER boundary;
    +GRANT ALL PRIVILEGES ON DATABASE boundary TO boundary;
    +ALTER USER boundary PASSWORD 'boundary';
    +

    3. Boundary database init

    3.1 Create config file

    </tmp/config.hcl>

    disable_mlock = true
    +
    +controller {
    +  name = "controller-0"
    +  database {
    +    url = "postgresql://boundary:boundary@172.28.128.11:5432/boundary?sslmode=disable"
    +  }
    +}
    +
    +kms "aead" {
    +  purpose = "root"
    +  aead_type = "aes-gcm"
    +  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
    +  key_id = "global_root"
    +}
    +
    +kms "aead" {
    +  purpose = "worker-auth"
    +  aead_type = "aes-gcm"
    +  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +  key_id = "global_worker-auth"
    +}
    +
    +kms "aead" {
    +    purpose   = "recovery"
    +    aead_type = "aes-gcm"
    +    key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +    key_id    = "global_recovery"
    +}
    +

    3.2 Init database

    boundary database init -config=/tmp/config.hcl
    +

    4. Boundary Job

    4.1 Boundary Controller Job

    boundary-controller.nomad
    locals {
    +  version = "0.6.2"
    +  postgre_ip = "172.28.128.11"
    +  postgre_port = "5432"
    +}
    +
    +job "boundary-controller" {
    +  type = "service"
    +  datacenters = ["hashistack"]
    +  namespace = "boundary"
    +
    +  group "controller" {
    +    count = 1
    +
    +    network {
    +      mode = "host"
    +    }
    +
    +    task "migration" {
    +      driver = "raw_exec"
    +
    +      env {
    +        BOUNDARY_POSTGRES_URL = "postgresql://boundary:boundary@${local.postgre_ip}:${local.postgre_port}/boundary?sslmode=disable"
    +      }
    +      artifact {
    +        source = "https://releases.hashicorp.com/boundary/${local.version}/boundary_${local.version}_linux_amd64.zip"
    +      }
    +      template {
    +        data = <<EOH
    +disable_mlock = true
    +
    +{{ range service "postgresql" }}
    +controller {
    +  name = "controller-0"
    +  database {
    +    url = "postgresql://boundary:boundary@{{ .Address }}:{{ .Port }}/boundary?sslmode=disable"
    +  }
    +}
    +{{ end }}
    +
    +listener "tcp" {
    +  address = "0.0.0.0:9200"
    +  purpose = "api"
    +  tls_disable = true
    +}
    +listener "tcp" {
    +  address = "0.0.0.0:9201"
    +  purpose = "cluster"
    +  tls_disable = true
    +}
    +
    +kms "aead" {
    +  purpose = "root"
    +  aead_type = "aes-gcm"
    +  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
    +  key_id = "global_root"
    +}
    +
    +kms "aead" {
    +  purpose = "worker-auth"
    +  aead_type = "aes-gcm"
    +  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +  key_id = "global_worker-auth"
    +}
    +
    +kms "aead" {
    +    purpose   = "recovery"
    +    aead_type = "aes-gcm"
    +    key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +    key_id    = "global_recovery"
    +}
    +EOH
    +        destination = "local/config/config.hcl"
    +      }
    +      config {
    +        command = "local/boundary"
    +        args = ["database", "migrate", "-config", "local/config/config.hcl"]
    +      }
    +      lifecycle {
    +        hook    = "prestart"
    +        sidecar = false
    +      }
    +    }
    +
    +    task "controller" {
    +      driver = "docker"
    +
    +      config {
    +        image = "hashicorp/boundary:${local.version}"
    +        port_map {
    +          controller = 9200
    +          cluster = 9201
    +        }
    +        mount {
    +          type   = "bind"
    +          source = "local/config"
    +          target = "/boundary"
    +        }
    +        // network_mode = "boundary-net"
    +        // network_aliases = [
    +        //   "boundary-controller"
    +        // ]
    +      }
    +      
    +      template {
    +        data = <<EOH
    +disable_mlock = true
    +
    +{{ range service "postgresql" }}
    +controller {
    +  name = "controller-0"
    +  database {
    +    url = "postgresql://boundary:boundary@{{ .Address }}:{{ .Port }}/boundary?sslmode=disable"
    +  }
    +  public_cluster_addr = "{{ env "NOMAD_ADDR_cluster" }}"
    +}
    +{{ end }}
    +
    +listener "tcp" {
    +  address = "0.0.0.0:9200"
    +  purpose = "api"
    +  tls_disable = true
    +}
    +listener "tcp" {
    +  address = "0.0.0.0:9201"
    +  purpose = "cluster"
    +  tls_disable = true
    +}
    +
    +kms "aead" {
    +  purpose = "root"
    +  aead_type = "aes-gcm"
    +  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
    +  key_id = "global_root"
    +}
    +
    +kms "aead" {
    +  purpose = "worker-auth"
    +  aead_type = "aes-gcm"
    +  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +  key_id = "global_worker-auth"
    +}
    +
    +kms "aead" {
    +    purpose   = "recovery"
    +    aead_type = "aes-gcm"
    +    key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +    key_id    = "global_recovery"
    +}
    +EOH
    +        destination = "local/config/config.hcl"
    +      }
    +      
    +      env {
    +        // BOUNDARY_POSTGRES_URL = "postgresql://boundary:boundary@\${local.postgre_ip}:\${local.postgre_port}/boundary?sslmode=disable"
    +        SKIP_SETCAP = true
    +      }
    +
    +      resources {
    +        cpu    = 300
    +        memory = 500
    +        network {
    +          port "controller" {
    +            static = 9200
    +          }
    +          port "cluster" {
    +            static = 9201
    +          }
    +        }
    +      }
    +
    +      service {
    +        name = "boundary"
    +        tags = ["cluster"]
    +
    +        port = "cluster"
    +
    +        check {
    +          type  = "tcp"
    +          interval = "10s"
    +          timeout  = "2s"
    +          port  = "cluster"
    +        }
    +      }
    +    }
    +  }
    +}
    +
    nomad job run -namespace="boundary" boundary-controller.nomad
    +

    4.2 Boundary worker Job

    boundary-controller.nomad
    locals {
    +  version = "0.6.2"
    +}
    +
    +job "boundary-worker" {
    +  type = "service"
    +  datacenters = ["hashistack"]
    +  namespace = "boundary"
    +  
    +  group "worker" {
    +    count = 1
    +
    +    scaling {
    +      enabled = true
    +      min = 1
    +      max = 3
    +    }
    +
    +    network {
    +      mode = "host"
    +    }
    +    
    +    task "worker" {
    +      driver = "docker"
    +
    +      config {
    +        image = "hashicorp/boundary:${local.version}"
    +        port_map {
    +          proxy = 9202
    +        }
    +        volumes = [
    +          "local/boundary:/boundary/",
    +        ]
    +        // network_mode = "boundary-net"
    +      }
    +      
    +      template {
    +        data = <<EOH
    +disable_mlock = true
    +
    +listener "tcp" {
    +  address = "0.0.0.0:9202"
    +  purpose = "proxy"
    +  tls_disable = true
    +}
    +
    +worker {
    +  name = "worker-0"
    +  controllers = [
    +{{ range service "boundary" }}
    +		"{{ .Address }}",
    +{{ end }}
    +  ]
    +  public_addr = "{{ env "NOMAD_ADDR_proxy" }}"
    +}
    +
    +kms "aead" {
    +  purpose = "root"
    +  aead_type = "aes-gcm"
    +  key = "sP1fnF5Xz85RrXyELHFeZg9Ad2qt4Z4bgNHVGtD6ung="
    +  key_id = "global_root"
    +}
    +
    +kms "aead" {
    +  purpose = "worker-auth"
    +  aead_type = "aes-gcm"
    +  key = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +  key_id = "global_worker-auth"
    +}
    +
    +kms "aead" {
    +  purpose   = "recovery"
    +  aead_type = "aes-gcm"
    +  key       = "8fZBjCUfN0TzjEGLQldGY4+iE9AkOvCfjh7+p0GtRBQ="
    +  key_id    = "global_recovery"
    +}
    +EOH
    +        destination = "/local/boundary/config.hcl"
    +      }
    +      
    +      env {
    +        // BOUNDARY_POSTGRES_URL = "postgresql://boundary:boundary@172.28.128.11:5432/boundary?sslmode=disable"
    +        SKIP_SETCAP = true
    +      }
    +
    +      resources {
    +        network {
    +          port "proxy" {}
    +        }
    +      }
    +    }
    +  }
    +}
    +
    nomad job run -namespace="boundary" boundary-worker.nomad
    +
    `,23),i=[o];function p(l,c){return s(),a("div",null,i)}const d=n(t,[["render",p],["__file","OnConsulNomad.html.vue"]]),v=JSON.parse('{"path":"/04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.html","title":"Boundary Install on Consul-Nomad","lang":"ko-KR","frontmatter":{"description":"Boundary Install","tag":["Boundary","Install"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Boundary Install on Consul-Nomad"}],["meta",{"property":"og:description","content":"Boundary Install"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Boundary"}],["meta",{"property":"article:tag","content":"Install"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Boundary Install on Consul-Nomad\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1. Nomad namespace create","slug":"_1-nomad-namespace-create","link":"#_1-nomad-namespace-create","children":[]},{"level":2,"title":"2. Postgresql setup","slug":"_2-postgresql-setup","link":"#_2-postgresql-setup","children":[{"level":3,"title":"2.1 Postgresql job run","slug":"_2-1-postgresql-job-run","link":"#_2-1-postgresql-job-run","children":[]},{"level":3,"title":"2.2 Posgresql init","slug":"_2-2-posgresql-init","link":"#_2-2-posgresql-init","children":[]}]},{"level":2,"title":"3. Boundary database init","slug":"_3-boundary-database-init","link":"#_3-boundary-database-init","children":[{"level":3,"title":"3.1 Create config file","slug":"_3-1-create-config-file","link":"#_3-1-create-config-file","children":[]},{"level":3,"title":"3.2 Init database","slug":"_3-2-init-database","link":"#_3-2-init-database","children":[]}]},{"level":2,"title":"4. Boundary Job","slug":"_4-boundary-job","link":"#_4-boundary-job","children":[{"level":3,"title":"4.1 Boundary Controller Job","slug":"_4-1-boundary-controller-job","link":"#_4-1-boundary-controller-job","children":[]},{"level":3,"title":"4.2 Boundary worker Job","slug":"_4-2-boundary-worker-job","link":"#_4-2-boundary-worker-job","children":[]}]}],"git":{"createdTime":1634219407000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":2},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":2.29,"words":688},"filePathRelative":"04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.md","localizedDate":"2021년 10월 14일","excerpt":"\\n

    1. Nomad namespace create

    \\n
    nomad namespace apply -description \\"Boundary\\" boundary\\n

    2. Postgresql setup

    "}');export{d as comp,v as data}; diff --git a/assets/OnNomad-devmode.html-DozOQZMV.js b/assets/OnNomad-devmode.html-DozOQZMV.js new file mode 100644 index 0000000000..f41c29ee63 --- /dev/null +++ b/assets/OnNomad-devmode.html-DozOQZMV.js @@ -0,0 +1,79 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as p}from"./app-Bzk8Nrll.js";const t={},e=p(`

    Boundary Run Dev Mode on Nomad Job

    1. Job sample

    locals {
    +  version = "0.8.1"
    +  private_ip = "192.168.0.27"
    +  public_ip = "11.129.13.30"
    +}
    +
    +job "boundary-dev" {
    +  type = "service"
    +  datacenters = ["home"]
    +  namespace = "boundary"
    +
    +  constraint {
    +    attribute = "${attr.os.name}"
    +    value     = "raspbian"
    +  }
    +
    +  group "dev" {
    +    count = 1
    +
    +    ephemeral_disk { sticky  = true }
    +
    +    network {
    +      mode = "host"
    +      port "api" {
    +        static = 9200
    +        to = 9200
    +      }
    +      port "cluster" {
    +        static = 9201
    +        to = 9201
    +      }
    +      port "worker" {
    +        static = 9202
    +        to = 9202
    +      }
    +    }
    +
    +    task "dev" {
    +      driver = "raw_exec"
    +
    +      env {
    +        BOUNDARY_DEV_CONTROLLER_API_LISTEN_ADDRESS = local.private_ip
    +        BOUNDARY_DEV_CONTROLLER_CLUSTER_LISTEN_ADDRESS = "0.0.0.0"
    +        BOUNDARY_DEV_WORKER_PUBLIC_ADDRESS = local.public_ip
    +        BOUNDARY_DEV_WORKER_PROXY_LISTEN_ADDRESS = local.private_ip
    +        BOUNDARY_DEV_PASSWORD = "password"
    +      }
    +
    +      // artifact {
    +      //   source = "https://releases.hashicorp.com/boundary/\${local.version}/boundary_\${local.version}_linux_arm.zip"
    +      // }
    +
    +      config {
    +        command = "boundary"
    +        args = ["dev"]
    +      }
    +
    +      resources {
    +        cpu    = 500
    +        memory = 500
    +      }
    +
    +      service {
    +        name = "boundary"
    +        tags = ["cluster"]
    +
    +        port = "cluster"
    +
    +        check {
    +          type  = "tcp"
    +          interval = "10s"
    +          timeout  = "2s"
    +          port  = "api"
    +        }
    +      }
    +    }
    +  }
    +}
    +

    2. ENV

    `,5),o=[e];function c(l,i){return s(),a("div",null,o)}const k=n(t,[["render",c],["__file","OnNomad-devmode.html.vue"]]),d=JSON.parse('{"path":"/04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.html","title":"Boundary Run Dev Mode on Nomad Job","lang":"ko-KR","frontmatter":{"description":"Boundary Dev Mode (Nomad)","tag":["Boundary","Install"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Boundary Run Dev Mode on Nomad Job"}],["meta",{"property":"og:description","content":"Boundary Dev Mode (Nomad)"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Boundary"}],["meta",{"property":"article:tag","content":"Install"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Boundary Run Dev Mode on Nomad Job\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"1. Job sample","slug":"_1-job-sample","link":"#_1-job-sample","children":[]},{"level":2,"title":"2. ENV","slug":"_2-env","link":"#_2-env","children":[]}],"git":{"createdTime":1653031029000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.44,"words":132},"filePathRelative":"04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.md","localizedDate":"2022년 5월 20일","excerpt":"\\n

    1. Job sample

    \\n
    locals {\\n  version = \\"0.8.1\\"\\n  private_ip = \\"192.168.0.27\\"\\n  public_ip = \\"11.129.13.30\\"\\n}\\n\\njob \\"boundary-dev\\" {\\n  type = \\"service\\"\\n  datacenters = [\\"home\\"]\\n  namespace = \\"boundary\\"\\n\\n  constraint {\\n    attribute = \\"${attr.os.name}\\"\\n    value     = \\"raspbian\\"\\n  }\\n\\n  group \\"dev\\" {\\n    count = 1\\n\\n    ephemeral_disk { sticky  = true }\\n\\n    network {\\n      mode = \\"host\\"\\n      port \\"api\\" {\\n        static = 9200\\n        to = 9200\\n      }\\n      port \\"cluster\\" {\\n        static = 9201\\n        to = 9201\\n      }\\n      port \\"worker\\" {\\n        static = 9202\\n        to = 9202\\n      }\\n    }\\n\\n    task \\"dev\\" {\\n      driver = \\"raw_exec\\"\\n\\n      env {\\n        BOUNDARY_DEV_CONTROLLER_API_LISTEN_ADDRESS = local.private_ip\\n        BOUNDARY_DEV_CONTROLLER_CLUSTER_LISTEN_ADDRESS = \\"0.0.0.0\\"\\n        BOUNDARY_DEV_WORKER_PUBLIC_ADDRESS = local.public_ip\\n        BOUNDARY_DEV_WORKER_PROXY_LISTEN_ADDRESS = local.private_ip\\n        BOUNDARY_DEV_PASSWORD = \\"password\\"\\n      }\\n\\n      // artifact {\\n      //   source = \\"https://releases.hashicorp.com/boundary/${local.version}/boundary_${local.version}_linux_arm.zip\\"\\n      // }\\n\\n      config {\\n        command = \\"boundary\\"\\n        args = [\\"dev\\"]\\n      }\\n\\n      resources {\\n        cpu    = 500\\n        memory = 500\\n      }\\n\\n      service {\\n        name = \\"boundary\\"\\n        tags = [\\"cluster\\"]\\n\\n        port = \\"cluster\\"\\n\\n        check {\\n          type  = \\"tcp\\"\\n          interval = \\"10s\\"\\n          timeout  = \\"2s\\"\\n          port  = \\"api\\"\\n        }\\n      }\\n    }\\n  }\\n}\\n
    "}');export{k as comp,d as data}; diff --git a/assets/Oom_killer.html-Cah9Qu1H.js b/assets/Oom_killer.html-Cah9Qu1H.js new file mode 100644 index 0000000000..10af5b0e65 --- /dev/null +++ b/assets/Oom_killer.html-Cah9Qu1H.js @@ -0,0 +1 @@ +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as a,c as t,b as o,d as e,a as n,e as c}from"./app-Bzk8Nrll.js";const m={},s=c('

    OOM Killer가 일하는 방식

    OOM Killer의 주요 업무는 다음 두 가지입니다.

    각 프로세스의 oom_score 관련 정보는 /proc/(pid) 디렉토리 하위에서 찾을 수 있습니다.

    oom_killer는 점수를 나타내는 oom_score만 있으면 임무를 완수할 수 있습니다. 그렇다면 oom_adj와 oom_score_adj의 역할은 무엇일까요? man proc을 이용해 확인해 보겠습니다.

    위의 설명에 따르면 OOM Killer가 유일하게 의존하는 변수는 oom_score이고, oom_adj 또는 oom_score_adj을 이용해 그 값을 조정할 수 있습니다. 현재 사용하고 있는 커널 버전은 kernel-3.10.0-957.el7입니다. 리눅스 저장소에서 버전에 맞는 커널 소스 코드를 찾았습니다.

    ',8),d={href:"https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster/",target:"_blank",rel:"noopener noreferrer"};function p(_,h){const r=i("ExternalLinkIcon");return a(),t("div",null,[s,o("ul",null,[o("li",null,[e("참조 링크: "),o("a",d,[e("https://engineering.linecorp.com/ko/blog/prometheus-container-kubernetes-cluster/"),n(r)])])])])}const j=l(m,[["render",p],["__file","Oom_killer.html.vue"]]),O=JSON.parse('{"path":"/01-Infrastructure/Linux/TroubleShooting/Oom_killer.html","title":"OOM Killer가 일하는 방식","lang":"ko-KR","frontmatter":{"description":"oom_killer","tag":["linux","oom","oom_killer"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/01-Infrastructure/Linux/TroubleShooting/Oom_killer.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"OOM Killer가 일하는 방식"}],["meta",{"property":"og:description","content":"oom_killer"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"linux"}],["meta",{"property":"article:tag","content":"oom"}],["meta",{"property":"article:tag","content":"oom_killer"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"OOM Killer가 일하는 방식\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"OOM Killer의 주요 업무는 다음 두 가지입니다.","slug":"oom-killer의-주요-업무는-다음-두-가지입니다","link":"#oom-killer의-주요-업무는-다음-두-가지입니다","children":[]},{"level":2,"title":"각 프로세스의 oom_score 관련 정보는 /proc/(pid) 디렉토리 하위에서 찾을 수 있습니다.","slug":"각-프로세스의-oom-score-관련-정보는-proc-pid-디렉토리-하위에서-찾을-수-있습니다","link":"#각-프로세스의-oom-score-관련-정보는-proc-pid-디렉토리-하위에서-찾을-수-있습니다","children":[]},{"level":2,"title":"oom_killer는 점수를 나타내는 oom_score만 있으면 임무를 완수할 수 있습니다. 그렇다면 oom_adj와 oom_score_adj의 역할은 무엇일까요? man proc을 이용해 확인해 보겠습니다.","slug":"oom-killer는-점수를-나타내는-oom-score만-있으면-임무를-완수할-수-있습니다-그렇다면-oom-adj와-oom-score-adj의-역할은-무엇일까요-man-proc을-이용해-확인해-보겠습니다","link":"#oom-killer는-점수를-나타내는-oom-score만-있으면-임무를-완수할-수-있습니다-그렇다면-oom-adj와-oom-score-adj의-역할은-무엇일까요-man-proc을-이용해-확인해-보겠습니다","children":[]},{"level":2,"title":"위의 설명에 따르면 OOM Killer가 유일하게 의존하는 변수는 oom_score이고, oom_adj 또는 oom_score_adj을 이용해 그 값을 조정할 수 있습니다. 현재 사용하고 있는 커널 버전은 kernel-3.10.0-957.el7입니다. 리눅스 저장소에서 버전에 맞는 커널 소스 코드를 찾았습니다.","slug":"위의-설명에-따르면-oom-killer가-유일하게-의존하는-변수는-oom-score이고-oom-adj-또는-oom-score-adj을-이용해-그-값을-조정할-수-있습니다-현재-사용하고-있는-커널-버전은-kernel-3-10-0-957-el7입니다-리눅스-저장소에서-버전에-맞는-커널-소스-코드를-찾았습니다","link":"#위의-설명에-따르면-oom-killer가-유일하게-의존하는-변수는-oom-score이고-oom-adj-또는-oom-score-adj을-이용해-그-값을-조정할-수-있습니다-현재-사용하고-있는-커널-버전은-kernel-3-10-0-957-el7입니다-리눅스-저장소에서-버전에-맞는-커널-소스-코드를-찾았습니다","children":[]}],"git":{"createdTime":1640764183000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"ung","email":"swbs90@naver.com","commits":1}]},"readingTime":{"minutes":0.33,"words":100},"filePathRelative":"01-Infrastructure/Linux/TroubleShooting/Oom_killer.md","localizedDate":"2021년 12월 29일","excerpt":"\\n

    OOM Killer의 주요 업무는 다음 두 가지입니다.

    \\n\\n

    각 프로세스의 oom_score 관련 정보는 /proc/(pid) 디렉토리 하위에서 찾을 수 있습니다.

    \\n"}');export{j as comp,O as data}; diff --git a/assets/PlantUML.html-C1o0QjFB.js b/assets/PlantUML.html-C1o0QjFB.js new file mode 100644 index 0000000000..77261325f7 --- /dev/null +++ b/assets/PlantUML.html-C1o0QjFB.js @@ -0,0 +1,200 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as r,c as d,b as n,d as e,a as s,e as i}from"./app-Bzk8Nrll.js";const o={},c=n("h1",{id:"uml",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#uml"},[n("span",null,"UML")])],-1),u={href:"https://github.com/gmunguia/markdown-it-plantuml#readme",target:"_blank",rel:"noopener noreferrer"},m=i(`

    기본 사용법

    UML 블록은 @startuml@enduml 사이에 UML 구성을 위한 구성을 넣어 표기합니다. 아래와 같이 md 파일 내에 작성하면

    @startuml
    +Bob -> Alice : hello
    +@enduml
    +

    다음과 같이 표기됩니다.

    @startuml
    Bob -> Alice : hello
    @enduml

    `,5),p={href:"http://plantuml.com/",target:"_blank",rel:"noopener noreferrer"},v=i(`

    PlantUml 예제

    Sample Terraform Action

    @startuml
    +actor User
    +interface Terraform
    +cloud CLOUD
    +
    +User ->> Terraform : Apply
    +User <<- Terraform : State
    +Terraform ->> CLOUD : Probisioning
    +CLOUD ->> Terraform : Response
    +@enduml
    +

    @startuml
    actor User
    interface Terraform
    cloud CLOUD

    User ->> Terraform : Apply
    User <<- Terraform : State
    Terraform ->> CLOUD : Probisioning
    CLOUD ->> Terraform : Response
    @enduml

    Sequence Diagram

    `,6),b={href:"http://plantuml.com/sequence-diagram",target:"_blank",rel:"noopener noreferrer"},g=i(`
    @startuml
    +Alice -> Bob: Authentication Request
    +Bob --> Alice: Authentication Response
    +
    +Alice -> Bob: Another authentication Request
    +Alice <-- Bob: another authentication Response
    +@enduml
    +

    @startuml
    Alice -> Bob: Authentication Request
    Bob --> Alice: Authentication Response

    Alice -> Bob: Another authentication Request
    Alice <-- Bob: another authentication Response
    @enduml

    UseCase Diagram

    `,4),h={href:"http://plantuml.com/use-case-diagram",target:"_blank",rel:"noopener noreferrer"},k=i(`
    @startuml
    +:Main Admin: as Admin
    +(Use the application) as (Use)
    +
    +User -> (Start)
    +User --> (Use)
    +
    +Admin ---> (Use)
    +
    +note right of Admin : This is an example.
    +
    +note right of (Use)
    +  A note can also
    +  be on several lines
    +end note
    +
    +note "This note is connected\\nto several objects." as N2
    +(Start) .. N2
    +N2 .. (Use)
    +@enduml
    +

    @startuml
    :Main Admin: as Admin
    (Use the application) as (Use)

    User -> (Start)
    User --> (Use)

    Admin ---> (Use)

    note right of Admin : This is an example.

    note right of (Use)
    A note can also
    be on several lines
    end note

    note "This note is connected\\nto several objects." as N2
    (Start) .. N2
    N2 .. (Use)
    @enduml

    Class Diagram

    `,8),f={href:"http://plantuml.com/class-diagram",target:"_blank",rel:"noopener noreferrer"},w=i(`
    @startuml
    +Object <|-- Dummy
    +
    +class Dummy {
    +  String data
    +  void methods()
    +  -field1
    +  #field2
    +  ~method1()
    +  +method2()
    +}
    +
    +class Flight {
    +   flightNumber : Integer
    +   departureTime : Date
    +}
    +
    +class Car
    +
    +Driver - Car : drives >
    +Car *- Wheel : have 4 >
    +Car -- Person : < owns
    +@enduml
    +

    @startuml
    Object <|-- Dummy

    class Dummy {
    String data
    void methods()
    -field1
    #field2
    ~method1()
    +method2()
    }

    class Flight {
    flightNumber : Integer
    departureTime : Date
    }

    class Car

    Driver - Car : drives >
    Car *- Wheel : have 4 >
    Car -- Person : < owns
    @enduml

    Activity Diagram

    `,7),x={href:"http://plantuml.com/activity-diagram-beta",target:"_blank",rel:"noopener noreferrer"},_=i(`
    @startuml
    +start
    +partition Initialization {
    +    :read config file;
    +    :init internal variable;
    +}
    +partition Running {
    +    if (multiprocessor?) then (yes)
    +    fork
    +        :Treatment 1;
    +    fork again
    +        :Treatment 2;
    +        detach
    +    end fork
    +    else (monoproc)
    +    :Treatment 1;
    +    :Treatment 2;
    +    endif
    +}
    +
    +stop
    +@enduml
    +

    @startuml
    start
    partition Initialization {
    :read config file;
    :init internal variable;
    }
    partition Running {
    if (multiprocessor?) then (yes)
    fork
    :Treatment 1;
    fork again
    :Treatment 2;
    detach
    end fork
    else (monoproc)
    :Treatment 1;
    :Treatment 2;
    endif
    }

    stop
    @enduml

    Component Diagram

    `,4),y={href:"http://plantuml.com/component-diagram",target:"_blank",rel:"noopener noreferrer"},S=i(`
    @startuml
    +package "Some Group" {
    +  HTTP - [First Component]
    +  [Another Component]
    +}
    +
    +node "Other Groups" {
    +  FTP - [Second Component]
    +  [First Component] --> FTP
    +}
    +
    +cloud {
    +  [Example 1]
    +}
    +
    +
    +database "MySql" {
    +  folder "This is my folder" {
    +    [Folder 3]
    +  }
    +  frame "Foo" {
    +    [Frame 4]
    +  }
    +}
    +
    +
    +[Another Component] --> [Example 1]
    +[Example 1] --> [Folder 3]
    +[Folder 3] --> [Frame 4]
    +@enduml
    +

    @startuml
    package "Some Group" {
    HTTP - [First Component]
    [Another Component]
    }

    node "Other Groups" {
    FTP - [Second Component]
    [First Component] --> FTP
    }

    cloud {
    [Example 1]
    }

    database "MySql" {
    folder "This is my folder" {
    [Folder 3]
    }
    frame "Foo" {
    [Frame 4]
    }
    }

    [Another Component] --> [Example 1]
    [Example 1] --> [Folder 3]
    [Folder 3] --> [Frame 4]
    @enduml

    State Diagram

    `,7),q={href:"http://plantuml.com/state-diagram",target:"_blank",rel:"noopener noreferrer"},T=i(`
    @startuml
    +[*] --> State1
    +State1 --> [*]
    +State1 : this is a string
    +State1 : this is another string
    +
    +State1 -> State2
    +State2 --> [*]
    +
    +scale 350 width
    +[*] --> NotShooting
    +
    +state NotShooting {
    +  [*] --> Idle
    +  Idle --> Configuring : EvConfig
    +  Configuring --> Idle : EvConfig
    +}
    +
    +state Configuring {
    +  [*] --> NewValueSelection
    +  NewValueSelection --> NewValuePreview : EvNewValue
    +  NewValuePreview --> NewValueSelection : EvNewValueRejected
    +  NewValuePreview --> NewValueSelection : EvNewValueSaved
    +
    +  state NewValuePreview {
    +     State1 -> State2
    +  }
    +}
    +@enduml
    +

    @startuml
    [] --> State1
    State1 --> [
    ]
    State1 : this is a string
    State1 : this is another string

    State1 -> State2
    State2 --> [*]

    scale 350 width
    [*] --> NotShooting

    state NotShooting {
    [*] --> Idle
    Idle --> Configuring : EvConfig
    Configuring --> Idle : EvConfig
    }

    state Configuring {
    [*] --> NewValueSelection
    NewValueSelection --> NewValuePreview : EvNewValue
    NewValuePreview --> NewValueSelection : EvNewValueRejected
    NewValuePreview --> NewValueSelection : EvNewValueSaved

    state NewValuePreview {
    State1 -> State2
    }
    }
    @enduml

    Network Diagram

    `,8),U={href:"https://plantuml.com/nwdiag",target:"_blank",rel:"noopener noreferrer"},C=i(`
    @startuml
    +nwdiag {
    +  network dmz {
    +      address = "210.x.x.x/24"
    +
    +      // set multiple addresses (using comma)
    +      web01 [address = "210.x.x.1, 210.x.x.20"];
    +      web02 [address = "210.x.x.2"];
    +  }
    +  network internal {
    +      address = "172.x.x.x/24";
    +
    +      web01 [address = "172.x.x.1"];
    +      web02 [address = "172.x.x.2"];
    +      db01;
    +      db02;
    +  }
    +}
    +@enduml
    +

    @startuml
    nwdiag {
    network dmz {
    address = "210.x.x.x/24"

      // set multiple addresses (using comma)
    +  web01 [address = "210.x.x.1, 210.x.x.20"];
    +  web02 [address = "210.x.x.2"];
    +

    }
    network internal {
    address = "172.x.x.x/24";

      web01 [address = "172.x.x.1"];
    +  web02 [address = "172.x.x.2"];
    +  db01;
    +  db02;
    +

    }
    }
    @enduml

    Gantt Diagram

    `,7),N={href:"https://plantuml.com/gantt-diagram",target:"_blank",rel:"noopener noreferrer"},A=i(`
    @startuml
    +@startgantt
    +[Prototype design] lasts 15 days
    +[Test prototype] lasts 10 days
    +-- All example --
    +[Task 1 (1 day)] lasts 1 day
    +[T2 (5 days)] lasts 5 days
    +[T3 (1 week)] lasts 1 week
    +[T4 (1 week and 4 days)] lasts 1 week and 4 days
    +[T5 (2 weeks)] lasts 2 weeks
    +@endgantt
    +@enduml
    +

    @startuml
    @startgantt
    [Prototype design] lasts 15 days
    [Test prototype] lasts 10 days
    -- All example --
    [Task 1 (1 day)] lasts 1 day
    [T2 (5 days)] lasts 5 days
    [T3 (1 week)] lasts 1 week
    [T4 (1 week and 4 days)] lasts 1 week and 4 days
    [T5 (2 weeks)] lasts 2 weeks
    @endgantt
    @enduml

    MindMap

    `,3),D={href:"https://plantuml.com/mindmap-diagram",target:"_blank",rel:"noopener noreferrer"},M=i(`
    @startuml
    +@startmindmap
    +* Debian
    +** Ubuntu
    +*** Linux Mint
    +*** Kubuntu
    +*** Lubuntu
    +*** KDE Neon
    +** LMDE
    +** SolydXK
    +** SteamOS
    +** Raspbian with a very long name
    +*** <s>Raspmbc</s> => OSMC
    +*** <s>Raspyfi</s> => Volumio
    +@endmindmap
    +@enduml
    +
    +

    @startuml
    @startmindmap

    `,3);function L(P,V){const a=l("ExternalLinkIcon");return r(),d("div",null,[c,n("p",null,[n("a",u,[e("markdown-it-plantuml"),s(a)]),e(" 플러그인을 활성화 하여 UML 작성이 가능합니다. 아래는 플러그인 개발자의 안내를 풀어 일부 설명합니다.")]),m,n("p",null,[e("다양한 예제는 "),n("a",p,[e("plantuml.com"),s(a)]),e("에서 확인할 수 있습니다.")]),v,n("p",null,[n("a",b,[e("http://plantuml.com/sequence-diagram"),s(a)])]),g,n("p",null,[n("a",h,[e("http://plantuml.com/use-case-diagram"),s(a)])]),k,n("p",null,[n("a",f,[e("http://plantuml.com/class-diagram"),s(a)])]),w,n("p",null,[n("a",x,[e("http://plantuml.com/activity-diagram-beta"),s(a)])]),_,n("p",null,[n("a",y,[e("http://plantuml.com/component-diagram"),s(a)])]),S,n("p",null,[n("a",q,[e("http://plantuml.com/state-diagram"),s(a)])]),T,n("p",null,[n("a",U,[e("https://plantuml.com/nwdiag"),s(a)])]),C,n("p",null,[n("a",N,[e("https://plantuml.com/gantt-diagram"),s(a)])]),A,n("p",null,[n("a",D,[e("https://plantuml.com/mindmap-diagram"),s(a)])]),M])}const F=t(o,[["render",L],["__file","PlantUML.html.vue"]]),O=JSON.parse('{"path":"/00-Howto/03-Tips/PlantUML.html","title":"UML","lang":"ko-KR","frontmatter":{"description":"UML markdown-it-plantuml 플러그인을 활성화 하여 UML 작성이 가능합니다. 아래는 플러그인 개발자의 안내를 풀어 일부 설명합니다. 기본 사용법 UML 블록은 @startuml 과 @enduml 사이에 UML 구성을 위한 구성을 넣어 표기합니다. 아래와 같이 md 파일 내에 작성하면 다음과 같이 표...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/03-Tips/PlantUML.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"UML"}],["meta",{"property":"og:description","content":"UML markdown-it-plantuml 플러그인을 활성화 하여 UML 작성이 가능합니다. 아래는 플러그인 개발자의 안내를 풀어 일부 설명합니다. 기본 사용법 UML 블록은 @startuml 과 @enduml 사이에 UML 구성을 위한 구성을 넣어 표기합니다. 아래와 같이 md 파일 내에 작성하면 다음과 같이 표..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"UML\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"기본 사용법","slug":"기본-사용법","link":"#기본-사용법","children":[]},{"level":2,"title":"PlantUml 예제","slug":"plantuml-예제","link":"#plantuml-예제","children":[{"level":3,"title":"Sample Terraform Action","slug":"sample-terraform-action","link":"#sample-terraform-action","children":[]},{"level":3,"title":"Sequence Diagram","slug":"sequence-diagram","link":"#sequence-diagram","children":[]},{"level":3,"title":"UseCase Diagram","slug":"usecase-diagram","link":"#usecase-diagram","children":[]},{"level":3,"title":"Class Diagram","slug":"class-diagram","link":"#class-diagram","children":[]},{"level":3,"title":"Activity Diagram","slug":"activity-diagram","link":"#activity-diagram","children":[]},{"level":3,"title":"Component Diagram","slug":"component-diagram","link":"#component-diagram","children":[]},{"level":3,"title":"State Diagram","slug":"state-diagram","link":"#state-diagram","children":[]},{"level":3,"title":"Network Diagram","slug":"network-diagram","link":"#network-diagram","children":[]},{"level":3,"title":"Gantt Diagram","slug":"gantt-diagram","link":"#gantt-diagram","children":[]},{"level":3,"title":"MindMap","slug":"mindmap","link":"#mindmap","children":[]}]}],"git":{"createdTime":1634225909000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":2.77,"words":831},"filePathRelative":"00-Howto/03-Tips/PlantUML.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    markdown-it-plantuml 플러그인을 활성화 하여 UML 작성이 가능합니다. 아래는 플러그인 개발자의 안내를 풀어 일부 설명합니다.

    \\n

    기본 사용법

    \\n

    UML 블록은 @startuml@enduml 사이에 UML 구성을 위한 구성을 넣어 표기합니다. 아래와 같이 md 파일 내에 작성하면

    ","autoDesc":true}');export{F as comp,O as data}; diff --git a/assets/ProviderBundling.html-CNwkfDLV.js b/assets/ProviderBundling.html-CNwkfDLV.js new file mode 100644 index 0000000000..23b7a823f1 --- /dev/null +++ b/assets/ProviderBundling.html-CNwkfDLV.js @@ -0,0 +1,71 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as i,b as n,d as s,a as e,e as r}from"./app-Bzk8Nrll.js";const l={},c=n("h1",{id:"terraform-provider-번들링",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#terraform-provider-번들링"},[n("span",null,"Terraform Provider - 번들링")])],-1),u={href:"https://github.com/hashicorp/terraform/tree/main/tools/terraform-bundle",target:"_blank",rel:"noopener noreferrer"},d=n("br",null,null,-1),m=n("p",null,'Airgap 환경에서 사용할 특정 버전의 Terraform과 여러 제공자 플러그인을 모두 포함하는 zip 파일 인 "번들 아카이브"를 생성하는 툴을 사용합니다. 일반적으로 Terraform init을 통해 특정 구성 작업에 필요한 플러그인을 다운로드하고 설치하지만 Airgap 환경에서는 공식 플러그인 저장소에 액세스 할 수 없는 경우가 발생합니다. Bundle 툴을 사용하여 Terraform 버전과 선택한 공급자를 모두 설치하기 위해 대상 시스템에 압축을 풀 수있는 zip 파일이 생성되므로 즉석 플러그인 설치가 필요하지 않습니다.',-1),v=n("div",{class:"hint-container warning"},[n("p",{class:"hint-container-title"},"주의"),n("p",null,"번들로 작성된 zip파일을 url로 등록하기 때문에 번들을 다운받을 수 있는 웹서버나 넥서스 같은 원격 저장소가 필요합니다.")],-1),k=n("h2",{id:"build-툴-준비",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#build-툴-준비"},[n("span",null,"build 툴 준비")])],-1),b={href:"https://github.com/hashicorp/terraform/tree/main/tools/terraform-bundle",target:"_blank",rel:"noopener noreferrer"},h=n("code",null,"terraform-bundle",-1),g=r(`
    # Ubuntu
    +# Install Go: https://github.com/golang/go/wiki/Ubuntu
    +$ sudo add-apt-repository ppa:longsleep/golang-backports
    +$ sudo apt update -y
    +$ sudo apt install golang-go -y
    +#sudo apt install golang-1.14-go -y
    +
    +# Build terraform-bundle from a release tag that matches your TF version
    +# Otherwise you might get an error like:
    +# "Failed to read config: this version of terraform-bundle can only build bundles for . . ."
    +$ git clone https://github.com/hashicorp/terraform.git
    +$ cd terraform
    +$ go install ./tools/terraform-bundle
    +
    +#verify that terraform-bundle tool is there
    +$ ls ~/go/bin/ 
    +$ export PATH=\${PATH}:$HOME/go/bin/
    +$ terraform-bundle --version
    +0.13.0
    +

    bundle 용 hcl 파일 구성 및 생성

    bundle 구성할 명세를 hcl로 작성합니다. (e.g. tf-bundle.hcl)

    공식(Official) 프로바이더의 경우 source 정의를 생략할 수 있습니다. 그렇지 않는 경우에는 반드시 source에 대한 정의가 필요합니다.

    terraform {
    +  # Version of Terraform to include in the bundle. An exact version number is required.
    +  version = "0.15.4"
    +}
    +
    +# Define which provider plugins are to be included
    +providers {
    +  null = {
    +    versions = ["= 3.1.0"]
    +  }
    +  time = {
    +    versions = ["= 0.7.1"]
    +  }
    +  random = {
    +    versions = ["= 3.1.0"]
    +  }
    +  template = {
    +    versions = ["= 2.2.0"]
    +  }
    +  tfe = {
    +    versions = ["= 0.25.3"]
    +  }
    +  vsphere = {
    +    versions = ["= 1.26.0"]
    +  }
    +  vault = {
    +    versions = ["= 2.20.0"]
    +  }
    +  consul = {
    +    versions = ["= 2.12.0"]
    +  }
    +  kubernetes = {
    +    versions = ["= 2.2.0"]
    +  }
    +  ad = {
    +    versions = ["=0.4.2"]
    +  }
    +  openstack = {
    +    versions = ["= 1.42.0"]
    +    source = "terraform-provider-openstack/openstack"
    +  }
    +  nsxt = {
    +    versions = ["= 3.1.1"]
    +    source = "vmware/nsxt"
    +  }
    +  vra7 = {
    +    versions = ["= 3.0.2"]
    +    source = "vmware/vra7"
    +  }
    +}
    +

    번들 생성은 다음과 같이 커맨드로 실행 합니다.

    terraform-bundle package -os=linux -arch=amd64 tf-bundle.hcl
    +

    이 작업을 통해 Terraform Enterprise에서 기존 Terraform을 다운로드 받고 Provider를 다운로드 받던 동작을 미리 수행한 번들이 생성 됩니다.

    생성된 번들 파일(zip)은 TFE Admin Console을 통해 적용

    `,10);function f(y,_){const a=o("ExternalLinkIcon");return p(),i("div",null,[c,n("blockquote",null,[n("p",null,[n("a",u,[s("https://github.com/hashicorp/terraform/tree/main/tools/terraform-bundle"),e(a)]),d,s(" Terraform Enterprise에서 동작하는 기능입니다.")])]),m,v,k,n("ul",null,[n("li",null,[n("a",b,[s("번들 구성 및 빌드 안내"),e(a)]),s("에 따라 "),h,s("을 빌드합니다.")])]),g])}const x=t(l,[["render",f],["__file","ProviderBundling.html.vue"]]),A=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.html","title":"Terraform Provider - 번들링","lang":"ko-KR","frontmatter":{"description":"공식 registry 접근이 불가능하거나 별도로 Provider를 관리해야 하는 경우 사용","tag":["terraform","provider"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Terraform Provider - 번들링"}],["meta",{"property":"og:description","content":"공식 registry 접근이 불가능하거나 별도로 Provider를 관리해야 하는 경우 사용"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"provider"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Terraform Provider - 번들링\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"build 툴 준비","slug":"build-툴-준비","link":"#build-툴-준비","children":[]},{"level":2,"title":"bundle 용 hcl 파일 구성 및 생성","slug":"bundle-용-hcl-파일-구성-및-생성","link":"#bundle-용-hcl-파일-구성-및-생성","children":[]}],"git":{"createdTime":1650413630000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.85,"words":256},"filePathRelative":"04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.md","localizedDate":"2022년 4월 20일","excerpt":"\\n
    \\n

    https://github.com/hashicorp/terraform/tree/main/tools/terraform-bundle
    \\nTerraform Enterprise에서 동작하는 기능입니다.

    \\n
    \\n

    Airgap 환경에서 사용할 특정 버전의 Terraform과 여러 제공자 플러그인을 모두 포함하는 zip 파일 인 \\"번들 아카이브\\"를 생성하는 툴을 사용합니다. 일반적으로 Terraform init을 통해 특정 구성 작업에 필요한 플러그인을 다운로드하고 설치하지만 Airgap 환경에서는 공식 플러그인 저장소에 액세스 할 수 없는 경우가 발생합니다. Bundle 툴을 사용하여 Terraform 버전과 선택한 공급자를 모두 설치하기 위해 대상 시스템에 압축을 풀 수있는 zip 파일이 생성되므로 즉석 플러그인 설치가 필요하지 않습니다.

    "}');export{x as comp,A as data}; diff --git a/assets/ProviderLocalFilesystem.html-XUIaiwWB.js b/assets/ProviderLocalFilesystem.html-XUIaiwWB.js new file mode 100644 index 0000000000..39e5493f93 --- /dev/null +++ b/assets/ProviderLocalFilesystem.html-XUIaiwWB.js @@ -0,0 +1,110 @@ +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c as l,b as n,d as a,a as e,e as t}from"./app-Bzk8Nrll.js";const p={},c=n("h1",{id:"terraform-provider-로컬-디렉토리",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#terraform-provider-로컬-디렉토리"},[n("span",null,"Terraform Provider - 로컬 디렉토리")])],-1),d={href:"https://www.terraform.io/docs/cli/config/config-file.html#implied-local-mirror-directories",target:"_blank",rel:"noopener noreferrer"},u=n("br",null,null,-1),m={href:"https://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providers",target:"_blank",rel:"noopener noreferrer"},v=t(`

    환경

    Test Provider

    `,3),k={href:"https://releases.hashicorp.com/terraform-provider-random/3.1.0/terraform-provider-random_3.1.0_linux_amd64.zip",target:"_blank",rel:"noopener noreferrer"},h={href:"https://releases.hashicorp.com/terraform-provider-nsxt/3.2.1/terraform-provider-nsxt_3.2.1_linux_amd64.zip",target:"_blank",rel:"noopener noreferrer"},b=n("h2",{id:"구성-절차",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#구성-절차"},[n("span",null,"구성 절차")])],-1),f={href:"https://releases.hashicorp.com",target:"_blank",rel:"noopener noreferrer"},g=n("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[n("pre",{class:"language-bash"},[n("code",null,[a("$ "),n("span",{class:"token function"},"wget"),a(` https://releases.hashicorp.com/terraform-provider-nsxt/3.2.1/terraform-provider-nsxt_3.2.1_linux_amd64.zip +$ `),n("span",{class:"token function"},"wget"),a(` https://releases.hashicorp.com/terraform-provider-random/3.1.0/terraform-provider-random_3.1.0_linux_amd64.zip +`)])])],-1),_=t(`
  • Plugin 디렉토리 구성
    로컬 Provider를 찾기위한 디렉토리 구조를 생성합니다. host_name은 환경마다 상이할 수 있습니다.

    ~/.terraform.d/plugins/\${host_name}/\${namespace}/\${type}/\${version}/\${target}

    $ mkdir -p ~/.terraform.d/plugins/localhost.localdomain/vmware/nsxt/3.2.1/linux_amd64
    +$ mkdir -p ~/.terraform.d/plugins/localhost.localdomain/hashicorp/random/3.1.0/linux_amd64
    +
  • Provider 바이너리 파일 구성

    기존에 받아놓은 zip 파일을 압축 해제하고, 생성한 Provider 디렉토리 각각에 맞는 프로바이더를 복사합니다.

    $ unzip terraform-provider-random_3.1.0_linux_amd64.zip
    +Archive:  terraform-provider-random_3.1.0_linux_amd64.zip
    +  inflating: terraform-provider-random_v3.1.0_x5
    +  
    +$ mv ./terraform-provider-random_v3.1.0_x5 ~/.terraform.d/plugins/localhost.localdomain/hashicorp/random/3.1.0/linux_amd64
    +
    +$ unzip terraform-provider-nsxt_3.2.1_linux_amd64.zip
    +Archive:  terraform-provider-nsxt_3.2.1_linux_amd64.zip
    +  inflating: CHANGELOG.md
    +  inflating: LICENSE.txt
    +  inflating: README.md
    +  inflating: terraform-provider-nsxt_v3.2.1
    +  
    +$ mv ./terraform-provider-nsxt_v3.2.1 ~/.terraform.d/plugins/localhost.localdomain/vmware/nsxt/3.2.1/linux_amd64
    +
  • 로컬 Provider 구성 확인

  • `,3),x=t(`

    파일 구조

    $ tree -a ~/.terraform.d/
    +/root/.terraform.d/
    +├── \`plugins\`
    +│   └── localhost.localdomain
    +│       ├── hashicorp
    +│       │   └── random
    +│       │       └── 3.1.0
    +│       │           └── linux_amd64
    +│       │               └── terraform-provider-random_v3.1.0_x5
    +│       └── vmware
    +│           └── nsxt
    +│               └── 3.2.1
    +│                   └── linux_amd64
    +│                       └── terraform-provider-nsxt_v3.2.1
    +├── checkpoint_cache
    +└── checkpoint_signature
    +
    1. 워크스페이스 생성 (디렉토리) - airgapped 는 임의의 이름 입니다.

      $ mkdir ./airgapped
      +$ cd ./airgapped
      +
    2. tf 파일 작성

      $ cat <<EOF> terraform.tf
      +terraform {
      +  required_providers {
      +    nsxt = {
      +      source = "localhost.localdomain/vmware/nsxt"
      +      version = "3.2.1"
      +    }
      +    random = {
      +      source = "localhost.localdomain/hashicorp/random"
      +      version = "3.1.0"
      +    }
      +  }
      +}
      +
      +provider "nsxt" {
      +  # Configuration options
      +}
      +
      +provider "random" {
      +  # Configuration options
      +}
      +
      +resource "random_id" "test" {
      +  byte_length = 8
      +}
      +
      +output "random_id" {
      +  value = random_id.test
      +}
      +EOF
      +
    3. Terraform init을 수행하여 정상적으로 로컬 Provider를 가져오는지 확인합니다.

      $ terraform init
      +
      +Initializing the backend...
      +
      +Initializing provider plugins...
      +- Finding localhost.localdomain/vmware/nsxt versions matching "3.2.1"...
      +- Finding localhost.localdomain/hashicorp/random versions matching "3.1.0"...
      +- Installing localhost.localdomain/vmware/nsxt v3.2.1...
      +- Installed localhost.localdomain/vmware/nsxt v3.2.1 (unauthenticated)
      +- Installing localhost.localdomain/hashicorp/random v3.1.0...
      +- Installed localhost.localdomain/hashicorp/random v3.1.0 (unauthenticated)
      +
      +Terraform has created a lock file .terraform.lock.hcl to record the provider
      +selections it made above. Include this file in your version control repository
      +so that Terraform can guarantee to make the same selections by default when
      +you run "terraform init" in the future.
      +
      +Terraform has been successfully initialized!
      +
      +You may now begin working with Terraform. Try running "terraform plan" to see
      +any changes that are required for your infrastructure. All Terraform commands
      +should now work.
      +
      +If you ever set or change modules or backend configuration for Terraform,
      +rerun this command to reinitialize your working directory. If you forget, other
      +commands will detect it and remind you to do so if necessary.
      +
    `,2);function q(y,T){const s=o("ExternalLinkIcon");return i(),l("div",null,[c,n("blockquote",null,[n("p",null,[n("a",d,[a("https://www.terraform.io/docs/cli/config/config-file.html#implied-local-mirror-directories"),e(s)]),u,n("a",m,[a("https://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providers"),e(s)])])]),v,n("ul",null,[n("li",null,[a("random : "),n("a",k,[a("https://releases.hashicorp.com/terraform-provider-random/3.1.0/terraform-provider-random_3.1.0_linux_amd64.zip"),e(s)])]),n("li",null,[a("nsxt : "),n("a",h,[a("https://releases.hashicorp.com/terraform-provider-nsxt/3.2.1/terraform-provider-nsxt_3.2.1_linux_amd64.zip"),e(s)])])]),b,n("ol",null,[n("li",null,[n("p",null,[a("필요한 Provider zip파일을 "),n("a",f,[a("https://releases.hashicorp.com"),e(s)]),a(" 에서 미리 다운 받습니다. 받아놓은 zip 파일이 있는 경우 대상 시스템에 복사해둡니다.")]),g]),_]),x])}const E=r(p,[["render",q],["__file","ProviderLocalFilesystem.html.vue"]]),O=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.html","title":"Terraform Provider - 로컬 디렉토리","lang":"ko-KR","frontmatter":{"description":"인터넷 사용 불가 시 로컬환경에서의 프로바이더 구성","tag":["terraform","provider"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Terraform Provider - 로컬 디렉토리"}],["meta",{"property":"og:description","content":"인터넷 사용 불가 시 로컬환경에서의 프로바이더 구성"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-19T11:31:31.000Z"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"provider"}],["meta",{"property":"article:modified_time","content":"2023-09-19T11:31:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Terraform Provider - 로컬 디렉토리\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-19T11:31:31.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"환경","slug":"환경","link":"#환경","children":[]},{"level":2,"title":"Test Provider","slug":"test-provider","link":"#test-provider","children":[]},{"level":2,"title":"구성 절차","slug":"구성-절차","link":"#구성-절차","children":[]}],"git":{"createdTime":1650413630000,"updatedTime":1695123091000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":1.34,"words":401},"filePathRelative":"04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.md","localizedDate":"2022년 4월 20일","excerpt":"\\n
    \\n

    https://www.terraform.io/docs/cli/config/config-file.html#implied-local-mirror-directories
    \\nhttps://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providers

    \\n
    "}');export{E as comp,O as data}; diff --git a/assets/ProviderLocalMirroring.html-C7POxPIe.js b/assets/ProviderLocalMirroring.html-C7POxPIe.js new file mode 100644 index 0000000000..a01bf498c9 --- /dev/null +++ b/assets/ProviderLocalMirroring.html-C7POxPIe.js @@ -0,0 +1,15 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as n,c as l,b as r,d as e,a as o}from"./app-Bzk8Nrll.js";const s={},c=r("h1",{id:"terraform-provider-로컬-미러링",tabindex:"-1"},[r("a",{class:"header-anchor",href:"#terraform-provider-로컬-미러링"},[r("span",null,"Terraform Provider - 로컬 미러링")])],-1),p={href:"https://www.terraform.io/docs/cli/config/config-file.html#provider_installation",target:"_blank",rel:"noopener noreferrer"},m={href:"http://registry.terraform.io/",target:"_blank",rel:"noopener noreferrer"},d=r("p",null,"하지만 네트워크이 느리거나 폐쇄망인 경우, 직접 다운로드가 아닌 다른 방법으로 프로바이더를 사용할 수 있습니다.",-1),h=r("p",null,"CLI 설정 파일에 명시적으로 설정하는 방법과 설정하지 않고 사용하는 방법이 있습니다.",-1),f=r("p",null,"상대적으로 설정이 간편한 filesystem_mirror 설정 방법은 다음과 같습니다.",-1),_=r("li",null,[r("p",null,"Terraform 사용 환경에 맞춰 terraform configuration 파일 구성하기"),r("ul",null,[r("li",null,"Windows : 사용자의 %APPDATA% 디렉토리 상에 terraform.rc"),r("li",null,"Linux/MacOS : 사용자 홈 디렉토리 상에 .terraformrc")])],-1),g=r("li",null,[r("p",null,"다음 처럼 'provider_installation' 설정하기"),r("div",{class:"language-text","data-ext":"text","data-title":"text"},[r("pre",{class:"language-text"},[r("code",null,`provider_installation { + filesystem_mirror { + path = "/usr/share/terraform/providers" + include = ["*/*"] # registry.terrafom.io/hashicorp/* + } +} +`)])])],-1),u=r("p",null,"대상 디렉토리 설정하기",-1),v=r("li",null,[r("p",null,"예를 들어 aws provider는 다음과 같이 코드 상에 사용"),r("div",{class:"language-text","data-ext":"text","data-title":"text"},[r("pre",{class:"language-text"},[r("code",null,`terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "3.36.0" + } + } +} +`)])])],-1),T=r("p",null,"지정된 경로 상에 다음과 같은 HOSTNAME/NAMESPACE/TYPE/VERSION/TARGET 형태로 디렉토리 구조를 지정",-1),y={href:"http://registry.terraform.io/",target:"_blank",rel:"noopener noreferrer"},x={href:"https://releases.hashicorp.com/",target:"_blank",rel:"noopener noreferrer"};function w(P,A){const t=i("ExternalLinkIcon");return n(),l("div",null,[c,r("blockquote",null,[r("p",null,[r("a",p,[e("https://www.terraform.io/docs/cli/config/config-file.html#provider_installation"),o(t)])])]),r("p",null,[e("Terraform CLI를 사용할 때, 기본적으로 코드 상에서 사용하는 플러그인은 "),r("a",m,[e("registry.terraform.io"),o(t)]),e("에서 다운로드 받게 되어 있습니다.")]),d,h,f,r("ol",null,[_,g,r("li",null,[u,r("ul",null,[v,r("li",null,[T,r("ul",null,[r("li",null,[e('HOSTNAME = "'),r("a",y,[e("registry.terraform.io"),o(t)]),e('", NAMESPACE="hashicorp", TYPE="aws", VERSION="3.36.0", TARGET은 클라이언트 환경에 대한 것으로 현재 실행 환경에 따라 "darwin_amd64", "linux_arm" "windows_amd64" 등으로 설정하시면 됩니다.')])])]),r("li",null,[r("p",null,[e("사용하시고자 하는 프로바이어더의 다운로드는 다음 링크에서 가능합니다. "),r("a",x,[e("https://releases.hashicorp.com"),o(t)])])])])])])])}const b=a(s,[["render",w],["__file","ProviderLocalMirroring.html.vue"]]),L=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.html","title":"Terraform Provider - 로컬 미러링","lang":"ko-KR","frontmatter":{"description":"공식 registry 접근이 불가능하거나 별도로 Provider를 관리해야 하는 경우 사용","tag":["terraform","provider"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Terraform Provider - 로컬 미러링"}],["meta",{"property":"og:description","content":"공식 registry 접근이 불가능하거나 별도로 Provider를 관리해야 하는 경우 사용"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"provider"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Terraform Provider - 로컬 미러링\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1650413630000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.24,"words":72},"filePathRelative":"04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.md","localizedDate":"2022년 4월 20일","excerpt":"\\n
    \\n

    https://www.terraform.io/docs/cli/config/config-file.html#provider_installation

    \\n
    \\n

    Terraform CLI를 사용할 때, 기본적으로 코드 상에서 사용하는 플러그인은 registry.terraform.io에서 다운로드 받게 되어 있습니다.

    "}');export{b as comp,L as data}; diff --git a/assets/SSH Too many authentication failures.html-BcSkL8WN.js b/assets/SSH Too many authentication failures.html-BcSkL8WN.js new file mode 100644 index 0000000000..629b67599e --- /dev/null +++ b/assets/SSH Too many authentication failures.html-BcSkL8WN.js @@ -0,0 +1,14 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as t,e as s,b as e}from"./app-Bzk8Nrll.js";const o={},i=s(`

    SSH Too many authentication failures

    직역하자면 너무많은 인증 실패로 인한 SSH 접속이 안된다. 는 메시지를 간혹 보게되는 경우가 있다.

    $ ssh myserver
    +Received disconnect from 192.168.0.43 port 22:2: Too many authentication failures
    +Connection to 192.168.0.43 closed by remote host.
    +Connection to 192.168.0.43 closed.
    +

    특히나 클라우드나 VM을 새로 프로비저닝 해서 사용하려고 할때 IP가 중복되어 재사용되어야 하는 경우에 주로 발생하는 걸로 추측된다.

    위 메시지의 발생 원인은 이미 SSH로 접속하려고 하는 클라이언트 환경에 많은 SSH ID 정보가 저장되어있고, SSH Client를 실행할 때 ssh-agent로 이미 알고있는 모든 SSH 키와 다른 모든 키에 대해 접속을 시도하게 된다. 이때 SSH로 접속하고자 하는 원격 서버는 특정 ID 키로 맵핑되어있고, 기존의 키 정보와 맞지 않거나 동일한 대상에 대한 SSH ID 정보와 달라진 것이 원인으로 확인된다.

    해결 방법 1

    접속하고자 하는 Client 환경에서 SSH 키를 초기화 하는 방법

    $ ssh-add -D
    +

    위와 같이 했을 때 Could not open a connection to your authentication agent. 와 같은 오류가 발생한다면 다음 방법으로 초기화 한다.

    $ exec ssh-agent bash
    +$ ssh-add -D
    +All identities removed.
    +

    해결 방법 2

    SSH 옵션으로 Public Key를 이용한 접속을 일시적으로 사용하지 않도록 하는 방법

    $ ssh -p 22 -o PubkeyAuthentication=no username@myserver
    +

    해결 방법 3

    ~/.ssh/config의 대상 호스트에 IdentitiesOnly=yes를 추가하는 벙법
    많은 ID를 제공 하더라도 ssh가 ssh_config 파일에 구성된 인증 ID 파일만 사용하도록 지정한다고 함

    `,15),c=e("div",{class:"language-ini","data-ext":"ini","data-title":"ini"},[e("pre",{ini:"",class:"language-ini"},[e("code",null,`Host myserver +IdentityFile ~/.ssh/key_rsa +IdentitiesOnly yes +Port 22 +`)]),e("div",{class:"highlight-lines"},[e("br"),e("br"),e("div",{class:"highlight-line"}," "),e("br")])],-1),r=[i,c];function l(p,d){return n(),t("div",null,r)}const m=a(o,[["render",l],["__file","SSH Too many authentication failures.html.vue"]]),g=JSON.parse('{"path":"/01-Infrastructure/Linux/TroubleShooting/SSH%20Too%20many%20authentication%20failures.html","title":"SSH Too many authentication failures","lang":"ko-KR","frontmatter":{"description":"SSH Too many authentication failures","tag":["linux","ssh"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/01-Infrastructure/Linux/TroubleShooting/SSH%20Too%20many%20authentication%20failures.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"SSH Too many authentication failures"}],["meta",{"property":"og:description","content":"SSH Too many authentication failures"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"linux"}],["meta",{"property":"article:tag","content":"ssh"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SSH Too many authentication failures\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"해결 방법 1","slug":"해결-방법-1","link":"#해결-방법-1","children":[]},{"level":2,"title":"해결 방법 2","slug":"해결-방법-2","link":"#해결-방법-2","children":[]},{"level":2,"title":"해결 방법 3","slug":"해결-방법-3","link":"#해결-방법-3","children":[]}],"git":{"createdTime":1628085698000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2}]},"readingTime":{"minutes":0.39,"words":117},"filePathRelative":"01-Infrastructure/Linux/TroubleShooting/SSH Too many authentication failures.md","localizedDate":"2021년 8월 4일","excerpt":"\\n

    직역하자면 너무많은 인증 실패로 인한 SSH 접속이 안된다. 는 메시지를 간혹 보게되는 경우가 있다.

    \\n
    $ ssh myserver\\nReceived disconnect from 192.168.0.43 port 22:2: Too many authentication failures\\nConnection to 192.168.0.43 closed by remote host.\\nConnection to 192.168.0.43 closed.\\n
    "}');export{m as comp,g as data}; diff --git a/assets/Server.html-C8bYcrnP.js b/assets/Server.html-C8bYcrnP.js new file mode 100644 index 0000000000..f28e862f30 --- /dev/null +++ b/assets/Server.html-C8bYcrnP.js @@ -0,0 +1,81 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-Bzk8Nrll.js";const t={},o=e(`

    Nomad 서버 설정

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server설정 파일입니다.
    네트워크는 프라이빗(온프레이머스) 환경입니다.

    #nomad server 설정
    +server {
    +  enabled = true
    +  bootstrap_expect = 3
    +  license_path="/opt/nomad/license/nomad.license"
    +  server_join {
    +    retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
    +  }
    +  raft_protocol = 3
    +  event_buffer_size = 100
    +  non_voting_server = false
    +  heartbeat_grace = "10s"
    +}
    + 
    + 
    +#tls 설정
    +tls {
    +  http = true
    +  rpc  = true
    + 
    +  ca_file   = "/opt/ssl/nomad/nomad-agent-ca.pem"
    +  cert_file = "/opt/ssl/nomad/global-server-nomad-0.pem"
    +  key_file  = "/opt/ssl/nomad/global-server-nomad-0-key.pem"
    + 
    +  #UI오픈할 서버만 변경
    +  verify_server_hostname = false
    +  verify_https_client    = false
    +  #일반서버는 아래와 같이 설정
    +  verify_server_hostname = true
    +  verify_https_client    = true
    +}
    +

    Nomad 서버 최소 설정 (20220807기준)

    data_dir = "/opt/consul"
    +
    +client_addr = "0.0.0.0"
    +
    +datacenter = "my-dc"
    +
    +#ui
    +ui_config {
    +  enabled = true
    +}
    +
    +# server
    +server = true
    +
    +# Bind addr
    +bind_addr = "0.0.0.0" # Listen on all IPv4
    +# Advertise addr - if you want to point clients to a different address than bind or LB.
    +advertise_addr = "node ip"
    +
    +# Enterprise License
    +license_path = "/opt/nomad/nomad.lic"
    +
    +# bootstrap_expect
    +bootstrap_expect=1
    +
    +# encrypt
    +encrypt = "7w+zkhqa+YD4GSKXjRWETBIT8hs53Sr/w95oiVxq5Qc="
    +
    +# retry_join
    +retry_join = ["server ip"]
    +
    +key_file = "/opt/consul/my-dc-server-consul-0-key.pem"
    +cert_file = "/opt/consul/my-dc-server-consul-0.pem"
    +ca_file = "/opt/consul/consul-agent-ca.pem"
    +auto_encrypt {
    +  allow_tls = true
    +}
    +
    +verify_incoming = false
    +verify_incoming_rpc = false
    +verify_outgoing = false
    +verify_server_hostname = false
    +
    +ports {
    +  http = 8500
    +  dns = 8600
    +  server = 8300
    +}
    +
    +
    `,5),p=[o];function i(l,c){return s(),a("div",null,p)}const u=n(t,[["render",i],["__file","Server.html.vue"]]),v=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/02-Config/Server.html","title":"Nomad 서버 설정","lang":"ko-KR","frontmatter":{"description":"Nomad Server Configuration","tag":["Nomad","Enterprise","Configuration","Server"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/02-Config/Server.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Nomad 서버 설정"}],["meta",{"property":"og:description","content":"Nomad Server Configuration"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"Enterprise"}],["meta",{"property":"article:tag","content":"Configuration"}],["meta",{"property":"article:tag","content":"Server"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Nomad 서버 설정\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1629555140000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"admin","email":"sulee@expernet.co.kr","commits":2},{"name":"swbs90","email":"swbs90@naver.com","commits":1}]},"readingTime":{"minutes":0.54,"words":161},"filePathRelative":"04-HashiCorp/07-Nomad/02-Config/Server.md","localizedDate":"2021년 8월 21일","excerpt":"\\n
    \\n

    \\n

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server설정 파일입니다.
    \\n네트워크는 프라이빗(온프레이머스) 환경입니다.

    \\n
    \\n
    #nomad server 설정\\nserver {\\n  enabled = true\\n  bootstrap_expect = 3\\n  license_path=\\"/opt/nomad/license/nomad.license\\"\\n  server_join {\\n    retry_join = [\\"172.30.1.17\\",\\"172.30.1.18\\",\\"172.30.1.19\\"]\\n  }\\n  raft_protocol = 3\\n  event_buffer_size = 100\\n  non_voting_server = false\\n  heartbeat_grace = \\"10s\\"\\n}\\n \\n \\n#tls 설정\\ntls {\\n  http = true\\n  rpc  = true\\n \\n  ca_file   = \\"/opt/ssl/nomad/nomad-agent-ca.pem\\"\\n  cert_file = \\"/opt/ssl/nomad/global-server-nomad-0.pem\\"\\n  key_file  = \\"/opt/ssl/nomad/global-server-nomad-0-key.pem\\"\\n \\n  #UI오픈할 서버만 변경\\n  verify_server_hostname = false\\n  verify_https_client    = false\\n  #일반서버는 아래와 같이 설정\\n  verify_server_hostname = true\\n  verify_https_client    = true\\n}\\n
    "}');export{u as comp,v as data}; diff --git a/assets/StateRemove.html-DwYONtji.js b/assets/StateRemove.html-DwYONtji.js new file mode 100644 index 0000000000..7b5e64a28e --- /dev/null +++ b/assets/StateRemove.html-DwYONtji.js @@ -0,0 +1,34 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e as a}from"./app-Bzk8Nrll.js";const t={},o=a(`

    State rm

    `,2),l=[o];function r(d,i){return n(),s("div",null,l)}const p=e(t,[["render",r],["__file","StateRemove.html.vue"]]),m=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.html","title":"State rm","lang":"ko-KR","frontmatter":{"description":"Terraform State Maintenance","tag":["Terraform","State"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"State rm"}],["meta",{"property":"og:description","content":"Terraform State Maintenance"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Terraform"}],["meta",{"property":"article:tag","content":"State"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"State rm\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1642083602000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.39,"words":116},"filePathRelative":"04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.md","localizedDate":"2022년 1월 13일","excerpt":"\\n"}');export{p as comp,m as data}; diff --git a/assets/TFEAdminPasswordReset.html-3khlDNqw.js b/assets/TFEAdminPasswordReset.html-3khlDNqw.js new file mode 100644 index 0000000000..fb77cbc896 --- /dev/null +++ b/assets/TFEAdminPasswordReset.html-3khlDNqw.js @@ -0,0 +1,22 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as n,e as t}from"./app-Bzk8Nrll.js";const e={},p=t(`

    Terraform Enterprise 사용자 비밀번호 변경

    Terraform Enterprise를 사용할 때, UI(https://TFE_SERVER) 상으로 접속할 수 없는 상황에서 비밀번호 변경이 필요한 경우, 아래와 같이 작업할 수 있다.

    Admin 계정의 경우

    다음과 같이 수정 가능.

    # 이전 버전의 TFE
    +sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
    +## 수정 최신 버전의 TFE에서는 Container 이름이 변경됨 (2022.6.21)
    +sudo docker exec -it tfe-atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
    +
    irb(main):050:0> admin_user = User.find_by(username: "tfe-local-admin")
    +=> #<User id: 33, email: "tfe-local-admin@test.com", username: "tfe-local-admin", is_admin: false, created_at: "2020-06-24 05:12:12", updated_at: "2020-07-01 09:12:25", suspended_at: nil, two_factor_delivery: nil, two_factor_sms_number: nil, two_factor_secret_key: nil, two_factor_recovery_index: 0, two_factor_recovery_secret_key: nil, two_factor_verified_at: nil, two_factor_enabled_at: nil, is_service_account: false, used_recovery_codes_encrypted: nil, last_auth_through_saml: nil, external_id: "user-361SGA3yMg3P1nGT", accepted_terms_at: nil, accepted_privacy_policy_at: nil, invitation_token: nil, invitation_created_at: nil, is_cyborg: false, onboarding_status: nil>
    +irb(main):051:0> admin_user.password = '<<Password>>'
    +=> "<<Password>>"
    +irb(main):052:0> admin_user.password_confirmation = '<<Password>>'
    +=> "<<Password>>"
    +irb(main):053:0> admin_user.save
    +2020-07-01 10:03:32 [DEBUG] {:msg=>"SettingStorage::Postgres failed to look up setting 'basic.base_domain'"}
    +=> true
    +

    일반 사용자의 경우

    sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
    +user = User.find_by(email: "user@example.com")
    +user.update(:password => '<<PASSWORD>>')
    +user.save!
    +

    일반 사용자를 Admin으로 승격이 필요할 때

    sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
    +user = User.find_by(email: "user@example.com")
    +user.update(is_admin: true)
    +user.save!
    +
    `,10),o=[p];function r(c,i){return s(),n("div",null,o)}const u=a(e,[["render",r],["__file","TFEAdminPasswordReset.html.vue"]]),k=JSON.parse(`{"path":"/04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.html","title":"Terraform Enterprise 사용자 비밀번호 변경","lang":"ko-KR","frontmatter":{"description":"TFE Admin 사용자 비밀 번호 변경이 필요할 경우","author":"jsp","tag":["terraform","admin","password"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Terraform Enterprise 사용자 비밀번호 변경"}],["meta",{"property":"og:description","content":"TFE Admin 사용자 비밀 번호 변경이 필요할 경우"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:author","content":"jsp"}],["meta",{"property":"article:tag","content":"terraform"}],["meta",{"property":"article:tag","content":"admin"}],["meta",{"property":"article:tag","content":"password"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Terraform Enterprise 사용자 비밀번호 변경\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"jsp\\"}]}"]]},"headers":[{"level":2,"title":"Admin 계정의 경우","slug":"admin-계정의-경우","link":"#admin-계정의-경우","children":[]},{"level":2,"title":"일반 사용자의 경우","slug":"일반-사용자의-경우","link":"#일반-사용자의-경우","children":[]},{"level":2,"title":"일반 사용자를 Admin으로 승격이 필요할 때","slug":"일반-사용자를-admin으로-승격이-필요할-때","link":"#일반-사용자를-admin으로-승격이-필요할-때","children":[]}],"git":{"createdTime":1631870131000,"updatedTime":1695042774000,"contributors":[{"name":"powhapki","email":"powhapki@gmail.com","commits":11},{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.73,"words":219},"filePathRelative":"04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.md","localizedDate":"2021년 9월 17일","excerpt":"\\n

    Terraform Enterprise를 사용할 때, UI(https://TFE_SERVER) 상으로 접속할 수 없는 상황에서 비밀번호 변경이 필요한 경우, 아래와 같이 작업할 수 있다.

    \\n

    Admin 계정의 경우

    \\n

    다음과 같이 수정 가능.

    \\n
    # 이전 버전의 TFE\\nsudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'\\n## 수정 최신 버전의 TFE에서는 Container 이름이 변경됨 (2022.6.21)\\nsudo docker exec -it tfe-atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'\\n
    ","copyright":{"author":"jsp"}}`);export{u as comp,k as data}; diff --git a/assets/TFE_v202111-1(582)_Issue.html-BxHCp0t5.js b/assets/TFE_v202111-1(582)_Issue.html-BxHCp0t5.js new file mode 100644 index 0000000000..00c6199a80 --- /dev/null +++ b/assets/TFE_v202111-1(582)_Issue.html-BxHCp0t5.js @@ -0,0 +1 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as l,c as s,b as e,d as t,a as o,e as i}from"./app-Bzk8Nrll.js";const c="/assets/tfe_v202111-1_issue_portal--GrQwtKW.png",p={},d=i('

    TFE Release v202111-1 (582) Issue

    v202111-1 (582) 버전 이상으로 설치, 또는 업그레이드 시 발생하는 이슈

    현상

    error_console
    error_console
    ',4),m=e("thead",null,[e("tr",null,[e("th",null,"Nginx access Log")])],-1),h={href:"http://172.11.0.1:9292/",target:"_blank",rel:"noopener noreferrer"},u={href:"http://tfe.mydomain.com",target:"_blank",rel:"noopener noreferrer"},_=e("thead",null,[e("tr",null,[e("th",null,"Ptfe_atlas Log")])],-1),f={href:"https://github.com/rails/execjs",target:"_blank",rel:"noopener noreferrer"},g=e("h2",{id:"v202111-1-582-release",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v202111-1-582-release"},[e("span",null,"v202111-1 (582) Release")])],-1),b=e("p",null,"v202111-1 (582) Release를 기점으로 TFE 내 포함된 container 들에 대한 update 가 있었으며, 해당 update 는 Alpine 3.14 를 사용하게 되는데 이는 특정 버전 이상의 'docker' 와 'libsecomp' 를 요구합니다. (docker - 20.10.0 이상 / libsecomp - 2.4.4 이상)",-1),v={href:"https://www.terraform.io/enterprise/release/v202111-1",target:"_blank",rel:"noopener noreferrer"},T={href:"https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.14.0#faccessat2",target:"_blank",rel:"noopener noreferrer"};function y(k,x){const r=a("ExternalLinkIcon");return l(),s("div",null,[d,e("table",null,[m,e("tbody",null,[e("tr",null,[e("td",null,[t('2021/12/17 02:58:31 [error] 10#10: *913 connect(0mfailed (111: Connection refused) while connecting to upstream, client: 10.10.10.100, server:tfe.mydomain.com, reguest: "GET / HTTP/1.1", upstream: "'),e("a",h,[t("http://172.11.0.1:9292/"),o(r)]),t('", host: "'),e("a",u,[t("tfe.mydomain.com"),o(r)]),t('"')])])])]),e("table",null,[_,e("tbody",null,[e("tr",null,[e("td",null,[t("ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. see "),e("a",f,[t("https://github.com/rails/execjs"),o(r)]),t(" for a list fo available runtimes.")])])])]),g,b,e("ul",null,[e("li",null,[t("TFE Release v202111-1 (582) : "),e("a",v,[t("https://www.terraform.io/enterprise/release/v202111-1"),o(r)])]),e("li",null,[t("Release Notes for Alpine 3.14.0 : "),e("a",T,[t("https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.14.0#faccessat2"),o(r)])])])])}const w=n(p,[["render",y],["__file","TFE_v202111-1(582)_Issue.html.vue"]]),F=JSON.parse('{"path":"/04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.html","title":"TFE Release v202111-1 (582) Issue","lang":"ko-KR","frontmatter":{"description":"Terraform Enterprise - Ready state command canceled: context deadline exceeded","tag":["Terraform","Enterprise"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"TFE Release v202111-1 (582) Issue"}],["meta",{"property":"og:description","content":"Terraform Enterprise - Ready state command canceled: context deadline exceeded"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:37:06.000Z"}],["meta",{"property":"article:tag","content":"Terraform"}],["meta",{"property":"article:tag","content":"Enterprise"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:37:06.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"TFE Release v202111-1 (582) Issue\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:37:06.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"현상","slug":"현상","link":"#현상","children":[]},{"level":2,"title":"v202111-1 (582) Release","slug":"v202111-1-582-release","link":"#v202111-1-582-release","children":[]}],"git":{"createdTime":1640238674000,"updatedTime":1695044226000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":2},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":0.39,"words":116},"filePathRelative":"04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.md","localizedDate":"2021년 12월 23일","excerpt":"\\n
    \\n

    v202111-1 (582) 버전 이상으로 설치, 또는 업그레이드 시 발생하는 이슈

    \\n
    \\n

    현상

    \\n
    error_console
    \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
    Nginx access Log
    2021/12/17 02:58:31 [error] 10#10: *913 connect(0mfailed (111: Connection refused) while connecting to upstream, client: 10.10.10.100, server:tfe.mydomain.com, reguest: \\"GET / HTTP/1.1\\", upstream: \\"http://172.11.0.1:9292/\\", host: \\"tfe.mydomain.com\\"
    "}');export{w as comp,F as data}; diff --git a/assets/Tableau10-B-NsZVaP.js b/assets/Tableau10-B-NsZVaP.js new file mode 100644 index 0000000000..4223ec34a3 --- /dev/null +++ b/assets/Tableau10-B-NsZVaP.js @@ -0,0 +1 @@ +function o(e){for(var c=e.length/6|0,n=new Array(c),a=0;aTabs 기본 사용법
    ::: tabs
    +@tab title
    +__markdown content__
    +
    +@tab javascript
    +\`\`\` javascript
    +() => {
    +  console.log('Javascript code example')
    +}
    +\`\`\`
    +:::
    +

    다음과 같이 표기됩니다.

    `,3),f=a("p",null,[a("strong",null,"markdown content")],-1),x=a("div",{class:"language-javascript","data-ext":"js","data-title":"js"},[a("pre",{class:"language-javascript"},[a("code",null,[a("span",{class:"token punctuation"},"("),a("span",{class:"token punctuation"},")"),n(),a("span",{class:"token operator"},"=>"),n(),a("span",{class:"token punctuation"},"{"),n(` + console`),a("span",{class:"token punctuation"},"."),a("span",{class:"token function"},"log"),a("span",{class:"token punctuation"},"("),a("span",{class:"token string"},"'Javascript code example'"),a("span",{class:"token punctuation"},")"),n(` +`),a("span",{class:"token punctuation"},"}"),n(` +`)])])],-1),w=c(`

    Code Tabs 기본 사용법

    Code Tabs는 코드 블록만을 표기하는 탭을 제공합니다.

    ::: code-tabs#shell
    +
    +@tab pnpm
    +
    +\`\`\`bash
    +pnpm add -D vuepress-plugin-md-enhance
    +\`\`\`
    +
    +@tab yarn
    +
    +\`\`\`bash
    +yarn add -D vuepress-plugin-md-enhance
    +\`\`\`
    +
    +@tab:active npm
    +
    +\`\`\`bash
    +npm i -D vuepress-plugin-md-enhance
    +\`\`\`
    +
    +:::
    +

    다음과 같이 표기됩니다.

    `,4),y=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token function"},"pnpm"),n(),a("span",{class:"token function"},"add"),n(),a("span",{class:"token parameter variable"},"-D"),n(` vuepress-plugin-md-enhance +`)])])],-1),C=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token function"},"yarn"),n(),a("span",{class:"token function"},"add"),n(),a("span",{class:"token parameter variable"},"-D"),n(` vuepress-plugin-md-enhance +`)])])],-1),j=a("div",{class:"language-bash","data-ext":"sh","data-title":"sh"},[a("pre",{class:"language-bash"},[a("code",null,[a("span",{class:"token function"},"npm"),n(" i "),a("span",{class:"token parameter variable"},"-D"),n(` vuepress-plugin-md-enhance +`)])])],-1);function A(D,E){const l=o("ExternalLinkIcon"),p=o("Tabs"),d=o("CodeTabs");return u(),b("div",null,[v,a("p",null,[n("컨텐츠에 탭을 추가하여 상황에 따라 선택적으로 문서를 읽을 수 있도록 합니다."),h,n(" 상세 내용은 "),k,n("의 "),a("a",g,[n("Tabs"),i(l)]),n(", "),a("a",_,[n("Code Tabs"),i(l)]),n(" 를 확인해보세요.")]),T,i(p,{id:"13",data:[{id:"title"},{id:"javascript"}]},{title0:s(({value:e,isActive:t})=>[n("title")]),title1:s(({value:e,isActive:t})=>[n("javascript")]),tab0:s(({value:e,isActive:t})=>[f]),tab1:s(({value:e,isActive:t})=>[x]),_:1}),w,i(d,{id:"33",data:[{id:"pnpm"},{id:"yarn"},{id:"npm"}],active:2,"tab-id":"shell"},{title0:s(({value:e,isActive:t})=>[n("pnpm")]),title1:s(({value:e,isActive:t})=>[n("yarn")]),title2:s(({value:e,isActive:t})=>[n("npm")]),tab0:s(({value:e,isActive:t})=>[y]),tab1:s(({value:e,isActive:t})=>[C]),tab2:s(({value:e,isActive:t})=>[j]),_:1})])}const V=r(m,[["render",A],["__file","Tabs.html.vue"]]),B=JSON.parse('{"path":"/00-Howto/03-Tips/Tabs.html","title":"Tabs","lang":"ko-KR","frontmatter":{"description":"Tabs 컨텐츠에 탭을 추가하여 상황에 따라 선택적으로 문서를 읽을 수 있도록 합니다. 상세 내용은 Markdown Enhance의 Tabs, Code Tabs 를 확인해보세요. Tabs 기본 사용법 다음과 같이 표기됩니다. Code Tabs 기본 사용법 Code Tabs는 코드 블록만을 표기하는 탭을 제공합니다. ...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/03-Tips/Tabs.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Tabs"}],["meta",{"property":"og:description","content":"Tabs 컨텐츠에 탭을 추가하여 상황에 따라 선택적으로 문서를 읽을 수 있도록 합니다. 상세 내용은 Markdown Enhance의 Tabs, Code Tabs 를 확인해보세요. Tabs 기본 사용법 다음과 같이 표기됩니다. Code Tabs 기본 사용법 Code Tabs는 코드 블록만을 표기하는 탭을 제공합니다. ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-10-25T07:22:33.000Z"}],["meta",{"property":"article:modified_time","content":"2023-10-25T07:22:33.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Tabs\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-10-25T07:22:33.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Tabs 기본 사용법","slug":"tabs-기본-사용법","link":"#tabs-기본-사용법","children":[]},{"level":2,"title":"Code Tabs 기본 사용법","slug":"code-tabs-기본-사용법","link":"#code-tabs-기본-사용법","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1698218553000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":3},{"name":"Administrator","email":"admin@example.com","commits":1}]},"readingTime":{"minutes":0.4,"words":119},"filePathRelative":"00-Howto/03-Tips/Tabs.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    컨텐츠에 탭을 추가하여 상황에 따라 선택적으로 문서를 읽을 수 있도록 합니다.
    \\n상세 내용은 Markdown EnhanceTabs, Code Tabs 를 확인해보세요.

    ","autoDesc":true}');export{V as comp,B as data}; diff --git a/assets/Terraform_Logo-Bt0ckPKZ.png b/assets/Terraform_Logo-Bt0ckPKZ.png new file mode 100644 index 0000000000..02f47aec83 Binary files /dev/null and b/assets/Terraform_Logo-Bt0ckPKZ.png differ diff --git a/assets/TipBox.html-DpUp3CBK.js b/assets/TipBox.html-DpUp3CBK.js new file mode 100644 index 0000000000..7e83132b92 --- /dev/null +++ b/assets/TipBox.html-DpUp3CBK.js @@ -0,0 +1,27 @@ +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c as l,b as n,d as e,a as s,e as i}from"./app-Bzk8Nrll.js";const d={},c=n("h1",{id:"tip-box",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#tip-box"},[n("span",null,"Tip Box")])],-1),p=n("br",null,null,-1),m={href:"https://vuepress.vuejs.org/guide/markdown.html#:~:text=markdown.toc%20option.-,%23,-Custom%20Containers",target:"_blank",rel:"noopener noreferrer"},u=i(`

    기본 사용법

    ::: tip
    +This is a tip
    +:::
    +
    +::: warning
    +This is a warning
    +:::
    +
    +::: danger
    +This is a dangerous warning
    +:::
    +
    +::: details
    +This is a details block, which does not work in IE / Edge
    +:::
    +

    다음과 같이 표기됩니다.

    This is a tip

    경고

    This is a warning

    위험

    This is a dangerous warning

    세부사항

    This is a details block, which does not work in IE / Edge

    응용

    타입 우측에 타이틀명을 추가하여 기본 값을 변경합니다.

    ::: danger STOP
    +Danger zone, do not proceed
    +Go to [here](https://vuepress.vuejs.org/guide/markdown.html#:~:text=markdown.toc%20option.-,%23,-Custom%20Containers)
    +:::
    +
    +::: details Click me to view the code
    +\`\`\`js
    +console.log('Hello, VuePress!')
    +\`\`\`
    +:::
    +
    `,10),h={class:"hint-container caution"},v=n("p",{class:"hint-container-title"},"STOP",-1),g=n("br",null,null,-1),k={href:"https://vuepress.vuejs.org/guide/markdown.html#:~:text=markdown.toc%20option.-,%23,-Custom%20Containers",target:"_blank",rel:"noopener noreferrer"},b=i(`
    Click me to view the code
    console.log('Hello, VuePress!')
    +
    `,1);function T(w,_){const a=o("ExternalLinkIcon");return r(),l("div",null,[c,n("p",null,[e("문서 작성시 팁과 주의사항을 표기하는 방법을 설명합니다."),p,n("a",m,[e("공식 문서"),s(a)])]),u,n("div",h,[v,n("p",null,[e("Danger zone, do not proceed"),g,e(" Go to "),n("a",k,[e("here"),s(a)])])]),b])}const y=t(d,[["render",T],["__file","TipBox.html.vue"]]),B=JSON.parse('{"path":"/00-Howto/03-Tips/TipBox.html","title":"Tip Box","lang":"ko-KR","frontmatter":{"description":"Tip Box 문서 작성시 팁과 주의사항을 표기하는 방법을 설명합니다. 공식 문서 기본 사용법 다음과 같이 표기됩니다. 팁 This is a tip 경고 This is a warning 위험 This is a dangerous warning 세부사항 This is a details block, which does n...","head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/00-Howto/03-Tips/TipBox.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Tip Box"}],["meta",{"property":"og:description","content":"Tip Box 문서 작성시 팁과 주의사항을 표기하는 방법을 설명합니다. 공식 문서 기본 사용법 다음과 같이 표기됩니다. 팁 This is a tip 경고 This is a warning 위험 This is a dangerous warning 세부사항 This is a details block, which does n..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Tip Box\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"기본 사용법","slug":"기본-사용법","link":"#기본-사용법","children":[]},{"level":2,"title":"응용","slug":"응용","link":"#응용","children":[]}],"git":{"createdTime":1634225909000,"updatedTime":1695042774000,"contributors":[{"name":"Administrator","email":"admin@example.com","commits":1},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.44,"words":133},"filePathRelative":"00-Howto/03-Tips/TipBox.md","localizedDate":"2021년 10월 15일","excerpt":"\\n

    문서 작성시 팁과 주의사항을 표기하는 방법을 설명합니다.
    \\n공식 문서

    \\n

    기본 사용법

    \\n
    ::: tip\\nThis is a tip\\n:::\\n\\n::: warning\\nThis is a warning\\n:::\\n\\n::: danger\\nThis is a dangerous warning\\n:::\\n\\n::: details\\nThis is a details block, which does not work in IE / Edge\\n:::\\n
    ","autoDesc":true}');export{y as comp,B as data}; diff --git a/assets/VaultSWLB-nginx.html-Ba3IVIOo.js b/assets/VaultSWLB-nginx.html-Ba3IVIOo.js new file mode 100644 index 0000000000..1b63c04094 --- /dev/null +++ b/assets/VaultSWLB-nginx.html-Ba3IVIOo.js @@ -0,0 +1,84 @@ +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-Bzk8Nrll.js";const e={},p=t(`

    Vault SWLB용도의 nginx

    nginx job 파일

    job "nginx" {
    +  datacenters = ["dc1"]
    +
    +  group "nginx" {
    +
    +    constraint {
    +      attribute = "${attr.unique.hostname}"
    +      value     = "slave0"
    +    }
    +
    +    #Vault tls가 있고 nomad client hcl 파일에 host volume이 명시되어 있는 설정 값
    +    volume "cert-data" {
    +      type      = "host"
    +      source    = "cert-data"
    +      read_only = false
    +    }
    +
    +    #실패 없이 되라고 행운의 숫자인 7을 4번 줌
    +    network {
    +      port "http" {
    +        to     = 7777
    +        static = 7777
    +      }
    +    }
    +
    +    service {
    +      name = "nginx"
    +      port = "http"
    +    }
    +
    +    task "nginx" {
    +      driver = "docker"
    +
    +      volume_mount {
    +        volume      = "cert-data"
    +        destination = "/usr/local/cert"
    +      }
    +
    +      config {
    +        image = "nginx"
    +
    +        ports = ["http"]
    +        volumes = [
    +          "local:/etc/nginx/conf.d",
    +
    +        ]
    +      }
    +      template {
    +        data = <<EOF        
    +#Vault는 active서버 1대외에는 전부 standby상태이며 
    +#서비스 호출 시(write)에는 active 서비스만 호출해야함으로 아래와 같이 consul에서 서비스를 불러옴
    +
    +upstream backend {
    +{{ range service "active.vault" }}
    +  server {{ .Address }}:{{ .Port }};
    +{{ else }}server 127.0.0.1:65535; # force a 502
    +{{ end }}
    +}
    +
    +server {
    +   listen 7777 ssl;
    +   #위에서 nomad host volume을 mount한 cert를 가져옴
    +   ssl on;
    +   ssl_certificate /usr/local/cert/vault/global-client-vault-0.pem;
    +   ssl_certificate_key /usr/local/cert/vault/global-client-vault-0-key.pem;
    +   #vault ui 접근 시 / -> /ui redirect되기 때문에 location이 /외에는 되지 않는다.
    +   location / {
    +      proxy_pass https://backend;
    +   }
    +}
    +EOF
    +
    +        destination   = "local/load-balancer.conf"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +      resources {
    +        cpu = 100
    +        memory = 201
    +      }
    +    }
    +  }
    +}
    +
    `,4),o=[p];function c(l,i){return s(),a("div",null,o)}const d=n(e,[["render",c],["__file","VaultSWLB-nginx.html.vue"]]),k=JSON.parse('{"path":"/04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.html","title":"Vault SWLB용도의 nginx","lang":"ko-KR","frontmatter":{"description":"Nomad Sample","tag":["Nomad","Sample","Job","Vault","SWLB"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"Vault SWLB용도의 nginx"}],["meta",{"property":"og:description","content":"Nomad Sample"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Nomad"}],["meta",{"property":"article:tag","content":"Sample"}],["meta",{"property":"article:tag","content":"Job"}],["meta",{"property":"article:tag","content":"Vault"}],["meta",{"property":"article:tag","content":"SWLB"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vault SWLB용도의 nginx\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1645433790000,"updatedTime":1695042774000,"contributors":[{"name":"ung","email":"swbs90@naver.com","commits":3},{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.53,"words":159},"filePathRelative":"04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.md","localizedDate":"2022년 2월 21일","excerpt":"\\n\\n

    nginx job 파일

    \\n
    job \\"nginx\\" {\\n  datacenters = [\\"dc1\\"]\\n\\n  group \\"nginx\\" {\\n\\n    constraint {\\n      attribute = \\"${attr.unique.hostname}\\"\\n      value     = \\"slave0\\"\\n    }\\n\\n    #Vault tls가 있고 nomad client hcl 파일에 host volume이 명시되어 있는 설정 값\\n    volume \\"cert-data\\" {\\n      type      = \\"host\\"\\n      source    = \\"cert-data\\"\\n      read_only = false\\n    }\\n\\n    #실패 없이 되라고 행운의 숫자인 7을 4번 줌\\n    network {\\n      port \\"http\\" {\\n        to     = 7777\\n        static = 7777\\n      }\\n    }\\n\\n    service {\\n      name = \\"nginx\\"\\n      port = \\"http\\"\\n    }\\n\\n    task \\"nginx\\" {\\n      driver = \\"docker\\"\\n\\n      volume_mount {\\n        volume      = \\"cert-data\\"\\n        destination = \\"/usr/local/cert\\"\\n      }\\n\\n      config {\\n        image = \\"nginx\\"\\n\\n        ports = [\\"http\\"]\\n        volumes = [\\n          \\"local:/etc/nginx/conf.d\\",\\n\\n        ]\\n      }\\n      template {\\n        data = <<EOF        \\n#Vault는 active서버 1대외에는 전부 standby상태이며 \\n#서비스 호출 시(write)에는 active 서비스만 호출해야함으로 아래와 같이 consul에서 서비스를 불러옴\\n\\nupstream backend {\\n{{ range service \\"active.vault\\" }}\\n  server {{ .Address }}:{{ .Port }};\\n{{ else }}server 127.0.0.1:65535; # force a 502\\n{{ end }}\\n}\\n\\nserver {\\n   listen 7777 ssl;\\n   #위에서 nomad host volume을 mount한 cert를 가져옴\\n   ssl on;\\n   ssl_certificate /usr/local/cert/vault/global-client-vault-0.pem;\\n   ssl_certificate_key /usr/local/cert/vault/global-client-vault-0-key.pem;\\n   #vault ui 접근 시 / -> /ui redirect되기 때문에 location이 /외에는 되지 않는다.\\n   location / {\\n      proxy_pass https://backend;\\n   }\\n}\\nEOF\\n\\n        destination   = \\"local/load-balancer.conf\\"\\n        change_mode   = \\"signal\\"\\n        change_signal = \\"SIGHUP\\"\\n      }\\n      resources {\\n        cpu = 100\\n        memory = 201\\n      }\\n    }\\n  }\\n}\\n
    "}');export{d as comp,k as data}; diff --git a/assets/Vsphere_template_issue.html-8QDvgHPa.js b/assets/Vsphere_template_issue.html-8QDvgHPa.js new file mode 100644 index 0000000000..74193cc92f --- /dev/null +++ b/assets/Vsphere_template_issue.html-8QDvgHPa.js @@ -0,0 +1,25 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as n}from"./app-Bzk8Nrll.js";const t={},l=n(`

    VSphere 템플릿 생성 이슈

    1. redhat 계열
    1. debian 계열
    #수정 할 파일
    +vi /etc/systemd/system/vmtoolsd.service
    +
    +[Unit]
    +Description=Service for virtual machines hosted on VMware
    +Documentation=http://open-vm-tools.sourceforge.net/about.php
    +ConditionVirtualization=vmware
    +DefaultDependencies=no
    +Before=cloud-init-local.service
    +#아래 After=dbus.service추가
    +After=dbus.service
    +After=vgauth.service
    +After=apparmor.service
    +RequiresMountsFor=/tmp
    +After=systemd-remount-fs.service systemd-tmpfiles-setup.service systemd-modules-load.service
    +
    +[Service]
    +ExecStart=/usr/bin/vmtoolsd
    +TimeoutStopSec=5
    +
    +[Install]
    +WantedBy=multi-user.target
    +Alias=vmtoolsd.service
    +
    +
    `,6),i=[l];function o(r,p){return s(),a("div",null,i)}const m=e(t,[["render",o],["__file","Vsphere_template_issue.html.vue"]]),v=JSON.parse('{"path":"/02-PrivatePlatform/Vsphere/Vsphere_template_issue.html","title":"VSphere 템플릿 생성 이슈","lang":"ko-KR","frontmatter":{"description":"VSphere 템플릿 생성 이슈","tag":["vsphere","template"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/02-PrivatePlatform/Vsphere/Vsphere_template_issue.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"VSphere 템플릿 생성 이슈"}],["meta",{"property":"og:description","content":"VSphere 템플릿 생성 이슈"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"vsphere"}],["meta",{"property":"article:tag","content":"template"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"VSphere 템플릿 생성 이슈\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":0.28,"words":83},"filePathRelative":"02-PrivatePlatform/Vsphere/Vsphere_template_issue.md","localizedDate":"2023년 9월 18일","excerpt":"\\n
      \\n
    1. redhat 계열
    2. \\n
    \\n\\n
      \\n
    1. debian 계열
    2. \\n
    \\n"}');export{m as comp,v as data}; diff --git a/assets/acl-sample.html-CK60trmj.js b/assets/acl-sample.html-CK60trmj.js new file mode 100644 index 0000000000..9281916653 --- /dev/null +++ b/assets/acl-sample.html-CK60trmj.js @@ -0,0 +1,32 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as l}from"./app-Bzk8Nrll.js";const a={},i=l(`

    Consul ACL Policy sample

    Consul ACL을 활성화 할 경우 default를 deny로 할 지 allow를 할 지 정할 수 있다.
    deny로 할 경우에는 하나하나 policy로 tokne을 만들어서 사용해야 한다.

    Consul이 Vault의 Storage로 되어야 할 경우

    key_prefix "vault/" {
    +  policy = "write"
    +}
    +service "vault" {
    +   policy = "write"
    +}
    +agent_prefix "" {
    +   policy = "read"
    +}
    +session_prefix "" {
    +   policy = "write"
    +}
    +

    Consul Dns query가 필요할 경우

    node_prefix "" {
    +  policy = "read"
    +}
    +service_prefix "" {
    +  policy = "read"
    +}
    +# only needed if using prepared queries
    +query_prefix "" {
    +  policy = "read"
    +}
    +

    Consul UI 접근권한이 필요할 경우

    service_prefix "" {
    +  policy = "read"
    +}
    +key_prefix "" {
    +  policy = "read"
    +}
    +node_prefix "" {
    +  policy = "read"
    +}
    +
    `,8),o=[i];function s(r,c){return t(),n("div",null,o)}const p=e(a,[["render",s],["__file","acl-sample.html.vue"]]),m=JSON.parse('{"path":"/04-HashiCorp/04-Consul/02-Configuration/acl-sample.html","title":"","lang":"ko-KR","frontmatter":{"description":"Consul Acl Policy Sample","tag":["Consul","Acl","Policy"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/04-HashiCorp/04-Consul/02-Configuration/acl-sample.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:description","content":"Consul Acl Policy Sample"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"Consul"}],["meta",{"property":"article:tag","content":"Acl"}],["meta",{"property":"article:tag","content":"Policy"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"Consul ACL Policy sample","slug":"consul-acl-policy-sample","link":"#consul-acl-policy-sample","children":[]},{"level":2,"title":"Consul이 Vault의 Storage로 되어야 할 경우","slug":"consul이-vault의-storage로-되어야-할-경우","link":"#consul이-vault의-storage로-되어야-할-경우","children":[]},{"level":2,"title":"Consul Dns query가 필요할 경우","slug":"consul-dns-query가-필요할-경우","link":"#consul-dns-query가-필요할-경우","children":[]},{"level":2,"title":"Consul UI 접근권한이 필요할 경우","slug":"consul-ui-접근권한이-필요할-경우","link":"#consul-ui-접근권한이-필요할-경우","children":[]}],"git":{"createdTime":1648777606000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1},{"name":"ung","email":"swbs90@naver.com","commits":1}]},"readingTime":{"minutes":0.23,"words":69},"filePathRelative":"04-HashiCorp/04-Consul/02-Configuration/acl-sample.md","localizedDate":"2022년 4월 1일","excerpt":"

    Consul ACL Policy sample

    \\n

    Consul ACL을 활성화 할 경우 default를 deny로 할 지 allow를 할 지 정할 수 있다.
    \\ndeny로 할 경우에는 하나하나 policy로 tokne을 만들어서 사용해야 한다.

    \\n

    Consul이 Vault의 Storage로 되어야 할 경우

    \\n
    key_prefix \\"vault/\\" {\\n  policy = \\"write\\"\\n}\\nservice \\"vault\\" {\\n   policy = \\"write\\"\\n}\\nagent_prefix \\"\\" {\\n   policy = \\"read\\"\\n}\\nsession_prefix \\"\\" {\\n   policy = \\"write\\"\\n}\\n
    "}');export{p as comp,m as data}; diff --git a/assets/acronyms.html-CIiKRHMZ.js b/assets/acronyms.html-CIiKRHMZ.js new file mode 100644 index 0000000000..1bc2991b48 --- /dev/null +++ b/assets/acronyms.html-CIiKRHMZ.js @@ -0,0 +1 @@ +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as t,c as r,b as e,d as l,a as o,e as s}from"./app-Bzk8Nrll.js";const c={},h=e("h1",{id:"약어",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#약어"},[e("span",null,"약어")])],-1),d=e("br",null,null,-1),p={href:"https://en.wikipedia.org/wiki/List_of_information_technology_initialisms",target:"_blank",rel:"noopener noreferrer"},u=s('

    A

    B

    C

    D

    E

    F

    G

    H

    I

    J

    K

    L

    M

    N

    O

    P

    Q

    R

    S

    T

    U

    V

    W

    X

    Y

    Z

    ',52);function m(f,g){const i=n("ExternalLinkIcon");return t(),r("div",null,[h,e("blockquote",null,[e("p",null,[l("Full name definition"),d,e("a",p,[l("List of informationtechnology initialisms"),o(i)])])]),u])}const v=a(c,[["render",m],["__file","acronyms.html.vue"]]),k=JSON.parse('{"path":"/06-etc/infomation/acronyms.html","title":"약어","lang":"ko-KR","frontmatter":{"description":"acronyms list","tag":["acronyms","tip"],"head":[["meta",{"property":"og:url","content":"https://docmoa.github.io/06-etc/infomation/acronyms.html"}],["meta",{"property":"og:site_name","content":"docmoa"}],["meta",{"property":"og:title","content":"약어"}],["meta",{"property":"og:description","content":"acronyms list"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"ko-KR"}],["meta",{"property":"og:updated_time","content":"2023-09-18T13:12:54.000Z"}],["meta",{"property":"article:tag","content":"acronyms"}],["meta",{"property":"article:tag","content":"tip"}],["meta",{"property":"article:modified_time","content":"2023-09-18T13:12:54.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"약어\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2023-09-18T13:12:54.000Z\\",\\"author\\":[]}"]]},"headers":[{"level":2,"title":"A","slug":"a","link":"#a","children":[]},{"level":2,"title":"B","slug":"b","link":"#b","children":[]},{"level":2,"title":"C","slug":"c","link":"#c","children":[]},{"level":2,"title":"D","slug":"d","link":"#d","children":[]},{"level":2,"title":"E","slug":"e","link":"#e","children":[]},{"level":2,"title":"F","slug":"f","link":"#f","children":[]},{"level":2,"title":"G","slug":"g","link":"#g","children":[]},{"level":2,"title":"H","slug":"h","link":"#h","children":[]},{"level":2,"title":"I","slug":"i","link":"#i","children":[]},{"level":2,"title":"J","slug":"j","link":"#j","children":[]},{"level":2,"title":"K","slug":"k","link":"#k","children":[]},{"level":2,"title":"L","slug":"l","link":"#l","children":[]},{"level":2,"title":"M","slug":"m","link":"#m","children":[]},{"level":2,"title":"N","slug":"n","link":"#n","children":[]},{"level":2,"title":"O","slug":"o","link":"#o","children":[]},{"level":2,"title":"P","slug":"p","link":"#p","children":[]},{"level":2,"title":"Q","slug":"q","link":"#q","children":[]},{"level":2,"title":"R","slug":"r","link":"#r","children":[]},{"level":2,"title":"S","slug":"s","link":"#s","children":[]},{"level":2,"title":"T","slug":"t","link":"#t","children":[]},{"level":2,"title":"U","slug":"u","link":"#u","children":[]},{"level":2,"title":"V","slug":"v","link":"#v","children":[]},{"level":2,"title":"W","slug":"w","link":"#w","children":[]},{"level":2,"title":"X","slug":"x","link":"#x","children":[]},{"level":2,"title":"Y","slug":"y","link":"#y","children":[]},{"level":2,"title":"Z","slug":"z","link":"#z","children":[]}],"git":{"createdTime":1695042774000,"updatedTime":1695042774000,"contributors":[{"name":"Great-Stone","email":"hahohh@gmail.com","commits":1}]},"readingTime":{"minutes":1.82,"words":546},"filePathRelative":"06-etc/infomation/acronyms.md","localizedDate":"2023년 9월 18일","excerpt":"\\n
    \\n

    Full name definition
    \\nList of informationtechnology initialisms

    \\n
    \\n

    A

    \\n"}');export{v as comp,k as data}; diff --git a/assets/app-Bzk8Nrll.js b/assets/app-Bzk8Nrll.js new file mode 100644 index 0000000000..4a2ad9ae1d --- /dev/null +++ b/assets/app-Bzk8Nrll.js @@ -0,0 +1,5045 @@ +/** +* @vue/shared v3.4.19 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Ml(e,n){const t=new Set(e.split(","));return n?a=>t.has(a.toLowerCase()):a=>t.has(a)}const Ee={},St=[],un=()=>{},qu=()=>!1,Ea=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),$l=e=>e.startsWith("onUpdate:"),Re=Object.assign,Bl=(e,n)=>{const t=e.indexOf(n);t>-1&&e.splice(t,1)},Ju=Object.prototype.hasOwnProperty,ue=(e,n)=>Ju.call(e,n),Z=Array.isArray,Lt=e=>vs(e)==="[object Map]",Ci=e=>vs(e)==="[object Set]",ae=e=>typeof e=="function",He=e=>typeof e=="string",zt=e=>typeof e=="symbol",xe=e=>e!==null&&typeof e=="object",Ei=e=>(xe(e)||ae(e))&&ae(e.then)&&ae(e.catch),xi=Object.prototype.toString,vs=e=>xi.call(e),Wu=e=>vs(e).slice(8,-1),Ti=e=>vs(e)==="[object Object]",Ul=e=>He(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Pt=Ml(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),_s=e=>{const n=Object.create(null);return t=>n[t]||(n[t]=e(t))},Gu=/-(\w)/g,We=_s(e=>e.replace(Gu,(n,t)=>t?t.toUpperCase():"")),Yu=/\B([A-Z])/g,et=_s(e=>e.replace(Yu,"-$1").toLowerCase()),Kt=_s(e=>e.charAt(0).toUpperCase()+e.slice(1)),$s=_s(e=>e?`on${Kt(e)}`:""),Ln=(e,n)=>!Object.is(e,n),as=(e,n)=>{for(let t=0;t{Object.defineProperty(e,n,{configurable:!0,enumerable:!1,value:t})},ml=e=>{const n=parseFloat(e);return isNaN(n)?e:n},Xu=e=>{const n=He(e)?Number(e):NaN;return isNaN(n)?e:n};let qo;const Si=()=>qo||(qo=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function zl(e){if(Z(e)){const n={};for(let t=0;t{if(t){const a=t.split(Zu);a.length>1&&(n[a[0].trim()]=a[1].trim())}}),n}function Kl(e){let n="";if(He(e))n=e;else if(Z(e))for(let t=0;tHe(e)?e:e==null?"":Z(e)||xe(e)&&(e.toString===xi||!ae(e.toString))?JSON.stringify(e,Pi,2):String(e),Pi=(e,n)=>n&&n.__v_isRef?Pi(e,n.value):Lt(n)?{[`Map(${n.size})`]:[...n.entries()].reduce((t,[a,s],l)=>(t[Bs(a,l)+" =>"]=s,t),{})}:Ci(n)?{[`Set(${n.size})`]:[...n.values()].map(t=>Bs(t))}:zt(n)?Bs(n):xe(n)&&!Z(n)&&!Ti(n)?String(n):n,Bs=(e,n="")=>{var t;return zt(e)?`Symbol(${(t=e.description)!=null?t:n})`:e};/** +* @vue/reactivity v3.4.19 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let tn;class sd{constructor(n=!1){this.detached=n,this._active=!0,this.effects=[],this.cleanups=[],this.parent=tn,!n&&tn&&(this.index=(tn.scopes||(tn.scopes=[])).push(this)-1)}get active(){return this._active}run(n){if(this._active){const t=tn;try{return tn=this,n()}finally{tn=t}}}on(){tn=this}off(){tn=this.parent}stop(n){if(this._active){let t,a;for(t=0,a=this.effects.length;t=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),ft()}return this._dirtyLevel>=4}set dirty(n){this._dirtyLevel=n?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let n=Xn,t=dt;try{return Xn=!0,dt=this,this._runnings++,Jo(this),this.fn()}finally{Wo(this),this._runnings--,dt=t,Xn=n}}stop(){var n;this.active&&(Jo(this),Wo(this),(n=this.onStop)==null||n.call(this),this.active=!1)}}function rd(e){return e.value}function Jo(e){e._trackId++,e._depsLength=0}function Wo(e){if(e.deps.length>e._depsLength){for(let n=e._depsLength;n{const t=new Map;return t.cleanup=e,t.computed=n,t},os=new WeakMap,ht=Symbol(""),fl=Symbol("");function Ze(e,n,t){if(Xn&&dt){let a=os.get(e);a||os.set(e,a=new Map);let s=a.get(t);s||a.set(t,s=Ri(()=>a.delete(t))),Oi(dt,s)}}function Hn(e,n,t,a,s,l){const o=os.get(e);if(!o)return;let r=[];if(n==="clear")r=[...o.values()];else if(t==="length"&&Z(e)){const c=Number(a);o.forEach((p,d)=>{(d==="length"||!zt(d)&&d>=c)&&r.push(p)})}else switch(t!==void 0&&r.push(o.get(t)),n){case"add":Z(e)?Ul(t)&&r.push(o.get("length")):(r.push(o.get(ht)),Lt(e)&&r.push(o.get(fl)));break;case"delete":Z(e)||(r.push(o.get(ht)),Lt(e)&&r.push(o.get(fl)));break;case"set":Lt(e)&&r.push(o.get(ht));break}Jl();for(const c of r)c&&Di(c,4);Wl()}function id(e,n){var t;return(t=os.get(e))==null?void 0:t.get(n)}const pd=Ml("__proto__,__v_isRef,__isVue"),Hi=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(zt)),Go=cd();function cd(){const e={};return["includes","indexOf","lastIndexOf"].forEach(n=>{e[n]=function(...t){const a=ie(this);for(let l=0,o=this.length;l{e[n]=function(...t){kt(),Jl();const a=ie(this)[n].apply(this,t);return Wl(),ft(),a}}),e}function ud(e){const n=ie(this);return Ze(n,"has",e),n.hasOwnProperty(e)}class Fi{constructor(n=!1,t=!1){this._isReadonly=n,this._shallow=t}get(n,t,a){const s=this._isReadonly,l=this._shallow;if(t==="__v_isReactive")return!s;if(t==="__v_isReadonly")return s;if(t==="__v_isShallow")return l;if(t==="__v_raw")return a===(s?l?Ed:$i:l?Mi:ji).get(n)||Object.getPrototypeOf(n)===Object.getPrototypeOf(a)?n:void 0;const o=Z(n);if(!s){if(o&&ue(Go,t))return Reflect.get(Go,t,a);if(t==="hasOwnProperty")return ud}const r=Reflect.get(n,t,a);return(zt(t)?Hi.has(t):pd(t))||(s||Ze(n,"get",t),l)?r:$e(r)?o&&Ul(t)?r:r.value:xe(r)?s?nt(r):xa(r):r}}class Ni extends Fi{constructor(n=!1){super(!1,n)}set(n,t,a,s){let l=n[t];if(!this._shallow){const c=Ft(l);if(!rs(a)&&!Ft(a)&&(l=ie(l),a=ie(a)),!Z(n)&&$e(l)&&!$e(a))return c?!1:(l.value=a,!0)}const o=Z(n)&&Ul(t)?Number(t)e,bs=e=>Reflect.getPrototypeOf(e);function Ma(e,n,t=!1,a=!1){e=e.__v_raw;const s=ie(e),l=ie(n);t||(Ln(n,l)&&Ze(s,"get",n),Ze(s,"get",l));const{has:o}=bs(s),r=a?Gl:t?Ql:da;if(o.call(s,n))return r(e.get(n));if(o.call(s,l))return r(e.get(l));e!==s&&e.get(n)}function $a(e,n=!1){const t=this.__v_raw,a=ie(t),s=ie(e);return n||(Ln(e,s)&&Ze(a,"has",e),Ze(a,"has",s)),e===s?t.has(e):t.has(e)||t.has(s)}function Ba(e,n=!1){return e=e.__v_raw,!n&&Ze(ie(e),"iterate",ht),Reflect.get(e,"size",e)}function Yo(e){e=ie(e);const n=ie(this);return bs(n).has.call(n,e)||(n.add(e),Hn(n,"add",e,e)),this}function Xo(e,n){n=ie(n);const t=ie(this),{has:a,get:s}=bs(t);let l=a.call(t,e);l||(e=ie(e),l=a.call(t,e));const o=s.call(t,e);return t.set(e,n),l?Ln(n,o)&&Hn(t,"set",e,n):Hn(t,"add",e,n),this}function Qo(e){const n=ie(this),{has:t,get:a}=bs(n);let s=t.call(n,e);s||(e=ie(e),s=t.call(n,e)),a&&a.call(n,e);const l=n.delete(e);return s&&Hn(n,"delete",e,void 0),l}function Zo(){const e=ie(this),n=e.size!==0,t=e.clear();return n&&Hn(e,"clear",void 0,void 0),t}function Ua(e,n){return function(a,s){const l=this,o=l.__v_raw,r=ie(o),c=n?Gl:e?Ql:da;return!e&&Ze(r,"iterate",ht),o.forEach((p,d)=>a.call(s,c(p),c(d),l))}}function za(e,n,t){return function(...a){const s=this.__v_raw,l=ie(s),o=Lt(l),r=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,p=s[e](...a),d=t?Gl:n?Ql:da;return!n&&Ze(l,"iterate",c?fl:ht),{next(){const{value:h,done:m}=p.next();return m?{value:h,done:m}:{value:r?[d(h[0]),d(h[1])]:d(h),done:m}},[Symbol.iterator](){return this}}}}function Mn(e){return function(...n){return e==="delete"?!1:e==="clear"?void 0:this}}function kd(){const e={get(l){return Ma(this,l)},get size(){return Ba(this)},has:$a,add:Yo,set:Xo,delete:Qo,clear:Zo,forEach:Ua(!1,!1)},n={get(l){return Ma(this,l,!1,!0)},get size(){return Ba(this)},has:$a,add:Yo,set:Xo,delete:Qo,clear:Zo,forEach:Ua(!1,!0)},t={get(l){return Ma(this,l,!0)},get size(){return Ba(this,!0)},has(l){return $a.call(this,l,!0)},add:Mn("add"),set:Mn("set"),delete:Mn("delete"),clear:Mn("clear"),forEach:Ua(!0,!1)},a={get(l){return Ma(this,l,!0,!0)},get size(){return Ba(this,!0)},has(l){return $a.call(this,l,!0)},add:Mn("add"),set:Mn("set"),delete:Mn("delete"),clear:Mn("clear"),forEach:Ua(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(l=>{e[l]=za(l,!1,!1),t[l]=za(l,!0,!1),n[l]=za(l,!1,!0),a[l]=za(l,!0,!0)}),[e,t,n,a]}const[fd,vd,_d,bd]=kd();function Yl(e,n){const t=n?e?bd:_d:e?vd:fd;return(a,s,l)=>s==="__v_isReactive"?!e:s==="__v_isReadonly"?e:s==="__v_raw"?a:Reflect.get(ue(t,s)&&s in a?t:a,s,l)}const yd={get:Yl(!1,!1)},wd={get:Yl(!1,!0)},Cd={get:Yl(!0,!1)},ji=new WeakMap,Mi=new WeakMap,$i=new WeakMap,Ed=new WeakMap;function xd(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Td(e){return e.__v_skip||!Object.isExtensible(e)?0:xd(Wu(e))}function xa(e){return Ft(e)?e:Xl(e,!1,hd,yd,ji)}function Bi(e){return Xl(e,!1,gd,wd,Mi)}function nt(e){return Xl(e,!0,md,Cd,$i)}function Xl(e,n,t,a,s){if(!xe(e)||e.__v_raw&&!(n&&e.__v_isReactive))return e;const l=s.get(e);if(l)return l;const o=Td(e);if(o===0)return e;const r=new Proxy(e,o===2?a:t);return s.set(e,r),r}function It(e){return Ft(e)?It(e.__v_raw):!!(e&&e.__v_isReactive)}function Ft(e){return!!(e&&e.__v_isReadonly)}function rs(e){return!!(e&&e.__v_isShallow)}function Ui(e){return It(e)||Ft(e)}function ie(e){const n=e&&e.__v_raw;return n?ie(n):e}function zi(e){return Object.isExtensible(e)&&ls(e,"__v_skip",!0),e}const da=e=>xe(e)?xa(e):e,Ql=e=>xe(e)?nt(e):e;class Ki{constructor(n,t,a,s){this._setter=t,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new ql(()=>n(this._value),()=>sa(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!s,this.__v_isReadonly=a}get value(){const n=ie(this);return(!n._cacheable||n.effect.dirty)&&Ln(n._value,n._value=n.effect.run())&&sa(n,4),Zl(n),n.effect._dirtyLevel>=2&&sa(n,2),n._value}set value(n){this._setter(n)}get _dirty(){return this.effect.dirty}set _dirty(n){this.effect.dirty=n}}function Sd(e,n,t=!1){let a,s;const l=ae(e);return l?(a=e,s=un):(a=e.get,s=e.set),new Ki(a,s,l||!s,t)}function Zl(e){var n;Xn&&dt&&(e=ie(e),Oi(dt,(n=e.dep)!=null?n:e.dep=Ri(()=>e.dep=void 0,e instanceof Ki?e:void 0)))}function sa(e,n=4,t){e=ie(e);const a=e.dep;a&&Di(a,n)}function $e(e){return!!(e&&e.__v_isRef===!0)}function q(e){return qi(e,!1)}function ge(e){return qi(e,!0)}function qi(e,n){return $e(e)?e:new Ld(e,n)}class Ld{constructor(n,t){this.__v_isShallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?n:ie(n),this._value=t?n:da(n)}get value(){return Zl(this),this._value}set value(n){const t=this.__v_isShallow||rs(n)||Ft(n);n=t?n:ie(n),Ln(n,this._rawValue)&&(this._rawValue=n,this._value=t?n:da(n),sa(this,4))}}function fn(e){return $e(e)?e.value:e}const Pd={get:(e,n,t)=>fn(Reflect.get(e,n,t)),set:(e,n,t,a)=>{const s=e[n];return $e(s)&&!$e(t)?(s.value=t,!0):Reflect.set(e,n,t,a)}};function Ji(e){return It(e)?e:new Proxy(e,Pd)}class Id{constructor(n){this.dep=void 0,this.__v_isRef=!0;const{get:t,set:a}=n(()=>Zl(this),()=>sa(this));this._get=t,this._set=a}get value(){return this._get()}set value(n){this._set(n)}}function eo(e){return new Id(e)}function Ad(e){const n=Z(e)?new Array(e.length):{};for(const t in e)n[t]=Wi(e,t);return n}class Vd{constructor(n,t,a){this._object=n,this._key=t,this._defaultValue=a,this.__v_isRef=!0}get value(){const n=this._object[this._key];return n===void 0?this._defaultValue:n}set value(n){this._object[this._key]=n}get dep(){return id(ie(this._object),this._key)}}class Od{constructor(n){this._getter=n,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function qt(e,n,t){return $e(e)?e:ae(e)?new Od(e):xe(e)&&arguments.length>1?Wi(e,n,t):q(e)}function Wi(e,n,t){const a=e[n];return $e(a)?a:new Vd(e,n,t)}/** +* @vue/runtime-core v3.4.19 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Qn(e,n,t,a){try{return a?e(...a):e()}catch(s){Ta(s,n,t)}}function dn(e,n,t,a){if(ae(e)){const l=Qn(e,n,t,a);return l&&Ei(l)&&l.catch(o=>{Ta(o,n,t)}),l}const s=[];for(let l=0;l>>1,s=Be[a],l=ma(s);lTn&&Be.splice(n,1)}function Fd(e){Z(e)?At.push(...e):(!qn||!qn.includes(e,e.allowRecurse?it+1:it))&&At.push(e),Yi()}function er(e,n,t=ha?Tn+1:0){for(;tma(t)-ma(a));if(At.length=0,qn){qn.push(...n);return}for(qn=n,it=0;ite.id==null?1/0:e.id,Nd=(e,n)=>{const t=ma(e)-ma(n);if(t===0){if(e.pre&&!n.pre)return-1;if(n.pre&&!e.pre)return 1}return t};function Xi(e){vl=!1,ha=!0,Be.sort(Nd);try{for(Tn=0;TnHe(g)?g.trim():g)),h&&(s=t.map(ml))}let r,c=a[r=$s(n)]||a[r=$s(We(n))];!c&&l&&(c=a[r=$s(et(n))]),c&&dn(c,e,6,s);const p=a[r+"Once"];if(p){if(!e.emitted)e.emitted={};else if(e.emitted[r])return;e.emitted[r]=!0,dn(p,e,6,s)}}function Qi(e,n,t=!1){const a=n.emitsCache,s=a.get(e);if(s!==void 0)return s;const l=e.emits;let o={},r=!1;if(!ae(e)){const c=p=>{const d=Qi(p,n,!0);d&&(r=!0,Re(o,d))};!t&&n.mixins.length&&n.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!l&&!r?(xe(e)&&a.set(e,null),null):(Z(l)?l.forEach(c=>o[c]=null):Re(o,l),xe(e)&&a.set(e,o),o)}function ws(e,n){return!e||!Ea(n)?!1:(n=n.slice(2).replace(/Once$/,""),ue(e,n[0].toLowerCase()+n.slice(1))||ue(e,et(n))||ue(e,n))}let Fe=null,Cs=null;function ps(e){const n=Fe;return Fe=e,Cs=e&&e.type.__scopeId||null,n}function h_(e){Cs=e}function m_(){Cs=null}function Md(e,n=Fe,t){if(!n||e._n)return e;const a=(...s)=>{a._d&&dr(-1);const l=ps(n);let o;try{o=e(...s)}finally{ps(l),a._d&&dr(1)}return o};return a._n=!0,a._c=!0,a._d=!0,a}function Us(e){const{type:n,vnode:t,proxy:a,withProxy:s,props:l,propsOptions:[o],slots:r,attrs:c,emit:p,render:d,renderCache:h,data:m,setupState:g,ctx:v,inheritAttrs:w}=e;let C,_;const T=ps(e);try{if(t.shapeFlag&4){const S=s||a,R=S;C=kn(d.call(R,S,h,l,g,m,v)),_=c}else{const S=n;C=kn(S.length>1?S(l,{attrs:c,slots:r,emit:p}):S(l,null)),_=n.props?c:$d(c)}}catch(S){ia.length=0,Ta(S,e,1),C=Ae(sn)}let y=C;if(_&&w!==!1){const S=Object.keys(_),{shapeFlag:R}=y;S.length&&R&7&&(o&&S.some($l)&&(_=Bd(_,o)),y=Zn(y,_))}return t.dirs&&(y=Zn(y),y.dirs=y.dirs?y.dirs.concat(t.dirs):t.dirs),t.transition&&(y.transition=t.transition),C=y,ps(T),C}const $d=e=>{let n;for(const t in e)(t==="class"||t==="style"||Ea(t))&&((n||(n={}))[t]=e[t]);return n},Bd=(e,n)=>{const t={};for(const a in e)(!$l(a)||!(a.slice(9)in n))&&(t[a]=e[a]);return t};function Ud(e,n,t){const{props:a,children:s,component:l}=e,{props:o,children:r,patchFlag:c}=n,p=l.emitsOptions;if(n.dirs||n.transition)return!0;if(t&&c>=0){if(c&1024)return!0;if(c&16)return a?nr(a,o,p):!!o;if(c&8){const d=n.dynamicProps;for(let h=0;he.__isSuspense;function ep(e,n){n&&n.pendingBranch?Z(e)?n.effects.push(...e):n.effects.push(e):Fd(e)}const Wd=Symbol.for("v-scx"),Gd=()=>be(Wd);function to(e,n){return Es(e,null,n)}function Yd(e,n){return Es(e,null,{flush:"sync"})}const Ka={};function oe(e,n,t){return Es(e,n,t)}function Es(e,n,{immediate:t,deep:a,flush:s,once:l,onTrack:o,onTrigger:r}=Ee){if(n&&l){const x=n;n=(...K)=>{x(...K),R()}}const c=Me,p=x=>a===!0?x:ct(x,a===!1?1:void 0);let d,h=!1,m=!1;if($e(e)?(d=()=>e.value,h=rs(e)):It(e)?(d=()=>p(e),h=!0):Z(e)?(m=!0,h=e.some(x=>It(x)||rs(x)),d=()=>e.map(x=>{if($e(x))return x.value;if(It(x))return p(x);if(ae(x))return Qn(x,c,2)})):ae(e)?n?d=()=>Qn(e,c,2):d=()=>(g&&g(),dn(e,c,3,[v])):d=un,n&&a){const x=d;d=()=>ct(x())}let g,v=x=>{g=y.onStop=()=>{Qn(x,c,4),g=y.onStop=void 0}},w;if(Pa)if(v=un,n?t&&dn(n,c,3,[d(),m?[]:void 0,v]):d(),s==="sync"){const x=Gd();w=x.__watcherHandles||(x.__watcherHandles=[])}else return un;let C=m?new Array(e.length).fill(Ka):Ka;const _=()=>{if(!(!y.active||!y.dirty))if(n){const x=y.run();(a||h||(m?x.some((K,F)=>Ln(K,C[F])):Ln(x,C)))&&(g&&g(),dn(n,c,3,[x,C===Ka?void 0:m&&C[0]===Ka?[]:C,v]),C=x)}else y.run()};_.allowRecurse=!!n;let T;s==="sync"?T=_:s==="post"?T=()=>Ye(_,c&&c.suspense):(_.pre=!0,c&&(_.id=c.uid),T=()=>ys(_));const y=new ql(d,un,T),S=Ii(),R=()=>{y.stop(),S&&Bl(S.effects,y)};return n?t?_():C=y.run():s==="post"?Ye(y.run.bind(y),c&&c.suspense):y.run(),w&&w.push(R),R}function Xd(e,n,t){const a=this.proxy,s=He(e)?e.includes(".")?np(a,e):()=>a[e]:e.bind(a,a);let l;ae(n)?l=n:(l=n.handler,t=n);const o=La(this),r=Es(s,l.bind(a),t);return o(),r}function np(e,n){const t=n.split(".");return()=>{let a=e;for(let s=0;s0){if(t>=n)return e;t++}if(a=a||new Set,a.has(e))return e;if(a.add(e),$e(e))ct(e.value,n,t,a);else if(Z(e))for(let s=0;s{ct(s,n,t,a)});else if(Ti(e))for(const s in e)ct(e[s],n,t,a);return e}function g_(e,n){if(Fe===null)return e;const t=Ss(Fe)||Fe.proxy,a=e.dirs||(e.dirs=[]);for(let s=0;s{e.isMounted=!0}),so(()=>{e.isUnmounting=!0}),e}const rn=[Function,Array],ap={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:rn,onEnter:rn,onAfterEnter:rn,onEnterCancelled:rn,onBeforeLeave:rn,onLeave:rn,onAfterLeave:rn,onLeaveCancelled:rn,onBeforeAppear:rn,onAppear:rn,onAfterAppear:rn,onAppearCancelled:rn},Qd={name:"BaseTransition",props:ap,setup(e,{slots:n}){const t=_t(),a=tp();let s;return()=>{const l=n.default&&ao(n.default(),!0);if(!l||!l.length)return;let o=l[0];if(l.length>1){for(const w of l)if(w.type!==sn){o=w;break}}const r=ie(e),{mode:c}=r;if(a.isLeaving)return zs(o);const p=ar(o);if(!p)return zs(o);const d=ga(p,r,a,t);ka(p,d);const h=t.subTree,m=h&&ar(h);let g=!1;const{getTransitionKey:v}=p.type;if(v){const w=v();s===void 0?s=w:w!==s&&(s=w,g=!0)}if(m&&m.type!==sn&&(!pt(p,m)||g)){const w=ga(m,r,a,t);if(ka(m,w),c==="out-in")return a.isLeaving=!0,w.afterLeave=()=>{a.isLeaving=!1,t.update.active!==!1&&(t.effect.dirty=!0,t.update())},zs(o);c==="in-out"&&p.type!==sn&&(w.delayLeave=(C,_,T)=>{const y=sp(a,m);y[String(m.key)]=m,C[Jn]=()=>{_(),C[Jn]=void 0,delete d.delayedLeave},d.delayedLeave=T})}return o}}},Zd=Qd;function sp(e,n){const{leavingVNodes:t}=e;let a=t.get(n.type);return a||(a=Object.create(null),t.set(n.type,a)),a}function ga(e,n,t,a){const{appear:s,mode:l,persisted:o=!1,onBeforeEnter:r,onEnter:c,onAfterEnter:p,onEnterCancelled:d,onBeforeLeave:h,onLeave:m,onAfterLeave:g,onLeaveCancelled:v,onBeforeAppear:w,onAppear:C,onAfterAppear:_,onAppearCancelled:T}=n,y=String(e.key),S=sp(t,e),R=(F,H)=>{F&&dn(F,a,9,H)},x=(F,H)=>{const j=H[1];R(F,H),Z(F)?F.every(Y=>Y.length<=1)&&j():F.length<=1&&j()},K={mode:l,persisted:o,beforeEnter(F){let H=r;if(!t.isMounted)if(s)H=w||r;else return;F[Jn]&&F[Jn](!0);const j=S[y];j&&pt(e,j)&&j.el[Jn]&&j.el[Jn](),R(H,[F])},enter(F){let H=c,j=p,Y=d;if(!t.isMounted)if(s)H=C||c,j=_||p,Y=T||d;else return;let M=!1;const ee=F[qa]=Ie=>{M||(M=!0,Ie?R(Y,[F]):R(j,[F]),K.delayedLeave&&K.delayedLeave(),F[qa]=void 0)};H?x(H,[F,ee]):ee()},leave(F,H){const j=String(e.key);if(F[qa]&&F[qa](!0),t.isUnmounting)return H();R(h,[F]);let Y=!1;const M=F[Jn]=ee=>{Y||(Y=!0,H(),ee?R(v,[F]):R(g,[F]),F[Jn]=void 0,S[j]===e&&delete S[j])};S[j]=e,m?x(m,[F,M]):M()},clone(F){return ga(F,n,t,a)}};return K}function zs(e){if(Sa(e))return e=Zn(e),e.children=null,e}function ar(e){return Sa(e)?e.children?e.children[0]:void 0:e}function ka(e,n){e.shapeFlag&6&&e.component?ka(e.component.subTree,n):e.shapeFlag&128?(e.ssContent.transition=n.clone(e.ssContent),e.ssFallback.transition=n.clone(e.ssFallback)):e.transition=n}function ao(e,n=!1,t){let a=[],s=0;for(let l=0;l1)for(let l=0;l!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function eh(e){ae(e)&&(e={loader:e});const{loader:n,loadingComponent:t,errorComponent:a,delay:s=200,timeout:l,suspensible:o=!0,onError:r}=e;let c=null,p,d=0;const h=()=>(d++,c=null,m()),m=()=>{let g;return c||(g=c=n().catch(v=>{if(v=v instanceof Error?v:new Error(String(v)),r)return new Promise((w,C)=>{r(v,()=>w(h()),()=>C(v),d+1)});throw v}).then(v=>g!==c&&c?c:(v&&(v.__esModule||v[Symbol.toStringTag]==="Module")&&(v=v.default),p=v,v)))};return V({name:"AsyncComponentWrapper",__asyncLoader:m,get __asyncResolved(){return p},setup(){const g=Me;if(p)return()=>Ks(p,g);const v=T=>{c=null,Ta(T,g,13,!a)};if(o&&g.suspense||Pa)return m().then(T=>()=>Ks(T,g)).catch(T=>(v(T),()=>a?Ae(a,{error:T}):null));const w=q(!1),C=q(),_=q(!!s);return s&&setTimeout(()=>{_.value=!1},s),l!=null&&setTimeout(()=>{if(!w.value&&!C.value){const T=new Error(`Async component timed out after ${l}ms.`);v(T),C.value=T}},l),m().then(()=>{w.value=!0,g.parent&&Sa(g.parent.vnode)&&(g.parent.effect.dirty=!0,ys(g.parent.update))}).catch(T=>{v(T),C.value=T}),()=>{if(w.value&&p)return Ks(p,g);if(C.value&&a)return Ae(a,{error:C.value});if(t&&!_.value)return Ae(t)}}})}function Ks(e,n){const{ref:t,props:a,children:s,ce:l}=n.vnode,o=Ae(e,a,s);return o.ref=t,o.ce=l,delete n.vnode.ce,o}const Sa=e=>e.type.__isKeepAlive;function nh(e,n){lp(e,"a",n)}function th(e,n){lp(e,"da",n)}function lp(e,n,t=Me){const a=e.__wdc||(e.__wdc=()=>{let s=t;for(;s;){if(s.isDeactivated)return;s=s.parent}return e()});if(xs(n,a,t),t){let s=t.parent;for(;s&&s.parent;)Sa(s.parent.vnode)&&ah(a,n,t,s),s=s.parent}}function ah(e,n,t,a){const s=xs(n,e,a,!0);Jt(()=>{Bl(a[n],s)},t)}function xs(e,n,t=Me,a=!1){if(t){const s=t[e]||(t[e]=[]),l=n.__weh||(n.__weh=(...o)=>{if(t.isUnmounted)return;kt();const r=La(t),c=dn(n,t,e,o);return r(),ft(),c});return a?s.unshift(l):s.push(l),l}}const jn=e=>(n,t=Me)=>(!Pa||e==="sp")&&xs(e,(...a)=>n(...a),t),sh=jn("bm"),se=jn("m"),lh=jn("bu"),op=jn("u"),so=jn("bum"),Jt=jn("um"),oh=jn("sp"),rh=jn("rtg"),ih=jn("rtc");function ph(e,n=Me){xs("ec",e,n)}function k_(e,n,t,a){let s;const l=t&&t[a];if(Z(e)||He(e)){s=new Array(e.length);for(let o=0,r=e.length;on(o,r,void 0,l&&l[r]));else{const o=Object.keys(e);s=new Array(o.length);for(let r=0,c=o.length;rds(n)?!(n.type===sn||n.type===qe&&!rp(n.children)):!0)?e:null}const _l=e=>e?Ep(e)?Ss(e)||e.proxy:_l(e.parent):null,la=Re(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>_l(e.parent),$root:e=>_l(e.root),$emit:e=>e.emit,$options:e=>lo(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,ys(e.update)}),$nextTick:e=>e.n||(e.n=vt.bind(e.proxy)),$watch:e=>Xd.bind(e)}),qs=(e,n)=>e!==Ee&&!e.__isScriptSetup&&ue(e,n),ch={get({_:e},n){const{ctx:t,setupState:a,data:s,props:l,accessCache:o,type:r,appContext:c}=e;let p;if(n[0]!=="$"){const g=o[n];if(g!==void 0)switch(g){case 1:return a[n];case 2:return s[n];case 4:return t[n];case 3:return l[n]}else{if(qs(a,n))return o[n]=1,a[n];if(s!==Ee&&ue(s,n))return o[n]=2,s[n];if((p=e.propsOptions[0])&&ue(p,n))return o[n]=3,l[n];if(t!==Ee&&ue(t,n))return o[n]=4,t[n];bl&&(o[n]=0)}}const d=la[n];let h,m;if(d)return n==="$attrs"&&Ze(e,"get",n),d(e);if((h=r.__cssModules)&&(h=h[n]))return h;if(t!==Ee&&ue(t,n))return o[n]=4,t[n];if(m=c.config.globalProperties,ue(m,n))return m[n]},set({_:e},n,t){const{data:a,setupState:s,ctx:l}=e;return qs(s,n)?(s[n]=t,!0):a!==Ee&&ue(a,n)?(a[n]=t,!0):ue(e.props,n)||n[0]==="$"&&n.slice(1)in e?!1:(l[n]=t,!0)},has({_:{data:e,setupState:n,accessCache:t,ctx:a,appContext:s,propsOptions:l}},o){let r;return!!t[o]||e!==Ee&&ue(e,o)||qs(n,o)||(r=l[0])&&ue(r,o)||ue(a,o)||ue(la,o)||ue(s.config.globalProperties,o)},defineProperty(e,n,t){return t.get!=null?e._.accessCache[n]=0:ue(t,"value")&&this.set(e,n,t.value,null),Reflect.defineProperty(e,n,t)}};function sr(e){return Z(e)?e.reduce((n,t)=>(n[t]=null,n),{}):e}let bl=!0;function uh(e){const n=lo(e),t=e.proxy,a=e.ctx;bl=!1,n.beforeCreate&&lr(n.beforeCreate,e,"bc");const{data:s,computed:l,methods:o,watch:r,provide:c,inject:p,created:d,beforeMount:h,mounted:m,beforeUpdate:g,updated:v,activated:w,deactivated:C,beforeDestroy:_,beforeUnmount:T,destroyed:y,unmounted:S,render:R,renderTracked:x,renderTriggered:K,errorCaptured:F,serverPrefetch:H,expose:j,inheritAttrs:Y,components:M,directives:ee,filters:Ie}=n;if(p&&dh(p,a,null),o)for(const ne in o){const W=o[ne];ae(W)&&(a[ne]=W.bind(t))}if(s){const ne=s.call(t,t);xe(ne)&&(e.data=xa(ne))}if(bl=!0,l)for(const ne in l){const W=l[ne],Oe=ae(W)?W.bind(t,t):ae(W.get)?W.get.bind(t,t):un,wn=!ae(W)&&ae(W.set)?W.set.bind(t):un,on=b({get:Oe,set:wn});Object.defineProperty(a,ne,{enumerable:!0,configurable:!0,get:()=>on.value,set:Ne=>on.value=Ne})}if(r)for(const ne in r)ip(r[ne],a,t,ne);if(c){const ne=ae(c)?c.call(t):c;Reflect.ownKeys(ne).forEach(W=>{hn(W,ne[W])})}d&&lr(d,e,"c");function J(ne,W){Z(W)?W.forEach(Oe=>ne(Oe.bind(t))):W&&ne(W.bind(t))}if(J(sh,h),J(se,m),J(lh,g),J(op,v),J(nh,w),J(th,C),J(ph,F),J(ih,x),J(rh,K),J(so,T),J(Jt,S),J(oh,H),Z(j))if(j.length){const ne=e.exposed||(e.exposed={});j.forEach(W=>{Object.defineProperty(ne,W,{get:()=>t[W],set:Oe=>t[W]=Oe})})}else e.exposed||(e.exposed={});R&&e.render===un&&(e.render=R),Y!=null&&(e.inheritAttrs=Y),M&&(e.components=M),ee&&(e.directives=ee)}function dh(e,n,t=un){Z(e)&&(e=yl(e));for(const a in e){const s=e[a];let l;xe(s)?"default"in s?l=be(s.from||a,s.default,!0):l=be(s.from||a):l=be(s),$e(l)?Object.defineProperty(n,a,{enumerable:!0,configurable:!0,get:()=>l.value,set:o=>l.value=o}):n[a]=l}}function lr(e,n,t){dn(Z(e)?e.map(a=>a.bind(n.proxy)):e.bind(n.proxy),n,t)}function ip(e,n,t,a){const s=a.includes(".")?np(t,a):()=>t[a];if(He(e)){const l=n[e];ae(l)&&oe(s,l)}else if(ae(e))oe(s,e.bind(t));else if(xe(e))if(Z(e))e.forEach(l=>ip(l,n,t,a));else{const l=ae(e.handler)?e.handler.bind(t):n[e.handler];ae(l)&&oe(s,l,e)}}function lo(e){const n=e.type,{mixins:t,extends:a}=n,{mixins:s,optionsCache:l,config:{optionMergeStrategies:o}}=e.appContext,r=l.get(n);let c;return r?c=r:!s.length&&!t&&!a?c=n:(c={},s.length&&s.forEach(p=>cs(c,p,o,!0)),cs(c,n,o)),xe(n)&&l.set(n,c),c}function cs(e,n,t,a=!1){const{mixins:s,extends:l}=n;l&&cs(e,l,t,!0),s&&s.forEach(o=>cs(e,o,t,!0));for(const o in n)if(!(a&&o==="expose")){const r=hh[o]||t&&t[o];e[o]=r?r(e[o],n[o]):n[o]}return e}const hh={data:or,props:rr,emits:rr,methods:ta,computed:ta,beforeCreate:ze,created:ze,beforeMount:ze,mounted:ze,beforeUpdate:ze,updated:ze,beforeDestroy:ze,beforeUnmount:ze,destroyed:ze,unmounted:ze,activated:ze,deactivated:ze,errorCaptured:ze,serverPrefetch:ze,components:ta,directives:ta,watch:gh,provide:or,inject:mh};function or(e,n){return n?e?function(){return Re(ae(e)?e.call(this,this):e,ae(n)?n.call(this,this):n)}:n:e}function mh(e,n){return ta(yl(e),yl(n))}function yl(e){if(Z(e)){const n={};for(let t=0;t1)return t&&ae(n)?n.call(a&&a.proxy):n}}function vh(e,n,t,a=!1){const s={},l={};ls(l,Ts,1),e.propsDefaults=Object.create(null),cp(e,n,s,l);for(const o in e.propsOptions[0])o in s||(s[o]=void 0);t?e.props=a?s:Bi(s):e.type.props?e.props=s:e.props=l,e.attrs=l}function _h(e,n,t,a){const{props:s,attrs:l,vnode:{patchFlag:o}}=e,r=ie(s),[c]=e.propsOptions;let p=!1;if((a||o>0)&&!(o&16)){if(o&8){const d=e.vnode.dynamicProps;for(let h=0;h{c=!0;const[m,g]=up(h,n,!0);Re(o,m),g&&r.push(...g)};!t&&n.mixins.length&&n.mixins.forEach(d),e.extends&&d(e.extends),e.mixins&&e.mixins.forEach(d)}if(!l&&!c)return xe(e)&&a.set(e,St),St;if(Z(l))for(let d=0;d-1,g[1]=w<0||v-1||ue(g,"default"))&&r.push(h)}}}const p=[o,r];return xe(e)&&a.set(e,p),p}function ir(e){return e[0]!=="$"&&!Pt(e)}function pr(e){return e===null?"null":typeof e=="function"?e.name||"":typeof e=="object"&&e.constructor&&e.constructor.name||""}function cr(e,n){return pr(e)===pr(n)}function ur(e,n){return Z(n)?n.findIndex(t=>cr(t,e)):ae(n)&&cr(n,e)?0:-1}const dp=e=>e[0]==="_"||e==="$stable",oo=e=>Z(e)?e.map(kn):[kn(e)],bh=(e,n,t)=>{if(n._n)return n;const a=Md((...s)=>oo(n(...s)),t);return a._c=!1,a},hp=(e,n,t)=>{const a=e._ctx;for(const s in e){if(dp(s))continue;const l=e[s];if(ae(l))n[s]=bh(s,l,a);else if(l!=null){const o=oo(l);n[s]=()=>o}}},mp=(e,n)=>{const t=oo(n);e.slots.default=()=>t},yh=(e,n)=>{if(e.vnode.shapeFlag&32){const t=n._;t?(e.slots=ie(n),ls(n,"_",t)):hp(n,e.slots={})}else e.slots={},n&&mp(e,n);ls(e.slots,Ts,1)},wh=(e,n,t)=>{const{vnode:a,slots:s}=e;let l=!0,o=Ee;if(a.shapeFlag&32){const r=n._;r?t&&r===1?l=!1:(Re(s,n),!t&&r===1&&delete s._):(l=!n.$stable,hp(n,s)),o=n}else n&&(mp(e,n),o={default:1});if(l)for(const r in s)!dp(r)&&o[r]==null&&delete s[r]};function us(e,n,t,a,s=!1){if(Z(e)){e.forEach((m,g)=>us(m,n&&(Z(n)?n[g]:n),t,a,s));return}if(Vt(a)&&!s)return;const l=a.shapeFlag&4?Ss(a.component)||a.component.proxy:a.el,o=s?null:l,{i:r,r:c}=e,p=n&&n.r,d=r.refs===Ee?r.refs={}:r.refs,h=r.setupState;if(p!=null&&p!==c&&(He(p)?(d[p]=null,ue(h,p)&&(h[p]=null)):$e(p)&&(p.value=null)),ae(c))Qn(c,r,12,[o,d]);else{const m=He(c),g=$e(c);if(m||g){const v=()=>{if(e.f){const w=m?ue(h,c)?h[c]:d[c]:c.value;s?Z(w)&&Bl(w,l):Z(w)?w.includes(l)||w.push(l):m?(d[c]=[l],ue(h,c)&&(h[c]=d[c])):(c.value=[l],e.k&&(d[e.k]=c.value))}else m?(d[c]=o,ue(h,c)&&(h[c]=o)):g&&(c.value=o,e.k&&(d[e.k]=o))};o?(v.id=-1,Ye(v,t)):v()}}}let $n=!1;const Ch=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",Eh=e=>e.namespaceURI.includes("MathML"),Ja=e=>{if(Ch(e))return"svg";if(Eh(e))return"mathml"},Wa=e=>e.nodeType===8;function xh(e){const{mt:n,p:t,o:{patchProp:a,createText:s,nextSibling:l,parentNode:o,remove:r,insert:c,createComment:p}}=e,d=(y,S)=>{if(!S.hasChildNodes()){t(null,y,S),is(),S._vnode=y;return}$n=!1,h(S.firstChild,y,null,null,null),is(),S._vnode=y,$n&&console.error("Hydration completed but contains mismatches.")},h=(y,S,R,x,K,F=!1)=>{const H=Wa(y)&&y.data==="[",j=()=>w(y,S,R,x,K,H),{type:Y,ref:M,shapeFlag:ee,patchFlag:Ie}=S;let Se=y.nodeType;S.el=y,Ie===-2&&(F=!1,S.dynamicChildren=null);let J=null;switch(Y){case Nt:Se!==3?S.children===""?(c(S.el=s(""),o(y),y),J=y):J=j():(y.data!==S.children&&($n=!0,y.data=S.children),J=l(y));break;case sn:T(y)?(J=l(y),_(S.el=y.content.firstChild,y,R)):Se!==8||H?J=j():J=l(y);break;case ra:if(H&&(y=l(y),Se=y.nodeType),Se===1||Se===3){J=y;const ne=!S.children.length;for(let W=0;W{F=F||!!S.dynamicChildren;const{type:H,props:j,patchFlag:Y,shapeFlag:M,dirs:ee,transition:Ie}=S,Se=H==="input"||H==="option";if(Se||Y!==-1){ee&&xn(S,null,R,"created");let J=!1;if(T(y)){J=gp(x,Ie)&&R&&R.vnode.props&&R.vnode.props.appear;const W=y.content.firstChild;J&&Ie.beforeEnter(W),_(W,y,R),S.el=y=W}if(M&16&&!(j&&(j.innerHTML||j.textContent))){let W=g(y.firstChild,S,y,R,x,K,F);for(;W;){$n=!0;const Oe=W;W=W.nextSibling,r(Oe)}}else M&8&&y.textContent!==S.children&&($n=!0,y.textContent=S.children);if(j)if(Se||!F||Y&48)for(const W in j)(Se&&(W.endsWith("value")||W==="indeterminate")||Ea(W)&&!Pt(W)||W[0]===".")&&a(y,W,null,j[W],void 0,void 0,R);else j.onClick&&a(y,"onClick",null,j.onClick,void 0,void 0,R);let ne;(ne=j&&j.onVnodeBeforeMount)&&pn(ne,R,S),ee&&xn(S,null,R,"beforeMount"),((ne=j&&j.onVnodeMounted)||ee||J)&&ep(()=>{ne&&pn(ne,R,S),J&&Ie.enter(y),ee&&xn(S,null,R,"mounted")},x)}return y.nextSibling},g=(y,S,R,x,K,F,H)=>{H=H||!!S.dynamicChildren;const j=S.children,Y=j.length;for(let M=0;M{const{slotScopeIds:H}=S;H&&(K=K?K.concat(H):H);const j=o(y),Y=g(l(y),S,j,R,x,K,F);return Y&&Wa(Y)&&Y.data==="]"?l(S.anchor=Y):($n=!0,c(S.anchor=p("]"),j,Y),Y)},w=(y,S,R,x,K,F)=>{if($n=!0,S.el=null,F){const Y=C(y);for(;;){const M=l(y);if(M&&M!==Y)r(M);else break}}const H=l(y),j=o(y);return r(y),t(null,S,j,H,R,x,Ja(j),K),H},C=(y,S="[",R="]")=>{let x=0;for(;y;)if(y=l(y),y&&Wa(y)&&(y.data===S&&x++,y.data===R)){if(x===0)return l(y);x--}return y},_=(y,S,R)=>{const x=S.parentNode;x&&x.replaceChild(y,S);let K=R;for(;K;)K.vnode.el===S&&(K.vnode.el=K.subTree.el=y),K=K.parent},T=y=>y.nodeType===1&&y.tagName.toLowerCase()==="template";return[d,h]}const Ye=ep;function Th(e){return Sh(e,xh)}function Sh(e,n){const t=Si();t.__VUE__=!0;const{insert:a,remove:s,patchProp:l,createElement:o,createText:r,createComment:c,setText:p,setElementText:d,parentNode:h,nextSibling:m,setScopeId:g=un,insertStaticContent:v}=e,w=(k,f,E,I=null,P=null,D=null,B=void 0,O=null,N=!!f.dynamicChildren)=>{if(k===f)return;k&&!pt(k,f)&&(I=L(k),Ne(k,P,D,!0),k=null),f.patchFlag===-2&&(N=!1,f.dynamicChildren=null);const{type:A,ref:z,shapeFlag:Q}=f;switch(A){case Nt:C(k,f,E,I);break;case sn:_(k,f,E,I);break;case ra:k==null&&T(f,E,I,B);break;case qe:M(k,f,E,I,P,D,B,O,N);break;default:Q&1?R(k,f,E,I,P,D,B,O,N):Q&6?ee(k,f,E,I,P,D,B,O,N):(Q&64||Q&128)&&A.process(k,f,E,I,P,D,B,O,N,G)}z!=null&&P&&us(z,k&&k.ref,D,f||k,!f)},C=(k,f,E,I)=>{if(k==null)a(f.el=r(f.children),E,I);else{const P=f.el=k.el;f.children!==k.children&&p(P,f.children)}},_=(k,f,E,I)=>{k==null?a(f.el=c(f.children||""),E,I):f.el=k.el},T=(k,f,E,I)=>{[k.el,k.anchor]=v(k.children,f,E,I,k.el,k.anchor)},y=({el:k,anchor:f},E,I)=>{let P;for(;k&&k!==f;)P=m(k),a(k,E,I),k=P;a(f,E,I)},S=({el:k,anchor:f})=>{let E;for(;k&&k!==f;)E=m(k),s(k),k=E;s(f)},R=(k,f,E,I,P,D,B,O,N)=>{f.type==="svg"?B="svg":f.type==="math"&&(B="mathml"),k==null?x(f,E,I,P,D,B,O,N):H(k,f,P,D,B,O,N)},x=(k,f,E,I,P,D,B,O)=>{let N,A;const{props:z,shapeFlag:Q,transition:X,dirs:te}=k;if(N=k.el=o(k.type,D,z&&z.is,z),Q&8?d(N,k.children):Q&16&&F(k.children,N,null,I,P,Js(k,D),B,O),te&&xn(k,null,I,"created"),K(N,k,k.scopeId,B,I),z){for(const ve in z)ve!=="value"&&!Pt(ve)&&l(N,ve,null,z[ve],D,k.children,I,P,De);"value"in z&&l(N,"value",null,z.value,D),(A=z.onVnodeBeforeMount)&&pn(A,I,k)}te&&xn(k,null,I,"beforeMount");const le=gp(P,X);le&&X.beforeEnter(N),a(N,f,E),((A=z&&z.onVnodeMounted)||le||te)&&Ye(()=>{A&&pn(A,I,k),le&&X.enter(N),te&&xn(k,null,I,"mounted")},P)},K=(k,f,E,I,P)=>{if(E&&g(k,E),I)for(let D=0;D{for(let A=N;A{const O=f.el=k.el;let{patchFlag:N,dynamicChildren:A,dirs:z}=f;N|=k.patchFlag&16;const Q=k.props||Ee,X=f.props||Ee;let te;if(E&<(E,!1),(te=X.onVnodeBeforeUpdate)&&pn(te,E,f,k),z&&xn(f,k,E,"beforeUpdate"),E&<(E,!0),A?j(k.dynamicChildren,A,O,E,I,Js(f,P),D):B||W(k,f,O,null,E,I,Js(f,P),D,!1),N>0){if(N&16)Y(O,f,Q,X,E,I,P);else if(N&2&&Q.class!==X.class&&l(O,"class",null,X.class,P),N&4&&l(O,"style",Q.style,X.style,P),N&8){const le=f.dynamicProps;for(let ve=0;ve{te&&pn(te,E,f,k),z&&xn(f,k,E,"updated")},I)},j=(k,f,E,I,P,D,B)=>{for(let O=0;O{if(E!==I){if(E!==Ee)for(const O in E)!Pt(O)&&!(O in I)&&l(k,O,E[O],null,B,f.children,P,D,De);for(const O in I){if(Pt(O))continue;const N=I[O],A=E[O];N!==A&&O!=="value"&&l(k,O,A,N,B,f.children,P,D,De)}"value"in I&&l(k,"value",E.value,I.value,B)}},M=(k,f,E,I,P,D,B,O,N)=>{const A=f.el=k?k.el:r(""),z=f.anchor=k?k.anchor:r("");let{patchFlag:Q,dynamicChildren:X,slotScopeIds:te}=f;te&&(O=O?O.concat(te):te),k==null?(a(A,E,I),a(z,E,I),F(f.children||[],E,z,P,D,B,O,N)):Q>0&&Q&64&&X&&k.dynamicChildren?(j(k.dynamicChildren,X,E,P,D,B,O),(f.key!=null||P&&f===P.subTree)&&kp(k,f,!0)):W(k,f,E,z,P,D,B,O,N)},ee=(k,f,E,I,P,D,B,O,N)=>{f.slotScopeIds=O,k==null?f.shapeFlag&512?P.ctx.activate(f,E,I,B,N):Ie(f,E,I,P,D,B,N):Se(k,f,N)},Ie=(k,f,E,I,P,D,B)=>{const O=k.component=Hh(k,I,P);if(Sa(k)&&(O.ctx.renderer=G),Fh(O),O.asyncDep){if(P&&P.registerDep(O,J),!k.el){const N=O.subTree=Ae(sn);_(null,N,f,E)}}else J(O,k,f,E,P,D,B)},Se=(k,f,E)=>{const I=f.component=k.component;if(Ud(k,f,E))if(I.asyncDep&&!I.asyncResolved){ne(I,f,E);return}else I.next=f,Hd(I.update),I.effect.dirty=!0,I.update();else f.el=k.el,I.vnode=f},J=(k,f,E,I,P,D,B)=>{const O=()=>{if(k.isMounted){let{next:z,bu:Q,u:X,parent:te,vnode:le}=k;{const wt=fp(k);if(wt){z&&(z.el=le.el,ne(k,z,B)),wt.asyncDep.then(()=>{k.isUnmounted||O()});return}}let ve=z,Le;lt(k,!1),z?(z.el=le.el,ne(k,z,B)):z=le,Q&&as(Q),(Le=z.props&&z.props.onVnodeBeforeUpdate)&&pn(Le,te,z,le),lt(k,!0);const je=Us(k),mn=k.subTree;k.subTree=je,w(mn,je,h(mn.el),L(mn),k,P,D),z.el=je.el,ve===null&&zd(k,je.el),X&&Ye(X,P),(Le=z.props&&z.props.onVnodeUpdated)&&Ye(()=>pn(Le,te,z,le),P)}else{let z;const{el:Q,props:X}=f,{bm:te,m:le,parent:ve}=k,Le=Vt(f);if(lt(k,!1),te&&as(te),!Le&&(z=X&&X.onVnodeBeforeMount)&&pn(z,ve,f),lt(k,!0),Q&&we){const je=()=>{k.subTree=Us(k),we(Q,k.subTree,k,P,null)};Le?f.type.__asyncLoader().then(()=>!k.isUnmounted&&je()):je()}else{const je=k.subTree=Us(k);w(null,je,E,I,k,P,D),f.el=je.el}if(le&&Ye(le,P),!Le&&(z=X&&X.onVnodeMounted)){const je=f;Ye(()=>pn(z,ve,je),P)}(f.shapeFlag&256||ve&&Vt(ve.vnode)&&ve.vnode.shapeFlag&256)&&k.a&&Ye(k.a,P),k.isMounted=!0,f=E=I=null}},N=k.effect=new ql(O,un,()=>ys(A),k.scope),A=k.update=()=>{N.dirty&&N.run()};A.id=k.uid,lt(k,!0),A()},ne=(k,f,E)=>{f.component=k;const I=k.vnode.props;k.vnode=f,k.next=null,_h(k,f.props,I,E),wh(k,f.children,E),kt(),er(k),ft()},W=(k,f,E,I,P,D,B,O,N=!1)=>{const A=k&&k.children,z=k?k.shapeFlag:0,Q=f.children,{patchFlag:X,shapeFlag:te}=f;if(X>0){if(X&128){wn(A,Q,E,I,P,D,B,O,N);return}else if(X&256){Oe(A,Q,E,I,P,D,B,O,N);return}}te&8?(z&16&&De(A,P,D),Q!==A&&d(E,Q)):z&16?te&16?wn(A,Q,E,I,P,D,B,O,N):De(A,P,D,!0):(z&8&&d(E,""),te&16&&F(Q,E,I,P,D,B,O,N))},Oe=(k,f,E,I,P,D,B,O,N)=>{k=k||St,f=f||St;const A=k.length,z=f.length,Q=Math.min(A,z);let X;for(X=0;Xz?De(k,P,D,!0,!1,Q):F(f,E,I,P,D,B,O,N,Q)},wn=(k,f,E,I,P,D,B,O,N)=>{let A=0;const z=f.length;let Q=k.length-1,X=z-1;for(;A<=Q&&A<=X;){const te=k[A],le=f[A]=N?Wn(f[A]):kn(f[A]);if(pt(te,le))w(te,le,E,null,P,D,B,O,N);else break;A++}for(;A<=Q&&A<=X;){const te=k[Q],le=f[X]=N?Wn(f[X]):kn(f[X]);if(pt(te,le))w(te,le,E,null,P,D,B,O,N);else break;Q--,X--}if(A>Q){if(A<=X){const te=X+1,le=teX)for(;A<=Q;)Ne(k[A],P,D,!0),A++;else{const te=A,le=A,ve=new Map;for(A=le;A<=X;A++){const nn=f[A]=N?Wn(f[A]):kn(f[A]);nn.key!=null&&ve.set(nn.key,A)}let Le,je=0;const mn=X-le+1;let wt=!1,Uo=0;const Xt=new Array(mn);for(A=0;A=mn){Ne(nn,P,D,!0);continue}let En;if(nn.key!=null)En=ve.get(nn.key);else for(Le=le;Le<=X;Le++)if(Xt[Le-le]===0&&pt(nn,f[Le])){En=Le;break}En===void 0?Ne(nn,P,D,!0):(Xt[En-le]=A+1,En>=Uo?Uo=En:wt=!0,w(nn,f[En],E,null,P,D,B,O,N),je++)}const zo=wt?Lh(Xt):St;for(Le=zo.length-1,A=mn-1;A>=0;A--){const nn=le+A,En=f[nn],Ko=nn+1{const{el:D,type:B,transition:O,children:N,shapeFlag:A}=k;if(A&6){on(k.component.subTree,f,E,I);return}if(A&128){k.suspense.move(f,E,I);return}if(A&64){B.move(k,f,E,G);return}if(B===qe){a(D,f,E);for(let Q=0;QO.enter(D),P);else{const{leave:Q,delayLeave:X,afterLeave:te}=O,le=()=>a(D,f,E),ve=()=>{Q(D,()=>{le(),te&&te()})};X?X(D,le,ve):ve()}else a(D,f,E)},Ne=(k,f,E,I=!1,P=!1)=>{const{type:D,props:B,ref:O,children:N,dynamicChildren:A,shapeFlag:z,patchFlag:Q,dirs:X}=k;if(O!=null&&us(O,null,E,k,!0),z&256){f.ctx.deactivate(k);return}const te=z&1&&X,le=!Vt(k);let ve;if(le&&(ve=B&&B.onVnodeBeforeUnmount)&&pn(ve,f,k),z&6)Cn(k.component,E,I);else{if(z&128){k.suspense.unmount(E,I);return}te&&xn(k,null,f,"beforeUnmount"),z&64?k.type.remove(k,f,E,P,G,I):A&&(D!==qe||Q>0&&Q&64)?De(A,f,E,!1,!0):(D===qe&&Q&384||!P&&z&16)&&De(N,f,E),I&&en(k)}(le&&(ve=B&&B.onVnodeUnmounted)||te)&&Ye(()=>{ve&&pn(ve,f,k),te&&xn(k,null,f,"unmounted")},E)},en=k=>{const{type:f,el:E,anchor:I,transition:P}=k;if(f===qe){Vn(E,I);return}if(f===ra){S(k);return}const D=()=>{s(E),P&&!P.persisted&&P.afterLeave&&P.afterLeave()};if(k.shapeFlag&1&&P&&!P.persisted){const{leave:B,delayLeave:O}=P,N=()=>B(E,D);O?O(k.el,D,N):N()}else D()},Vn=(k,f)=>{let E;for(;k!==f;)E=m(k),s(k),k=E;s(f)},Cn=(k,f,E)=>{const{bum:I,scope:P,update:D,subTree:B,um:O}=k;I&&as(I),P.stop(),D&&(D.active=!1,Ne(B,k,f,E)),O&&Ye(O,f),Ye(()=>{k.isUnmounted=!0},f),f&&f.pendingBranch&&!f.isUnmounted&&k.asyncDep&&!k.asyncResolved&&k.suspenseId===f.pendingId&&(f.deps--,f.deps===0&&f.resolve())},De=(k,f,E,I=!1,P=!1,D=0)=>{for(let B=D;Bk.shapeFlag&6?L(k.component.subTree):k.shapeFlag&128?k.suspense.next():m(k.anchor||k.el);let U=!1;const $=(k,f,E)=>{k==null?f._vnode&&Ne(f._vnode,null,null,!0):w(f._vnode||null,k,f,null,null,null,E),U||(U=!0,er(),is(),U=!1),f._vnode=k},G={p:w,um:Ne,m:on,r:en,mt:Ie,mc:F,pc:W,pbc:j,n:L,o:e};let pe,we;return n&&([pe,we]=n(G)),{render:$,hydrate:pe,createApp:fh($,pe)}}function Js({type:e,props:n},t){return t==="svg"&&e==="foreignObject"||t==="mathml"&&e==="annotation-xml"&&n&&n.encoding&&n.encoding.includes("html")?void 0:t}function lt({effect:e,update:n},t){e.allowRecurse=n.allowRecurse=t}function gp(e,n){return(!e||e&&!e.pendingBranch)&&n&&!n.persisted}function kp(e,n,t=!1){const a=e.children,s=n.children;if(Z(a)&&Z(s))for(let l=0;l>1,e[t[r]]0&&(n[a]=t[l-1]),t[l]=a)}}for(l=t.length,o=t[l-1];l-- >0;)t[l]=o,o=n[o];return t}function fp(e){const n=e.subTree.component;if(n)return n.asyncDep&&!n.asyncResolved?n:fp(n)}const Ph=e=>e.__isTeleport,qe=Symbol.for("v-fgt"),Nt=Symbol.for("v-txt"),sn=Symbol.for("v-cmt"),ra=Symbol.for("v-stc"),ia=[];let vn=null;function vp(e=!1){ia.push(vn=e?null:[])}function Ih(){ia.pop(),vn=ia[ia.length-1]||null}let fa=1;function dr(e){fa+=e}function _p(e){return e.dynamicChildren=fa>0?vn||St:null,Ih(),fa>0&&vn&&vn.push(e),e}function v_(e,n,t,a,s,l){return _p(wp(e,n,t,a,s,l,!0))}function bp(e,n,t,a,s){return _p(Ae(e,n,t,a,s,!0))}function ds(e){return e?e.__v_isVNode===!0:!1}function pt(e,n){return e.type===n.type&&e.key===n.key}const Ts="__vInternal",yp=({key:e})=>e??null,ss=({ref:e,ref_key:n,ref_for:t})=>(typeof e=="number"&&(e=""+e),e!=null?He(e)||$e(e)||ae(e)?{i:Fe,r:e,k:n,f:!!t}:e:null);function wp(e,n=null,t=null,a=0,s=null,l=e===qe?0:1,o=!1,r=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:n,key:n&&yp(n),ref:n&&ss(n),scopeId:Cs,slotScopeIds:null,children:t,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:a,dynamicProps:s,dynamicChildren:null,appContext:null,ctx:Fe};return r?(ro(c,t),l&128&&e.normalize(c)):t&&(c.shapeFlag|=He(t)?8:16),fa>0&&!o&&vn&&(c.patchFlag>0||l&6)&&c.patchFlag!==32&&vn.push(c),c}const Ae=Ah;function Ah(e,n=null,t=null,a=0,s=null,l=!1){if((!e||e===Kd)&&(e=sn),ds(e)){const r=Zn(e,n,!0);return t&&ro(r,t),fa>0&&!l&&vn&&(r.shapeFlag&6?vn[vn.indexOf(e)]=r:vn.push(r)),r.patchFlag|=-2,r}if(Bh(e)&&(e=e.__vccOpts),n){n=Vh(n);let{class:r,style:c}=n;r&&!He(r)&&(n.class=Kl(r)),xe(c)&&(Ui(c)&&!Z(c)&&(c=Re({},c)),n.style=zl(c))}const o=He(e)?1:Jd(e)?128:Ph(e)?64:xe(e)?4:ae(e)?2:0;return wp(e,n,t,a,s,o,l,!0)}function Vh(e){return e?Ui(e)||Ts in e?Re({},e):e:null}function Zn(e,n,t=!1){const{props:a,ref:s,patchFlag:l,children:o}=e,r=n?Oh(a||{},n):a;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:r,key:r&&yp(r),ref:n&&n.ref?t&&s?Z(s)?s.concat(ss(n)):[s,ss(n)]:ss(n):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:n&&e.type!==qe?l===-1?16:l|16:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Zn(e.ssContent),ssFallback:e.ssFallback&&Zn(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Cp(e=" ",n=0){return Ae(Nt,null,e,n)}function __(e,n){const t=Ae(ra,null,e);return t.staticCount=n,t}function b_(e="",n=!1){return n?(vp(),bp(sn,null,e)):Ae(sn,null,e)}function kn(e){return e==null||typeof e=="boolean"?Ae(sn):Z(e)?Ae(qe,null,e.slice()):typeof e=="object"?Wn(e):Ae(Nt,null,String(e))}function Wn(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Zn(e)}function ro(e,n){let t=0;const{shapeFlag:a}=e;if(n==null)n=null;else if(Z(n))t=16;else if(typeof n=="object")if(a&65){const s=n.default;s&&(s._c&&(s._d=!1),ro(e,s()),s._c&&(s._d=!0));return}else{t=32;const s=n._;!s&&!(Ts in n)?n._ctx=Fe:s===3&&Fe&&(Fe.slots._===1?n._=1:(n._=2,e.patchFlag|=1024))}else ae(n)?(n={default:n,_ctx:Fe},t=32):(n=String(n),a&64?(t=16,n=[Cp(n)]):t=8);e.children=n,e.shapeFlag|=t}function Oh(...e){const n={};for(let t=0;tMe||Fe;let hs,Cl;{const e=Si(),n=(t,a)=>{let s;return(s=e[t])||(s=e[t]=[]),s.push(a),l=>{s.length>1?s.forEach(o=>o(l)):s[0](l)}};hs=n("__VUE_INSTANCE_SETTERS__",t=>Me=t),Cl=n("__VUE_SSR_SETTERS__",t=>Pa=t)}const La=e=>{const n=Me;return hs(e),e.scope.on(),()=>{e.scope.off(),hs(n)}},hr=()=>{Me&&Me.scope.off(),hs(null)};function Ep(e){return e.vnode.shapeFlag&4}let Pa=!1;function Fh(e,n=!1){n&&Cl(n);const{props:t,children:a}=e.vnode,s=Ep(e);vh(e,t,s,n),yh(e,a);const l=s?Nh(e,n):void 0;return n&&Cl(!1),l}function Nh(e,n){const t=e.type;e.accessCache=Object.create(null),e.proxy=zi(new Proxy(e.ctx,ch));const{setup:a}=t;if(a){const s=e.setupContext=a.length>1?Mh(e):null,l=La(e);kt();const o=Qn(a,e,0,[e.props,s]);if(ft(),l(),Ei(o)){if(o.then(hr,hr),n)return o.then(r=>{mr(e,r,n)}).catch(r=>{Ta(r,e,0)});e.asyncDep=o}else mr(e,o,n)}else xp(e,n)}function mr(e,n,t){ae(n)?e.type.__ssrInlineRender?e.ssrRender=n:e.render=n:xe(n)&&(e.setupState=Ji(n)),xp(e,t)}let gr;function xp(e,n,t){const a=e.type;if(!e.render){if(!n&&gr&&!a.render){const s=a.template||lo(e).template;if(s){const{isCustomElement:l,compilerOptions:o}=e.appContext.config,{delimiters:r,compilerOptions:c}=a,p=Re(Re({isCustomElement:l,delimiters:r},o),c);a.render=gr(s,p)}}e.render=a.render||un}{const s=La(e);kt();try{uh(e)}finally{ft(),s()}}}function jh(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(n,t){return Ze(e,"get","$attrs"),n[t]}}))}function Mh(e){const n=t=>{e.exposed=t||{}};return{get attrs(){return jh(e)},slots:e.slots,emit:e.emit,expose:n}}function Ss(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Ji(zi(e.exposed)),{get(n,t){if(t in n)return n[t];if(t in la)return la[t](e)},has(n,t){return t in n||t in la}}))}function $h(e,n=!0){return ae(e)?e.displayName||e.name:e.name||n&&e.__name}function Bh(e){return ae(e)&&"__vccOpts"in e}const b=(e,n)=>Sd(e,n,Pa);function y_(e,n,t=Ee){const a=_t(),s=We(n),l=et(n),o=eo((c,p)=>{let d;return Yd(()=>{const h=e[n];Ln(d,h)&&(d=h,p())}),{get(){return c(),t.get?t.get(d):d},set(h){const m=a.vnode.props;!(m&&(n in m||s in m||l in m)&&(`onUpdate:${n}`in m||`onUpdate:${s}`in m||`onUpdate:${l}`in m))&&Ln(h,d)&&(d=h,p()),a.emit(`update:${n}`,t.set?t.set(h):h)}}}),r=n==="modelValue"?"modelModifiers":`${n}Modifiers`;return o[Symbol.iterator]=()=>{let c=0;return{next(){return c<2?{value:c++?e[r]||{}:o,done:!1}:{done:!0}}}},o}function i(e,n,t){const a=arguments.length;return a===2?xe(n)&&!Z(n)?ds(n)?Ae(e,null,[n]):Ae(e,n):Ae(e,null,n):(a>3?t=Array.prototype.slice.call(arguments,2):a===3&&ds(t)&&(t=[t]),Ae(e,n,t))}const Uh="3.4.19";/** +* @vue/runtime-dom v3.4.19 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/const zh="http://www.w3.org/2000/svg",Kh="http://www.w3.org/1998/Math/MathML",Gn=typeof document<"u"?document:null,kr=Gn&&Gn.createElement("template"),qh={insert:(e,n,t)=>{n.insertBefore(e,t||null)},remove:e=>{const n=e.parentNode;n&&n.removeChild(e)},createElement:(e,n,t,a)=>{const s=n==="svg"?Gn.createElementNS(zh,e):n==="mathml"?Gn.createElementNS(Kh,e):Gn.createElement(e,t?{is:t}:void 0);return e==="select"&&a&&a.multiple!=null&&s.setAttribute("multiple",a.multiple),s},createText:e=>Gn.createTextNode(e),createComment:e=>Gn.createComment(e),setText:(e,n)=>{e.nodeValue=n},setElementText:(e,n)=>{e.textContent=n},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Gn.querySelector(e),setScopeId(e,n){e.setAttribute(n,"")},insertStaticContent(e,n,t,a,s,l){const o=t?t.previousSibling:n.lastChild;if(s&&(s===l||s.nextSibling))for(;n.insertBefore(s.cloneNode(!0),t),!(s===l||!(s=s.nextSibling)););else{kr.innerHTML=a==="svg"?`${e}`:a==="mathml"?`${e}`:e;const r=kr.content;if(a==="svg"||a==="mathml"){const c=r.firstChild;for(;c.firstChild;)r.appendChild(c.firstChild);r.removeChild(c)}n.insertBefore(r,t)}return[o?o.nextSibling:n.firstChild,t?t.previousSibling:n.lastChild]}},Bn="transition",Qt="animation",jt=Symbol("_vtc"),Fn=(e,{slots:n})=>i(Zd,Sp(e),n);Fn.displayName="Transition";const Tp={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Jh=Fn.props=Re({},ap,Tp),ot=(e,n=[])=>{Z(e)?e.forEach(t=>t(...n)):e&&e(...n)},fr=e=>e?Z(e)?e.some(n=>n.length>1):e.length>1:!1;function Sp(e){const n={};for(const M in e)M in Tp||(n[M]=e[M]);if(e.css===!1)return n;const{name:t="v",type:a,duration:s,enterFromClass:l=`${t}-enter-from`,enterActiveClass:o=`${t}-enter-active`,enterToClass:r=`${t}-enter-to`,appearFromClass:c=l,appearActiveClass:p=o,appearToClass:d=r,leaveFromClass:h=`${t}-leave-from`,leaveActiveClass:m=`${t}-leave-active`,leaveToClass:g=`${t}-leave-to`}=e,v=Wh(s),w=v&&v[0],C=v&&v[1],{onBeforeEnter:_,onEnter:T,onEnterCancelled:y,onLeave:S,onLeaveCancelled:R,onBeforeAppear:x=_,onAppear:K=T,onAppearCancelled:F=y}=n,H=(M,ee,Ie)=>{zn(M,ee?d:r),zn(M,ee?p:o),Ie&&Ie()},j=(M,ee)=>{M._isLeaving=!1,zn(M,h),zn(M,g),zn(M,m),ee&&ee()},Y=M=>(ee,Ie)=>{const Se=M?K:T,J=()=>H(ee,M,Ie);ot(Se,[ee,J]),vr(()=>{zn(ee,M?c:l),Dn(ee,M?d:r),fr(Se)||_r(ee,a,w,J)})};return Re(n,{onBeforeEnter(M){ot(_,[M]),Dn(M,l),Dn(M,o)},onBeforeAppear(M){ot(x,[M]),Dn(M,c),Dn(M,p)},onEnter:Y(!1),onAppear:Y(!0),onLeave(M,ee){M._isLeaving=!0;const Ie=()=>j(M,ee);Dn(M,h),Pp(),Dn(M,m),vr(()=>{M._isLeaving&&(zn(M,h),Dn(M,g),fr(S)||_r(M,a,C,Ie))}),ot(S,[M,Ie])},onEnterCancelled(M){H(M,!1),ot(y,[M])},onAppearCancelled(M){H(M,!0),ot(F,[M])},onLeaveCancelled(M){j(M),ot(R,[M])}})}function Wh(e){if(e==null)return null;if(xe(e))return[Ws(e.enter),Ws(e.leave)];{const n=Ws(e);return[n,n]}}function Ws(e){return Xu(e)}function Dn(e,n){n.split(/\s+/).forEach(t=>t&&e.classList.add(t)),(e[jt]||(e[jt]=new Set)).add(n)}function zn(e,n){n.split(/\s+/).forEach(a=>a&&e.classList.remove(a));const t=e[jt];t&&(t.delete(n),t.size||(e[jt]=void 0))}function vr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Gh=0;function _r(e,n,t,a){const s=e._endId=++Gh,l=()=>{s===e._endId&&a()};if(t)return setTimeout(l,t);const{type:o,timeout:r,propCount:c}=Lp(e,n);if(!o)return a();const p=o+"end";let d=0;const h=()=>{e.removeEventListener(p,m),l()},m=g=>{g.target===e&&++d>=c&&h()};setTimeout(()=>{d(t[v]||"").split(", "),s=a(`${Bn}Delay`),l=a(`${Bn}Duration`),o=br(s,l),r=a(`${Qt}Delay`),c=a(`${Qt}Duration`),p=br(r,c);let d=null,h=0,m=0;n===Bn?o>0&&(d=Bn,h=o,m=l.length):n===Qt?p>0&&(d=Qt,h=p,m=c.length):(h=Math.max(o,p),d=h>0?o>p?Bn:Qt:null,m=d?d===Bn?l.length:c.length:0);const g=d===Bn&&/\b(transform|all)(,|$)/.test(a(`${Bn}Property`).toString());return{type:d,timeout:h,propCount:m,hasTransform:g}}function br(e,n){for(;e.lengthyr(t)+yr(e[a])))}function yr(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Pp(){return document.body.offsetHeight}function Yh(e,n,t){const a=e[jt];a&&(n=(n?[n,...a]:[...a]).join(" ")),n==null?e.removeAttribute("class"):t?e.setAttribute("class",n):e.className=n}const va=Symbol("_vod"),w_={beforeMount(e,{value:n},{transition:t}){e[va]=e.style.display==="none"?"":e.style.display,t&&n?t.beforeEnter(e):Zt(e,n)},mounted(e,{value:n},{transition:t}){t&&n&&t.enter(e)},updated(e,{value:n,oldValue:t},{transition:a}){!n==!t&&(e.style.display===e[va]||!n)||(a?n?(a.beforeEnter(e),Zt(e,!0),a.enter(e)):a.leave(e,()=>{Zt(e,!1)}):Zt(e,n))},beforeUnmount(e,{value:n}){Zt(e,n)}};function Zt(e,n){e.style.display=n?e[va]:"none"}const Xh=Symbol(""),Qh=/(^|;)\s*display\s*:/;function Zh(e,n,t){const a=e.style,s=He(t),l=a.display;let o=!1;if(t&&!s){if(n&&!He(n))for(const r in n)t[r]==null&&El(a,r,"");for(const r in t)r==="display"&&(o=!0),El(a,r,t[r])}else if(s){if(n!==t){const r=a[Xh];r&&(t+=";"+r),a.cssText=t,o=Qh.test(t)}}else n&&e.removeAttribute("style");va in e&&(e[va]=o?a.display:"",a.display=l)}const wr=/\s*!important$/;function El(e,n,t){if(Z(t))t.forEach(a=>El(e,n,a));else if(t==null&&(t=""),n.startsWith("--"))e.setProperty(n,t);else{const a=em(e,n);wr.test(t)?e.setProperty(et(a),t.replace(wr,""),"important"):e[a]=t}}const Cr=["Webkit","Moz","ms"],Gs={};function em(e,n){const t=Gs[n];if(t)return t;let a=We(n);if(a!=="filter"&&a in e)return Gs[n]=a;a=Kt(a);for(let s=0;sYs||(om.then(()=>Ys=0),Ys=Date.now());function im(e,n){const t=a=>{if(!a._vts)a._vts=Date.now();else if(a._vts<=t.attached)return;dn(pm(a,t.value),n,5,[a])};return t.value=e,t.attached=rm(),t}function pm(e,n){if(Z(n)){const t=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{t.call(e),e._stopped=!0},n.map(a=>s=>!s._stopped&&a&&a(s))}else return n}const Sr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,cm=(e,n,t,a,s,l,o,r,c)=>{const p=s==="svg";n==="class"?Yh(e,a,p):n==="style"?Zh(e,t,a):Ea(n)?$l(n)||sm(e,n,t,a,o):(n[0]==="."?(n=n.slice(1),!0):n[0]==="^"?(n=n.slice(1),!1):um(e,n,a,p))?tm(e,n,a,l,o,r,c):(n==="true-value"?e._trueValue=a:n==="false-value"&&(e._falseValue=a),nm(e,n,a,p))};function um(e,n,t,a){if(a)return!!(n==="innerHTML"||n==="textContent"||n in e&&Sr(n)&&ae(t));if(n==="spellcheck"||n==="draggable"||n==="translate"||n==="form"||n==="list"&&e.tagName==="INPUT"||n==="type"&&e.tagName==="TEXTAREA")return!1;if(n==="width"||n==="height"){const s=e.tagName;if(s==="IMG"||s==="VIDEO"||s==="CANVAS"||s==="SOURCE")return!1}return Sr(n)&&He(t)?!1:n in e}const Ip=new WeakMap,Ap=new WeakMap,ms=Symbol("_moveCb"),Lr=Symbol("_enterCb"),Vp={name:"TransitionGroup",props:Re({},Jh,{tag:String,moveClass:String}),setup(e,{slots:n}){const t=_t(),a=tp();let s,l;return op(()=>{if(!s.length)return;const o=e.moveClass||`${e.name||"v"}-move`;if(!fm(s[0].el,t.vnode.el,o))return;s.forEach(mm),s.forEach(gm);const r=s.filter(km);Pp(),r.forEach(c=>{const p=c.el,d=p.style;Dn(p,o),d.transform=d.webkitTransform=d.transitionDuration="";const h=p[ms]=m=>{m&&m.target!==p||(!m||/transform$/.test(m.propertyName))&&(p.removeEventListener("transitionend",h),p[ms]=null,zn(p,o))};p.addEventListener("transitionend",h)})}),()=>{const o=ie(e),r=Sp(o);let c=o.tag||qe;s=l,l=n.default?ao(n.default()):[];for(let p=0;pdelete e.mode;Vp.props;const hm=Vp;function mm(e){const n=e.el;n[ms]&&n[ms](),n[Lr]&&n[Lr]()}function gm(e){Ap.set(e,e.el.getBoundingClientRect())}function km(e){const n=Ip.get(e),t=Ap.get(e),a=n.left-t.left,s=n.top-t.top;if(a||s){const l=e.el.style;return l.transform=l.webkitTransform=`translate(${a}px,${s}px)`,l.transitionDuration="0s",e}}function fm(e,n,t){const a=e.cloneNode(),s=e[jt];s&&s.forEach(r=>{r.split(/\s+/).forEach(c=>c&&a.classList.remove(c))}),t.split(/\s+/).forEach(r=>r&&a.classList.add(r)),a.style.display="none";const l=n.nodeType===1?n:n.parentNode;l.appendChild(a);const{hasTransform:o}=Lp(a);return l.removeChild(a),o}const Pr=e=>{const n=e.props["onUpdate:modelValue"]||!1;return Z(n)?t=>as(n,t):n};function vm(e){e.target.composing=!0}function Ir(e){const n=e.target;n.composing&&(n.composing=!1,n.dispatchEvent(new Event("input")))}const Xs=Symbol("_assign"),C_={created(e,{modifiers:{lazy:n,trim:t,number:a}},s){e[Xs]=Pr(s);const l=a||s.props&&s.props.type==="number";Et(e,n?"change":"input",o=>{if(o.target.composing)return;let r=e.value;t&&(r=r.trim()),l&&(r=ml(r)),e[Xs](r)}),t&&Et(e,"change",()=>{e.value=e.value.trim()}),n||(Et(e,"compositionstart",vm),Et(e,"compositionend",Ir),Et(e,"change",Ir))},mounted(e,{value:n}){e.value=n??""},beforeUpdate(e,{value:n,modifiers:{lazy:t,trim:a,number:s}},l){if(e[Xs]=Pr(l),e.composing)return;const o=s||e.type==="number"?ml(e.value):e.value,r=n??"";o!==r&&(document.activeElement===e&&e.type!=="range"&&(t||a&&e.value.trim()===r)||(e.value=r))}},_m=["ctrl","shift","alt","meta"],bm={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,n)=>_m.some(t=>e[`${t}Key`]&&!n.includes(t))},E_=(e,n)=>{const t=e._withMods||(e._withMods={}),a=n.join(".");return t[a]||(t[a]=(s,...l)=>{for(let o=0;o{const t=e._withKeys||(e._withKeys={}),a=n.join(".");return t[a]||(t[a]=s=>{if(!("key"in s))return;const l=et(s.key);if(n.some(o=>o===l||ym[o]===l))return e(s)})},wm=Re({patchProp:cm},qh);let Qs,Ar=!1;function Cm(){return Qs=Ar?Qs:Th(wm),Ar=!0,Qs}const Em=(...e)=>{const n=Cm().createApp(...e),{mount:t}=n;return n.mount=a=>{const s=Tm(a);if(s)return t(s,!0,xm(s))},n};function xm(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Tm(e){return He(e)?document.querySelector(e):e}var Sm=["link","meta","script","style","noscript","template"],Lm=["title","base"],Pm=([e,n,t])=>Lm.includes(e)?e:Sm.includes(e)?e==="meta"&&n.name?`${e}.${n.name}`:e==="template"&&n.id?`${e}.${n.id}`:JSON.stringify([e,Object.entries(n).map(([a,s])=>typeof s=="boolean"?s?[a,""]:null:[a,s]).filter(a=>a!=null).sort(([a],[s])=>a.localeCompare(s)),t]):null,Im=e=>{const n=new Set,t=[];return e.forEach(a=>{const s=Pm(a);s&&!n.has(s)&&(n.add(s),t.push(a))}),t},Am=e=>e[0]==="/"?e:`/${e}`,io=e=>e[e.length-1]==="/"||e.endsWith(".html")?e:`${e}/`,yn=e=>/^(https?:)?\/\//.test(e),Vm=/.md((\?|#).*)?$/,_a=(e,n="/")=>!!(yn(e)||e.startsWith("/")&&!e.startsWith(n)&&!Vm.test(e)),Op=e=>/^[a-z][a-z0-9+.-]*:/.test(e),_n=e=>Object.prototype.toString.call(e)==="[object Object]",Om=e=>{const[n,...t]=e.split(/(\?|#)/);if(!n||n.endsWith("/"))return e;let a=n.replace(/(^|\/)README.md$/i,"$1index.html");return a.endsWith(".md")?a=a.substring(0,a.length-3)+".html":a.endsWith(".html")||(a=a+".html"),a.endsWith("/index.html")&&(a=a.substring(0,a.length-10)),a+t.join("")},Ls=e=>e[e.length-1]==="/"?e.slice(0,-1):e,Dp=e=>e[0]==="/"?e.slice(1):e,Dm=(e,n)=>{const t=Object.keys(e).sort((a,s)=>{const l=s.split("/").length-a.split("/").length;return l!==0?l:s.length-a.length});for(const a of t)if(n.startsWith(a))return a;return"/"},Rp=e=>typeof e=="function",me=e=>typeof e=="string";const Rm="modulepreload",Hm=function(e){return"/"+e},Vr={},u=function(n,t,a){let s=Promise.resolve();if(t&&t.length>0){const l=document.getElementsByTagName("link");s=Promise.all(t.map(o=>{if(o=Hm(o),o in Vr)return;Vr[o]=!0;const r=o.endsWith(".css"),c=r?'[rel="stylesheet"]':"";if(!!a)for(let h=l.length-1;h>=0;h--){const m=l[h];if(m.href===o&&(!r||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${o}"]${c}`))return;const d=document.createElement("link");if(d.rel=r?"stylesheet":Rm,r||(d.as="script",d.crossOrigin=""),d.href=o,document.head.appendChild(d),r)return new Promise((h,m)=>{d.addEventListener("load",h),d.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${o}`)))})}))}return s.then(()=>n()).catch(l=>{const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=l,window.dispatchEvent(o),!o.defaultPrevented)throw l})},Fm=JSON.parse("{}"),Nm=Object.fromEntries([["/",{loader:()=>u(()=>import("./index.html-D4cWqosL.js"),__vite__mapDeps([0,1])),meta:{d:1627556976e3,e:``,r:{minutes:1.14,words:341},y:"h",t:"docmoa",i:"home"}}],["/00-Howto/01-Overview.html",{loader:()=>u(()=>import("./01-Overview.html-BtGYf7na.js"),__vite__mapDeps([2,3,1])),meta:{d:1634225909e3,e:` +

    문서가 인터넷상에 공개되는 목적은 접근성을 극대화 하기 위함 입니다. 또한 로컬환경에서 빠르게 문서를 검색하기 위해 해당 git repo를 clone 받거나 download 받아서 별도의 마크다운 툴과 연동하는 것도 가능합니다. VuePress 기반으로 구성되었기 때문에 이외의 방식은 문서 표기에 제약이 있을 수 있습니다.

    +

    github page

    +

    docmoa의 공개된 페이지를 통해 문서를 읽을 수 있습니다.

    `,r:{minutes:.64,words:192},y:"a",t:"docmoa 활용 가이드"}}],["/00-Howto/",{loader:()=>u(()=>import("./index.html-D-7U4fdm.js"),__vite__mapDeps([4,1])),meta:{d:1628085698e3,e:` +

    문서를 올바르게 작성하고 공유하기 위한 몇가지 사항을 안내합니다.

    +
    +

    안내

    +

    문서 기여 시 문서 작성 가이드를 꼭 한번 확인해주세요.

    +
    +

    활용 가이드

    +

    docmoa를 활용할 수 있는 몇가지 가이드를 docmoa 활용 가이드 에서 설명합니다.

    `,r:{minutes:.26,words:77},y:"a",t:'How to "docmoa"'}}],["/01-Infrastructure/",{loader:()=>u(()=>import("./index.html-Da7Zh9h7.js"),__vite__mapDeps([5,1])),meta:{d:1628085698e3,g:["Infrastructure"],e:` +

    Recent pages

    +
      +
    • + {{ page.title }} + + [ {{ (new Date(page.frontmatter.date)).toLocaleString() }} ] + +
    • +
    `,r:{minutes:.19,words:58},y:"a",t:"Infrastructure"}}],["/02-PrivatePlatform/",{loader:()=>u(()=>import("./index.html-BikDGuPB.js"),__vite__mapDeps([6,1])),meta:{d:1695042774e3,g:["Platform"],e:` +

    Recent pages

    +
      +
    • + {{ page.title }} + + [ {{ (new Date(page.frontmatter.date)).toLocaleString() }} ] + +
    • +
    `,r:{minutes:.2,words:59},y:"a",t:"Private Platform"}}],["/03-PublicCloud/",{loader:()=>u(()=>import("./index.html-Br7ta8RV.js"),__vite__mapDeps([7,1])),meta:{d:1695042774e3,g:["Cloud"],e:` +

    Recent pages

    +
      +
    • + {{ page.title }} + + [ {{ (new Date(page.frontmatter.date)).toLocaleString() }} ] + +
    • +
    +`,r:{minutes:.2,words:59},y:"a",t:"Public Cloud"}}],["/04-HashiCorp/",{loader:()=>u(()=>import("./index.html-CJ-C5MJx.js"),__vite__mapDeps([8,1])),meta:{d:1628085698e3,g:["HashiCorp"],e:` +

    Recent pages

    +
      +
    • + {{ page.title }} + + [ {{ (new Date(page.frontmatter.date)).toLocaleString() }} ] + +
    • +
    +

    Packer

    +
    +

    다양한 플랫폼에 대한 VM, 컨테이너 이미지 생성 자동화 도구

    +
    `,r:{minutes:1.31,words:393},y:"a",t:"HashiCorp"}}],["/05-Software/",{loader:()=>u(()=>import("./index.html-BcBPf8Rf.js"),__vite__mapDeps([9,1])),meta:{d:164032788e4,g:["Software"],e:` +

    Recent pages

    +
      +
    • + {{ page.title }} + + [ {{ (new Date(page.frontmatter.date)).toLocaleString() }} ] + +
    • +
    +

    Jenkins

    +`,r:{minutes:.21,words:62},y:"a",t:"Software"}}],["/06-etc/",{loader:()=>u(()=>import("./index.html-C5t7F1jk.js"),__vite__mapDeps([10,1])),meta:{d:1640259507e3,g:["Etc"],e:` +

    Recent pages

    +
      +
    • + {{ page.title }} + + [ {{ (new Date(page.frontmatter.date)).toLocaleString() }} ] + +
    • +
    +`,r:{minutes:.19,words:58},y:"a",t:"Etc."}}],["/99-about/01-About.html",{loader:()=>u(()=>import("./01-About.html-7Tx-G8Iy.js"),__vite__mapDeps([11,1])),meta:{d:1628085698e3,e:` +
    logo
    logo
    +

    기술은 지속적으로 발전하고 시시각각 변화하고 있습니다. 더불어 IT라는 분야도 점점더 세분화되고, 혼자서는 모든것을 아는것은 거의 불가능합니다.
    +IT 업을 하면서 정리와 스크랩은 일상이 되어가지만 변화를 쫓아가기는 정말 버겁습니다.

    +

    하지만 혼자서가 아니라면 어떨까 라는 생각을 합니다.
    +집단지성 이란 표현이 있듯, 개인보다는 여럿이 만들어가는 노트입니다. 과거에는 이런 노하우가 개인의 자산으로 비밀처럼 감춰두던 지식이였지만 지금은 서로 공유하고, 알리고, 기여하는 것도 의미 있는 시기인것 같습니다.

    `,r:{minutes:.26,words:78},y:"a",t:"docmoa"}}],["/99-about/02-Thanks.html",{loader:()=>u(()=>import("./02-Thanks.html-BWdU5ZFB.js"),__vite__mapDeps([12,1])),meta:{d:1628085698e3,e:` +
    +

    Contributors:

    + +

    Loading contributors...

    +
    `,r:{minutes:.3,words:89},y:"a",t:"Thank you"}}],["/00-Howto/02-Guide/01-Start.html",{loader:()=>u(()=>import("./01-Start.html-CZXt5onJ.js"),__vite__mapDeps([13,3,1])),meta:{d:1634225909e3,e:` +

    docmoa에 문서 기여하기위한 가이드를 설명합니다.

    +
    +

    +

    다양한 방법으로 문서를 작성하고 기여할 수 있습니다.
    +얽매이지 마세요.

    +
    +

    문서는 모두 git으로 관리되며 공개되어있습니다. 문서 기여를 위한 방식은 별도 안내로 구분하여 설명합니다.

    `,r:{minutes:.81,words:242},y:"a",t:"문서작성 '시작'"}}],["/00-Howto/02-Guide/02-Contribute.html",{loader:()=>u(()=>import("./02-Contribute.html-CujV57Pk.js"),__vite__mapDeps([14,1])),meta:{d:1634225909e3,e:` +

    docmoa에 문서 기여하기위한 가이드를 설명합니다. 일반적인 github 상에서의 코드 기여 방식과 동일합니다.

    +

    git 설치(Option)

    +

    로컬 환경에서 git 명령을 수행하기 위해 설치합니다. github 브라우저 환경에서 수정하는 것도 가능하지만, 로컬에서 문서를 활용하고 오프라인 작업을 위해서는 설치하시기를 권장합니다.

    +

    Git 설치 방법 안내를 참고하여 아래 설명합니다.

    `,r:{minutes:1.12,words:335},y:"a",t:"Contribute"}}],["/00-Howto/02-Guide/04-Template.html",{loader:()=>u(()=>import("./04-Template.html-D_srWqRi.js"),__vite__mapDeps([15,1])),meta:{d:1634225909e3,e:` +

    docmoa에 문서 템플릿을 설명합니다.

    +
    +

    주의

    +

    기본 템플릿 가이드를 잘 지켜주세요. 함께 만드는 문서 모음이므로, 기본적인 형식이 필요합니다.

    +
    +

    최소 Template

    +
    ---
    +
    +---
    +
    +# h1 제목 = Title 입니다.
    +내용은 마크다운 형식으로 작성합니다.
    +
    +## h2 제목
    +
    +
    `,r:{minutes:.15,words:44},y:"a",t:"Template"}}],["/00-Howto/02-Guide/",{loader:()=>u(()=>import("./index.html-D-y1LV_e.js"),__vite__mapDeps([16,1])),meta:{d:1695042774e3,e:` +`,r:{minutes:.02,words:5},y:"a",t:"Guides"}}],["/00-Howto/03-Tips/Chart.html",{loader:()=>u(()=>import("./Chart.html-Dir6u0uC.js"),__vite__mapDeps([17,1])),meta:{d:1663987184e3,e:` +

    문서 작성시 차트를 추가하는 방법을 안내합니다.

    + +

    차트 구성 방식은 ChartJS를 따릅니다.

    +

    ::: chart:::로 처리합니다.

    `,r:{minutes:.92,words:277},y:"a",t:"Chart"}}],["/00-Howto/03-Tips/CodeBlock.html",{loader:()=>u(()=>import("./CodeBlock.html-2_d1c3y7.js"),__vite__mapDeps([18,1])),meta:{d:1634225909e3,e:'\n

    마크다운 기본 사용 법과 거의 동일합니다.

    \n

    기본 사용법

    \n

    코드블록은 ``` 과 ``` 사이에 코드를 넣어 로 표기합니다. 아래와 같이 md 파일 내에 작성하면

    \n
    ```\n# Code block e.g.\nThis is my code\n```\n
    ',r:{minutes:.66,words:197},y:"a",t:"Code Block"}}],["/00-Howto/03-Tips/Link.html",{loader:()=>u(()=>import("./Link.html-Bu_HIehI.js"),__vite__mapDeps([19,1])),meta:{d:1634225909e3,e:` +

    문서 작성시 외부 링크를 포함하는 예를 설명합니다. 참고 문서

    +

    텍스트에 링크 달기

    +

    설명하던 글의 특정 단어에 대해 외부 링크를 추가하고자 하는 경우 브라킷[ ]과 괄호를 사용합니다. domain을 같이 기입하는 경우 새창에서 열기로 표기됩니다.

    +
    새창으로 이동하는 [링크 달기](http://docmoa.github.io/00-Howto/03-Tips/Link.html)
    +현재 창에서 이동하는 [링크 달기](/00-Howto/03-Tips/Link.html)
    +
    `,r:{minutes:.44,words:133},y:"a",t:"Link"}}],["/00-Howto/03-Tips/PlantUML.html",{loader:()=>u(()=>import("./PlantUML.html-C1o0QjFB.js"),__vite__mapDeps([20,1])),meta:{d:1634225909e3,e:` +

    markdown-it-plantuml 플러그인을 활성화 하여 UML 작성이 가능합니다. 아래는 플러그인 개발자의 안내를 풀어 일부 설명합니다.

    +

    기본 사용법

    +

    UML 블록은 @startuml@enduml 사이에 UML 구성을 위한 구성을 넣어 표기합니다. 아래와 같이 md 파일 내에 작성하면

    `,r:{minutes:2.77,words:831},y:"a",t:"UML"}}],["/00-Howto/03-Tips/",{loader:()=>u(()=>import("./index.html-Dr0IN6A8.js"),__vite__mapDeps([21,1])),meta:{d:1695042774e3,e:` +`,r:{minutes:.02,words:5},y:"a",t:"Tips"}}],["/00-Howto/03-Tips/Tabs.html",{loader:()=>u(()=>import("./Tabs.html-CBnV8yex.js"),__vite__mapDeps([22,1])),meta:{d:1634225909e3,e:` +

    컨텐츠에 탭을 추가하여 상황에 따라 선택적으로 문서를 읽을 수 있도록 합니다.
    +상세 내용은 Markdown EnhanceTabs, Code Tabs 를 확인해보세요.

    `,r:{minutes:.4,words:119},y:"a",t:"Tabs"}}],["/00-Howto/03-Tips/TipBox.html",{loader:()=>u(()=>import("./TipBox.html-DpUp3CBK.js"),__vite__mapDeps([23,1])),meta:{d:1634225909e3,e:` +

    문서 작성시 팁과 주의사항을 표기하는 방법을 설명합니다.
    +공식 문서

    +

    기본 사용법

    +
    ::: tip
    +This is a tip
    +:::
    +
    +::: warning
    +This is a warning
    +:::
    +
    +::: danger
    +This is a dangerous warning
    +:::
    +
    +::: details
    +This is a details block, which does not work in IE / Edge
    +:::
    +
    `,r:{minutes:.44,words:133},y:"a",t:"Tip Box"}}],["/01-Infrastructure/Container/container_runtime_sheet.html",{loader:()=>u(()=>import("./container_runtime_sheet.html-CjV2Qyyt.js"),__vite__mapDeps([24,1])),meta:{d:1640262e6,g:["container","docker","podman"],e:` +
    +

    update : 2021. 12. 23.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CRI-OContainerd CRI pluginDocker EnginegVisor CRI pluginCRI-O Kata Containers
    sponsorsCNCFCNCFDocker IncGoogleIntel
    started20162015Mar 201320152017
    version1.231.1920.10release-20211129.01.13
    runtimerunc (default)containerd managing runcruncrunsckata-runtime
    kernelsharedsharedsharedpartially sharedisolated
    syscall filteringnononoyesno
    kernel blobsnonononoyes
    footprint----30mb
    start time<10ms<10ms<10ms<10ms<100ms
    io performancehost performancehost performancehost performanceslowhost performance
    network performancehost performancehost performancehost performanceslow (see comment)close to host performance
    Docshttps://github.com/kubernetes-sigs/cri-o/https://github.com/containerd/crihttps://github.com/moby/mobyhttps://github.com/google/gvisorhttps://github.com/kata-containers/runtime
    장점경량의 쿠버네티스 전용 Docker 데몬이 필요하지 않음 OpenShift의 기본 컨테이너 런타임 아마도 최고의 컨테이너 기본 런타임최신 Docker Engine과 함께 기본적으로 설치됨 Kubernetes는 ContainerD를 직접 사용할 수 있으며, Docker또한 동일한 호스트에서 직접 사용할 수도 있음 DockerD 데몬을 실행할 필요가 없음방대한 수의 사용자가 테스트하고 반복 한 가장 성숙한 런타임 seccomp, SELinux 및 AppArmor를 사용하여 강화할 수 있음 가장 빠른 시작 시간 메모리 사용량이 가장 적음gcloud appengine에서 고객 간의 격리 계층으로 사용함 상태를 저장하지 않는 웹 앱에 적합 표준 컨테이너에 두 개의 보안 계층을 추가함아마도 가장 안전한 옵션 보안에 대한 주요 절충안으로 오버헤드가 발생하는것은 그렇게 나쁘지 않은 것으로 보임
    단점Docker Engine이 같고 있는 동일한 보안 이슈를 가지고 있음 보안정책을 별도로 관리해야 함This is slightly newer as it has been through a few iterations of being installed differently.Kubernetes는 CRI 플러그인 아키텍처로 이동하고 있음 보안을 강화하고 관리하는것은 너무 복잡함버전이 지정되지 않았으며 아직 Kubernetes에서 프로덕션에 사용해서는 안됨 많은 syscall을 만드는 응용 프로그램에는 적합하지 않음 400 개 Linux syscall이 모두 구현되어 일부 앱이 작동하지 않을 수 있음 (예 : postgres).kata-runtime 자체는 v1이지만 이것이 Kubernetes 상에서 어떻게 준비 되어 있는지 확인이 필요 30MB 메모리 오버 헤드로 인한 비효율적 패킹 시작 시간
    `,r:{minutes:.58,words:173},y:"a",t:"Container Runtimes 비교 표"}}],["/01-Infrastructure/Container/rancher-desktop-disk-resize-mac.html",{loader:()=>u(()=>import("./rancher-desktop-disk-resize-mac.html-c5Gx1GDU.js"),__vite__mapDeps([25,1])),meta:{d:1683621743e3,g:["rancher","docker","mac"],e:` +
    +

    Private docker registry
    +Rancher Desktop
    +MacOS
    +Src : https://slack-archive.rancher.com/t/8508077/on-my-m1-mac-i-ve-started-getting-this-error-and-it-wont-go-#3e8d178c-aee8-46e6-b4cc-094c2339cbaa

    +
    `,r:{minutes:.36,words:109},y:"a",t:"Rancher Desktop Disk Resize on MAC"}}],["/01-Infrastructure/Container/rancher-desktop-insecure-setup-mac.html",{loader:()=>u(()=>import("./rancher-desktop-insecure-setup-mac.html-CXvCoHFH.js"),__vite__mapDeps([26,1])),meta:{d:165217296e4,g:["rancher","docker","mac"],e:` +
    +

    Private docker registry
    +Rancher Desktop
    +MacOS
    +Setup insecure-registries

    +
    +

    issue

    +
    $ docker push 192.168.60.11:5000/example-python:1.0
    +Error response from daemon: Get https://192.168.60.11:5000/v1/example-python: http: server gave HTTP response to HTTPS client
    +
    `,r:{minutes:.53,words:158},y:"a",t:"Rancher Desktop Insecure Setup on MAC"}}],["/02-PrivatePlatform/OpenShift/deploying_specificnode_by_namespace.html",{loader:()=>u(()=>import("./deploying_specificnode_by_namespace.html-QxOq8fbf.js"),__vite__mapDeps([27,1])),meta:{d:1695042774e3,g:["openshift","ocp"],e:` +
    +

    원문 : https://blog.openshift.com/deploying-applications-to-specific-nodes/

    +
    +

    Deployment나 Deployment Config에서 Nodeselect를 지정하는 방법 외에 Project 단위로 설정하는 방법을 설명합니다.

    `,r:{minutes:.55,words:165},y:"a",t:"OpenShift 3.x - 프로젝트 별로 특정 노드에 배포하기"}}],["/02-PrivatePlatform/OpenShift/openshift3.11_custom_metric_with_jboss.html",{loader:()=>u(()=>import("./openshift3.11_custom_metric_with_jboss.html-Ct_0tbjB.js"),__vite__mapDeps([28,1])),meta:{d:1695042774e3,g:["openshift","ocp","jboss"],e:` +
    +

    Autoscaling applications using custom metrics on OpenShift Container Platform 3.11 with JBoss EAP or Wildfly

    +
    +

    Red Hat OpenShift Container Platform 3.11 (OCP) 은 기본적으로 CPU에 대한 애플리케이션 자동 확장을 지원합니다. 추가적으로 apis/autoscaling/v2beta1를 활성화하여 Memory의 메트릭을 기반으로 한 기능도 지원 합니다. CPU나 Memory의 경우 애플리케이션에 종속되지 않은 기본적인 메트릭이나, 때로는 추가적인 메트릭 요소를 기반으로 확장할 필요성이 있습니다.

    `,r:{minutes:6.43,words:1930},y:"a",t:"OpenShift 3.11 - Custom metric with JBoss"}}],["/02-PrivatePlatform/Vsphere/Vsphere_template_issue.html",{loader:()=>u(()=>import("./Vsphere_template_issue.html-8QDvgHPa.js"),__vite__mapDeps([29,1])),meta:{d:1695042774e3,g:["vsphere","template"],e:` +
      +
    1. redhat 계열
    2. +
    +
      +
    • 아래 네개의 패키지의 설치가 필요하다. 특히 perl은 거의 설치가 안되어 있음
    • +
    • open-vm-tools, open-vm-tools-deploypkg, net-tools, perl
    • +
    • 설치 후 template 생성하고 배포하면 됨
    • +
    +
      +
    1. debian 계열
    2. +
    +
      +
    • /etc/systemd/system/vmtoolsd.service 파일에 구문 추가
    • +
    • 18.04은 추가하여도 가끔 NIC, hostname이 기존에 템플릿의 정보를 가져올때가 있음
    • +
    `,r:{minutes:.28,words:83},y:"a",t:"VSphere 템플릿 생성 이슈"}}],["/03-PublicCloud/AlibabaCloud/CredentialConfig.html",{loader:()=>u(()=>import("./CredentialConfig.html-B_-qDT_l.js"),__vite__mapDeps([30,1])),meta:{d:1695042774e3,g:["alibaba","aliyun"],e:` +

    1. CLI 설치

    +

    1.1 Download 방식

    +`,r:{minutes:1.14,words:342},y:"a",t:"Alibaba CLI 설정"}}],["/03-PublicCloud/AlibabaCloud/",{loader:()=>u(()=>import("./index.html-Bk0z2AFD.js"),__vite__mapDeps([31,1])),meta:{d:1695042774e3,e:` +`,r:{minutes:.01,words:4},y:"a",t:"Alibaba Cloud"}}],["/03-PublicCloud/Azure/",{loader:()=>u(()=>import("./index.html-CierunXN.js"),__vite__mapDeps([32,1])),meta:{d:1695042774e3,e:` +`,r:{minutes:.01,words:3},y:"a",t:"Azure"}}],["/03-PublicCloud/NCP/",{loader:()=>u(()=>import("./index.html-CIUsUagt.js"),__vite__mapDeps([33,1])),meta:{d:1695042774e3,e:` +`,r:{minutes:.01,words:4},y:"a",t:"NCP(Naver Cloud Platform)"}}],["/06-etc/class/devops-discussion-1st.html",{loader:()=>u(()=>import("./devops-discussion-1st.html--IXABsuM.js"),__vite__mapDeps([34,1])),meta:{d:1640262e6,g:["devops","container"],e:` +

    일시 : 2019년 4월 24일 수요일 저녁 19시 ~ 21시

    +

    안내 : 컨테이너 연구소 - 컨테이너 시스템의 활용 방향 및 미래에 관련해서 좌담

    +

    장소 : 메가존 지하 강연장

    +

    Q. 컨테이너란 무엇일까?

    +
      +
    • +

      자원을 잘 나눠주는 프로세스.

      +
    • +
    • +

      Zip같은 패키지인데 바퀴도 있고 엔진도 있는

      +
    • +
    • +

      개발자들의 공용어

      +
    • +
    • +

      VM이 H/W와의 분리였다면 컨테이너는 OS와 분리

      +
    • +
    • +

      떠나보낸 연인...하지만 사랑한다

      +
      +
    • +
    `,r:{minutes:.17,words:51},y:"a",t:"DevOps 연구소 좌담회 (1차)"}}],["/06-etc/class/devops-discussion-20240213.html",{loader:()=>u(()=>import("./devops-discussion-20240213.html-B8CuZjzi.js"),__vite__mapDeps([35,1])),meta:{a:"GS",d:1707896358e3,g:["devops","ai"],e:`
    torder-townhall
    torder-townhall
    +

    DevOps Korea 좌담회 2024.2.13.

    +
      +
    • +

      일시 : 2024년 2월 13일 (화) 19:00

      +
    • +
    • +

      안내 :

      + +
    • +
    • +

      장소 : 파크원2타워 티오더

      +
    • +
    • +

      주요 아젠다

      +
        +
      • AI시대 관련 변화된 시대에 맞게 대응 할 방향
      • +
      • DevOps 업무에 도움이 될만한 AI도구 및 방법론
      • +
      • 이미 적용중인 AI를 활용한 엔지니어링 업무소개 등
      • +
      +
    • +
    `,r:{minutes:.96,words:289},y:"a",t:"DevOps Korea 좌담회 2024.2.13."}}],["/06-etc/class/devops-discussion-2nd.html",{loader:()=>u(()=>import("./devops-discussion-2nd.html-CotdaWQ0.js"),__vite__mapDeps([36,1])),meta:{d:1640262e6,g:["devops","container"],e:` +

    일시 : 2019년 5월 23일 (목) 19:00 ~ 21:30

    +

    안내 : 컨테이너 연구소 - 컨테이너 시스템의 활용 방향 및 미래에 관련해서 좌담 part2

    +

    장소 : 대륭서초타워 베스핀글로벌

    +

    Q. 컨테이너란?

    +
      +
    • Namespace가 지원되는 Process (Tech 관점)
    • +
    • 스타트업에서는 비용 절감 가능 ($ 관점)
    • +
    `,r:{minutes:.34,words:103},y:"a",t:"DevOps 연구소 좌담회 (2차)"}}],["/06-etc/infomation/Keyboard-Eng.html",{loader:()=>u(()=>import("./Keyboard-Eng.html-CK_we2gb.js"),__vite__mapDeps([37,1])),meta:{d:1695042774e3,g:["keyboard","tip"],e:` +
    +

    원본링크 : https://www.facebook.com/DoppioLover/posts/10225430970749092?__cft__[0]=AZVQVje3HpQ-XcR1aulTomjrKkwP3dgMkwtxvqSrRed0-yZn5vMd_fFoawk9FqeqSj7bIGYg4Ui1zUDYaE2anZDkndlmTgjhFCEnIkTD__lyGfjDt8Kf8od2Ayz3ZPT4PSo&__tn__=%2CO%2CP-R

    +
    `,r:{minutes:.52,words:156},y:"a",t:"키보드의 특수기호 영어 명칭"}}],["/06-etc/infomation/acronyms.html",{loader:()=>u(()=>import("./acronyms.html-CIiKRHMZ.js"),__vite__mapDeps([38,1])),meta:{d:1695042774e3,g:["acronyms","tip"],e:` +
    +

    Full name definition
    +List of informationtechnology initialisms

    +
    +

    A

    +
      +
    • ACL : Access Control List
    • +
    • AD : Active Directory
    • +
    • AES : Advanced Encryption Standard
    • +
    • AJAX : Asynchronous JavaScript and XML
    • +
    • API : Application Programming Interface
    • +
    • ARP : Address Resolution Protocol
    • +
    • AWS : Amazon Web Service
    • +
    • APM : Application Performance Monitoring(Management)
    • +
    `,r:{minutes:1.82,words:546},y:"a",t:"약어"}}],["/06-etc/mac/brew-cert-issue.html",{loader:()=>u(()=>import("./brew-cert-issue.html-yO1nxQHI.js"),__vite__mapDeps([39,1])),meta:{d:1640262179e3,g:["mac","homebrew","brew"],e:` +
      +
    • 현상 : brew 설치시 인증서 에러 발생
    • +
    +
    +

    https://apple.stackexchange.com/questions/393481/homebrew-cask-download-failure-ssl-certificate-problem-certificate-has-expired

    +
    `,r:{minutes:.66,words:198},y:"a",t:"homebrew install - certificate has expired"}}],["/06-etc/mac/libunistring-issue.html",{loader:()=>u(()=>import("./libunistring-issue.html-D9OVyvgy.js"),__vite__mapDeps([40,1])),meta:{d:1675205792e3,g:["mac","homebrew","brew","wget"],e:` +

    현상

    +

    macOS Ventura 업그레이드 후 wget 실행시 오류 발생

    +
    $ wget
    +dyld[4414]: Library not loaded: /usr/local/opt/libunistring/lib/libunistring.2.dylib
    +  Referenced from: <1ECBA17E-A426-310D-9902-EFF0D9E10532> /usr/local/Cellar/wget/1.21.3/bin/wget
    +  Reason: tried: '/usr/local/opt/libunistring/lib/libunistring.2.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/opt/libunistring/lib/libunistring.2.dylib' (no such file), '/usr/local/opt/libunistring/lib/libunistring.2.dylib' (no such file), '/usr/local/lib/libunistring.2.dylib' (no such file), '/usr/lib/libunistring.2.dylib' (no such file, not in dyld cache), '/usr/local/Cellar/libunistring/1.1/lib/libunistring.2.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/usr/local/Cellar/libunistring/1.1/lib/libunistring.2.dylib' (no such file), '/usr/local/Cellar/libunistring/1.1/lib/libunistring.2.dylib' (no such file), '/usr/local/lib/libunistring.2.dylib' (no such file), '/usr/lib/libunistring.2.dylib' (no such file, not in dyld cache)
    +[1]    4414 abort      wget
    +
    `,r:{minutes:.93,words:280},y:"a",t:"Library not loaded: libunistring.2.dylib"}}],["/06-etc/nodejs/node-sass.html",{loader:()=>u(()=>import("./node-sass.html-CEK1jTCc.js"),__vite__mapDeps([41,1])),meta:{d:1667618041e3,g:["arm","nodejs"],e:` +

    aarch64에서 vuepress 실행을 위해 테스트를 하던 도중 node-gyp와 node-sass에 대한 오류를 맞이하게 되었다.

    +

    node-sass의 경우 arm환경에 대한 빌드 릴리즈가 없는 관계로 npm install을 실행하면 다시 빌드를 하게되는데, 이때 node-sass를 빌드하는 과정에서 빌드 실패가 발생함

    +

    node-sass란?

    +

    node환경에서 sass는 css 코드로 변환해주는 스타일 전처리언어이다. c/c++로 되어있는 구성요소로 인해 빠른 빌드 속도를 제공한다.

    `,r:{minutes:.39,words:118},y:"a",t:"node-sass와 sass로의 전환"}}],["/01-Infrastructure/Linux/TroubleShooting/Oom_killer.html",{loader:()=>u(()=>import("./Oom_killer.html-Cah9Qu1H.js"),__vite__mapDeps([42,1])),meta:{d:1640764183e3,g:["linux","oom","oom_killer"],e:` +

    OOM Killer의 주요 업무는 다음 두 가지입니다.

    +
      +
    • 실행 중인 모든 프로세스를 살펴보며 각 프로세스의 메모리 사용량에 따라 OOM 점수를 산출합니다.
    • +
    • OS에서 메모리가 더 필요하면 점수가 가장 높은 프로세스를 종료시킵니다.
    • +
    +

    각 프로세스의 oom_score 관련 정보는 /proc/(pid) 디렉토리 하위에서 찾을 수 있습니다.

    +
      +
    • oom_adj (oom_adjust)
    • +
    • oom_score_adj
    • +
    • oom_score
    • +
    `,r:{minutes:.33,words:100},y:"a",t:"OOM Killer가 일하는 방식"}}],["/01-Infrastructure/Linux/TroubleShooting/SSH%20Too%20many%20authentication%20failures.html",{loader:()=>u(()=>import("./SSH Too many authentication failures.html-BcSkL8WN.js"),__vite__mapDeps([43,1])),meta:{d:1628085698e3,g:["linux","ssh"],e:` +

    직역하자면 너무많은 인증 실패로 인한 SSH 접속이 안된다. 는 메시지를 간혹 보게되는 경우가 있다.

    +
    $ ssh myserver
    +Received disconnect from 192.168.0.43 port 22:2: Too many authentication failures
    +Connection to 192.168.0.43 closed by remote host.
    +Connection to 192.168.0.43 closed.
    +
    `,r:{minutes:.39,words:117},y:"a",t:"SSH Too many authentication failures"}}],["/01-Infrastructure/Linux/TroubleShooting/docker_bridge_netstat.html",{loader:()=>u(()=>import("./docker_bridge_netstat.html-DPHuUA76.js"),__vite__mapDeps([44,1])),meta:{d:1639634821e3,g:["linux","docker","bridge","netstat"],e:` +
      +
    • 단순 netstat만으로 bridge모드로 기동된 docker의 port를 체크할 수 없다
    • +
    • 그래서 아래와 같은 절차가 필요하다.
    • +
    +

    먼저 찾으려는 컨테이너의 port를 확인한다. (nomad로 배포되어 있는 컨테이너임)

    +
    nomad alloc status d78d5b32
    +ID                  = d78d5b32-00c3-5468-284a-8c201058c53a
    +Eval ID             = c6c9a1d9
    +Name                = 08_grafana.08_grafana[0]
    +Node ID             = e11b7729
    +Node Name           = slave1
    +Job ID              = 08_grafana
    +Job Version         = 0
    +Client Status       = running
    +Client Description  = Tasks are running
    +Desired Status      = run
    +Desired Description = <none>
    +Created             = 18h42m ago
    +Modified            = 2h36m ago
    +
    +Allocation Addresses (mode = "bridge")
    +Label                   Dynamic  Address
    +*http                   yes      10.0.0.161:25546
    +*connect-proxy-grafana  yes      10.0.0.161:29382 -> 29382
    +
    `,r:{minutes:1.06,words:317},y:"a",t:"docker나 nomad를 이용하여 bridge모드로 기동된 컨테이너의 port 체크"}}],["/02-PrivatePlatform/Kubernetes/01-Information/Kubernetes_scheduler.html",{loader:()=>u(()=>import("./Kubernetes_scheduler.html-DMN8z5Cq.js"),__vite__mapDeps([45,1])),meta:{d:1695042774e3,g:["kubernetes","scheduler","알고리즘"],e:` +
      +
    • 원본: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-scheduling/scheduler_algorithm.md
    • +
    • 예약되지 않은 각 포드에 대해 Kubernetes 스케줄러는 규칙 집합에 따라 클러스터에서 노드를 찾으려고합니다. Kubernetes 스케줄러에 대한 일반적인 소개는 scheduler.md 에서 찾을 수 있습니다 . 이 문서에서는 포드의 노드를 선택하는 방법에 대한 알고리즘을 설명합니다. 포드의 대상 노드를 선택하기 전에 두 단계가 있습니다. 첫 번째 단계는 모든 노드를 필터링하고 두 번째 단계는 나머지 노드의 순위를 매겨 포드에 가장 적합한 것을 찾는 것입니다.
    • +
    `,r:{minutes:.65,words:196},y:"a",t:"kubernetes 스케쥴러 알고리즘"}}],["/02-PrivatePlatform/Kubernetes/02-Config/kubernetes_with_out_docker.html",{loader:()=>u(()=>import("./kubernetes_with_out_docker.html-DUkVpTlq.js"),__vite__mapDeps([46,1])),meta:{d:1695042774e3,g:["kubernetes","docker아님","containerd"],e:` +
      +
    • docker가 없어도 k8s를 올릴 수 있다!
    • +
    +
    # 먼저 설치하여 환경파일을 가져오고 원하는 버전을 설치한다.
    +sudo apt-get install containerd -y
    +
    +sudo mkdir -p /etc/containerd
    +
    +containerd config default | sudo tee /etc/containerd/config.toml
    +
    +sudo systemctl stop containerd
    +
    +curl -LO https://github.com/containerd/containerd/releases/download/v1.4.4/containerd-1.4.4-linux-amd64.tar.gz
    +
    +tar xvf containerd-1.4.4-linux-amd64.tar.gz
    +
    +rm containerd-1.4.4-linux-amd64.tar.gz
    +
    +sudo cp bin/* /usr/bin/
    +
    +sudo systemctl start containerd
    +
    +rm -rf bin
    +
    +sudo systemctl status containerd --lines 1
    +
    +# k8s 설치시작
    +curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
    +
    +sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
    +
    +sudo apt-get install kubeadm kubelet kubectl -y
    +
    +sudo apt-mark hold kubeadm kubelet kubectl containerd
    +
    +#echo 'net.bridge.bridge-nf-call-iptables = 1' | sudo tee -a /etc/sysctl.conf
    +
    +SOURCE_FILE="/etc/sysctl.conf"
    +LINE_INPUT="net.bridge.bridge-nf-call-iptables = 1"
    +
    +grep -qF "$LINE_INPUT" "$SOURCE_FILE"  || echo "$LINE_INPUT" | sudo tee -a "$SOURCE_FILE"
    +
    +sudo echo '1' | sudo tee /proc/sys/net/ipv4/ip_forward
    +
    +cat /proc/sys/net/ipv4/ip_forward
    +
    +sudo sysctl --system
    +
    +sudo modprobe overlay
    +sudo modprobe br_netfilter
    +
    +sudo swapoff -a
    +
    +sudo sed -ri '/\\sswap\\s/s/^#?/#/' /etc/fstab
    +
    +cat /etc/fstab
    +
    `,r:{minutes:.79,words:237},y:"a",t:"containerd를 런타임으로 사용한 Kubernetes 설치"}}],["/02-PrivatePlatform/Kubernetes/02-Config/vagrant_k8s.html",{loader:()=>u(()=>import("./vagrant_k8s.html-BzqLHCKE.js"),__vite__mapDeps([47,1])),meta:{d:1695042774e3,g:["kubernetes","vagrant","docker","install"],e:` +
    +
    +

    github : https://github.com/Great-Stone/vagrant-k8s

    +
    `,r:{minutes:1.78,words:535},y:"a",t:"Kubernetes, Vagrant로 로컬 환경 구성"}}],["/02-PrivatePlatform/Kubernetes/05-Kops/01-kops-on-aws.html",{loader:()=>u(()=>import("./01-kops-on-aws.html-ROAC0cUk.js"),__vite__mapDeps([48,1])),meta:{d:1695042774e3,g:["Kubernetes","Kops","EKS","PKOS"],e:` +
    +

    💡 본 글은 PKOS(Production Kubernetes Online Study) 2기 스터디의 일부로 작성된 내용입니다.
    +실제 Production Kubernetes 환경에서 활용 가능한 다양한 정보를 전달하기 위한 시리즈로 작성 예정입니다.

    +
    +

    1. 실습환경 사전준비

    +

    본 스터디는 AWS 환경에서 Kops(Kubernetes Operations)를 활용한 실습으로 진행할 예정입니다.

    +
    +

    📌 참고 : 필자는 개인적인 이유로 Route 53 계정과, kOps 클러스터 운영 계정을 나눠서 진행합니다.
    +하나의 계정에서 실습을 진행할 경우에는 사전 환경구성이 다를 수 있는 점 참고 부탁드립니다.

    +
    `,r:{minutes:3.52,words:1055},y:"a",t:"[PKOS] 1편 - AWS Kops 설치 및 기본 사용"}}],["/02-PrivatePlatform/Kubernetes/05-Kops/02-kops-network-storage.html",{loader:()=>u(()=>import("./02-kops-network-storage.html-CvfHSXZ8.js"),__vite__mapDeps([49,1])),meta:{d:1695042774e3,g:["Kubernetes","Kops","EKS","PKOS"],e:` +

    지난 1주차 스터디에이어 2주차 스터디를 진행하였습니다. 이번 스터디에서는 "쿠버네티스 네트워크" 및 "쿠버네티스 스토리지"를 중심으로 학습하였습니다.

    +
    +

    참고 :
    +원활한 실습을 위해 인스턴스 타입을 변경한 후 진행합니다.

    +
    +

    0. 사전준비

    +

    1) Kops 클러스터의 인스턴 그룹 변경

    +
    kops get ig
    +NAME			ROLE	MACHINETYPE	MIN	MAX	ZONES
    +master-ap-northeast-2a	Master	t3.medium	1	1	ap-northeast-2a
    +nodes-ap-northeast-2a	Node	t3.medium	1	1	ap-northeast-2a
    +nodes-ap-northeast-2c	Node	t3.medium	1	1	ap-northeast-2c
    +
    `,r:{minutes:7.23,words:2169},y:"a",t:"[PKOS] 2편 - 네트워크 & 스토리지"}}],["/02-PrivatePlatform/Kubernetes/06-EKS/01-eks-deploy.html",{loader:()=>u(()=>import("./01-eks-deploy.html-9Qn8CqOo.js"),__vite__mapDeps([50,1])),meta:{d:1695042774e3,g:["Kubernetes","EKS","PKOS"],e:` +

    이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

    +
    img
    img
    +

    필자는 기본적인 스터디내용을 이번 시리즈에 연재할 예정이며, 추가적으로 HashiCorp의 Consul, Vault 등을 샘플로 배포하며 연동하는 내용을 조금씩 다뤄볼 예정이다.

    `,r:{minutes:5.19,words:1556},y:"a",t:"AEWS 1주차 - Amzaon EKS 설치 및 기본 사용"}}],["/02-PrivatePlatform/Kubernetes/06-EKS/02-eks-networking.html",{loader:()=>u(()=>import("./02-eks-networking.html-C9T0s8D7.js"),__vite__mapDeps([51,1])),meta:{d:1695042774e3,g:["Kubernetes","EKS","PKOS"],e:` +

    이번에 연재할 스터디는 AWS EKS Workshop Study (=AEWS)이다. AWS에서 공식적으로 제공되는 다양한 HOL 기반의 Workshop과 가시다님의 팀에서 2차 가공한 컨텐츠를 기반으로 진행한다.

    +
    img
    img
    +

    0. 실습환경 준비

    `,r:{minutes:4.48,words:1344},y:"a",t:"AEWS 2주차 - Amzaon EKS Networking"}}],["/02-PrivatePlatform/Kubernetes/06-EKS/03-eks-storage.html",{loader:()=>u(()=>import("./03-eks-storage.html-Y0k0NC6t.js"),__vite__mapDeps([52,1])),meta:{d:1695042774e3,e:` +`,r:{minutes:.02,words:5},y:"a",t:"AEWS 3주차 - Amzaon EKS Storage"}}],["/04-HashiCorp/01-Packer/01-Information/HCP_Packer_Intro.html",{loader:()=>u(()=>import("./HCP_Packer_Intro.html-BtpDpHuz.js"),__vite__mapDeps([53,1])),meta:{d:1647827749e3,g:["Packer","HCP","Terraform"],e:` +

    HashiCorp의 제품은 설치형과 더불어 SaaS 모델로도 사용가능한 모델이 제공됩니다. 여기에는 지금까지 Terraform Cloud, HCP Vault, HCP Consul 이 제공되었습니다. HCP는 HashiCorp Cloud Platform의 약자 입니다.

    + +

    여기에 최근 HCP Packer가 공식적으로 GA(General Available)되었습니다. HashiCorp의 솔루션들에 대해서 우선 OSS(Open Source Software)로 떠올려 볼 수 있지만 기업을 위해 기능이 차별화된 설치형 엔터프라이즈와 더불어 클라우드형 서비스도 제공되고 있으며 향후 새로운 솔루션들이 추가될 전망입니다.

    `,r:{minutes:1.25,words:376},y:"a",t:"HCP Packer 소개"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/AlibabaCloud.html",{loader:()=>u(()=>import("./AlibabaCloud.html-Ddvy_KKt.js"),__vite__mapDeps([54,1])),meta:{d:1632808034e3,g:["Packer","Sample","Alibaba"],e:` +

    packer.pkr.hcl

    + +
    # packer build -force .
    +
    +locals {
    +  access_key = vault("/kv-v2/data/alicloud", "access_key")
    +  secret_key = vault("/kv-v2/data/alicloud", "secret_key")
    +}
    +
    +variable "region" {
    +  default     = "ap-southeast-1"
    +  description = "https://www.alibabacloud.com/help/doc-detail/40654.htm"
    +}
    +
    +source "alicloud-ecs" "basic-example" {
    +  access_key           = local.access_key
    +  secret_key           = local.secret_key
    +  region               = var.region
    +  image_name           = "ssh_otp_image_1_5"
    +  source_image         = "centos_7_9_x64_20G_alibase_20210623.vhd"
    +  ssh_username         = "root"
    +  instance_type        = "ecs.n1.tiny"
    +  io_optimized         = true
    +  internet_charge_type = "PayByTraffic"
    +  image_force_delete   = true
    +}
    +
    +build {
    +  sources = ["sources.alicloud-ecs.basic-example"]
    +
    +  provisioner "file" {
    +    source      = "./files/"
    +    destination = "/tmp"
    +  }
    +
    +# Vault OTP
    +  provisioner "shell" {
    +    inline = [
    +      "cp /tmp/sshd /etc/pam.d/sshd",
    +      "cp /tmp/sshd_config /etc/ssh/sshd_config",
    +      "mkdir -p /etc/vault.d",
    +      "cp /tmp/vault.hcl /etc/vault.d/vault.hcl",
    +      "cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper",
    +      "/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev",
    +      "sudo adduser test",
    +      "echo password | passwd --stdin test",
    +      "echo 'test ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers",
    +      "sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config"
    +    ]
    +  }
    +
    +# Apache
    +  provisioner "shell" {
    +    inline = [
    +      "sudo yum -y update",
    +      "sleep 15",
    +      "sudo yum -y update",
    +      "sudo yum -y install httpd",
    +      "sudo systemctl enable httpd",
    +      "sudo systemctl start httpd",
    +      "chmod +x /tmp/deploy_app.sh",
    +      "PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh",
    +      # "sudo firewall-cmd --zone=public --permanent --add-port=80/tcp",
    +      # "sudo firewall-cmd --reload",
    +    ]
    +  }
    +}
    +
    +variable "placeholder" {
    +  default     = "placekitten.com"
    +  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
    +}
    +
    `,r:{minutes:.96,words:289},y:"a",t:"Alibaba Cloud Packer Sample"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/Azure.html",{loader:()=>u(()=>import("./Azure.html-DPMhwAjI.js"),__vite__mapDeps([55,1])),meta:{d:1632808034e3,g:["Packer","Sample","Azure"],e:` +

    packer.pkr.hcl

    + +
    # packer init -upgrade .
    +# packer build -force .
    +
    +locals {
    +  client_id = vault("/kv/data/azure", "client_id")
    +  client_secret = vault("/kv/data/azure", "client_secret")
    +  tenant_id = vault("/kv/data/azure", "tenant_id")
    +  subscription_id = vault("/kv/data/azure", "subscription_id")
    +  resource_group_name = var.resource_name
    +  virtual_network_name = "kbid-d-krc-vnet-002"
    +  virtual_network_subnet_name  = "d-mgmt-snet-001"
    +  virtual_network_resource_group_name  = "kbid-d-krc-mgmt-rg"
    +  timestamp = formatdate("YYYYMMDD_hhmmss", timeadd(timestamp(), "9h")) #생성되는 이미지 이름을 time 기반으로 생성
    +}
    +
    +variable "placeholder" {
    +  default     = "placekitten.com"
    +  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
    +}
    +
    +# Basic example : https://www.packer.io/docs/builders/azure/arm#basic-example
    +# MS Guide : https://docs.microsoft.com/ko-kr/azure/virtual-machines/linux/build-image-with-packer
    +source "azure-arm" "basic-example" {
    +  client_id = local.client_id
    +  client_secret = local.client_secret
    +  subscription_id = local.subscription_id
    +  tenant_id = local.tenant_id
    +
    +  # shared_image_gallery {
    +  #   subscription = local.subscription_id
    +  #   resource_group = "myrg"
    +  #   gallery_name = "GalleryName"
    +  #   image_name = "gs_pkr_\${local.timestamp}"
    +  #   image_version = "1.0.0"
    +  # }
    +  managed_image_resource_group_name = local.resource_group_name
    +  managed_image_name = "${var.image_name}-${local.timestamp}"
    +
    +  os_type = "Linux"
    +  # az vm image list-publishers --location koreacentral --output table
    +  image_publisher = "RedHat"
    +  # az vm image list-offers --location koreacentral --publisher RedHat --output table
    +  image_offer = "RHEL"
    +  # az vm image list-skus --location koreacentral --publisher RedHat --offer RHEL --output table
    +  image_sku = "8_4"
    +
    +  azure_tags = {
    +    dept = "KBHC Terraform POC"
    +  }
    +  
    +  # az vm list-skus --location koreacentral --all --output table
    +  build_resource_group_name = local.resource_group_name
    +
    +  #########################################
    +  # 기존 생성되어있는 network 를 사용하기 위한 항목 #
    +  #########################################
    +  virtual_network_name = local.virtual_network_name
    +  virtual_network_subnet_name = local.virtual_network_subnet_name
    +  virtual_network_resource_group_name = local.virtual_network_resource_group_name
    +  
    +  # location = "koreacentral"
    +  vm_size = "Standard_A2_v2"
    +}
    +
    +build {
    +  sources = ["sources.azure-arm.basic-example"]
    +
    +  provisioner "file" {
    +    source      = "./files/"
    +    destination = "/tmp"
    +  }
    +
    +# Vault OTP
    +  provisioner "shell" {
    +    inline = [
    +      "sudo cp /tmp/sshd /etc/pam.d/sshd",
    +      "sudo cp /tmp/sshd_config /etc/ssh/sshd_config",
    +      "sudo mkdir -p /etc/vault.d",
    +      "sudo cp /tmp/vault.hcl /etc/vault.d/vault.hcl",
    +      "sudo cp /tmp/vault-ssh-helper /usr/bin/vault-ssh-helper",
    +      "echo \\"=== Vault_Check ===\\"",
    +      "curl http://10.0.9.10:8200",
    +      "/usr/bin/vault-ssh-helper -verify-only -config=/etc/vault.d/vault.hcl -dev",
    +      "echo \\"=== Add User ===\\"",
    +      "sudo adduser jboss",
    +      "echo password | sudo passwd --stdin jboss",
    +      "echo 'jboss ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /etc/sudoers",
    +      "echo \\"=== SELINUX DISABLE ===\\"",
    +      "sudo sed -ie 's/SELINUX=enforcing/SELINUX=disabled /g' /etc/selinux/config"
    +    ]
    +  }
    +
    +# Apache
    +  provisioner "shell" {
    +    inline = [
    +      "sudo yum -y update",
    +      "sleep 15",
    +      "sudo yum -y update",
    +      "sudo yum -y install httpd",
    +      "sudo systemctl enable httpd",
    +      "sudo systemctl start httpd",
    +      "chmod +x /tmp/deploy_app.sh",
    +      "sudo PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/deploy_app.sh",
    +      "sudo firewall-cmd --zone=public --permanent --add-port=80/tcp",
    +      "sudo firewall-cmd --reload",
    +    ]
    +  }
    +}
    +
    `,r:{minutes:1.41,words:424},y:"a",t:"Azure Packer Sample"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/GCP.html",{loader:()=>u(()=>import("./GCP.html-BxxzWfAX.js"),__vite__mapDeps([56,1])),meta:{d:1632809937e3,g:["Packer","Sample","GCP"],e:` +

    packer.pkr.hcl

    +
    variable "base_image" {
    +  default = "ubuntu-1804-bionic-v20210415"
    +}
    +variable "project" {
    +  default = "gs-test-282101"
    +}
    +variable "region" {
    +  default = "asia-northeast2"
    +}
    +variable "zone" {
    +  default = "asia-northeast2-a"
    +}
    +variable "image_name" {
    +  
    +}
    +variable "placeholder" {
    +  default     = "placekitten.com"
    +  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
    +}
    +
    +source "googlecompute" "basic-example" {
    +  project_id = var.project
    +  source_image = var.base_image
    +  ssh_username = "ubuntu"
    +  zone = var.zone
    +  disk_size = 10
    +  disk_type = "pd-ssd"
    +  image_name = var.image_name
    +}
    +
    +build {
    +  name = "packer"
    +  source "sources.googlecompute.basic-example" {
    +      name = "packer"
    +  }
    +
    +  provisioner "file"{
    +    source = "./files"
    +    destination = "/tmp/"
    +  }
    +
    +  provisioner "shell" {
    +    inline = [
    +      "sudo apt-get -y update",
    +      "sleep 15",
    +      "sudo apt-get -y update",
    +      "sudo apt-get -y install apache2",
    +      "sudo systemctl enable apache2",
    +      "sudo systemctl start apache2",
    +      "sudo chown -R ubuntu:ubuntu /var/www/html",
    +      "chmod +x /tmp/files/*.sh",
    +      "PLACEHOLDER=${var.placeholder} WIDTH=600 HEIGHT=800 PREFIX=gs /tmp/files/deploy_app.sh",
    +    ]
    +  }
    +}
    +
    `,r:{minutes:.49,words:148},y:"a",t:"Google Cloud Platform Packer Sample"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/aws-ubuntu.html",{loader:()=>u(()=>import("./aws-ubuntu.html-qEdr7imC.js"),__vite__mapDeps([57,1])),meta:{d:1639995661e3,g:["Packer","Sample","aws"],e:` +

    ubuntu.pkr.hcl

    +
    # packer init client.pkr.hcl
    +# packer build -force .
    +
    +variable "region" {
    +  default = "ap-northeast-2"
    +}
    +
    +variable "cni-version" {
    +  default = "1.0.1"
    +}
    +
    +packer {
    +  required_plugins {
    +    amazon = {
    +      version = ">= 0.0.2"
    +      source  = "github.com/hashicorp/amazon"
    +    }
    +  }
    +}
    +
    +source "amazon-ebs" "example" {
    +  ami_name      = "gs_demo_ubuntu_{{timestamp}}"
    +  instance_type = "t3.micro"
    +  region        = var.region
    +  source_ami_filter {
    +    filters = {
    +      name                = "ubuntu/images/*ubuntu-bionic-18.04-amd64-server-*"
    +      root-device-type    = "ebs"
    +      virtualization-type = "hvm"
    +    }
    +    most_recent = true
    +    owners      = ["099720109477"]
    +  }
    +  ssh_username = "ubuntu"
    +}
    +
    +build {
    +  sources = ["source.amazon-ebs.example"]
    +
    +  provisioner "file" {
    +    source      = "./file/"
    +    destination = "/tmp"
    +  }
    +
    +  provisioner "shell" {
    +    inline = [
    +      "set -x",
    +      "echo Connected via Consul/Nomad client at \\"${build.User}@${build.Host}:${build.Port}\\"",
    +      "sudo apt-get update",
    +      "sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release",
    +      "sudo apt-get update",
    +      "curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -",
    +      "sudo apt-add-repository \\"deb [arch=amd64] https://apt.releases.hashicorp.com bionic main\\"",
    +      "sudo apt-get update && sudo apt-get -y install consul nomad netcat nginx",
    +      "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -",
    +      "sudo add-apt-repository \\"deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable\\"",
    +      "sudo apt-get update",
    +      "sudo apt-get install -y docker-ce openjdk-11-jdk",
    +      "curl -sL -o cni-plugins.tgz https://github.com/containernetworking/plugins/releases/download/v${var.cni-version}/cni-plugins-linux-amd64-v${var.cni-version}.tgz",
    +      "sudo mkdir -p /opt/cni/bin",
    +      "sudo tar -C /opt/cni/bin -xzf cni-plugins.tgz",
    +    ]
    +  }
    +}
    +
    `,r:{minutes:.7,words:211},y:"a",t:"AWS Packer Sample - Ubuntu"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/aws-windows.html",{loader:()=>u(()=>import("./aws-windows.html-p-W261cy.js"),__vite__mapDeps([58,1])),meta:{d:1639995661e3,g:["Packer","Sample","aws"],e:` +
    +

    참고 : Build a Windows Image

    +
    +

    windows.pkr.hcl

    +
    variable "region" {
    +  default = "ap-northeast-2"
    +}
    +
    +variable "cni-version" {
    +  default = "1.0.1"
    +}
    +
    +locals {
    +  nomad_url  = "https://releases.hashicorp.com/nomad/1.2.3/nomad_1.2.3_windows_amd64.zip"
    +  consul_url = "https://releases.hashicorp.com/consul/1.11.1/consul_1.11.1_windows_amd64.zip"
    +  jre_url    = "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.13%2B8/OpenJDK11U-jre_x64_windows_hotspot_11.0.13_8.zip"
    +}
    +
    +packer {
    +  required_plugins {
    +    amazon = {
    +      version = ">= 0.0.2"
    +      source  = "github.com/hashicorp/amazon"
    +    }
    +  }
    +}
    +
    +source "amazon-ebs" "example" {
    +  ami_name      = "gs_demo_windows_{{timestamp}}"
    +  communicator  = "winrm"
    +  instance_type = "t2.micro"
    +  region        = var.region
    +  source_ami_filter {
    +    filters = {
    +      name                = "*Windows_Server-2019-English-Full-Base*"
    +      root-device-type    = "ebs"
    +      virtualization-type = "hvm"
    +    }
    +    most_recent = true
    +    owners      = ["amazon"]
    +  }
    +  user_data_file = "./bootstrap_win.txt"
    +  winrm_password = "SuperS3cr3t!!!!"
    +  winrm_username = "Administrator"
    +}
    +
    +build {
    +  sources = ["source.amazon-ebs.example"]
    +
    +  provisioner "powershell" {
    +    inline = [
    +      "New-Item \\"C:\\\\temp\\" -ItemType Directory",
    +    ]
    +  }
    +
    +  // provisioner "file" {
    +  //   source = "./file/"
    +  //   destination = "/tmp"
    +  // }
    +
    +  provisioner "powershell" {
    +    inline = [
    +      "New-Item \\"C:\\\\hashicorp\\\\jre\\\\\\" -ItemType Directory",
    +      "New-Item \\"C:\\\\hashicorp\\\\consul\\\\bin\\\\\\" -ItemType Directory",
    +      "New-Item \\"C:\\\\hashicorp\\\\consul\\\\data\\\\\\" -ItemType Directory",
    +      "New-Item \\"C:\\\\hashicorp\\\\consul\\\\conf\\\\\\" -ItemType Directory",
    +      "New-Item \\"C:\\\\hashicorp\\\\nomad\\\\bin\\\\\\" -ItemType Directory",
    +      "New-Item \\"C:\\\\hashicorp\\\\nomad\\\\data\\\\\\" -ItemType Directory",
    +      "New-Item \\"C:\\\\hashicorp\\\\nomad\\\\conf\\\\\\" -ItemType Directory",
    +      "Invoke-WebRequest -Uri ${local.jre_url} -OutFile $env:TEMP\\\\jre.zip",
    +      "Invoke-WebRequest -Uri ${local.consul_url} -OutFile $env:TEMP\\\\consul.zip",
    +      "Invoke-WebRequest -Uri ${local.nomad_url} -OutFile $env:TEMP\\\\nomad.zip",
    +      "Expand-Archive $env:TEMP\\\\jre.zip -DestinationPath C:\\\\hashicorp\\\\jre\\\\",
    +      "Expand-Archive $env:TEMP\\\\consul.zip -DestinationPath C:\\\\hashicorp\\\\consul\\\\bin\\\\",
    +      "Expand-Archive $env:TEMP\\\\nomad.zip -DestinationPath C:\\\\hashicorp\\\\nomad\\\\bin\\\\",
    +      "[Environment]::SetEnvironmentVariable(\\"Path\\", $env:Path + \\";C:\\\\hashicorp\\\\jre\\\\jdk-11.0.13+8-jre\\\\bin;C:\\\\hashicorp\\\\nomad\\\\bin;C:\\\\hashicorp\\\\consul\\\\bin\\", \\"Machine\\")",
    +      // "$old = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\\\\System\\\\CurrentControlSet\\\\Control\\\\Session Manager\\\\Environment' -Name path).path",
    +      // "$new = \\"$old;C:\\\\hashicorp\\\\jre\\\\jdk-11.0.13+8-jre\\\\bin;C:\\\\hashicorp\\\\nomad\\\\bin;C:\\\\hashicorp\\\\consul\\\\bin\\"",
    +      // "Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\\\\System\\\\CurrentControlSet\\\\Control\\\\Session Manager\\\\Environment' -Name path -Value $new",
    +    ]
    +  }
    +}
    +
    `,r:{minutes:1.84,words:551},y:"a",t:"AWS Packer Sample - Windows"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/nCloud.html",{loader:()=>u(()=>import("./nCloud.html-DSp6DFO8.js"),__vite__mapDeps([59,1])),meta:{d:1632809475e3,g:["Packer","Sample","NCP"],e:` +

    packer.pkr.hcl

    +
    packer {
    +  required_plugins {
    +    ncloud = {
    +      version = ">= 0.0.1"
    +      source  = "github.com/hashicorp/ncloud"
    +    }
    +  }
    +}
    +
    +source "ncloud" "example-linux" {
    +  access_key                = var.access_key
    +  secret_key                = var.secret_key
    +  server_image_product_code = "SPSW0LINUX000139"
    +  server_product_code       = "SPSVRGPUSSD00001"
    +  server_image_name         = var.image_name
    +  server_image_description  = "server image description"
    +  region                    = "Korea"
    +  communicator              = "ssh"
    +  ssh_username              = "root"
    +}
    +
    +build {
    +  sources = ["source.ncloud.example-linux"]
    +
    +  provisioner "file" {
    +    source = "jupyter.service"
    +    destination = "/etc/systemd/system/jupyter.service"
    +  }
    +
    +  provisioner "shell" {
    +    inline = [
    +      "yum clean all",
    +      "yum -y install epel-release",
    +      "yum -y install python3",
    +      "yum -y install python-pip",
    +      "pip3 install --upgrade pip",
    +      "adduser jupyter",
    +      "su - jupyter",
    +      "pip3 install --user jupyter jupyter",
    +      "systemctl enable jupyter",
    +      "systemctl start jupyter"
    +    ]
    +  }
    +}
    +
    +variable "access_key" {
    +  type    = string
    +}
    +
    +variable "secret_key" {
    +  type    = string
    +}
    +
    +variable "image_name" {
    +  type    = string
    +  default = "test"
    +}
    +
    `,r:{minutes:.36,words:108},y:"a",t:"Naver Cloud Platform Packer Sample"}}],["/04-HashiCorp/02-Vagrant/02-Config/multi-linux-sample.html",{loader:()=>u(()=>import("./multi-linux-sample.html-Kdb35Vof.js"),__vite__mapDeps([60,1])),meta:{d:162872854e4,g:["vagrant","virtualbox","linux"],e:` +
    # -*- mode: ruby -*-
    +# vi: set ft=ruby :
    +
    +# base image : https://app.vagrantup.com/bento
    +# Cluster IP have to set subnetting on private network subnet of VM
    +
    +$debianip = 50
    +$centip = 60
    +$suseip = 70
    +
    +debian_cluster = {
    +  "ubuntu" => { :image => "bento/ubuntu-18.04"}
    +}
    +cent_cluster = {
    +  "centos" => { :image => "centos/7"},
    +  "rocky" => { :image => "rockylinux/8"},
    +}
    +suse_cluster =  {
    +  "suse" => { :image => "opensuse/Tumbleweed.x86_64" }
    +}
    +
    +Vagrant.configure("2") do |config|
    +
    +  config.vm.synced_folder '.', '/vagrant', disabled: true
    +
    +  debian_cluster.each_with_index do |(hostname, info), i|
    +    config.vm.define hostname do |server|
    +      server.vm.box = info[:image]
    +      server.vm.hostname = hostname
    +      server.vm.network "private_network", name: "vboxnet1", ip: "172.28.128.#{i + $debianip}"
    +
    +      server.vm.provider "virtualbox" do |v|
    +        v.name = hostname
    +        v.gui = false
    +        v.memory = 1024
    +        v.cpus = 1
    +
    +        v.customize ["modifyvm", :id, "--vram", "9"]
    +      end # end provider
    +    end # end config
    +  end # end cluster foreach
    +
    +  suse_cluster.each_with_index do |(hostname, info), i|
    +    config.vm.define hostname do |server|
    +      server.vm.box = info[:image]
    +      server.vm.hostname = hostname
    +      server.vm.network "private_network", name: "vboxnet1", ip: "172.28.128.#{i + $suseip}"
    +      server.vm.provider "virtualbox" do |v|
    +        v.name = hostname
    +        v.gui = false
    +        v.memory = 1024
    +        v.cpus = 1
    +
    +        v.customize ["modifyvm", :id, "--vram", "9"]
    +      end # end provider
    +    end # end config
    +  end # end cluster foreach
    +
    +  cent_cluster.each_with_index do |(hostname, info), i|
    +    config.vm.define hostname do |server|
    +      server.vm.box = info[:image]
    +      server.vm.hostname = hostname
    +      server.vm.network "private_network", name: "vboxnet1", ip: "172.28.128.#{i + $centip}"
    +
    +      server.vm.provider "virtualbox" do |v|
    +        v.name = hostname
    +        v.gui = false
    +        v.memory = 1024
    +        v.cpus = 1
    +
    +        v.customize ["modifyvm", :id, "--vram", "9"]
    +      end # end provider
    +    end # end config
    +  end # end cluster foreach
    +  
    +end
    +
    +
    `,r:{minutes:.7,words:210},y:"a",t:"다양한 Linux 생성 샘플"}}],["/04-HashiCorp/02-Vagrant/04-TroubleShooting/hostonlynetworkissue.html",{loader:()=>u(()=>import("./hostonlynetworkissue.html-BPAGGOgU.js"),__vite__mapDeps([61,1])),meta:{d:1635125433e3,g:["vagrant","virtualbox"],e:` +

    https://discuss.hashicorp.com/t/vagrant-2-2-18-osx-11-6-cannot-create-private-network/30984/9
    +https://discuss.hashicorp.com/t/vagran-can-not-assign-ip-address-to-virtualbox-machine/30930

    `,r:{minutes:.34,words:101},y:"a",t:"Network : Code E_ACCESSDENIED (0x80070005)"}}],["/04-HashiCorp/03-Terraform/01-Information/00-introduction.html",{loader:()=>u(()=>import("./00-introduction.html-BslrSW0n.js"),__vite__mapDeps([62,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` + +

    1. Provision

    +

    프로비저닝과 관련하여 우리는 Day 0부터 Day 2까지의 여정이 있습니다.

    +
      +
    • Day 0에 요구사항을 분석하고 아키텍쳐를 설계하고 훈련을 합니다.
    • +
    • Day 1에 드디어 설계된 아키텍쳐를 구현하지요. 인프라, 네트워크, 서비스 구성 등등 말이죠.
    • +
    • Day 2는 이제 Day 1에서 구성된 요소를 유지하고 관리하고 모니터링하면서 더나은 아키텍쳐로 변경하거나 추가 서비스를 붙이는 반복적 작업을 합니다.
    • +
    `,r:{minutes:.69,words:207},y:"a",t:"Terraform 개념 소개"}}],["/04-HashiCorp/03-Terraform/01-Information/01-infrastructure_maturity.html",{loader:()=>u(()=>import("./01-infrastructure_maturity.html-CSXVrsyb.js"),__vite__mapDeps([63,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` + +

    이번에는 인프라의 변화와 적응이라는 제목으로 인프라의 성숙도와 관련한 이야기를 나누고자 합니다.

    +
    image-20200707110714298
    image-20200707110714298
    `,r:{minutes:.69,words:208},y:"a",t:"인프라의 변화와 적응"}}],["/04-HashiCorp/03-Terraform/01-Information/02-hcl.html",{loader:()=>u(()=>import("./02-hcl.html-DQHZhYcD.js"),__vite__mapDeps([64,1])),meta:{d:1640262e6,g:["terraform","usecase","IaC","HCL"],e:` +

    Terraform의 가장 주요한 기능으로 Infrastructure as Code 를 이야기 할 수 있습니다. 그리고 이를 지원하는 HCL에 대해 알아보고자 합니다.

    +`,r:{minutes:.92,words:275},y:"a",t:"HCL - HashiCorp Configuration Language"}}],["/04-HashiCorp/03-Terraform/01-Information/remoteruns.html",{loader:()=>u(()=>import("./remoteruns.html-Dd5GtFY-.js"),__vite__mapDeps([65,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` +

    Terraform의 Remote Runs이라는 기능에 대해 확인합니다.

    +

    Terraform Cloud와 Terraform Enterprise는 원격으로 트리거링 되어 동작하는 메커니즘을 제공하고 있습니다.

    +`,r:{minutes:.82,words:247},y:"a",t:"Remote Runs"}}],["/04-HashiCorp/03-Terraform/01-Information/remotestate.html",{loader:()=>u(()=>import("./remotestate.html-CRXBKn_z.js"),__vite__mapDeps([66,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` +

    Terraform을 수행하고나면 실행되고난 뒤의 상태가 저장됩니다. 로컬에서 OSS로 실행 했을 때의 terraform.tfstate 파일이 그것 입니다. 서로 다른 팀이 각자의 워크스페이스에서 작업하고 난뒤 각 상태 공유하면 변경된 내역에 따라 다음 작업을 이어갈 수 있습니다. Terraform은 Terraform Cloud, HashiCorp Consul, Amazon S3, Alibaba Cloud OSS 등에 상태 저장을 지원합니다.

    +`,r:{minutes:.6,words:179},y:"a",t:"Remote State"}}],["/04-HashiCorp/03-Terraform/01-Information/sentinel.html",{loader:()=>u(()=>import("./sentinel.html-BMW4Re1c.js"),__vite__mapDeps([67,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` +

    Terraform은 인프라의 코드화 측면에서 그 기능을 충실히 실현해줍니다. 하지만 팀과 조직에서는 단지 인프라의 코드적 관리와 더불어 다른 기능들이 필요하기 마련입니다. 그중 하나로 정책을 꼽을 수 있습니다.

    +`,r:{minutes:.47,words:140},y:"a",t:"Policy as Code : Sentinel"}}],["/04-HashiCorp/03-Terraform/01-Information/variables.html",{loader:()=>u(()=>import("./variables.html-CYnZOPtz.js"),__vite__mapDeps([68,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` +

    Terraform은 코드로 인프라를 관리하기위한 그 '코드'의 핵심 요소인 변수처리를 다양하게 지원합니다.

    + +

    Terraform에서는 다양한 변수와 작성된 변수를 관리하기 위한 메커니즘을 제공합니다. 가장 기본이되는 기능 중 하나이며 오픈소스와 엔터프라이즈 모두에서 사용가능합니다.

    `,r:{minutes:.97,words:290},y:"a",t:"Variables"}}],["/04-HashiCorp/03-Terraform/01-Information/workspace.html",{loader:()=>u(()=>import("./workspace.html-DmVbFuEE.js"),__vite__mapDeps([69,1])),meta:{d:1640262e6,g:["terraform","IaC"],e:` +

    Terraform의 워크스페이스(Workspace)는 일종의 원하는 인프라의 프로비저닝 단위로서, 하나의 state를 갖는 공간입니다. Terraform에서의 plan 혹은 apply 가 이뤄지는 단위이기도 합니다.

    +`,r:{minutes:.48,words:144},y:"a",t:"Workspace"}}],["/04-HashiCorp/03-Terraform/02-Config/TFEAdminPasswordReset.html",{loader:()=>u(()=>import("./TFEAdminPasswordReset.html-3khlDNqw.js"),__vite__mapDeps([70,1])),meta:{a:"jsp",d:1631870131e3,g:["terraform","admin","password"],e:` +

    Terraform Enterprise를 사용할 때, UI(https://TFE_SERVER) 상으로 접속할 수 없는 상황에서 비밀번호 변경이 필요한 경우, 아래와 같이 작업할 수 있다.

    +

    Admin 계정의 경우

    +

    다음과 같이 수정 가능.

    +
    # 이전 버전의 TFE
    +sudo docker exec -it ptfe_atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
    +## 수정 최신 버전의 TFE에서는 Container 이름이 변경됨 (2022.6.21)
    +sudo docker exec -it tfe-atlas /usr/bin/init.sh /app/scripts/wait-for-token -- bash -i -c 'cd /app && ./bin/rails c'
    +
    `,r:{minutes:.73,words:219},y:"a",t:"Terraform Enterprise 사용자 비밀번호 변경"}}],["/04-HashiCorp/03-Terraform/02-Config/terraform-cloud-agent-guide-custom.html",{loader:()=>u(()=>import("./terraform-cloud-agent-guide-custom.html-DJkAXNqy.js"),__vite__mapDeps([71,1])),meta:{d:1704702469e3,g:["Terraform"],e:` +

    Terraform Cloud Agent(Agent)는 Terraform Enterprise/Cloud(TFE/C)에서 사용가능한 사용자 정의 Terraform 실행 환경을 제공합니다. 사용자는 Agent를 사용하여 Terraform 실행을 위해 기본 제공되는 이미지 대신 커스텀 패키지가 설치된 별도 이미지를 사용할 수 있고, 이미지 실행 위치를 네트워크 환경에서 자체 호스팅 할 수 있습니다.

    +
    Monosnap Terraform Agent | onemodel 2024-01-08 14-40-18
    Monosnap Terraform Agent | onemodel 2024-01-08 14-40-18
    `,r:{minutes:1.74,words:522},y:"a",t:"Terraform Cloud Agent 가이드"}}],["/04-HashiCorp/03-Terraform/03-Sample/hashicat-azure.html",{loader:()=>u(()=>import("./hashicat-azure.html-D3ng3dBJ.js"),__vite__mapDeps([72,1])),meta:{d:168941454e4,g:["Terraform","Terraform on Azure","Azure","HashiCat","Terraform OSS","Terraform Cloud","Terraform Enterprise","Terraform 샘플","IaC"],e:` +
    +

    본 글은 HashiCorp의 공식 워크샵인 "Intro to Terraform on Azure" 내용을 발췌하여 작성한 글입니다. 참고

    +

    실습 원본 소스코드는 hashicat-azure 저장소에서 확인할 수 있습니다.

    +
    `,r:{minutes:4.18,words:1254},y:"a",t:"Intro to Terraform on Azure"}}],["/04-HashiCorp/03-Terraform/03-Sample/nomad-csi-sample.html",{loader:()=>u(()=>import("./nomad-csi-sample.html-1pYlYBZl.js"),__vite__mapDeps([73,1])),meta:{d:166454877e4,g:["Nomad","terrafom","CSI"],e:` +`,r:{minutes:1.51,words:452},y:"a",t:"Nomad CSI Sample"}}],["/04-HashiCorp/03-Terraform/04-TroubleShooting/NotAllowAdminUsername.html",{loader:()=>u(()=>import("./NotAllowAdminUsername.html-BUoNYKC5.js"),__vite__mapDeps([74,1])),meta:{d:1632444868e3,g:["Terraform","Azure"],e:` + + + + + + + + + + + +
    Log
    Error : compute.VirtualMachinesClient#CreateOrUpdate: Failure sending request: StatusCode=400 – Original Error: Code=“InvalidParameter” Message=“The Admin Username specified is not allowed.” Target="adminUsername"
    `,r:{minutes:.52,words:156},y:"a",t:"The Admin Username specified is not allowed."}}],["/04-HashiCorp/03-Terraform/04-TroubleShooting/StateRemove.html",{loader:()=>u(()=>import("./StateRemove.html-DwYONtji.js"),__vite__mapDeps([75,1])),meta:{d:1642083602e3,g:["Terraform","State"],e:` +
      +
    • +

      현상

      +
      ... googleapi: Error 400: Invalid request: Invalid request since instance is not running.
      +

      : Terraform을 통하지 않고 리소스가 삭제되어, 해당 리소스를 찾지 못하는 상황 발생

      +
    • +
    • +

      State 삭제

      +

      Local 환경의 terraform에 remote를 Terraform cloud로 지정

      +
      terraform {
      +  required_version = ">= 0.12"
      +  backend "remote" {
      +    hostname = "app.terraform.io"
      +    organization = "lguplus"
      +
      +    workspaces {
      +      name = "kids_library"
      +    }
      +  }
      +}
      +

      state 리스트 확인 terraform state list

      +
      my-workspace > terraform state list
      +random_pet.sql
      +module.Cluster_GKE.google_container_cluster.k8sexample
      +module.Cluster_GKE.google_container_node_pool.pool_1
      +module.Cluster_GKE.google_container_node_pool.pool_2
      +module.gcs_buckets.google_storage_bucket.buckets[0]
      +module.sql-db.google_sql_database.default
      +module.sql-db.google_sql_database_instance.default
      +module.sql-db.google_sql_user.default
      +module.sql-db.null_resource.module_depends_on
      +module.sql-db.random_id.user-password
      +module.network.module.routes.google_compute_route.route["egress-internet"]
      +module.network.module.subnets.google_compute_subnetwork.subnetwork["asia-northeast3/fc-kidslib-stg-subnet-1"]
      +module.network.module.vpc.google_compute_network.network
      +

      존재하지 않는 resource를 삭제 terraform state rm [resource_name]

      +
      my-workspace > terraform state rm module.sql-db
      +Removed module.sql-db.google_sql_database.default
      +Removed module.sql-db.google_sql_database_instance.default
      +Removed module.sql-db.google_sql_user.default
      +Removed module.sql-db.null_resource.module_depends_on
      +Removed module.sql-db.random_id.user-password
      +Successfully removed 5 resource instance(s).
      +
    • +
    `,r:{minutes:.39,words:116},y:"a",t:"State rm"}}],["/04-HashiCorp/03-Terraform/04-TroubleShooting/TFE_v202111-1(582)_Issue.html",{loader:()=>u(()=>import("./TFE_v202111-1(582)_Issue.html-BxHCp0t5.js"),__vite__mapDeps([76,1])),meta:{d:1640238674e3,g:["Terraform","Enterprise"],e:` +
    +

    v202111-1 (582) 버전 이상으로 설치, 또는 업그레이드 시 발생하는 이슈

    +
    +

    현상

    +
    error_console
    + + + + + + + + + + + +
    Nginx access Log
    2021/12/17 02:58:31 [error] 10#10: *913 connect(0mfailed (111: Connection refused) while connecting to upstream, client: 10.10.10.100, server:tfe.mydomain.com, reguest: "GET / HTTP/1.1", upstream: "http://172.11.0.1:9292/", host: "tfe.mydomain.com"
    `,r:{minutes:.39,words:116},y:"a",t:"TFE Release v202111-1 (582) Issue"}}],["/04-HashiCorp/03-Terraform/04-TroubleShooting/error-state-snapshot-was-created-by-terraform-version.html",{loader:()=>u(()=>import("./error-state-snapshot-was-created-by-terraform-version.html-C61Znmv0.js"),__vite__mapDeps([77,1])),meta:{d:1695176473e3,g:["Terraform","Azure"],e:` + + + + + + + + + + + +
    Log
    Error: state snapshot was created by Terraform v0.13.2, which is newer than current v0.12.26; upgrade to Terraform v0.13.2 or greater to work with this state
    +
      +
    • 버전관련 에러 메시지에 대해 몇가지 테스트 해본 결과, 상위버전으로 Terraform State가 생성 된 이후 하위 버전으로 Refresh/Plan/Apply 를 수행하는 경우에 발생하는 것으로 확인
    • +
    • terraform_remote_state 는 버전에 관계 없이 워크스페이스 간에 output을 읽어올 수 있음을 확인
    • +
    `,r:{minutes:.4,words:121},y:"a",t:"Error: state snapshot was created by Terraform vX.Y.Z"}}],["/04-HashiCorp/03-Terraform/04-TroubleShooting/re-install.html",{loader:()=>u(()=>import("./re-install.html-DTxZAjyz.js"),__vite__mapDeps([78,1])),meta:{a:"jsp",d:1655861356e3,g:["Terraform","Enterprise","TFE"],e:` +
    +

    관련 Knowledge Base Article : https://support.hashicorp.com/hc/en-us/articles/4409044739859-Container-ptfe-base-startup-failed

    +
    `,r:{minutes:.16,words:49},y:"a",t:"TFE 재설치 주의사항"}}],["/04-HashiCorp/03-Terraform/05-Airgap/ProviderBundling.html",{loader:()=>u(()=>import("./ProviderBundling.html-CNwkfDLV.js"),__vite__mapDeps([79,1])),meta:{d:165041363e4,g:["terraform","provider"],e:` +
    +

    https://github.com/hashicorp/terraform/tree/main/tools/terraform-bundle
    +Terraform Enterprise에서 동작하는 기능입니다.

    +
    +

    Airgap 환경에서 사용할 특정 버전의 Terraform과 여러 제공자 플러그인을 모두 포함하는 zip 파일 인 "번들 아카이브"를 생성하는 툴을 사용합니다. 일반적으로 Terraform init을 통해 특정 구성 작업에 필요한 플러그인을 다운로드하고 설치하지만 Airgap 환경에서는 공식 플러그인 저장소에 액세스 할 수 없는 경우가 발생합니다. Bundle 툴을 사용하여 Terraform 버전과 선택한 공급자를 모두 설치하기 위해 대상 시스템에 압축을 풀 수있는 zip 파일이 생성되므로 즉석 플러그인 설치가 필요하지 않습니다.

    `,r:{minutes:.85,words:256},y:"a",t:"Terraform Provider - 번들링"}}],["/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalFilesystem.html",{loader:()=>u(()=>import("./ProviderLocalFilesystem.html-XUIaiwWB.js"),__vite__mapDeps([80,1])),meta:{d:165041363e4,g:["terraform","provider"],e:` +
    +

    https://www.terraform.io/docs/cli/config/config-file.html#implied-local-mirror-directories
    +https://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providers

    +
    `,r:{minutes:1.34,words:401},y:"a",t:"Terraform Provider - 로컬 디렉토리"}}],["/04-HashiCorp/03-Terraform/05-Airgap/ProviderLocalMirroring.html",{loader:()=>u(()=>import("./ProviderLocalMirroring.html-C7POxPIe.js"),__vite__mapDeps([81,1])),meta:{d:165041363e4,g:["terraform","provider"],e:` +
    +

    https://www.terraform.io/docs/cli/config/config-file.html#provider_installation

    +
    +

    Terraform CLI를 사용할 때, 기본적으로 코드 상에서 사용하는 플러그인은 registry.terraform.io에서 다운로드 받게 되어 있습니다.

    `,r:{minutes:.24,words:72},y:"a",t:"Terraform Provider - 로컬 미러링"}}],["/04-HashiCorp/04-Consul/01-Information/Consul%20Enterprise%20Feature.html",{loader:()=>u(()=>import("./Consul Enterprise Feature.html-BQm8HMfI.js"),__vite__mapDeps([82,1])),meta:{d:1628557352e3,g:["Consul","Enterprise"],e:` +
      +
    • Enterprise Global Visibility & Scale +
        +
      • Network Segments
      • +
      • Advanced Federation
      • +
      • Redendancy Zones
      • +
      • Enganced Read Scalability
      • +
      +
    • +
    • Governance & Policy +
        +
      • Namespaces
      • +
      • Single Sign On
      • +
      • Audit Logging
      • +
      • Sentinel
      • +
      +
    • +
    `,r:{minutes:.5,words:151},y:"a",t:"Consul Enterprise Feature"}}],["/04-HashiCorp/04-Consul/01-Information/consul-sizing.html",{loader:()=>u(()=>import("./consul-sizing.html-BwUw-uKK.js"),__vite__mapDeps([83,1])),meta:{d:1642218029e3,g:["consul","sizing"],e:` +
    +

    https://learn.hashicorp.com/tutorials/consul/reference-architecture

    +

    Consul은 Server/Client 구조로 구성되며, Client의 경우 자원사용량이 매우 미미하므로 자원산정은 Server를 기준으로 산정

    +
    `,r:{minutes:.46,words:137},y:"a",t:"Consul Sizing"}}],["/04-HashiCorp/04-Consul/01-Information/port-info.html",{loader:()=>u(()=>import("./port-info.html-BSA3VqKM.js"),__vite__mapDeps([84,1])),meta:{d:164023929e4,g:["consul","port","requirement"],e:` +
    +

    https://www.consul.io/docs/install/ports

    +
    +

    Consul 포트

    +
    arc
    +

    Port Table

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    UseDefault Ports
    DNS (TCP and UDP)8600
    HTTP (TCP Only)8500
    HTTPS (TCP Only)disabled (8501)*
    gRPC (TCP Only)disabled (8502)*
    LAN Serf (TCP and UDP)8301
    Wan Serf (TCP and UDP)8302
    server (TCP Only)8300
    Sidecar Proxy Min: 자동으로 할당된 사이드카 서비스 등록에 사용할 포함 최소 포트 번호21000
    Sidecar Proxy Max: 자동으로 할당된 사이드카 서비스 등록에 사용할 포괄적인 최대 포트 번호21255
    `,r:{minutes:.31,words:92},y:"a",t:"Consul Port"}}],["/04-HashiCorp/04-Consul/02-Configuration/ForwardDns.html",{loader:()=>u(()=>import("./ForwardDns.html-w11anbby.js"),__vite__mapDeps([85,1])),meta:{d:1642495573e3,g:["Consul","Enterprise","Configuration","ForwardDns"],e:` +

    Consul dns를 local에서도 사용해야 할 경우에는 dns forward를 해줘야한다. 아래는 ubuntu 환경에서 진행하였음

    +

    설정 명령어

    +
    #systemd-resolved 설정파일 추가 및 변경
    +mkdir -p /etc/systemd/resolved.conf.d
    +(
    +cat <<-EOF
    +[Resolve]
    +DNS=127.0.0.1
    +DNSSEC=false
    +Domains=~consul
    +EOF
    +) | sudo tee /etc/systemd/resolved.conf.d/consul.conf
    +(
    +cat <<-EOF
    +nameserver 127.0.0.1
    +options edns0 trust-ad
    +EOF
    +) | sudo tee /etc/resolv.conf
    +#iptables에 consul dns port 추가
    +iptables --table nat --append OUTPUT --destination localhost --protocol udp --match udp --dport 53 --jump REDIRECT --to-ports 8600
    +iptables --table nat --append OUTPUT --destination localhost --protocol tcp --match tcp --dport 53 --jump REDIRECT --to-ports 8600
    +#service 재시작
    +systemctl restart systemd-resolved
    +
    `,r:{minutes:.41,words:124},y:"a",t:"ForwardDns"}}],["/04-HashiCorp/04-Consul/02-Configuration/acl-sample.html",{loader:()=>u(()=>import("./acl-sample.html-CK60trmj.js"),__vite__mapDeps([86,1])),meta:{d:1648777606e3,g:["Consul","Acl","Policy"],e:`

    Consul ACL Policy sample

    +

    Consul ACL을 활성화 할 경우 default를 deny로 할 지 allow를 할 지 정할 수 있다.
    +deny로 할 경우에는 하나하나 policy로 tokne을 만들어서 사용해야 한다.

    +

    Consul이 Vault의 Storage로 되어야 할 경우

    +
    key_prefix "vault/" {
    +  policy = "write"
    +}
    +service "vault" {
    +   policy = "write"
    +}
    +agent_prefix "" {
    +   policy = "read"
    +}
    +session_prefix "" {
    +   policy = "write"
    +}
    +
    `,r:{minutes:.23,words:69},y:"a",t:""}}],["/04-HashiCorp/04-Consul/02-Configuration/client.html",{loader:()=>u(()=>import("./client.html-DFcgtSZo.js"),__vite__mapDeps([87,1])),meta:{d:1629519876e3,g:["Consul","Enterprise","Configuration","Client"],e:` +
    +

    +

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 Client설정 파일입니다.
    +네트워크는 프라이빗(온프레이머스) 환경입니다.

    +
    +
    #consul client 설정
    +server = false
    +
    +acl = {
    +  enabled = true
    +  default_policy = "deny"
    +  enable_token_persistence = true
    +  tokens = {
    +    agent = "f820514a-5215-e741-fcb3-c00857405230"
    +  }
    +}
    +
    +license_path = "/opt/license/consul.license"
    +
    +retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
    +
    +rejoin_after_leave = true
    +
    +
    +#tls 설정
    +ca_file = "/opt/ssl/consul/consul-agent-ca.pem"
    +auto_encrypt = {
    +  tls = true
    +}
    +
    +verify_incoming = false
    +verify_outgoing = true
    +verify_server_hostname = true
    +
    `,r:{minutes:.46,words:138},y:"a",t:"Consul 클라이언트 설정"}}],["/04-HashiCorp/04-Consul/02-Configuration/common.html",{loader:()=>u(()=>import("./common.html-DqrMHNmB.js"),__vite__mapDeps([88,1])),meta:{d:1629519254e3,g:["Consul","Enterprise","Configuration","Common"],e:` +
    +

    +

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server, client의 공통설정 파일입니다.
    +저는 agent.hcl파일안에 다 넣고 실행하지만 나눠서 추후에는 기능별로 나눠서 사용할 예정입니다.

    +
    +
    #node name에는 _금지
    +#node_name
    + 
    +client_addr = "0.0.0.0"
    +bind_addr = "{{ GetInterfaceIP \`ens192\` }}"
    +advertise_addr = "{{ GetInterfaceIP \`ens224\` }}"
    + 
    +#ipv4, ipv6를 나눠서 설정할 수 있음.
    +#advertise_addr_ipv4
    +#advertise_addr_ipv6
    + 
    +ports {
    +  #http = 8500
    +  http = -1
    +  dns = 8600
    +  #https = -1
    +  https = 8500
    +  serf_lan = 8301
    +  grpc = 8502
    +  server = 8300
    +}
    + 
    +#gossip ip 지정
    +#serf_lan
    +#gossip 대역대 지정
    +#serf_lan_allowed_cidrs
    + 
    +#사용자 감사, 사용자가 consul에서 사용한 행동을 기록
    +#audit {
    +#  enabled = true
    +#  sink "My sink" {
    +#    type   = "file"
    +#    format = "json"
    +#    path   = "data/audit/audit.json"
    +#    #consul의 감사작성방법 규칙, 현재는 best-effort만지원
    +#    delivery_guarantee = "best-effort"
    +#    rotate_duration = "24h"
    +#    rotate_max_files = 15
    +#    rotate_bytes = 25165824
    +#  }
    +#}
    + 
    +#consul 서버관리 설정 변경
    +#autopoilt {
    +#    #새로운 서버가 클러스터에 추가될 때 죽은 서버 자동제거
    +#    cleanup_dead_servers = ture
    +#
    +#    last_contact_threshold = 200ms
    +#    #최소 quorm 수 지정
    +#    min_quorum = ni
    +#    #클러스터에 서버가 추가될 시 안정상태로 되어야 하는 최소 시간
    +#    server_stabilization_time = 10s
    +#}
    + 
    +#동시에 처리할 수 있는 인증서 서명 요청 제한
    +#csr_max_concurrent = 0
    +#서버가 수락할 인증서 서명 요청(CSR)의 최대 수에 대한 속도 제한을 설정
    +#csr_max_per_second = 50
    +#클러스터에서 이전 루트 인증서를 교체할 때 사용
    +#leaf_cert_ttl = 72h
    +#CA 키 생성 타입
    +#private_key_type = ec
    +#CA 키 생성될 길이
    +#private_key_bits = 256
    + 
    +#서버에서만 client를 join할 수 있게 함
    +#disable remote exec
    + 
    +#enable syslog = true
    +log_level = "DEBUG"
    +data_dir = "/var/log/consul/consul"
    +log_file = "/var/log/consul/consul.log"
    +log_rotate_duration = "24h"
    +log_rotate_bytes = 104857600
    +log_rotate_max_files = 100
    + 
    +license_path = "/opt/license/consul.license"
    + 
    +acl {
    +  enabled = true
    +  default_policy = "allow"
    +  enable_token_persistence = true
    + 
    +  #acl policy ttl, 줄이면 새로고침 빈도 상승, 성능에 영향을 미칠 수 있음
    +  #policy_ttl = 30s
    +  #acl role ttl, 줄이면 새로고침 빈도 상승, 성능에 영향을 미칠 수 있음
    +  #role_ttl = 30s
    +}
    + 
    +connect {
    +  enabled = true
    +  #vault 연동 옵션
    +  #ca_provider
    +}
    + 
    +dns_config {
    +  allow_stale = true,
    +  max_stale = "87600h"
    +}
    + 
    +#block_endpoints할성화시 restapi 차단
    +#http_config {
    +#    block_endpoints = false
    +#}
    + 
    +#segments
    + 
    +rpc {
    +  enable_streaming = true
    +}
    + 
    +encrypt = "7VY2fVm0p6vJUYNS/oex/mr2e59dy4AaGMefTKtUGi0="
    +encrypt_verify_incoming = false
    +encrypt_verify_outgoing = false
    +
    `,r:{minutes:.56,words:168},y:"a",t:"Consul 공통 설정"}}],["/04-HashiCorp/04-Consul/02-Configuration/server.html",{loader:()=>u(()=>import("./server.html-BnjH4AJE.js"),__vite__mapDeps([89,1])),meta:{d:1629519523e3,g:["Consul","Enterprise","Configuration","Server"],e:` +
    +

    +

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server설정 파일입니다.
    +네트워크는 프라이빗(온프레이머스) 환경입니다.

    +
    +
    #consul server 설정
    +server = true
    +ui_config {
    +  enabled = true
    +}
    +bootstrap_expect = 3
    +  
    +license_path = "/opt/license/consul.license"
    + 
    +retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
    + 
    +performance {
    +  raft_multiplier = 1
    +}
    + 
    +#raft protocal 버전, consul 업데이트 시 1씩 증가
    +raft_protocol = 3
    + 
    +#node가 완전히 삭제되는 시간
    +reconnect_timeout = "72h"
    + 
    +raft_snapshot_interval = "5s"
    + 
    +#해당 서버를 non-voting server로 지정
    +#read_replica = false
    + 
    +limits {
    +  http_max_conns_per_client = 200
    +  rpc_handshake_timeout = "5s"
    +}
    + 
    +key_file = "/opt/ssl/consul/dc1-server-consul-0-key.pem"
    +cert_file = "/opt/ssl/consul/dc1-server-consul-0.pem"
    +ca_file = "/opt/ssl/consul/consul-agent-ca.pem"
    +auto_encrypt {
    +  allow_tls = true
    +}
    + 
    +verify_incoming = false,
    +verify_incoming_rpc = true
    +verify_outgoing = true
    +verify_server_hostname = false
    +
    `,r:{minutes:.59,words:177},y:"a",t:"Consul 서버 설정"}}],["/04-HashiCorp/04-Consul/03-UseCase/Consul%20Enterprise%20Feature.html",{loader:()=>u(()=>import("./Consul Enterprise Feature.html-I88Lp2Lj.js"),__vite__mapDeps([90,1])),meta:{d:1628557352e3,g:["Consul","Hybrid","Kubetenetes","k8s","VM"],e:` +
    +

    이 문서에서는 Consul을 사용하여 상이한 두 Consul로 구성된 클러스터(마스터가 별개)의 서비스를 연계하는 방법을 설명합니다.

    +
    +

    1. 개요

    +

    1.1 아키텍처

    +

    네트워크 영역이 분리되어있는 두 환경의 애플리케이션 서비스들을 Service Mesh로 구성하는 방법을 알아 봅니다. 이번 구성 예에서는 Kubernetes와 Baremetal(BM)이나 VirtualMachine(VM)에 Consul Cluster(Datacenter)를 구성하고 각 환경의 애플리케이션 서비스를 Mesh Gateway로 연계합니다.

    `,r:{minutes:8.05,words:2415},y:"a",t:"Consul Mesh Gateway - K8S x BMs/VMs"}}],["/04-HashiCorp/04-Consul/03-UseCase/Consul%20Health%20Check.html",{loader:()=>u(()=>import("./Consul Health Check.html-DGZ9Ux0W.js"),__vite__mapDeps([91,1])),meta:{a:"euimokna",d:1629704782e3,g:["Consul"],e:` +
    +

    https://www.consul.io/docs/discovery/services
    +https://learn.hashicorp.com/tutorials/consul/service-registration-health-checks?in=consul/developer-discovery#tuning-scripts-to-be-compatible-with-consul

    +
    `,r:{minutes:.23,words:70},y:"a",t:"Consul Health Check on VMs"}}],["/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Enterprise%20Feature.html",{loader:()=>u(()=>import("./Consul Enterprise Feature.html-CWApFHx1.js"),__vite__mapDeps([92,1])),meta:{d:1628557352e3,g:["Consul"],e:` +
    +

    https://support.hashicorp.com/hc/en-us/articles/360058026733-Identifying-and-Recovering-from-a-Consul-Split-Brain

    +
    `,r:{minutes:.65,words:194},y:"a",t:"Identifying consul split-brain"}}],["/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Install.html",{loader:()=>u(()=>import("./Consul Install.html-CqnmzIZZ.js"),__vite__mapDeps([93,1])),meta:{d:1645588712e3,g:["Consul","install"],e:` +

    AmazonLinux 환경에서 하기와 같은 명령어로 consul 설치 후 systemd 를 통한 Consul 시작시 오류 발생

    +
    sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
    +sudo yum -y install consul
    +
    `,r:{minutes:.22,words:67},y:"a",t:"Consul yum install issue"}}],["/04-HashiCorp/04-Consul/04-TroubleShooting/Consul%20Sidecar%20Inject%20not%20working%20on%20k8s.html",{loader:()=>u(()=>import("./Consul Sidecar Inject not working on k8s.html-CDhmBQ6y.js"),__vite__mapDeps([94,1])),meta:{d:1628557352e3,g:["Consul","ServiceMesh","SideCar","Kubernetes","K8S"],e:` +
    +

    Consul Version : 1.9.x
    +Helm Chart : 0.30.0

    +
    +

    Consul을 쿠버네티스 상에 구성하게 되면 annotation 구성만으로도 쉽게 Sidecar를 애플리케이션과 함께 배포 가능하다.

    +

    참고 : Controlling Injection Via Annotation

    `,r:{minutes:.83,words:250},y:"a",t:"Consul Sidecar Inject not working on K8S"}}],["/04-HashiCorp/04-Consul/04-TroubleShooting/connection-termination.html",{loader:()=>u(()=>import("./connection-termination.html-DgTeN3Bf.js"),__vite__mapDeps([95,1])),meta:{d:1649167505e3,g:["Consul","ServiceMesh","SideCar","Kubernetes","K8S"],e:` +
    + +
    `,r:{minutes:.42,words:126},y:"a",t:"Connection termination"}}],["/04-HashiCorp/04-Consul/05-Template_Sample/kv-sample.html",{loader:()=>u(()=>import("./kv-sample.html-iPAR6SxB.js"),__vite__mapDeps([96,1])),meta:{d:1634871035e3,g:["Consul","Consul Template"],e:` +

    참고 : https://learn.hashicorp.com/tutorials/consul/consul-template

    +

    템플릿 파일 변환 하기

    +

    템플릿 파일 작성

    +
      +
    • 대상 kv : apache/version
    • +
    +
    # apache_install.sh.ctmpl
    +#!/bin/bash
    +sudo apt-get remove -y apache2
    +sudo apt-get install -y apache2={{ key "/apache/version" }}
    +
    `,r:{minutes:.36,words:109},y:"a",t:"KV Sample"}}],["/04-HashiCorp/04-Consul/05-Template_Sample/nginx.html",{loader:()=>u(()=>import("./nginx.html-B52ZmRRf.js"),__vite__mapDeps([97,1])),meta:{d:1634871035e3,g:["Consul","Consul Template","NGINX"],e:` +

    참고 : https://learn.hashicorp.com/tutorials/consul/load-balancing-nginx

    +

    템플릿 파일 변환 하기

    +

    템플릿 파일 작성

    +
      +
    • 대상 서비스 : nginx-backend
    • +
    +
    # nginx.conf.ctmpl
    +upstream backend {
    +  {{- range service "nginx-backend" }}
    +  server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
    +  {{else}}server 127.0.0.1:65535; # force a 502
    +  {{- end}}
    +}
    +
    +server {
    +  listen 80 default_server;
    +
    +  location /stub_status {
    +    stub_status;
    +  }
    +
    +  location / {
    +    proxy_pass http://backend;
    +  }
    +}
    +
    `,r:{minutes:.36,words:108},y:"a",t:"NGINX Sample"}}],["/04-HashiCorp/05-Boundary/01-Install/OnConsulNomad.html",{loader:()=>u(()=>import("./OnConsulNomad.html-PufX9nh_.js"),__vite__mapDeps([98,1])),meta:{d:1634219407e3,g:["Boundary","Install"],e:` +

    1. Nomad namespace create

    +
    nomad namespace apply -description "Boundary" boundary
    +

    2. Postgresql setup

    `,r:{minutes:2.29,words:688},y:"a",t:"Boundary Install on Consul-Nomad"}}],["/04-HashiCorp/05-Boundary/01-Install/OnNomad-devmode.html",{loader:()=>u(()=>import("./OnNomad-devmode.html-DozOQZMV.js"),__vite__mapDeps([99,1])),meta:{d:1653031029e3,g:["Boundary","Install"],e:` +

    1. Job sample

    +
    locals {
    +  version = "0.8.1"
    +  private_ip = "192.168.0.27"
    +  public_ip = "11.129.13.30"
    +}
    +
    +job "boundary-dev" {
    +  type = "service"
    +  datacenters = ["home"]
    +  namespace = "boundary"
    +
    +  constraint {
    +    attribute = "${attr.os.name}"
    +    value     = "raspbian"
    +  }
    +
    +  group "dev" {
    +    count = 1
    +
    +    ephemeral_disk { sticky  = true }
    +
    +    network {
    +      mode = "host"
    +      port "api" {
    +        static = 9200
    +        to = 9200
    +      }
    +      port "cluster" {
    +        static = 9201
    +        to = 9201
    +      }
    +      port "worker" {
    +        static = 9202
    +        to = 9202
    +      }
    +    }
    +
    +    task "dev" {
    +      driver = "raw_exec"
    +
    +      env {
    +        BOUNDARY_DEV_CONTROLLER_API_LISTEN_ADDRESS = local.private_ip
    +        BOUNDARY_DEV_CONTROLLER_CLUSTER_LISTEN_ADDRESS = "0.0.0.0"
    +        BOUNDARY_DEV_WORKER_PUBLIC_ADDRESS = local.public_ip
    +        BOUNDARY_DEV_WORKER_PROXY_LISTEN_ADDRESS = local.private_ip
    +        BOUNDARY_DEV_PASSWORD = "password"
    +      }
    +
    +      // artifact {
    +      //   source = "https://releases.hashicorp.com/boundary/\${local.version}/boundary_\${local.version}_linux_arm.zip"
    +      // }
    +
    +      config {
    +        command = "boundary"
    +        args = ["dev"]
    +      }
    +
    +      resources {
    +        cpu    = 500
    +        memory = 500
    +      }
    +
    +      service {
    +        name = "boundary"
    +        tags = ["cluster"]
    +
    +        port = "cluster"
    +
    +        check {
    +          type  = "tcp"
    +          interval = "10s"
    +          timeout  = "2s"
    +          port  = "api"
    +        }
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.44,words:132},y:"a",t:"Boundary Run Dev Mode on Nomad Job"}}],["/04-HashiCorp/05-Boundary/02-Config/BoundaryTerraformSample01.html",{loader:()=>u(()=>import("./BoundaryTerraformSample01.html-68Tij93V.js"),__vite__mapDeps([100,1])),meta:{d:1634219407e3,g:["Boundary","Terraform","Config"],e:` +`,r:{minutes:1.72,words:516},y:"a",t:"Configure Boudary using Terraform"}}],["/04-HashiCorp/06-Vault/01-Information/kmip-faq.html",{loader:()=>u(()=>import("./kmip-faq.html-C7ANvaZR.js"),__vite__mapDeps([101,1])),meta:{a:"hashicat(MZC), chadness12(MZC)",d:1707979472e3,g:["vault","kmip"],e:` +
    KMIP 적용 흐름도
    KMIP 적용 흐름도
    +

    Client 인증서의 유효 기간

    +

    기본 설정시 1,209,600초(2주)의 유효 기간을 갖게 되며, 설정에 따라 긴 유효시간의 적용이 가능합니다. (옵션 : deault_tls_client_ttl)
    +설정은 상기 도식화한 절차 중 "2. kmip 기본 config" 단계에 적용 가능하며. 이는 KMIP 적용 흐름도의 "4. kmip scope, role 정의" 단계에서 override 할 수 있습니다.

    `,r:{minutes:1.6,words:480},y:"a",t:"Vault KMIP FAQ"}}],["/04-HashiCorp/06-Vault/01-Information/port-info.html",{loader:()=>u(()=>import("./port-info.html-pJreTwb3.js"),__vite__mapDeps([102,1])),meta:{d:1640239298e3,g:["vault","port","requirement"],e:` +
    +

    https://learn.hashicorp.com/tutorials/vault/reference-architecture#network-connectivity

    +
    +

    Vault 포트

    +

    TCP

    +`,r:{minutes:.51,words:154},y:"a",t:"Vault Listen Address & Port"}}],["/04-HashiCorp/06-Vault/01-Information/vault-audit.html",{loader:()=>u(()=>import("./vault-audit.html-CDGLjQ2_.js"),__vite__mapDeps([103,1])),meta:{d:1641009317e3,g:["vault","audit"],e:` +

    Vault Audit은 -path를 달리하여 여러 Audit 메커니즘을 중복해서 구성 가능

    +

    File

    +
    $ vault audit enable file file_path=/var/log/vault/vault_audit.log
    +$ vault audit enable -path=file2 file file_path=/var/log/vault/vault_audit2.log
    +
    `,r:{minutes:.22,words:65},y:"a",t:"Vault Audit"}}],["/04-HashiCorp/06-Vault/01-Information/vault-dev-mode-option.html",{loader:()=>u(()=>import("./vault-dev-mode-option.html-COroIWK7.js"),__vite__mapDeps([104,1])),meta:{d:1676188911e3,g:["vault","optinos"],e:` +

    볼트 개발 모드 서버를 시작하는 기초적인 커맨드와 실행 후 안내 메시지는 다음과 같다.

    +
    $ vault server -dev
    +
    +==> Vault server configuration:
    +
    +             Api Address: http://127.0.0.1:8200
    +                     Cgo: disabled
    +         Cluster Address: https://127.0.0.1:8201
    +   Environment Variables: HOME, ITERM_PROFILE, ...
    +              Go Version: go1.19.4
    +              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
    +               Log Level: info
    +                   Mlock: supported: false, enabled: false
    +           Recovery Mode: false
    +                 Storage: inmem
    +                 Version: Vault v1.12.3, built 2023-02-02T09:07:27Z
    +             Version Sha: 209b3dd99fe8ca320340d08c70cff5f620261f9b
    +
    +==> Vault server started! Log data will stream in below:
    +
    +...
    +
    `,r:{minutes:1.49,words:447},y:"a",t:"Vault Development Mode Options"}}],["/04-HashiCorp/06-Vault/01-Information/vault-server-configuration-info.html",{loader:()=>u(()=>import("./vault-server-configuration-info.html-DINKbJUe.js"),__vite__mapDeps([105,1])),meta:{d:1676186503e3,g:["vault","configuration"],e:` +

    볼트 서버를 시작하는 기초적인 커맨드와 실행 후 안내 메시지는 다음과 같다.

    +
    $ vault server -dev
    +
    +==> Vault server configuration:
    +
    +             Api Address: http://127.0.0.1:8200
    +                     Cgo: disabled
    +         Cluster Address: https://127.0.0.1:8201
    +   Environment Variables: HOME, ITERM_PROFILE, ...
    +              Go Version: go1.19.4
    +              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
    +               Log Level: info
    +                   Mlock: supported: false, enabled: false
    +           Recovery Mode: false
    +                 Storage: inmem
    +                 Version: Vault v1.12.3, built 2023-02-02T09:07:27Z
    +             Version Sha: 209b3dd99fe8ca320340d08c70cff5f620261f9b
    +
    +==> Vault server started! Log data will stream in below:
    +
    +...
    +
    `,r:{minutes:.64,words:193},y:"a",t:"Vault Server Configuration - Info"}}],["/04-HashiCorp/06-Vault/01-Information/vault-sizing.html",{loader:()=>u(()=>import("./vault-sizing.html-BIukP5am.js"),__vite__mapDeps([106,1])),meta:{d:1641009317e3,g:["vault","sizing"],e:` +
    +

    https://learn.hashicorp.com/tutorials/vault/reference-architecture#deployment-system-requirements

    +

    Vault의 Backend-Storage 사용 여부에 따라 구성에 차이가 발생

    +
    `,r:{minutes:.65,words:196},y:"a",t:"Vault Sizing"}}],["/04-HashiCorp/06-Vault/01-Information/vault-token.html",{loader:()=>u(()=>import("./vault-token.html-DztbiN7r.js"),__vite__mapDeps([107,1])),meta:{d:1677917082e3,g:["vault","token"],e:` +
    +

    https://developer.hashicorp.com/vault/docs/concepts/tokens

    +
    +
    image-20230304125220402
    image-20230304125220402
    `,r:{minutes:3.45,words:1034},y:"a",t:"Token의 이해"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/keymgmt.html",{loader:()=>u(()=>import("./keymgmt.html-10SIymGL.js"),__vite__mapDeps([108,1])),meta:{d:1691365293e3,g:["vault","Vault Enterprise","keymgmt"],e:` +
    +

    Key Management Secret Engine을 활성화 하기 위해서는 ADP 수준의 라이선스가 필요하다.

    +
    +

    Key Management 시크릿 엔진은 KMS(Key Management Service)를 공급하는 대상의 암호화 키의 배포 및 수명 주기 관리를 위한 워크플로를 제공한다. KMS 공급자 고유의 암호화 기능을 기존처럼 사용하면서도, 볼트에서 키를 중앙 집중식으로 제어할 수 있다.

    +

    볼트는 KMS의 구성에 사용되는 Key Meterial 원본을 생성하여 보유한다. 관리가능한 KMS에 대해 키 수명주기를 설정 및 관리하면 Key Meterial의 복사본이 대상에 배포된다. 이 방식으로 볼트는 KMS 서비스의 전체 수명 주기 관리 및 키 복구 수단을 제공한다. 지원되는 KMS는 다음과 같다.

    `,r:{minutes:13.59,words:4078},y:"a",t:"Key Management"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/kmip-mongo.html",{loader:()=>u(()=>import("./kmip-mongo.html-XXGOUK4V.js"),__vite__mapDeps([109,1])),meta:{d:1641009317e3,g:["vault","Vault Enterprise","KMIP","MongoDB"],e:` +
    +

    Enterprise 기능

    +
    +

    Vault - dev mode run (Option)

    +
    VAULT_UI=true vault server -dev-root-token-id=root -dev -log-level=trace
    +
    +export VAULT_ADDR="http://127.0.0.1:8200"
    +echo "export VAULT_ADDR=$VAULT_ADDR" >> /root/.bashrc
    +vault status
    +vault login root
    +
    `,r:{minutes:.85,words:256},y:"a",t:"KMIP - MongoDB"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/pki-nginx.html",{loader:()=>u(()=>import("./pki-nginx.html-CUq8HqPC.js"),__vite__mapDeps([110,1])),meta:{d:1641009317e3,g:["vault","PKI"],e:` +
    +

    https://learn.hashicorp.com/tutorials/vault/pki-engine

    +
    +

    Vault 구성

    +

    환경 변수

    +
    export VAULT_SKIP_VERIFY=True
    +export VAULT_ADDR='http://172.28.128.11:8200'
    +export VAULT_TOKEN=s.8YXFI825TZxnwLtYHsLc9Fnb
    +
    `,r:{minutes:3.2,words:960},y:"a",t:"PKI - nginx 샘플"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-debian.html",{loader:()=>u(()=>import("./ssh-otp-debian.html-DLDjZJ9W.js"),__vite__mapDeps([111,1])),meta:{d:1641009317e3,g:["vault","SSH","OTP","Debian","Ubuntu"],e:` +
    +

    https://learn.hashicorp.com/tutorials/vault/pki-engine

    +
    +

    Vault설정

    +

    시크릿 엔진 활성화

    +
    $ vault secrets enable -path ssh ssh
    +
    `,r:{minutes:1.11,words:333},y:"a",t:"SSH OTP - Debian 계열"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-otp-redhat.html",{loader:()=>u(()=>import("./ssh-otp-redhat.html-DwB1_tZt.js"),__vite__mapDeps([112,1])),meta:{d:1641009317e3,g:["vault","SSH","OTP","Rocky","RHEL","CentOS"],e:` +
    +

    https://learn.hashicorp.com/tutorials/vault/pki-engine

    +
    +

    Vault설정

    +

    시크릿 엔진 활성화

    +
    $ vault secrets enable -path ssh ssh
    +
    `,r:{minutes:1.05,words:315},y:"a",t:"SSH OTP - RedHat 계열"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/ssh-signed-certificates.html",{loader:()=>u(()=>import("./ssh-signed-certificates.html-BukdSfUL.js"),__vite__mapDeps([113,1])),meta:{d:1641009317e3,g:["vault","SSH"],e:` +

    Vault설정

    +

    시크릿 엔진 활성화

    +
    $ vault secrets enable -path=ssh-client-signer ssh
    +
    `,r:{minutes:.74,words:222},y:"a",t:"SSH - Signed Certificates"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/transform-fpe.html",{loader:()=>u(()=>import("./transform-fpe.html-QAq4yL3V.js"),__vite__mapDeps([114,1])),meta:{d:1645244902e3,g:["vault","transform","fpe"],e:` +

    Transform secrets 엔진은 제공된 입력 값에 대해 안전한 데이터 변환 및 토큰화를 처리합니다. 변환 방법은 FF3-1 을 통한 형태 보존 암호화(FPE) 와 같은 NIST 검증된 암호화 표준을 포함 할 수 있지만 마스킹과 같은 다른 수단을 통한 데이터의 익명 변환일 수도 있습니다.

    +`,r:{minutes:.87,words:260},y:"a",t:"Transform FPE (Ent)"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/transit-import.html",{loader:()=>u(()=>import("./transit-import.html-C9MCha3F.js"),__vite__mapDeps([115,1])),meta:{d:1695292702e3,g:["vault","transit"],e:` +

    키 가져오기(Import) 기능은 HSM, 사용자 정의 키, 기타 외부 시스템에서 기존 키를 가져와야 하는 경우를 지원한다. 공개키(Public Key)만을 가져올 수도 있다.

    +`,r:{minutes:4.53,words:1360},y:"a",t:"Transit (Import)"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/transit.html",{loader:()=>u(()=>import("./transit.html-D29kRUEL.js"),__vite__mapDeps([116,1])),meta:{d:1641009317e3,g:["vault","transit"],e:` + +

    Vault구성 (Option)

    +

    시크릿 엔진 활성화

    +
    export VAULT_SKIP_VERIFY=True
    +export VAULT_ADDR='http://172.28.128.21:8200'
    +export VAULT_TOKEN=<mytoken>
    +
    `,r:{minutes:1.44,words:433},y:"a",t:"Transit"}}],["/04-HashiCorp/06-Vault/03-Auth_Method/aws-auth.html",{loader:()=>u(()=>import("./aws-auth.html-CZDrbpdq.js"),__vite__mapDeps([117,1])),meta:{d:1688129915e3,g:["vault auth","AWS"],e:` +
    +

    https://developer.hashicorp.com/vault/docs/auth/aws

    +

    https://developer.hashicorp.com/vault/api-docs/auth/aws

    +

    https://blog.gruntwork.io/a-guide-to-automating-hashicorp-vault-3-authenticating-with-an-iam-user-or-role-a3203a3ee088

    +
    `,r:{minutes:4.49,words:1346},y:"a",t:"AWS Auth Method"}}],["/04-HashiCorp/06-Vault/03-Auth_Method/mfa-login.html",{loader:()=>u(()=>import("./mfa-login.html-BW3fIyzd.js"),__vite__mapDeps([118,1])),meta:{d:1651826418e3,g:["vault auth","MFA"],e:` +
    +

    HashiCorp Learn - Login MFA : https://learn.hashicorp.com/tutorials/vault/multi-factor-authentication
    +Configure TOTP MFA Method : https://www.vaultproject.io/api-docs/secret/identity/mfa/totp
    +Vault Login MFA Overview : https://www.vaultproject.io/docs/auth/login-mfa
    +1.10.3+ recommend : https://discuss.hashicorp.com/t/vault-1-10-3-released/39394

    +
    `,r:{minutes:.94,words:282},y:"a",t:"MFA Login with Vault TOTP"}}],["/04-HashiCorp/06-Vault/03-Auth_Method/super-user-create.html",{loader:()=>u(()=>import("./super-user-create.html-BgsspQ9P.js"),__vite__mapDeps([119,1])),meta:{d:1641009317e3,g:["vault auth"],e:` +
    +

    주의

    +

    해당 방법은 username/password 방식의 Admin권한의 사용자를 생성하나,
    +보안상 실 구성에는 권장하지 않습니다.

    +
    +
      +
    1. userpass 활성화
    2. +
    +
    vault auth enable userpass
    +
    `,r:{minutes:.64,words:192},y:"a",t:"Vault SuperUser 생성"}}],["/04-HashiCorp/06-Vault/03-Auth_Method/token_role.html",{loader:()=>u(()=>import("./token_role.html-B8tnjLEI.js"),__vite__mapDeps([120,1])),meta:{d:1651059687e3,g:["vault auth"],e:` +

    별도 Auth Method를 사용하지 않고 Token으로만 사용하는 경우 Token에 대한 role을 생성하여 해당 role의 정의된 설정에 종속된 Token을 생성할 수 있음

    +
      +
    • Entity가 발생하므로 Vault Client Count 절약 가능
    • +
    • 일관된 Token 생성 가능
    • +
    • Token에 대한 별도 Tune(TTL 조정 등) 가능
    • +
    +

    절차

    +
      +
    1. +

      UI > Access > Entities > [create entity] : 100y-entity

      +
    2. +
    3. +

      entity에서 aliases 생성 : 100y-alias

      +
    4. +
    5. +

      role 생성 (payload.json)

      +
      {
      +  "allowed_policies": [
      +    "my-policy"
      +  ],
      +  "name": "100y",
      +  "orphan": false,
      +  "bound_cidrs": ["127.0.0.1/32", "128.252.0.0/16"],
      +  "renewable": true,
      +  "allowed_entity_aliases": ["100y-alias"]
      +}
      +
    6. +
    7. +

      role 적용

      +
      curl -H "X-Vault-Token: hvs.QKRiVmCedA06dCSc2TptmSk1" -X POST --data @payload.json http://127.0.0.1:8200/v1/auth/token/roles/100y
      +
    8. +
    9. +

      role에 대한 사용자 정의 tune 적용(옵션)

      +
      vault auth tune -max-lease-ttl=876000h token/role/100y
      +vault auth tune -default-lease-ttl=876000h token/role/100y
      +
    10. +
    11. +

      tune 적용된 role 확인

      +
      $ vault read auth/token/roles/100y
      +
      +Key                         Value
      +---                         -----
      +allowed_entity_aliases      [100y-alias]
      +allowed_policies            [default]
      +allowed_policies_glob       []
      +bound_cidrs                 [127.0.0.1 128.252.0.0/16]
      +disallowed_policies         []
      +disallowed_policies_glob    []
      +explicit_max_ttl            0s
      +name                        100y
      +orphan                      false
      +path_suffix                 n/a
      +period                      0s
      +renewable                   true
      +token_bound_cidrs           [127.0.0.1 128.252.0.0/16]
      +token_explicit_max_ttl      0s
      +token_no_default_policy     false
      +token_period                0s
      +token_type                  default-service
      +
    12. +
    13. +

      token 생성

      +
      $ vault token create -entity-alias=100y-alias -role=100y
      +Key                  Value
      +---                  -----
      +token                hvs.CAESIIveQyE34VOowkCXj4InopxsQHWXu2iW00UQDDCTb-pIGh4KHGh2cy5UZGJ4MjJic1RjY1BlVGRWVHhzNFgwWW4
      +token_accessor       Cx6qjyUGwqPmqoPNe9tmkCiN
      +token_duration       876000h
      +token_renewable      true
      +token_policies       ["default"]
      +identity_policies    ["default"]
      +policies             ["default"]
      +
    14. +
    15. +

      token이 role의 구성이 반영되었는지 확인

      +
      $ vault token lookup hvs.CAESIIveQyE34VOowkCXj4InopxsQHWXu2iW00UQDDCTb-pIGh4KHGh2cy5UZGJ4MjJic1RjY1BlVGRWVHhzNFgwWW4
      +
      +Key                            Value
      +---                            -----
      +accessor                       Cx6qjyUGwqPmqoPNe9tmkCiN
      +bound_cidrs                    [127.0.0.1 128.252.0.0/16]
      +creation_time                  1651059486
      +creation_ttl                   876000h
      +display_name                   token
      +entity_id                      53fc4716-fc0d-db34-14b8-ab4258f89fb1
      +expire_time                    2122-04-03T20:38:06.73198+09:00
      +explicit_max_ttl               0s
      +external_namespace_policies    map[]
      +id                             hvs.CAESIIveQyE34VOowkCXj4InopxsQHWXu2iW00UQDDCTb-pIGh4KHGh2cy5UZGJ4MjJic1RjY1BlVGRWVHhzNFgwWW4
      +identity_policies              [default]
      +issue_time                     2022-04-27T20:38:06.731984+09:00
      +meta                           <nil>
      +num_uses                       0
      +orphan                         false
      +path                           auth/token/create/100y
      +policies                       [default]
      +renewable                      true
      +role                           100y
      +ttl                            875999h59m3s
      +type                           service
      +
    16. +
    `,r:{minutes:1.18,words:353},y:"a",t:"Token Role"}}],["/04-HashiCorp/06-Vault/03-Auth_Method/vault-kv-v2-ui-policy.html",{loader:()=>u(()=>import("./vault-kv-v2-ui-policy.html-D5LINTkA.js"),__vite__mapDeps([121,1])),meta:{d:1641009317e3,g:["vault","kv","policy"],e:` +
    +

    사용자별 UI 접근에 대한 설정을 Kv-v2를 예로 확인

    +
    +

    Policy 구성

    +

    UI 접근을 위해서는 metadata에 대한 권한 추가가 필요함

    +
    $ vault policy write ui-kv-policy - << EOF
    +
    +path "kv-v2/data/path/" {
    +  capabilities = ["create", "update", "read", "delete", "list"]
    +}
    +path "kv-v2/delete/path/" {
    +  capabilities = ["update"]
    +}
    +path "kv-v2/metadata/path/" {
    +  capabilities = ["list", "read", "delete"]
    +}
    +path "kv-v2/destroy/path/" {
    +  capabilities = ["update"]
    +}
    +
    +path "kv-v2/data/path/userid/*" {
    +  capabilities = ["create", "update", "read", "delete", "list"]
    +}
    +path "kv-v2/delete/path/userid/*" {
    +  capabilities = ["update"]
    +}
    +path "kv-v2/metadata/path/userid/*" {
    +  capabilities = ["list", "read", "delete"]
    +}
    +path "kv-v2/destroy/path/userid/*" {
    +  capabilities = ["update"]
    +}
    +
    +# Additional access for UI
    +path "kv-v2/metadata" {
    +  capabilities = ["list"]
    +}
    +EOF
    +
    +##### or #####
    +
    +vault policy write ui-kv-policy - << EOF
    +
    +path "kv-v2/data/path/userid" {
    +  capabilities = ["create", "update", "read", "delete", "list"]
    +}
    +path "kv-v2/delete/path/userid" {
    +  capabilities = ["update"]
    +}
    +path "kv-v2/metadata/path/userid" {
    +  capabilities = ["list", "read", "delete"]
    +}
    +path "kv-v2/destroy/path/userid" {
    +  capabilities = ["update"]
    +}
    +
    +# Additional access for UI
    +path "kv-v2/metadata/*" {
    +  capabilities = ["list"]
    +}
    +EOF
    +
    +
    `,r:{minutes:.76,words:227},y:"a",t:"kv-v2 UI Policy"}}],["/04-HashiCorp/06-Vault/04-UseCase/argocd-vault-plugin.html",{loader:()=>u(()=>import("./argocd-vault-plugin.html-Bs4TVigs.js"),__vite__mapDeps([122,1])),meta:{d:1686536963e3,g:["vault","argocd","gitops","devsescops","pipeline","github","gitlab","secret","kubernetes","k8s","eks"],e:` +
    +

    참고 : 본 글은 AEWS 스터디 7주차 내용중 일부로 작성된 내용입니다.

    +
    +

    1. ArgoCD

    +img +

    1) 개요 및 소개

    +

    Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.

    `,r:{minutes:7.09,words:2127},y:"a",t:"ArgoCD Vault Plugin"}}],["/04-HashiCorp/06-Vault/04-UseCase/jenkins-pipeilne-vault-approle.html",{loader:()=>u(()=>import("./jenkins-pipeilne-vault-approle.html-BSKyMc3X.js"),__vite__mapDeps([123,1])),meta:{d:1656429396e3,g:["vault","jenkins","approle"],e:` +

    Vault의 AppRole 인증 방식은 Vault Token을 얻기위한 단기 자격증명을 사용하는 장점이 있지만 자동화된 환경에 어울리는(반대로 사람에게 불편한)방식으로 Vault를 이용하는 애플리케이션/스크립트의 배포 파이프라인을 구성하는 방식을 추천합니다.

    +
    TEST ENV +
    $ sw_vers
    +ProductName:	macOS
    +ProductVersion:	12.4
    +
    +$ brew --version
    +Homebrew 3.5.2
    +
    +$ git version
    +git version 2.27.0
    +
    +$ java -version
    +openjdk version "11.0.14.1" 2022-02-08
    +
    +$ gradle --version
    +Welcome to Gradle 7.4.2!
    +
    +$ docker version
    +Client:
    + Version:           20.10.9
    +
    +Server:
    + Engine:
    +  Version:          20.10.14
    +
    +$ vault version
    +Vault v1.11.0
    +
    +$ nomad version
    +Nomad v1.3.1
    +
    +$ curl --version
    +curl 7.79.1 (x86_64-apple-darwin21.0)
    +
    `,r:{minutes:5.45,words:1635},y:"a",t:"Jenkins Pipeline Vault Approle (with Nomad)"}}],["/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault-otp.html",{loader:()=>u(()=>import("./jenkins-with-vault-otp.html-CgbhmfaN.js"),__vite__mapDeps([124,1])),meta:{d:1645000713e3,g:["vault","jenkins","otp","token"],e:` +

    jenkins와 vault otp를 연동하여 pipe line에서 ssh/scp test

    +

    otp 설정은 docmoa의 ssh-otp 참고

    + +

    vault token 설정

    +
    # ssh 권한을 사용 할 policy 생성
    +$ tee ssh-policy.hcl <<EOF
    +# To list SSH secrets paths
    +path "ssh/*" {
    +  capabilities = [ "list" ]
    +}
    +# To use the configured SSH secrets engine otp_key_role role
    +path "ssh/creds/otp_key_role" {
    +  capabilities = ["create", "read", "update"]
    +}
    +EOF
    +
    +#ssh(otp) 정책 생성
    +$ vault policy write ssh ssh-policy.hcl
    +
    +#rest api에서 사용 할 token 생성
    +$ vault token create -policy=ssh
    +
    `,r:{minutes:.66,words:198},y:"a",t:"jenkins with vault otp"}}],["/04-HashiCorp/06-Vault/04-UseCase/jenkins-with-vault.html",{loader:()=>u(()=>import("./jenkins-with-vault.html-Bquo_ttW.js"),__vite__mapDeps([125,1])),meta:{d:1643344857e3,g:["vault","jenkins","screct","kv"],e:` +

    jenkins와 vault를 연동하여 pipe line에서 kv 사용하기
    +이 예제는 진짜 kv까지만 테스트함

    +
    # approle 엔진 생성
    +$ vault auth enable approle
    +# kv2 enable
    +$ vault secrets enable kv-v2
    +# kv enable
    +$ vault secrets enable -path=kv kv
    +
    +# jenkins 정책으로 될 파일 생성 v1, v2
    +$ tee jenkins-policy.hcl <<EOF
    +path "kv/secret/data/jenkins/*" {
    +  capabilities = [ "read" ]
    +}
    +path "kv-v2/data/jenkins/*" {
    +  capabilities = [ "read" ]
    +}
    +EOF
    +
    +#jenkins 정책 생성
    +vault policy write jenkins jenkins-policy.hcl
    +
    +#approle 생성 및 정책 jenkins에 연결
    +vault write auth/approle/role/jenkins token_policies="jenkins" \\
    +token_ttl=1h token_max_ttl=4h
    + 
    +#Role id, secret id 가져오기
    +
    +vault read auth/approle/role/jenkins/role-id
    +vault write -f auth/approle/role/jenkins/secret-id
    +
    +
    +vault secrets enable -path=kv kv
    +$ tee gitlab.json <<EOF
    +{
    +  "gitlabIP": "172.21.2.52",
    +  "api-key": "RjLAbbWsSAzXoyBvo2qL"
    +}
    +EOF
    +
    +tee gitlab-v2.json <<EOF
    +{
    +  "gitlabIP": "172.21.2.52",
    +  "api-key": "RjLAbbWsSAzXoyBvo2qL",
    +  "version": "v2"
    +}
    +EOF
    +
    +vault kv put kv/secret/data/jenkins/gitlab @gitlab.json
    +vault kv put kv-v2/jenkins/gitlab @gitlab-v2.json
    +
    `,r:{minutes:.86,words:259},y:"a",t:"jenkins with vault"}}],["/04-HashiCorp/06-Vault/04-UseCase/mtls.html",{loader:()=>u(()=>import("./mtls.html-yuwnSBw9.js"),__vite__mapDeps([126,1])),meta:{d:1679214075e3,g:["vault","pki","mTLS"],e:` +
    +

    Demo App Github : https://github.com/Great-Stone/vault-mtls-demo

    +
    +

    1. mTLS 설명

    +

    1.1 SSL과 TLS

    +

    SSL(Secure Sokets Layer, 보안 소캣 계층)는 클라이언트와 서버 사이에 전송된 데이터를 암호화 하고 인터넷 연결에 보안을 유지하는 표준 기술이다. 악의적 외부인이 클라이언트와 서버 사이에 전송되는 정보를 확인 및 탈취하는 것을 방지한다.

    `,r:{minutes:8.66,words:2599},y:"a",t:"Vault PKI - mTLS demo"}}],["/04-HashiCorp/06-Vault/04-UseCase/nomad-integration.html",{loader:()=>u(()=>import("./nomad-integration.html-Dc0XFJO_.js"),__vite__mapDeps([127,1])),meta:{d:16536985e5,g:["nomad","vault","aws","db"],e:` +
    +

    Dev Mode 를 활용한 테스트

    + +
    `,r:{minutes:4.24,words:1271},y:"a",t:"Vault & Nomad Integration Test"}}],["/04-HashiCorp/06-Vault/04-UseCase/sentinel-check-identity-cidr.html",{loader:()=>u(()=>import("./sentinel-check-identity-cidr.html-695nF6hn.js"),__vite__mapDeps([128,1])),meta:{d:1668567247e3,g:["vault","sentinel","cidr","enterprise"],e:` +
    +

    Enterprise 기능

    +
    +

    Token Role에 bound_cidr을 적용하거나 여타 인증(AppRole, Userpass 등)에 허용하는 cidr을 적용하는 경우 다시 Token을 발급하거나 인증받지 않는한은 cidr을 기반으로한 차단을 동적으로 적용할 수 없다.

    +

    이경우 Sentinel을 사용하여 동적인 정책을 적용할 수 있다. Sentinel은 ACL방식의 기존 Policy와는 달리 Path가 아닌 다른 검증 조건을 추가할 수 있다.

    `,r:{minutes:1.32,words:395},y:"a",t:"Sentinel - (Identity & CIDR)"}}],["/04-HashiCorp/06-Vault/04-UseCase/spring-boot.html",{loader:()=>u(()=>import("./spring-boot.html-2E8NVdcM.js"),__vite__mapDeps([129,1])),meta:{d:1681084498e3,g:["vault","java","spring"],e:` +
    +

    Example Source : https://github.com/Great-Stone/vault_springboot_example

    +
    +

    볼트는 애플리케이션(앱)의 구성관리, 특히 사용자 ID, 패스워드, Token, 인증서, 엔드포인트, AWS 자격증명 등과 같은 민감한 정보를 안전하게 저장하는 중앙 집중식 인프라를 제공한다. 서비스의 성장과 더불어, 이를 구성하는 앱은 확장과 분리 요구 사항이 발생하면 구성 관리가 어려워 진다. 특히, 시크릿 정보가 포함되는 구성 관리는 수동으로 관리하는 경우 로컬 환경을 포함한 여러 시스템에 노출되는 위험성을 갖고, 환경마다 다른 시크릿을 관리하기위한 유지 관리의 노력과 비용이 증가한다. 볼트에서 이야기하는 앱과 관련한 "시크릿 스프롤(퍼짐)" 현상은 다음과 같다.

    `,r:{minutes:5.07,words:1520},y:"a",t:"Vault로 Spring Boot 구성관리"}}],["/04-HashiCorp/06-Vault/04-UseCase/terraform-with-aws-secret-engine.html",{loader:()=>u(()=>import("./terraform-with-aws-secret-engine.html-BWFLLgFQ.js"),__vite__mapDeps([130,1])),meta:{a:"powhapki",d:1650373852e3,g:["terraform","vault","aws"],e:` +`,r:{minutes:1.18,words:353},y:"a",t:"Terraform 코드 상에서 Vault 연동하기"}}],["/04-HashiCorp/06-Vault/04-UseCase/transit-stress-test.html",{loader:()=>u(()=>import("./transit-stress-test.html-wiHaB4DE.js"),__vite__mapDeps([131,1])),meta:{d:1647319815e3,g:["vault","performance","transit"],e:` +
    +

    wrk github : https://github.com/wg/wrk
    +transit : https://www.vaultproject.io/docs/secrets/transit

    +
    `,r:{minutes:.51,words:153},y:"a",t:"Vault Stress Test"}}],["/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-integration-three-methods.html",{loader:()=>u(()=>import("./vault-k8s-integration-three-methods.html-LiLO8xBQ.js"),__vite__mapDeps([132,1])),meta:{d:1682257604e3,g:["vault","kubernetes","secret","VSO"],e:` +
      +
    • Sidecar Agent Injector
    • +
    • CSI provider
    • +
    • Vault Secrets Operator
    • +
    +
    +

    개요

    +

    본 글에서는 HashiCorp Vault 및 Kubernetes 통합을 위해 HashiCorp가 지원하는 세 가지 방법을 자세히 비교한다:

    +
      +
    1. 볼트 사이드카 에이전트 인젝터(Sidecar Agent Injector)
    2. +
    3. 볼트 컨테이너 스토리지 인터페이스 공급자(Container Storage Interface (CSI) provider)
    4. +
    5. 볼트 시크릿 오퍼레이터(Secrets Operator)
    6. +
    +

    각 방법에 대한 실용적인 지침(guidance)을 제공하여 사용 사례에 가장 적합한 방법을 이해하고 선택할 수 있도록 안내한다.

    +
    `,r:{minutes:3.22,words:967},y:"a",t:"Kubernetes Vault 통합방안 3가지 비교"}}],["/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-manually-using-the-sidecar.html",{loader:()=>u(()=>import("./vault-k8s-manually-using-the-sidecar.html-CCPhH6l3.js"),__vite__mapDeps([133,1])),meta:{d:1701658962e3,g:["vault","kubernetes"],e:` +

    Kubernetes(K8s)환경에서 외부 Vault(External Vault Server)와 연계하는 경우 일반적으로 kubernetes 인증방식을 활용하여 Vault와 K8s 간 플랫폼 수준에서의 인증을 처리하나, K8s로의 Cluster API에 대한 inbound가 막혀있는 경우 이같은 방식은 사용할 수 없다. 따라서 helm, vso 같은 방식의 사용이 불가능하므로 Vault Agent를 Sidecar로 함께 배포하는 경우 수동으로 구성해주어야 한다.

    `,r:{minutes:2.59,words:777},y:"a",t:"Kubernetes에 Vault Agent(Sidecar) 수동 구성"}}],["/04-HashiCorp/06-Vault/04-UseCase/vault-k8s-usecase-csi-injection.html",{loader:()=>u(()=>import("./vault-k8s-usecase-csi-injection.html-zpyEzJMk.js"),__vite__mapDeps([134,1])),meta:{d:1678338483e3,g:["vault","kubernetes"],e:` +

    Vault에 저장된 시크릿 또는 발행되는(Dynamic) 시크릿을 획득하기 위해서는, 시크릿을 요청하는 클라이언트(사람/앱/장비)가 다음의 과정을 수행해야 합니다.

    +
      +
    1. 클라이언트가 Vault 토큰을 획득하기 위한 인증 절차
    2. +
    3. 획득한 Vault 토큰의 수명주기 관리 (갱신과 재요청)
    4. +
    5. Vault의 특정 시크릿 경로를 저장하고 해당 시크릿 요청
    6. +
    7. 동적(Dynamic) 시크릿인 경우 해당 임대(Lease)정보 확인 및 갱신과 재요청
    8. +
    +

    Vault는 위의 과정을 클라이언트 대신 플랫폼 수준에서 대행할 수 있는 방안을 제공하고 있습니다. 여기서는 Kubernetes 상에서의 Vault와의 통합 구성을 활용하여 위 과정을 대체하고 Kubernetes 플랫폼 자체(Kuberetes Native)의 기능을 사용하듯 Vault의 시크릿을 사용하게 만드는 방식에 대해 설명합니다.

    `,r:{minutes:12.85,words:3855},y:"a",t:"How to integrate Vault with K8s (CSI & Injection & VSO)"}}],["/04-HashiCorp/06-Vault/04-UseCase/windows-password-rotation.html",{loader:()=>u(()=>import("./windows-password-rotation.html-BsqYRvmj.js"),__vite__mapDeps([135,1])),meta:{d:1641009317e3,g:["vault","windows","nomad","password"],e:` +
    +

    https://scarolan.github.io/painless-password-rotation/#37

    +
    +

    Kv 추가

    +
    $ vault secrets enable -version=2 -path=systemcreds/ kv
    +
    `,r:{minutes:2.12,words:636},y:"a",t:"Windows Password rotation"}}],["/04-HashiCorp/06-Vault/05-TroubleShooting/400-error.html",{loader:()=>u(()=>import("./400-error.html-De4cfBHo.js"),__vite__mapDeps([136,1])),meta:{d:1647319815e3,g:["vault","error","400"],e:` +
    +

    Vault HTTP Status Codes : https://www.vaultproject.io/api#http-status-codes

    +
    +

    Vault에 API 요청시 400에러가 발생하는 경우 Vault로 전달된 데이터 형태가 올바른지 확인이 필요하다.

    +
      +
    • 400 : Invalid request, missing or invalid data.
    • +
    `,r:{minutes:.46,words:139},y:"a",t:"Vault 400 Error"}}],["/04-HashiCorp/06-Vault/05-TroubleShooting/vault-sizing.html",{loader:()=>u(()=>import("./vault-sizing.html-CuSmCHes.js"),__vite__mapDeps([137,1])),meta:{d:1641009317e3,g:["vault","MiriaDB"],e:` +
      +
    • +

      현상 : $vault read mysql/creds/my-role 입력시 오류

      +
    • +
    • +

      오류 내용 :

      +
    • +
    +
    Error reading mysql/creds/my-role: Error making API request.
    +URL: GET http://127.0.0.1:8200/v1/mysql/creds/my-role
    +Code: 500. Errors:
    +
    +* 1 error occurred:
    +      * Error 1470: String 'v-root-my-role-87BP93fheiaHKGelc' is too long for user name (should be no longer than 16)
    +
    `,r:{minutes:.39,words:116},y:"a",t:"Vault MariaDB5.5 Dynamic Secret"}}],["/04-HashiCorp/06-Vault/06-Config/tls-config.html",{loader:()=>u(()=>import("./tls-config.html-By79nyGp.js"),__vite__mapDeps([138,1])),meta:{d:1645407262e3,g:["Vault","https","Configuration","Server"],e:` +
      +
    • Consul tls create 명령어를 이용하여 인증서 생성, 그외에 사설인증서 만드는 방법으로는 더 테스트 해봐야 할듯
    • +
    +
    # consul tls create로 인증서 생성
    +consul tls ca create -domain=vault -days 3650
    +consul tls cert create -domain=vault -dc=global  -server -days 3650
    +consul tls cert create -domain=vault -dc=global  -client -days 3650
    +consul tls cert create -domain=vault -dc=global  -cli -days 3650
    +
    +# vault config는 아래와 같다.
    +ui = true
    +
    +storage "consul" {
    +  address = "127.0.0.1:8500"
    +  path    = "vault/"
    +}
    +
    +listener "tcp" {
    +  address         = "0.0.0.0:8200"
    +  #tls_disable = 1
    +  tls_cert_file = "/root/temp/global-server-vault-0.pem"
    +  tls_key_file  = "/root/temp/global-server-vault-0-key.pem"
    +}
    +
    +disable_mlock = true
    +default_lease_ttl = "768h"
    +max_lease_ttl = "768h"
    +
    +api_addr =  "https://172.21.2.50:8200"
    +
    +# 명령어를 써야 할 경우 cli 인증서를 export 해줘야한다.
    +export VAULT_CACERT="\${HOME}/temp/vault-agent-ca.pem"
    +export VAULT_CLIENT_CERT="\${HOME}/temp/global-cli-vault-0.pem"
    +export VAULT_CLIENT_KEY="\${HOME}/temp/global-cli-vault-0-key.pem"
    +
    `,r:{minutes:.41,words:124},y:"a",t:"Vault Server tls 설정"}}],["/04-HashiCorp/06-Vault/06-Config/vault-agent.html",{loader:()=>u(()=>import("./vault-agent.html-BXv48qmS.js"),__vite__mapDeps([139,1])),meta:{d:1656577693e3,g:["Vault","AWS","Configuration","Agent"],e:` +
    +

    참고 URL : https://learn.hashicorp.com/tutorials/vault/agent-aws

    +
    +
    Test ENV +
    $ sw_vers
    +ProductName:	macOS
    +ProductVersion:	12.4
    +
    +$ vault version
    +Vault v1.11.0
    +
    `,r:{minutes:2.77,words:831},y:"a",t:"Vault Agent (with aws secret)"}}],["/04-HashiCorp/06-Vault/06-Config/vault-entierprise-license.html",{loader:()=>u(()=>import("./vault-entierprise-license.html-DVI43IYK.js"),__vite__mapDeps([140,1])),meta:{d:1695769332e3,g:["Vault","Enterprise","License"],e:` +`,r:{minutes:.53,words:160},y:"a",t:"Vault +1.12 라이선스"}}],["/04-HashiCorp/06-Vault/07-Sentinel-Sample/aws-secrets-credential-type-check.html",{loader:()=>u(()=>import("./aws-secrets-credential-type-check.html-hCBGD_vi.js"),__vite__mapDeps([141,1])),meta:{d:1689216508e3,g:["Vault","Sentinel","Policy"],e:` +

    1. EGP용 정책 생성 egp_iam_user_deny.sentinel

    +
    import "strings"
    +
    +# print(request.data)
    +credential_type = request.data.credential_type
    +print("CREDENTIAL_TYPE: ", credential_type)
    +
    +allow_role_type = ["federation_token"]
    +
    +role_type_check = rule {
    +  credential_type in allow_role_type
    +}
    +
    +# Only check AWS Secret Engine
    +# Only check create, update
    +precond = rule {
    +	request.operation in ["create", "update"]
    +}
    +
    +main = rule when precond {
    +    role_type_check
    +}
    +
    `,r:{minutes:.7,words:210},y:"a",t:"AWS Secrets Role Type Check"}}],["/04-HashiCorp/06-Vault/07-Sentinel-Sample/transit-secrets-key-exportable-deny.html",{loader:()=>u(()=>import("./transit-secrets-key-exportable-deny.html-FvvIqCAX.js"),__vite__mapDeps([142,1])),meta:{d:1702878519e3,g:["Vault","Sentinel","Policy"],e:` +

    1. EGP용 정책 생성 exportable_deny.sentinel

    +
    import "strings"
    +
    +exportable = request.data.exportable
    +
    +exportable_check = rule {
    +  exportable is "false"
    +}
    +
    +main = rule {
    +  exportable_check
    +}
    +
    `,r:{minutes:.77,words:231},y:"a",t:"Transit Key Exportable Deny"}}],["/04-HashiCorp/07-Nomad/01-Information/nomad-sizing.html",{loader:()=>u(()=>import("./nomad-sizing.html-DYiixYEk.js"),__vite__mapDeps([143,1])),meta:{d:1642218029e3,g:["nomad","sizing"],e:` +
    +

    https://learn.hashicorp.com/tutorials/nomad/production-reference-architecture-vm-with-consul

    +

    Nomad는 Server/Client 구조로 구성되며, Client의 경우 자원사용량이 매우 미미하므로 자원산정은 Server를 기준으로 산정

    +
    `,r:{minutes:.26,words:79},y:"a",t:"Nomad Sizing"}}],["/04-HashiCorp/07-Nomad/01-Information/nomad_job_restart.html",{loader:()=>u(()=>import("./nomad_job_restart.html-DHBvZoL7.js"),__vite__mapDeps([144,1])),meta:{d:1644889869e3,g:["nomad","sizing"],e:` +
    +

    원문 : https://www.hashicorp.com/blog/resilient-infrastructure-with-nomad-restarting-tasks
    +Nomad가 종종 운영자 개입 없이 장애, 중단 상황, Nomad 클러스터 인프라의 유지 관리를 처리하는 방법 확인

    +
    `,r:{minutes:1.01,words:304},y:"a",t:"task 복구 방식"}}],["/04-HashiCorp/07-Nomad/02-Config/Cloudwatch-Logging.html",{loader:()=>u(()=>import("./Cloudwatch-Logging.html-B1APZMkF.js"),__vite__mapDeps([145,1])),meta:{d:1639533195e3,g:["Nomad","AWS","Cloudwatch","log"],e:` +

    docker 런타임에는 log driver로 "awslogs"를 지원합니다.
    +https://docs.docker.com/config/containers/logging/awslogs/

    +
    +

    +

    Nomad에서 docker 자체의 로깅을 사용하므로서, Nomad에서 실행되는 docker 기반 컨테이너의 로깅이 특정 환경에 락인되는것을 방지합니다.

    +
    `,r:{minutes:.85,words:256},y:"a",t:"Docker log driver and Cloudwatch on Nomad"}}],["/04-HashiCorp/07-Nomad/02-Config/Namespace.html",{loader:()=>u(()=>import("./Namespace.html-Ch8o5mNT.js"),__vite__mapDeps([146,1])),meta:{d:1628557352e3,g:["Nomad","Namespace"],e:` +
    +

    Nomad Version : >= 1.0.0
    +Nomad Ent. Version : >= 0.7.0
    +https://learn.hashicorp.com/tutorials/nomad/namespaces

    +
    +

    Namespace 생성

    +
    $ nomad namespace apply -description "PoC Application" apps
    +
    `,r:{minutes:.38,words:113},y:"a",t:"Nomad Namespace"}}],["/04-HashiCorp/07-Nomad/02-Config/Nomad-Ui-Token.html",{loader:()=>u(()=>import("./Nomad-Ui-Token.html-DDo04qBT.js"),__vite__mapDeps([147,1])),meta:{d:1648772616e3,g:["Nomad","ACL"],e:` +
    +

    +

    해당 Token의 policy는 특정인이 원하여 만들었으며, 더 다양한 제약과 허용을 할 수 있습니다. 해당 policy는 아래와 같은 제약과 허용을 합니다.

    +
      +
    1. UI에서 exec(job에 접근) 제한
    2. +
    3. 그 외에 job, node, volume, server등의 모든 화면 읽어오기
    4. +
    +
    +

    Nomad cli

    +
    #원하는 권한이 있는 policy file
    +$ cat nomad-ui-policy.hcl
    +namespace "*" {
    +  policy       = "read"
    +  capabilities = ["submit-job", "dispatch-job", "read-logs", "list-jobs", "parse-job", "read-job", "csi-list-volume", "csi-read-volume", "list-scaling-policies", "read-scaling-policy", "read-job-scaling", "read-fs"]
    +}
    +node {
    +  policy = "read"
    +}
    +host_volume "*" {
    +  policy = "write"
    +}
    +plugin {
    +  policy = "read"
    +}
    +
    +#위에서 만든 policy 파일을 nomad cluster에 적용
    +$ nomad acl policy apply -description "Production UI policy" prod-ui nomad-ui-policy.hcl
    +
    +#해당 policy로 token생성(policy는 여러개를 넣을 수도 있음)
    +$ nomad acl token create -name="prod ui token" -policy=prod-ui -type=client | tee ui-prod.token
    +#웹 브라우저 로그인을 위해 Secret ID 복사
    +
    `,r:{minutes:.4,words:121},y:"a",t:"Nomad UI Token"}}],["/04-HashiCorp/07-Nomad/02-Config/Nomad-sslkey-create.html",{loader:()=>u(()=>import("./Nomad-sslkey-create.html-DXTi_C5p.js"),__vite__mapDeps([148,1])),meta:{d:162955514e4,g:["Nomad","SSL"],e:` +
    +

    +

    공식 사이트에 consul 인증서 생성 가이드는 있는데 Nomad 인증서 생성가이드는
    +Show Terminal을 들어가야 볼 수 있기때문에 귀찮음을 해결하기 위해 공유합니다.

    +
    +

    Nomad 인증서 생성

    +
    consul tls ca create -domain=nomad -days 3650
    +
    +consul tls cert create -domain=nomad -dc=global  -server -days 3650
    +
    +consul tls cert create -domain=nomad -dc=global  -client -days 3650
    +
    +consul tls cert create -domain=nomad -dc=global  -cli -days 3650
    +
    `,r:{minutes:.28,words:83},y:"a",t:"Nomad 인증서 생성"}}],["/04-HashiCorp/07-Nomad/02-Config/Server.html",{loader:()=>u(()=>import("./Server.html-C8bYcrnP.js"),__vite__mapDeps([149,1])),meta:{d:162955514e4,g:["Nomad","Enterprise","Configuration","Server"],e:` +
    +

    +

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server설정 파일입니다.
    +네트워크는 프라이빗(온프레이머스) 환경입니다.

    +
    +
    #nomad server 설정
    +server {
    +  enabled = true
    +  bootstrap_expect = 3
    +  license_path="/opt/nomad/license/nomad.license"
    +  server_join {
    +    retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
    +  }
    +  raft_protocol = 3
    +  event_buffer_size = 100
    +  non_voting_server = false
    +  heartbeat_grace = "10s"
    +}
    + 
    + 
    +#tls 설정
    +tls {
    +  http = true
    +  rpc  = true
    + 
    +  ca_file   = "/opt/ssl/nomad/nomad-agent-ca.pem"
    +  cert_file = "/opt/ssl/nomad/global-server-nomad-0.pem"
    +  key_file  = "/opt/ssl/nomad/global-server-nomad-0-key.pem"
    + 
    +  #UI오픈할 서버만 변경
    +  verify_server_hostname = false
    +  verify_https_client    = false
    +  #일반서버는 아래와 같이 설정
    +  verify_server_hostname = true
    +  verify_https_client    = true
    +}
    +
    `,r:{minutes:.54,words:161},y:"a",t:"Nomad 서버 설정"}}],["/04-HashiCorp/07-Nomad/02-Config/client.html",{loader:()=>u(()=>import("./client.html-BZaRWunh.js"),__vite__mapDeps([150,1])),meta:{d:162955514e4,g:["Nomad","Enterprise","Configuration","Client"],e:` +
    +

    +

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 Client설정 파일입니다.
    +네트워크는 프라이빗(온프레이머스) 환경입니다.

    +
    +
    #nomad client 설정
    + 
    +client {
    +  enabled = true
    +  servers = ["172.30.1.17","172.30.1.18","172.30.1.19"]
    +  server_join {
    +    retry_join = ["172.30.1.17","172.30.1.18","172.30.1.19"]
    +    retry_max = 3
    +    retry_interval = "15s"
    +  }
    +  #host에서 nomad에서 사용할 수 있는 volume 설정
    +  host_volume "logs" {
    +    path      = "/var/logs/elk/"
    +    read_only = false
    +  }
    +  #각각의 client의 레이블 작성
    +  #meta {
    +  #   name = "moon"
    +  #   zone = "web"
    +  #}
    +  #nomad에서 예약할 자원
    +  reserved {
    +    #Specifies the amount of CPU to reserve, in MHz.
    +    cpu = 200
    +    #Specifies the amount of memory to reserve, in MB.
    +    memory = 8192
    +    #Specifies the amount of disk to reserve, in MB.
    +    disk = 102400
    +  }
    +  no_host_uuid = true
    +  #bridge network interface name
    +  bridge_network_name = "nomad"
    +  bridge_network_subnet = "172.26.64.0/20"
    +  cni_path = "/opt/cni/bin"
    +  cni_config_dir = "/opt/cni/config"
    +}
    +#tls 설정
    +tls {
    +  http = true
    +  rpc  = true
    + 
    +  ca_file   = "/opt/ssl/nomad/nomad-agent-ca.pem"
    +  cert_file = "/opt/ssl/nomad/global-client-nomad-0.pem"
    +  key_file  = "/opt/ssl/nomad/global-client-nomad-0-key.pem"
    + 
    +  verify_server_hostname = true
    +  verify_https_client    = true
    +}
    +
    `,r:{minutes:.62,words:185},y:"a",t:"Nomad 클라이언트 설정"}}],["/04-HashiCorp/07-Nomad/02-Config/common.html",{loader:()=>u(()=>import("./common.html-BFTGzfD3.js"),__vite__mapDeps([151,1])),meta:{d:162955514e4,g:["Nomad","Enterprise","Configuration","Common"],e:` +
    +

    +

    최대한 설정값을 넣어보고, 번역기도 돌려보고 물어도 보고 넣은 server, client의 공통설정 파일입니다.
    +저는 agent.hcl파일안에 다 넣고 실행하지만 나눠서 추후에는 기능별로 나눠서 사용할 예정입니다.

    +
    +
    #nomad 공통 설정
    +datacenter = "dc1"
    +region = "global"
    +data_dir = "/opt/nomad/nomad"
    +bind_addr = "{{ GetInterfaceIP \`ens192\` }}"
    + 
    +advertise {
    +  # Defaults to the first private IP address.
    +  #http = "{{ GetInterfaceIP \`ens244\` }}"
    +  #rpc  = "{{ GetInterfaceIP \`ens244\` }}"
    +  #serf = "{{ GetInterfaceIP \`ens244\` }}"
    +  http = "{{ GetInterfaceIP \`ens192\` }}"
    +  rpc = "{{ GetInterfaceIP \`ens192\` }}"
    +  serf = "{{ GetInterfaceIP \`ens192\` }}"
    +}
    + 
    +consul {
    +  address  = "127.0.0.1:8500"
    +  server_service_name = "nomad"
    +  client_service_name = "nomad-client"
    +  auto_advertise  = true
    +  server_auto_join  = true
    +  client_auto_join  = true
    +  #consul join용 token
    +  token = "33ee4276-e1ef-8e5b-d212-1f94ca8cf81e"
    +}
    +enable_syslog = false
    +enable_debug = false
    +disable_update_check = false
    + 
    +log_level = "DEBUG"
    +log_file = "/var/log/nomad/nomad.log"
    +log_rotate_duration = "24h"
    +log_rotate_bytes = 104857600
    +log_rotate_max_files = 100
    + 
    +ports {
    +  http = 4646
    +  rpc = 4647
    +  serf = 4648
    +}
    + 
    +#prometheus에서 nomad의 metrics값을 수집 해 갈 수 있게 해주는 설정
    +telemetry {
    +  collection_interval = "1s"
    +  disable_hostname = true
    +  prometheus_metrics = true
    +  publish_allocation_metrics = true
    +  publish_node_metrics = true
    +}
    + 
    + 
    +plugin "docker" {
    +  config {
    +    auth {
    +      config = "/root/.docker/config.json"
    +    }
    +    #온프레이머스환경에서는 해당 이미지를 private repository에 ㅓㄶ고 변경
    +    infra_image = "google-containers/pause-amd64:3.1"
    +  }
    +}
    + 
    +acl {
    +  enabled = true
    +}
    +
    `,r:{minutes:.43,words:130},y:"a",t:"Nomad 공통 설정"}}],["/04-HashiCorp/07-Nomad/02-Config/consul-namespace.html",{loader:()=>u(()=>import("./consul-namespace.html-DVrju3c2.js"),__vite__mapDeps([152,1])),meta:{d:1630463356e3,g:["Nomad","Enterprise","Consul"],e:` +

    Job의 Consul Namespace 정의

    +

    Consul Enterprise는 Namespace가 있어서 Nomad로 Consul에 서비스 등록 시 특정 Namespace를 지정할 수 있음

    +

    Job > Group > Consul

    +
    job "frontback_job" {
    +  group "backend_group_v1" {
    +
    +    count = 1
    +
    +    consul {
    +      namespace = "mynamespace"
    +    }
    +
    +    service {
    +      name = "backend"
    +      port = "http"
    +
    +      connect {
    +        sidecar_service {}
    +      }
    +
    +      check {
    +        type     = "http"
    +        path     = "/"
    +        interval = "5s"
    +        timeout  = "3s"
    +      }
    +    }
    +# 생략
    +
    `,r:{minutes:2.79,words:838},y:"a",t:"Consul namespace 사용시 Nomad의 서비스 등록"}}],["/04-HashiCorp/07-Nomad/02-Config/csi-nfs.html",{loader:()=>u(()=>import("./csi-nfs.html-lgxMjeBE.js"),__vite__mapDeps([153,1])),meta:{d:1639892483e3,g:["Nomad","config","csi","nfs"],e:` +
      +
    • nomad에서 외부 storage를 사용하기 위한 plugin +
        +
      • 그 중에서도 접근성이 좋은 nfs를 사용, public cloud에서 제공하는 storage와는 사용법이 다를 수 있음
      • +
      +
    • +
    • 구성환경은 아래와 같다.(사실 nfs server정보만 보면 될 거 같음) +
        +
      • nfs-server 10.0.0.151:/mnt/data
      • +
      +
    • +
    +

    controller

    +
      +
    • 하나이상의 node에 storage를 배포할 수 있게 해주는 중앙관리 기능
    • +
    • 어느 node(client)에 띄어져도 상관없다.
    • +
    `,r:{minutes:.69,words:207},y:"a",t:"nomad csi (nfs)"}}],["/04-HashiCorp/07-Nomad/02-Config/custom-ui-link.html",{loader:()=>u(()=>import("./custom-ui-link.html-BlrHvl1y.js"),__vite__mapDeps([154,1])),meta:{d:1661946241e3,g:["Nomad","UI"],e:` +
    +

    https://www.nomadproject.io/docs/configuration/ui

    +
    +

    Nomad ui 설정에 다음과 같이 Consul과 Vault의 링크를 추가할 수 있습니다.

    +
    ui {
    +  enabled =  true
    +
    +  consul {
    +    ui_url = "https://consul.example.com:8500/ui"
    +  }
    +
    +  vault {
    +    ui_url = "https://vault.example.com:8200/ui"
    +  }
    +}
    +
    `,r:{minutes:.14,words:41},y:"a",t:"Nomad UI에 Consul과 Vault 링크 추가"}}],["/04-HashiCorp/07-Nomad/02-Config/integrateVault.html",{loader:()=>u(()=>import("./integrateVault.html-CLUbyXkX.js"),__vite__mapDeps([155,1])),meta:{d:1642495601e3,g:["Nomad","Namespace"],e:` +

    아래 작업 전 Forward DNS for Consul Service Discovery을 진행해야함

    + +

    예제를 위한 vault kv 설정

    +
    # 사용된 policy들
    +$ cat nomad-cluster-role.json
    +{
    +    "allowed_policies": "admin",
    +    "token_explicit_max_ttl": 0,
    +    "name": "nomad-cluster",
    +    "orphan": true,
    +    "token_period": 259200,
    +    "renewable": true
    +}
    +vault write /auth/token/roles/nomad-cluster @nomad-cluster-role.json
    +
    +$ cat admin-policy.hcl 
    +# Read system health check
    +path "sys/health"
    +{
    +  capabilities = ["read", "sudo"]
    +}
    +
    +# Create and manage ACL policies broadly across Vault
    +
    +# List existing policies
    +path "sys/policies/acl"
    +{
    +  capabilities = ["list"]
    +}
    +
    +# Create and manage ACL policies
    +path "sys/policies/acl/*"
    +{
    +  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
    +}
    +
    +# Enable and manage authentication methods broadly across Vault
    +
    +# Manage auth methods broadly across Vault
    +path "auth/*"
    +{
    +  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
    +}
    +
    +# Create, update, and delete auth methods
    +path "sys/auth/*"
    +{
    +  capabilities = ["create", "update", "delete", "sudo"]
    +}
    +
    +# List auth methods
    +path "sys/auth"
    +{
    +  capabilities = ["read"]
    +}
    +
    +# Enable and manage the key/value secrets engine at \`secret/\` path
    +
    +# List, create, update, and delete key/value secrets
    +path "secret/*"
    +{
    +  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
    +}
    +
    +# Manage secrets engines
    +path "sys/mounts/*"
    +{
    +  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
    +}
    +
    +# List existing secrets engines.
    +path "sys/mounts"
    +{
    +  capabilities = ["read"]
    +}
    +
    +vault policy write admin admin-policy.hcl
    +
    +# token 생성
    +vault token create -policy admin -period 72h -orphan
    +
    `,r:{minutes:.73,words:220},y:"a",t:"integrate Vault"}}],["/04-HashiCorp/07-Nomad/02-Config/nomad-guide-basic.html",{loader:()=>u(()=>import("./nomad-guide-basic.html-BqDBn4kt.js"),__vite__mapDeps([156,1])),meta:{d:1652660873e3,g:["Nomad","Sample"],e:` +

    Download

    + +

    on Linux

    `,r:{minutes:2.74,words:823},y:"a",t:"Nomad Guide - Basic"}}],["/04-HashiCorp/07-Nomad/02-Config/nomad-on-windows.html",{loader:()=>u(()=>import("./nomad-on-windows.html-Co-Gyxsw.js"),__vite__mapDeps([157,1])),meta:{d:1658932718e3,g:["Nomad","Windows"],e:` +

    Nomad를 Windows환경에 구성하고 실행을위해 서비스로 등록하는 방법을 알아봅니다. 솔루션 실행 환경 또는 운영/개발자의 익숙함 정도에 따라 다양한 OS를 선택하여 애플리케이션을 배포하게 됩니다. Nomad를 통해 배포를 위한 오케스트레이터를 Windows 환경에 적용하고 서비스에 등록하여 상시적으로 실행될 수 있도록하는 구성을 안내합니다.

    +

    Port 구성

    +
    +

    참고 url : Port used

    +
    `,r:{minutes:2.35,words:704},y:"a",t:"Nomad on Windows"}}],["/04-HashiCorp/07-Nomad/04-UseCase/jenkins-pipeline.html",{loader:()=>u(()=>import("./jenkins-pipeline.html-hwIFCVL_.js"),__vite__mapDeps([158,1])),meta:{d:1656411375e3,g:["Nomad","Jenkins","Java","Docker","Vault"],e:` +

    Test ENV

    +
    $ sw_vers
    +ProductName:	macOS
    +ProductVersion:	12.4
    +
    +$ brew --version
    +Homebrew 3.5.2
    +
    +$ git version
    +git version 2.27.0
    +
    +$ java -version
    +openjdk version "11.0.14.1" 2022-02-08
    +
    +$ gradle --version
    +Welcome to Gradle 7.4.2!
    +
    +$ docker version
    +Client:
    + Version:           20.10.9
    +
    +Server:
    + Engine:
    +  Version:          20.10.14
    +
    +$ vault version
    +Vault v1.11.0
    +
    +$ nomad version
    +Nomad v1.3.1
    +
    +$ curl --version
    +curl 7.79.1 (x86_64-apple-darwin21.0)
    +
    +$ aws --version
    +aws-cli/2.7.11 Python/3.10.5 Darwin/21.5.0 source/x86_64 prompt/off
    +
    `,r:{minutes:5.52,words:1656},y:"a",t:"Jenkins Pipeline Nomad (Integrated Vault)"}}],["/04-HashiCorp/07-Nomad/04-UseCase/job-start-from-hcl.html",{loader:()=>u(()=>import("./job-start-from-hcl.html-BwGQHsUl.js"),__vite__mapDeps([159,1])),meta:{d:167721987e4,g:["Nomad","API","HCL"],e:` +

    HCL로 작성된 Job의 경우 Nomad CLI 또는 UI 접속이 가능하다면 바로 적용 가능하다.

    +
    HCL Job Sample (2048.hcl) +
    job "2048-game" {
    +  datacenters = ["dc1"]
    +  type        = "service"
    +  group "game" {
    +    count = 1 # number of instances
    +
    +    network {
    +      port "http" {
    +        static = 80
    +      }
    +    }
    +
    +    task "2048" {
    +      driver = "docker"
    +
    +      config {
    +        image = "alexwhen/docker-2048"
    +
    +        ports = [
    +          "http"
    +        ]
    +
    +      }
    +
    +      resources {
    +        cpu    = 500 # 500 MHz
    +        memory = 256 # 256MB
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:1.01,words:304},y:"a",t:"Pass HCL to API"}}],["/04-HashiCorp/07-Nomad/04-UseCase/springboot-graceful-shutdown.html",{loader:()=>u(()=>import("./springboot-graceful-shutdown.html-C0Rq6jUd.js"),__vite__mapDeps([160,1])),meta:{d:1673502484e3,g:["Nomad","SpringBoot","Java"],e:` +
    +

    GitHub 리소스 : https://github.com/Great-Stone/nomad-springboot-graceful-shutdown

    +
    +

    테스트 환경

    +
      +
    • Gradle 7.4.2
    • +
    • Java 11
    • +
    • Spring Boot 2.7.7
    • +
    • Nomad 1.4.3
    • +
    `,r:{minutes:1.67,words:502},y:"a",t:"Graceful Shutdown 적용 (kill_signal)"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/DAS.html",{loader:()=>u(()=>import("./DAS.html-BoEa47a0.js"),__vite__mapDeps([161,1])),meta:{d:1664763179e3,g:["Nomad","sample","job","autoscaler","das"],e:` +
      +
    • Nomad autoscaler 배포 후 사용할 수 있는 기능 중에 하나
    • +
    • Dynamic application sizing(DAS)의 기능이 설정되어 있는 job을 배포 한 이후 autoscaler job에서 resource의 권고를 받아올 수 있음
    • +
    • 권고 받은 값을 사용자가 확인 후 허용할 경우 job의 resource의 변화가 정상적으로 적용됨
    • +
    +

    autoscaler job은 기존에 사용하던 job을 사용

    +`,r:{minutes:1.23,words:370},y:"a",t:"Dynamic application sizing"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/VaultSWLB-nginx.html",{loader:()=>u(()=>import("./VaultSWLB-nginx.html-Ba3IVIOo.js"),__vite__mapDeps([162,1])),meta:{d:164543379e4,g:["Nomad","Sample","Job","Vault","SWLB"],e:` +
      +
    • Vault의 HA구성 시에는 LB가 필요한데, LB대용으로 SWLB를 이용하여 Vault를 사용할 수 있다. +
        +
      • 해당 페이지에서는 nginx를 사용하였지만, HAproxy도 비슷하게 사용이 가능하다.
      • +
      +
    • +
    +

    nginx job 파일

    +
    job "nginx" {
    +  datacenters = ["dc1"]
    +
    +  group "nginx" {
    +
    +    constraint {
    +      attribute = "${attr.unique.hostname}"
    +      value     = "slave0"
    +    }
    +
    +    #Vault tls가 있고 nomad client hcl 파일에 host volume이 명시되어 있는 설정 값
    +    volume "cert-data" {
    +      type      = "host"
    +      source    = "cert-data"
    +      read_only = false
    +    }
    +
    +    #실패 없이 되라고 행운의 숫자인 7을 4번 줌
    +    network {
    +      port "http" {
    +        to     = 7777
    +        static = 7777
    +      }
    +    }
    +
    +    service {
    +      name = "nginx"
    +      port = "http"
    +    }
    +
    +    task "nginx" {
    +      driver = "docker"
    +
    +      volume_mount {
    +        volume      = "cert-data"
    +        destination = "/usr/local/cert"
    +      }
    +
    +      config {
    +        image = "nginx"
    +
    +        ports = ["http"]
    +        volumes = [
    +          "local:/etc/nginx/conf.d",
    +
    +        ]
    +      }
    +      template {
    +        data = <<EOF        
    +#Vault는 active서버 1대외에는 전부 standby상태이며 
    +#서비스 호출 시(write)에는 active 서비스만 호출해야함으로 아래와 같이 consul에서 서비스를 불러옴
    +
    +upstream backend {
    +{{ range service "active.vault" }}
    +  server {{ .Address }}:{{ .Port }};
    +{{ else }}server 127.0.0.1:65535; # force a 502
    +{{ end }}
    +}
    +
    +server {
    +   listen 7777 ssl;
    +   #위에서 nomad host volume을 mount한 cert를 가져옴
    +   ssl on;
    +   ssl_certificate /usr/local/cert/vault/global-client-vault-0.pem;
    +   ssl_certificate_key /usr/local/cert/vault/global-client-vault-0-key.pem;
    +   #vault ui 접근 시 / -> /ui redirect되기 때문에 location이 /외에는 되지 않는다.
    +   location / {
    +      proxy_pass https://backend;
    +   }
    +}
    +EOF
    +
    +        destination   = "local/load-balancer.conf"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +      resources {
    +        cpu = 100
    +        memory = 201
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.53,words:159},y:"a",t:"Vault SWLB용도의 nginx"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/autoscaler.html",{loader:()=>u(()=>import("./autoscaler.html--G0q8-Ol.js"),__vite__mapDeps([163,1])),meta:{d:1644990252e3,g:["Nomad","sample","job","autoscaler","aws"],e:` +`,r:{minutes:1.2,words:361},y:"a",t:"Autoscaler"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/code-server.html",{loader:()=>u(()=>import("./code-server.html-Cf6r2Y5O.js"),__vite__mapDeps([164,1])),meta:{d:1669181764e3,g:["Nomad","Sample","Job","vs-code"],e:` +
      +
    • vs-code를 local이 아닌 환경에서 사용할 수 있도록 도와주는 code-server의 예시입니다.
    • +
    • 이 code의 변수는 nomad variable를 활용해서 배포합니다.
    • +
    +

    변수 선언

    +
      +
    • web ui 접근 password와 code-server terminal에서 사용 할 sudo의 password 를 변수로 선언했습니다.
    • +
    +
    # nomad var put {path기반의 varialbes} key=vaule
    +$ nomad var put code/config password=password
    +
    `,r:{minutes:.41,words:122},y:"a",t:"code-server"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/elastic.html",{loader:()=>u(()=>import("./elastic.html-BMFJjvfm.js"),__vite__mapDeps([165,1])),meta:{d:1632449108e3,g:["Nomad","Sample","Job"],e:` +
    job "elastic" {
    +  datacenters = ["dc1"]
    +
    +  group "elastic" {
    +    network {
    +      port "db" {
    +        static = 9200
    +      }
    +      port "kibana" {
    +        static = 5601
    +      }
    +    }
    +
    +    service {
    +      port = "db"
    +
    +      check {
    +        type     = "tcp"
    +        interval = "10s"
    +        timeout  = "2s"
    +      }
    +    }
    +
    +    task "elasticsearch" {
    +      driver = "docker"
    +
    +      config {
    +        image = "docker.elastic.co/elasticsearch/elasticsearch:6.8.9"
    +        ports = ["db"]
    +        mount = [
    +          {
    +            type   = "bind"
    +            source = "local/elasticsearch.yml"
    +            target = "/usr/share/elasticsearch/config/elasticsearch.yml"
    +          }
    +        ]
    +      }
    +
    +      template {
    +        data = <<EOH
    +network.host: 0.0.0.0
    +discovery.seed_hosts: ["127.0.0.1"]
    +xpack.security.enabled: true
    +xpack.license.self_generated.type: trial
    +xpack.monitoring.collection.enabled: true
    +EOH
    +        destination = "local/elasticsearch.yml"
    +      }
    +
    +      env {
    +        # "discovery.type":"single-node",
    +        ES_JAVA_OPTS = "-Xms512m -Xmx1024m"
    +      }
    +
    +      resources {
    +        cpu    = 2000
    +        memory = 1024
    +      }
    +    }
    +
    +    task "kibana" {
    +      driver = "docker"
    +
    +      config {
    +        image = "docker.elastic.co/kibana/kibana:6.8.9"
    +        ports = ["kibana"]
    +        mount = [
    +          {
    +            type   = "bind"
    +            source = "local/kibana.yml"
    +            target = "/usr/share/kibana/config/kibana.yml"
    +          }
    +        ]
    +      }
    +
    +      template {
    +        data = <<EOH
    +server.name: kibana
    +elasticsearch.username: elastic
    +elasticsearch.password: elastic
    +EOH
    +        destination = "local/kibana.yml"
    +      }
    +
    +      env {
    +        ELASTICSEARCH_URL="http://172.28.128.31:9200"
    +      }
    +
    +      resources {
    +        cpu    = 1000
    +        memory = 1024
    +      }
    +    }
    +  }
    +}
    +
    +
    `,r:{minutes:.39,words:118},y:"a",t:"Elastic"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/elk_version7.html",{loader:()=>u(()=>import("./elk_version7.html-Cf6byBKT.js"),__vite__mapDeps([166,1])),meta:{d:1642040133e3,g:["Nomad","Sample","Job"],e:` +

    7버전에 elsaticsearch에서 기본패스워드를 찾지 못해서 env로 넣어줌

    +

    logstash는 적당한 샘플을 찾지 못해서 일단은 비워둠

    +
    job "elk" {
    +
    +    datacenters = ["dc1"]
    +
    +    constraint {
    +        attribute = "${attr.kernel.name}"
    +        value = "linux"
    +    }
    +
    +    update {
    +        stagger = "10s"
    +        max_parallel = 1
    +    }
    +
    +    group "elk" {
    +        count = 1
    +
    +        restart {
    +            attempts = 2
    +            interval = "1m"
    +            delay = "15s"
    +            mode = "delay"
    +        }
    +        network {
    +          port "elastic" {
    +            to     = 9200
    +            static = 9200
    +          }
    +          port "kibana" {
    +            to     = 5601
    +          }
    +          port "logstash" {
    +            to     = 5000
    +          }
    +        }
    +
    +        task "elasticsearch" {
    +            driver = "docker"
    +
    +           constraint {
    +             attribute = "${attr.unique.hostname}"
    +             value     = "slave2"
    +           }
    +
    +            config {
    +                image = "elasticsearch:7.16.2"
    +                ports = ["elastic"]
    +                volumes = [
    +          "local/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml",
    +        ]
    +      }
    +      template {
    +        data = <<EOF
    +cluster.name: "my-cluster"
    +network.host: 0.0.0.0
    +discovery.type: single-node
    +discovery.seed_hosts: ["127.0.0.1"]
    +xpack.security.enabled: true
    +xpack.license.self_generated.type: trial
    +xpack.monitoring.collection.enabled: true
    +EOF
    +        destination   = "local/elasticsearch.yml"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +            env {
    +              ELASTIC_PASSWORD = "elastic"
    +            }
    +
    +            service {
    +                name = "${TASKGROUP}-elasticsearch"
    +                port = "elastic"
    +            }
    +
    +            resources {
    +                cpu = 500
    +                memory = 2048
    +            }
    +        }
    +
    +        task "kibana" {
    +            driver = "docker"
    +
    +            constraint {
    +              attribute = "${attr.unique.hostname}"
    +              value     = "slave2"
    +            }
    +
    +            config {
    +                image = "kibana:7.16.2"
    +                ports = ["kibana"]
    +                volumes = [
    +          "local/kibana.yml:/usr/share/kibana/config/kibana.yml"
    +        ]
    +      }
    +      template {
    +        data = <<EOF
    +#
    +# ** THIS IS AN AUTO-GENERATED FILE **
    +#
    +
    +# Default Kibana configuration for docker target
    +server.host: "0.0.0.0"
    +server.shutdownTimeout: "5s"
    +elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
    +elasticsearch.username: elastic
    +elasticsearch.password: elastic
    +EOF
    +
    +        destination   = "local/kibana.yml"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +
    +            service {
    +                name = "${TASKGROUP}-kibana"
    +                port = "kibana"
    +                check {
    +                    type = "http"
    +                    path = "/"
    +                    interval = "10s"
    +                    timeout = "2s"
    +                }
    +            }
    +
    +            resources {
    +                cpu = 500
    +                memory = 1200
    +            }
    +        }
    +
    +        task "logstash" {
    +            driver = "docker"
    +
    +            constraint {
    +              attribute = "${attr.unique.hostname}"
    +              value     = "slave2"
    +            }
    +
    +            config {
    +                image = "logstash:7.16.2"
    +                ports = ["logstash"]
    +                volumes = [
    +          "local/logstash.yml:/usr/share/logstash/config/logstash.yml"
    +        ]
    +      }
    +      template {
    +        data = <<EOF
    +http.host: "0.0.0.0"
    +xpack.monitoring.elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
    +EOF
    +
    +        destination   = "local/logstash.yml"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +
    +            service {
    +                name = "${TASKGROUP}-logstash"
    +                port = "logstash"
    +            }
    +
    +            resources {
    +                cpu = 200
    +                memory = 1024
    +            }
    +        }
    +    }
    +}
    +
    +
    `,r:{minutes:.79,words:236},y:"a",t:"elk_version7"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/ingress-gateway.html",{loader:()=>u(()=>import("./ingress-gateway.html-9SiCbfk_.js"),__vite__mapDeps([167,1])),meta:{d:164015281e4,g:["Nomad","Sample","Job",""],e:` +

    Nomad job으로 ingress gateway 사용하기 (with consul)

    +
      +
    • consul로 설정하는 ingress gateway가 아닌 Nomad job 기동 시에 ingress gateway 활성화 예제 +
        +
      • hashicorp 공식 예제가 아닌 다른 걸 해보려하다, 특이한 부분을 확인함
      • +
      +
    • +
    +

    테스트 job (python fastapi)

    +
    job "22-fastapi" {
    +  datacenters = ["dc1"]
    +
    +  group "fastapi" {
    +
    +    network {
    +      mode = "bridge"
    +      #service가 80으로 뜸, 만약 다른 포트로 뜨는 서비스를 사용 할 경우 image와 to만 변경
    +      port "http" {
    +        to = 80
    +      }
    +    }
    +    
    +    service {
    +      name = "fastapi"
    +      #여기서 port에 위에서 미리 선언한 http를 쓸 경우 다이나믹한 port를 가져오는데 
    +      #그럴 경우 ingress gateway에서 못 읽어 온다.
    +      port = "80"
    +      connect {
    +        sidecar_service{}
    +      }
    +    }
    +
    +    task "fastapi" {
    +      driver = "docker"
    +
    +      config {
    +        image = "tiangolo/uvicorn-gunicorn-fastapi"
    +        ports = ["http"]
    +      }
    +
    +      resources {
    +        cpu    = 500
    +        memory = 282
    +      }
    +    }
    +    scaling  {
    +      enabled = true
    +      min     = 1
    +      max     = 3
    +
    +      policy {
    +        evaluation_interval = "5s"
    +        cooldown            = "1m"
    +        #driver = "nomad-apm"
    +        check "mem_allocated_percentage" {
    +          source = "nomad-apm"
    +          query  = "max_memory"
    +
    +          strategy "target-value" {
    +            target = 80
    +          }
    +        }
    +      }
    +    }
    +  }
    +}
    +
    +
    `,r:{minutes:.56,words:169},y:"a",t:"Nomad ingress gateway"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/install-ansible-docker.html",{loader:()=>u(()=>import("./install-ansible-docker.html-DRjH0VEL.js"),__vite__mapDeps([168,1])),meta:{d:1693402327e3,g:["Nomad","Ansible","Job","Docker"],e:` +
    +

    참고 : https://discuss.hashicorp.com/t/escape-characters-recognized-as-a-variable-in-template-stanza/40525

    +
    +

    Nomad를 통해 Ops작업을 수행할 때 sysbatch 타입의 Job에 Ansible을 raw_exec로 실행하면 전체 노드에서 일괄로 작업을 수행할 수 있다.

    `,r:{minutes:.84,words:252},y:"a",t:"Nomad에서 Ansible로 Docker 설치와 Template 주의사항"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/jboss.html",{loader:()=>u(()=>import("./jboss.html-BXExyhGy.js"),__vite__mapDeps([169,1])),meta:{d:1639892483e3,g:["Nomad","Sample","Job","wildfly","JBoss"],e:` +
    +

    image info : https://quay.io/repository/wildfly/wildfly?tab=info
    +github : https://github.com/jboss-dockerfiles/wildfly
    +wildfly docker example : http://www.mastertheboss.com/soa-cloud/docker/deploying-applications-on-your-docker-wildfly-image/

    +
    `,r:{minutes:1.05,words:314},y:"a",t:"Wildfly(Jboss)"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/jenkins_java_driver.html",{loader:()=>u(()=>import("./jenkins_java_driver.html-hGi3Yh8x.js"),__vite__mapDeps([170,1])),meta:{d:166773544e4,g:["Nomad","Sample","Job","Jenkins"],e:` +
    +

    Nomad

    + +
    `,r:{minutes:1.29,words:387},y:"a",t:"Jenkins(Java Driver) on Nomad"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/keycloak.html",{loader:()=>u(()=>import("./keycloak.html-CaOYyGHJ.js"),__vite__mapDeps([171,1])),meta:{d:16324491e5,g:["Nomad","Sample","Job"],e:` +

    Keycloak은 Stateful 한 특성이 있어서 볼륨을 붙여주는것이 좋다.

    +
    // nomad namespace apply -description "Keycloak" keycloak
    +
    +job "keycloak" {
    +  type = "service"
    +  datacenters = ["dc1"]
    +  namespace = "keycloak"
    +
    +  group "keycloak" {
    +    count = 1
    +
    +    volume "keycloak-vol" {
    +      type = "host"
    +      read_only = false
    +      source = "keycloak-vol"
    +    }
    +
    +    task "keycloak" {
    +      driver = "docker"
    +
    +      volume_mount {
    +        volume = "keycloak-vol"
    +        destination = "/opt/jboss/keycloak/standalone/data"
    +        read_only = false
    +      }
    +
    +      config {
    +        image = "quay.io/keycloak/keycloak:14.0.0"
    +        port_map {
    +          keycloak = 8080
    +          callback = 8250
    +        }
    +      }
    +      
    +      env {
    +        KEYCLOAK_USER = "admin"
    +        KEYCLOAK_PASSWORD = "admin"
    +      }
    +
    +      resources {
    +        memory = 550
    +
    +        network {
    +          port "keycloak" {
    +            static = 18080
    +          }
    +          port "callback" {
    +            static = 18250
    +          }
    +        }
    +      }
    +
    +      service {
    +        name = "keycloak"
    +        tags = ["auth"]
    +
    +        check {
    +          type  = "tcp"
    +          interval = "10s"
    +          timeout  = "2s"
    +          port  = "keycloak"
    +        }
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.31,words:93},y:"a",t:"Keycloak"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/mongodb.html",{loader:()=>u(()=>import("./mongodb.html-MginIp_a.js"),__vite__mapDeps([172,1])),meta:{d:1632449108e3,g:["Nomad","Sample","Job"],e:` +
    job "mongodb" {
    +  datacenters = ["dc1"]
    +
    +  group "mongodb" {
    +    network {
    +      port "db" {
    +        static = 27017
    +      }
    +    }
    +
    +    service {
    +      port = "db"
    +
    +      check {
    +        type     = "tcp"
    +        interval = "10s"
    +        timeout  = "2s"
    +      }
    +    }
    +
    +    task "mongodb" {
    +      driver = "docker"
    +
    +      config {
    +        image = "mongo:3.6.21"
    +        ports = ["db"]
    +      }
    +
    +      env {
    +        MONGO_INITDB_ROOT_USERNAME = "admin"
    +        MONGO_INITDB_ROOT_PASSWORD = "password"
    +      }
    +
    +      resources {
    +        cpu    = 2000
    +        memory = 1024
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.17,words:50},y:"a",t:"MongoDB"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/nexus.html",{loader:()=>u(()=>import("./nexus.html-CbAkU-gG.js"),__vite__mapDeps([173,1])),meta:{d:1656325647e3,g:["Nomad","Sample","Job"],e:` +
    job "nexus" {
    +  datacenters = ["dc1"]
    +
    +  group "nexus" {
    +    count = 1
    +
    +    network {
    +      port "http" {
    +        to = 8081
    +        static = 8081
    +      }
    +    }
    +
    +    task "nexus" {
    +      driver = "docker"
    +
    +      config {
    +        image = "sonatype/nexus3"
    +        ports = ["http"]
    +      }
    +      
    +      env {
    +        INSTALL4J_ADD_VM_PARAMS = "-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/some-other-dir"  
    +      }
    +      
    +      resources {
    +        cpu    = 1000
    +        memory = 2703
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.16,words:49},y:"a",t:"Nexus"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/nginx.html",{loader:()=>u(()=>import("./nginx.html-L9iFTT7o.js"),__vite__mapDeps([174,1])),meta:{d:1633418665e3,g:["Nomad","Sample","Job","reverse proxy","consul service discovery"],e:` +
      +
    • nomad와 consul로 cluster로 구성되어 있는 환경에 L4이후에 nomad로 LB를 해야할 경우 사용 +
        +
      • nginx server_name설정에서 도메인을 받아오고 location에서는 각각의 서브도메인 별로 reverse proxy 동작 +
          +
        • reverse proxy(up stream)에서는 각각의 consul의 등록된 서비스 호출
        • +
        +
      • +
      +
    • +
    +
    job "nginx" {
    +  datacenters = ["dc1"]
    +
    +  group "nginx" {
    +    //인증서는 host volume에 업로드
    +    volume "certs" {
    +      type      = "host"
    +      source    = "certs"
    +      read_only = true
    +    }
    + 
    +    network {
    +      port "http" {
    +        static  = 80
    +        to      = 80
    +      }
    +      port "https" {
    +        to      = 443
    +        static  = 443
    +      }
    +    }
    +
    +    service {
    +      name = "nginx"
    +      port = "http"
    +      tags = ["web"]
    +      check {    
    +        type     = "tcp"
    +        port     = "http"
    +        interval = "2s"
    +        timeout  = "2s"
    +      }
    +    }
    +
    +    task "server" {
    +
    +      driver = "docker"
    +
    +      volume_mount {
    +        volume      = "certs"
    +        destination = "/etc/nginx/certs"
    +      }
    +
    +      config {
    +        image = "nginx"
    +        ports = ["http","https"]
    +        #ports = ["http","https"]
    +        volumes = [
    +          "local:/etc/nginx/conf.d",
    +        ]
    +      }
    +
    +      template {
    +        data = <<EOF
    +
    +upstream login.shoping.co.kr {
    +{{ range service "login" }}
    +  server {{ .Address }}:{{ .Port }};
    +{{ else }}server 127.0.0.1:65535; # force a 502
    +{{ end }}
    +}
    +upstream singup.shoping.co.kr {
    +{{ range service "signup" }}
    +  server {{ .Address }}:{{ .Port }};
    +{{ else }}server 127.0.0.1:65535; # force a 502
    +{{ end }}
    +}
    +upstream main.shoping.co.kr {
    +{{ range service "main" }}
    +  server {{ .Address }}:{{ .Port }};
    +{{ else }}server 127.0.0.1:65535; # force a 502
    +{{ end }}
    +}
    +
    +server {
    +   listen 80;
    +   listen 443 ssl;
    +   //domain 및 subdomain호출
    +   server_name *.shoping.co.kr;
    +   ssl_certificate "/etc/nginx/certs/server.pem";
    +   ssl_certificate_key "/etc/nginx/certs/server.key";
    +   proxy_read_timeout      300;
    +   proxy_buffers           64 16k;
    +
    +   //각 sub도메인별
    +   location / {
    +      if ($host = login.shoping.co.kr) {
    +        proxy_pass login.shoping.co.kr;
    +      }
    +      if ($host = singup.shoping.co.kr) {
    +        proxy_pass singup.shoping.co.kr;
    +      }
    +      if ($host !~ "(.*main)") {
    +        proxy_pass main.shoping.co.kr;
    +      }
    +   }
    +}
    +
    +
    +
    +EOF
    +
    +        destination   = "local/load-balancer.conf"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +      resources {
    +        cpu = 2000
    +        memory = 2000
    +      }
    +    }
    +  }
    +}
    +
    +
    +
    `,r:{minutes:.67,words:200},y:"a",t:"nginx"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/nomad-pack.html",{loader:()=>u(()=>import("./nomad-pack.html-CG6ZvRfF.js"),__vite__mapDeps([175,1])),meta:{d:16395807e5,g:["Nomad","Sample","Job","nomad-pack","vuepress"],e:` +`,r:{minutes:1.76,words:527},y:"a",t:"nomad-pack custom registry"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/oracleXE.html",{loader:()=>u(()=>import("./oracleXE.html-D0RbxaR-.js"),__vite__mapDeps([176,1])),meta:{d:1632449108e3,g:["Nomad","Sample","Job"],e:` +
    job "oracle" {
    +  datacenters = ["dc1"]
    +
    +  group "oracle" {
    +    network {
    +      port "db" {
    +        static = 1521
    +      }
    +      port "manage" {
    +        static = 5500
    +      }
    +    }
    +
    +    service {
    +      port = "db"
    +
    +      check {
    +        type     = "tcp"
    +        interval = "10s"
    +        timeout  = "2s"
    +      }
    +    }
    +
    +    task "oracle" {
    +      driver = "docker"
    +
    +      config {
    +        image = "oracle/database:18.4.0-xe"
    +        ports = ["db", "manage"]
    +      }
    +
    +      env {
    +        DB_MEMORY = "2GB"
    +        ORACLE_PWD = "password"
    +        ORACLE_SID = "XE"
    +      }
    +
    +      resources {
    +        cpu    = 2000
    +        memory = 1024
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.2,words:59},y:"a",t:"Oracle XE"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/param-batch-job.html",{loader:()=>u(()=>import("./param-batch-job.html-b8M0pZxI.js"),__vite__mapDeps([177,1])),meta:{d:1640585468e3,g:["Nomad","Sample","Job","param","batch"],e:` +
      +
    • parameter를 받아와서 해당 값을 이용하여 다음으로 실행될 job의 값을 다이나믹하게 넣어 만드는 샘플 +
        +
      • meta_required에 원하는 값을 넣고 template에 env "NOMAD_META_메타명(key)"와 같이 넣어주면 된다.
      • +
      +
    • +
    +
    job "24-paramete" {
    +  datacenters = ["dc1"]
    +  type = "batch"
    +
    +  parameterized {
    +    payload       = "forbidden"
    +    meta_required = ["room_num"]
    +  }
    +
    +  group "run-main-job" {
    +
    +    task "run-main-job" {
    +      driver = "raw_exec"
    +
    +      config {
    +        command = "nomad"
    +        # arguments
    +        args = ["job", "run", "${NOMAD_TASK_DIR}/room.job" ]
    +      }
    +      template {
    +        data = <<EOH
    +
    +#####################
    +
    +job "{{ env "NOMAD_META_room_num" }}" {
    +    datacenters = ["dc1"]
    +
    +    group "jboss" {
    +
    +        network {
    +            port "http" {
    +                to = "8080"
    +            }
    +        }
    +        service {
    +            port = "http"
    +            name = "{{ env "NOMAD_META_room_num" }}"
    +            check {
    +                type     = "tcp"
    +                interval = "10s"
    +                timeout  = "2s"
    +            }
    +        }
    +        task "http" {
    +            driver = "docker"
    +            config {
    +                image = "jboss/wildfly"
    +                ports = ["http"]
    +            }
    +            resources {
    +                cpu    = 500
    +                memory = 282
    +            }
    +        }
    +    }
    +}
    +
    +EOH
    +    destination = "local/room.job"
    +      }
    +    }
    +  }
    +}
    +
    `,r:{minutes:.32,words:97},y:"a",t:"param-batch-job"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/redis.html",{loader:()=>u(()=>import("./redis.html-CxghhVv_.js"),__vite__mapDeps([178,1])),meta:{d:1633418665e3,g:["Nomad","Sample","Job"],e:` +
      +
    • 추가내용: redis는 data dir, cluster info dir(클러스터 시) 이 두개의 dir가 필요하여 volume을 추가로 붙여줘야한다. +
        +
      • data dir을 변경이 번거로움(docker build를 다시 해야함) 그래서 클러스터 시에는 docker build, nomad volume으로 나눔과 같은 방법이 있음
      • +
      • cluster-enabled : 클러스터로 사용합니다. (cluster volume으로 빼둬야됨)
      • +
      • cluster-config-file : 노드별로 클러스터 노드 정보를 conf 파일에 저장합니다.
      • +
      • cluster-node-timeout : 노드간 통신이 되지 않아 timeout 되는 시간을 설정합니다.
      • +
      +
    • +
    `,r:{minutes:.85,words:255},y:"a",t:"redis"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/scouter.html",{loader:()=>u(()=>import("./scouter.html-N24lYJY6.js"),__vite__mapDeps([179,1])),meta:{d:1639485865e3,g:["Nomad","Consul","Scouter","Job"],e:` +
      +
    • 공식 Github : https://github.com/scouter-project/scouter
    • +
    • Scouter 다운로드 +
        +
      • scouter collector와 host-agent 를 실행하는 job에서 버전정보를 기반으로 다운로드
      • +
      • host agent는 system 타입으로 모든 노드에서 실행되도록 구성
      • +
      +
    • +
    • tomcat 다운로드 +
        +
      • 다운로드 받지않고 호스트에 미리 설치해 놓는 방식이 더 가벼워보임 --> 아마도 Packer, Terraform의 조합이면 가능
      • +
      • 다운로드 받게 구성하면 컨테이너처럼 Nomad가 배포할 때마다 다운받아서 구성 가능
      • +
      +
    • +
    • Template - server.xml +
        +
      • server.xml 기본 구성에서 port가 선언되는 자리를 java option에서 받을 수 있도록 변경
      • +
      • Template 구성도 가능하고 미리 구성한 xml을 다운로드 받게 하는것도 괜찮아 보임
      • +
      +
    • +
    • Consul과 함께 구성된 경우 Nginx같은 LB 구성 Job 에서 backend를 동적으로 구성 가능
    • +
    • Nomad에 Scouter Collector와 Host Agent를 위한 별도 Namespace를 구성하는 것도 관리를 위해 좋아보임
      +nomad namespace apply -description "scouter" scouter
    • +
    `,r:{minutes:2.03,words:608},y:"a",t:"Scouter"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/service-mesh-test.html",{loader:()=>u(()=>import("./service-mesh-test.html-ByTBwNOA.js"),__vite__mapDeps([180,1])),meta:{d:1632449108e3,g:["Nomad","Consul","Sample","Job","Service Mesh"],e:` +

    HashiCorp 공식 Service Mesh Test App

    +

    https://learn.hashicorp.com/tutorials/nomad/consul-service-mesh

    +
    job "countdash" {
    +  region      = "global"
    +  datacenters = ["dc1"]
    +  # namespace   = "mesh"
    +
    +  group "api" {
    +    network {
    +      mode = "bridge"
    +      port "api" {
    +        to = 9001 # static 설정이 없으므로 컨테이너의 앱 9001과 노출되는 임의의 포트와 맵핑
    +      }
    +    }
    +
    +    service {
    +      name = "count-api"
    +      port = "api" # 임의의 포트 할당을 network port id로 지정
    +
    +      connect {
    +        sidecar_service {}
    +      }
    +    }
    +
    +    task "web" {
    +      driver = "docker"
    +      config {
    +        image = "hashicorpnomad/counter-api:v1"
    +        ports = ["api"]
    +      }
    +    }
    +  }
    +
    +  group "dashboard" {
    +    network {
    +      mode = "bridge"
    +      port "http" {
    +        static = 9002 # 컨테이너 앱 9002와 외부노출되는 사용자 지정 static port를 맵핑
    +        to     = 9002
    +      }
    +    }
    +
    +    service {
    +      name = "count-dashboard"
    +      port = "http" # 할당된 포트를 network port id로 지정
    +
    +      connect {
    +        sidecar_service {
    +          proxy {
    +            upstreams {
    +              destination_name = "count-api"
    +              local_bind_port  = 8080 # backend인 count-api의 실제 port와는 별개로 frontend가 호출할 port 지정
    +            }
    +          }
    +        }
    +      }
    +    }
    +
    +    task "dashboard" {
    +      driver = "docker"
    +      env {
    +        COUNTING_SERVICE_URL = "http://${NOMAD_UPSTREAM_ADDR_count_api}"
    +      }
    +      config {
    +        image = "hashicorpnomad/counter-dashboard:v1"
    +      }
    +    }
    +
    +    scaling {
    +      enabled = true
    +      min = 1
    +      max = 10
    +    }
    +  }
    +}
    +
    `,r:{minutes:.41,words:124},y:"a",t:"Service Mesh Test"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/sidecar-tomcat.html",{loader:()=>u(()=>import("./sidecar-tomcat.html-CJjpWreP.js"),__vite__mapDeps([181,1])),meta:{d:1634806591e3,g:["Nomad","Sample","Job","sidecar","tomcat"],e:` +`,r:{minutes:.27,words:82},y:"a",t:"tomcat-sidecar"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/tomcat.html",{loader:()=>u(()=>import("./tomcat.html-DFo0Pri6.js"),__vite__mapDeps([182,1])),meta:{d:1632449108e3,g:["Nomad","Consul","Sample","Job"],e:` +
      +
    • tomcat 다운로드 +
        +
      • 다운로드 받지않고 호스트에 미리 설치해 놓는 방식이 더 가벼워보임 --> 아마도 Packer, Terraform의 조합이면 가능
      • +
      • 다운로드 받게 구성하면 컨테이너처럼 Nomad가 배포할 때마다 다운받아서 구성 가능
      • +
      +
    • +
    • Template - server.xml +
        +
      • server.xml 기본 구성에서 port가 선언되는 자리를 java option에서 받을 수 있도록 변경
      • +
      • Template 구성도 가능하고 미리 구성한 xml을 다운로드 받게 하는것도 괜찮아 보임
      • +
      +
    • +
    • Consul과 함께 구성된 경우 Nginx같은 LB 구성 Job 에서 backend를 동적으로 구성 가능
    • +
    `,r:{minutes:2.07,words:620},y:"a",t:"Tomcat"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/update.html",{loader:()=>u(()=>import("./update.html-B_F_Bdp6.js"),__vite__mapDeps([183,1])),meta:{d:163347429e4,g:["Nomad","Sample","Job"],e:` +
    +

    +

    nomad의 배포 방법 중 canary와 rolling update 관련된 내용입니다.

    +
    +
    ...
    +  #canary update - 새로운 버전의 task를 canary 변수의 수만큼 기동시키고 상황에 맞게 확인 후 배포
    +  group "canary" {
    +    count = 5
    +
    +    update {
    +      max_parallel     = 1
    +      canary           = 1
    +      min_healthy_time = "30s"
    +      healthy_deadline = "10m"
    +      #배포 실패시 자동으로 전 버전으로 돌아감(배포 중이던 task 제거됨)
    +      auto_revert      = true
    +      #task가 기동되어도 자동으로 다음 버전으로 넘어가지 않음(배포 전 버전 task 제거되지않음)
    +      auto_promote     = false
    +    }
    +  }
    +  #rolling update - 기동 중이던 task를 하나씩(max_parallel만큼) 신규 task로 변환하면서 배포
    +  group "api-server" {
    +    count = 6
    +
    +    update {
    +      max_parallel     = 2
    +      min_healthy_time = "30s"
    +      healthy_deadline = "10m"
    +    }
    +  }
    +  #배포 시 service에 canary로 배포된 task에만 붙일 수 있는 tag 설정
    +  service {
    +    port = "http"
    +    name = "canary-deployments"
    +
    +    tags = [
    +      "live"
    +    ]
    +
    +    canary_tags = [
    +      "canary"
    +    ]
    +}
    +...
    +
    `,r:{minutes:.24,words:72},y:"a",t:"update"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/withConsulKV.html",{loader:()=>u(()=>import("./withConsulKV.html-vguQwm4X.js"),__vite__mapDeps([184,1])),meta:{d:1633322494e3,g:["Nomad","Sample","Job"],e:` +

    Consul의 KV에 값을 저장하고 비교하여 task batch를 수행하는 예제

    +
      +
    • curl 을 사용하는 경우
      curl -X GET http://127.0.0.1:8500/v1/kv/docmoa/commit_date | jq -r '.[0].Value | @base64d'
      +
    • +
    • template의 key를 사용하는 경우
      {{ key "docmoa/commit_date" }}
      +
    • +
    `,r:{minutes:.54,words:162},y:"a",t:"Consul KV Sample"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/withVaultKV.html",{loader:()=>u(()=>import("./withVaultKV.html-BnsB2ERk.js"),__vite__mapDeps([185,1])),meta:{d:1642495624e3,g:["Nomad","Sample","Job"],e:` +

    nomad job에서 vault의 secret에서 kv사용하기

    + +

    nomad hcl 설정

    +
    job "logs" {
    +    datacenters = ["dc1"]
    +
    +    constraint {
    +        attribute = "${attr.kernel.name}"
    +        value = "linux"
    +    }
    +
    +    update {
    +        stagger = "10s"
    +        max_parallel = 1
    +    }
    +
    +    group "elk" {
    +        count = 1
    +
    +        restart {
    +            attempts = 2
    +            interval = "1m"
    +            delay = "15s"
    +            mode = "delay"
    +        }
    +        network {
    +          port "elk" {
    +            to     = 9200
    +            static = 9200
    +          }
    +          port "kibana" {
    +            to     = 5601
    +          }
    +          port "logstash" {
    +            to     = 5000
    +          }
    +        }
    +
    +        task "elasticsearch" {
    +            driver = "docker"
    +
    +            vault {
    +              policies  = ["admin"]
    +              change_mode   = "signal"
    +              change_signal = "SIGINT"
    +            }
    +
    +            config {
    +                image = "elasticsearch:7.16.2"
    +                ports = ["elk"]
    +                volumes = [
    +          "local/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml",
    +        ]
    +      }
    +      template {
    +        data = <<EOF
    +cluster.name: "my-cluster"
    +network.host: 0.0.0.0
    +discovery.type: single-node
    +discovery.seed_hosts: ["127.0.0.1"]
    +xpack.security.enabled: true
    +xpack.license.self_generated.type: trial
    +xpack.monitoring.collection.enabled: true
    +EOF
    +        destination   = "local/elasticsearch.yml"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +      template {
    +        data = <<EOH
    +ELASTIC_PASSWORD="{{with secret "secret/elastic"}}{{.Data.passwd}}{{end}}"
    +EOH
    +
    +  destination = "secrets/file.env"
    +  env         = true
    +}
    +
    +            service {
    +                name = "${TASKGROUP}-elasticsearch"
    +                port = "elk"
    +            }
    +
    +            resources {
    +                cpu = 500
    +                memory = 1048
    +            }
    +        }
    +
    +        task "kibana" {
    +            driver = "docker"
    +
    +            vault {
    +              policies  = ["admin"]
    +              change_mode   = "signal"
    +              change_signal = "SIGINT"
    +            }
    +
    +            config {
    +                image = "kibana:7.16.2"
    +                ports = ["kibana"]
    +                volumes = [
    +          "local/kibana.yml:/usr/share/kibana/config/kibana.yml"
    +        ]
    +      }
    +      template {
    +        data = <<EOF
    +#
    +# ** THIS IS AN AUTO-GENERATED FILE **
    +#
    +
    +# Default Kibana configuration for docker target
    +server.host: "0.0.0.0"
    +server.shutdownTimeout: "5s"
    +elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
    +elasticsearch.username: elastic
    +{{ with secret "secret/elastic" }}
    +elasticsearch.password: {{.Data.passwd}}
    +{{ end }}
    +
    +EOF
    +
    +        destination   = "local/kibana.yml"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +
    +            service {
    +                name = "${TASKGROUP}-kibana"
    +                port = "kibana"
    +                check {
    +                    type = "http"
    +                    path = "/"
    +                    interval = "10s"
    +                    timeout = "2s"
    +                }
    +            }
    +
    +            resources {
    +                cpu = 500
    +                memory = 1200
    +            }
    +        }
    +
    +        task "logstash" {
    +            driver = "docker"
    +
    +            config {
    +                image = "logstash:7.16.2"
    +                ports = ["logstash"]
    +                volumes = [
    +          "local/logstash.yml:/usr/share/logstash/config/logstash.yml"
    +        ]
    +      }
    +      template {
    +        data = <<EOF
    +http.host: "0.0.0.0"
    +xpack.monitoring.elasticsearch.hosts: [ "http://{{ env "NOMAD_IP_elk" }}:{{ env "NOMAD_PORT_elk" }}" ]
    +EOF
    +
    +        destination   = "local/logstash.yml"
    +        change_mode   = "signal"
    +        change_signal = "SIGHUP"
    +      }
    +
    +            service {
    +                name = "${TASKGROUP}-logstash"
    +                port = "logstash"
    +            }
    +
    +            resources {
    +                cpu = 200
    +                memory = 1024
    +            }
    +        }
    +    }
    +}
    +
    `,r:{minutes:.86,words:258},y:"a",t:"vaultKV"}}],["/04-HashiCorp/08-Updates/97-2024/2024-01.html",{loader:()=>u(()=>import("./2024-01.html-BqO2nuKk.js"),__vite__mapDeps([186,1])),meta:{d:1704238435e3,g:["Hashicorp","Update","Jan"],e:` +

    Product 소개

    +
      +
    • HashiCorp 2023 year in review: Community +
        +
      • Hashicorp Blog
      • +
      • 작년 2023년 한 해동안 있었던 Hashicorp 관련 이야기: 개최된 컨퍼런스 및 이벤트부터 새로 출시된 솔루션 별 트레이닝 및 자격증 관련, 그리고 창업자 Mitchell Hashimoto 의 퇴사 소식 등을 한 번에 확인하실 수 있습니다.
      • +
      +
    • +
    `,r:{minutes:1.12,words:336},y:"a",t:"2024년 1월"}}],["/04-HashiCorp/08-Updates/97-2024/2024-02.html",{loader:()=>u(()=>import("./2024-02.html-C7mJntCJ.js"),__vite__mapDeps([187,1])),meta:{d:17072917e5,g:["Hashicorp","Update","Feb"],e:` +

    Product 소개

    +
      +
    • HCP Vault Radar begins limited beta +
        +
      • Hashicorp Blog
      • +
      • 작년 2023년 10월 Hashiconf 에서 공개된 HCP Vault Radar 가 Alpha 를 거쳐 Beta 가 출시되었습니다. Beta 에서는 RBAC/ABAC 을 지원하며 스캔할 수 있는 새로운 데이터 소스도 선보입니다
      • +
      +
    • +
    `,r:{minutes:.75,words:226},y:"a",t:"2024년 2월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-01.html",{loader:()=>u(()=>import("./2023-01.html-ggQMyTJT.js"),__vite__mapDeps([188,1])),meta:{d:1672798178e3,g:["Hashicorp","Update","Jan"],e:` +

    Product 소개

    +
      +
    • +

      Dynamic Secrets for Waypoint with Vault

      +
        +
      • Hashicorp Blog
      • +
      • Application 에 대한 Build, Deployment 및 Monitoring 을 간소화하고 쉽게 접근할 수 있도록 개발자를 지원하는 Waypoint 에서도 이제 Vault 와 연동하여 Hashicorp Vault config sourcer plugin 을 통해 Application Config 에 포함되는 보안 정보를 관리할 수 있습니다.
      • +
      +
    • +
    `,r:{minutes:1.03,words:308},y:"a",t:"2023년 1월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-02.html",{loader:()=>u(()=>import("./2023-02.html-ClDBGKm0.js"),__vite__mapDeps([189,1])),meta:{d:1675423442e3,g:["Hashicorp","Update","Feb"],e:` +

    Product 소개

    +
      +
    • +

      Terraform Cloud Adds ‘Projects’ to Organize Workspaces at Scale

      +
        +
      • Hashicorp Blog
      • +
      • 기존의 Terraform Cloud 에서는 연관되는 Workspace 간 그룹화가 불가능하고 Organization 및 Workspace 단위로만 권한 할당이 가능함으로 인해 사용자들은 자원 활용에 대한 제약사항을 고려하여 Organization 및 Workspace 를 분할하는 불편함을 감수해야 했습니다. 'Projects' 는 Workspace 를 그룹화하고 Project 단위로 권한 설정을 지원함으로써 앞서 소개한 제약사항을 해소하도록 지원합니다.
      • +
      +
    • +
    `,r:{minutes:.95,words:285},y:"a",t:"2023년 2월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-03.html",{loader:()=>u(()=>import("./2023-03.html-C303hGPW.js"),__vite__mapDeps([190,1])),meta:{d:167819845e4,g:["Hashicorp","Update","Mar"],e:` +

    Product 소개

    +
      +
    • +

      Writing Terraform for unsupported resources

      +
        +
      • Hashicorp Blog
      • +
      • Terraform 과 대상 환경 간 연동을 위해 Provider 를 활용할 때 대상 환경이 API 을 통해서는 지원하는 기능이지만 Provider 에는 구현되어 있지 않아 사용할 수 없는 기능이 종종 있습니다 (예: Vault Provider 기반 Vault 운영 시 Unseal 기능 사용 불가). Terracurl 을 통해 API 을 통해서만 지원되는 기능들을 Terraform Code 로 구성하여 하나의 Resource 로 관리할 수 있습니다.
      • +
      +
    • +
    `,r:{minutes:1.23,words:370},y:"a",t:"2023년 3월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-04.html",{loader:()=>u(()=>import("./2023-04.html-sQgt4Mf_.js"),__vite__mapDeps([191,1])),meta:{d:1680766775e3,g:["Hashicorp","Update","Apr"],e:` +

    Product 소개

    +
      +
    • +

      Dynamic provider credentials now generally available for Terraform Cloud

      +
        +
      • Hashicorp Blog
      • +
      • AWS, MS Azure, GCP 등 Cloud 환경을 Terraform 과 연동 및 인증하기 위해 Workspace Variable 또는 Variable Set 을 활용하여 Credential 정보를 설정해서 사용했습니다. 해당 Credential 정보는 장기간의 TTL 을 설정하여 사용하거나 혹은 보안취약점을 보완하기 위해 관리자가 수동으로 갱신 및 설정하는 등의 번거로움을 동반하고 있었습니다. Dynamic Provider Credential 은 각 Cloud Service 의 OIDC Provider 를 기반으로 Terraform 에 대한 TLS 인증 확인을 수행함으로써 매 Apply 마다 Terraform 에 대한 인증 처리 후 Resource Provisioning 등을 수행하는 동적인증처리 를 지원합니다.
      • +
      • Hashicorp Officlal Tutorial 을 참고하여 테스트 해보실 수 있습니다.
      • +
      +
    • +
    `,r:{minutes:.96,words:287},y:"a",t:"2023년 4월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-05.html",{loader:()=>u(()=>import("./2023-05.html-iXkPu4qP.js"),__vite__mapDeps([192,1])),meta:{d:1682489034e3,g:["Hashicorp","Update","May"],e:` +

    Product 소개

    +`,r:{minutes:.91,words:272},y:"a",t:"2023년 5월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-06.html",{loader:()=>u(()=>import("./2023-06.html-BjObLeBp.js"),__vite__mapDeps([193,1])),meta:{d:1686116077e3,g:["Hashicorp","Update","Jun"],e:` +

    Product 소개

    +
      +
    • +

      Terraform Cloud updates plans with an enhanced Free tier and more flexibility

      +
        +
      • Hashicorp Blog
      • +
      • Terraform Cloud 에 대한 요금제가 개편됩니다. 요금제 개편과 함께 각 요금제에서 사용가능한 기능들도 추가되었습니다. (예를 들어 기존 Free Tier 에서는 사용불가 했던 Sentinel/OPA, SSO, Terraform Agent, Run Tasks 등)
      • +
      +
    • +
    • +

      Terraform Cloud adds Vault-backed dynamic credentials

      +
        +
      • Hashicorp Blog
      • +
      • 지난 4월 소개된 Dynamic provider credentials now generally available for Terraform Cloud 에 이어 Vault의 Cloud Secrets Engine (AWS, Azure, GCP) 를 연계한 동적 자격증명 발급 기능이 출시되었습니다. 이제, Terraform Apply 수행 시 마다 Vault 로 부터 자격증명을 발급받아 사용함으로써 보다 보안 강화된 워크플로우를 구성할 수 있습니다.
      • +
      +
    • +
    `,r:{minutes:1.29,words:388},y:"a",t:"2023년 6월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-07.html",{loader:()=>u(()=>import("./2023-07.html-CSUKPa-P.js"),__vite__mapDeps([194,1])),meta:{d:1689171954e3,g:["Hashicorp","Update","Jul"],e:` +

    Product 소개 (Hashidays 2023)

    +
      +
    • 매년 유럽 네덜란드에서 이틀간 진행되던 Hashiconf 가 올해는 한단계 더 성장하여 영국 런던, 프랑스 파리 그리고 독일 뮌헨에서 동시에 진행되는 Hashidays 2023 으로 개최되었습니다. 새롭게 펼쳐진 Hashidays 에서는 Terraform, Vault, Consul 그리고 Boundary 에 대해 그동안 사용자들이 사용하면서 느꼈던 불편함을 해소할 신기능을 출시했습니다. +
        +
      • Hashicorp Blog
      • +
      • HCP Vault Secrets (Public Beta): Vault 에서 가장 많이 활용되고 있는 Secret Engine 중 하나인 KV Engine 기반 시크릿 관리에 특화된 HCP SaaS 서비스로써 애플리케이션 개발 및 운영에 사용되는 시크릿에 대한 저장, 접근 그리고 자동 동기화 기능을 더욱 쉽게 구성 가능하도록 지원합니다. 특히 기존 사용 중인 AWS Secret Manager 등 클라우드 서비스와도 원클릭 동기화를 지원함으로써 기존 워크플로우에 대해 최소한의 변경으로 시크릿 동적관리가 가능합니다.
      • +
      • Vault Secrets Operator: CRD 기반으로 K8S의 Secrets 와 연계하여 Secrets 를 동적으로 관리함으로써 최소한의 추가 구성으로 기존 구성하여 사용중인 Secrets 에 대해 보안을 강화합니다.
      • +
      • Boundary Enterprise: 기존에 Opensource 그리고 HCP SaaS 로만 제공되던 Boundary 가 드디어 Enterprise Edition 이 출시되었습니다. Enterprise Edition 에서는 그동안 많은 사용자들이 필요로 했던 Session Recording 과 더불어 그외 다양한 신규 기능들이 추가되었습니다.
      • +
      • Terraform: 지난 6월호에서 소개한 Vault-backed dynamic credentials 의 정식 GA 출시, 그동안 너무 불편하고 어려웠던 Terraform import 를 보완해줄 Config-driven Import, 생성 및 관리 중인 자원에 대한 보다 효과적인 관리를 위한 Explorer (Beta) 등 다양한 기능이 추가되었습니다.
      • +
      • Consul 1.16: Envoy Proxy 에 Extension 이 출시되어 WASM (Web Assembly) Code 기반 추가 기능을 활용하거나 외부 보안 서비스와 연계하여 인증 기반 기능 활용합니다, 또한 여러 Cluster 에 걸쳐 동일 서비스에 대해 동일 서비스 이름을 사용하도록 하는 Sameness Groups 를 통해 서비스 관리 뿐만 아니라 장애 발생 시 수행하는 Failover 를 보다 간단하게 처리할 수 있습니다.
      • +
      +
    • +
    `,r:{minutes:1.49,words:448},y:"a",t:"2023년 7월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-08.html",{loader:()=>u(()=>import("./2023-08.html-Bq8h3G2V.js"),__vite__mapDeps([195,1])),meta:{d:1690862751e3,g:["Hashicorp","Update","Aug"],e:` +

    Product 소개

    +
      +
    • Using Terraform dynamic provider credentials in your AWS landing zones +
        +
      • Hashicorp Blog
      • +
      • 지난 4월 소개된 Terraform 사용 시 필요한 대상 환경에 대한 클라우드 자격증명을 Vault 와 연동하여 동적으로 사용 및 관리하는 Dynamic Provider Credentials 기능을 AWS Landing Zone 에서도 사용하실 수 있습니다. Terraform 과 함께 Landing Zone 으로 시작하는 AWS 의 여정에서 더 보안 강화된 IaC 를 경험해보세요!
      • +
      +
    • +
    `,r:{minutes:1.01,words:302},y:"a",t:"2023년 8월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-09.html",{loader:()=>u(()=>import("./2023-09.html-D8Ie08jE.js"),__vite__mapDeps([196,1])),meta:{d:1693797134e3,g:["Hashicorp","Update","Sep"],e:` +

    Product 소개

    +
      +
    • Terraform ephemeral workspaces public beta now available +
        +
      • Hashicorp Blog
      • +
      • 개발 및 테스트 등을 위해 임시로 사용하는 Workspace 에 대해 사용 완료 후 방치함으로 인해 발생되는 자원 낭비 또는 보안 유출 위험성을 방지하고자 Workspace 사용에 대해 미리 시간 설정을 할 수 있는 기능이 베타로 출시되었습니다. 이제, 정해놓은 타이머가 도래하면 Workspace 와 해당 Workspace 를 통해 생성한 자원을 자동 폐기 및 정리함으로써 자원 관리의 효율성을 높이고 미사용 자원에 대한 보안 유출 등을 방지할 수 있습니다. 베타 버전은 Terraform Cloud (Plus 요금제 이상) 에서 체험 및 사용 가능합니다.
      • +
      +
    • +
    `,r:{minutes:.95,words:284},y:"a",t:"2023년 9월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-10.html",{loader:()=>u(()=>import("./2023-10.html-DCZIi768.js"),__vite__mapDeps([197,1])),meta:{d:1696476441e3,g:["Hashicorp","Update","Oct"],e:` +

    Product 소개

    +
      +
    • Creating a multi-cloud golden image pipeline with Terraform Cloud and HCP Packer +
        +
      • Hashicorp Blog
      • +
      • 조직 내 클라우드 사용환경에서 "표준화" 되지 않은 VM OS Image 로 인해 장애 발생 시 케이스를 표준화 하지 못하고 대응에 미진한 경우를 종종 접하곤 합니다. Hashicorp Packer 와 Terraform 을 연동하여 조직 내 사용중인 각 클라우드 환경 마다 Golden OS Image 를 구성하고 이를 활용하여 인스턴스 자원 배포하는 과정까지의 라이프사이클을 "표준화" 함으로써 보다 더 효율적이고 안정적인 시스템 환경을 구성해보세요.
      • +
      +
    • +
    `,r:{minutes:.9,words:271},y:"a",t:"2023년 10월"}}],["/04-HashiCorp/08-Updates/98-2023/2023-11.html",{loader:()=>u(()=>import("./2023-11.html-CTNmw6DG.js"),__vite__mapDeps([198,1])),meta:{d:1698990531e3,g:["Hashicorp","Update","Nov"],e:` +

    Product 소개

    +
      +
    • Infrastructure and security releases open HashiConf 2023 +
        +
      • Hashicorp Blog
      • +
      • 샌프란시스코에서 개최된 Hashiconf 2023 에서 8가지의 솔루션에 대해 그룹을 크게 Infrastructure 와 Security 로 구분지어 앞으로의 솔루션 포트폴리오 및 업데이트를 진행하며, Terraform test framework, Vault Secret Sync, Vault Radar 등 워크플로우 개선을 위한 새로운 기능이 공개했습니다. 자세한 사항은 행사 현장을 직접 다녀온 이들이 전해주는 Hashicorp Korea: Hashiconf 2023 에서 확인하세요!
      • +
      +
    • +
    `,r:{minutes:.99,words:298},y:"a",t:"2023년 11월"}}],["/04-HashiCorp/08-Updates/99-2022/2022-07.html",{loader:()=>u(()=>import("./2022-07.html-DG6lpmIo.js"),__vite__mapDeps([199,1])),meta:{d:1656980314e3,g:["Hashicorp","Update","July"],e:` +

    Product 소개

    +
      +
    • +

      HCP Boundary 출시 (Public Beta)

      +
        +
      • HCP Boundary 소개 Blog
      • +
      • Hashicorp Korea Snapshot
      • +
      • HCP Boundary 시작하기
      • +
      • Hashicorp 는 모든 솔루션에 대해 사용자가 직접 설치하는 설치형 을 비롯해 이와 동일한 경험을 기반으로 SaaS 형태의 Cloud 서비스를 제공하고 있습니다. 지난 6월 21일, HCP Boundary 의 Public Beta 가 공개되어 무료 제공 중입니다.
      • +
      • AWS Platform 에 One click 으로 Cluster 생성 및 이용 가능하며 간단한 Network Peering 과정을 거쳐 AWS Platform 에 구성된 HVN (Hashicorp Virtual Network) 및 Cluster 와 사용자의 AWS 환경을 연결하여 미리 구성한 서비스들을 연동합니다. (AWS 지원 Region 확장 및 타 Cloud Platform 지원 예정)
      • +
      • HCP 계정 생성 시, USD 50불이 기본 Credit 으로 제공되며 이를 활용하여 Boundary Public Beta 외에도 다양한 Vault, Consul 과 같은 HCP Service 을 약 1개월간 체험해보실 수 있습니다.
      • +
      +
    • +
    • +

      Hashicorp Developer Site 출시 (Public Beta)

      +
        +
      • Hashicorp Developer Site 소개 Blog
      • +
      • Hashicorp Developer Site
      • +
      • Tutorial 과 Reference Architecture 정보가 learn.hashicorp.com 을 비롯, 각 solution 별 website 에 파편화 되어 있어 Hashicorp Solution 을 보다 더 쉽게 이해하고 업무에 적용하는데에 어려움이 있었습니다. 새롭게 출시된 Hashicorp Developer Site 에서는 이러한 그동안 축적된 유용한 자료와 이를 테스트 해볼 수 있는 환경을 한 곳에 모아 통합 제공함으로써 보다 더 쉽게 Hashicorp Solution 을 경험할 수 있습니다.
      • +
      • Public Beta 기간에는 Hashicorp Solution 중 Vault 와 Waypoint 에 대해 이용 가능하고, 추후 모든 Solution 에 대해 제공 예정입니다.
      • +
      +
    • +
    `,r:{minutes:1.27,words:382},y:"a",t:"2022년 7월"}}],["/04-HashiCorp/08-Updates/99-2022/2022-08.html",{loader:()=>u(()=>import("./2022-08.html-4-PzckqW.js"),__vite__mapDeps([200,1])),meta:{d:1659510375e3,g:["Hashicorp","Update","Aug"],e:` +

    Product 소개

    +
      +
    • +

      Consul Service Mesh 에 대한 AWS Lambda 지원 (Public Beta)

      + +
    • +
    `,r:{minutes:.69,words:207},y:"a",t:"2022년 8월"}}],["/04-HashiCorp/08-Updates/99-2022/2022-09.html",{loader:()=>u(()=>import("./2022-09.html-DEZ_nh_3.js"),__vite__mapDeps([201,1])),meta:{d:1662203164e3,g:["Hashicorp","Update","Sep"],e:` +

    Product 소개

    +
      +
    • +

      CDKTF (Cloud Development Kit for Terraform) General Available

      +
        +
      • Hashicorp Blog
      • +
      • Python, Go 등 프로그래밍 언어 기반으로 Terraform 을 활용하실 수 있도록 지원하는 CDKTF 가 정식 출시 되었습니다. CDKTF를 사용하면 개발자는 익숙한 프로그래밍 언어에서 컨텍스트 전환 없이 코드로 인프라를 설정할 수 있으며, 애플리케이션 비즈니스 로직을 정의하는 데 사용하는 인프라 리소스를 프로비저닝하기 위해 동일한 도구와 구문을 사용할 수 있습니다. 팀은 익숙한 구문으로 협업하면서 Terraform 에코시스템의 기능을 계속 활용하고 확립된 Terraform 배포 파이프라인을 통해 인프라 구성을 배포할 수 있습니다.
      • +
      • 참고문서 1: CDK for Terraform v0.12: CHANGELOG
      • +
      • 참고문서 2: CDKTF Overview
      • +
      • 참고문서 3: CDKTF Tutorials
      • +
      +
    • +
    `,r:{minutes:.83,words:249},y:"a",t:"2022년 9월"}}],["/04-HashiCorp/08-Updates/99-2022/2022-10.html",{loader:()=>u(()=>import("./2022-10.html-y6mQjU3f.js"),__vite__mapDeps([202,1])),meta:{d:1664864834e3,g:["Hashicorp","Update","Oct"],e:` +

    Product 소개

    +
      +
    • +

      Nomad: Nomad Variables and Service Discovery

      +
        +
      • Hashicorp Blog
      • +
      • Hashicorp Nomad는 Container 뿐만 아니라 Container 화 하기 어려운 Legacy Application 에 대해 배포하고 관리하는 데 사용되는 간단하고 유연한 오케스트레이터입니다. Nomad는 On-prem 및 Cloud 환경을 가리지 않고 작동합니다. Cloudflare, Roblox, Q2 및 Pandora와 같은 조직의 프로덕션에서 널리 채택되고 사용됩니다. 새롭게 출시된 HashiCorp Nomad 1.4 Beta Release 에서는 상태 확인을 통해 Service Discovery 지원을 강화하고 사용자가 구성 값을 저장할 수 있도록 하는 Nomad Variable 기능이 도입 되었습니다.
      • +
      +
    • +
    `,r:{minutes:.75,words:225},y:"a",t:"2022년 10월"}}],["/04-HashiCorp/08-Updates/99-2022/2022-11.html",{loader:()=>u(()=>import("./2022-11.html-m34nQJRW.js"),__vite__mapDeps([203,1])),meta:{d:166729331e4,g:["Hashicorp","Update","Nov"],e:` +

    Product 소개

    +
      +
    • +

      Hashiconf Global

      +
        +
      • +

        Hashicorp Blog

        +
      • +
      • +

        Day 1: ZTS (Zero Trust Security) 와 Cloud Service Networking 을 메인 주제로 새로운 기능과 HCP 서비스에 대한 소개

        + +
      • +
      • +

        Day 2: Infrastructure 및 Application 자동화 관련 제품군을 메인 주제로 새로운 기능 소개

        +
          +
        • Terraform Cloud: Self Service 및 Compliance 관련 기능 강화
        • +
        • Nomad 1.4 GA: 단독 제품으로서의 Service Discovery, Variable 등 기능 강화
        • +
        • HCP Waypoint Public Beta 출시: Opensource 버전만 지원하던 Waypoint 의 HCP Version Public Beta 출시
        • +
        +
      • +
      +
    • +
    `,r:{minutes:1.7,words:511},y:"a",t:"2022년 11월"}}],["/04-HashiCorp/08-Updates/99-2022/2022-12.html",{loader:()=>u(()=>import("./2022-12.html-C2Kpn3zh.js"),__vite__mapDeps([204,1])),meta:{d:1670381763e3,g:["Hashicorp","Update","Dec"],e:` +

    Product 소개

    +
      +
    • +

      Terraform Run Tasks in Public Registry

      +
        +
      • Hashicorp Blog
      • +
      • 활발하게 활용되고 있는 Terraform Run Tasks (3rd party 연동 및 통합) 기능이 강화되었습니다. 이제 Terraform Public Registry 에서 Run Tasks 를 검색하여 필요한 3rd party service 와 연동하여 자원 관리에 필요한 Cost Management, Policy Compliance 와 같은 다양한 기능들을 적용하여 보다 효율적인 자원 관리가 가능합니다.
      • +
      +
    • +
    `,r:{minutes:.82,words:247},y:"a",t:"2022년 12월"}}],["/05-Software/Jenkins/pipeline101/00-introduction.html",{loader:()=>u(()=>import("./00-introduction.html-BtI6zsW3.js"),__vite__mapDeps([205,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +
    +

    Update at 31 Jul, 2019

    +
    +

    Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CategoryNameVersion
    VMVirtualBox6.0.10
    OSRed Hat Enterprise Linux8.0.0
    JDKRed Hat OpenJDK1.8.222
    JenkinsJenkins rpm2.176.2
    `,r:{minutes:.96,words:287},y:"a",t:"Pipeline on Jenkins 101 : Introduction"}}],["/05-Software/Jenkins/pipeline101/01-cicd.html",{loader:()=>u(()=>import("./01-cicd.html-OsUvvkt4.js"),__vite__mapDeps([206,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    1.1 CI/CD Concept Definitions

    +
      +
    • Continuous integration
    • +
    • Continuous delivery
    • +
    • Continuous deployment
    • +
    • Source control management (SCM)
    • +
    +

    1.2 Delivery vs Deployment

    +
      +
    • Continuous Delivery requires user intervention +
        +
      • When? : Stage to Production
      • +
      +
    • +
    `,r:{minutes:.23,words:70},y:"a",t:"1. CI/CD"}}],["/05-Software/Jenkins/pipeline101/02-jobs.html",{loader:()=>u(()=>import("./02-jobs.html-Cq6dIiBF.js"),__vite__mapDeps([207,208,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    프로젝트는 Job의 일부 입니다. 즉, 모든 프로젝트가 Job이지만 모든 Job이 프로젝트는 아닙니다. Job의 구조는 다음과 같습니다.

    +

    FreeStyleProejct, MatrixProject, ExternalJob만 New job에 표시됩니다.

    +

    2.1 New pipeline

    +

    Step 1에서는 stage없이 기본 Pipeline을 실행하여 수행 테스트를 합니다.

    +
      +
    1. +

      Jenkins 로그인

      +
    2. +
    3. +

      좌측 새로운 Item 클릭

      +
    4. +
    5. +

      Enter an item name에 Job 이름 설정 (e.g. 2.Jobs)

      +
    6. +
    7. +

      Pipeline 선택 후 OK 버튼 클릭

      +
    8. +
    9. +

      Pipeline 항목 오른 쪽 Try sample Pipelie...클릭하여 Hello world 클릭 후 저장

      +
      node {
      +   echo 'Hello World'
      +}
      +
    10. +
    11. +

      좌측 Build now클릭

      +
    12. +
    13. +

      좌측 Build History의 최근 빌드된 항목(e.g. #1) 우측에 마우스를 가져가면 dropdown 버튼이 생깁니다. 해당 버튼을 클릭하여 Console Output 클릭

      +
    14. +
    15. +

      수행된 echo 동작 출력을 확인합니다.

      +
      Started by user GyuSeok.Lee
      +Running in Durability level: MAX_SURVIVABILITY
      +[Pipeline] Start of Pipeline
      +[Pipeline] node
      +Running on Jenkins in /var/lib/jenkins/workspace/2.Jobs
      +[Pipeline] {
      +[Pipeline] echo
      +Hello World
      +[Pipeline] }
      +[Pipeline] // node
      +[Pipeline] End of Pipeline
      +Finished: SUCCESS
      +
    16. +
    `,r:{minutes:1.83,words:548},y:"a",t:"2. Jobs"}}],["/05-Software/Jenkins/pipeline101/03-builds.html",{loader:()=>u(()=>import("./03-builds.html-sZm3VZd3.js"),__vite__mapDeps([209,210,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    3.1 Tracking build state

    +

    Pipeline이 수행되는 동작을 추적하는 과정을 확인합니다. 이를 이를 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 03-01.TrackingBuildState)

    +

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    +
    pipeline {
    +    agent any
    +    stages {
    +        stage('Deploy') {
    +            steps {
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh 'for n in \`seq 1 10\`; do echo $n; sleep 1; done'
    +                }
    +                timeout(time: 1, unit: 'MINUTES') {
    +                    sh 'for n in \`seq 1 50\`; do echo $n; sleep 1; done'
    +                }
    +            }
    +        }
    +    }
    +}
    +
    `,r:{minutes:1.62,words:487},y:"a",t:"3. Builds"}}],["/05-Software/Jenkins/pipeline101/04-agent.html",{loader:()=>u(()=>import("./04-agent.html-DwW982Yy.js"),__vite__mapDeps([211,212,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    빌드를 수행하기 위한 Worker로 다중 Jenkins를 컨트롤 할 수 있습니다. 이때 명령을 수행하는 Jenkins는 Master, 빌드를 수행하는 Jenkins는 Worker로 구분합니다. 여기서는 Worker의 연결을 원격 호스트의 Jenkins를 SSH를 통해 연결하는 방식과 컨테이너로 구성된 Jenkins를 연결하는 과정을 확인 합니다.

    +

    Master-Slave 방식, 또는 Master-Agent 방식으로 표현합니다.

    +
    +

    +

    ※ Slave 호스트에 Jenkins를 설치할 필요는 없습니다.

    +
    `,r:{minutes:2.31,words:693},y:"a",t:"4. Agents and Distributing Builds"}}],["/05-Software/Jenkins/pipeline101/05-plugins.html",{loader:()=>u(()=>import("./05-plugins.html-C_L6Paff.js"),__vite__mapDeps([213,214,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    Jenkins가 유용한 툴인 이유중 하나는 방대한 양의 플러그인 입니다. Jenkins의 기능을 확장시키고, 관리, 빌드 정책 등을 확장 시켜주고, 타 서비스와의 연계를 쉽게 가능하도록 합니다.

    +

    Plugin Index

    +
    1564450122219
    +

    5.1 Adding plugins via plugin manager

    `,r:{minutes:.65,words:196},y:"a",t:"5. Plugins"}}],["/05-Software/Jenkins/pipeline101/06-notifications.html",{loader:()=>u(()=>import("./06-notifications.html-CawbIy-9.js"),__vite__mapDeps([215,216,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    Jenkins빌드의 결과를 받아볼 수 있는 몇가지 방안에 대해 알아봅니다.

    +

    6.1 Notifications of build state

    +

    Jenkins에서는 플러그인이나 외부 툴에 의해 빌드에 대한 결과를 받아 볼 수 있습니다. 대표적으로는 Jenkins의 슬랙 플러그인을 사용하여 슬랙으로 빌드에 결과를 받아보거나, catlight.io 에서 데스크탑용 어플리케이션에 연동하는 방법도 있습니다.

    `,r:{minutes:.62,words:187},y:"a",t:"6. Notifications"}}],["/05-Software/Jenkins/pipeline101/07-testing.html",{loader:()=>u(()=>import("./07-testing.html-DItX3hY7.js"),__vite__mapDeps([217,218,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    7.1 Code coverage tests and reports

    +

    테스트 Pipeline 구성시 테스트 과정을 지정할 수 있습니다. Testing을 위한 Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 07-01.CodeCoverageTestsAndReports)

    +

    설정은 다음과 같이 수행합니다.

    +
      +
    1. +

      Pipeline 스크립트에 다음과 같이 입력 합니다. 테스트와 빌드, 검증 후 결과를 보관하는 단계까지 이루어 집니다.

      +
      pipeline {
      +    agent any
      +    stages {
      +        stage('Build') {
      +            steps {
      +                sh '''
      +                  echo This > app.sh
      +                  echo That >> app.sh
      +                '''
      +            }
      +        }
      +        stage('Test') {
      +            steps {
      +                sh '''
      +                  grep This app.sh >> \${BUILD_ID}.cov
      +                  grep That app.sh >> \${BUILD_ID}.cov
      +                '''
      +            }
      +        }
      +        stage('Coverage'){
      +            steps {
      +                sh '''
      +                  app_lines=\`cat app.sh | wc -l\`
      +                  cov_lines=\`cat \${BUILD_ID}.cov | wc -l\`
      +                  echo The app has \`expr $app_lines - $cov_lines\` lines uncovered > \${BUILD_ID}.rpt
      +                  cat \${BUILD_ID}.rpt
      +                '''
      +                archiveArtifacts "\${env.BUILD_ID}.rpt"
      +            }
      +        }
      +    }
      +}
      +
    2. +
    3. +

      빌드가 완료되면 해당 Job화면을 리로드 합니다. Pipeline에 archiveArtifacts가 추가되었으므로 해당 Job에서 이를 관리합니다.
      +

      +
    4. +
    5. +

      해당 아카이브에는 코드 검증 후의 결과가 저장 됩니다.

      +
    6. +
    `,r:{minutes:.68,words:204},y:"a",t:"7. Testing"}}],["/05-Software/Jenkins/pipeline101/08-restapi.html",{loader:()=>u(()=>import("./08-restapi.html-E0A3Day-.js"),__vite__mapDeps([219,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    Jenkins는 외부 서비스와의 연동이나 정보 조회를 위한 API를 제공합니다.

    +

    8.1 Triggering builds via the REST API

    +

    Jenkins REST API 테스트를 위해서는 Jenkins에 인증 가능한 Token을 취득하고 curl이나 Postman 같은 도구를 사용하여 확인 가능 합니다. 우선 Token을 얻는 방법은 다음과 같습니다.

    +
      +
    1. +

      Jenkins에 로그인 합니다.

      +
    2. +
    3. +

      우측 상단의 로그인 아이디에 마우스를 호버하면 드롭박스 버튼이 나타납니다. 설정을 클릭합니다.

      +
    4. +
    5. +

      API Token에서 Current token을 확인합니다. 등록된 Token이 없는 경우 다음과 같이 신규 Token을 발급 받습니다.

      +
        +
      • +

        ADD NEW TOKEN을 클릭합니다.

        +
      • +
      • +

        이름을 기입하는 칸에 로그인 한 아이디를 등록합니다. (e.g. admin)

        +
      • +
      • +

        GENERATE를 클릭하여 Token을 생성합니다.

        +
      • +
      +
    6. +
    7. +

      이름과 Token을 사용하여 다음과 같이 curl로 접속하면 Jenkins-Crumb 프롬프트가 나타납니다.

      +
      $ curl --user "admin:TOKEN" 'http://myjenkins.com/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)'
      +
      +Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605
      +
    8. +
    9. +

      Crumb를 확인했으면 다시 헤더 값에 Jenkins-Crumb:를 추가하여 02-04.MultiStep Job을 빌드하기 위해 다음과 같이 요청합니다.

      +
      $ curl -X POST http://myjenkins.com/job/02-04.MultiStep/build --user gyulee:11479bdec9cada082d189938a3946348be --data-urlencode json='' -H "Jenkins-Crumb:89e1fd9c402824c89465f6b97f49b605"
      +
    10. +
    `,r:{minutes:.55,words:166},y:"a",t:"8. REST API"}}],["/05-Software/Jenkins/pipeline101/09-security.html",{loader:()=>u(()=>import("./09-security.html-CSGyCK8z.js"),__vite__mapDeps([220,221,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    9.1 Securing your deployment with users

    +

    사용자별 배포수행을 위한 사용자 설정을 설명합니다.

    +
      +
    • Jenkins 관리로 이동하여 Configure Global Security를 클릭합니다.
    • +
    +

    Enable security는 보안 설정 여부를 설정하는 항목으로 기본적으로는 비활성화되어있습니다. 체크하여 활성화하면 다양한 보안 옵션을 설정할 수 있는 항목이 표기 됩니다.

    `,r:{minutes:1.44,words:433},y:"a",t:"9. Security"}}],["/05-Software/Jenkins/pipeline101/10-artifacts.html",{loader:()=>u(()=>import("./10-artifacts.html-Bs2kQ5kz.js"),__vite__mapDeps([222,223,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    빌드 이후 빌드의 결과를 기록하고 저장하는 방법을 설명합니다.

    +

    10.1 Creating and storing artifacts

    +

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 10-01.CreatingAndStoringArtifacts)

    +

    Pipeline에 다음과 같이 스크립트를 추가합니다.

    +
    pipeline {
    +    agent any
    +    stages{
    +        stage('Build') {
    +            steps{
    +                sh 'echo "Generating artifacts for \${BUILD_NUMBER}" > output.txt'
    +            }
    +        }
    +        stage('Archive') {
    +            steps {
    +                archiveArtifacts artifacts: 'output.txt', onlyIfSuccessful: true
    +            }
    +        }
    +    }
    +}
    +
    `,r:{minutes:.57,words:170},y:"a",t:"10. Artifacts"}}],["/05-Software/Jenkins/pipeline101/11-pipelines.html",{loader:()=>u(()=>import("./11-pipelines.html-C2Sm7x5b.js"),__vite__mapDeps([224,225,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    11.1 Automating deployment with pipelines

    +

    Pipeline 타입의 Item을 추가로 생성합니다. (e.g. 11-01.AutomatingDeploymentWithPipelines)

    +

    Pipeline에 다음과 같은 스크립트를 입력합니다.

    +
    pipeline {
    +    agent any
    +    stages {
    +        stage('Build') {
    +            steps {
    +                sh 'echo "Hello World"'
    +            }
    +        }
    +        stage('Test') {
    +            steps {
    +                sh 'echo "Test Hello World!"'
    +            }
    +        }
    +    }
    +}
    +
    `,r:{minutes:1.43,words:428},y:"a",t:"11. Pipelines"}}],["/05-Software/Jenkins/pipeline101/12-appendix.html",{loader:()=>u(()=>import("./12-appendix.html-DYgAE8cJ.js"),__vite__mapDeps([226,1])),meta:{d:164032788e4,g:["cicd","jenkins"],e:` +

    GitHub SCM 연동 이슈

    +

    GitHub를 SCM으로 사용하는 경우 다음과 같은 메시지가 출력되면서 진행되지 않는 경우가 있습니다.

    +
    GitHub API Usage: Current quota has 5000 remaining (447380 over budget). Next quota of 5000 in 5 days 0 hr. Sleeping for 4 days 23 hr.
    +14:07:33 GitHub API Usage: The quota may have been refreshed earlier than expected, rechecking...
    +
    `,r:{minutes:.31,words:93},y:"a",t:"Apendix"}}],["/05-Software/Jenkins/pipeline101/13-jenkins_101_single.html",{loader:()=>u(()=>import("./13-jenkins_101_single.html-Dgqr7ZGI.js"),__vite__mapDeps([227,208,210,212,214,216,218,221,223,225,1])),meta:{d:1640328154e3,g:["cicd","jenkins"],e:` +
    +

    Update at 31 Jul, 2019

    +
    +

    Introduction

    +

    Jenkins Pipeline 을 구성하기 위해 VM 환경에서 Jenkins와 관련 Echo System을 구성합니다. 각 Product의 버전은 문서를 작성하는 시점에서의 최신 버전을 위주로 다운로드 및 설치되었습니다. 구성 기반 환경 및 버전은 필요에 따라 변경 가능합니다.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CategoryNameVersion
    VMVirtualBox6.0.10
    OSRed Hat Enterprise Linux8.0.0
    JDKRed Hat OpenJDK1.8.222
    JenkinsJenkins rpm2.176.2
    `,r:{minutes:13.08,words:3923},y:"a",t:"Pipeline on Jenkins 101 (Single Page)"}}],["/05-Software/Tomcat/tomcat101/01-Introduction.html",{loader:()=>u(()=>import("./01-Introduction.html-DUosUNA9.js"),__vite__mapDeps([228,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +

    본 내용은 톰캣을 좀더 잘 알고 잘 써보기 위한 제안이랄까요?

    +

    톰캣의 특성상 쉽게 접할 수 있는 메뉴얼적인 지식보다는, 톰캣을 더 잘 사용하고 운영 할 수 있을만한 아이디어를 공유하고자 시작한 지식공유 활동입니다. 담고 있는 내용은 '톰캣 알고 쓰기' 유튜브 강의 내용에 대한 정리입니다. 유튜브에 강의를 올리면 출퇴근 시간을 이용해 짬짬히 들을 수 있을 것 같은 생각이 들어 시작하였지만 얼마나 출퇴근 시간에 이용하셨을지는 미지수이고 동영상으로 모든 것을 다 표현할 수 없다는 점을 감안하여 다시 글로 정리합니다.

    `,r:{minutes:1.32,words:396},y:"a",t:"1. Tomcat 소개"}}],["/05-Software/Tomcat/tomcat101/02-env.html",{loader:()=>u(()=>import("./02-env.html-DG_OSaqw.js"),__vite__mapDeps([229,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` + +
    +

    2.1 OS

    +

    톰캣을 설치하는 OS 플랫폼 환경은 모든 환경을 지원합니다. 그나마 예전에는 일부 Unix/Linux/OSX 환경에서 Apache HTTP Server 설치하듯 컴파일을 통해 구성하였으나, 최근에는 압축파일을 해제하고 바로 사용할 수 있는 경우가 대부분입니다.
    +톰캣을 운영하기 위해 OS를 선택해야하는 입장이라면 다음과 같은 설치 타입을 고려할 수 있습니다.

    `,r:{minutes:1.25,words:375},y:"a",t:"2. Tomcat 설치환경"}}],["/05-Software/Tomcat/tomcat101/03-installation.html",{loader:()=>u(()=>import("./03-installation.html-C2Y8sNUT.js"),__vite__mapDeps([230,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • 설치파일 받기
    • +
    • 윈도우에 설치하기
    • +
    • 유닉스/리눅스에 설치하기
    • +
    • 설치 후 작업
    • +
    • 디렉토리 구조
    • +
    +`,r:{minutes:1.38,words:414},y:"a",t:"3. Tomcat 설치"}}],["/05-Software/Tomcat/tomcat101/04-configuration.html",{loader:()=>u(()=>import("./04-configuration.html-DzqaMvOy.js"),__vite__mapDeps([231,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • 리스너
    • +
    • 자바옵션
    • +
    • 클래스로더
    • +
    • setenv?
    • +
    • web.xml
    • +
    • 로그
    • +
    +`,r:{minutes:1.84,words:552},y:"a",t:"4. Tomcat 환경설정"}}],["/05-Software/Tomcat/tomcat101/05-deployment.html",{loader:()=>u(()=>import("./05-deployment.html-B4eAra3Y.js"),__vite__mapDeps([232,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • Web Application
    • +
    • by Manager
    • +
    • by webapps DIR
    • +
    • by context.xml
    • +
    • ROOT
    • +
    • Auto Deployment & Hot Deployment
    • +
    • Parallel Deployment
    • +
    +`,r:{minutes:2.35,words:706},y:"a",t:"5. Tomcat 애플리케이션 배포"}}],["/05-Software/Tomcat/tomcat101/06-dbconnection.html",{loader:()=>u(()=>import("./06-dbconnection.html-Lm8jzNRi.js"),__vite__mapDeps([233,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • JDBC Connection Pool
    • +
    • DB 연동 예제
    • +
    • DB 연동 설정값
    • +
    • JNDI Lookup
    • +
    +`,r:{minutes:1.56,words:468},y:"a",t:"6. Tomcat Database 연동"}}],["/05-Software/Tomcat/tomcat101/07-host.html",{loader:()=>u(()=>import("./07-host.html-C9EMg1b9.js"),__vite__mapDeps([234,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • 호스트 구성
    • +
    • 호스트 특징
    • +
    • host manager
    • +
    + +
    +

    7.1 호스트 구성

    +

    톰캣에 정의된 바로는 Host로 정의되나 일반적인 기능으로 표현한다면 가상 호스트(Virtual Host)와 같은 기능 입니다. 특정 host 명, 즉 http url로 서비스를 분기하는 역할을 합니다. server.xml 기본으로 설정되어있는 localhost인 호스트의 내용은 다음과 같습니다.

    `,r:{minutes:.68,words:205},y:"a",t:"7. Tomcat HOST"}}],["/05-Software/Tomcat/tomcat101/08-webserver.html",{loader:()=>u(()=>import("./08-webserver.html-CfNCoUcb.js"),__vite__mapDeps([235,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • 웹서버 연동의 이유
    • +
    • mod_jk
    • +
    • 클러스터
    • +
    + +
    +

    8.1 웹서버 연동의 이유

    +

    톰캣 단일로 서비스하는 경우도 있지만 일반적으로 웹서버와 연동하여 사용하는 경우가 보다 더 많습니다. 그 이유를 다음과 같이 정리합니다.

    `,r:{minutes:1.51,words:452},y:"a",t:"8. Tomcat 웹서버 연동"}}],["/05-Software/Tomcat/tomcat101/09-thread.html",{loader:()=>u(()=>import("./09-thread.html-XImYqvQZ.js"),__vite__mapDeps([236,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • Thread?
    • +
    • 설정
    • +
    • Thread Dump
    • +
    + +
    +

    9.1 Thread?

    +

    Thread는 JVM내에 요청된 작업을 동시에 처리하기 위한 작은 cpu라고 볼 수 있습니다. 톰캣에 서비스 처리를 요청하는 경우 해당 요청은 Queue에 쌓여 FIFO로 Thread에 전달되고 Thread에 여유가 있는 경우 Queue에 들어온 요청은 바로 Thread로 전달되어 Queue Length는 0을 유지하지만 Thread가 모두 사용중이여서 더이상의 요청 처리를 하지 못하는 경우 새로 발생한 요청은 Queue에 쌓이면서 지연이 발생합니다.

    `,r:{minutes:.83,words:250},y:"a",t:"9. Tomcat 쓰레드"}}],["/05-Software/Tomcat/tomcat101/10-monitoring.html",{loader:()=>u(()=>import("./10-monitoring.html-B9YmIXXL.js"),__vite__mapDeps([237,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • 모니터링은 왜 하나?
    • +
    • 톰캣 기본 모니터링 툴
    • +
    • psi-Probe
    • +
    • jkstatus
    • +
    • visualVM
    • +
    • JMC
    • +
    • APM
    • +
    +`,r:{minutes:1.2,words:360},y:"a",t:"10. Tomcat 모니터링"}}],["/05-Software/Tomcat/tomcat101/11-tip.html",{loader:()=>u(()=>import("./11-tip.html-DaXXBiss.js"),__vite__mapDeps([238,1])),meta:{d:164032788e4,g:["Tomcat","Java"],e:` +
      +
    • 디렉토리
    • +
    • setenv
    • +
    • 실행 유저
    • +
    • Connector
    • +
    + +
    +

    11.1 디렉토리

    `,r:{minutes:.54,words:162},y:"a",t:"11. Tomcat 팁"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/00-overview.html",{loader:()=>u(()=>import("./00-overview.html-BPGT2Xsa.js"),__vite__mapDeps([239,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` + +
    +

    과정 안내

    +
      +
    • +

      이 과정은 IaC 도구인 Terraform을 사용하여 클라우드 리소스를 생성하는 실습(Hands-on)과정입니다.

      +
    • +
    • +

      💻 표시는 실제 실습을 수행하는 단계 입니다.

      +
    • +
    • +

      사전 준비 사항

      +
        +
      • 인터넷 연결이 가능한 사용자 별 랩탑 또는 데스크탑 환경이 필요합니다.
      • +
      • 실습을 위한 샘플 코드활용을 위해 github에 접속 가능해야 합니다.
      • +
      • Naver Cloud Platform(NCP)에 회원 가입이 필요합니다.
      • +
      • 과정을 실행하기 위해서는 NCP 리소스 사용을 위한 크래딧 또는 결재수단 이 필요합니다. 과정 진행을 위해 강사가 크래딧을 제공할 수 있습니다.
      • +
      • 실습을 수행하기 위한 랩탑 환경에 코드 편집기(IDE)로 Visual Studio Code 를 활용합니다. + +
      • +
      +
    • +
    • +

      컨텐츠

      +
        +
      1. Terraform 소개
      2. +
      3. Terraform 기본 +
          +
        • 💻 Lab - Setup and Basic Usage
        • +
        +
      4. +
      5. Terraform 실행 : plan apply destroy +
          +
        • 💻 Lab - Terraform in Action
        • +
        +
      6. +
      7. 테라폼 프로비저닝 도구 사용 및 구성 +
          +
        • 💻 Lab - Terraform으로 프로비저닝 하기
        • +
        +
      8. +
      9. 테라폼 상태파일(State)
      10. +
      11. Terraform Cloud +
          +
        • 💻 Lab - Terraform Remote State
        • +
        +
      12. +
      +
    • +
    `,r:{minutes:.5,words:149},y:"a",t:"Workshop 안내"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/01-terraform-intro.html",{loader:()=>u(()=>import("./01-terraform-intro.html-B1MhIda9.js"),__vite__mapDeps([240,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    NCP 서버를 어떻게 프로비저닝 하죠?

    +

    새로운 NCP의 인스턴스를 프로비저닝 할 수있는 몇 가지 다른 방법을 살펴 보겠습니다. 시작하기 전에 다음을 포함한 몇 가지 기본 정보를 수집해야합니다 (더 많은 옵션이 있습니다).

    +
      +
    • 서버 이름
    • +
    • 운영 체제 (Image)
    • +
    • VM 크기
    • +
    • 지리적 위치 (Region)
    • +
    • 보안 그룹
    • +
    +

    서버 만들기 Method 1: nCloud Console (GUI)

    + +
    +

    서버 만들기 Method 2: nCloud CLI

    `,r:{minutes:.95,words:285},y:"a",t:"01. 테라폼 소개"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-terraform-basic.html",{loader:()=>u(()=>import("./02-terraform-basic.html-D7TMDab7.js"),__vite__mapDeps([241,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    Terraform 이란?

    +
    logo
    +
      +
    • +

      Terraform은 오픈 소스 프로비저닝 도구입니다.

      + +
    • +
    • +

      Go로 작성된 단일 바이너리로 제공됩니다. Terraform은 크로스 플랫폼이며 Linux, Windows 또는 MacOS에서 실행할 수 있습니다.

      +
    • +
    • +

      terraform 설치는 쉽습니다. zip 파일을 다운로드하고 압축을 풀고 실행하기 만하면됩니다.

      + +
    • +
    `,r:{minutes:1.24,words:372},y:"a",t:"02. 테라폼 기본"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/02-z-lab_terraform_basic.html",{loader:()=>u(()=>import("./02-z-lab_terraform_basic.html-updYTors.js"),__vite__mapDeps([242,243,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +
    +

    🏡 Moving in - Explore Your Workspace

    +

    @slidestart blood

    +

    Terraform 명령줄 도구는 MacOS, FreeBSD, OpenBSD, Windows, Solaris 및 Linux에서 사용할 수 있습니다.

    +
    +

    Terraform 언어는 사람과 기계가 읽을 수 있도록 설계되었습니다.

    +
    +

    대부분의 최신 코드 편집기는 Terraform 구문 강조 표시를 지원합니다.

    +

    @slideend

    +

    테라폼 설치 및 구성

    `,r:{minutes:2.29,words:687},y:"a",t:"💻 Lab - Setup and Basic Usage"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-terraform-in-Action.html",{loader:()=>u(()=>import("./03-terraform-in-Action.html-DJ9Tld7l.js"),__vite__mapDeps([244,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    리소스 분석

    +

    모든 Terraform으로 구성되는 리소스는 정확히 동일한 방식으로 구성됩니다.

    +
    resource type "name" {
    +  parameter = "foo"
    +  parameter2 = "bar"
    +  list = ["one", "two", "three"]
    +}
    +
    `,r:{minutes:1.35,words:406},y:"a",t:"03. 테라폼 실행"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/03-z-lab_terraform_action.html",{loader:()=>u(()=>import("./03-z-lab_terraform_action.html-Cew6P_D8.js"),__vite__mapDeps([245,243,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    편집기에서 열기

    +
    +
      +
    • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
    • +
    • 앞서 받은 디렉토리내의 lab02을 열어줍니다.
    • +
    +
    +

    📈 Terraform Graph

    +

    @slidestart blood

    +

    Terraform Graph는 모든 인프라에 대한 시각적 표현을 제공할 수 있습니다.

    `,r:{minutes:2.18,words:653},y:"a",t:"💻 Lab - Terraform in Action"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-ncp-provisioning-and-configuration.html",{loader:()=>u(()=>import("./04-ncp-provisioning-and-configuration.html-C77V6Qkn.js"),__vite__mapDeps([246,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    Terraform 프로비저닝 도구 사용

    +

    Terraform을 사용하여 가상 머신 또는 컨테이너를 세우고 나면 운영 체제와 애플리케이션을 구성 할 수 있습니다.

    +

    여기에서 Provisioner 가 등장합니다.

    +

    Terraform은 Bash, Powershell, Chef, Puppet, Ansible 등을 포함한 여러 유형의 Provisioner를 지원합니다.

    +

    https://www.terraform.io/docs/provisioners/index.html

    `,r:{minutes:.59,words:177},y:"a",t:"04. 테라폼 프로비저닝 도구 사용 및 구성"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/04-z-lab_provisioners_variables_outputs.html",{loader:()=>u(()=>import("./04-z-lab_provisioners_variables_outputs.html-H3qceguI.js"),__vite__mapDeps([247,243,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    편집기에서 열기

    +
    +
      +
    • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
    • +
    • 앞서 실습을 진행한 lab02을 열어줍니다.
    • +
    +
    +

    🛠️ Use a Provisioner

    +

    @slidestart blood

    +

    Terraform 프로비저닝 도구는 생성 시 한 번 실행됩니다.

    +
    `,r:{minutes:.87,words:260},y:"a",t:"💻 Lab - Provisioners, Variables, Outputs"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/05-terraform-state.html",{loader:()=>u(()=>import("./05-terraform-state.html-RoEWncbE.js"),__vite__mapDeps([248,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    Terraform State

    +

    Terraform은 stateful 애플리케이션입니다. 즉, state file 내부에서 빌드 한 모든 내용을 추적합니다.

    +

    앞서의 실습에서 반복된 Apply 작업 간에 Workspace 디렉토리에 나타난 terraform.tfstateterraform.tfstate.backup 파일을 보셨을 것입니다.

    +

    상태 파일은 Terraform이 알고있는 모든 것에 대한 기록 소스입니다.

    `,r:{minutes:.48,words:143},y:"a",t:"05. 테라폼 상태파일(State)"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-terraform-cloud.html",{loader:()=>u(()=>import("./06-terraform-cloud.html-BOv2Y4DV.js"),__vite__mapDeps([249,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    Terraform Cloud

    +

    Terraform Cloud는 Terraform을 사용하여 코드로 인프라를 작성하고 구축하기위한 최고의 워크 플로를 제공하는 무료 로 시작하는 SaaS 애플리케이션입니다.

    +
    +
      +
    • State 저장 및 관리
    • +
    • Terraform 실행을보고 승인하기위한 웹 UI
    • +
    • 개인 모듈 레지스트리
    • +
    • VCS (Version Control System) 통합
    • +
    • CLI, API 또는 GUI 기반 작업
    • +
    • 실행 이벤트 알림
    • +
    • 자동화를위한 전체 HTTP API
    • +
    `,r:{minutes:.38,words:114},y:"a",t:"06. Terraform Cloud"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/06-z-lab_terraform_cloud.html",{loader:()=>u(()=>import("./06-z-lab_terraform_cloud.html-BYSe2KEx.js"),__vite__mapDeps([250,243,1])),meta:{d:1695042774e3,g:["ncloud","ncp","terraform","workshop"],e:` +

    편집기에서 열기

    +
    +
      +
    • VSCode를 실행하고 File(파일) 메뉴에서 Open Folder... 를 클릭합니다.
    • +
    • 앞서 실습을 진행한 lab02을 열어줍니다.
    • +
    +
    +

    ☁️ Terraform Configuration

    +

    @slidestart blood

    +

    Terraform Cloud

    +

    Remote State 저장소는 모든 사용자에게 무료입니다.

    `,r:{minutes:1.32,words:397},y:"a",t:"💻 Lab - Terraform Cloud 연결"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/01-Install.html",{loader:()=>u(()=>import("./01-Install.html-8K2VTmo9.js"),__vite__mapDeps([251,1])),meta:{d:1645936869e3,g:["Consul","ServiceMesh","K8s","Kubernetes"],e:` +
    +

    +

    실습을 위한 조건은 다음과 같습니다.

    + +
    `,r:{minutes:.66,words:198},y:"a",t:"01. Install"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/02-SideCar.html",{loader:()=>u(()=>import("./02-SideCar.html-Cll9HLhK.js"),__vite__mapDeps([252,1])),meta:{d:1645936869e3,g:["Consul","ServiceMesh","K8s","Kubernetes"],e:` +
    +

    +

    실습을 위한 조건은 다음과 같습니다.

    +
      +
    • Consul 이 구성된 Kubernetes 환경
    • +
    • 설치 구성 시 connectInject 이 활성화 되어있어야 합니다.
    • +
    +
    +
    +

    참고 : https://learn.hashicorp.com/tutorials/consul/service-mesh-application-secure-networking?in=consul/kubernetes

    +
    `,r:{minutes:2.14,words:641},y:"a",t:"02. SideCar"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/03-use-crd.html",{loader:()=>u(()=>import("./03-use-crd.html-oAT-nlvR.js"),__vite__mapDeps([253,1])),meta:{d:1645936869e3,g:["Consul","ServiceMesh","K8s","Kubernetes"],e:` +
    +

    참고 : https://learn.hashicorp.com/tutorials/consul/kubernetes-custom-resource-definitions?in=consul/kubernetes

    +

    참고 : https://learn.hashicorp.com/tutorials/consul/service-mesh-zero-trust-network?in=consul/gs-consul-service-mesh

    +
    `,r:{minutes:1.86,words:559},y:"a",t:"03. CRD로 Consul Serive Mesh 관리"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/04-traffic-management.html",{loader:()=>u(()=>import("./04-traffic-management.html-D79x_2QV.js"),__vite__mapDeps([254,1])),meta:{d:1645936869e3,g:["Consul","ServiceMesh","K8s","Kubernetes"],e:` +

    실습을 진행하기 위한 디렉토리를 생성합니다.

    +
    mkdir ./traffic
    +

    Service Mesh는 HTTP 프로토콜 상에서 L7으로 동작하게 됩니다. 따라서 기본 프로토콜을 http로 변경합니다.

    +
    cat > ./traffic/service-to-service.yaml <<EOF
    +apiVersion: consul.hashicorp.com/v1alpha1
    +kind: ProxyDefaults
    +metadata:
    +  name: global
    +spec:
    +  config:
    +    protocol: http
    +EOF
    +
    `,r:{minutes:2.45,words:736},y:"a",t:"04. 트래픽 관리"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/ingress-and-serviceroute.html",{loader:()=>u(()=>import("./ingress-and-serviceroute.html-CPeITe-L.js"),__vite__mapDeps([255,1])),meta:{d:1645936869e3,g:["Consul","ServiceMesh","K8s","Kubernetes","ingress"],e:` +

    Ingress gateway가 8080을 Listen하도록 구성되어있으면, 아래와 같이 해당 포트의 요청을 받을 대상 서비스를 지정합니다.

    +
    apiVersion: consul.hashicorp.com/v1alpha1
    +kind: IngressGateway
    +metadata:
    +  name: ingress-gateway
    +spec:
    +  listeners:
    +    - port: 8080
    +      protocol: http
    +      services:
    +        - name: hashicups
    +          hosts: ["*"]
    +
    `,r:{minutes:.23,words:68},y:"a",t:"Ingress & ServiceRoute"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/multiport.html",{loader:()=>u(()=>import("./multiport.html-BE7Jum3S.js"),__vite__mapDeps([256,1])),meta:{d:1645936869e3,g:["Consul","ServiceMesh","K8s","Kubernetes","annotation"],e:` +
    +

    Consul Doc : https://www.consul.io/docs/k8s/connect#kubernetes-pods-with-multiple-ports

    +
    +

    annotation 에 다음과 같이 서비스 이름과 대상 포트를 리스트로 지정합니다.

    `,r:{minutes:.2,words:60},y:"a",t:"Multiport"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/envoy_timeout.html",{loader:()=>u(()=>import("./envoy_timeout.html-CVf-mWCV.js"),__vite__mapDeps([257,1])),meta:{d:1648260872e3,g:["Consul","ServiceMesh","K8s","Kubernetes","timeout"],e:` +
    +

    Consul API : https://www.consul.io/api-docs/config
    +Proxy Default : https://www.consul.io/docs/connect/config-entries/proxy-defaults
    +Envoy Integration : https://www.consul.io/docs/connect/proxies/envoy

    +
    `,r:{minutes:1.28,words:385},y:"a",t:"Envoy Timeout"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/consul-istio.html",{loader:()=>u(()=>import("./consul-istio.html-CFP_PMhV.js"),__vite__mapDeps([258,1])),meta:{d:166385002e4,g:["Consul","Istio","Kubetenetes","k8s","Performance"],e:` +

    1. 성능 테스트 수행 결과 요약

    +

    Case 2-1

    +
      +
    • Consul Ingress Gateway의 resources.limits 와 resources.requests 의 cpu, memory 를 각각 250m / 500Mi 로 수정
    • +
    • Istio Default 1527 Requests/sec 대비 1860 Requests/sec 로 약 22% 빠름 (Case 2-1)
    • +
    +

    Case 2-2

    +
      +
    • Consul Ingress Gateway resource allocation을 Istio와 동률 구성 시,
    • +
    • Istio Default 1527 Requests/sec 대비 3002 Requests/sec로 약 196% 빠름 (Case 2-2)
    • +
    `,r:{minutes:5.33,words:1598},y:"a",t:"Consul vs Istio - Performance Test"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/jaeger_tracing.html",{loader:()=>u(()=>import("./jaeger_tracing.html-bcpPC1Cp.js"),__vite__mapDeps([259,1])),meta:{a:"유형욱",d:1668245149e3,g:["Consul","Jaeger","Tracing","OpenTelemetry","Istio","IngressGateway","Kubetenetes","K8s"],e:` +

    0. 사전 요구사항

    +

    1) Consul Install

    +

    Jaeger 연동을 위해 Consul on K8s 환경을 구성합니다. 해당 가이드의 경우에는 여기를 참고하세요.

    +

    (1) 시크릿 생성 - 라이센스

    +
      +
    • 라이센스 파일 생성 및 시크릿 생성
    • +
    +
    # license파일 생성
    +vi consul.lic
    +
    +# 생성한 license파일로 secret 생성
    +kubectl create secret generic license --from-file='key=./consul.lic'
    +
    `,r:{minutes:3.36,words:1007},y:"a",t:"Jaeger를 활용한 Consul Service Mesh Tracing"}}],["/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/1-vso-overview.html",{loader:()=>u(()=>import("./1-vso-overview.html-CsgoBSJ3.js"),__vite__mapDeps([260,1])),meta:{d:1684599614e3,g:["vault","operator"],e:` +
    +

    참고:
    +현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *here*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

    +
    +
    img
    img
    `,r:{minutes:1.08,words:325},y:"a",t:"Vault Secrets Operator 개요"}}],["/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/2-vso-install.html",{loader:()=>u(()=>import("./2-vso-install.html-DEpsuV_3.js"),__vite__mapDeps([261,1])),meta:{d:1684599614e3,g:["vault","operator"],e:` +
    +

    참고:
    +현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *here*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

    +
    +

    사전 요구사항

    +
      +
    • Kubernetes 1.22+
    • +
    • Vault OSS/Enterprise 1.11+
    • +
    `,r:{minutes:.39,words:118},y:"a",t:"Vault Secrets Operator 설치"}}],["/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/3-vso-samples.html",{loader:()=>u(()=>import("./3-vso-samples.html-BFhpQZdK.js"),__vite__mapDeps([262,1])),meta:{d:1684599614e3,g:["vault","operator"],e:` +
    +

    📌 참고:
    +현재 Vault 비밀 오퍼레이터는 공개 베타 버전입니다. *here*에서 GitHub 이슈를 개설하여 피드백을 제공해 주세요.

    +
    +

    본 문서는 HashiCorp 공식 GitHub의 Vault Secret Operator 저장소 에서 제공하는 코드를 활용하여 환경구성 및 샘플 애플리케이션 배포/연동에 대한 상세 분석을 제공한다.

    `,r:{minutes:6.82,words:2045},y:"a",t:"Vault Secrets Operator 예제실습"}}],["/404.html",{loader:()=>u(()=>import("./404.html-32aGe-oF.js"),__vite__mapDeps([263,1])),meta:{e:`

    404 Not Found

    +`,r:{minutes:.01,words:3},y:"p",t:""}}],["/99-about/",{loader:()=>u(()=>import("./index.html-BLszlFoz.js"),__vite__mapDeps([264,1])),meta:{r:{minutes:0,words:1},y:"p",t:"99 about"}}],["/01-Infrastructure/Container/",{loader:()=>u(()=>import("./index.html-C2W_HHl5.js"),__vite__mapDeps([265,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Container"}}],["/02-PrivatePlatform/OpenShift/",{loader:()=>u(()=>import("./index.html-BE-2qlXB.js"),__vite__mapDeps([266,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Open Shift"}}],["/02-PrivatePlatform/Vsphere/",{loader:()=>u(()=>import("./index.html-Y991T9IQ.js"),__vite__mapDeps([267,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Vsphere"}}],["/06-etc/class/",{loader:()=>u(()=>import("./index.html-9Yp3ciBJ.js"),__vite__mapDeps([268,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Class"}}],["/06-etc/infomation/",{loader:()=>u(()=>import("./index.html-mpKLZ7_c.js"),__vite__mapDeps([269,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Infomation"}}],["/06-etc/mac/",{loader:()=>u(()=>import("./index.html-0pJIksRV.js"),__vite__mapDeps([270,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Mac"}}],["/06-etc/nodejs/",{loader:()=>u(()=>import("./index.html-CAQ-PxMe.js"),__vite__mapDeps([271,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Nodejs"}}],["/01-Infrastructure/Linux/TroubleShooting/",{loader:()=>u(()=>import("./index.html-D7d5L5hY.js"),__vite__mapDeps([272,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Trouble Shooting"}}],["/01-Infrastructure/Linux/",{loader:()=>u(()=>import("./index.html-Cx5G9-KF.js"),__vite__mapDeps([273,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Linux"}}],["/02-PrivatePlatform/Kubernetes/01-Information/",{loader:()=>u(()=>import("./index.html-CIZaqGwJ.js"),__vite__mapDeps([274,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Information"}}],["/02-PrivatePlatform/Kubernetes/",{loader:()=>u(()=>import("./index.html--C6VbOaD.js"),__vite__mapDeps([275,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Kubernetes"}}],["/02-PrivatePlatform/Kubernetes/02-Config/",{loader:()=>u(()=>import("./index.html-DoVw34AL.js"),__vite__mapDeps([276,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Config"}}],["/02-PrivatePlatform/Kubernetes/05-Kops/",{loader:()=>u(()=>import("./index.html-BLPfZ0m7.js"),__vite__mapDeps([277,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Kops"}}],["/02-PrivatePlatform/Kubernetes/06-EKS/",{loader:()=>u(()=>import("./index.html-C1r8iAhN.js"),__vite__mapDeps([278,1])),meta:{r:{minutes:0,words:1},y:"p",t:"06 EKS"}}],["/04-HashiCorp/01-Packer/01-Information/",{loader:()=>u(()=>import("./index.html-DPXvhdU3.js"),__vite__mapDeps([279,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Information"}}],["/04-HashiCorp/01-Packer/",{loader:()=>u(()=>import("./index.html-wLf1xqqZ.js"),__vite__mapDeps([280,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Packer"}}],["/04-HashiCorp/01-Packer/05-SamplePkr/",{loader:()=>u(()=>import("./index.html-s7Wkl66o.js"),__vite__mapDeps([281,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Sample Pkr"}}],["/04-HashiCorp/02-Vagrant/02-Config/",{loader:()=>u(()=>import("./index.html-CcClr91c.js"),__vite__mapDeps([282,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Config"}}],["/04-HashiCorp/02-Vagrant/",{loader:()=>u(()=>import("./index.html-B2Ds2mib.js"),__vite__mapDeps([283,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Vagrant"}}],["/04-HashiCorp/02-Vagrant/04-TroubleShooting/",{loader:()=>u(()=>import("./index.html-Bf1_GXwC.js"),__vite__mapDeps([284,1])),meta:{r:{minutes:0,words:1},y:"p",t:"04 Trouble Shooting"}}],["/04-HashiCorp/03-Terraform/01-Information/",{loader:()=>u(()=>import("./index.html-DK0THUGL.js"),__vite__mapDeps([285,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Information"}}],["/04-HashiCorp/03-Terraform/",{loader:()=>u(()=>import("./index.html-DUVcfygE.js"),__vite__mapDeps([286,1])),meta:{r:{minutes:0,words:1},y:"p",t:"03 Terraform"}}],["/04-HashiCorp/03-Terraform/02-Config/",{loader:()=>u(()=>import("./index.html-CAT5dI5U.js"),__vite__mapDeps([287,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Config"}}],["/04-HashiCorp/03-Terraform/03-Sample/",{loader:()=>u(()=>import("./index.html-CEtuUuQF.js"),__vite__mapDeps([288,1])),meta:{r:{minutes:0,words:1},y:"p",t:"03 Sample"}}],["/04-HashiCorp/03-Terraform/04-TroubleShooting/",{loader:()=>u(()=>import("./index.html-DccqpTT9.js"),__vite__mapDeps([289,1])),meta:{r:{minutes:0,words:1},y:"p",t:"04 Trouble Shooting"}}],["/04-HashiCorp/03-Terraform/05-Airgap/",{loader:()=>u(()=>import("./index.html-nCy78rUq.js"),__vite__mapDeps([290,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Airgap"}}],["/04-HashiCorp/04-Consul/01-Information/",{loader:()=>u(()=>import("./index.html-CjtAajix.js"),__vite__mapDeps([291,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Information"}}],["/04-HashiCorp/04-Consul/",{loader:()=>u(()=>import("./index.html-CJQDv2R5.js"),__vite__mapDeps([292,1])),meta:{r:{minutes:0,words:1},y:"p",t:"04 Consul"}}],["/04-HashiCorp/04-Consul/02-Configuration/",{loader:()=>u(()=>import("./index.html-BNDIkw2K.js"),__vite__mapDeps([293,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Configuration"}}],["/04-HashiCorp/04-Consul/03-UseCase/",{loader:()=>u(()=>import("./index.html-DxiOK83r.js"),__vite__mapDeps([294,1])),meta:{r:{minutes:0,words:1},y:"p",t:"03 Use Case"}}],["/04-HashiCorp/04-Consul/04-TroubleShooting/",{loader:()=>u(()=>import("./index.html-BSRJsirl.js"),__vite__mapDeps([295,1])),meta:{r:{minutes:0,words:1},y:"p",t:"04 Trouble Shooting"}}],["/04-HashiCorp/04-Consul/05-Template_Sample/",{loader:()=>u(()=>import("./index.html-DPMcgTsa.js"),__vite__mapDeps([296,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Template Sample"}}],["/04-HashiCorp/05-Boundary/01-Install/",{loader:()=>u(()=>import("./index.html-3L0orhhT.js"),__vite__mapDeps([297,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Install"}}],["/04-HashiCorp/05-Boundary/",{loader:()=>u(()=>import("./index.html-H3SdDBxu.js"),__vite__mapDeps([298,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Boundary"}}],["/04-HashiCorp/05-Boundary/02-Config/",{loader:()=>u(()=>import("./index.html-k-UYW5pW.js"),__vite__mapDeps([299,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Config"}}],["/04-HashiCorp/06-Vault/01-Information/",{loader:()=>u(()=>import("./index.html-xIiPoh75.js"),__vite__mapDeps([300,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Information"}}],["/04-HashiCorp/06-Vault/",{loader:()=>u(()=>import("./index.html-BY2TY-GN.js"),__vite__mapDeps([301,1])),meta:{r:{minutes:0,words:1},y:"p",t:"06 Vault"}}],["/04-HashiCorp/06-Vault/02-Secret_Engine/",{loader:()=>u(()=>import("./index.html-mfa_0e2S.js"),__vite__mapDeps([302,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Secret Engine"}}],["/04-HashiCorp/06-Vault/03-Auth_Method/",{loader:()=>u(()=>import("./index.html-BILRmv8l.js"),__vite__mapDeps([303,1])),meta:{r:{minutes:0,words:1},y:"p",t:"03 Auth Method"}}],["/04-HashiCorp/06-Vault/04-UseCase/",{loader:()=>u(()=>import("./index.html-CqOAKbz8.js"),__vite__mapDeps([304,1])),meta:{r:{minutes:0,words:1},y:"p",t:"04 Use Case"}}],["/04-HashiCorp/06-Vault/05-TroubleShooting/",{loader:()=>u(()=>import("./index.html-b1v4IoTd.js"),__vite__mapDeps([305,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Trouble Shooting"}}],["/04-HashiCorp/06-Vault/06-Config/",{loader:()=>u(()=>import("./index.html-CgmgeYBj.js"),__vite__mapDeps([306,1])),meta:{r:{minutes:0,words:1},y:"p",t:"06 Config"}}],["/04-HashiCorp/06-Vault/07-Sentinel-Sample/",{loader:()=>u(()=>import("./index.html-BNrxTMC6.js"),__vite__mapDeps([307,1])),meta:{r:{minutes:0,words:1},y:"p",t:"07 Sentinel Sample"}}],["/04-HashiCorp/07-Nomad/01-Information/",{loader:()=>u(()=>import("./index.html-BBKmscZU.js"),__vite__mapDeps([308,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Information"}}],["/04-HashiCorp/07-Nomad/",{loader:()=>u(()=>import("./index.html-CQLB9tTY.js"),__vite__mapDeps([309,1])),meta:{r:{minutes:0,words:1},y:"p",t:"07 Nomad"}}],["/04-HashiCorp/07-Nomad/02-Config/",{loader:()=>u(()=>import("./index.html-D_LPTw71.js"),__vite__mapDeps([310,1])),meta:{r:{minutes:0,words:1},y:"p",t:"02 Config"}}],["/04-HashiCorp/07-Nomad/04-UseCase/",{loader:()=>u(()=>import("./index.html-DS6XhucT.js"),__vite__mapDeps([311,1])),meta:{r:{minutes:0,words:1},y:"p",t:"04 Use Case"}}],["/04-HashiCorp/07-Nomad/05-SampleJob/",{loader:()=>u(()=>import("./index.html-DSS4UbEp.js"),__vite__mapDeps([312,1])),meta:{r:{minutes:0,words:1},y:"p",t:"05 Sample Job"}}],["/04-HashiCorp/08-Updates/97-2024/",{loader:()=>u(()=>import("./index.html-D_OTBbrV.js"),__vite__mapDeps([313,1])),meta:{r:{minutes:0,words:1},y:"p",t:"97 2024"}}],["/04-HashiCorp/08-Updates/",{loader:()=>u(()=>import("./index.html-8qOH7jzh.js"),__vite__mapDeps([314,1])),meta:{r:{minutes:0,words:1},y:"p",t:"08 Updates"}}],["/04-HashiCorp/08-Updates/98-2023/",{loader:()=>u(()=>import("./index.html-BmBdcU_O.js"),__vite__mapDeps([315,1])),meta:{r:{minutes:0,words:1},y:"p",t:"98 2023"}}],["/04-HashiCorp/08-Updates/99-2022/",{loader:()=>u(()=>import("./index.html-By9mFuaz.js"),__vite__mapDeps([316,1])),meta:{r:{minutes:0,words:1},y:"p",t:"99 2022"}}],["/05-Software/Jenkins/pipeline101/",{loader:()=>u(()=>import("./index.html-CLwoL5XG.js"),__vite__mapDeps([317,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Pipeline101"}}],["/05-Software/Jenkins/",{loader:()=>u(()=>import("./index.html-Ch-G6YLD.js"),__vite__mapDeps([318,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Jenkins"}}],["/05-Software/Tomcat/tomcat101/",{loader:()=>u(()=>import("./index.html-7hyYvoEG.js"),__vite__mapDeps([319,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Tomcat101"}}],["/05-Software/Tomcat/",{loader:()=>u(()=>import("./index.html-GlXEfwY0.js"),__vite__mapDeps([320,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Tomcat"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/01-intro_to_terraform_on_ncp/",{loader:()=>u(()=>import("./index.html-KDl0Hsol.js"),__vite__mapDeps([321,1])),meta:{r:{minutes:0,words:1},y:"p",t:"01 Intro to Terraform on Ncp"}}],["/03-PublicCloud/NCP/09-Terraform-Workshop/",{loader:()=>u(()=>import("./index.html-DaXtS7sB.js"),__vite__mapDeps([322,1])),meta:{r:{minutes:0,words:1},y:"p",t:"09 Terraform Workshop"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/ServiceMesh101/",{loader:()=>u(()=>import("./index.html-a1dbSbEr.js"),__vite__mapDeps([323,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Service Mesh101"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/",{loader:()=>u(()=>import("./index.html-DxPYE0xl.js"),__vite__mapDeps([324,1])),meta:{r:{minutes:0,words:1},y:"p",t:"06 on Kubernetes"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/annotation/",{loader:()=>u(()=>import("./index.html-DagDfy_4.js"),__vite__mapDeps([325,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Annotation"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/configuration/",{loader:()=>u(()=>import("./index.html-Bz3HFByO.js"),__vite__mapDeps([326,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Configuration"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/performance/",{loader:()=>u(()=>import("./index.html-bUpryGxr.js"),__vite__mapDeps([327,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Performance"}}],["/04-HashiCorp/04-Consul/06-on_Kubernetes/tracing/",{loader:()=>u(()=>import("./index.html-bct9jB7A.js"),__vite__mapDeps([328,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Tracing"}}],["/04-HashiCorp/06-Vault/01-Information/vault-secret-operator/",{loader:()=>u(()=>import("./index.html-DA0fCO1A.js"),__vite__mapDeps([329,1])),meta:{r:{minutes:0,words:1},y:"p",t:"Vault Secret Operator"}}],["/category/",{loader:()=>u(()=>import("./index.html-VPpMoLau.js"),__vite__mapDeps([330,1])),meta:{y:"p",t:"카테고리",I:!1}}],["/tag/",{loader:()=>u(()=>import("./index.html-zOoKtAv4.js"),__vite__mapDeps([331,1])),meta:{y:"p",t:"태그",I:!1}}],["/tag/infrastructure/",{loader:()=>u(()=>import("./index.html-B4dOH6ES.js"),__vite__mapDeps([332,1])),meta:{y:"p",t:"태그: Infrastructure",I:!1}}],["/tag/platform/",{loader:()=>u(()=>import("./index.html-oftUuUOw.js"),__vite__mapDeps([333,1])),meta:{y:"p",t:"태그: Platform",I:!1}}],["/tag/cloud/",{loader:()=>u(()=>import("./index.html-CTrkKBqc.js"),__vite__mapDeps([334,1])),meta:{y:"p",t:"태그: Cloud",I:!1}}],["/tag/hashicorp/",{loader:()=>u(()=>import("./index.html-Cfko_1rJ.js"),__vite__mapDeps([335,1])),meta:{y:"p",t:"태그: HashiCorp",I:!1}}],["/tag/software/",{loader:()=>u(()=>import("./index.html-BF_aYaEp.js"),__vite__mapDeps([336,1])),meta:{y:"p",t:"태그: Software",I:!1}}],["/tag/etc/",{loader:()=>u(()=>import("./index.html-CpSUNMQp.js"),__vite__mapDeps([337,1])),meta:{y:"p",t:"태그: Etc",I:!1}}],["/tag/container/",{loader:()=>u(()=>import("./index.html-CTJxT9up.js"),__vite__mapDeps([338,1])),meta:{y:"p",t:"태그: container",I:!1}}],["/tag/docker/",{loader:()=>u(()=>import("./index.html-pQy4XdCb.js"),__vite__mapDeps([339,1])),meta:{y:"p",t:"태그: docker",I:!1}}],["/tag/podman/",{loader:()=>u(()=>import("./index.html-2cA91sb6.js"),__vite__mapDeps([340,1])),meta:{y:"p",t:"태그: podman",I:!1}}],["/tag/rancher/",{loader:()=>u(()=>import("./index.html-Y5zHI8Sj.js"),__vite__mapDeps([341,1])),meta:{y:"p",t:"태그: rancher",I:!1}}],["/tag/mac/",{loader:()=>u(()=>import("./index.html-CyblnQzA.js"),__vite__mapDeps([342,1])),meta:{y:"p",t:"태그: mac",I:!1}}],["/tag/openshift/",{loader:()=>u(()=>import("./index.html-CEah2Pie.js"),__vite__mapDeps([343,1])),meta:{y:"p",t:"태그: openshift",I:!1}}],["/tag/ocp/",{loader:()=>u(()=>import("./index.html-C5_BWQA9.js"),__vite__mapDeps([344,1])),meta:{y:"p",t:"태그: ocp",I:!1}}],["/tag/jboss/",{loader:()=>u(()=>import("./index.html-CmlIA75P.js"),__vite__mapDeps([345,1])),meta:{y:"p",t:"태그: jboss",I:!1}}],["/tag/vsphere/",{loader:()=>u(()=>import("./index.html-BQjr9HCF.js"),__vite__mapDeps([346,1])),meta:{y:"p",t:"태그: vsphere",I:!1}}],["/tag/template/",{loader:()=>u(()=>import("./index.html-Cv-VJgI7.js"),__vite__mapDeps([347,1])),meta:{y:"p",t:"태그: template",I:!1}}],["/tag/alibaba/",{loader:()=>u(()=>import("./index.html-BBIP4ZL9.js"),__vite__mapDeps([348,1])),meta:{y:"p",t:"태그: alibaba",I:!1}}],["/tag/aliyun/",{loader:()=>u(()=>import("./index.html-BEV56aKZ.js"),__vite__mapDeps([349,1])),meta:{y:"p",t:"태그: aliyun",I:!1}}],["/tag/devops/",{loader:()=>u(()=>import("./index.html-DwFTYg5y.js"),__vite__mapDeps([350,1])),meta:{y:"p",t:"태그: devops",I:!1}}],["/tag/ai/",{loader:()=>u(()=>import("./index.html-C6KvNayw.js"),__vite__mapDeps([351,1])),meta:{y:"p",t:"태그: ai",I:!1}}],["/tag/keyboard/",{loader:()=>u(()=>import("./index.html-BQTG7BuQ.js"),__vite__mapDeps([352,1])),meta:{y:"p",t:"태그: keyboard",I:!1}}],["/tag/tip/",{loader:()=>u(()=>import("./index.html-BJZy3j7v.js"),__vite__mapDeps([353,1])),meta:{y:"p",t:"태그: tip",I:!1}}],["/tag/acronyms/",{loader:()=>u(()=>import("./index.html-B-pgB9pG.js"),__vite__mapDeps([354,1])),meta:{y:"p",t:"태그: acronyms",I:!1}}],["/tag/homebrew/",{loader:()=>u(()=>import("./index.html-fzDvWzlC.js"),__vite__mapDeps([355,1])),meta:{y:"p",t:"태그: homebrew",I:!1}}],["/tag/brew/",{loader:()=>u(()=>import("./index.html-DuuvZ83O.js"),__vite__mapDeps([356,1])),meta:{y:"p",t:"태그: brew",I:!1}}],["/tag/wget/",{loader:()=>u(()=>import("./index.html-C_kVuTQi.js"),__vite__mapDeps([357,1])),meta:{y:"p",t:"태그: wget",I:!1}}],["/tag/arm/",{loader:()=>u(()=>import("./index.html-BggXIKDt.js"),__vite__mapDeps([358,1])),meta:{y:"p",t:"태그: arm",I:!1}}],["/tag/nodejs/",{loader:()=>u(()=>import("./index.html-DLMLVJ8R.js"),__vite__mapDeps([359,1])),meta:{y:"p",t:"태그: nodejs",I:!1}}],["/tag/linux/",{loader:()=>u(()=>import("./index.html-D5Lk4DVr.js"),__vite__mapDeps([360,1])),meta:{y:"p",t:"태그: linux",I:!1}}],["/tag/oom/",{loader:()=>u(()=>import("./index.html-BvOBsnWu.js"),__vite__mapDeps([361,1])),meta:{y:"p",t:"태그: oom",I:!1}}],["/tag/oom-killer/",{loader:()=>u(()=>import("./index.html-D4qKC5cg.js"),__vite__mapDeps([362,1])),meta:{y:"p",t:"태그: oom_killer",I:!1}}],["/tag/ssh/",{loader:()=>u(()=>import("./index.html-unUkXPF2.js"),__vite__mapDeps([363,1])),meta:{y:"p",t:"태그: ssh",I:!1}}],["/tag/bridge/",{loader:()=>u(()=>import("./index.html-Dx2kFXDd.js"),__vite__mapDeps([364,1])),meta:{y:"p",t:"태그: bridge",I:!1}}],["/tag/netstat/",{loader:()=>u(()=>import("./index.html-BqKRlohc.js"),__vite__mapDeps([365,1])),meta:{y:"p",t:"태그: netstat",I:!1}}],["/tag/kubernetes/",{loader:()=>u(()=>import("./index.html-D_BQsV-j.js"),__vite__mapDeps([366,1])),meta:{y:"p",t:"태그: kubernetes",I:!1}}],["/tag/scheduler/",{loader:()=>u(()=>import("./index.html-B2uIxBsv.js"),__vite__mapDeps([367,1])),meta:{y:"p",t:"태그: scheduler",I:!1}}],["/tag/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/",{loader:()=>u(()=>import("./index.html-BsAHPhKo.js"),__vite__mapDeps([368,1])),meta:{y:"p",t:"태그: 알고리즘",I:!1}}],["/tag/docker%EC%95%84%EB%8B%98/",{loader:()=>u(()=>import("./index.html-DyLF8uD9.js"),__vite__mapDeps([369,1])),meta:{y:"p",t:"태그: docker아님",I:!1}}],["/tag/containerd/",{loader:()=>u(()=>import("./index.html-C4YGXnQS.js"),__vite__mapDeps([370,1])),meta:{y:"p",t:"태그: containerd",I:!1}}],["/tag/vagrant/",{loader:()=>u(()=>import("./index.html-CxTJPFTW.js"),__vite__mapDeps([371,1])),meta:{y:"p",t:"태그: vagrant",I:!1}}],["/tag/install/",{loader:()=>u(()=>import("./index.html-B6FRUmhJ.js"),__vite__mapDeps([372,1])),meta:{y:"p",t:"태그: install",I:!1}}],["/tag/kubernetes/",{loader:()=>u(()=>import("./index.html-D_BQsV-j.js"),__vite__mapDeps([366,1])),meta:{y:"p",t:"태그: Kubernetes",I:!1}}],["/tag/kops/",{loader:()=>u(()=>import("./index.html-i23O-Qtk.js"),__vite__mapDeps([373,1])),meta:{y:"p",t:"태그: Kops",I:!1}}],["/tag/eks/",{loader:()=>u(()=>import("./index.html-BX35AjeC.js"),__vite__mapDeps([374,1])),meta:{y:"p",t:"태그: EKS",I:!1}}],["/tag/pkos/",{loader:()=>u(()=>import("./index.html-CmLSUAEl.js"),__vite__mapDeps([375,1])),meta:{y:"p",t:"태그: PKOS",I:!1}}],["/tag/packer/",{loader:()=>u(()=>import("./index.html-5fghFVL4.js"),__vite__mapDeps([376,1])),meta:{y:"p",t:"태그: Packer",I:!1}}],["/tag/hcp/",{loader:()=>u(()=>import("./index.html-euWvXoZN.js"),__vite__mapDeps([377,1])),meta:{y:"p",t:"태그: HCP",I:!1}}],["/tag/terraform/",{loader:()=>u(()=>import("./index.html-CQIz16sa.js"),__vite__mapDeps([378,1])),meta:{y:"p",t:"태그: Terraform",I:!1}}],["/tag/sample/",{loader:()=>u(()=>import("./index.html-CdmsQh5K.js"),__vite__mapDeps([379,1])),meta:{y:"p",t:"태그: Sample",I:!1}}],["/tag/alibaba/",{loader:()=>u(()=>import("./index.html-BBIP4ZL9.js"),__vite__mapDeps([348,1])),meta:{y:"p",t:"태그: Alibaba",I:!1}}],["/tag/azure/",{loader:()=>u(()=>import("./index.html-CJqbrHat.js"),__vite__mapDeps([380,1])),meta:{y:"p",t:"태그: Azure",I:!1}}],["/tag/gcp/",{loader:()=>u(()=>import("./index.html-DIqfsULZ.js"),__vite__mapDeps([381,1])),meta:{y:"p",t:"태그: GCP",I:!1}}],["/tag/aws/",{loader:()=>u(()=>import("./index.html-CvidcWV7.js"),__vite__mapDeps([382,1])),meta:{y:"p",t:"태그: aws",I:!1}}],["/tag/ncp/",{loader:()=>u(()=>import("./index.html-BF9sdaEF.js"),__vite__mapDeps([383,1])),meta:{y:"p",t:"태그: NCP",I:!1}}],["/tag/virtualbox/",{loader:()=>u(()=>import("./index.html-MJbxI0SS.js"),__vite__mapDeps([384,1])),meta:{y:"p",t:"태그: virtualbox",I:!1}}],["/tag/terraform/",{loader:()=>u(()=>import("./index.html-CQIz16sa.js"),__vite__mapDeps([378,1])),meta:{y:"p",t:"태그: terraform",I:!1}}],["/tag/iac/",{loader:()=>u(()=>import("./index.html-CZz7gIva.js"),__vite__mapDeps([385,1])),meta:{y:"p",t:"태그: IaC",I:!1}}],["/tag/usecase/",{loader:()=>u(()=>import("./index.html-NHgQ8Gu7.js"),__vite__mapDeps([386,1])),meta:{y:"p",t:"태그: usecase",I:!1}}],["/tag/hcl/",{loader:()=>u(()=>import("./index.html-BsqDco2-.js"),__vite__mapDeps([387,1])),meta:{y:"p",t:"태그: HCL",I:!1}}],["/tag/admin/",{loader:()=>u(()=>import("./index.html-keoyN10d.js"),__vite__mapDeps([388,1])),meta:{y:"p",t:"태그: admin",I:!1}}],["/tag/password/",{loader:()=>u(()=>import("./index.html-C0wy1dkA.js"),__vite__mapDeps([389,1])),meta:{y:"p",t:"태그: password",I:!1}}],["/tag/terraform-on-azure/",{loader:()=>u(()=>import("./index.html-BG1Pjsto.js"),__vite__mapDeps([390,1])),meta:{y:"p",t:"태그: Terraform on Azure",I:!1}}],["/tag/hashicat/",{loader:()=>u(()=>import("./index.html-6sDacYwW.js"),__vite__mapDeps([391,1])),meta:{y:"p",t:"태그: HashiCat",I:!1}}],["/tag/terraform-oss/",{loader:()=>u(()=>import("./index.html-CtzrHCK3.js"),__vite__mapDeps([392,1])),meta:{y:"p",t:"태그: Terraform OSS",I:!1}}],["/tag/terraform-cloud/",{loader:()=>u(()=>import("./index.html-CFrMvdws.js"),__vite__mapDeps([393,1])),meta:{y:"p",t:"태그: Terraform Cloud",I:!1}}],["/tag/terraform-enterprise/",{loader:()=>u(()=>import("./index.html-BF52RzPf.js"),__vite__mapDeps([394,1])),meta:{y:"p",t:"태그: Terraform Enterprise",I:!1}}],["/tag/terraform-%EC%83%98%ED%94%8C/",{loader:()=>u(()=>import("./index.html-DJ4D0DJh.js"),__vite__mapDeps([395,1])),meta:{y:"p",t:"태그: Terraform 샘플",I:!1}}],["/tag/nomad/",{loader:()=>u(()=>import("./index.html-B8KgkMBY.js"),__vite__mapDeps([396,1])),meta:{y:"p",t:"태그: Nomad",I:!1}}],["/tag/terrafom/",{loader:()=>u(()=>import("./index.html-B1pnXePm.js"),__vite__mapDeps([397,1])),meta:{y:"p",t:"태그: terrafom",I:!1}}],["/tag/csi/",{loader:()=>u(()=>import("./index.html-S8M0EGI4.js"),__vite__mapDeps([398,1])),meta:{y:"p",t:"태그: CSI",I:!1}}],["/tag/state/",{loader:()=>u(()=>import("./index.html-Bq8qrdzR.js"),__vite__mapDeps([399,1])),meta:{y:"p",t:"태그: State",I:!1}}],["/tag/enterprise/",{loader:()=>u(()=>import("./index.html-S9AQkxl9.js"),__vite__mapDeps([400,1])),meta:{y:"p",t:"태그: Enterprise",I:!1}}],["/tag/tfe/",{loader:()=>u(()=>import("./index.html-B9XFa-ze.js"),__vite__mapDeps([401,1])),meta:{y:"p",t:"태그: TFE",I:!1}}],["/tag/provider/",{loader:()=>u(()=>import("./index.html-CAuA_CTO.js"),__vite__mapDeps([402,1])),meta:{y:"p",t:"태그: provider",I:!1}}],["/tag/consul/",{loader:()=>u(()=>import("./index.html-D99KEvGE.js"),__vite__mapDeps([403,1])),meta:{y:"p",t:"태그: Consul",I:!1}}],["/tag/consul/",{loader:()=>u(()=>import("./index.html-D99KEvGE.js"),__vite__mapDeps([403,1])),meta:{y:"p",t:"태그: consul",I:!1}}],["/tag/sizing/",{loader:()=>u(()=>import("./index.html-BopX_040.js"),__vite__mapDeps([404,1])),meta:{y:"p",t:"태그: sizing",I:!1}}],["/tag/port/",{loader:()=>u(()=>import("./index.html-BuaeLR9j.js"),__vite__mapDeps([405,1])),meta:{y:"p",t:"태그: port",I:!1}}],["/tag/requirement/",{loader:()=>u(()=>import("./index.html-BR364U9h.js"),__vite__mapDeps([406,1])),meta:{y:"p",t:"태그: requirement",I:!1}}],["/tag/configuration/",{loader:()=>u(()=>import("./index.html-C5n-WiG3.js"),__vite__mapDeps([407,1])),meta:{y:"p",t:"태그: Configuration",I:!1}}],["/tag/forwarddns/",{loader:()=>u(()=>import("./index.html-bvyp_l4x.js"),__vite__mapDeps([408,1])),meta:{y:"p",t:"태그: ForwardDns",I:!1}}],["/tag/acl/",{loader:()=>u(()=>import("./index.html-CeLFvJF4.js"),__vite__mapDeps([409,1])),meta:{y:"p",t:"태그: Acl",I:!1}}],["/tag/policy/",{loader:()=>u(()=>import("./index.html-ByKWy6GY.js"),__vite__mapDeps([410,1])),meta:{y:"p",t:"태그: Policy",I:!1}}],["/tag/client/",{loader:()=>u(()=>import("./index.html-6uHBwJuN.js"),__vite__mapDeps([411,1])),meta:{y:"p",t:"태그: Client",I:!1}}],["/tag/common/",{loader:()=>u(()=>import("./index.html-Cy0Tg075.js"),__vite__mapDeps([412,1])),meta:{y:"p",t:"태그: Common",I:!1}}],["/tag/server/",{loader:()=>u(()=>import("./index.html-B9Iyza3Y.js"),__vite__mapDeps([413,1])),meta:{y:"p",t:"태그: Server",I:!1}}],["/tag/hybrid/",{loader:()=>u(()=>import("./index.html-BduOr1T1.js"),__vite__mapDeps([414,1])),meta:{y:"p",t:"태그: Hybrid",I:!1}}],["/tag/kubetenetes/",{loader:()=>u(()=>import("./index.html-CqIGFBPh.js"),__vite__mapDeps([415,1])),meta:{y:"p",t:"태그: Kubetenetes",I:!1}}],["/tag/k8s/",{loader:()=>u(()=>import("./index.html-CikwTvHf.js"),__vite__mapDeps([416,1])),meta:{y:"p",t:"태그: k8s",I:!1}}],["/tag/vm/",{loader:()=>u(()=>import("./index.html-C_qE9ILI.js"),__vite__mapDeps([417,1])),meta:{y:"p",t:"태그: VM",I:!1}}],["/tag/servicemesh/",{loader:()=>u(()=>import("./index.html--QJEMUXu.js"),__vite__mapDeps([418,1])),meta:{y:"p",t:"태그: ServiceMesh",I:!1}}],["/tag/sidecar/",{loader:()=>u(()=>import("./index.html-B4LLQU-B.js"),__vite__mapDeps([419,1])),meta:{y:"p",t:"태그: SideCar",I:!1}}],["/tag/k8s/",{loader:()=>u(()=>import("./index.html-CikwTvHf.js"),__vite__mapDeps([416,1])),meta:{y:"p",t:"태그: K8S",I:!1}}],["/tag/consul-template/",{loader:()=>u(()=>import("./index.html-BjFvZ5a7.js"),__vite__mapDeps([420,1])),meta:{y:"p",t:"태그: Consul Template",I:!1}}],["/tag/nginx/",{loader:()=>u(()=>import("./index.html-DcotvADo.js"),__vite__mapDeps([421,1])),meta:{y:"p",t:"태그: NGINX",I:!1}}],["/tag/boundary/",{loader:()=>u(()=>import("./index.html-C8INjfIw.js"),__vite__mapDeps([422,1])),meta:{y:"p",t:"태그: Boundary",I:!1}}],["/tag/install/",{loader:()=>u(()=>import("./index.html-B6FRUmhJ.js"),__vite__mapDeps([372,1])),meta:{y:"p",t:"태그: Install",I:!1}}],["/tag/config/",{loader:()=>u(()=>import("./index.html-ChG7pV1C.js"),__vite__mapDeps([423,1])),meta:{y:"p",t:"태그: Config",I:!1}}],["/tag/vault/",{loader:()=>u(()=>import("./index.html-SJgMEq6R.js"),__vite__mapDeps([424,1])),meta:{y:"p",t:"태그: vault",I:!1}}],["/tag/kmip/",{loader:()=>u(()=>import("./index.html-DimCrq4r.js"),__vite__mapDeps([425,1])),meta:{y:"p",t:"태그: kmip",I:!1}}],["/tag/audit/",{loader:()=>u(()=>import("./index.html-BiGgiuzi.js"),__vite__mapDeps([426,1])),meta:{y:"p",t:"태그: audit",I:!1}}],["/tag/optinos/",{loader:()=>u(()=>import("./index.html-Dcf1IyfU.js"),__vite__mapDeps([427,1])),meta:{y:"p",t:"태그: optinos",I:!1}}],["/tag/configuration/",{loader:()=>u(()=>import("./index.html-C5n-WiG3.js"),__vite__mapDeps([407,1])),meta:{y:"p",t:"태그: configuration",I:!1}}],["/tag/token/",{loader:()=>u(()=>import("./index.html-DM6D9wmd.js"),__vite__mapDeps([428,1])),meta:{y:"p",t:"태그: token",I:!1}}],["/tag/vault-enterprise/",{loader:()=>u(()=>import("./index.html-B2XQD821.js"),__vite__mapDeps([429,1])),meta:{y:"p",t:"태그: Vault Enterprise",I:!1}}],["/tag/keymgmt/",{loader:()=>u(()=>import("./index.html-DwOJNE_j.js"),__vite__mapDeps([430,1])),meta:{y:"p",t:"태그: keymgmt",I:!1}}],["/tag/kmip/",{loader:()=>u(()=>import("./index.html-DimCrq4r.js"),__vite__mapDeps([425,1])),meta:{y:"p",t:"태그: KMIP",I:!1}}],["/tag/mongodb/",{loader:()=>u(()=>import("./index.html-Jw7ubVlF.js"),__vite__mapDeps([431,1])),meta:{y:"p",t:"태그: MongoDB",I:!1}}],["/tag/pki/",{loader:()=>u(()=>import("./index.html-vsoKrW4I.js"),__vite__mapDeps([432,1])),meta:{y:"p",t:"태그: PKI",I:!1}}],["/tag/ssh/",{loader:()=>u(()=>import("./index.html-unUkXPF2.js"),__vite__mapDeps([363,1])),meta:{y:"p",t:"태그: SSH",I:!1}}],["/tag/otp/",{loader:()=>u(()=>import("./index.html-CyxdOIsk.js"),__vite__mapDeps([433,1])),meta:{y:"p",t:"태그: OTP",I:!1}}],["/tag/debian/",{loader:()=>u(()=>import("./index.html-jiFMwsKl.js"),__vite__mapDeps([434,1])),meta:{y:"p",t:"태그: Debian",I:!1}}],["/tag/ubuntu/",{loader:()=>u(()=>import("./index.html-vUYXlQFs.js"),__vite__mapDeps([435,1])),meta:{y:"p",t:"태그: Ubuntu",I:!1}}],["/tag/rocky/",{loader:()=>u(()=>import("./index.html-Ja94iRA2.js"),__vite__mapDeps([436,1])),meta:{y:"p",t:"태그: Rocky",I:!1}}],["/tag/rhel/",{loader:()=>u(()=>import("./index.html-CUrRr4Pv.js"),__vite__mapDeps([437,1])),meta:{y:"p",t:"태그: RHEL",I:!1}}],["/tag/centos/",{loader:()=>u(()=>import("./index.html-BxluqjPf.js"),__vite__mapDeps([438,1])),meta:{y:"p",t:"태그: CentOS",I:!1}}],["/tag/transform/",{loader:()=>u(()=>import("./index.html-CfQGYS3C.js"),__vite__mapDeps([439,1])),meta:{y:"p",t:"태그: transform",I:!1}}],["/tag/fpe/",{loader:()=>u(()=>import("./index.html-DgTVepx_.js"),__vite__mapDeps([440,1])),meta:{y:"p",t:"태그: fpe",I:!1}}],["/tag/transit/",{loader:()=>u(()=>import("./index.html-C7Glq06n.js"),__vite__mapDeps([441,1])),meta:{y:"p",t:"태그: transit",I:!1}}],["/tag/vault-auth/",{loader:()=>u(()=>import("./index.html-DJjESVmS.js"),__vite__mapDeps([442,1])),meta:{y:"p",t:"태그: vault auth",I:!1}}],["/tag/aws/",{loader:()=>u(()=>import("./index.html-CvidcWV7.js"),__vite__mapDeps([382,1])),meta:{y:"p",t:"태그: AWS",I:!1}}],["/tag/mfa/",{loader:()=>u(()=>import("./index.html-WpJ-gk6q.js"),__vite__mapDeps([443,1])),meta:{y:"p",t:"태그: MFA",I:!1}}],["/tag/kv/",{loader:()=>u(()=>import("./index.html-MVPqhWoV.js"),__vite__mapDeps([444,1])),meta:{y:"p",t:"태그: kv",I:!1}}],["/tag/policy/",{loader:()=>u(()=>import("./index.html-ByKWy6GY.js"),__vite__mapDeps([410,1])),meta:{y:"p",t:"태그: policy",I:!1}}],["/tag/argocd/",{loader:()=>u(()=>import("./index.html-CY6KWj9G.js"),__vite__mapDeps([445,1])),meta:{y:"p",t:"태그: argocd",I:!1}}],["/tag/gitops/",{loader:()=>u(()=>import("./index.html-CvWV6fvZ.js"),__vite__mapDeps([446,1])),meta:{y:"p",t:"태그: gitops",I:!1}}],["/tag/devsescops/",{loader:()=>u(()=>import("./index.html-D1L525B6.js"),__vite__mapDeps([447,1])),meta:{y:"p",t:"태그: devsescops",I:!1}}],["/tag/pipeline/",{loader:()=>u(()=>import("./index.html-BDnVrQ9b.js"),__vite__mapDeps([448,1])),meta:{y:"p",t:"태그: pipeline",I:!1}}],["/tag/github/",{loader:()=>u(()=>import("./index.html-Dr_GfYP4.js"),__vite__mapDeps([449,1])),meta:{y:"p",t:"태그: github",I:!1}}],["/tag/gitlab/",{loader:()=>u(()=>import("./index.html-B6BrFWUb.js"),__vite__mapDeps([450,1])),meta:{y:"p",t:"태그: gitlab",I:!1}}],["/tag/secret/",{loader:()=>u(()=>import("./index.html-CkTazoSW.js"),__vite__mapDeps([451,1])),meta:{y:"p",t:"태그: secret",I:!1}}],["/tag/eks/",{loader:()=>u(()=>import("./index.html-BX35AjeC.js"),__vite__mapDeps([374,1])),meta:{y:"p",t:"태그: eks",I:!1}}],["/tag/jenkins/",{loader:()=>u(()=>import("./index.html-V4RqHKzL.js"),__vite__mapDeps([452,1])),meta:{y:"p",t:"태그: jenkins",I:!1}}],["/tag/approle/",{loader:()=>u(()=>import("./index.html-BnnT--QB.js"),__vite__mapDeps([453,1])),meta:{y:"p",t:"태그: approle",I:!1}}],["/tag/otp/",{loader:()=>u(()=>import("./index.html-CyxdOIsk.js"),__vite__mapDeps([433,1])),meta:{y:"p",t:"태그: otp",I:!1}}],["/tag/screct/",{loader:()=>u(()=>import("./index.html-N1Uh3b2r.js"),__vite__mapDeps([454,1])),meta:{y:"p",t:"태그: screct",I:!1}}],["/tag/pki/",{loader:()=>u(()=>import("./index.html-vsoKrW4I.js"),__vite__mapDeps([432,1])),meta:{y:"p",t:"태그: pki",I:!1}}],["/tag/mtls/",{loader:()=>u(()=>import("./index.html-CAl6qWlD.js"),__vite__mapDeps([455,1])),meta:{y:"p",t:"태그: mTLS",I:!1}}],["/tag/nomad/",{loader:()=>u(()=>import("./index.html-B8KgkMBY.js"),__vite__mapDeps([396,1])),meta:{y:"p",t:"태그: nomad",I:!1}}],["/tag/db/",{loader:()=>u(()=>import("./index.html-DtaXUAgd.js"),__vite__mapDeps([456,1])),meta:{y:"p",t:"태그: db",I:!1}}],["/tag/sentinel/",{loader:()=>u(()=>import("./index.html-B7305d4c.js"),__vite__mapDeps([457,1])),meta:{y:"p",t:"태그: sentinel",I:!1}}],["/tag/cidr/",{loader:()=>u(()=>import("./index.html-A4lvm2-S.js"),__vite__mapDeps([458,1])),meta:{y:"p",t:"태그: cidr",I:!1}}],["/tag/enterprise/",{loader:()=>u(()=>import("./index.html-S9AQkxl9.js"),__vite__mapDeps([400,1])),meta:{y:"p",t:"태그: enterprise",I:!1}}],["/tag/java/",{loader:()=>u(()=>import("./index.html-C70xfGpM.js"),__vite__mapDeps([459,1])),meta:{y:"p",t:"태그: java",I:!1}}],["/tag/spring/",{loader:()=>u(()=>import("./index.html-BpwaBgMD.js"),__vite__mapDeps([460,1])),meta:{y:"p",t:"태그: spring",I:!1}}],["/tag/performance/",{loader:()=>u(()=>import("./index.html-BMS8nkPy.js"),__vite__mapDeps([461,1])),meta:{y:"p",t:"태그: performance",I:!1}}],["/tag/vso/",{loader:()=>u(()=>import("./index.html-3ckqtICA.js"),__vite__mapDeps([462,1])),meta:{y:"p",t:"태그: VSO",I:!1}}],["/tag/windows/",{loader:()=>u(()=>import("./index.html-a4LJruRs.js"),__vite__mapDeps([463,1])),meta:{y:"p",t:"태그: windows",I:!1}}],["/tag/error/",{loader:()=>u(()=>import("./index.html-DMaoqUQj.js"),__vite__mapDeps([464,1])),meta:{y:"p",t:"태그: error",I:!1}}],["/tag/400/",{loader:()=>u(()=>import("./index.html-BbHtWqfw.js"),__vite__mapDeps([465,1])),meta:{y:"p",t:"태그: 400",I:!1}}],["/tag/miriadb/",{loader:()=>u(()=>import("./index.html-Bj13pYdT.js"),__vite__mapDeps([466,1])),meta:{y:"p",t:"태그: MiriaDB",I:!1}}],["/tag/vault/",{loader:()=>u(()=>import("./index.html-SJgMEq6R.js"),__vite__mapDeps([424,1])),meta:{y:"p",t:"태그: Vault",I:!1}}],["/tag/https/",{loader:()=>u(()=>import("./index.html-BCYYA0EV.js"),__vite__mapDeps([467,1])),meta:{y:"p",t:"태그: https",I:!1}}],["/tag/agent/",{loader:()=>u(()=>import("./index.html-D7s7Bm-s.js"),__vite__mapDeps([468,1])),meta:{y:"p",t:"태그: Agent",I:!1}}],["/tag/license/",{loader:()=>u(()=>import("./index.html-DNV1VoOS.js"),__vite__mapDeps([469,1])),meta:{y:"p",t:"태그: License",I:!1}}],["/tag/sentinel/",{loader:()=>u(()=>import("./index.html-B7305d4c.js"),__vite__mapDeps([457,1])),meta:{y:"p",t:"태그: Sentinel",I:!1}}],["/tag/cloudwatch/",{loader:()=>u(()=>import("./index.html-2OnomJdQ.js"),__vite__mapDeps([470,1])),meta:{y:"p",t:"태그: Cloudwatch",I:!1}}],["/tag/log/",{loader:()=>u(()=>import("./index.html-D5GZe-Dc.js"),__vite__mapDeps([471,1])),meta:{y:"p",t:"태그: log",I:!1}}],["/tag/namespace/",{loader:()=>u(()=>import("./index.html-BXlsA6SX.js"),__vite__mapDeps([472,1])),meta:{y:"p",t:"태그: Namespace",I:!1}}],["/tag/acl/",{loader:()=>u(()=>import("./index.html-CeLFvJF4.js"),__vite__mapDeps([409,1])),meta:{y:"p",t:"태그: ACL",I:!1}}],["/tag/ssl/",{loader:()=>u(()=>import("./index.html-Bwx_667r.js"),__vite__mapDeps([473,1])),meta:{y:"p",t:"태그: SSL",I:!1}}],["/tag/config/",{loader:()=>u(()=>import("./index.html-ChG7pV1C.js"),__vite__mapDeps([423,1])),meta:{y:"p",t:"태그: config",I:!1}}],["/tag/csi/",{loader:()=>u(()=>import("./index.html-S8M0EGI4.js"),__vite__mapDeps([398,1])),meta:{y:"p",t:"태그: csi",I:!1}}],["/tag/nfs/",{loader:()=>u(()=>import("./index.html-C9vPJFCM.js"),__vite__mapDeps([474,1])),meta:{y:"p",t:"태그: nfs",I:!1}}],["/tag/ui/",{loader:()=>u(()=>import("./index.html-DJpbJtkA.js"),__vite__mapDeps([475,1])),meta:{y:"p",t:"태그: UI",I:!1}}],["/tag/windows/",{loader:()=>u(()=>import("./index.html-a4LJruRs.js"),__vite__mapDeps([463,1])),meta:{y:"p",t:"태그: Windows",I:!1}}],["/tag/jenkins/",{loader:()=>u(()=>import("./index.html-V4RqHKzL.js"),__vite__mapDeps([452,1])),meta:{y:"p",t:"태그: Jenkins",I:!1}}],["/tag/java/",{loader:()=>u(()=>import("./index.html-C70xfGpM.js"),__vite__mapDeps([459,1])),meta:{y:"p",t:"태그: Java",I:!1}}],["/tag/docker/",{loader:()=>u(()=>import("./index.html-pQy4XdCb.js"),__vite__mapDeps([339,1])),meta:{y:"p",t:"태그: Docker",I:!1}}],["/tag/api/",{loader:()=>u(()=>import("./index.html-D_WXlAcx.js"),__vite__mapDeps([476,1])),meta:{y:"p",t:"태그: API",I:!1}}],["/tag/springboot/",{loader:()=>u(()=>import("./index.html-CfUgAf1s.js"),__vite__mapDeps([477,1])),meta:{y:"p",t:"태그: SpringBoot",I:!1}}],["/tag/sample/",{loader:()=>u(()=>import("./index.html-CdmsQh5K.js"),__vite__mapDeps([379,1])),meta:{y:"p",t:"태그: sample",I:!1}}],["/tag/job/",{loader:()=>u(()=>import("./index.html-Bt6R3NDy.js"),__vite__mapDeps([478,1])),meta:{y:"p",t:"태그: job",I:!1}}],["/tag/autoscaler/",{loader:()=>u(()=>import("./index.html-B4CBGKba.js"),__vite__mapDeps([479,1])),meta:{y:"p",t:"태그: autoscaler",I:!1}}],["/tag/das/",{loader:()=>u(()=>import("./index.html-DVPHr-AB.js"),__vite__mapDeps([480,1])),meta:{y:"p",t:"태그: das",I:!1}}],["/tag/job/",{loader:()=>u(()=>import("./index.html-Bt6R3NDy.js"),__vite__mapDeps([478,1])),meta:{y:"p",t:"태그: Job",I:!1}}],["/tag/swlb/",{loader:()=>u(()=>import("./index.html-BruheZiQ.js"),__vite__mapDeps([481,1])),meta:{y:"p",t:"태그: SWLB",I:!1}}],["/tag/vs-code/",{loader:()=>u(()=>import("./index.html-BaS3IBWL.js"),__vite__mapDeps([482,1])),meta:{y:"p",t:"태그: vs-code",I:!1}}],["/tag//",{loader:()=>u(()=>import("./index.html-zOoKtAv4.js"),__vite__mapDeps([331,1])),meta:{y:"p",t:"태그: ",I:!1}}],["/tag/ansible/",{loader:()=>u(()=>import("./index.html-BLASnx2N.js"),__vite__mapDeps([483,1])),meta:{y:"p",t:"태그: Ansible",I:!1}}],["/tag/wildfly/",{loader:()=>u(()=>import("./index.html-DAUv74io.js"),__vite__mapDeps([484,1])),meta:{y:"p",t:"태그: wildfly",I:!1}}],["/tag/jboss/",{loader:()=>u(()=>import("./index.html-CmlIA75P.js"),__vite__mapDeps([345,1])),meta:{y:"p",t:"태그: JBoss",I:!1}}],["/tag/reverse-proxy/",{loader:()=>u(()=>import("./index.html-BwINt9yw.js"),__vite__mapDeps([485,1])),meta:{y:"p",t:"태그: reverse proxy",I:!1}}],["/tag/consul-service-discovery/",{loader:()=>u(()=>import("./index.html-CJmOF4Uc.js"),__vite__mapDeps([486,1])),meta:{y:"p",t:"태그: consul service discovery",I:!1}}],["/tag/nomad-pack/",{loader:()=>u(()=>import("./index.html-B6fsItCj.js"),__vite__mapDeps([487,1])),meta:{y:"p",t:"태그: nomad-pack",I:!1}}],["/tag/vuepress/",{loader:()=>u(()=>import("./index.html-m4NvGwI9.js"),__vite__mapDeps([488,1])),meta:{y:"p",t:"태그: vuepress",I:!1}}],["/tag/param/",{loader:()=>u(()=>import("./index.html-CN16WffQ.js"),__vite__mapDeps([489,1])),meta:{y:"p",t:"태그: param",I:!1}}],["/tag/batch/",{loader:()=>u(()=>import("./index.html-BluWWvPM.js"),__vite__mapDeps([490,1])),meta:{y:"p",t:"태그: batch",I:!1}}],["/tag/scouter/",{loader:()=>u(()=>import("./index.html-ZxzMFLB7.js"),__vite__mapDeps([491,1])),meta:{y:"p",t:"태그: Scouter",I:!1}}],["/tag/service-mesh/",{loader:()=>u(()=>import("./index.html-qlIMCSXC.js"),__vite__mapDeps([492,1])),meta:{y:"p",t:"태그: Service Mesh",I:!1}}],["/tag/sidecar/",{loader:()=>u(()=>import("./index.html-B4LLQU-B.js"),__vite__mapDeps([419,1])),meta:{y:"p",t:"태그: sidecar",I:!1}}],["/tag/tomcat/",{loader:()=>u(()=>import("./index.html-D7p9qpLi.js"),__vite__mapDeps([493,1])),meta:{y:"p",t:"태그: tomcat",I:!1}}],["/tag/hashicorp/",{loader:()=>u(()=>import("./index.html-Cfko_1rJ.js"),__vite__mapDeps([335,1])),meta:{y:"p",t:"태그: Hashicorp",I:!1}}],["/tag/update/",{loader:()=>u(()=>import("./index.html-C_mWrlPz.js"),__vite__mapDeps([494,1])),meta:{y:"p",t:"태그: Update",I:!1}}],["/tag/jan/",{loader:()=>u(()=>import("./index.html-D7-__zu2.js"),__vite__mapDeps([495,1])),meta:{y:"p",t:"태그: Jan",I:!1}}],["/tag/feb/",{loader:()=>u(()=>import("./index.html-BvrB7I3s.js"),__vite__mapDeps([496,1])),meta:{y:"p",t:"태그: Feb",I:!1}}],["/tag/mar/",{loader:()=>u(()=>import("./index.html-Nzf5cVbT.js"),__vite__mapDeps([497,1])),meta:{y:"p",t:"태그: Mar",I:!1}}],["/tag/apr/",{loader:()=>u(()=>import("./index.html-Zsxrrt81.js"),__vite__mapDeps([498,1])),meta:{y:"p",t:"태그: Apr",I:!1}}],["/tag/may/",{loader:()=>u(()=>import("./index.html-C3iva97a.js"),__vite__mapDeps([499,1])),meta:{y:"p",t:"태그: May",I:!1}}],["/tag/jun/",{loader:()=>u(()=>import("./index.html-C1NKfsey.js"),__vite__mapDeps([500,1])),meta:{y:"p",t:"태그: Jun",I:!1}}],["/tag/jul/",{loader:()=>u(()=>import("./index.html-BwBRLpAS.js"),__vite__mapDeps([501,1])),meta:{y:"p",t:"태그: Jul",I:!1}}],["/tag/aug/",{loader:()=>u(()=>import("./index.html-Ck3tx8bR.js"),__vite__mapDeps([502,1])),meta:{y:"p",t:"태그: Aug",I:!1}}],["/tag/sep/",{loader:()=>u(()=>import("./index.html-BhcS2u_z.js"),__vite__mapDeps([503,1])),meta:{y:"p",t:"태그: Sep",I:!1}}],["/tag/oct/",{loader:()=>u(()=>import("./index.html-DrU5Zw3c.js"),__vite__mapDeps([504,1])),meta:{y:"p",t:"태그: Oct",I:!1}}],["/tag/nov/",{loader:()=>u(()=>import("./index.html-DxLgIHet.js"),__vite__mapDeps([505,1])),meta:{y:"p",t:"태그: Nov",I:!1}}],["/tag/july/",{loader:()=>u(()=>import("./index.html-C2yf9D51.js"),__vite__mapDeps([506,1])),meta:{y:"p",t:"태그: July",I:!1}}],["/tag/dec/",{loader:()=>u(()=>import("./index.html-B6kMSFtp.js"),__vite__mapDeps([507,1])),meta:{y:"p",t:"태그: Dec",I:!1}}],["/tag/cicd/",{loader:()=>u(()=>import("./index.html-CX5BgQPA.js"),__vite__mapDeps([508,1])),meta:{y:"p",t:"태그: cicd",I:!1}}],["/tag/tomcat/",{loader:()=>u(()=>import("./index.html-D7p9qpLi.js"),__vite__mapDeps([493,1])),meta:{y:"p",t:"태그: Tomcat",I:!1}}],["/tag/ncloud/",{loader:()=>u(()=>import("./index.html-BJFe6ZiL.js"),__vite__mapDeps([509,1])),meta:{y:"p",t:"태그: ncloud",I:!1}}],["/tag/ncp/",{loader:()=>u(()=>import("./index.html-BF9sdaEF.js"),__vite__mapDeps([383,1])),meta:{y:"p",t:"태그: ncp",I:!1}}],["/tag/workshop/",{loader:()=>u(()=>import("./index.html-odQGV5Lk.js"),__vite__mapDeps([510,1])),meta:{y:"p",t:"태그: workshop",I:!1}}],["/tag/k8s/",{loader:()=>u(()=>import("./index.html-CikwTvHf.js"),__vite__mapDeps([416,1])),meta:{y:"p",t:"태그: K8s",I:!1}}],["/tag/ingress/",{loader:()=>u(()=>import("./index.html-CXv2rEdZ.js"),__vite__mapDeps([511,1])),meta:{y:"p",t:"태그: ingress",I:!1}}],["/tag/annotation/",{loader:()=>u(()=>import("./index.html-bgIO2JEg.js"),__vite__mapDeps([512,1])),meta:{y:"p",t:"태그: annotation",I:!1}}],["/tag/timeout/",{loader:()=>u(()=>import("./index.html-ChgtSZ-Z.js"),__vite__mapDeps([513,1])),meta:{y:"p",t:"태그: timeout",I:!1}}],["/tag/istio/",{loader:()=>u(()=>import("./index.html-CHfJCgT3.js"),__vite__mapDeps([514,1])),meta:{y:"p",t:"태그: Istio",I:!1}}],["/tag/performance/",{loader:()=>u(()=>import("./index.html-BMS8nkPy.js"),__vite__mapDeps([461,1])),meta:{y:"p",t:"태그: Performance",I:!1}}],["/tag/jaeger/",{loader:()=>u(()=>import("./index.html-Cij7iqH7.js"),__vite__mapDeps([515,1])),meta:{y:"p",t:"태그: Jaeger",I:!1}}],["/tag/tracing/",{loader:()=>u(()=>import("./index.html-CoUmmtwf.js"),__vite__mapDeps([516,1])),meta:{y:"p",t:"태그: Tracing",I:!1}}],["/tag/opentelemetry/",{loader:()=>u(()=>import("./index.html-DEE8lR3g.js"),__vite__mapDeps([517,1])),meta:{y:"p",t:"태그: OpenTelemetry",I:!1}}],["/tag/ingressgateway/",{loader:()=>u(()=>import("./index.html-B-AYcfLz.js"),__vite__mapDeps([518,1])),meta:{y:"p",t:"태그: IngressGateway",I:!1}}],["/tag/operator/",{loader:()=>u(()=>import("./index.html-BN6tS7qC.js"),__vite__mapDeps([519,1])),meta:{y:"p",t:"태그: operator",I:!1}}],["/article/",{loader:()=>u(()=>import("./index.html-BNePmBVJ.js"),__vite__mapDeps([520,1])),meta:{y:"p",t:"게시글",I:!1}}],["/star/",{loader:()=>u(()=>import("./index.html-CeSawCUo.js"),__vite__mapDeps([521,1])),meta:{y:"p",t:"스타",I:!1}}],["/timeline/",{loader:()=>u(()=>import("./index.html-cSXKi2Z5.js"),__vite__mapDeps([522,1])),meta:{y:"p",t:"타임라인",I:!1}}]]);/*! + * vue-router v4.2.5 + * (c) 2023 Eduardo San Martin Morote + * @license MIT + */const xt=typeof window<"u";function jm(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const fe=Object.assign;function Zs(e,n){const t={};for(const a in n){const s=n[a];t[a]=bn(s)?s.map(e):e(s)}return t}const pa=()=>{},bn=Array.isArray,Mm=/\/$/,$m=e=>e.replace(Mm,"");function el(e,n,t="/"){let a,s={},l="",o="";const r=n.indexOf("#");let c=n.indexOf("?");return r=0&&(c=-1),c>-1&&(a=n.slice(0,c),l=n.slice(c+1,r>-1?r:n.length),s=e(l)),r>-1&&(a=a||n.slice(0,r),o=n.slice(r,n.length)),a=Km(a??n,t),{fullPath:a+(l&&"?")+l+o,path:a,query:s,hash:o}}function Bm(e,n){const t=n.query?e(n.query):"";return n.path+(t&&"?")+t+(n.hash||"")}function Or(e,n){return!n||!e.toLowerCase().startsWith(n.toLowerCase())?e:e.slice(n.length)||"/"}function Um(e,n,t){const a=n.matched.length-1,s=t.matched.length-1;return a>-1&&a===s&&Mt(n.matched[a],t.matched[s])&&Hp(n.params,t.params)&&e(n.query)===e(t.query)&&n.hash===t.hash}function Mt(e,n){return(e.aliasOf||e)===(n.aliasOf||n)}function Hp(e,n){if(Object.keys(e).length!==Object.keys(n).length)return!1;for(const t in e)if(!zm(e[t],n[t]))return!1;return!0}function zm(e,n){return bn(e)?Dr(e,n):bn(n)?Dr(n,e):e===n}function Dr(e,n){return bn(n)?e.length===n.length&&e.every((t,a)=>t===n[a]):e.length===1&&e[0]===n}function Km(e,n){if(e.startsWith("/"))return e;if(!e)return n;const t=n.split("/"),a=e.split("/"),s=a[a.length-1];(s===".."||s===".")&&a.push("");let l=t.length-1,o,r;for(o=0;o1&&l--;else break;return t.slice(0,l).join("/")+"/"+a.slice(o-(o===a.length?1:0)).join("/")}var ba;(function(e){e.pop="pop",e.push="push"})(ba||(ba={}));var ca;(function(e){e.back="back",e.forward="forward",e.unknown=""})(ca||(ca={}));function qm(e){if(!e)if(xt){const n=document.querySelector("base");e=n&&n.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),$m(e)}const Jm=/^[^#]+#/;function Wm(e,n){return e.replace(Jm,"#")+n}function Gm(e,n){const t=document.documentElement.getBoundingClientRect(),a=e.getBoundingClientRect();return{behavior:n.behavior,left:a.left-t.left-(n.left||0),top:a.top-t.top-(n.top||0)}}const Ps=()=>({left:window.pageXOffset,top:window.pageYOffset});function Ym(e){let n;if("el"in e){const t=e.el,a=typeof t=="string"&&t.startsWith("#"),s=typeof t=="string"?a?document.getElementById(t.slice(1)):document.querySelector(t):t;if(!s)return;n=Gm(s,e)}else n=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(n):window.scrollTo(n.left!=null?n.left:window.pageXOffset,n.top!=null?n.top:window.pageYOffset)}function Rr(e,n){return(history.state?history.state.position-n:-1)+e}const xl=new Map;function Xm(e,n){xl.set(e,n)}function Qm(e){const n=xl.get(e);return xl.delete(e),n}let Zm=()=>location.protocol+"//"+location.host;function Fp(e,n){const{pathname:t,search:a,hash:s}=n,l=e.indexOf("#");if(l>-1){let r=s.includes(e.slice(l))?e.slice(l).length:1,c=s.slice(r);return c[0]!=="/"&&(c="/"+c),Or(c,"")}return Or(t,e)+a+s}function e0(e,n,t,a){let s=[],l=[],o=null;const r=({state:m})=>{const g=Fp(e,location),v=t.value,w=n.value;let C=0;if(m){if(t.value=g,n.value=m,o&&o===v){o=null;return}C=w?m.position-w.position:0}else a(g);s.forEach(_=>{_(t.value,v,{delta:C,type:ba.pop,direction:C?C>0?ca.forward:ca.back:ca.unknown})})};function c(){o=t.value}function p(m){s.push(m);const g=()=>{const v=s.indexOf(m);v>-1&&s.splice(v,1)};return l.push(g),g}function d(){const{history:m}=window;m.state&&m.replaceState(fe({},m.state,{scroll:Ps()}),"")}function h(){for(const m of l)m();l=[],window.removeEventListener("popstate",r),window.removeEventListener("beforeunload",d)}return window.addEventListener("popstate",r),window.addEventListener("beforeunload",d,{passive:!0}),{pauseListeners:c,listen:p,destroy:h}}function Hr(e,n,t,a=!1,s=!1){return{back:e,current:n,forward:t,replaced:a,position:window.history.length,scroll:s?Ps():null}}function n0(e){const{history:n,location:t}=window,a={value:Fp(e,t)},s={value:n.state};s.value||l(a.value,{back:null,current:a.value,forward:null,position:n.length-1,replaced:!0,scroll:null},!0);function l(c,p,d){const h=e.indexOf("#"),m=h>-1?(t.host&&document.querySelector("base")?e:e.slice(h))+c:Zm()+e+c;try{n[d?"replaceState":"pushState"](p,"",m),s.value=p}catch(g){console.error(g),t[d?"replace":"assign"](m)}}function o(c,p){const d=fe({},n.state,Hr(s.value.back,c,s.value.forward,!0),p,{position:s.value.position});l(c,d,!0),a.value=c}function r(c,p){const d=fe({},s.value,n.state,{forward:c,scroll:Ps()});l(d.current,d,!0);const h=fe({},Hr(a.value,c,null),{position:d.position+1},p);l(c,h,!1),a.value=c}return{location:a,state:s,push:r,replace:o}}function t0(e){e=qm(e);const n=n0(e),t=e0(e,n.state,n.location,n.replace);function a(l,o=!0){o||t.pauseListeners(),history.go(l)}const s=fe({location:"",base:e,go:a,createHref:Wm.bind(null,e)},n,t);return Object.defineProperty(s,"location",{enumerable:!0,get:()=>n.location.value}),Object.defineProperty(s,"state",{enumerable:!0,get:()=>n.state.value}),s}function a0(e){return typeof e=="string"||e&&typeof e=="object"}function Np(e){return typeof e=="string"||typeof e=="symbol"}const Rn={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},jp=Symbol("");var Fr;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(Fr||(Fr={}));function $t(e,n){return fe(new Error,{type:e,[jp]:!0},n)}function On(e,n){return e instanceof Error&&jp in e&&(n==null||!!(e.type&n))}const Nr="[^/]+?",s0={sensitive:!1,strict:!1,start:!0,end:!0},l0=/[.+*?^${}()[\]/\\]/g;function o0(e,n){const t=fe({},s0,n),a=[];let s=t.start?"^":"";const l=[];for(const p of e){const d=p.length?[]:[90];t.strict&&!p.length&&(s+="/");for(let h=0;hn.length?n.length===1&&n[0]===80?1:-1:0}function i0(e,n){let t=0;const a=e.score,s=n.score;for(;t0&&n[n.length-1]<0}const p0={type:0,value:""},c0=/[a-zA-Z0-9_]/;function u0(e){if(!e)return[[]];if(e==="/")return[[p0]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function n(g){throw new Error(`ERR (${t})/"${p}": ${g}`)}let t=0,a=t;const s=[];let l;function o(){l&&s.push(l),l=[]}let r=0,c,p="",d="";function h(){p&&(t===0?l.push({type:0,value:p}):t===1||t===2||t===3?(l.length>1&&(c==="*"||c==="+")&&n(`A repeatable param (${p}) must be alone in its segment. eg: '/:ids+.`),l.push({type:1,value:p,regexp:d,repeatable:c==="*"||c==="+",optional:c==="*"||c==="?"})):n("Invalid state to consume buffer"),p="")}function m(){p+=c}for(;r{o(T)}:pa}function o(d){if(Np(d)){const h=a.get(d);h&&(a.delete(d),t.splice(t.indexOf(h),1),h.children.forEach(o),h.alias.forEach(o))}else{const h=t.indexOf(d);h>-1&&(t.splice(h,1),d.record.name&&a.delete(d.record.name),d.children.forEach(o),d.alias.forEach(o))}}function r(){return t}function c(d){let h=0;for(;h=0&&(d.record.path!==t[h].record.path||!Mp(d,t[h]));)h++;t.splice(h,0,d),d.record.name&&!$r(d)&&a.set(d.record.name,d)}function p(d,h){let m,g={},v,w;if("name"in d&&d.name){if(m=a.get(d.name),!m)throw $t(1,{location:d});w=m.record.name,g=fe(Mr(h.params,m.keys.filter(T=>!T.optional).map(T=>T.name)),d.params&&Mr(d.params,m.keys.map(T=>T.name))),v=m.stringify(g)}else if("path"in d)v=d.path,m=t.find(T=>T.re.test(v)),m&&(g=m.parse(v),w=m.record.name);else{if(m=h.name?a.get(h.name):t.find(T=>T.re.test(h.path)),!m)throw $t(1,{location:d,currentLocation:h});w=m.record.name,g=fe({},h.params,d.params),v=m.stringify(g)}const C=[];let _=m;for(;_;)C.unshift(_.record),_=_.parent;return{name:w,path:v,params:g,matched:C,meta:k0(C)}}return e.forEach(d=>l(d)),{addRoute:l,resolve:p,removeRoute:o,getRoutes:r,getRecordMatcher:s}}function Mr(e,n){const t={};for(const a of n)a in e&&(t[a]=e[a]);return t}function m0(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:g0(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function g0(e){const n={},t=e.props||!1;if("component"in e)n.default=t;else for(const a in e.components)n[a]=typeof t=="object"?t[a]:t;return n}function $r(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function k0(e){return e.reduce((n,t)=>fe(n,t.meta),{})}function Br(e,n){const t={};for(const a in e)t[a]=a in n?n[a]:e[a];return t}function Mp(e,n){return n.children.some(t=>t===e||Mp(e,t))}const $p=/#/g,f0=/&/g,v0=/\//g,_0=/=/g,b0=/\?/g,Bp=/\+/g,y0=/%5B/g,w0=/%5D/g,Up=/%5E/g,C0=/%60/g,zp=/%7B/g,E0=/%7C/g,Kp=/%7D/g,x0=/%20/g;function po(e){return encodeURI(""+e).replace(E0,"|").replace(y0,"[").replace(w0,"]")}function T0(e){return po(e).replace(zp,"{").replace(Kp,"}").replace(Up,"^")}function Tl(e){return po(e).replace(Bp,"%2B").replace(x0,"+").replace($p,"%23").replace(f0,"%26").replace(C0,"`").replace(zp,"{").replace(Kp,"}").replace(Up,"^")}function S0(e){return Tl(e).replace(_0,"%3D")}function L0(e){return po(e).replace($p,"%23").replace(b0,"%3F")}function P0(e){return e==null?"":L0(e).replace(v0,"%2F")}function gs(e){try{return decodeURIComponent(""+e)}catch{}return""+e}function I0(e){const n={};if(e===""||e==="?")return n;const a=(e[0]==="?"?e.slice(1):e).split("&");for(let s=0;sl&&Tl(l)):[a&&Tl(a)]).forEach(l=>{l!==void 0&&(n+=(n.length?"&":"")+t,l!=null&&(n+="="+l))})}return n}function A0(e){const n={};for(const t in e){const a=e[t];a!==void 0&&(n[t]=bn(a)?a.map(s=>s==null?null:""+s):a==null?a:""+a)}return n}const V0=Symbol(""),zr=Symbol(""),Is=Symbol(""),co=Symbol(""),Sl=Symbol("");function ea(){let e=[];function n(a){return e.push(a),()=>{const s=e.indexOf(a);s>-1&&e.splice(s,1)}}function t(){e=[]}return{add:n,list:()=>e.slice(),reset:t}}function Yn(e,n,t,a,s){const l=a&&(a.enterCallbacks[s]=a.enterCallbacks[s]||[]);return()=>new Promise((o,r)=>{const c=h=>{h===!1?r($t(4,{from:t,to:n})):h instanceof Error?r(h):a0(h)?r($t(2,{from:n,to:h})):(l&&a.enterCallbacks[s]===l&&typeof h=="function"&&l.push(h),o())},p=e.call(a&&a.instances[s],n,t,c);let d=Promise.resolve(p);e.length<3&&(d=d.then(c)),d.catch(h=>r(h))})}function nl(e,n,t,a){const s=[];for(const l of e)for(const o in l.components){let r=l.components[o];if(!(n!=="beforeRouteEnter"&&!l.instances[o]))if(O0(r)){const p=(r.__vccOpts||r)[n];p&&s.push(Yn(p,t,a,l,o))}else{let c=r();s.push(()=>c.then(p=>{if(!p)return Promise.reject(new Error(`Couldn't resolve component "${o}" at "${l.path}"`));const d=jm(p)?p.default:p;l.components[o]=d;const m=(d.__vccOpts||d)[n];return m&&Yn(m,t,a,l,o)()}))}}return s}function O0(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function Kr(e){const n=be(Is),t=be(co),a=b(()=>n.resolve(fn(e.to))),s=b(()=>{const{matched:c}=a.value,{length:p}=c,d=c[p-1],h=t.matched;if(!d||!h.length)return-1;const m=h.findIndex(Mt.bind(null,d));if(m>-1)return m;const g=qr(c[p-2]);return p>1&&qr(d)===g&&h[h.length-1].path!==g?h.findIndex(Mt.bind(null,c[p-2])):m}),l=b(()=>s.value>-1&&F0(t.params,a.value.params)),o=b(()=>s.value>-1&&s.value===t.matched.length-1&&Hp(t.params,a.value.params));function r(c={}){return H0(c)?n[fn(e.replace)?"replace":"push"](fn(e.to)).catch(pa):Promise.resolve()}return{route:a,href:b(()=>a.value.href),isActive:l,isExactActive:o,navigate:r}}const D0=V({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:Kr,setup(e,{slots:n}){const t=xa(Kr(e)),{options:a}=be(Is),s=b(()=>({[Jr(e.activeClass,a.linkActiveClass,"router-link-active")]:t.isActive,[Jr(e.exactActiveClass,a.linkExactActiveClass,"router-link-exact-active")]:t.isExactActive}));return()=>{const l=n.default&&n.default(t);return e.custom?l:i("a",{"aria-current":t.isExactActive?e.ariaCurrentValue:null,href:t.href,onClick:t.navigate,class:s.value},l)}}}),R0=D0;function H0(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const n=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(n))return}return e.preventDefault&&e.preventDefault(),!0}}function F0(e,n){for(const t in n){const a=n[t],s=e[t];if(typeof a=="string"){if(a!==s)return!1}else if(!bn(s)||s.length!==a.length||a.some((l,o)=>l!==s[o]))return!1}return!0}function qr(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Jr=(e,n,t)=>e??n??t,N0=V({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:n,slots:t}){const a=be(Sl),s=b(()=>e.route||a.value),l=be(zr,0),o=b(()=>{let p=fn(l);const{matched:d}=s.value;let h;for(;(h=d[p])&&!h.components;)p++;return p}),r=b(()=>s.value.matched[o.value]);hn(zr,b(()=>o.value+1)),hn(V0,r),hn(Sl,s);const c=q();return oe(()=>[c.value,r.value,e.name],([p,d,h],[m,g,v])=>{d&&(d.instances[h]=p,g&&g!==d&&p&&p===m&&(d.leaveGuards.size||(d.leaveGuards=g.leaveGuards),d.updateGuards.size||(d.updateGuards=g.updateGuards))),p&&d&&(!g||!Mt(d,g)||!m)&&(d.enterCallbacks[h]||[]).forEach(w=>w(p))},{flush:"post"}),()=>{const p=s.value,d=e.name,h=r.value,m=h&&h.components[d];if(!m)return Wr(t.default,{Component:m,route:p});const g=h.props[d],v=g?g===!0?p.params:typeof g=="function"?g(p):g:null,C=i(m,fe({},v,n,{onVnodeUnmounted:_=>{_.component.isUnmounted&&(h.instances[d]=null)},ref:c}));return Wr(t.default,{Component:C,route:p})||C}}});function Wr(e,n){if(!e)return null;const t=e(n);return t.length===1?t[0]:t}const j0=N0;function M0(e){const n=h0(e.routes,e),t=e.parseQuery||I0,a=e.stringifyQuery||Ur,s=e.history,l=ea(),o=ea(),r=ea(),c=ge(Rn);let p=Rn;xt&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const d=Zs.bind(null,L=>""+L),h=Zs.bind(null,P0),m=Zs.bind(null,gs);function g(L,U){let $,G;return Np(L)?($=n.getRecordMatcher(L),G=U):G=L,n.addRoute(G,$)}function v(L){const U=n.getRecordMatcher(L);U&&n.removeRoute(U)}function w(){return n.getRoutes().map(L=>L.record)}function C(L){return!!n.getRecordMatcher(L)}function _(L,U){if(U=fe({},U||c.value),typeof L=="string"){const f=el(t,L,U.path),E=n.resolve({path:f.path},U),I=s.createHref(f.fullPath);return fe(f,E,{params:m(E.params),hash:gs(f.hash),redirectedFrom:void 0,href:I})}let $;if("path"in L)$=fe({},L,{path:el(t,L.path,U.path).path});else{const f=fe({},L.params);for(const E in f)f[E]==null&&delete f[E];$=fe({},L,{params:h(f)}),U.params=h(U.params)}const G=n.resolve($,U),pe=L.hash||"";G.params=d(m(G.params));const we=Bm(a,fe({},L,{hash:T0(pe),path:G.path})),k=s.createHref(we);return fe({fullPath:we,hash:pe,query:a===Ur?A0(L.query):L.query||{}},G,{redirectedFrom:void 0,href:k})}function T(L){return typeof L=="string"?el(t,L,c.value.path):fe({},L)}function y(L,U){if(p!==L)return $t(8,{from:U,to:L})}function S(L){return K(L)}function R(L){return S(fe(T(L),{replace:!0}))}function x(L){const U=L.matched[L.matched.length-1];if(U&&U.redirect){const{redirect:$}=U;let G=typeof $=="function"?$(L):$;return typeof G=="string"&&(G=G.includes("?")||G.includes("#")?G=T(G):{path:G},G.params={}),fe({query:L.query,hash:L.hash,params:"path"in G?{}:L.params},G)}}function K(L,U){const $=p=_(L),G=c.value,pe=L.state,we=L.force,k=L.replace===!0,f=x($);if(f)return K(fe(T(f),{state:typeof f=="object"?fe({},pe,f.state):pe,force:we,replace:k}),U||$);const E=$;E.redirectedFrom=U;let I;return!we&&Um(a,G,$)&&(I=$t(16,{to:E,from:G}),on(G,G,!0,!1)),(I?Promise.resolve(I):j(E,G)).catch(P=>On(P)?On(P,2)?P:wn(P):W(P,E,G)).then(P=>{if(P){if(On(P,2))return K(fe({replace:k},T(P.to),{state:typeof P.to=="object"?fe({},pe,P.to.state):pe,force:we}),U||E)}else P=M(E,G,!0,k,pe);return Y(E,G,P),P})}function F(L,U){const $=y(L,U);return $?Promise.reject($):Promise.resolve()}function H(L){const U=Vn.values().next().value;return U&&typeof U.runWithContext=="function"?U.runWithContext(L):L()}function j(L,U){let $;const[G,pe,we]=$0(L,U);$=nl(G.reverse(),"beforeRouteLeave",L,U);for(const f of G)f.leaveGuards.forEach(E=>{$.push(Yn(E,L,U))});const k=F.bind(null,L,U);return $.push(k),De($).then(()=>{$=[];for(const f of l.list())$.push(Yn(f,L,U));return $.push(k),De($)}).then(()=>{$=nl(pe,"beforeRouteUpdate",L,U);for(const f of pe)f.updateGuards.forEach(E=>{$.push(Yn(E,L,U))});return $.push(k),De($)}).then(()=>{$=[];for(const f of we)if(f.beforeEnter)if(bn(f.beforeEnter))for(const E of f.beforeEnter)$.push(Yn(E,L,U));else $.push(Yn(f.beforeEnter,L,U));return $.push(k),De($)}).then(()=>(L.matched.forEach(f=>f.enterCallbacks={}),$=nl(we,"beforeRouteEnter",L,U),$.push(k),De($))).then(()=>{$=[];for(const f of o.list())$.push(Yn(f,L,U));return $.push(k),De($)}).catch(f=>On(f,8)?f:Promise.reject(f))}function Y(L,U,$){r.list().forEach(G=>H(()=>G(L,U,$)))}function M(L,U,$,G,pe){const we=y(L,U);if(we)return we;const k=U===Rn,f=xt?history.state:{};$&&(G||k?s.replace(L.fullPath,fe({scroll:k&&f&&f.scroll},pe)):s.push(L.fullPath,pe)),c.value=L,on(L,U,$,k),wn()}let ee;function Ie(){ee||(ee=s.listen((L,U,$)=>{if(!Cn.listening)return;const G=_(L),pe=x(G);if(pe){K(fe(pe,{replace:!0}),G).catch(pa);return}p=G;const we=c.value;xt&&Xm(Rr(we.fullPath,$.delta),Ps()),j(G,we).catch(k=>On(k,12)?k:On(k,2)?(K(k.to,G).then(f=>{On(f,20)&&!$.delta&&$.type===ba.pop&&s.go(-1,!1)}).catch(pa),Promise.reject()):($.delta&&s.go(-$.delta,!1),W(k,G,we))).then(k=>{k=k||M(G,we,!1),k&&($.delta&&!On(k,8)?s.go(-$.delta,!1):$.type===ba.pop&&On(k,20)&&s.go(-1,!1)),Y(G,we,k)}).catch(pa)}))}let Se=ea(),J=ea(),ne;function W(L,U,$){wn(L);const G=J.list();return G.length?G.forEach(pe=>pe(L,U,$)):console.error(L),Promise.reject(L)}function Oe(){return ne&&c.value!==Rn?Promise.resolve():new Promise((L,U)=>{Se.add([L,U])})}function wn(L){return ne||(ne=!L,Ie(),Se.list().forEach(([U,$])=>L?$(L):U()),Se.reset()),L}function on(L,U,$,G){const{scrollBehavior:pe}=e;if(!xt||!pe)return Promise.resolve();const we=!$&&Qm(Rr(L.fullPath,0))||(G||!$)&&history.state&&history.state.scroll||null;return vt().then(()=>pe(L,U,we)).then(k=>k&&Ym(k)).catch(k=>W(k,L,U))}const Ne=L=>s.go(L);let en;const Vn=new Set,Cn={currentRoute:c,listening:!0,addRoute:g,removeRoute:v,hasRoute:C,getRoutes:w,resolve:_,options:e,push:S,replace:R,go:Ne,back:()=>Ne(-1),forward:()=>Ne(1),beforeEach:l.add,beforeResolve:o.add,afterEach:r.add,onError:J.add,isReady:Oe,install(L){const U=this;L.component("RouterLink",R0),L.component("RouterView",j0),L.config.globalProperties.$router=U,Object.defineProperty(L.config.globalProperties,"$route",{enumerable:!0,get:()=>fn(c)}),xt&&!en&&c.value===Rn&&(en=!0,S(s.location).catch(pe=>{}));const $={};for(const pe in Rn)Object.defineProperty($,pe,{get:()=>c.value[pe],enumerable:!0});L.provide(Is,U),L.provide(co,Bi($)),L.provide(Sl,c);const G=L.unmount;Vn.add(L),L.unmount=function(){Vn.delete(L),Vn.size<1&&(p=Rn,ee&&ee(),ee=null,c.value=Rn,en=!1,ne=!1),G()}}};function De(L){return L.reduce((U,$)=>U.then(()=>H($)),Promise.resolve())}return Cn}function $0(e,n){const t=[],a=[],s=[],l=Math.max(n.matched.length,e.matched.length);for(let o=0;oMt(p,r))?a.push(r):t.push(r));const c=e.matched[o];c&&(n.matched.find(p=>Mt(p,c))||s.push(c))}return[t,a,s]}function Pn(){return be(Is)}function In(){return be(co)}var uo=Symbol(""),An=()=>{const e=be(uo);if(!e)throw new Error("useClientData() is called without provider.");return e},B0=()=>An().pageComponent,ke=()=>An().pageData,ye=()=>An().pageFrontmatter,U0=()=>An().pageHead,qp=()=>An().pageLang,z0=()=>An().pageLayout,ln=()=>An().routeLocale,K0=()=>An().routes,Jp=()=>An().siteData,Ia=()=>An().siteLocaleData,q0=Symbol(""),Wp=ge(Fm),ya=ge(Nm),Gp=e=>{const n=Om(e);if(ya.value[n])return n;const t=encodeURI(n);return ya.value[t]?t:Wp.value[n]||n},Wt=e=>{const n=Gp(e),t=ya.value[n]??{...ya.value["/404.html"],notFound:!0};return{path:n,notFound:!1,...t}},As=V({name:"ClientOnly",setup(e,n){const t=q(!1);return se(()=>{t.value=!0}),()=>{var a,s;return t.value?(s=(a=n.slots).default)==null?void 0:s.call(a):null}}}),Yp=V({name:"Content",props:{path:{type:String,required:!1,default:""}},setup(e){const n=B0(),t=b(()=>{if(!e.path)return n.value;const a=Wt(e.path);return eh(()=>a.loader().then(({comp:s})=>s))});return()=>i(t.value)}}),Ce=e=>yn(e)?e:`/${Dp(e)}`,J0=e=>{if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget){const n=e.currentTarget.getAttribute("target");if(n!=null&&n.match(/\b_blank\b/i))return}return e.preventDefault(),!0}},Ve=({active:e=!1,activeClass:n="route-link-active",to:t,...a},{slots:s})=>{var r;const l=Pn(),o=Ce(Gp(t));return i("a",{...a,class:["route-link",{[n]:e}],href:o,onClick:(c={})=>{J0(c)?l.push(t).catch():Promise.resolve()}},(r=s.default)==null?void 0:r.call(s))};Ve.displayName="RouteLink";Ve.props={active:Boolean,activeClass:String,to:String};var W0="Layout",G0="en-US",rt=xa({resolveLayouts:e=>e.reduce((n,t)=>({...n,...t.layouts}),{}),resolvePageHead:(e,n,t)=>{const a=me(n.description)?n.description:t.description,s=[...Array.isArray(n.head)?n.head:[],...t.head,["title",{},e],["meta",{name:"description",content:a}]];return Im(s)},resolvePageHeadTitle:(e,n)=>[e.title,n.title].filter(t=>!!t).join(" | "),resolvePageLang:(e,n)=>e.lang||n.lang||G0,resolvePageLayout:(e,n)=>{const t=me(e.frontmatter.layout)?e.frontmatter.layout:W0;return n[t]},resolveRouteLocale:(e,n)=>Dm(e,n),resolveSiteLocaleData:(e,n)=>{var t;return{...e,...e.locales[n],head:[...((t=e.locales[n])==null?void 0:t.head)??[],...e.head??[]]}}});const Y0={};var Ge=(e={})=>e,Je=Uint8Array,Tt=Uint16Array,X0=Int32Array,Xp=new Je([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),Qp=new Je([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),Q0=new Je([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),Zp=function(e,n){for(var t=new Tt(31),a=0;a<31;++a)t[a]=n+=1<>1|(Te&21845)<<1;Un=(Un&52428)>>2|(Un&13107)<<2,Un=(Un&61680)>>4|(Un&3855)<<4,Ll[Te]=((Un&65280)>>8|(Un&255)<<8)>>1}var ua=function(e,n,t){for(var a=e.length,s=0,l=new Tt(n);s>c]=p}else for(r=new Tt(a),s=0;s>15-e[s]);return r},Aa=new Je(288);for(var Te=0;Te<144;++Te)Aa[Te]=8;for(var Te=144;Te<256;++Te)Aa[Te]=9;for(var Te=256;Te<280;++Te)Aa[Te]=7;for(var Te=280;Te<288;++Te)Aa[Te]=8;var tc=new Je(32);for(var Te=0;Te<32;++Te)tc[Te]=5;var tg=ua(Aa,9,1),ag=ua(tc,5,1),tl=function(e){for(var n=e[0],t=1;tn&&(n=e[t]);return n},gn=function(e,n,t){var a=n/8|0;return(e[a]|e[a+1]<<8)>>(n&7)&t},al=function(e,n){var t=n/8|0;return(e[t]|e[t+1]<<8|e[t+2]<<16)>>(n&7)},sg=function(e){return(e+7)/8|0},ho=function(e,n,t){return(n==null||n<0)&&(n=0),(t==null||t>e.length)&&(t=e.length),new Je(e.subarray(n,t))},lg=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],cn=function(e,n,t){var a=new Error(n||lg[e]);if(a.code=e,Error.captureStackTrace&&Error.captureStackTrace(a,cn),!t)throw a;return a},og=function(e,n,t,a){var s=e.length,l=a?a.length:0;if(!s||n.f&&!n.l)return t||new Je(0);var o=!t,r=o||n.i!=2,c=n.i;o&&(t=new Je(s*3));var p=function(pe){var we=t.length;if(pe>we){var k=new Je(Math.max(we*2,pe));k.set(t),t=k}},d=n.f||0,h=n.p||0,m=n.b||0,g=n.l,v=n.d,w=n.m,C=n.n,_=s*8;do{if(!g){d=gn(e,h,1);var T=gn(e,h+1,3);if(h+=3,T)if(T==1)g=tg,v=ag,w=9,C=5;else if(T==2){var x=gn(e,h,31)+257,K=gn(e,h+10,15)+4,F=x+gn(e,h+5,31)+1;h+=14;for(var H=new Je(F),j=new Je(19),Y=0;Y>4;if(y<16)H[Y++]=y;else{var J=0,ne=0;for(y==16?(ne=3+gn(e,h,3),h+=2,J=H[Y-1]):y==17?(ne=3+gn(e,h,7),h+=3):y==18&&(ne=11+gn(e,h,127),h+=7);ne--;)H[Y++]=J}}var W=H.subarray(0,x),Oe=H.subarray(x);w=tl(W),C=tl(Oe),g=ua(W,w,1),v=ua(Oe,C,1)}else cn(1);else{var y=sg(h)+4,S=e[y-4]|e[y-3]<<8,R=y+S;if(R>s){c&&cn(0);break}r&&p(m+S),t.set(e.subarray(y,R),m),n.b=m+=S,n.p=h=R*8,n.f=d;continue}if(h>_){c&&cn(0);break}}r&&p(m+131072);for(var wn=(1<>4;if(h+=J&15,h>_){c&&cn(0);break}if(J||cn(2),en<256)t[m++]=en;else if(en==256){Ne=h,g=null;break}else{var Vn=en-254;if(en>264){var Y=en-257,Cn=Xp[Y];Vn=gn(e,h,(1<>4;De||cn(3),h+=De&15;var Oe=ng[L];if(L>3){var Cn=Qp[L];Oe+=al(e,h)&(1<_){c&&cn(0);break}r&&p(m+131072);var U=m+Vn;if(m>4>7||(e[0]<<8|e[1])%31)&&cn(6,"invalid zlib data"),(e[1]>>5&1)==+!n&&cn(6,"invalid zlib data: "+(e[1]&32?"need":"unexpected")+" dictionary"),(e[1]>>3&4)+2};function pg(e,n){return og(e.subarray(ig(e,n&&n.dictionary),-4),{i:2},n&&n.out,n&&n.dictionary)}var Gr=typeof TextEncoder<"u"&&new TextEncoder,Pl=typeof TextDecoder<"u"&&new TextDecoder,cg=0;try{Pl.decode(rg,{stream:!0}),cg=1}catch{}var ug=function(e){for(var n="",t=0;;){var a=e[t++],s=(a>127)+(a>223)+(a>239);if(t+s>e.length)return{s:n,r:ho(e,t-1)};s?s==3?(a=((a&15)<<18|(e[t++]&63)<<12|(e[t++]&63)<<6|e[t++]&63)-65536,n+=String.fromCharCode(55296|a>>10,56320|a&1023)):s&1?n+=String.fromCharCode((a&31)<<6|e[t++]&63):n+=String.fromCharCode((a&15)<<12|(e[t++]&63)<<6|e[t++]&63):n+=String.fromCharCode(a)}};function dg(e,n){if(n){for(var t=new Je(e.length),a=0;a>1)),o=0,r=function(d){l[o++]=d},a=0;al.length){var c=new Je(o+8+(s-a<<1));c.set(l),l=c}var p=e.charCodeAt(a);p<128||n?r(p):p<2048?(r(192|p>>6),r(128|p&63)):p>55295&&p<57344?(p=65536+(p&1047552)|e.charCodeAt(++a)&1023,r(240|p>>18),r(128|p>>12&63),r(128|p>>6&63),r(128|p&63)):(r(224|p>>12),r(128|p>>6&63),r(128|p&63))}return ho(l,0,o)}function hg(e,n){if(n){for(var t="",a=0;atypeof e<"u",ac=Object.keys,de=({name:e="",color:n="currentColor"},{slots:t})=>{var a;return i("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${e}-icon`],viewBox:"0 0 1024 1024",fill:n,"aria-label":`${e} icon`},(a=t.default)==null?void 0:a.call(t))};de.displayName="IconBase";const bt=({size:e=48,stroke:n=4,wrapper:t=!0,height:a=2*e})=>{const s=i("svg",{xmlns:"http://www.w3.org/2000/svg",width:e,height:e,preserveAspectRatio:"xMidYMid",viewBox:"25 25 50 50"},[i("animateTransform",{attributeName:"transform",type:"rotate",dur:"2s",keyTimes:"0;1",repeatCount:"indefinite",values:"0;360"}),i("circle",{cx:"50",cy:"50",r:"20",fill:"none",stroke:"currentColor","stroke-width":n,"stroke-linecap":"round"},[i("animate",{attributeName:"stroke-dasharray",dur:"1.5s",keyTimes:"0;0.5;1",repeatCount:"indefinite",values:"1,200;90,200;1,200"}),i("animate",{attributeName:"stroke-dashoffset",dur:"1.5s",keyTimes:"0;0.5;1",repeatCount:"indefinite",values:"0;-35px;-125px"})])]);return t?i("div",{class:"loading-icon-wrapper",style:`display:flex;align-items:center;justify-content:center;height:${a}px`},s):s};bt.displayName="LoadingIcon";const sc=(e,{slots:n})=>{var t;return(t=n.default)==null?void 0:t.call(n)},mo=()=>i(de,{name:"github"},()=>i("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));mo.displayName="GitHubIcon";const go=()=>i(de,{name:"gitlab"},()=>i("path",{d:"M229.333 78.688C223.52 62 199.895 62 193.895 78.688L87.958 406.438h247.5c-.188 0-106.125-327.75-106.125-327.75zM33.77 571.438c-4.875 15 .563 31.687 13.313 41.25l464.812 345L87.77 406.438zm301.5-165 176.813 551.25 176.812-551.25zm655.125 165-54-165-424.312 551.25 464.812-345c12.938-9.563 18.188-26.25 13.5-41.25zM830.27 78.688c-5.812-16.688-29.437-16.688-35.437 0l-106.125 327.75h247.5z"}));go.displayName="GitLabIcon";const ko=()=>i(de,{name:"gitee"},()=>i("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));ko.displayName="GiteeIcon";const fo=()=>i(de,{name:"bitbucket"},()=>i("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));fo.displayName="BitbucketIcon";const vo=()=>i(de,{name:"source"},()=>i("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));vo.displayName="SourceIcon";const Ke=(e,n)=>{var a;const t=(a=(n==null?void 0:n._instance)||_t())==null?void 0:a.appContext.components;return t?e in t||We(e)in t||Kt(We(e))in t:!1},gg=e=>/\b(?:Android|iPhone)/i.test(e),kg=e=>/version\/([\w.]+) .*(mobile ?safari|safari)/i.test(e),fg=e=>[/\((ipad);[-\w),; ]+apple/i,/applecoremedia\/[\w.]+ \((ipad)/i,/\b(ipad)\d\d?,\d\d?[;\]].+ios/i].some(n=>n.test(e)),Vs=(e,n)=>{let t=1;for(let a=0;a>6;return t+=t<<3,t^=t>>11,t%n};let vg=class{constructor(){this.messageElements={};const n="message-container",t=document.getElementById(n);t?this.containerElement=t:(this.containerElement=document.createElement("div"),this.containerElement.id=n,document.body.appendChild(this.containerElement))}pop(n,t=2e3){const a=document.createElement("div"),s=Date.now();return a.className="message move-in",a.innerHTML=n,this.containerElement.appendChild(a),this.messageElements[s]=a,t>0&&setTimeout(()=>{this.close(s)},t),s}close(n){if(n){const t=this.messageElements[n];t.classList.remove("move-in"),t.classList.add("move-out"),t.addEventListener("animationend",()=>{t.remove(),delete this.messageElements[n]})}else ac(this.messageElements).forEach(t=>this.close(Number(t)))}destroy(){document.body.removeChild(this.containerElement)}};const lc=/#.*$/u,_g=e=>{const n=lc.exec(e);return n?n[0]:""},Yr=e=>decodeURI(e).replace(lc,"").replace(/\/index\.html$/iu,"/").replace(/\.html$/iu,"").replace(/(README|index)?\.md$/iu,""),oc=(e,n)=>{if(!mg(n))return!1;const t=Yr(e.path),a=Yr(n),s=_g(n);return s?s===e.hash&&(!a||t===a):t===a};let bg=class{constructor(){this.popupElements={};const n="popup-container",t=document.getElementById(n);t?this.containerElement=t:(this.containerElement=document.createElement("div"),this.containerElement.id=n,document.body.appendChild(this.containerElement))}emit(n,t){const a=document.createElement("div"),s=document.createElement("div"),l=Date.now();return this.containerElement.appendChild(a),this.popupElements[l]=a,a.className="popup-wrapper appear",a.appendChild(s),a.addEventListener("click",()=>this.close(l)),s.className="popup-container",s.innerHTML=n,typeof t=="number"&&setTimeout(()=>{this.close(l)},t),l}close(n){if(n){const t=this.popupElements[n];t.classList.replace("appear","disappear"),t.children[0].addEventListener("animationend",()=>{t.remove(),delete this.popupElements[n]})}else ac(this.popupElements).forEach(t=>this.close(Number(t)))}destroy(){document.body.removeChild(this.containerElement)}};const yg=e=>yn(e)?e:`https://github.com/${e}`,_o=e=>!yn(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,wg=()=>{const{availWidth:e,availHeight:n}=screen,{screenLeft:t,screenTop:a,innerWidth:s,innerHeight:l}=window,o=Math.max(e/2,600),r=Math.max(n/2,400);return{width:o,height:r,left:t+s/2-o/2,top:a+l/2-r/2}},Cg=(e,n="_blank",t=["resizable","status"])=>{var r,c;const{width:a,height:s,left:l,top:o}=wg();(c=(r=window.open(e,n,`width=${a},height=${s},left=${l},top=${o},${t.join(",")}`))==null?void 0:r.focus)==null||c.call(r)};var Eg=e=>Object.prototype.toString.call(e)==="[object Object]",wa=e=>typeof e=="string";const rc=Array.isArray,Xr=e=>Eg(e)&&wa(e.name),Ca=(e,n=!1)=>e?rc(e)?e.map(t=>wa(t)?{name:t}:Xr(t)?t:null).filter(t=>t!==null):wa(e)?[{name:e}]:Xr(e)?[e]:(console.error(`Expect "author" to be \`AuthorInfo[] | AuthorInfo | string[] | string ${n?"":"| false"} | undefined\`, but got`,e),[]):[],ic=(e,n)=>{if(e){if(rc(e)&&e.every(wa))return e;if(wa(e))return[e];console.error(`Expect ${n||"value"} to be \`string[] | string | undefined\`, but got`,e)}return[]},pc=e=>ic(e,"category"),cc=e=>ic(e,"tag");function bo(e,n){let t,a,s;const l=q(!0),o=()=>{l.value=!0,s()};oe(e,o,{flush:"sync"});const r=typeof n=="function"?n:n.get,c=typeof n=="function"?void 0:n.set,p=eo((d,h)=>(a=d,s=h,{get(){return l.value&&(t=r(),l.value=!1),a(),t},set(m){c==null||c(m)}}));return Object.isExtensible(p)&&(p.trigger=o),p}function tt(e){return Ii()?(od(e),!0):!1}function Xe(e){return typeof e=="function"?e():fn(e)}const Va=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const xg=Object.prototype.toString,Tg=e=>xg.call(e)==="[object Object]",Bt=()=>{},Qr=Sg();function Sg(){var e,n;return Va&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(ad|hone|od)/.test(window.navigator.userAgent)||((n=window==null?void 0:window.navigator)==null?void 0:n.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function yo(e,n){function t(...a){return new Promise((s,l)=>{Promise.resolve(e(()=>n.apply(this,a),{fn:n,thisArg:this,args:a})).then(s).catch(l)})}return t}const uc=e=>e();function Lg(e,n={}){let t,a,s=Bt;const l=r=>{clearTimeout(r),s(),s=Bt};return r=>{const c=Xe(e),p=Xe(n.maxWait);return t&&l(t),c<=0||p!==void 0&&p<=0?(a&&(l(a),a=null),Promise.resolve(r())):new Promise((d,h)=>{s=n.rejectOnCancel?h:d,p&&!a&&(a=setTimeout(()=>{t&&l(t),a=null,d(r())},p)),t=setTimeout(()=>{a&&l(a),a=null,d(r())},c)})}}function Pg(e,n=!0,t=!0,a=!1){let s=0,l,o=!0,r=Bt,c;const p=()=>{l&&(clearTimeout(l),l=void 0,r(),r=Bt)};return h=>{const m=Xe(e),g=Date.now()-s,v=()=>c=h();return p(),m<=0?(s=Date.now(),v()):(g>m&&(t||!o)?(s=Date.now(),v()):n&&(c=new Promise((w,C)=>{r=a?C:w,l=setTimeout(()=>{s=Date.now(),o=!0,w(v()),p()},Math.max(0,m-g))})),!t&&!l&&(l=setTimeout(()=>o=!0,m)),o=!1,c)}}function Ig(e=uc){const n=q(!0);function t(){n.value=!1}function a(){n.value=!0}const s=(...l)=>{n.value&&e(...l)};return{isActive:nt(n),pause:t,resume:a,eventFilter:s}}function Ag(e){let n;function t(){return n||(n=e()),n}return t.reset=async()=>{const a=n;n=void 0,a&&await a},t}function Vg(e){return e||_t()}function Og(...e){if(e.length!==1)return qt(...e);const n=e[0];return typeof n=="function"?nt(eo(()=>({get:n,set:Bt}))):q(n)}function wo(e,n=200,t={}){return yo(Lg(n,t),e)}function Dg(e,n=200,t=!1,a=!0,s=!1){return yo(Pg(n,t,a,s),e)}function Rg(e,n,t={}){const{eventFilter:a=uc,...s}=t;return oe(e,yo(a,n),s)}function Hg(e,n,t={}){const{eventFilter:a,...s}=t,{eventFilter:l,pause:o,resume:r,isActive:c}=Ig(a);return{stop:Rg(e,n,{...s,eventFilter:l}),pause:o,resume:r,isActive:c}}function Os(e,n=!0,t){Vg()?se(e,t):n?e():vt(e)}function Fg(e,n,t={}){const{immediate:a=!0}=t,s=q(!1);let l=null;function o(){l&&(clearTimeout(l),l=null)}function r(){s.value=!1,o()}function c(...p){o(),s.value=!0,l=setTimeout(()=>{s.value=!1,l=null,e(...p)},Xe(n))}return a&&(s.value=!0,Va&&c()),tt(r),{isPending:nt(s),start:c,stop:r}}function ks(e=!1,n={}){const{truthyValue:t=!0,falsyValue:a=!1}=n,s=$e(e),l=q(e);function o(r){if(arguments.length)return l.value=r,l.value;{const c=Xe(t);return l.value=l.value===c?Xe(a):c,l.value}}return s?o:[l,o]}function Sn(e){var n;const t=Xe(e);return(n=t==null?void 0:t.$el)!=null?n:t}const Nn=Va?window:void 0,dc=Va?window.document:void 0,hc=Va?window.navigator:void 0;function Pe(...e){let n,t,a,s;if(typeof e[0]=="string"||Array.isArray(e[0])?([t,a,s]=e,n=Nn):[n,t,a,s]=e,!n)return Bt;Array.isArray(t)||(t=[t]),Array.isArray(a)||(a=[a]);const l=[],o=()=>{l.forEach(d=>d()),l.length=0},r=(d,h,m,g)=>(d.addEventListener(h,m,g),()=>d.removeEventListener(h,m,g)),c=oe(()=>[Sn(n),Xe(s)],([d,h])=>{if(o(),!d)return;const m=Tg(h)?{...h}:h;l.push(...t.flatMap(g=>a.map(v=>r(d,g,v,m))))},{immediate:!0,flush:"post"}),p=()=>{c(),o()};return tt(p),p}function Ng(){const e=q(!1);return _t()&&se(()=>{e.value=!0}),e}function Gt(e){const n=Ng();return b(()=>(n.value,!!e()))}function mc(e,n={}){const{window:t=Nn}=n,a=Gt(()=>t&&"matchMedia"in t&&typeof t.matchMedia=="function");let s;const l=q(!1),o=p=>{l.value=p.matches},r=()=>{s&&("removeEventListener"in s?s.removeEventListener("change",o):s.removeListener(o))},c=to(()=>{a.value&&(r(),s=t.matchMedia(Xe(e)),"addEventListener"in s?s.addEventListener("change",o):s.addListener(o),l.value=s.matches)});return tt(()=>{c(),r(),s=void 0}),l}function Zr(e,n={}){const{controls:t=!1,navigator:a=hc}=n,s=Gt(()=>a&&"permissions"in a);let l;const o=typeof e=="string"?{name:e}:e,r=q(),c=()=>{l&&(r.value=l.state)},p=Ag(async()=>{if(s.value){if(!l)try{l=await a.permissions.query(o),Pe(l,"change",c),c()}catch{r.value="prompt"}return l}});return p(),t?{state:r,isSupported:s,query:p}:r}function jg(e={}){const{navigator:n=hc,read:t=!1,source:a,copiedDuring:s=1500,legacy:l=!1}=e,o=Gt(()=>n&&"clipboard"in n),r=Zr("clipboard-read"),c=Zr("clipboard-write"),p=b(()=>o.value||l),d=q(""),h=q(!1),m=Fg(()=>h.value=!1,s);function g(){o.value&&r.value!=="denied"?n.clipboard.readText().then(_=>{d.value=_}):d.value=C()}p.value&&t&&Pe(["copy","cut"],g);async function v(_=Xe(a)){p.value&&_!=null&&(o.value&&c.value!=="denied"?await n.clipboard.writeText(_):w(_),d.value=_,h.value=!0,m.start())}function w(_){const T=document.createElement("textarea");T.value=_??"",T.style.position="absolute",T.style.opacity="0",document.body.appendChild(T),T.select(),document.execCommand("copy"),T.remove()}function C(){var _,T,y;return(y=(T=(_=document==null?void 0:document.getSelection)==null?void 0:_.call(document))==null?void 0:T.toString())!=null?y:""}return{isSupported:p,text:d,copied:h,copy:v}}const Ga=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},Ya="__vueuse_ssr_handlers__",Mg=$g();function $g(){return Ya in Ga||(Ga[Ya]=Ga[Ya]||{}),Ga[Ya]}function Bg(e,n){return Mg[e]||n}function Ug(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const zg={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},ei="vueuse-storage";function Co(e,n,t,a={}){var s;const{flush:l="pre",deep:o=!0,listenToStorageChanges:r=!0,writeDefaults:c=!0,mergeDefaults:p=!1,shallow:d,window:h=Nn,eventFilter:m,onError:g=H=>{console.error(H)},initOnMounted:v}=a,w=(d?ge:q)(typeof n=="function"?n():n);if(!t)try{t=Bg("getDefaultStorage",()=>{var H;return(H=Nn)==null?void 0:H.localStorage})()}catch(H){g(H)}if(!t)return w;const C=Xe(n),_=Ug(C),T=(s=a.serializer)!=null?s:zg[_],{pause:y,resume:S}=Hg(w,()=>R(w.value),{flush:l,deep:o,eventFilter:m});return h&&r&&Os(()=>{Pe(h,"storage",F),Pe(h,ei,K),v&&F()}),v||F(),w;function R(H){try{if(H==null)t.removeItem(e);else{const j=T.write(H),Y=t.getItem(e);Y!==j&&(t.setItem(e,j),h&&h.dispatchEvent(new CustomEvent(ei,{detail:{key:e,oldValue:Y,newValue:j,storageArea:t}})))}}catch(j){g(j)}}function x(H){const j=H?H.newValue:t.getItem(e);if(j==null)return c&&C!=null&&t.setItem(e,T.write(C)),C;if(!H&&p){const Y=T.read(j);return typeof p=="function"?p(Y,C):_==="object"&&!Array.isArray(Y)?{...C,...Y}:Y}else return typeof j!="string"?j:T.read(j)}function K(H){F(H.detail)}function F(H){if(!(H&&H.storageArea!==t)){if(H&&H.key==null){w.value=C;return}if(!(H&&H.key!==e)){y();try{(H==null?void 0:H.newValue)!==T.write(w.value)&&(w.value=x(H))}catch(j){g(j)}finally{H?vt(S):S()}}}}}function Kg(e){return mc("(prefers-color-scheme: dark)",e)}function gc(e,n,t={}){const{window:a=Nn,...s}=t;let l;const o=Gt(()=>a&&"MutationObserver"in a),r=()=>{l&&(l.disconnect(),l=void 0)},c=oe(()=>Sn(e),h=>{r(),o.value&&a&&h&&(l=new MutationObserver(n),l.observe(h,s))},{immediate:!0}),p=()=>l==null?void 0:l.takeRecords(),d=()=>{r(),c()};return tt(d),{isSupported:o,stop:d,takeRecords:p}}function qg(e,n,t={}){const{window:a=Nn,...s}=t;let l;const o=Gt(()=>a&&"ResizeObserver"in a),r=()=>{l&&(l.disconnect(),l=void 0)},c=b(()=>Array.isArray(e)?e.map(h=>Sn(h)):[Sn(e)]),p=oe(c,h=>{if(r(),o.value&&a){l=new ResizeObserver(n);for(const m of h)m&&l.observe(m,s)}},{immediate:!0,flush:"post",deep:!0}),d=()=>{r(),p()};return tt(d),{isSupported:o,stop:d}}function Jg(e,n={width:0,height:0},t={}){const{window:a=Nn,box:s="content-box"}=t,l=b(()=>{var h,m;return(m=(h=Sn(e))==null?void 0:h.namespaceURI)==null?void 0:m.includes("svg")}),o=q(n.width),r=q(n.height),{stop:c}=qg(e,([h])=>{const m=s==="border-box"?h.borderBoxSize:s==="content-box"?h.contentBoxSize:h.devicePixelContentBoxSize;if(a&&l.value){const g=Sn(e);if(g){const v=a.getComputedStyle(g);o.value=Number.parseFloat(v.width),r.value=Number.parseFloat(v.height)}}else if(m){const g=Array.isArray(m)?m:[m];o.value=g.reduce((v,{inlineSize:w})=>v+w,0),r.value=g.reduce((v,{blockSize:w})=>v+w,0)}else o.value=h.contentRect.width,r.value=h.contentRect.height},t);Os(()=>{const h=Sn(e);h&&(o.value="offsetWidth"in h?h.offsetWidth:n.width,r.value="offsetHeight"in h?h.offsetHeight:n.height)});const p=oe(()=>Sn(e),h=>{o.value=h?n.width:0,r.value=h?n.height:0});function d(){c(),p()}return{width:o,height:r,stop:d}}const ni=["fullscreenchange","webkitfullscreenchange","webkitendfullscreen","mozfullscreenchange","MSFullscreenChange"];function Eo(e,n={}){const{document:t=dc,autoExit:a=!1}=n,s=b(()=>{var _;return(_=Sn(e))!=null?_:t==null?void 0:t.querySelector("html")}),l=q(!1),o=b(()=>["requestFullscreen","webkitRequestFullscreen","webkitEnterFullscreen","webkitEnterFullScreen","webkitRequestFullScreen","mozRequestFullScreen","msRequestFullscreen"].find(_=>t&&_ in t||s.value&&_ in s.value)),r=b(()=>["exitFullscreen","webkitExitFullscreen","webkitExitFullScreen","webkitCancelFullScreen","mozCancelFullScreen","msExitFullscreen"].find(_=>t&&_ in t||s.value&&_ in s.value)),c=b(()=>["fullScreen","webkitIsFullScreen","webkitDisplayingFullscreen","mozFullScreen","msFullscreenElement"].find(_=>t&&_ in t||s.value&&_ in s.value)),p=["fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement"].find(_=>t&&_ in t),d=Gt(()=>s.value&&t&&o.value!==void 0&&r.value!==void 0&&c.value!==void 0),h=()=>p?(t==null?void 0:t[p])===s.value:!1,m=()=>{if(c.value){if(t&&t[c.value]!=null)return t[c.value];{const _=s.value;if((_==null?void 0:_[c.value])!=null)return!!_[c.value]}}return!1};async function g(){if(!(!d.value||!l.value)){if(r.value)if((t==null?void 0:t[r.value])!=null)await t[r.value]();else{const _=s.value;(_==null?void 0:_[r.value])!=null&&await _[r.value]()}l.value=!1}}async function v(){if(!d.value||l.value)return;m()&&await g();const _=s.value;o.value&&(_==null?void 0:_[o.value])!=null&&(await _[o.value](),l.value=!0)}async function w(){await(l.value?g():v())}const C=()=>{const _=m();(!_||_&&h())&&(l.value=_)};return Pe(t,ni,C,!1),Pe(()=>Sn(s),ni,C,!1),a&&tt(g),{isSupported:d,isFullscreen:l,enter:v,exit:g,toggle:w}}function sl(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function kc(e){const n=window.getComputedStyle(e);if(n.overflowX==="scroll"||n.overflowY==="scroll"||n.overflowX==="auto"&&e.clientWidth1?!0:(n.preventDefault&&n.preventDefault(),!1)}const Xa=new WeakMap;function fc(e,n=!1){const t=q(n);let a=null,s;oe(Og(e),r=>{const c=sl(Xe(r));if(c){const p=c;Xa.get(p)||Xa.set(p,s),t.value&&(p.style.overflow="hidden")}},{immediate:!0});const l=()=>{const r=sl(Xe(e));!r||t.value||(Qr&&(a=Pe(r,"touchmove",c=>{Wg(c)},{passive:!1})),r.style.overflow="hidden",t.value=!0)},o=()=>{var r;const c=sl(Xe(e));!c||!t.value||(Qr&&(a==null||a()),c.style.overflow=(r=Xa.get(c))!=null?r:"",Xa.delete(c),t.value=!1)};return tt(o),b({get(){return t.value},set(r){r?l():o()}})}let Gg=0;function Yg(e,n={}){const t=q(!1),{document:a=dc,immediate:s=!0,manual:l=!1,id:o=`vueuse_styletag_${++Gg}`}=n,r=q(e);let c=()=>{};const p=()=>{if(!a)return;const h=a.getElementById(o)||a.createElement("style");h.isConnected||(h.id=o,n.media&&(h.media=n.media),a.head.appendChild(h)),!t.value&&(c=oe(r,m=>{h.textContent=m},{immediate:!0}),t.value=!0)},d=()=>{!a||!t.value||(c(),a.head.removeChild(a.getElementById(o)),t.value=!1)};return s&&!l&&Os(p),l||tt(d),{id:o,css:r,unload:d,load:p,isLoaded:nt(t)}}function Xg(e={}){const{window:n=Nn,behavior:t="auto"}=e;if(!n)return{x:q(0),y:q(0)};const a=q(n.scrollX),s=q(n.scrollY),l=b({get(){return a.value},set(r){scrollTo({left:r,behavior:t})}}),o=b({get(){return s.value},set(r){scrollTo({top:r,behavior:t})}});return Pe(n,"scroll",()=>{a.value=n.scrollX,s.value=n.scrollY},{capture:!1,passive:!0}),{x:l,y:o}}function Qg(e={}){const{window:n=Nn,initialWidth:t=Number.POSITIVE_INFINITY,initialHeight:a=Number.POSITIVE_INFINITY,listenOrientation:s=!0,includeScrollbar:l=!0}=e,o=q(t),r=q(a),c=()=>{n&&(l?(o.value=n.innerWidth,r.value=n.innerHeight):(o.value=n.document.documentElement.clientWidth,r.value=n.document.documentElement.clientHeight))};if(c(),Os(c),Pe("resize",c,{passive:!0}),s){const p=mc("(orientation: portrait)");oe(p,()=>c())}return{width:o,height:r}}const vc=e=>{const n=ln();return b(()=>e[n.value]??{})},Zg=e=>typeof e<"u",ek=Array.isArray,_c=(e,n)=>me(e)&&e.startsWith(n),nk=(e,n)=>me(e)&&e.endsWith(n),tk=Object.entries,ak=Object.keys,sk=e=>_c(e,"/");var lk=V({name:"FontIcon",props:{icon:{type:String,default:""},color:{type:String,default:""},size:{type:[String,Number],default:""}},setup(e){const n=b(()=>{const a=["font-icon icon"],s=`iconfont icon-${e.icon}`;return a.push(s),a}),t=b(()=>{const a={};return e.color&&(a.color=e.color),e.size&&(a["font-size"]=Number.isNaN(Number(e.size))?e.size:`${e.size}px`),ak(a).length?a:null});return()=>e.icon?i("span",{key:e.icon,class:n.value,style:t.value}):null}});const ti="https://codepen.io",ok=e=>{let n="";for(const t in e)t!=="prefill"&&t!=="open"&&(n!==""&&(n+="&"),n+=t+"="+encodeURIComponent(e[t]));return n},bc=e=>{const n=e.preview==="true"?"embed/preview":"embed";if("prefill"in e)return[ti,n,"prefill"].join("/");let t=e["slug-hash"];if(!t)throw new Error("slug-hash is required");return e.token&&(t+="/"+e.token),[ti,e.user||"anon",n,t+"?"+ok(e)].join("/").replace(/\/\//g,"//")},Il=(e,n)=>{const t=document.createElement(e);for(const a in n)Object.prototype.hasOwnProperty.call(n,a)&&t.setAttribute(a,n[a].toString());return t},rk=e=>{const n=Il("form",{class:"code-pen-embed-form",style:"display: none;",method:"post",action:bc(e),target:e.name||""});for(const t in e)t!=="prefill"&&n.append(Il("input",{type:"hidden",name:t,value:String(e[t])}));return n},ik=e=>{const{height:n=300,class:t="",name:a="CodePen Embed"}=e,s={class:`cp_embed_iframe ${t}`,src:bc(e),allowfullscreen:"",allowpaymentrequest:"",allowTransparency:"",frameborder:0,width:"100%",height:n,name:a,scrolling:"no",style:"width: 100%; overflow: hidden; display: block;",title:e["pen-title"]||a};return"prefill"in e||(s.loading="lazy"),e["slug-hash"]&&(s.id=`code-pen-embed-${e["slug-hash"].replace("/","_")}`),Il("iframe",s)},pk=(e,n)=>{if(e.parentNode){const t=document.createElement("div");return t.className="code-pen-embed-wrapper",t.append(n),e.parentNode.replaceChild(t,e),t}return e.append(n),e};let ck=1;const ai=(e,n)=>{const t=typeof n=="string"?document.querySelector(n):n instanceof HTMLElement?n:null;e.user||(e.user="anon"),e.name||(e.name=t?`code-pen-api-${ck++}`:"_blank");const a=document.createDocumentFragment();let s=null;"prefill"in e&&(e.data=JSON.stringify(e.prefill||"{}"),s=rk(e),a.append(s)),t?(a.append(ik(e)),pk(t,a)):document.body.appendChild(a),s&&s.submit()};var uk=V({name:"CodePen",props:{link:{type:String,default:""},user:{type:String,default:""},slugHash:{type:String,default:""},title:{type:String,default:""},height:{type:[String,Number],default:380},theme:{type:String,default:"default"},defaultTab:{type:Array,default:()=>["result"]},status:{type:String,default:"preview"}},setup(e){const n=()=>{const l=/(?:^(?:https?:)?\/\/codepen.io\/|^\/|^)(.*?)\/(?:pen|embed)\/(.*?)\/?$/.exec(e.link);return{user:l==null?void 0:l[1],slugHash:l==null?void 0:l[2]}},t=b(()=>n().user||e.user),a=b(()=>n().slugHash||e.slugHash),s=b(()=>({user:t.value,"slug-hash":a.value,"theme-id":e.theme,"default-tab":e.defaultTab.join(","),"pen-title":e.title,height:e.height,preview:e.status==="preview"?"true":""}));return se(()=>{e.status!=="clicktorun"&&ai(s.value,`.codepen-${a.value}`)}),()=>i("div",{class:["codepen-wrapper",`codepen-${a.value}`]},[e.status==="clicktorun"?i("button",{type:"button",class:"codepen-button",onClick:()=>{ai(s.value,`.codepen-${a.value}`)}},"Run Code"):null,i("span",["See the Pen ",i("a",{href:e.link},[e.title])," by ",i("a",{href:`https://codepen.io/${t.value}`},[t.value])," on ",i("a",{href:"https://codepen.io"},["CodePen"]),"."])])}});const si=e=>me(e)?e:`${e}px`,dk=(e,n=0)=>{const t=ge(),a=b(()=>si(fn(e.width)||"100%")),s=q("auto"),l=c=>{if(me(c)){const[p,d]=c.split(":"),h=Number(p)/Number(d);if(!Number.isNaN(h))return h}return typeof c=="number"?c:16/9},o=c=>{const p=fn(e.height),d=l(fn(e.ratio));return p?si(p):`${Number(c)/d+fn(n)}px`},r=()=>{t.value&&(s.value=o(t.value.clientWidth))};return se(()=>{r(),$e(n)&&oe(n,r),Pe("orientationchange",r),Pe("resize",r)}),{el:t,width:a,height:s,resize:r}},hk=e=>yn(e)?e:Ce(e);var mk={"/":{hint:"

    이 브라우저는 PDF를 포함할 수 없습니다. PDF를 보려면 다운로드하십시오: PDF 다운로드

    "}};const ll=e=>{console.error(`[PDF]: ${e}`)},gk=e=>{for(;e.firstChild;)e.removeChild(e.firstChild)},kk=e=>e==="string"?document.querySelector(e):e instanceof HTMLElement?e:document.body,fk=e=>{let n="";return e&&(n+=tk(e).map(([t,a])=>t==="noToolbar"?`toolbar=${a?0:1}`:`${encodeURIComponent(t)}=${encodeURIComponent(a)}`).join("&"),n&&(n=`#${n.slice(0,n.length-1)}`)),n},vk=(e,n,t,a,s)=>{gk(n);const l=`${e==="pdfjs"?`${io(Ce(null))}web/viewer.html?file=${encodeURIComponent(t)}`:t}${fk(a)}`,o=e==="pdfjs"||e==="iframe"?"iframe":"embed",r=document.createElement(o);return r.className="pdf-viewer",r.type="application/pdf",r.title=s,r.src=l,r instanceof HTMLIFrameElement&&(r.allow="fullscreen"),n.classList.add("pdf-viewer-container"),n.appendChild(r),n.getElementsByTagName(o)[0]},_k=(e,n,{title:t,hint:a,options:s={}})=>{var v,w;if(typeof window>"u"||!((v=window==null?void 0:window.navigator)!=null&&v.userAgent))return null;const{navigator:l}=window,{userAgent:o}=l,r=Zg(window.Promise),c=fg(o)||gg(o),p=!c&&kg(o),d=!c&&/firefox/iu.test(o)&&o.split("rv:").length>1?parseInt(o.split("rv:")[1].split(".")[0],10)>18:!1,h=!c&&(r||d);if(!me(e))return ll("URL is not valid"),null;const m=kk(n);if(!m)return ll("Target element cannot be determined"),null;const g=t||((w=/\/([^/]+).pdf/.exec(e))==null?void 0:w[1])||"PDF Viewer";return h||!c?vk(p?"iframe":"embed",m,e,s,g):(m.innerHTML=a.replace(/\[url\]/g,e),ll("This browser does not support embedded PDFs"),null)};var bk=V({name:"PDF",props:{url:{type:String,required:!0},title:{type:String,default:""},width:{type:[String,Number],default:"100%"},height:{type:[String,Number],default:void 0},ratio:{type:[String,Number],default:16/9},page:{type:[String,Number],default:1},noToolbar:Boolean,zoom:{type:[String,Number],default:100}},setup(e){const{el:n,width:t,height:a,resize:s}=dk(e),l=vc(mk);return se(()=>{_k(hk(e.url),n.value,{title:e.title,hint:l.value.hint,options:{page:e.page,noToolbar:e.noToolbar,zoom:e.zoom}}),s()}),()=>i("div",{class:"pdf-viewer-wrapper",ref:n,style:{width:t.value,height:a.value}})}}),yk=[{name:"twitter",link:"https://twitter.com/intent/tweet?text=[title]&url=[url]&hashtags=[tags][title]",color:"#000",shape:''},{name:"facebook",link:"https://www.facebook.com/sharer/sharer.php?u=[url]&title=[title]&description=[description]"e=[summary]&hashtag=[tags]",color:"#3c599b",shape:''},{name:"reddit",link:"https://www.reddit.com/submit?title=[title]&url=[url]",color:"#ff4501",shape:''},{name:"telegram",link:"https://t.me/share/url?url=[url]&text=[title]%0D%0A[description|summary]",color:"#158cc7",shape:''},{name:"whatsapp",link:"https://api.whatsapp.com/send?text=[title]%0D%0A[url]%0D%0A[description|summary]",color:"#31B84C",shape:''},{name:"email",link:"mailto:?subject=[title]&body=[url]%0D%0A%0D%0A[description|summary]",color:"#1384FF",action:"open",shape:''}];const Qa=e=>{var n;return((n=document.querySelector(`meta[name="${e}"]`))==null?void 0:n.getAttribute("content"))??null},li=(e,n="")=>{const t=["vp-share-icon",n];return yn(e)||sk(e)?i("img",{class:t,src:e,loading:"lazy","no-view":""}):_c(e,"<")&&nk(e,">")?i("div",{class:t,innerHTML:e}):i("div",{class:[...t,e]})};var wk=V({name:"ShareService",props:{config:{type:Object,default:()=>({})},plain:Boolean,title:{type:String,required:!1},description:{type:String,required:!1},url:{type:String,required:!1},summary:{type:String,required:!1},cover:{type:String,required:!1},tag:{type:[Array,String],required:!1}},setup(e){let n;const t=ke(),a=ye(),s=q(!1),l=()=>{var v;const r=e.title??t.value.title,c=e.description??a.value.description??Qa("description")??Qa("og:description")??Qa("twitter:description"),p=e.url??typeof window>"u"?null:window.location.href,d=e.cover??Qa("og:image"),h=(v=document.querySelector(".theme-default-content :not(a) > img"))==null?void 0:v.getAttribute("src"),m=e.tag??a.value.tag??a.value.tags,g=ek(m)?m.filter(me).join(","):me(m)?m:null;return e.config.link.replace(/\[([^\]]+)\]/g,(w,C)=>{const _=C.split("|");for(const T of _){if(T==="url"&&p)return p;if(T==="title"&&r)return r;if(T==="description"&&c)return c;if(T==="summary"&&e.summary)return e.summary;if(T==="cover"&&d)return d;if(T==="image"&&h)return h;if(T==="tags"&&g)return g}return""})},o=()=>{const r=l();switch(e.config.action){case"navigate":window.open(r);break;case"open":window.open(r,"_blank");break;case"qrcode":u(()=>import("./browser-D6eOinvE.js").then(c=>c.b),__vite__mapDeps([])).then(({toDataURL:c})=>c(r,{errorCorrectionLevel:"H",width:250,scale:1,margin:1.5})).then(c=>{n.emit(``)});break;default:Cg(r,"share")}};return se(()=>{n=new bg}),()=>{const{config:{name:r,icon:c,shape:p,color:d},plain:h}=e;return[i("button",{type:"button",class:["vp-share-button",{plain:h}],"aria-label":r,"data-balloon-pos":"up",onClick:()=>o()},h?li(p,"plain"):c?li(c):i("div",{class:"vp-share-icon colorful",style:{background:d},innerHTML:p})),s.value?i("div",{class:"share-popup"}):null]}}});const oi=yk;var Ck=V({name:"Share",props:{services:{type:[String,Array],default:()=>oi.map(({name:e})=>e)},titleGetter:{type:Function,default:e=>e.title},descriptionGetter:{type:Function,default:e=>e.frontmatter.description},summaryGetter:{type:Function,default:e=>e.summary},coverGetter:{type:Function,default:e=>e.cover},tagGetter:{type:Function,default:({frontmatter:e})=>e.tag||e.tags},inline:Boolean,colorful:Boolean},setup(e){const n=ke(),t=b(()=>(me(e.services)?e.services.split(","):e.services).map(s=>_n(s)?s.name&&s.link?s:null:oi.find(({name:l})=>l===s)).filter(s=>!!s)),a=b(()=>{const s={};return["titleGetter","descriptionGetter","summaryGetter","coverGetter","tagGetter"].forEach(l=>{if(Rp(e[l])){const o=e[l](n.value);o&&(s[l.replace("Getter","")]=o)}}),s});return()=>i("div",{class:"vp-share-buttons",style:e.inline?{display:"inline-block"}:{}},t.value.map(s=>i(wk,{config:s,...a.value,plain:!e.colorful})))}}),Ek={"/":{source:"소스 코드"}},xk=V({name:"SiteInfo",components:{BitbucketIcon:fo,GiteeIcon:ko,GitHubIcon:mo,GitLabIcon:go,SourceIcon:vo},props:{name:{type:String,required:!0},desc:{type:String,default:""},logo:{type:String,default:""},url:{type:String,required:!0},preview:{type:String,required:!0},repo:{type:String,default:""}},setup(e){const n=vc(Ek),t=b(()=>e.repo?_o(e.repo):null);return()=>i("div",{class:"vp-site-info","data-name":e.name},[i("a",{class:"vp-site-info-navigator",title:e.name,href:e.url,target:"_blank"}),i("div",{class:"vp-site-info-preview",style:{background:`url(${Ce(e.preview)}) center/cover no-repeat`}}),i("div",{class:"vp-site-info-detail"},[e.logo?i("img",{class:"vp-site-info-logo",src:e.logo,alt:"",loading:"lazy","no-view":""}):null,i("div",{class:"vp-site-info-name"},e.name),i("div",{class:"vp-site-info-desc"},e.desc)]),e.repo?i("div",{class:"vp-site-info-source-wrapper"},i("a",{class:"vp-site-info-source",href:e.repo,"aria-label":n.value.source,"data-balloon-pos":"left",title:n.value.source,target:"_blank"},i(an(`${t.value}Icon`)))):null])}}),Tk=V({name:"VidStack",props:{sources:{type:Array,default:()=>[]},tracks:{type:Array,default:()=>[]}},setup(e,{attrs:n}){return se(async()=>{await Promise.all([u(()=>import("./vidstack-player-X42x4ssq.js"),__vite__mapDeps([523,524])),u(()=>import("./vidstack-player-layouts-c3Bu6zPS.js"),__vite__mapDeps([525,524,526])),u(()=>import("./vidstack-player-ui-BQjgjIeO.js"),__vite__mapDeps([527,524,526]))])}),()=>i("media-player",n,[i("media-provider",[n.poster?i("media-poster",{class:"vds-poster",alt:n.alt||n.title}):null,e.sources.map(t=>_n(t)?i("source",t):i("source",{src:t})),e.tracks.map(t=>i("track",t))]),i("media-audio-layout"),i("media-video-layout",n)])}});const yc=({type:e="info",text:n="",vertical:t,color:a},{slots:s})=>{var l;return i("span",{class:["vp-badge",e,{diy:a}],style:{verticalAlign:t??!1,backgroundColor:a??!1}},((l=s.default)==null?void 0:l.call(s))||n)};yc.displayName="Badge";const Sk=Ge({enhance:({app:e})=>{Ke("FontIcon")||e.component("FontIcon",lk),Ke("CodePen")||e.component("CodePen",uk),Ke("PDF")||e.component("PDF",bk),Ke("Share")||e.component("Share",Ck),Ke("SiteInfo")||e.component("SiteInfo",xk),Ke("VidStack")||e.component("VidStack",Tk),Ke("Badge")||e.component("Badge",yc)},setup:()=>{Yg(` @import url("https://at.alicdn.com/t/c/font_2410206_5vb9zlyghj.css"); + `)},rootComponents:[]}),ri=async(e,n)=>{const{path:t,query:a}=e.currentRoute.value,{scrollBehavior:s}=e.options;e.options.scrollBehavior=void 0,await e.replace({path:t,query:a,hash:n}),e.options.scrollBehavior=s},Lk=({headerLinkSelector:e,headerAnchorSelector:n,delay:t,offset:a=5})=>{const s=Pn();Pe("scroll",wo(()=>{var v,w;const o=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(o-0)h.some(_=>_.hash===C.hash));for(let C=0;C=(((v=_.parentElement)==null?void 0:v.offsetTop)??0)-a,S=!T||o<(((w=T.parentElement)==null?void 0:w.offsetTop)??0)-a;if(!(y&&S))continue;const x=decodeURIComponent(s.currentRoute.value.hash),K=decodeURIComponent(_.hash);if(x===K)return;if(d){for(let F=C+1;F{const n=ln();return b(()=>e[n.value]??{})},Dk=(e,n)=>{var a;const t=(a=(n==null?void 0:n._instance)||_t())==null?void 0:a.appContext.components;return t?e in t||We(e)in t||Kt(We(e))in t:!1},ol=e=>typeof e=="number",ii=(e,n)=>me(e)&&e.startsWith(n),Rk=(e,n)=>me(e)&&e.endsWith(n),Hk=Object.entries,Fk=Object.keys;let wc=e=>me(e.title)?{title:e.title}:null;const Cc=Symbol(""),Nk=e=>{wc=e},jk=()=>be(Cc),Mk=e=>{e.provide(Cc,wc)};var $k={"/":{title:"목차",empty:"목차 없음"}};const Bk=V({name:"Catalog",props:{base:{type:String,default:""},level:{type:Number,default:3},index:Boolean,hideHeading:Boolean},setup(e){const n=jk(),t=Ds($k),a=ke(),s=K0(),l=Jp(),r=ge(Hk(s.value).map(([p,{meta:d}])=>{const h=n(d);if(!h)return null;const m=p.split("/").length;return{level:Rk(p,"/")?m-2:m-1,base:p.replace(/\/[^/]+\/?$/,"/"),path:p,...h}}).filter(p=>_n(p)&&me(p.title))),c=b(()=>{const p=e.base?Am(io(e.base)):a.value.path.replace(/\/[^/]+$/,"/"),d=p.split("/").length-2,h=[];return r.value.filter(({level:m,path:g})=>{if(!ii(g,p)||g===p)return!1;if(p==="/"){const v=Fk(l.value.locales).filter(w=>w!=="/");if(g==="/404.html"||v.some(w=>ii(g,w)))return!1}return m-d<=e.level}).sort(({title:m,level:g,order:v},{title:w,level:C,order:_})=>{const T=g-C;return T||(ol(v)?ol(_)?v>0?_>0?v-_:-1:_<0?v-_:1:v:ol(_)?_:m.localeCompare(w))}).forEach(m=>{var w;const{base:g,level:v}=m;switch(v-d){case 1:{h.push(m);break}case 2:{const C=h.find(_=>_.path===g);C&&(C.children??(C.children=[])).push(m);break}default:{const C=h.find(_=>_.path===g.replace(/\/[^/]+\/$/,"/"));if(C){const _=(w=C.children)==null?void 0:w.find(T=>T.path===g);_&&(_.children??(_.children=[])).push(m)}}}}),h});return()=>{const p=c.value.some(d=>d.children);return i("div",{class:["vp-catalog-wrapper",{index:e.index}]},[e.hideHeading?null:i("h2",{class:"vp-catalog-main-title"},t.value.title),c.value.length?i(e.index?"ol":"ul",{class:["vp-catalogs",{deep:p}]},c.value.map(({children:d=[],title:h,path:m,content:g})=>{const v=i(Ve,{class:"vp-catalog-title",to:m},()=>g?i(g):h);return i("li",{class:"vp-catalog"},p?[i("h3",{id:h,class:["vp-catalog-child-title",{"has-children":d.length}]},[i("a",{href:`#${h}`,class:"vp-catalog-header-anchor","aria-hidden":!0},"#"),v]),d.length?i(e.index?"ol":"ul",{class:"vp-child-catalogs"},d.map(({children:w=[],content:C,path:_,title:T})=>i("li",{class:"vp-child-catalog"},[i("div",{class:["vp-catalog-sub-title",{"has-children":w.length}]},[i("a",{href:`#${T}`,class:"vp-catalog-header-anchor"},"#"),i(Ve,{class:"vp-catalog-title",to:_},()=>C?i(C):T)]),w.length?i(e.index?"ol":"div",{class:e.index?"vp-sub-catalogs":"vp-sub-catalogs-wrapper"},w.map(({content:y,path:S,title:R})=>e.index?i("li",{class:"vp-sub-catalog"},i(Ve,{to:S},()=>y?i(y):R)):i(Ve,{class:"vp-sub-catalog-link",to:S},()=>y?i(y):R))):null]))):null]:i("div",{class:"vp-catalog-child-title"},v))})):i("p",{class:"vp-empty-catalog"},t.value.empty)])}}}),Uk=Ge({enhance:({app:e})=>{Mk(e),Dk("Catalog",e)||e.component("Catalog",Bk)}}),Ec=e=>{const n=ln();return b(()=>e[n.value]??{})},Al=Array.isArray,xo=(e,n)=>me(e)&&e.startsWith(n),Yt=Object.entries,zk=Object.fromEntries,mt=Object.keys,To=e=>{if(e){if(typeof e=="number")return new Date(e);const n=Date.parse(e.toString());if(!Number.isNaN(n))return new Date(n)}return null},Rs=e=>xo(e,"/");var Kk={"/":{backToTop:"맨 위로"}};const qk=V({name:"BackToTop",setup(e){const n=ye(),t=Ec(Kk),a=ge(),{height:s}=Jg(a),{height:l}=Qg(),{y:o}=Xg(),r=b(()=>n.value.backToTop!==!1&&o.value>100),c=b(()=>o.value/(s.value-l.value)*100);return se(()=>{a.value=document.body}),()=>i(Fn,{name:"back-to-top"},()=>r.value?i("button",{type:"button",class:"vp-back-to-top-button","aria-label":t.value.backToTop,onClick:()=>{window.scrollTo({top:0,behavior:"smooth"})}},[i("span",{class:"vp-scroll-progress",role:"progressbar","aria-labelledby":"loadinglabel","aria-valuenow":c.value},i("svg",i("circle",{cx:"50%",cy:"50%",style:{"stroke-dasharray":`calc(${Math.PI*c.value}% - ${4*Math.PI}px) calc(${Math.PI*100}% - ${4*Math.PI}px)`}}))),i("div",{class:"back-to-top-icon"})]):null)}}),Jk=Ge({rootComponents:[qk]}),Wk=i("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[i("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),i("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),xc=V({name:"ExternalLinkIcon",props:{locales:{type:Object,required:!1,default:()=>({})}},setup(e){const n=ln(),t=b(()=>e.locales[n.value]??{openInNewWindow:"open in new window"});return()=>i("span",[Wk,i("span",{class:"external-link-icon-sr-only"},t.value.openInNewWindow)])}});var Gk={};const Yk=Gk,Xk=Ge({enhance({app:e}){e.component("ExternalLinkIcon",i(xc,{locales:Yk}))}});/** + * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT + */const ce={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:e=>{const n=ce.isStarted();e=rl(e,ce.settings.minimum,1),ce.status=e===1?null:e;const t=ce.render(!n),a=t.querySelector(ce.settings.barSelector),s=ce.settings.speed,l=ce.settings.easing;return t.offsetWidth,Qk(o=>{Za(a,{transform:"translate3d("+pi(e)+"%,0,0)",transition:"all "+s+"ms "+l}),e===1?(Za(t,{transition:"none",opacity:"1"}),t.offsetWidth,setTimeout(function(){Za(t,{transition:"all "+s+"ms linear",opacity:"0"}),setTimeout(function(){ce.remove(),o()},s)},s)):setTimeout(()=>o(),s)}),ce},isStarted:()=>typeof ce.status=="number",start:()=>{ce.status||ce.set(0);const e=()=>{setTimeout(()=>{ce.status&&(ce.trickle(),e())},ce.settings.trickleSpeed)};return ce.settings.trickle&&e(),ce},done:e=>!e&&!ce.status?ce:ce.inc(.3+.5*Math.random()).set(1),inc:e=>{let n=ce.status;return n?(typeof e!="number"&&(e=(1-n)*rl(Math.random()*n,.1,.95)),n=rl(n+e,0,.994),ce.set(n)):ce.start()},trickle:()=>ce.inc(Math.random()*ce.settings.trickleRate),render:e=>{if(ce.isRendered())return document.getElementById("nprogress");ci(document.documentElement,"nprogress-busy");const n=document.createElement("div");n.id="nprogress",n.innerHTML=ce.settings.template;const t=n.querySelector(ce.settings.barSelector),a=e?"-100":pi(ce.status||0),s=document.querySelector(ce.settings.parent);return Za(t,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),s!==document.body&&ci(s,"nprogress-custom-parent"),s==null||s.appendChild(n),n},remove:()=>{ui(document.documentElement,"nprogress-busy"),ui(document.querySelector(ce.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&Zk(e)},isRendered:()=>!!document.getElementById("nprogress")},rl=(e,n,t)=>et?t:e,pi=e=>(-1+e)*100,Qk=function(){const e=[];function n(){const t=e.shift();t&&t(n)}return function(t){e.push(t),e.length===1&&n()}}(),Za=function(){const e=["Webkit","O","Moz","ms"],n={};function t(o){return o.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(r,c){return c.toUpperCase()})}function a(o){const r=document.body.style;if(o in r)return o;let c=e.length;const p=o.charAt(0).toUpperCase()+o.slice(1);let d;for(;c--;)if(d=e[c]+p,d in r)return d;return o}function s(o){return o=t(o),n[o]??(n[o]=a(o))}function l(o,r,c){r=s(r),o.style[r]=c}return function(o,r){for(const c in r){const p=r[c];p!==void 0&&Object.prototype.hasOwnProperty.call(r,c)&&l(o,c,p)}}}(),Tc=(e,n)=>(typeof e=="string"?e:So(e)).indexOf(" "+n+" ")>=0,ci=(e,n)=>{const t=So(e),a=t+n;Tc(t,n)||(e.className=a.substring(1))},ui=(e,n)=>{const t=So(e);if(!Tc(e,n))return;const a=t.replace(" "+n+" "," ");e.className=a.substring(1,a.length-1)},So=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),Zk=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)},ef=()=>{se(()=>{const e=Pn(),n=new Set;n.add(e.currentRoute.value.path),e.beforeEach(t=>{n.has(t.path)||ce.start()}),e.afterEach(t=>{n.add(t.path),ce.done()})})},nf=Ge({setup(){ef()}}),tf=JSON.parse('{"encrypt":{},"logo":"/logo.png","repo":"docmoa/docs","docsDir":"docs","print":true,"fullscreen":true,"contributors":true,"lastUpdated":true,"locales":{"/":{"lang":"ko-KR","navbarLocales":{"langName":"한국어","selectLangAriaLabel":"언어 선택"},"metaLocales":{"author":"작성자","date":"작성일","origin":"원본","views":"조회수","category":"카테고리","tag":"태그","readingTime":"읽는 시간","words":"단어","toc":"이 페이지에서","prev":"이전","next":"다음","lastUpdated":"마지막 수정","contributors":"기여자","editLink":"Edit this page on GitHub","print":"인쇄"},"blogLocales":{"article":"게시글","articleList":"글 목록","category":"카테고리","tag":"태그","timeline":"타임라인","timelineTitle":"어제 한 번 더!","all":"모두","intro":"프로필","star":"스타","empty":"$text가 비어있습니다."},"paginationLocales":{"prev":"이전","next":"다음","navigate":"이동","action":"가기","errorText":"1에서 $page 사이의 숫자를 입력하세요!"},"outlookLocales":{"themeColor":"테마 색상","darkmode":"테마 모드","fullscreen":"전체 화면"},"routeLocales":{"skipToContent":"본문으로 건너뛰기","notFoundTitle":"페이지를 찾을 수 없습니다.","notFoundMsg":["여기에는 아무것도 없습니다.","어떻게 여기까지 오셨나요?","4-0-4 입니다.","깨진 링크가 있는 것 같습니다."],"back":"뒤로가기","home":"메인으로","openInNewWindow":"새 창에서 열기"},"navbar":["/",{"text":"How To","icon":"launch","link":"/00-Howto/"},{"text":"Infra","icon":"computer","children":[{"text":"Infrastructure","link":"/01-Infrastructure/"},{"text":"Private-Platform","link":"/02-PrivatePlatform/"},{"text":"Public-Cloud","link":"/03-PublicCloud/"}]},{"text":"Software","icon":"code","link":"/05-Software/"},{"text":"HashiCorp","icon":"workingDirectory","link":"/04-HashiCorp/"},{"text":"Etc.","icon":"flex","link":"/06-etc/"},{"text":"MORE","icon":"more","children":[{"text":"About","icon":"info","link":"/99-about/01-About.html"},{"text":"Thank you","icon":"like","link":"/99-about/02-Thanks.html"}]},{"text":"Tags","icon":"tag","link":"/tag"}],"sidebar":{"/00-Howto/":"structure","/01-Infrastructure/":"structure","/02-PrivatePlatform/":"structure","/03-PublicCloud/":"structure","/04-HashiCorp/":"structure","/05-Software/":"structure","/06-etc/":"structure","/99-about/":"structure","/":["","tag","99-about/01-About"]},"footer":"CC BY-NC-ND 4.0 Licensed | ⓒ 2021-present docmoa™ contributers all rights reserved.","displayFooter":true}}}'),af=q(tf),Sc=()=>af,Lc=Symbol(""),sf=()=>{const e=be(Lc);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},lf=(e,n)=>{const{locales:t,...a}=e;return{...a,...t==null?void 0:t[n]}},of=Ge({enhance({app:e}){const n=Sc(),t=e._context.provides[uo],a=b(()=>lf(n.value,t.routeLocale.value));e.provide(Lc,a),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return n.value}},$themeLocale:{get(){return a.value}}})}}),rf=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,pf=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&rf.test(navigator.userAgent),il=new Map,cf=({delay:e=500,duration:n=2e3,locales:t,selector:a,showInMobile:s})=>{const{copy:l}=jg({legacy:!0}),o=Ec(t),r=ke(),c=h=>{if(!h.hasAttribute("copy-code-registered")){const m=document.createElement("button");m.type="button",m.classList.add("vp-copy-code-button"),m.innerHTML='
    ',m.setAttribute("aria-label",o.value.copy),m.setAttribute("data-copied",o.value.copied),h.parentElement&&h.parentElement.insertBefore(m,h),h.setAttribute("copy-code-registered","")}},p=()=>{vt().then(()=>setTimeout(()=>{a.forEach(h=>{document.querySelectorAll(h).forEach(c)})},e))},d=(h,m,g)=>{let{innerText:v=""}=m;/language-(shellscript|shell|bash|sh|zsh)/.test(h.classList.toString())&&(v=v.replace(/^ *(\$|>) /gm,"")),l(v).then(()=>{g.classList.add("copied"),clearTimeout(il.get(g));const w=setTimeout(()=>{g.classList.remove("copied"),g.blur(),il.delete(g)},n);il.set(g,w)})};se(()=>{const h=!pf()||s;h&&p(),Pe("click",m=>{const g=m.target;if(g.matches('div[class*="language-"] > button.copy')){const v=g.parentElement,w=g.nextElementSibling;w&&d(v,w,g)}else if(g.matches('div[class*="language-"] div.vp-copy-icon')){const v=g.parentElement,w=v.parentElement,C=v.nextElementSibling;C&&d(w,C,v)}}),oe(()=>r.value.path,()=>{h&&p()})})};var uf={"/":{copy:"코드 복사",copied:"복사됨"}},df=['.theme-hope-content div[class*="language-"] pre'];const hf=500,mf=2e3,gf=uf,kf=df,ff=!1,vf=Ge({setup:()=>{cf({selector:kf,locales:gf,duration:mf,delay:hf,showInMobile:ff})}});var _f={"/":{author:"저작권자 :author",license:":license 프로토콜에 따라",link:":link"}},bf={canonical:"https://docmoa.github.io",author:"",license:"CC BY-NC-ND 4.0 Licensed | ⓒ 2021-present docmoa™ contributers all rights reserved.",global:!1,disableCopy:!1,disableSelection:!1,triggerLength:100,maxLength:0};const Kn=bf,{canonical:es}=Kn,yf=()=>{const e=ye(),n=Ds(_f),t=ke(),a=b(()=>!!e.value.copy||e.value.copy!==!1&&Kn.global),s=b(()=>_n(e.value.copy)?e.value.copy:null),l=b(()=>{var g;return((g=s.value)==null?void 0:g.disableCopy)??Kn.disableCopy}),o=b(()=>{var g;return a.value?((g=s.value)==null?void 0:g.disableSelection)??Kn.disableSelection:!1}),r=b(()=>{var g;return a.value?((g=s.value)==null?void 0:g.maxLength)??Kn.maxLength:0}),c=b(()=>{var g;return((g=s.value)==null?void 0:g.triggerLength)??Kn.triggerLength}),p=()=>es?`${Ls(yn(es)?es:`https://${es}`)}${t.value.path}`:window.location.href,d=(g,v)=>{const{author:w,license:C,link:_}=n.value;return[g?w.replace(":author",g):"",v?C.replace(":license",v):"",_.replace(":link",p())].filter(T=>T).join(` +`)},h=()=>{if(me(t.value.copyright))return t.value.copyright.replace(":link",p());const{author:g,license:v}=t.value.copyright||{};return d(g??Kn.author,v??Kn.license)},m=g=>{const v=getSelection();if(v){const w=v.getRangeAt(0);if(a.value){const C=w.toString().length;if(l.value||r.value&&C>r.value)return g.preventDefault();if(C>=c.value){g.preventDefault();const _=h(),T=document.createElement("div");T.appendChild(v.getRangeAt(0).cloneContents()),g.clipboardData&&(g.clipboardData.setData("text/html",`${T.innerHTML}
    `),g.clipboardData.setData("text/plain",`${v.getRangeAt(0).cloneContents().textContent||""} +------ +${_}`))}}}};se(()=>{const g=document.querySelector("#app");Pe(g,"copy",m),to(()=>{g.style.userSelect=o.value?"none":"auto"})})},wf=Ge({setup:()=>{yf()}}),gt=e=>{const n=atob(e);return hg(pg(dg(n,!0)))},Cf=e=>typeof e<"u",Ef=Array.isArray,xf=Object.entries,Pc=Object.keys,Vl=(e,...n)=>{if(n.length===0)return e;const t=n.shift()||null;return t&&xf(t).forEach(([a,s])=>{a==="__proto__"||a==="constructor"||(_n(e[a])&&_n(s)?Vl(e[a],s):Ef(s)?e[a]=[...s]:_n(s)?e[a]={...s}:e[a]=t[a])}),Vl(e,...n)},fs=()=>{const e=document.documentElement;return e.classList.contains("dark")||e.getAttribute("data-theme")==="dark"},Tf=(e,n)=>n==="json"?JSON.parse(e):new Function(`let config,__chart_js_config__; +{ +${e} +__chart_js_config__=config; +} +return __chart_js_config__;`)();var Sf=V({name:"ChartJS",props:{config:{type:String,required:!0},id:{type:String,required:!0},title:{type:String,default:""},type:{type:String,default:"json"}},setup(e){const n=ge(),t=ge(),a=q(!1),s=q(!0),l=b(()=>gt(e.config));let o=!1,r;const c=async p=>{const[{default:d}]=await Promise.all([u(()=>import("./auto-C0MMSKEI.js"),__vite__mapDeps([])),o?Promise.resolve():(o=!0,new Promise(g=>setTimeout(g,800)))]);d.defaults.borderColor=p?"#ccc":"#36A2EB",d.defaults.color=p?"#fff":"#000",d.defaults.maintainAspectRatio=!1;const h=Tf(l.value,e.type),m=t.value.getContext("2d");r==null||r.destroy(),r=new d(m,h),s.value=!1};return se(()=>{a.value=fs(),gc(document.documentElement,()=>{a.value=fs()},{attributeFilter:["class","data-theme"],attributes:!0}),oe(a,p=>c(p),{immediate:!0})}),()=>[e.title?i("div",{class:"chartjs-title"},decodeURIComponent(e.title)):null,s.value?i(bt,{class:"chartjs-loading",height:192}):null,i("div",{ref:n,class:"chartjs-wrapper",id:e.id,style:{display:s.value?"none":"block"}},i("canvas",{ref:t,height:400}))]}});const ns=Co("VUEPRESS_CODE_TAB_STORE",{});var Lf=V({name:"CodeTabs",props:{active:{type:Number,default:0},data:{type:Array,required:!0},id:{type:String,required:!0},tabId:{type:String,default:""}},slots:Object,setup(e,{slots:n}){const t=q(e.active),a=ge([]),s=()=>{e.tabId&&(ns.value[e.tabId]=e.data[t.value].id)},l=(p=t.value)=>{t.value=p{t.value=p>0?p-1:a.value.length-1,a.value[t.value].focus()},r=(p,d)=>{p.key===" "||p.key==="Enter"?(p.preventDefault(),t.value=d):p.key==="ArrowRight"?(p.preventDefault(),l()):p.key==="ArrowLeft"&&(p.preventDefault(),o()),e.tabId&&(ns.value[e.tabId]=e.data[t.value].id)},c=()=>{if(e.tabId){const p=e.data.findIndex(({id:d})=>ns.value[e.tabId]===d);if(p!==-1)return p}return e.active};return se(()=>{t.value=c(),oe(()=>ns.value[e.tabId],(p,d)=>{if(e.tabId&&p!==d){const h=e.data.findIndex(({id:m})=>m===p);h!==-1&&(t.value=h)}})}),()=>e.data.length?i("div",{class:"vp-code-tabs"},[i("div",{class:"vp-code-tabs-nav",role:"tablist"},e.data.map(({id:p},d)=>{const h=d===t.value;return i("button",{type:"button",ref:m=>{m&&(a.value[d]=m)},class:["vp-code-tab-nav",{active:h}],role:"tab","aria-controls":`codetab-${e.id}-${d}`,"aria-selected":h,onClick:()=>{t.value=d,s()},onKeydown:m=>r(m,d)},n[`title${d}`]({value:p,isActive:h}))})),e.data.map(({id:p},d)=>{const h=d===t.value;return i("div",{class:["vp-code-tab",{active:h}],id:`codetab-${e.id}-${d}`,role:"tabpanel","aria-expanded":h},[i("div",{class:"vp-code-tab-title"},n[`title${d}`]({value:p,isActive:h})),n[`tab${d}`]({value:p,isActive:h})])})]):null}});const Ic=({active:e=!1},{slots:n})=>{var t;return i("div",{class:["code-group-item",{active:e}],"aria-selected":e},(t=n.default)==null?void 0:t.call(n))};Ic.displayName="CodeGroupItem";const Pf=V({name:"CodeGroup",slots:Object,setup(e,{slots:n}){const t=q(-1),a=ge([]),s=(r=t.value)=>{t.value=r{t.value=r>0?r-1:a.value.length-1,a.value[t.value].focus()},o=(r,c)=>{r.key===" "||r.key==="Enter"?(r.preventDefault(),t.value=c):r.key==="ArrowRight"?(r.preventDefault(),s(c)):r.key==="ArrowLeft"&&(r.preventDefault(),l(c))};return()=>{var c;const r=(((c=n.default)==null?void 0:c.call(n))||[]).filter(p=>p.type.name==="CodeGroupItem").map(p=>(p.props===null&&(p.props={}),p));return r.length===0?null:(t.value<0||t.value>r.length-1?(t.value=r.findIndex(p=>"active"in p.props),t.value===-1&&(t.value=0)):r.forEach((p,d)=>{p.props.active=d===t.value}),i("div",{class:"code-group"},[i("div",{class:"code-group-nav"},r.map((p,d)=>{const h=d===t.value;return i("button",{type:"button",ref:m=>{m&&(a.value[d]=m)},class:["code-group-nav-tab",{active:h}],"aria-pressed":h,"aria-expanded":h,onClick:()=>{t.value=d},onKeydown:m=>o(m,d)},p.props.title)})),r]))}}}),If='',Af='',Vf='';var Of={useBabel:!1,jsLib:[],cssLib:[],codepenLayout:"left",codepenEditors:"101",babel:"https://unpkg.com/@babel/standalone/babel.min.js",vue:"https://unpkg.com/vue/dist/vue.global.prod.js",react:"https://unpkg.com/react/umd/react.production.min.js",reactDOM:"https://unpkg.com/react-dom/umd/react-dom.production.min.js"};const pl=Of,di={html:{types:["html","slim","haml","md","markdown","vue"],map:{html:"none",vue:"none",md:"markdown"}},js:{types:["js","javascript","coffee","coffeescript","ts","typescript","ls","livescript"],map:{js:"none",javascript:"none",coffee:"coffeescript",ls:"livescript",ts:"typescript"}},css:{types:["css","less","sass","scss","stylus","styl"],map:{css:"none",styl:"stylus"}}},Df=(e,n,t)=>{const a=document.createElement(e);return _n(n)&&Pc(n).forEach(s=>{if(s.indexOf("data"))a[s]=n[s];else{const l=s.replace("data","");a.dataset[l]=n[s]}}),t&&t.forEach(s=>{a.appendChild(s)}),a},Lo=e=>({...pl,...e,jsLib:Array.from(new Set([...pl.jsLib||[],...e.jsLib||[]])),cssLib:Array.from(new Set([...pl.cssLib||[],...e.cssLib||[]]))}),Ot=(e,n)=>{if(Cf(e[n]))return e[n];const t=new Promise(a=>{var l;const s=document.createElement("script");s.src=n,(l=document.querySelector("body"))==null||l.appendChild(s),s.onload=()=>{a()}});return e[n]=t,t},Rf=(e,n)=>{if(n.css&&Array.from(e.childNodes).every(t=>t.nodeName!=="STYLE")){const t=Df("style",{innerHTML:n.css});e.appendChild(t)}},Hf=(e,n,t)=>{const a=t.getScript();if(a&&Array.from(n.childNodes).every(s=>s.nodeName!=="SCRIPT")){const s=document.createElement("script");s.appendChild(document.createTextNode(`{const document=window.document.querySelector('#${e} .vp-code-demo-display').shadowRoot; +${a}}`)),n.appendChild(s)}},Ff=e=>{const n=Pc(e),t={html:[],js:[],css:[],isLegal:!1};return["html","js","css"].forEach(a=>{const s=n.filter(l=>di[a].types.includes(l));if(s.length){const l=s[0];t[a]=[e[l].replace(/^\n|\n$/g,""),di[a].map[l]||l]}}),t.isLegal=(!t.html.length||t.html[1]==="none")&&(!t.js.length||t.js[1]==="none")&&(!t.css.length||t.css[1]==="none"),t},Ac=e=>e.replace(/
    /g,"
    ").replace(/<((\S+)[^<]*?)\s+\/>/g,"<$1>"),Vc=e=>`
    +${Ac(e)} +
    `,Nf=e=>`${e.replace("export default ","const $reactApp = ").replace(/App\.__style__(\s*)=(\s*)`([\s\S]*)?`/,"")}; +ReactDOM.createRoot(document.getElementById("app")).render(React.createElement($reactApp))`,jf=e=>e.replace(/export\s+default\s*\{(\n*[\s\S]*)\n*\}\s*;?$/u,"Vue.createApp({$1}).mount('#app')").replace(/export\s+default\s*define(Async)?Component\s*\(\s*\{(\n*[\s\S]*)\n*\}\s*\)\s*;?$/u,"Vue.createApp({$1}).mount('#app')").trim(),Oc=e=>`(function(exports){var module={};module.exports=exports;${e};return module.exports.__esModule?module.exports.default:module.exports;})({})`,Mf=(e,n)=>{const t=Lo(n),a=e.js[0]||"";return{...t,html:Ac(e.html[0]||""),js:a,css:e.css[0]||"",isLegal:e.isLegal,getScript:()=>{var s;return t.useBabel?((s=window.Babel.transform(a,{presets:["es2015"]}))==null?void 0:s.code)||"":a}}},$f=/