요즘 개발 후 배포할 때 빠지지 않고 등장하는 주제 중 하나가 도커입니다. 도커 하면 뒤이어 따라 나오는 것이 쿠버네티스입니다. 이 글에선 쿠버네티스가 대체 무엇인지, 왜 필요한지 그리고 무엇을 할 수 있는지에 대해 알아보도록 하겠습니다.

 

 

 

1. Kubernetes. 쿠버네티스.

 

Kubernetes, 또는 쿠버네티스, 또는 간단히 "큐브(kube)"는 Linux 컨테이너 작업을 자동화하는 오픈소스 플랫폼입니다.


쿠버네티스는 컨테이너화 된 워크로드와 서비스를 관리하기 위한 이식할 수 있고, 확장 가능한 오픈소스 플랫폼으로, 선언적 구성과 자동화를 모두 지원합니다. 즉, Linux 컨테이너를 실행하는 호스트 그룹을 함께 클러스터링 할 수 있으며 쿠버네티스를 통해 이러한 클러스터를 쉽고 효율적으로 관리할 수 있습니다. 이 클러스터는 퍼블릭 클라우드, 프라이빗 클라우드 또는 하이브리드 클라우드 전체로 호스트를 확장할 수 있습니다. 

이러한 이유로 쿠버네티스는 실시간 데이터 스트리밍과 같이 신속한 확장을 요하는 클라우드 네이티브 애플리케이션을 호스팅 하는 데 이상적인 플랫폼입니다.

쿠버네티스는 원래 Google 엔지니어들이 개발하고 설계한 플랫폼입니다. Google은 초창기에 Linux 컨테이너 기술에 기여(cgroups)하였으며 Google 제품이 컨테이너에서 어떻게 작동하는지 대중에게 공개하였습니다. 이는 Google의 클라우드 서비스를 구동하는 기술이기도 합니다. Google은 내부 플랫폼인 Borg를 통해 일주일에 20억 개 이상의 컨테이너 배포를 생성하고 있습니다. Borg는 쿠버네티스의 전신이었으며 수년 동안 Borg를 개발하는 과정에서 축적된 경험은 쿠버네티스 기술의 주요 원동력이 되었습니다.

 

 

 

2. 기술의 변화.

 

사실 컨테이너 기술은 최신 기술이 아닙니다. 이전부터 존재해왔었지만 불과 몇 년 전부터 도커와 함께 핫해진 개념입니다. 이와 함께 쿠버네티스도 시간이 지나면서 유용하게 되었는데 어떻게 변해왔길래 유용해졌는지 알아봅시다.

 

[그림 1] 시간에 따른 배포 방식의 변화.

 

전통적인 배포 방식은 애플리케이션을 물리 서버에서 실행되는 방식입니다. 한 물리 서버에서 여러 애플리케이션의 리소스 한계를 정의할 방법이 없었으므로 리소스 할당의 문제가 발생했습니다. 예를 들어 물리 서버 하나에서 여러 애플리케이션을 실행하면 리소스 전부를 차지하는 애플리케이션 인스턴스가 있을 수 있고, 결과적으로는 다른 애플리케이션의 성능이 저하될 수 있었습니다. 이에 대한 해결책은 서로 다른 여러 물리 서버에서 각 애플리케이션을 실행하는 방법이 있었습니다. 그러나 이는 리소스가 충분히 활용되지 않는다는 점에서 확장 가능하지 않았으므로, 물리 서버를 많이 유지하기 위해서 많은 비용이 들어가는 단점이 있습니다.

 

그 해결책으로 가상화가 도입되었습니다. 이는 단일 물리 서버의 CPU에서 여러 가상 시스템 (VM)을 실행하는 방법입니다. 가상화를 사용하면 VM 간에 애플리케이션을 격리하고 애플리케이션의 정보를 다른 애플리케이션에서 자유롭게 액세스 할 수 없으므로, 일정 수준의 보안성을 제공할 수도 있습니다. 가상화를 사용하면 물리 서버에서 리소스를 보다 효율적으로 활용할 수 있으며, 쉽게 애플리케이션을 추가하거나 업데이트할 수 있고 하드웨어 비용을 절감할 수 있어 더 나은 확장성을 제공합니다. 가상화를 통해 일련의 물리적 리소스를 폐기 가능한(disposable) 가상 머신으로 구성된 클러스터로 만들 수도 있습니다. 각 VM은 가상화된 하드웨어 상에서 자체 운영체제를 포함한 하나의 완전한 머신으로 존재합니다.

 

이후 좀 더 나아가 가상화에서 격리성을 완화시킨 컨테이너 배포 방식이 도입되었습니다. 컨테이너 방식은 VM과 유사하지만 애플리케이션 간에 운영체제(OS)를 공유합니다. 그러므로 컨테이너는 가볍다고 여겨집니다. VM과 마찬가지로 컨테이너에는 자체 파일 시스템, CPU, 메모리, 프로세스 공간 등이 있습니다. 기본 인프라와의 종속성을 끊었기 때문에, 클라우드나 OS 배포본에 모두 이식할 수 있다는 장점이 있습니다.

 

일반적으로 컨테이너 방식의 배포는 다음과 같은 추가적인 장점을 제공해 인기가 있다고 알려져 있습니다.

  • 빠른 생성과 배포: VM 이미지를 사용하는 것에 비해 컨테이너 이미지 생성이 보다 쉽고 효율적입니다.
  • CI&CD: 안정적이고 주기적으로 컨테이너 이미지를 빌드해서 배포할 수 있고 빠르고 쉽게 롤백할 수 있습니다.
  • 개발과 운영의 관심사 분리: 배포 시점이 아닌 빌드/릴리스 시점에 애플리케이션 컨테이너 이미지를 만들기 때문에, 애플리케이션이 인프라에서 분리됩니다.
  • 가시성은 OS 수준의 정보와 메트릭에 머무르지 않고, 애플리케이션의 헬스체크 및 그 밖의 시그널을 볼 수 있습니다.
  • 개발, 테스팅 및 운영 환경에 걸친 일관성: 랩탑에서도 클라우드에서와 동일하게 구동됩니다.
  • 클라우드 및 OS 배포판 간 이식성: Ubuntu, RHEL, CoreOS, 온-프레미스, 주요 퍼블릭 클라우드와 어디에서든 구동 가능합니다.
  • 애플리케이션 중심 관리: 추상화 수준이 가상 하드웨어 상에서 OS를 실행하는 수준에서 논리적인 리소스를 사용하는 OS 상에서 애플리케이션을 실행하는 수준으로 높아집니다.
  • 느슨하게 결합, 분산, 유연하고 자유로운 마이크로 서비스: 애플리케이션은 단일 목적의 머신에서 모놀리식 스택으로 구동되지 않고 보다 작고 독립적인 단위로 쪼개져서 동적으로 배포되고 관리됩니다.
  • 리소스 격리: 애플리케이션 성능을 예측할 수 있습니다.
  • 자원 사용량: 리소스 사용의 효율이 높습니다.

 

 

 

3. 쿠버네티스는 왜 필요할까?

 

컨테이너는 애플리케이션을 포장하고 실행하는 좋은 방법입니다. 프로덕션 환경에서는 애플리케이션을 실행하는 컨테이너를 관리하고 가동 중지 시간이 없는지 확인해야 합니다. 예를 들어 컨테이너가 다운되면 다른 컨테이너를 다시 시작해야 합니다. 이 문제를 시스템에 의해 처리한다면 더 쉽지 않을까요?
이게 쿠버네티스가 필요한 이유입니다. 쿠버네티스는 분산 시스템을 탄력적으로 실행하기 위한 프레임 워크를 제공합니다. 애플리케이션의 확장과 장애 조치를 처리하고, 배포 패턴 등을 제공해줍니다.

 

고 가용성 외에도 다른 포인트도 있습니다. 실제 프로덕션 애플리케이션은 여러 컨테이너에 걸쳐 있으며 이러한 컨테이너는 여러 서버 호스트에 배포되어야 합니다. 컨테이너를 위한 보안은 멀티레이어 구조이며 복잡할 수 있습니다. 바로 여기에 다시 쿠버네티스가 사용됩니다.

쿠버네티스는 이러한 워크로드를 위해 규모에 맞는 컨테이너를 배포하는 데 필요한 오케스트레이션 및 관리 기능을 제공합니다. 쿠버네티스 오케스트레이션을 사용하면 여러 컨테이너에 걸쳐 애플리케이션 서비스를 구축하고 클러스터 전체에서 컨테이너의 일정을 계획하고 이러한 컨테이너를 확장하여 컨테이너의 상태를 지속적으로 관리할 수 있습니다. 쿠버네티스를 활용하면 IT 보안을 한층 강화할 수 있습니다.

[그림 2] 쿠버네티스를 통한 오케스트레이션.

 

물론 이는 실제 환경에서 컨테이너를 사용하는 방식에 따라 달라집니다. Linux 컨테이너를 사용하는 가장 기본적인 방식은 컨테이너를 효율적이고 빠른 가상 머신으로 다루는 것입니다. 이를 프로덕션 환경과 여러 애플리케이션으로 확장하고 나면 개별 서비스를 제공하기 위해 같은 위치에 배치된 여러 개의 컨테이너를 함께 사용해야 한다는 것을 분명히 알 수 있습니다. 따라서 환경에서 컨테이너 수가 크게 증가하며 컨테이너가 누적됨에 따라 복잡성도 증가합니다.

 

쿠버네티스는 컨테이너를 "포드(pod)"로 분류하여 컨테이너 급증과 관련된 여러 가지 문제를 해결합니다. 포드는 그룹화된 컨테이너에 추상화 계층을 추가하므로 사용자가 워크로드를 예약하고 네트워킹 및 저장소와 같은 필수 서비스를 컨테이너에 제공할 수 있습니다. 쿠버네티스의 또 다른 부분을 사용해 이러한 포드 전체에서 부하를 분산하고 적합한 수의 컨테이너를 실행하여 워크로드를 지원할 수 있습니다.

 

 

 

4. 쿠버네티스는 무엇을 할 수 있나요?

 

사용자의 환경에서 쿠버네티스를 사용할 경우 얻을 수 있는 주요 이점은 쿠버네티스를 통해 물리 또는 가상 머신의 클러스트에서 컨테이너를 예약하고 실행할 수 있는 플랫폼이 확보된다는 것입니다. 더 넓게 보면, 프로덕션 환경에 컨테이너 기반 인프라를 완전히 구현해서 사용할 수 있습니다. 또한 쿠버네티스는 운영 작업 자동화와 관련이 있으므로 다른 애플리케이션 플랫폼 또는 관리 시스템에서 가능한 작업의 상당수를 컨테이너를 사용해 수행할 수 있습니다.

 

쿠버네티스를 사용하여 수행할 수 있는 작업은 다음과 같습니다.

 

  • 여러 호스트에 걸쳐 컨테이너를 오케스트레이션 합니다.
  • 하드웨어를 최대한 활용하여 엔터프라이즈 애플리케이션을 실행하는 데 필요한 리소스를 극대화합니다.
  • 애플리케이션 배포 및 업데이트를 제어하고 자동화합니다.
  • 스토리지를 장착 및 추가해 스테이트풀(stateful) 애플리케이션을 실행합니다.
  • 컨테이너화 된 애플리케이션과 해당 리소스를 즉시 확장합니다.
  • 선언적으로(Declaratively) 서비스를 관리함으로써, 배포한 애플리케이션이 항상 배포 목적대로 실행되도록 합니다.
  • 자동 배치, 자동 재시작, 자동 복제, 자동 확장을 사용해 애플리케이션 상태 확인과 셀프 복구를 수행합니다.
  • DNS 이름을 사용하거나 자체 IP주소를 사용해 컨테이너를 노출시킬 수 있습니다.
  • 컨테이너에 대한 트래픽이 많으면 네트워크 트래픽을 로드밸런싱 해 안정적인 배포가 이루어지게 합니다.
  • 자동화된 롤아웃과 롤백을 지원합니다.
  • 실패한 컨테이너를 다시 시작하고 교체하며 응답하지 않는 컨테이너를 죽이고 서비스를 새로 준비합니다.

 

이 외에도 다양한 기능을 제공해 줍니다. 

 

 

 

 

 

반응형

0. 앞선글

 

React를 사용해 로그인 기능을 구현하던 도중 예제에서 JWT를 통해 인증 기능을 구현한 글을 봤습니다. JWT가 좋다 많이 쓴다 하지만 막연히 인증을 위해 쓰면 된다고만 알고 있었을 뿐 대체 왜 쓰는지 알아보지 않았습니다. 이 기회에 대체 요즘의 웹에서는 왜 JWT를 사용하는가에 대해 알아보고자 합니다.

 

이 글은 Mariano Calandra님께서 hackernoon에 작성한 글 중 하나를 번역 한 글이며 원문은 다음과 같습니다.

Why do we Need the JSON Web Token (JWT) in the Modern Web Era?

 

Why do we Need the JSON Web Token (JWT) in the Modern Web Era? | Hacker Noon

Hold on tight: the HTTP protocol is terribly flawed(*) and when it comes to user authentication this problem screams loudly.

hackernoon.com

 

 

 

1. 왜 모던 웹 시대에 JWT(JSON Web Token)이 필요할까?

 

 

HTTP 프로토콜은 엄청난 결함*이 있으며 사용자 인증에 관해서는 이 문제가 크게 작용합니다.

 

오랜 기간 동안 우리는 개발자로서 HTTP 프로토콜로 싸워왔습니다. 때로는 좋은 결과를 얻었고 그렇지 않을 때도 있지만 우리는 행복하다고 생각했습니다. 불행히도 웹은 빠르게 발전되었고 이러한 솔루션 중 많은 부분이 너무 빨리 구식이 되었습니다.

 

망설이는 자는 길을 잃습니다...

 

시간이 지나고 한 무리의 사람들이 "문제"와의 싸움을 그만두고 이제 그것을 받아들여야 할 때라는 것을 깨달았습니다. 그로 인해 탄생한 결과를 JSON Web Token(JWT)이라고 하며 여기에서 이야기를 들려 드리도록 하겠습니다.

 

 

* HTTP의 Stateless 특성은 분명히 결함은 아닙니다. 단지 도발이었습니다 :)

 

 

 

2. 옛날 옛적에...

 

REST API(예를 들어 GET / orders)가 있고 권한이 있는 사용자에게만 액세스를 제한하려고 한다고 가정해 봅시다.

 

가장 단순한 접근 방식은 API가 사용자 이름과 비밀번호를 요청하는 것입니다. 그리고 해당 자격 증명이 실제로 존재하는지를 데이터베이스에서 검색해 인증을 확인합니다. 마지막으로 인증된 사용자에게 해당 요청을 수행할 권한이 있는지 확인합니다. 두 검사 모두 통과하면 실제 API가 실행됩니다. 논리적인 것 같아 보입니다.

 

 

 

3. State에 대한 문제.

 

HTTP 프로토콜은 Stateless로 동작합니다. 새 요청(예를 들어 GET / order / 42)은 이전 요청에 대해 아무것도 알지 못하기 때문에 새 요청마다 다시 인증해야 합니다 (그림 1).

 

[그림 1 ] HTTP 프로토콜의 Stateless 특성으로 인해 새로운 모든 API 요청에는 완전한 인증이 필요합니다.

 

이를 처리하는 전통적인 방법은 SSS(Server Side Sessions)를 사용하는 것입니다. 이 시나리오를 통해 보자면 먼저 사용자 이름과 비밀번호를 확인합니다. 이들이 인증된 경우, 서버는 세션 ID를 메모리에 저장하고 그 ID를 클라이언트에게 반환합니다. 그 후부터는 클라이언트를 식별하기 위해 세션 ID를 서버로 보내면 됩니다 (그림 2).

 

[그림 2] SSS를 사용하면 인증 데이터베이스에 대한 인증요청 수가 줄어듭니다.

 

이 솔루션은 문제를 해결하지만 다른 문제를 만듭니다. 아마 더 큰 문제를 말입니다.

 

 

 

4. 확장에 대한 문제.

 

IT 세상에서의 시간은 빨리 흐릅니다. 어제 일반적으로 사용되었던 솔루션이 구식이 되었을 수도 있습니다. SSS도 그중 하나입니다.

 

API 시대에는 우리의 엔드 포인트가 많은 요청에 직면할 수 있으므로 인프라의 확장이 필요합니다. 스케일링에는 두 가지 유형이 있습니다.

  • 수직 확장: 서버에 더 많은 리소스를 추가하는 것을 의미합니다. 이는 한계점이 낮은 비싼 솔루션입니다. (예를 들면 서버에 최대 리소스를 할당하는 것)
  • 수평 확장: 로드 밸런서 뒤에 새 서버를 추가하는 것을 의미합니다. 이는 더 간단하고 비용면에서 효과적입니다.

두 번째 접근이 유리하단 것이 명확해 보입니다. 어떤 일이 일어날지 한번 살펴보겠습니다.

 

초기 시나리오에서는 로드 밸런서 뒤에 서버가 하나만 있습니다. 클라이언트가 세션 ID xyz를 사용하여 요청을 수행하면 해당 레코드는 서버의 메모리에서 찾을 수 있다는 것이 보장됩니다. (그림 3).

 

[그림 3] 로드 밸런서 뒤에있는 단일 서버. 요청의 세션 ID는 서버 메모리에서 찾을 수 있습니다.

 

여태까지는 그런대로 잘 되어가고 있습니다. 이제 위의 인프라를 확장해야 한다고 상상해 봅시다. 새로운 서버 (예를 들어 서버 2:2)가 로드 밸런서 뒤에 추가되며 추가된 새로운 서버는 xyz 클라이언트가 발행 한 요청을 다음과 같이 처리합니다(그림 4).

 

[그림 4] 새 서버가 LB 뒤에 있으며 이전 세션에 대해 아무것도 모르므로 사용자가 인식되지 않습니다.

 

인증되지 않습니다! 새로운 서버의 메모리에는 xyz 세션이 없으므로 인증 프로세스가 실패합니다. 이 문제를 해결하기 위해 사용할 수 있는 세 가지 해결 방법이 있습니다.

  • 서버 간 세션 동기화: 구현에 까다롭고 오류가 발생하기 쉽습니다.
  • 외부 인메모리 DB 사용: 좋은 솔루션이지만 다른 인프라 구성요소가 추가됩니다.
  • 다른 세 번째 방법: HTTP의 Stateless 특성을 수용하고 더 나은 솔루션을 찾습니다.

 

 

 

5. 더 나은 해결책.

 

JWT(JSON Web Token)는 공개 표준(RFC 7519)으로, 인증 및 권한 부여 여부와 같은 두 당사자(발급자 및 대상) 간에 정보를 전송하는 방법을 정의합니다. 발행된 각 토큰은 디지털 서명되어 안전한 통신이 이루어 지므로 사용자는 토큰이 진짜인지 또는 위조되었는지를 확인할 수 있습니다.


각 토큰은 API에 대해 주어진 요청을 허용하거나 거부하는데 필요한 모든 정보가 자체 포함되어 있습니다. 토큰을 확인하는 방법과 권한 인증이 발생하는 방법을 이해하려면 한 발짝 물러서서 JWT를 살펴봐야 합니다.

 

 

6. JWT 해부.

 

JWT 토큰은 본질적으로 인코딩 된 긴 텍스트 문자열입니다. 이 문자열은 점(.) 기호로 구분된 세 개의 작은 부분으로 구성됩니다. 이 부분들은 다음과 같습니다.

  • 헤더(header)
  • 페이로드(payload) 또는 바디(body)
  • 서명(signature)

따라서 토큰은 다음과 같습니다.

 

header.payload.signature

 

6.1. 헤더(header)

 

헤더 섹션에는 토큰 자체에 대한 정보가 포함되어 있습니다.

 

{
  "kid": "ywdoAL4WL...rV4InvRo=",
  "alg": "RS256"
}

 

이 JSON은 토큰(alg)을 서명하는 데 사용된 알고리즘과 유효성을 검사하는 데 사용해야 하는 키(kid)가 무엇인지 설명합니다. 이 JSON은 최종적으로 Base64 URL로 인코딩 됩니다. 

 

eyJraWQiOiJ -TRUNCATED- JTMjU2In0

 

6.2. 페이로드(payload) 또는 바디(body)

 

페이로드는 JWT의 가장 중요한 부분입니다. 클라이언트에 대한 정보(JWT의 클레임들)가 포함되어 있습니다.

 

{
  [...]
  "iss": "https://cognito-idp.eu-west-1.amazonaws.com/XXX",
  "name": "Mariano Calandra",
  "admin": false
}

 

iss 속성은 등록된 클레임이며 토큰을 발급 한 자격 증명 공급자 (이 경우 Amazon Cognito)를 나타냅니다. 필요에 따라 클레임을 추가할 수 있습니다(예를 들어 admin 클레임). 그런 다음 페이로드는 Base64 URL로 인코딩 됩니다.

 

eyJzdWIiOiJkZGU5N2Y0ZC0wNmQyLTQwZjEtYWJkNi0xZWRhODM1YzExM2UiLCJhdWQiOiI3c2Jzamh -TRUNCATED- hbnRfaWQiOiJ4cGVwcGVycy5jb20iLCJleHAiOjE1N jY4MzQwMDgsImlhdCI6MTU2NjgzMDQwOH0

 

6.3. 서명(signature)

 

토큰의 세 번째 부분은 다음 단계에 따라 계산된 해시입니다.

  • 인코딩 된 헤더와 인코딩 된 페이로드를 점(.)으로 합칩니다.
  • 헤더의 alg 속성(이 경우는 RS256)과 개인키에 지정된 암호화 알고리즘을 사용해 결괏값을 해시합니다.
  • 해시된 결과를 Base64 URL로 인코딩합니다.

의사 코드(pseudo-code)를 통해 보도록 합시다.

 

data = base64UrlEncode(header) + "." + base64UrlEncode(payload);
hash = RS256(data, private_key);
signature = base64UrlEncode(hash);

 

이 내용을 계산하면 다음과 같은 서명이 나옵니다.

 

POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85 -TRUNCATED- FfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA

 

6.4 모든 걸 합쳐서.

 

인코딩 된 헤더, 인코딩 된 페이로드, 인코딩 된 서명이 있으면 조각을 간단히 점에 의한 병합으로 모든 것을 결합할 수 있습니다.

 

eyJzdWIiOiJkZGU5N2Y0ZC0wNmQyLTQwZjEtYWJkNi0xZWRhODM1YzExM2UiLCJhdWQiOiI3c2Jzamh -TRUNCATED- hbnRfaWQiOiJ4cGVwcGVycy5jb20iLCJleHAiOjE1N jY4MzQwMDgsImlhdCI6MTU2NjgzMDQwOH0.eyJzdWIiOiJkZGU5N2Y0ZC0wNmQyLTQwZjEtYWJkNi0xZWRhODM1YzExM2UiLCJhdWQiOiI3c2Jzamh -TRUNCATED- hbnRfaWQiOiJ4cGVwcGVycy5jb20iLCJleHAiOjE1N jY4MzQwMDgsImlhdCI6MTU2NjgzMDQwOH0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85 -TRUNCATED- FfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA

 

참고: 위의 토큰이 암호화된 것처럼 보이지만 그렇지 않습니다! RS256과 달리 Base64 URL은 암호화 알고리즘이 아니므로 페이로드를 염두에 두십시오!

 

6.5. JWT 검증

 

토큰은 자체 포함되어 있으므로 검증에 필요한 모든 정보를 갖고 있습니다. 예를 들어, RS256(헤더의 alg 속성)과 개인 키를 사용하여 토큰이 서명되었음을 알 수 있습니다. 이제 검증을 수행하기 위해 올바른 공개 키를 얻는 방법을 알아야 합니다. 네 공개키 말입니다.

 

참고 : 비대칭 암호화에서 공개 키는 메시지를 암호화하는 데 사용되고 개인 키는 메시지를 해독하는 데 사용됩니다. 서명 알고리즘에서 이 프로세스는 완전히 전환되어 있습니다! 여기서 메시지(위의 의사 코드의 데이터)는 개인 키를 사용하여 서명되며 공개 키는 서명이 유효한지 확인하는 데 사용됩니다.

 

본문의 iss 속성은 발급자의 엔드포인트(이 경우 Amazon Cognito이지만 다른 공급자와 크게 다르지 않아야 합니다.)를 나타내며 해당 URI를 복사하여 문자열 /.well-known/jwks.json에 추가합니다. 아마 다음과 같이 보일 것입니다.

 

https://cognito-idp.eu-west-1.amazonaws.com/XXX/.well-known/jwks.json

 

위의 URL 다음에 JSON이 위치합니다.

 

{
  "keys": [
    {
      "alg": "RS256",
      "e": "AQAB",
      "kid": "ywdoAL4WL...rV4InvRo=",
      "kty": "RSA",
      "n": "m7uImGR -TRUNCATED AhaabmiCq5WMQ",
      "use": "sig"
    },
    {...}
  ]
}

 

키 배열에서 동일한 토큰 헤더의 kid가 있는지 검색하세요. 속성 e와 n은 공개 키를 계산하는 공개된 설명자와 와 절댓값입니다.


우리가 그것을 얻으면 서명을 확인할 수 있습니다. 만약 유효하다면 토큰에 포함된 정보를 신뢰할 수 있습니다.

참고 : 공개 키 계산 또는 서명 확인 프로세스는 쉽지 않으며이 게시물의 범위를 벗어납니다.

 

 

 

7. 실제 시나리오.

 

처음 액세스 할 때 클라이언트는 권한 부여 서버(여기서는 Amazon Cognito, Microsoft, Salesforce 또는 유사한 다른 공급자)에 연결하여 사용자 이름과 암호를 보내야 합니다. 자격 증명이 유효하면 JWT 토큰이 클라이언트에 반환되고 이를 사용하여 API를 요청합니다(이 예제에서는 Amazon API Gateway 엔드 포인트).

 

[그림 5] 실제 시나리오의 전체 흐름.


위 시나리오(그림 5)에서 API는 토큰 유효성 검사의 유일한 책임자이며 서명이 위조된 경우 요청을 거부할 수 있습니다.

 

더 나아 가 클라이언트가 보호된 API를 호출하여 주문을 삭제하려고 한다고 가정하고(예를 들어 DELETE / order / 42) 이 조치는 관리자만 수행해야 한다고 합시다.
JWT를 사용하면 페이로드 본문에 사용자 지정 클레임을 추가하는 것만큼만 이 작업을 수행하기가 어려울 뿐입니다(예를 들어 위 페이로드의 클레임인 admin: true). API가 호출되면 먼저 서명 인증을 확인한 후 admin 클레임이 참인지 확인합니다.

 

 

 

8. 정리.

 

지금은 여기까지입니다. 우리는 JWT에 대해 많은 것을 보았지만 여전히 다른 것이 빠져 있습니다.

  • JWT를 얻도록 Amazpn Cognito를 어떻게 구성하나요?
  • 사용자 지정 클레임을 추가할 수 있도록 Amazon Cognito를 어떻게 구성하나요?
  • 인증을 위한 JWT를 프로그래밍적으로 검증하는 방법은 무엇입니까?

 

걱정하지 마세요. 나중에 이 질문에 대답할 기회가 있을 겁니다. 지금은 몇 가지 핵심사항에 대해 요약을 해보도록 하겠습니다.

  • HTTP 프로토콜은 Stateless이므로 새 요청은 이전 요청에 대해 아무것도 알지 못합니다.
  • SSS는 HTTP의 Stateless에 대한 솔루션 이었지만 장기적으로 보았을 때 확정성에 위협이 되었습니다.
  • JWT는 API에 대한 요청을 허가하나 거부하는데 필요한 모든 정보가 자체 포함되어 있습니다.
  • JWT는 설계상 Stateless이므로 HTTP의 Stateless와 싸우지 않아도 됩니다.
  • JWT는 인코딩 되어있지 암호화되어있는 것이 아닙니다.

 

 

9. 맺음글.

 

굉장히 유용한 글이었습니다. JWT가 왜 나타나게 되었는지부터 설명되어 있어 자연스레 JWT의 필요성을 알게 해 주는 글입니다.

 

단순히 글로만 설명하는 것이 아닌 그림으로 워크플로우를 알려주고 있어 이해하기 편했습니다. 또한 JWT의 기본적인 구조와 검증 원리 역시 설명하고 있어 JWT를 이해하는데 많은 도움이 되었습니다. 

 

여러분들도 읽고 많은 도움이 되셨으면 좋겠습니다.

 

 

 

 

 

반응형

 

SourceTree에서 History 항목의 한글 깨짐 현상을 수정하는 방법에 대해 알아봅니다.

 

 

 

1. SourceTree 한글 설정.

 

SourceTree의 일반 메뉴에 대해 한글 설정은 옵션에서 할 수 있습니다.

 

도구 -> 옵션으로 이동 후 Repo Settings 그룹 박스 내에 있는 언어와 기본 텍스트 인코딩을 변경해주면 됩니다.

 

 

위와 같이 설정 후 SourceTree를 재시작해 주시면 됩니다.

 

 

 

2. History의 커밋 메시지 한글화.

 

위와 같이 설정해도 History내부의 한글은 여전히 깨져있는 상태입니다.

 

 

이 현상은 각 워크 스페이스마다의 설정을 변경해 주어야 합니다.

 

워크스페이스 우측 상단에 보이는 설정을 클릭합니다.

 

 

이후 저장소 설정 화면이 나타날텐데 "설정 파일 편집..."을 선택한 뒤 텍스트 편집기로 config파일을 엽니다.

 

기본 config 파일은 아마 다음과 같을겁니다.

 

[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	ignorecase = true
[remote "origin"]
	url = https://github.com/....git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master

 

이 설정을 다음과 같이 수정해 줍시다.

 

[i18n]
    logOutputEncoding = euc-kr
    commitEncoding = UTF-8
[core]
	repositoryformatversion = 0
	filemode = false
	bare = false
	logallrefupdates = true
	ignorecase = true
[remote "origin"]
	url = https://github.com/....git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master

 

이후 config파일을 저장 한 뒤 저장소 설정의 확인 버튼을 클릭합니다.

 

 

정상적으로 한글이 노출되는 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

0. 앞선 글.

 

많은 이들이 모던 웹 개발하면 PWA와 SPA를 손꼽습니다. 실제로 제 글에서도 그렇게 소개를 했고요. 그렇게 알고 넘어갔는데 웹을 돌아다니던 중 다음과 같은 글을 발견했습니다.

 

https://itnext.io/the-state-of-progressive-web-apps-adoption-by-developers-8b119783d3b9

 

The State Of Progressive Web Apps Adoption By Developers

Are PWA preached or adopted by developers? Do they use these despite the lack of support on iOS? Here are some hints I gathered with polls…

itnext.io

 

눈이 갈 수밖에 없는 글이었습니다. PWA가 좋다곤 하는데 과연 얼마나 많은 개발자들이 PWA를 구현해서 사용하고 있을까요? 실제로 궁금하여 글을 읽고 내용을 공유하기 위해 한글 번역글을 남깁니다.

 

 

 

1. 프로그레시브 웹 앱의 채택 상태.

 

PWA가 개발자에 의해 전파되거나 선택되었나요? 그들은 iOS에서의 지원이 부족함에도 불구하고 PWA를 하나요? 지난 며칠간의 조사에서 수집한 몇 가지 힌트가 여기 있습니다.

 

Photo by YTCount on Unsplash

 

이번 주 "Apple vs Hey"이야기에 이어서 이러한 이슈에 대한 해결책으로 볼 수 있는 PWA가 대부분 개발자들에게 전파되었거나 실제로 이미 채택되었는지가 궁금했습니다. 

 

이런 나의 질문에 답하기 위해 트위터에서 설문조사를 실시하였고 이 새 블로그 게시물에 여러분과 공유하고 싶은 몇 가지 흥미로운 사실을 알게 되었습니다.

 

 

 

2. 한계점.

 

전 통계의 전문가도 아니고 조사 결과를 해석하는 전문가도 아닙니다. 또한 이 조사는 트위터에서 이루어졌으며 SNS를 통해 답변을 받았으므로 아마 충분하지 않을 수도 있습니다.

 

그러므로 이 글에서 읽은 내용을 당연하다고 생각하지는 말아주세요. 이글의 수치와 의견들을 더도 말고 덜도 말고 그저 힌트로만 봐주시기 바랍니다.

 

또한 저는 PWA의 열렬한 애호가입니다. 공정성을 유지하기 위해 노력할 수 있지만 동등한 경우에는 아마 PWA에 대해 부정적이기보단 긍정적일 겁니다.

 

 

 

3. 47%의 개발자는 PWA를 사용하지 않습니다.

 

나는 DEV.to의 PWA를 거의 매일 이용하고 있으며 트위터도 그러려고 하고 있습니다. 그러나 난 내가 내 핸드폰에서 매주 다른 것을 사용하고 있다고 생각하지 않았습니다. 그것이 처음 이 질문에 관심을 갖고 선택한 이유입니다.

 

놀랍게도 개발자의 47% 는 홈 화면에 설치된 PWA를 사용하고 있지 않습니다.

 

많은 개발자가 브라우저와 함께 PWA를 사용하고 있기 때문에 실제로는 숫자가 낮아야 한다고 생각하는 경향이 있지만 이 숫자는 꽤나 중요합니다.

 

물론 다음 장에서 볼 수 있듯이 이러한 낮은 비율의 주된 이유는 iOS에서 PWA에 대한 적절한 지원이 없기 때문입니다.

 

그럼에도 불구하고 이건 여전히 두 명의 개발자 중 거의 한 명이 PWA를 사용하지 않는다는 것을 의미하며 이는 개인적으로 모바일 기기에서 기술의 미래에 대해 약간의 걱정을 갖게 만듭니다. 응용프로그램을 프로그래밍하는 개발자가 응용프로그램을 사용하지 않거나 사용할 수 없다면 많은 사람들이 어떻게 받아들일까요?

 

반면에 긍정적인 점은 유리잔의 반은 비어있다기 보단 차 있다는 것입니다. 개발자 중 3명 중 1명꼴인 29% 는 매주 두 개 이상의 PWA를 사용합니다. 

 

https://bit.ly/3gbFUOI

 

 

또 흥미로운 점은 일부 개발자들은 PWA를 홈 화면에 고정해 두지 않을 채 데스크톱에서 여러 PWA를 사용하고 있다는 것입니다.

 

이것은 다음과 같은 생각으로 이어졌습니다: PWA의 미래가 실제로 모바일 폰이 첫 번째 장소가 아니라 데스크톱이었다면 어떨까요? 실제로 데스크톱이 모바일보다 먼저 인기를 얻으려면 어떻게 해야 할까요? 만약 이런 가정이 사실이면 구글의 크롬북은 적절했을까요? 아니면 구글은 크롬북을 믿고 PWA를 추진했을까요?

 

아마 너무 과한 해석과 과장이지만 난 정말로 이런 식으로 발전할지 안 할지를 알고 싶어 미래를 기다리고 있습니다. 

 

 

 

4. PWA를 사용하지 않는 사람들의 63%는 iOS를 사용합니다.

 

위에서 말했듯이 iOS의 부분적인 지원은 개발자가 PWA를 사용하지 않는 주된 이유입니다. PWA를 사용하지 않는 이들 중 63 %는 iPhone을 가지고 있습니다. 이는 모든 개발자의 30 %가 단순히 Apple에서 만든 전화를 가지고 있기 때문에 PWA를 사용하지 않는 것을 의미합니다.

 

https://bit.ly/31ufIuM

 

부분적인 지원보다 제가 받은 의견에 따르면 개발자들은 애플이 자동으로 "홈 화면에 추가하기"를 보여주지 않기 때문에 iOS에서 PWA 사용을 포기하는 것 같습니다. 애플은 Apple은 다른 브라우저를 통해 이러한 기능을 애플 기기에 구현하지 못하게 했습니다. 

 

나는 그것을 시도했고 맞았습니다. UX는 약간 실망스럽지만 iOS 홈 화면에 PWA를 설치할 수 있습니다. 관심이 있으시면 다음 트윗대로 진행하세요.

 

https://bit.ly/2VvkT9L

 

홈 화면에 웹 사이트를 추가할 수 있지만 적절한 PWA인 웹 사이트만 독립 실행형 앱으로 작동합니다. 정확한 피드백에 대해 Julio에게 감사드립니다.

 

나는 안드로이드에서 Firefox 모바일의 "홈 화면에 추가"UX를 시도해 보았습니다. 믿거나 말거나 그것이 실제로 최고라고 생각합니다. 크롬은 훌륭하지만 파이어 폭스는 나의 손을 이끌리게 했으며 "이봐 David 내가 여기 PWA를 네 폰에 올바르게 추가하는 방법을 보여줄게"라고 말하는듯한 느낌이 들었습니다.

 

파이어폭스의 디자이너가 이 글을 읽게 될지 모르겠지만 축하합니다. 대단한 일입니다!

 

https://bit.ly/2Vzl4Bc

 

5. 개발자의 8%가 Google Play의 앱을 좋아합니다.

 

모든 개발자의 30%가 iPhone으로 인해 PWA를 사용하지 않는다 하더라도 PWA 친화적인 안드로이드 폰을 소유하고 있는 17%의 개발자들 역시 PWA를 사용하지 않습니다. 이것이 마지막 설문조사를 실시한 이유입니다. 왜일까요?

 

가능한 해결책에 대해 생각하는데 시간이 걸렸으며 내 제안으로 설문 조사를 너무 편향적으로 만들지 않을까 걱정했습니다. 아마 오픈된 질문을 하는 게 더 나을 겁니다.

 

안드로이드에서 PWA를 사용하지 않는 전체의 7%에서 대부분의 개발자인 44%는 스토어나 Google Play의 UX나 디자인이 좋다고 느끼기 때문에 PWA를 사용하지 않습니다. 

 

솔직히 말해서 나는 이러한 사실을 해석하는 방법을 모르겠습니다. 내게도 같은 이유로 나쁜 기본 앱만큼 못생기고 성능이 좋지 않은 웹 애플리케이션이 있을 수도 있습니다. 나는 그게 개념과 실행에 관한 것이라고 생각합니다. 기술에 관계없이 나쁘게 구현되거나 디자인된 경우 결국에는 놀랍지 않을 것입니다.

 

https://bit.ly/2ZqJDRI

 

주목할만한 점: PWA가 저가형 장치에 가장 적합하다고 언급한 피드백에 따라 KaiOS가 PWA를 지원하는지 궁금합니다. 맞춰볼까요? KaiOS은 PWA를 지원할 뿐만 아니라 이를 스토어에 게시할 수도 있습니다.

 

 

 

6. 결론

 

PWA의 미래는 데스크톱일까요? 아니면 KaiOS 매장과 Google Play에 모두 게시할 수 있는 모바일 기기가 미래가 될까요? 아니면 언젠가 EU가 애플을 PWA 친화적으로 만들까요? 그 누가 알겠습니까...

 

그러나 확실히 나는 몇 가지 흥미로운 힌트를 배웠으며 앞으로도 PWA를 믿습니다.

 

또한, 저는 2021년 6월 달력에 이런 조사를 다시 실시하기 위해 미리 알림을 추가했습니다. 내년에는 이 주제가 어떻게 진화했는지 살펴보겠습니다.

 

당신의 의견과 피드백을 기다리겠습니다. 

 

David

 

 

 

7. 맺음글

 

글이 오롯이 PWA의 채택률에 집중되어 있지 않다는 생각이 들긴 하지만 설문으로 얻어진 수치만을 보더라도 충분의 의미가 있다고 생각합니다.

 

모두가 모던 웹의 SPA와 PWA를 말하고 있지만 실제로 아직 많은 사람이 쓰고 있진 않단 걸 알 수 있는 글이었습니다. 

 

David의 말대로 내년에도 이와 같은 조사가 수행된다면 어떻게 바뀔지 궁금합니다. 

 

 

 

 

 

반응형

 

 

 

Cloud환경이 아닌 Self-managed로 Jira를 설치하는 방법에 대해 알아봅니다. 이 글에선 Ubuntu server 20.04 버전에 Jiral를 설치해 보도록 하겠습니다.

 

 

 

0. 사전 준비.

 

Ubuntu server 20.04 환경을 미리 준비합니다.

 

 

 

1. Jira Software 다운로드.

 

여기에서 Jira Software를 다운로드합니다. 서비스될 운영체제에 맞게 다운로드해주시면 되며 이 글에선 Ubuntu server에 설치할 것이므로 Linux 64 bit를 선택해 다운로드해 줍니다.

 

 

다운로드가 끝나면 파일을 서버로 옮겨주세요.

 

 

 

2. Jira 설치.

 

서버로 접속 해 설치 파일을 복사한 경로로 이동합니다.

 

 

다음 명령어로 복사한 설치 파일에 권한을 부여합니다.

 

$ sudo chmod a+x atlassian-jira-software-8.10.0-x64.bin

 

실행 권한이 부여되면 다음 명령어로 설치 파일을 실행시켜줍니다.

 

$ sudo ./atlassian-jira-software-8.10.0-x64.bin

 

 

Jira는 OpenJDK를 필요로 합니다. Y를 입력해 Jira를 설치하면서 OpenJDK가 같이 설치되도록 합니다.

 

 

설치 준비 과정을 잠시 기다린 후 위와 같은 메시지가 나오면 엔터를 눌러줍니다.

 

 

설치 옵션을 골라줍니다. 첫 설치 환경이고  잘 모르겠으면 기본 설정을 사용해 줍시다. 1을 입력하세요.

 

 

앞서 선택한 옵션에 따라 Jira가 설치될 때 참고할 설정입니다. 확인 후 엔터를 눌러주세요.

 

 

설치가 끝나면 Jira를 바로 실행할 건지 묻습니다. 엔터를 눌러 바로 실행시키도록 합니다.

 

 

설치가 완료되었습니다. 이제 나머지 구성을 진행해 봅시다.

 

 

 

3. MySQL 설치.

 

제가 가장 삽질한 부분입니다. 2020.06.25 현재 Ubuntu Server 20.04로 mysql-server를 설치하면 8.0.20 버전이 설치됩니다. 별도 커넥터도 설치하고 구성 파일도 변경해봤으나 이 버전은 Jira에서 자꾸 에러 메시지를 던집니다.

 

공식적인 답변을 확인한 결과 MySQL8의 지원은 Jira 이슈에 공식적으로 등록된 상태이며 6일 전인 2020.06.19에 개발이 시작되었다고 댓글이 달렸습니다.

 

https://jira.atlassian.com/browse/JRASERVER-67359

 

[JRASERVER-67359] Add support for Jira to use MySQL 8 - Create and track feature requests for Atlassian products.

Problem Definition Current versions of Jira support several database types/versions, however Jira does not currently support MySQL 8.0 per the Supported platforms - Atlassian Documentation. For MySQL, only 5.5 -> 5.7 versions are currently supported. It wo

jira.atlassian.com

 

결론적으로 이 글이 작성되는 시점에선 MySQL 8 버전은 지원하고 있지 않으며 적절한 드라이버를 사용해도 Jira는 오류를 반환합니다. 따라서 우리는 5.7 버전의 MySQL을 설치해 보도록 하겠습니다.

 

먼저 현재 리포지토리에 MySQL 5.7 버전이 있나 확인해 봅시다.

 

$ sudo apt-cache mysql-server

 

 

슬프게도 등록되어 있지 않습니다. 그 말은 우리가 별도로 다운로드 해와야 한다는 뜻이죠. 다음 작업을 차근차근 따라 해 봅시다. 먼저 deb 파일을 받습니다.

 

$ sudo wget https://dev.mysql.com/get/mysql-apt-config_0.8.12-1_all.deb 

 

 

받은 deb 파일을 확인하고 실행시킵니다.

 

$ sudo dpkg -i mysql-apt-config_0.8.12-1_all.deb

 

 

위의 명령어를 실행하면 MySQL에 대한 apt 설정을 할 수 있는 화면이 보입니다.

 

 

Ubuntu bionic을 선택합니다.

 

 

MySQL Server & Cluster를 선택합니다.

 

 

MySQL 5.7을 선택합니다.

 

 

구성이 MySQL 5.7로 바뀐 것을 확인한 뒤 OK를 선택합니다. 이후 업데이트 후 다시 MySQL을 검색해 봅니다.

 

$ sudo apt-get update

$ sudo apt-cache policy mysql-server

 

 

이제 5.7 버전이 보입니다. 이 버전을 설치합시다. mysql-client를 설치하지 않고 mysql-community-server를 설치하면 client를 먼저 설치하라는 메시지와 함께 server 설치가 실패합니다. client를 먼저 설치해줍시다.

 

$ sudo apt install -f mysql-client=5.7.30-1ubuntu18.04

$ sudo apt install -f mysql-community-server=5.7.30-1ubuntu18.04

 

server 설치를 수행하게 되면 다음과 같은 화면에 보입니다.

 

 

루트 유저의 비밀 번호를 입력합니다. 설치가 완료되면 다음 명령어로 제대로 5.7 버전이 설치되었는지 확인합니다.

 

$ sudo mysql --version

 

 

 

 

3. MySQL DB 생성.

 

이제 MySQL에 Jira가 사용할 DB와 유저를 생성해 봅시다.

 

다음 명령어로 MySQL에 접속합니다.

 

$ sudo mysql -u root -p

 

 

먼저 Jira를 위한 DB를 생성합니다. 다음 쿼리를 실행시켜 주세요.

 

> CREATE DATABASE jira CHARACTER SET utf8mb4;

 

 

이제 이 DB에 유저를 생성한 뒤 권한을 부여해 줍니다.

 

> CREATE USER 'jira'@'localhost' IDENTIFIED BY 'P@SSWORD';

> GRANT ALL PRIVILEGES ON jira.* TO 'jira'@'localhost';

> FLUSH PRIVILEGES;

 

 

이제 새로 생성한 유저로 다시 mysql에 접속해 확인해 보도록 합니다.

 

$ sudo mysql -u jira -p

> show databases;

 

 

로그인도 정상적으로 되고 DB도 생성된 것을 확인하였습니다.

 

 

 

4. JDBC 설치 및 MySQL 설정 변경.

 

다음으로 Jira가 MySQL에 붙을 수 있도록  MySQL JDBC 드라이버를 설치해야 합니다. 우리는 5.7.30 버전의 MySQL을 설치했으므로 이 버전에 맞는 커넥터를 다운로드하여야 합니다. 여기에서 커넥터를 다운로드하도록 합니다. 이 글을 따라 하셨다면 다음과 같이 설정 후 다운로드하시면 됩니다.

 

 

어떤 걸 다운해도 상관없습니다. 어차피 압축을 풀고 그 내부의 파일을 사용해야 합니다. 다운로드한 파일을 압축 해제 한 뒤 폴더 내에 있는 "mysql-connector-java-5.1.49-bin.jar" 파일만 서버로 옮겨줍니다.

 

이제 이 jar 파일을 jira 설치 경로 내의 lib 폴더로 이동시켜 줍니다.

 

$ ls

$ sudo cp mysql-connector-java-5.1.49-bin.jar /opt/atlassian/jira/lib/

$ ls /opt/atlassian/jira/lib

 

 

그 후 해당 파일에 읽기 권한을 부여합니다.

 

$ sudo chmod 755 /opt/atlassian/jira/lib/mysql-connector-java-5.1.49-bin.jar

 

추가한 커넥터를 사용할 수 있도록 jira를 재시작합니다. jira start/stop 명령어는 jira를 설치한 방식에 따라 달라질 수 있습니다. 아래의 명령어는 linux installer를 통해 설치했을 때 사용할 수 있는 명령어임을 참고해 주세요.

 

$ sudo /etc/init.d/jira stop

$ sudo /etc/init.d/jira start

 

다음은 MySQL의 설정 파일을 변경해 줄 차례입니다. MySQL의 기본 설정값을 사용하게 되면 Jira가 신나게 경고 메시지를 띄워대니 그냥 Jira 설정에 맞춰서 변경합니다.

 

MySQL의 설정 파일은 /etc/mysql/my.cnf입니다. 우선 이 설정 파일의 백업본을 생성해 줍니다.

 

$ cd /etc/mysql

$ sudo cp my.cnf my.cnf.bak

 

 

그 후 my.cnf에 다음 설정을 추가합니다.

 

[mysqld]
default-storage-engine=INNODB
character_set_server=utf8mb4
innodb_default_row_format=DYNAMIC
innodb_large_prefix=ON
innodb_file_format=Barracuda
innodb_log_file_size=2G

 

** 20.07.20 기존 아래의 코드에서 위의 코드로 수정되었습니다.

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
default-storage-engine=INNODB
max_allowed_packet=256M
innodb_log_file_size=2GB
transaction-isolation=READ-COMMITTED
binlog_format=row

 

$ sudo vi my.cnf

 

 

** 만약 my.cnf에 "sql_mode = NO_AUTO_VALUE_ON_ZERO"가 있다면 이 옵션은 삭제해 주세요.

 

설정 파일을 변경하였으면 MySQL을 재시작합니다.

 

$ sudo service mysql restart

 

 

 

5. Jira 초기 설정 및 라이선스 활성화.

 

Jira가 설치된 서버에 8080 포트로 접근하면 다음과 같은 Jira 초기 설정 화면을 확인할 수 있습니다.

 

 

** 우측 상단의 "언어"를 클릭하면 한국어 옵션을 사용할 수 있습니다.

 

"사용자를 위해 설정하십시오."옵션은 별다른 추가 설정 없이 Trial 라이선스로 사용해 볼 수 있도록 해주는 옵션입니다. 이 옵션은 별도의 DB를 사용하지 않고 내장 DB를 사용하며 Atlassian의 계정과 연동해 알아서 설정을 구성해주고 Trial 라이선스를 발급받을 수 있는 화면으로 이동해 바로 라이선스 발급 절차를 진행할 수 있도록 해줍니다.

 

사전 준비사항에 언급한 대로 이 글에선 MySQL을 사용해 구성해 보도록 하겠습니다. "직접 설정하겠습니다"를 선택해주세요.

 

아래와 같이 설정한 DB 및 계정 정보를 입력한 후 테스트 연결 버튼을 클릭해 봅시다.

 

 

 

버튼을 클릭했을 때 아래와 같은 메시지가 보이면 연결에 성공한 것입니다.

 

 

** 만약 드라이버를 못 찾는다면 커넥터를 다시 한번 확인해 주시고 권한을 수정했는지 확인해 주시기 바랍니다.

** 만약 MySQL 5.7+ 설정에 관한 오류가 생기면 my.cnf 파일을 다시 확인해 주시기 바랍니다.

** 위의 두 과정 모두 jira 및 mysql의 재시작이 필요합니다.

 

이제 Jira가 구성될 때까지 느긋하게 기다려주세요.

 

 

왜 다시 영어로 돌아간 건지는 모르겠지만 구성이 완료되면 위와 같은 화면이 보입니다.

 

Application Title은 말 그대로 Jira의 타이틀입니다. 원하는 값으로 설정해 주세요.

Mode는 유저 생성에 관련된 설정값입니다. Private로 선택하면 회원가입이 막히고 Admin이 직접 추가한 유저만 사용할 수 있습니다.

BaseURL은 jira가 서비스될 URL을 입력해 줍니다.

 

 

이제 라이선스 키를 입력해 줄 차례입니다. 여기로 이동해 jira를 구입한 계정으로 로그인 한 뒤 라이선스 정보를 확인합니다.

 

라이선스를 클릭하면 오른쪽에 "License Key View License"와 같은 항목이 있습니다. "View License"를 선택해주세요.

 

 

위에 적혀있는 서버 ID를 입력한 후 Save를 클릭합니다. 그러면 "View License"에 생성된 라이선스 코드가 보이게 됩니다. 이 값을 jira의 "Your License Key"에 붙여 넣어 주고 Next 버튼을 클릭합니다.

 

 

이제 관리자 계정을 생성해 줄 차례입니다. 원하는 대로 생성해 주세요.

 

 

다음은 이메일 알림 관련 설정입니다. 이 내용은 추후 별도의 포스팅으로 다루겠습니다. Later를 선택한 후 Finish 버튼을 클릭합니다.

 

잠시 기다리면 구성이 끝나고 Jira가 시작됩니다. 사용할 언어를 다시 한국어로 변경합니다. 마지막으로 아바타 설정을 마치면 이제 Jira를 사용할 수 있습니다!

 

 

이제 "샘플 프로젝트 생성"을 선택해서 미리 잘 짜여 있는 예시를 볼 수도 있고 "새 프로젝트 만들기"를 선택해서 바로 자신만의 프로젝트를 구성할 수도 있습니다.

 

 

 

 

 

 

반응형

 

이번에 JIRA 사용을 테스트해 볼 일이 생겨 MySQL을 설치할 일이 생겼습니다. 이 글에서 Ubuntu Server 20.04에 MySQL을 설치하는 방법에 대해 알아봅니다.

 

 

 

0. 사전 준비

 

미리 Ubuntu Server 20.04 환경을 준비해 둡니다.

 

 

 

1. MySQL 설치.

 

다음 명령어로 서버에 MySQL을 설치합니다.

 

$ sudp apt-get update

$ sudo apt-get install mysql-server

 

 

 

2. MySQL Secure Installation 실행.

 

MariaDB와 다르게 MySQL은 apt-get 설치후에 추가 설치 과정이 필요합니다. 다음 명령어로 Secure intallation을 진행합니다.

 

$ sudo mysql_secure_installation

 

 

암호 검증에 관한 물음입니다. Y를 입력해 줍시다.

 

 

암호 복잡도에 관한 정책입니다. 2를 입력해 줍니다.

 

 

루트 계정의 암호를 입력해줍니다. 입력한 암호의 보안 강도가 측정되며 이 값을 사용할 것인지 묻습니다. Y를 입력합니다.

 

 

MySQL은 기본으로 익명의 유저를 갖으며 배포 환경에선 이 유저를 지워야 합니다. 아예 설정에서부터 지우도록 합니다. Y를 입력합니다.

 

 

일반적으로 root유저는 localhost에서만 접속이 가능합니다. 이 부분은 사용 환경에 맞춰서 진행해 주세요. 전 JIRA에서만 사용할 용도로 설치하기 때문에 Y를 입력해 원격지에서 접속을 막도록 하겠습니다.

 

 

MySQL은 기본적으로 누구나 접근 가능한 "test" DB를 생성합니다. 이 DB 역시 배포 환경에선 지워져야 할 대상이므로 설정에서부터 지우도록 합니다. Y를  입력합니다.

 

 

테이블 권한을 다시 불러와 줍니다. Y를 입력합니다.

 

이제 모든 설정이 끝났습니다. 다음 명령어로 직접 MySQL에 접속해 봅니다.

 

$ sudo mysql -u root -p

 

 

정상적으로 DB에 접속되는것을 확인할 수 있습니다.

 

 

 

 

 

반응형

 

Ubuntu server 20.04에 Redmine을 설치하는 법에 대해 알아봅니다.

 

 

 

0. 사전 준비.

 

우분투 서버를 미리 설치해 둡니다. 우분투 서버에 접속 해 apt-get update를 진행해 주세요.

 

 

 

1. MariaDB 설치 및 Database 구성.

 

Redmine은 MariaDB를 사용합니다. Redmine 설치에 앞서 MariaDB를 설치해 줍시다.

 

$ sudo apt-get install mariadb-server

 

MariaDB가 설치되면 이제 Database를 생성해줍시다.

 

$ sudo mysql -u root -p

> CREATE DATABASE redmine CHARACTER SET utf8mb4;

 

 

DB 생성 후 redmine DB에 유저를 생성한 뒤 권한을 부여해 줍니다.

 

> GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost' IDENTIFIED BY 'P@SSWORD';

> FLUSH PRIVILEGES;

 

 

생성한 유저로 로그인해서 DB가 제대로 생성되었는지 확인합니다.

 

$ mysql -u redmine -p

> SHOW DATABASES;

 

 

 

 

2, Apache 설치.

 

Redmine을 서비스하기 위해 필요한 제품을 설치합니다.

 

$ sudo apt-get install apache2 libapache2-mod-passenger

 

 

 

3. Redmine 설치.

 

이제 Redmine을 설치합니다.

 

$ sudo apt-get install redmine redmine-mysql

 

설치를 진행하다 보면 "Configuring redmine" 화면이 나타납니다. 설정을 진행해 줍시다.

 

OK를 선택합니다.

 

Yes를 선택합니다.

 

 

관리자 암호를 입력해줍니다.

 

 

다시 한번 입력하도록 합니다. 이후 설치가 완료되면 다음 명령어를 통해 bundler를 설치합니다.

 

$ sudo gem update

$ sudo gem install bundler

 

 

설치 완료 후 Apache passenger 모듈의 설정 파일을 다음과 같이 수정합니다.

 

$ sudo vi /etc/apache2/mods-available/passenger.conf

 

<IfModule mod_passenger.c>
  PassengerDefaultUser www-data
  PassengerRoot /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini
  PassengerDefaultRuby /usr/bin/ruby
</IfModule>

 

 

이제 www에 Redmine의 심볼릭 링크를 생성해 줍니다.

 

$ sudo ln -s /usr/share/redmine/public /var/www/html/redmine

 

 

 

4. Apache2에 Redmine 구성파일 생성.

 

/etc/apache2/sites-available 폴더에 Redmine.conf 파일을 생성 한 뒤 다음과 같이 작성합니다.

 

$ sudo vi /etc/apache2/sites-available/redmine.conf

<VirtualHost *:80>
  ServerAdmin admin@example.com
  DocumentRoot /var/www/html/redmine
  ServerName projects.example.com
  ServerAlias www.projects.example.com
  <Directory /var/www/html/redmine>
    RailsBaseURI /redmine
    PassengerResolveSymlinksInDocumentRoot on
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

 

 

만약 도메인 이름을 아직 정하지 못했거나 없다면 위의 방식 대신 아래의 방식을 사용해 IP주소를 통해 Redmine에 액세스 할 수 있습니다.

 

위와는 다른 000-default.conf 파일을 수정합니다.

 

$ sudo vi /etc/apache2/sites-available/000-default.conf

 

<VirtualHost *:80>
  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/html/redmine
  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined
  <Directory /var/www/html/redmine>
    RailsBaseURI /redmine
    PassengerResolveSymlinksInDocumentRoot on
  </Directory>
</VirtualHost>

 

 

이제 Apache의 www-data 사용자가 액세스 할 수 있도록 Gemfile.lock을 생성하고 권한을 부여해 줍니다.

 

$ sudo touch /usr/share/redmine/Gemfile.lock

$ sudo chown www-data:www-data /usr/share/redmine/Gemfile.lock

 

구성 파일대로 Redmine을 활성화합니다.

 

$ sudo service apache2 restart

 

이제 호스트나 IP주소를 입력해 웹사이트로 이동해 봅시다.

 

 

정상적으로 Redmine이 활성화된 것을 확인할 수 있습니다. 이제 Redmine에서 프로젝트에 대한 설정을 한 뒤 사용하시면 됩니다.

 

기본 Admin계정 및 암호는 admin/admin입니다.

 

 

 

 

 

반응형

 

앞선 글에서 Express를 이용해 클러스터를 구성해 여러 워커를 가진 서버를 띄워보았습니다. 이번에는 제작한 웹 서버를 이용해 이중화 구성을 한 뒤 nginx를 이용해 로드밸런서를 만들어 그럴듯한 운영 환경을 구성해 보도록 하겠습니다.

 

 

 

0. 사전 준비.

 

이 글에서는 앞선 글에서 작성한 웹 서버를 사용합니다. 이전 글을 참고해 웹 서버를 준비해 주세요.

Nginx를 사용할 예정이니 미리 서버를 다운로드하여 주셔도 좋습니다. 단, Docker를 이용하실 분은 별도의 Nginx를 준비하지 않으셔도 됩니다.

모든 결과물을 한번에 배포하기 위해 docker-compose를 사용할 예정입니다. docker-compose를 준비해 주세요.

 

 

 

1. Nginx를 이용한 이중화 구성.

 

이제 우리가 만든 서버는 여러 개의 워커를 갖고 도커 위에서 동작합니다. 이제 안정성을 위해 두 개의 서버를 올려두고 그 앞에 Nginx를 둬서 로드밸런서의 역할을 하도록 합시다.

 

우리가 할 일은 단지 nginx의 config파일을 추가하는것 뿐 입니다. 아무런 추가 작업을 진행하지 않은 채 설정 파일을 추가해 주는 것만으로도 로드밸런서를 구성할 수 있습니다.

 

새로운 폴더인 my-nginx를 생성하고 그 안에 nginx 폴더를 만들어줍니다. 그리고 그 안에 nginx.conf 파일을 작성합니다.

 

#./nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

 

사실 위의 설정파일은 nginx를 설치하면 가장 기본으로 들어가 있는 값입니다. 스킵하셔도 무방합니다만 나중에 수정된 값을 사용할 수 있으므로 넣어주었습니다.

 

설정 파일을 살펴봅시다. nginx.conf의 맨 아래 include를 보세요. 이 include가 의미하는 바는 /etc/nginx/conf.d폴더 내의 모든 conf파일을 가져와 http 아래에 두겠다는 뜻입니다.

 

따라서 우리가 다음에 작성할 설정 파일이 바로 아래 추가될 것이란 의미가 됩니다. 이 외에도 nginx의 default 페이지가 담긴 default.conf도 추가될 것입니다. 

 

 

그다음으로nginx 안에 conf.d 폴더를 생성 한 뒤 my-react-lb.conf파일을 작성합니다.

 

# ./nginx/conf.d/my-react-lb.conf

upstream my-react {
    #least_conn;
    #ip_hash;
    server localhost:3000 weight=10 max_fails=3 fail_timeout=10s;
    server localhost:3001 weight=10 max_fails=3 fail_timeout=10s;
}    
server {
    listen                8080;
    server_name  localhost;
    location / {
        proxy_pass http://my-react;
    }
}

 

이 파일의 폴더 경로도 실제 리눅스 시스템에서 nginx가 위치하는 폴더의 경로를 맞추기 위함이므로 임의의 폴더에 작성하셔도 됩니다.

 

server항목을 먼저 봅시다. 이 서버는 8080 포트에서 동작하고 리버스 프록시로 동직 합니다. 즉, localhost:8080으로 들어오면 내부적으로 my-react로 연결시킨다는 뜻입니다. 이때 연결하는 my-react가 위에서 정의한 upstream입니다.

 

upstream 내부에 두 개의 서버가 정의되어 있습니다. 유저가 들어올 때마다 번갈아 가면서 3000, 3001 포트에 올라간 서버가 접속을 수용할 겁니다. 정리하자면 localhost:8080으로 접속하게 되면 내부적으로 로드밸런싱을 거쳐 localhost:3000, locahost:3001로 이동하게 된다는 겁니다.

 

주석 처리한 least_conn은 번갈아 가면서 수용하는 게 아닌 현재 접속이 가장 적은 곳에 우선 접속할 수 있도록 하는 기능이며 ip_hash는 접속한 사용자의 ip를 해싱해서 같은 사용자는 같은 서버로 접속할 수 있게 해 세션 문제 등을 해결할 수 있도록 하는 기능입니다.

 

따라서 메인 서버에 위의 설정 파일을 갖는 nginx를 설치한 뒤 Docker로 3000, 3001 포트에 앞서 작성한 웹 서버를 두대 올려 둔 후 localhost:8080으로 접속하면 정상적으로 로드밸런싱이 동작할 겁니다. 

 

 

 

2. Docker compose를 이용해 한번에 배포하기.

 

 

사실 위와 같이 배포하기 위해서는 서버에 nginx를 직접 설치해야 하며 그 서버에서 Docker가 동작 해 웹서버가 돌아야 합니다. 즉, nginx가 돌아가는 localhost에 웹서버가 동작해야 한다는 뜻입니다.

 

서버에 접속해 nginx를 설치하고 conf 파일을 수정해준 뒤 그 서버에 docker를 설치하고 docker 명령어로 두 개의 웹서버를 실행시키는 과정이 필요하게 됩니다. 이것만 해도 귀찮아지기 시작합니다. 게다가 수작업이 들어가는 만큼 오류의 가능성도 높아집니다.

 

이제 좀 더 편하게 위 과정을 다음과 같이 변경할 겁니다.

  1. Nginx를 직접 설치하지 않고 Docker Image로 빌드 해 Docker 명령어로 배포한다
  2. 명령어를 미리 작성해 둬 한번에 여러 이미지를 배포한다.

그러기 위해선 docker-compose와 nginx를 docker image로 빌드하는 작업이 필요합니다. 우선 nginx를 docker image로 빌드하기 위해 Dockerfile을 작성합니다.

 

# ./Dockerfile

FROM nginx:stable

COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./nginx/conf.d/my-react-lb.conf /etc/nginx/conf.d/my-react-lb.conf

CMD ["nginx", "-g", "daemon off;"]

 

매우 간단합니다. nginx 안정화 버전을 받아 우리가 작성한 conf파일을 복사 한 뒤 실행합니다. 이제 다음 명령어로 docker image를 빌드합니다.

 

$ docker build . -t my-nginx:0.0.1

 

그리고 빌드한 이미지를 실행해 봅시다. 

 

$ docker run -itd -p 3000:3000 my-react:0.0.1

$ docker run -itd -p 3001:3000 my-react:0.0.1

$ docker run -itd -p 8080:8080 my-nginx:0.0.1

 

localhost:8080으로 이동하면 어떻게 될까요?

 

 

에러가 보이는 이유에 대해 생각해 봅시다. 우리는 nginx를 도커로 돌렸으며 my-react-lb.conf의 upstream에 server로 localhost:3000와 localhost:3001을 등록했습니다. 과연 nginx 컨테이너 내에서 이 두 서버에 접속할 수 있을까요?

 

궁금하신 분은 다음 명령어로 한번 테스트해보시기 바랍니다.

 

$ docker exec -u 0 -it (my-nginx 컨테이너 ID) bash

$ apt-get update

$ apt-get intall telnet

$ telnet localhost 3000

 

당연히 독립된 네트워크 이므로 접속을 할 수 없습니다.

 

 

그러므로 우리는 Docker compose를 이용해야 합니다. Docker compose는 같이 배포되는 컨테이너끼리 미리 정의된 이름으로 접속이 가능합니다. 마치 도메인 네임처럼 말입니다.

 

최상위 폴더로 이동해 docker-compose.yml 파일을 생성합니다 저 같은 경우엔 my-react와 my-nginx를 포함하고 있는 폴더에 작성하였습니다.

 

 

그리고 docker-compose.yml을 다음과 같이 작성해 줍니다.

 

# docker-compose.yml

version: "3"
services:
    my-react-A:
        image: my-react:0.0.1
        ports:
            - "3000:3000"
    my-react-B:
        image: my-react:0.0.1
        ports:
            - "3001:3000"
    nginx:
        image: my-nginx:0.0.1
        ports:
            - "8080:8080"

 

여기서 서비스 항목을 봅시다. 우리는 my-react-A, my-react-B, nginx 이렇게 총 세 개의 컨테이너를 생성하도록 작성하였습니다. 당연히 이름은 변경해도 됩니다. 서비스 내의 image는 도커 이미지를 의미합니다. ports는 docker run의 -p 옵션과 동일합니다. 

 

이런 식으로 docker-compose를 구성하게 되면 위의 세 컨테이너 간에는 서비스에 작성한 이름으로 서로 접근이 가능해집니다. 따라서 우리의 my-react-lb.conf가 바뀌어야 함을 의미하죠.

 

# ./nginx/conf.d/my-react-lb.conf

upstream my-react {
    #least_conn;
    #ip_hash;
    server my-react-A:3000 weight=10 max_fails=3 fail_timeout=10s;
    server my-react-B:3000 weight=10 max_fails=3 fail_timeout=10s;
}    
server {
    listen                8080;
    server_name  localhost;
    location / {
        proxy_pass http://my-react;
    }
}

 

그리고 다시 도커 이미지를 빌드해 줍니다.

 

$ docker build ./my-nginx -t my-nginx:0.0.2

 

잊지 마세요. 태그에 버전을 0.0.2로 변경하였으므로 당연히 docker-compose를 변경해야 합니다. 이제 docker-compose를 통해 컨테이너를 올려보겠습니다.

 

$ docker-compoase up -d

 

우리가 작성한 docker-compomse파일 대로 컨테이너가 실행되었습니다. 이제 localhost:8080으로 이동해 보세요. react페이지가 보이시나요? 

 

그렇다면 우리가 어떤 서버로 접속했는지 알아보기 위해 localhost:8080/where로 이동해 봅시다.

 

 

위와 같이 접속할 때마다 서버 정보가 변경되는 것을 확인할 수 있습니다. 실제 운영환경에선 이렇게 하면 서버가 바뀌며 세션정보가 유실되므로 앞서 설명한 ip_hash와 least_conn옵션을 켜고 서버를 배포해야 합니다.

 

 

 

 

 

 

반응형

 

서버 운영에 있어서 성능과 안정성은 매우 중요한 요소들 중 하나입니다. 이 글에선 Express 서버를 이용할 때 여러 워커를 클러스터 구성을 이용해 서비스하고 NGINX를 이용해 이중화 구성을 하는 방법에 대해 알아보도록 합니다.

 

 

 

0. 사전 준비.

 

이 글에서 웹서버는 React와 Express를 사용해 배포한 서버를 사용할 예정입니다. 실제 배포는  Docker를 사용해 도커 이미지를 생성한 후 실행시키도록 할 예정입니다. 미리 NPM와 Docker를 준비합시다.

마지막으로 모든 작업 결과물을 한번에 구동시키기 위해 Docker compose를 사용할 예정이니 Docker compose도 준비해 주세요.

 

 

 

1. NodeJS

 

 

NodeJS는 구글이 구글 크롬에 사용하려고 제작한 V8 오픈소스 자바스크립트 엔진을 기반으로 제작된 자바스크립트 런타임입니다. NodeJS는 다음과 같은 특징이 있습니다.

  1. 단일 스레드.
  2. 비동기 방식.
  3. 이벤트 루프를 사용.
  4. NPM.

NodeJS는 싱글 스레드이기 때문에 하나의 CPU를 여럿이 나눠 갖는 건 비효율적입니다. 따라서 CPU 숫자에 맞춰서 서버를 띄워보겠습니다.

 

 

 

2. 웹서버 생성.

 

 

우선 프론트를 준비합니다. 다음 명령어로 react app을 생성해 주세요.

 

$ npx create-reac-app my-react

 

이 앱을 Express 서버로 서비스할 예정입니다. 폴더로 들어가서 다음 패키지를 설치하세요.

 

$ cd my-react

 

$ npm i express express-favicon 

 

이제 express 서버를 실행할 코드를 작성해야 합니다. server 폴더에 index.js파일을 만드신 후 다음과 같이 코딩해 주세요.

 

//// ./server/index.js

const express = require('express');
const favicon = require('express-favicon');
const path = require('path');

const app = express();
const port = 3000;
const server = app.listen(port, () => {
    console.log(`Server is listening on port ${server.address().port}.`);
});

app.use(favicon(path.join(__dirname, '../public/favicon.ico')));
app.use(express.static(__dirname));
app.use(express.static(path.join(__dirname, '../build')));

app.get('/ping', (req, res) => {
    return res.send('pong');
});
app.get('/*', (req, res) => {
    return res.sendFile(path.join(__dirname, '../build', 'index.html'));
});

 

이제 기본으로 생성된 react를 빌드해 줍시다.

 

$ npm run build

 

여기까지 수행하셨으면 폴더 구조가 다음과 같을 겁니다.

 

 

우리가 작성한 express서버가 react app을 잘 서빙하는지 확인해 봅시다. localhost:3000으로 이동해 react 앱을 확인해 보세요. 그리고 localhost:3000/ping으로 이동해 pong 메시지를 잘 수신하는지도 확인합시다.

 

 

 

3. 클러스터 구성.

 

앞서 설명한 대로 NodeJS는 단일 스레드입니다. 이제 우린 서버의 CPU개수에 맞춰서 여러 워커를 띄어서 서버를 운영해 보도록 하겠습니다.

 

여러 워커가 돌아갈 때 워커가 동작하는 서버를 구분하기 위해 uuid 패키지를 설치합니다.

 

$ npm i uuid

 

그리고./server/index.js를 다음과 같이 수정해주세요.

 

//// ./server/index.js
const cluster = require('cluster');
const os = require('os');
const uuid = require('uuid');

const port = 3000;
const instance_id = uuid.v4();

//// Create worker.
const cpu_count = os.cpus().length;
const worker_count = cpu_count / 2;

//// If master, create workers and revive dead worker.
if(cluster.isMaster){
    console.log(`Server ID: ${instance_id}`);
    console.log(`Number of server's CPU: ${cpu_count}`);
    console.log(`Number of workers to create: ${worker_count}`);
    console.log(`Now create total ${worker_count} workers ...`);

    //// Message listener
    const workerMsgListener = (msg) => {
        const worker_id = msg.worker_id;
        //// Send master's id.
        if(msg.cmd === 'MASTER_ID'){
            cluster.workers[worker_id].send({cmd: 'MASTER_ID', master_id: instance_id});
        }
    }

    //// Create workers
    for(var i = 0; i < worker_count; i ++){
        const worker = cluster.fork();
        console.log(`Worker is created. [${i +1}/${worker_count}]`);
        worker.on('message', workerMsgListener);
    }

    //// Worker is now online.
    cluster.on('online', (worker) => { console.log(`Worker is now online: ${worker.process.pid}`); });

    //// Re-create dead worker.
    cluster.on('exit', (deadWorker) => {
        console.log(`Worker is dead: ${deadWorker.process.pid}`);
        const worker = cluster.fork();
        console.log(`New worker is created.`);
        worker.on('message', workerMsgListener);
    });
}
//// If worker, run servers.
else if(cluster.isWorker){
    const express = require('express');
    const favicon = require('express-favicon');
    const path = require('path');

    const app = express();
    const worker_id = cluster.worker.id;
    const server = app.listen(port, () => { console.log(`Server is listening on port ${server.address().port}.`); });

    let master_id = "";

    //// Request master's id to master.
    process.send({worker_id: worker_id, cmd: 'MASTER_ID'});
    process.on('message', (msg) => {
        if(msg.cmd === 'MASTER_ID'){
            master_id = msg.master_id;
        }
    });

    app.use(favicon(path.join(__dirname, '../public/favicon.ico')));
    app.use(express.static(__dirname));
    app.use(express.static(path.join(__dirname, '../build')));

    app.get('/ping', (req, res) => { return res.send('pong'); });
    app.get('/where', (req, res) => { return res.send(`Running server: ${master_id} \n Running worker: ${worker_id}`); });
    app.get('/kill', (req, res) => { cluster.worker.kill(); return res.send(`Called worker killer.`);})
    app.get('/*', (req, res) => { return res.sendFile(path.join(__dirname, '../build', 'index.html')); });
}

 

소스가 길어졌으니 주석을 확인하면서 차근차근 코딩해 주세요. 간단히 설명해드리자면 다음과 같습니다.

  1. 우리는 한 서버의 CPU개수의 절반만큼의 워커를 생성할 겁니다.
  2. 만약 현재 생성된 클러스터가 마스터라면 워커 클러스터를 생성하고 관리하는 역할을 수행합니다.
  3. 마스터는 미리 정해진 개수만큼 워커를 생성하고 만약 죽은 워커가 발견된 경우 새 워커를 생성시켜 항상 일정 개수의 워커가 서버에서 동작할 수 있도록 합니다.
  4. 워커는 express 서버를 구동합니다.
  5. 워커에는 어느 서버에서 수행되고 있는지 확인할 수 있는 기능과 현재 워커를 죽일 수 있는 기능이 추가되었습니다.

코딩이 끝났다면 다시 서버를 실행시켜 보도록 합시다.

 

$ node server

 

콘솔에서 다음과 같은 메시지를 확인할 수 있습니다.

 

 

이제 localhost:3000/where로 이동해 보세요. 현재 우리가 접속한 서버와 워커의 번호를 확인할 수 있습니다. 여기서 localhost:3000/kill로 이동하면 워커를 죽일 수 있으며 이때 마스터는 죽은 워커를 확인 해 새 워커를 생성합니다. 

 

 

이후 다시 localhost:3000/where로 이동하면 워커의 번호가 변경된 것을 확인할 수 있습니다.

 

 

 

4. Docker를 이용한 서버 구동

 

 

이제 우리가 작성한 서버를 도커를 이용해 배포해봅시다. 간단히 배포하기 위해 Dockerfile을 사용해 배포할 예정입니다. 

 

Dockerfile 파일을 생성 한 뒤 다음과 같이 작성해 주세요.

 

# ./Dockerfile

FROM node:slim

# app 폴더 생성.
RUN mkdir -p /app

# 작업 폴더를 app폴더로 지정.
WORKDIR /app

# dockerfile과 같은 경로의 파일들을 app폴더로 복사
ADD ./ /app

# 패키지 파일 설치.
RUN npm install

# 환경을 배포 환경으로 변경.
ENV NODE_ENV=production

#빌드 수행
RUN npm run build

ENV HOST=0.0.0.0 PORT=3000
EXPOSE ${PORT}

#서버 실행
CMD ["node", "server"]

 

아주 간단한 Dockerfile을 작성했습니다. 주석을 참고하시면 모두 이해하실 수 있을 겁니다. 이제 다음 명령어를 통해 작성한 Dockerfile을 이용해 도커 이미지를 생성합니다.

 

$ docker build . -t my-react:0.0.1

 

 

도커 이미지가 정상적으로 생성된 것을 확인할 수 있습니다. 다음 명령어를 통해 직접 실행시켜 주세요.

 

$ docker run -itd -p 8080:3000 my-react:0.0.1

 

이제 localhost:8080로 이동하면 모든 기능을 정상적으로 사용할 수 있습니다.

 

 

 

 

 

 

반응형

 

기존에 사용하고 있던 Harbor에 LDAP를 연동해서 쓰고 있었는데 LDAP 서버가 문제가 생기는 바람에 더 이상 로그인이 불가능 한 상황에 이르렀습니다. 밀고 다시 설치할까 하던 중에 공식 홈페이지에 2.0.0 버전이 릴리즈 되었다는 사실을 알게 되어 이렇게 된 김에 버전을 올리기로 하였습니다. 

 

공식 홈페이지 글에 따르면 크게 세가지 변경점이 있다고 합니다.

  1. OCI 호환.
  2. Aqua Trivy를 기본 스캐너로 탑재.
  3. 다크 모드 지원.

 

 

다크 모드로 보다 세련되어 보이게 변경되었습니다. 맘에 드네요. 그럼 기존 설치한 Harbor 1.10.2 버전을 2.0.0 버전으로 업그레이드해 보도록 하겠습니다.

 

 

 

1. Harbor 요구사항 확인.

 

다행스럽게도 기존 요구사항이 2.0으로 오면서 변경되거나 하지는 않았습니다. Harbor의 요구사항은 다음과 같습니다.

 

Harbor는 리눅스에서 동작합니다. 기존 버전에서는 우분투 서버 18.04.3버전을 사용했습니다.

 

하드웨어 요구사항은 다음과 같습니다.

리소스 최소 사양 권장 사양
CPU 2 CPU 4CPU
메모리 4 GB 8 GB
스토리지 40 GB 160 GB

 

소프트웨어 요구사항은 다음과 같습니다.

소프트웨어 버전
Docker Engine 17.06.0 CE 버전 이상
Docker Compose 1.18.0 버전 이상
OpenSSL 최신 버전 권장

 

도커 및 도커 컴포즈 설치는 다음 글을 참고해주세요.

 

네트워크 포트 사용은 다음과 같습니다.

 

포트 프로토콜 설명      
443 HTTPS Harbor 포털 및 코어 API는이 포트에서 HTTPS 요청을 수락합니다. 구성 파일에서이 포트를 변경할 수 있습니다.
4443 HTTPS 하버 용 Docker Content Trust 서비스에 대한 연결입니다. 공증(Notary)이 활성화 된 경우에만 필요합니다. 구성 파일에서이 포트를 변경할 수 있습니다.
80 HTTP Harbor 포털 및 코어 API는이 포트에서 HTTP 요청을 수락합니다. 구성 파일에서이 포트를 변경할 수 있습니다.




2. Harbor Installer 다운로드.

 

Harbor 릴리즈 페이지로 이동하여 원하는 버전을 다운받습니다. 여러 버전이 있지만 이 글에서는 2.0 버전으로의 업그레이드를 목적으로 하고 있기 때문에 2.0.0 태그의 버전을 다운받도록 하겠습니다. 별다른 문제나 요구가 없는 한 "harbor-offline-installer-v2.0.0.tgz" 파일을 다운로드하여주시면 됩니다. 

 

다운로드한 파일이 위치한 폴더로 이동한 뒤 다음 명령어로 압축을 풀어줍니다.

 

$ tar xvf harbor-offline-installer-v2.0.0.tgz

 

 

 

 

3. Harbor 사전 설정.

 

파일이 준비되었으면 설치할 때 적용될 설정 파일을 수정해야 합니다. 기존 harbor.yml에서 .tmpl이 추가된 것으로 보입니다. hatbot.yml의 탬플릿의 느낌 같네요. 직접 파일을 열어보면 아시겠지만 변경된 내용이 좀 있습니다.

 

우선 TLS관련 설정이 추가되었습니다. HTTPS 바로 아래에 추가되었으니 TLS 설정이 필요하신분은 건드리시면 될 겁니다.

공식 홈페이지에 따르면 이 기능에 대한 설명은 다음과 같습니다.

 

By default, The internal communication between Harbor’s component (harbor-core,harbor-jobservice,proxy,harbor-portal,registry,registryctl,trivy_adapter,clair_adapter,chartmuseum) use HTTP protocol which might not be secure enough for some production environment. Since Harbor v2.0, TLS can be used for this internal network. In production environments, always use HTTPS is a recommended best practice.
This functionality is introduced via the internal_tls in harbor.yml file. To enabled internal TLS, set enabled to true and set the dir value to the path of directory that contains the internal cert files.

기본적으로 Harbor 구성 요소 (하버 코어, 하버 작업 서비스, 프록시, 하버 포털, 레지스트리, 레지스트리) 간의 내부 통신은 일부 프로덕션 환경에 충분히 안전하지 않을 수 있는 HTTP 프로토콜을 사용합니다. Harbor v2.0부터 이 내부 네트워크에 TLS를 사용할 수 있습니다. 프로덕션 환경에서는 항상 HTTPS를 사용하는 것이 좋습니다.
이 기능은 internal_tlsin harbor.yml파일을 통해 도입되었습니다 . 내부 TLS를 활성화하기 위해서는 enabled값을 true로 설정하고 dir을 내부 인증 파일이 포함된 디렉터리 경로로 설정하세요.

 

또한 Trivy설정이 추가되었습니다. Trivy를 간단하게 설명하자면 다음과 같습니다.

 

A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI

CI에 적합한 컨테이너를위한 간단하고 포괄적인 취약점 스캐너

 

Trivy에 관심이 있으신 분은 여기를 참조해 주시길 바라며 이 기능은 추후 사용하게된다면 포스팅하겠습니다.

 

그 외의 설정은 모두 동일합니다. 이제 이 파일을 복사해 줍시다.

 

$ cp harbor.yml.tmpl harbor.yml

 

복사한 harbor.yml파일을 적절하게 수정해 줍시다. 다른 건 몰라도 hostname은 변경해야 합니다. 물론 실제 도커를 사용하기 위해서는 인증서도 적용해야 합니다.

 

$ vi harbor.yml

 

 

 

4. Harbor 설치.

 

이제 남은 건 인스톨 스크립트를 실행하는 것뿐입니다. 업그레이드를 하는 경우 기존에 돌아가고 있는 harbor를 내리는 것을 잊지 마세요. 

 

$ sudo docker-compose down -v

 

 

혹시 몰라서 구 버전의 harbor docker compose 파일이 존재하는 폴더에서 실행시켜줬습니다.

 

이제 새 버전의 harbor을 설치합니다.

 

$ sudo ./install.sh

 

 

쭉 설치가 진행되고 마지막에 성공적으로 설치되었으며 자동으로 시작되었다는 메시지가 출력됩니다.

 

이제 호스트로 이동하면 정상적으로 harbor에 접속할 수 있습니다.

 

 

 

 

 

반응형

+ Recent posts