Windows에서 PuTTY를 사용해 AWS EC2 인스턴스에 원격 접속하는 방법에 대해 알아봅니다.
0. 사전 준비
PuTTY를 설치해 둡니다. 미리 EC2 인스턴스 생성에 사용한 키 파일을 준비해 둡니다.
1. PuTTYgen을 사용하여 프라이빗 키 변환
PuTTY에서는 SSH 키의 프라이빗 키 형식을 기본적으로 지원하지 않습니다. PuTTY에는 PuTTYgen이라는 도구가 있는데, 이 도구는 키를 필요한 PuTTY Private Key 형식으로 변환할 수 있습니다. PuTTY를 사용하여 인스턴스에 연결하려면 .pem 키 파일을 .ppk 키 파일로 변환해야 합니다.
먼저 PuTTYgen을 실행해 Parameters의 Type of key to generate에서 RSA를 선택해 줍니다.
Actions의 Load an existing private key file에서 Load 버튼을 클릭해 .pem 키 파일을 로드합니다.
로드 시 파일 확장자 필터를 All files로 해야 .pem 파일이 보입니다. 성공적으로 로드되면 다음과 같은 창이 보입니다.
이제 Save the genereated key의 Save private key 버튼을 클릭해 .ppk 키 파일을 저장합니다.
PuTTYgen에서 암호 없이 키 저장에 대한 경고가 표시되지만 예를 클릭해 키 파일을 저장해줍니다. 이름은 혼동을 방지하기 위해 .pem 파일과 같게 지어줬습니다. 이제 .ppk 키 파일이 준비되었습니다.
2. PuTTY를 이용해 EC2 인스턴스에 접속.
PuTTY를 실행한 뒤 HostName에 EC2 인스턴스의 퍼블릭 DNS를 입력합니다. EC2 인스턴스의 퍼블릭 DNS는 AWS EC2 콘솔로 이동해 우측 상단의 연결 버튼을 클릭하면 SSH 클라이언트 탭에서 확인할 수 있습니다.
HostName을 입력한 뒤 Port가 22인지 확인하세요. 이제 앞서 생성한 .ppk 키 파일을 불러올 차례입니다.
좌측 Cetegory에서 Connection -> SSH -> Auth로 이동합니다. Authentication parameters의 Private key file for authentication에 있는 Browse 버튼을 클릭해 생성한 .ppk 파일을 불러옵니다.
이 연결을 계속해서 사용하려면 Session으로 이동해 설정을 저장해 두는게 좋습니다. 이제 Open 버튼을 클릭해 EC2 인스턴스에 접속해 봅시다.
Docker, Express, NGINX, AWS ELB를 복합적으로 사용해 고가용성을 위한 애플리케이션 환경을 구축해 봅니다.
로드 밸런싱이 무엇인가요?
로드 밸런싱은 들어오는 네트워크 트래픽을 서버 그룹에 분산하는 데 사용되는 기술 혹은 알고리즘입니다. 모든 공용 사용자에게 서버에서 호스팅 하는 서비스에 대한 단일 진입점을 제공합니다.
프로덕션 서버는 일반적으로 로드 밸런서 뒤에서 실행됩니다. 서버 전체에서 들어오는 부하를 "균등"하게 나워 서버 과부하를 방지할 수 있기 때문입니다.
또한 로드 밸런서는 트래픽을 라우팅 하는 서버에 보조 기능을 제공합니다. 로드 밸런서는 역방향 프락시 역할을 합니다. 역방향 프락시는 서버 그룹과 사용자 간의 중개자와 같습니다. 역방향 프락시가 처리하는 모든 요청은 요청 조건에 따라 적절한 서버로 전달됩니다. 이런 방식으로 역방향 프락시는 구성파일, 토큰, 암호와 같은 민감한 데이터가 저장되는 주 서버에 대한 액세스를 방지하면서 서버의 ID를 익명으로 유지합니다.
로드 밸런서로서의 NGINX와 AWS ELB
NGINX는 역방향 프락시의 역할도 할 수 있는 빠른 무료 오픈소스 로드 밸런서입니다.
반면에 ELB는 아마존 AWS에서 제공하는 로드 밸런싱 서비스입니다. ELB는 ALB, CLB, NLB의 세 가지 유형이 있습니다. 게이트웨이 로드 밸런서라고 하는 새로운 로드 밸런서도 AWS가 제공하는 클라우드 서비스 제품군에 추가되었습니다.
이 튜토리얼의 아이디어는 다중 포트에서 실행되는 고 가용성 Node.js 서버를 제공할 수 있는 계단식의 다중 로드 밸런서 구조를 만드는 것입니다.
* 참고사항
Docker 애플리케이션을 위해 AWS의 자체 ECS, ECR 환경을 활용하는 방법을 포함해 AWS에서 Node.js 애플리케이션을 위한 인프라를 구축하는 방법에는 여러 가지 방법이 있습니다. 하지만 이 자습서에서는 이에 초점을 맞추지 않고 EC2 인스턴스, 로드 밸런서의 메커니즘, 로드밸런싱, 프락시 포트를 통해 Docker와 상호작용 하는 방법을 더 잘 이해하는 것을 목표로 합니다.
아키텍처 개요
이것이 우리가 목표로 하는 아키텍처입니다. AWS와 NGINX에서 관리하는 다중 로드 밸런서는 node 앱을 위한 EC2 인스턴스에서 여러 포트를 관리하는데 도움이 됩니다. 이 아키텍처의 장점은 두 인스턴스가 AZ1과 AZ2의 서로 다른 가용 영역을 가진다는 것입니다. 이로 인해 한 영역이 다운되더라도 다른 영역은 계속 동작할 것이며 두 애플리케이션은 충돌하지 않게 됩니다.
앱 폴더에는 Dockerfile과 함께 노드 서버 소스 코드가 포함되어 있습니다. nginx 폴더에는 업스트림 서버 포트 구성을 정의하는 구성 파일인 nginx.conf가 있으며 다음과 같습니다:
http{
upstream lb {
server 172.17.0.1:1000 weight=1;
server 172.17.0.1:2000 weight=1;
server 172.17.0.1:3000 weight=1;
}
server {
listen 80;
location / {
proxy_pass http://lb;
}
}
}
이 구성 파일은 메인 NGINX 서버가 포트 80번에서 동작한다고 지정하고 루트 위치 "/"은 lb라는 이 구성 파일에 정의된 업스트림으로 요청(프락시 패스)을 릴레이 합니다. lb는 서버 수를 지정하는 업스트림 객체이며 이 객체는 포함될 서버의 수와 이러한 서버가 내부적으로 실행될 포트를 지정합니다. 여기서 포함될 서버란 docker-compose를 통해 마운트 되는 서버이며 이는 이후 섹션에서 좀 더 자세히 다룹니다. 프락시는 80번 포트의 트래픽 부하를 분산시키게 됩니다.
우리의 경우 업스트림 프락시는 트래픽을 1000번, 2000번, 3000번 포트로 보냅니다. 이 포트 번호는 docmer-compose YAML 파일에서 작성되는 env 변수로 익스프레스 서버 인스턴스에 전송되는 내부 PORT 값과 일치해야 합니다.
각각의 시작되는 인스턴스에 대해 다음 작업을 수행해 줍니다.
1. 생성했던 키 페어를 이용해 인스턴스에 SSG로 연결합니다.
2. 다음 터미널 명령을 수행해 dockerfile로 앱 이미지를 빌드합니다.
git clone https://github.com/sowmenappd/load_balanced_nodejs_app.git
cd load_balanced_nodejs_app/app
docker build -t app .
3. 다음으로 NGINX 서버 이미지를 빌드합니다.
cd ../nginx
docker build -t nginx-s .
4. docker images 명령어를 실행 해 다음과 같은 내용이 표시되는지 확인합니다.
5. 두 번째 서버는 docker-compose.yml 파일을 수정해야 합니다. env 변수중 SERVER_ID는 모든 앱인 app1, app2, app3에 대해 2로 변경해야 합니다.
걱정하지 마세요 프로덕션 서버에서는 이런 일을 할 필요가 없습니다. 이는 데모 목적만을 위한 것입니다.
6. 마지막으로 다음 명령어를 실행합니다.
cd ..
docker-compose up -d
격리된 서버는 이제 백그라운드에서 세 개의 express 앱을 실행하게 됩니다. 이제 필요한 것은 AWS가 제공하는 로드 밸런서인 AWS ALB를 사용하여 이 시스템을 마운트 하는 것입니다.
AWS ALB에 시스템 마운트 하기.
이제 두 인스턴스 모두 마운트 할 준비가 되었습니다. 다음 단계를 따라 AWS에 애플리케이션 로드 밸런서를 설정하도록 합시다.
1. EC2 대시보드의 대상 그룹으로 이동해 대상 그룹을 만듭니다.
2. 대상 유형은 인스턴스를 선택 한 뒤 대상 그룹 이름을 작성하고 다음을 클릭하세요.
3. 실행 중인 두 인스턴스를 선택하고 선택한 인스턴스를 위한 포트를 80으로 설정한 뒤 아래에 보류 중인 것으로 포함 버튼을 클릭합니다.
4. 보류 중인 항목에 추가된 인스턴스를 확인한 뒤 대상 그룹을 생성합니다.
5. 이제 로드 밸런서로 이동해 로드 밸런서 생성 버튼을 클릭한 뒤 Application Load Balancer 생성을 클릭합니다.
6. 앞서 인스턴스를 생성할 때 선택한 가용 영역을 고른 뒤 계속합니다.
7. 보안 그룹 구성으로 이동해 모든 IP에 대해 80번 포트가 열려있는 새 보안 그룹을 만들고 다음을 클릭합니다.
8. 라우팅 구성에서 앞서 기존 대상 그룹을 고른 뒤 앞서 생성한 대상 그룹을 선택합니다.
9. 설정을 검토 한 뒤 로드 밸런서를 생성합니다. 로드 밸런서는 생성 후 상태 확인을 실행 한 뒤 몇 분 내로 실행되어야 합니다.
이제 EC2 대시보드의 로드 밸런서에서 방금 생성한 로드 밸런서의 DNS이름을 복사합니다.
이 DNS 주소를 브라우저에 붙여 넣고 엔터를 누르세요. 브라우저를 새로 고칠 때마다 SERVER_ID와 PORT가 다른 값을 전송하는 것을 볼 수 있습니다.
이는 기본적으로 NGINX와 AWS 로드 밸런서가 로드 밸런싱을 위해 기본적으로 라운드 로빈 알고리즘을 사용하기 때문입니다.
맺는 글.
이렇게 배포된 시스템은 다중 로드 밸런서의 구성을 통해 높은 가용성을 보장하고 오랜 기간 동작하는 동안 많은 양의 트래픽을 견딜 수 있게 되었습니다. 이 자습서를 뒤이어 소스 제어와 통합 배포 파이프라인을 관리하고 GitHub 저장소에 커밋할 때 변경사항을 서버에 배포하는 방법을 보여주는 또 다른 기사를 게시할 예정입니다.
NGINX는 웹 서비스, 역방향 프록시, 캐싱, 로드밸런싱, 미디어 스트리밍등을 위한 오픈소스 소프트웨어 입니다. 이 글에서는 자주 사용하는 NGINX 구성을 몇가지 다루도록 하겠습니다.
1. Listen To Port
server {
# Standard HTTP Protocol
listen 80;
# Standard HTTPS Protocol
listen 443 ssl;
# Listen on 80 using IPv6
listen [::]:80;
# Listen only on using IPv6
listen [::]:80 ipv6only=on;
}
2. Access Logging
server {
# Relative or full path to log file
access_log /path/to/file.log;
# Turn 'on' or 'off'
access_log on;
}
3. Domain Name
server {
# Listen to yourdomain.com
server_name yourdomain.com;
# Listen to multiple domains
server_name yourdomain.com www.yourdomain.com;
# Listen to all domains
server_name *.yourdomain.com;
# Listen to all top-level domains
server_name yourdomain.*;
# Listen to unspecified Hostnames (Listens to IP address itself)
server_name "";
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://0.0.0.0:3000;
# where 0.0.0.0:3000 is your application server (Ex: node.js) bound on 0.0.0.0 listening on port 3000
}
}
7. Load Balancing
upstream node_js {
server 0.0.0.0:3000;
server 0.0.0.0:4000;
server 123.131.121.122;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://node_js;
}
}
이제 도커 이미지로 빌드해 봅시다. 자동으로 생성된 Dockerfile을 우클릭 해 Docker 이미지 빌드를 클릭합니다.
정상적으로 빌드되는지 확인합니다. 해당 방식으로 이미지를 빌드하면 자동으로 latest 태그가 설정되어 빌드가 됩니다.
3. fluentd 추가하기.
앞서 작성한 프로그램이 로그를 남기면 fluentd는 그 로그파일을 읽어 Elasticsearch로 로그를 전송하는 역할을 담당하게 될 겁니다. 이렇게 로그 파일을 추적해 로그를 전송하는 방식은 fluentd의 "tail" Input Plugin을 사용합니다. 자세한 내용은 공식 홈페이지를 참고해 주시기 바랍니다: Fluentd - tail
로그를 읽어 들이는 Input Plugin이 있으면 반대로 읽은 로그를 Elasticsearch로 전송하는 기능도 있습니다. 다행히도 별도의 구현을 할 필요 없이 바로 Elasticsearch로 로그를 보낼 수 있도록 Elasticsearch Output Plugin을 제공해주고 있습니다. 자세한 내용은 공식 홈페이지를 참고해 주시기 바랍니다: Fluentd - elasticsearch
이제 tail과 elasticsearch를 사용할 수 있도록 fluentd의 설정 파일을 작성하도록 하겠습니다.
앞서 작성한 프로그램의 로그를 읽어 elasticsearch로 전송하는 설정 파일입니다. 다음으로는 이 설정 파일과 elasticsearch 플러그인을 사용할 수 있도록 fluentd 이미지를 빌드하는 dockerfile을 작성합니다.
# /fluentd/dockerfile
FROM fluentd:v1.9.1-debian-1.0
COPY /conf/* /fluentd/etc/
USER root
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-document", "--version", "4.3.3"]
USER fluent
이제 이 dockerfile을 사용해 fluentd 이미지를 빌드해 줍니다.
$ docker build . -t my-fluentd:0.0.1
** 여기서는 conf 파일을 포함하는 이미지를 미리 빌드해서 사용합니다.
** 만약 docker-compose에서 새로 빌드하고자 한다면 dockerfile의 "COPY /conf/* /etc/"를 사용할 수 없으므로 별도로 conf 파일을 옮겨주는 작업이 필요합니다.
4. 배포를 위한 docker-compose 작성.
이 글은 간단한 테스트를 위한 글이므로 테스트 프로그램, fluentd, Elasticsearch, Kibana 모두 한 docker-compose 파일 내에 작성해 배포하도록 진행하겠습니다.
내용은 간단합니다. 앞서 빌드한 이미지인 fluentdtester:latest와 my-fluentd:0.0.1 그리고 elasticsearch와 kibana를 실행시킵니다. fluentd-tester와 fluentd 서비스는 test-logs라는 이름의 볼륨을 이용해 데이터를 공유하도록 해주었습니다.
이를 통해 fluentd-service에서 작성한 로그파일에 fluentd가 접근해 tail로 로그파일을 elasticsearch로 전송합니다.
다음 명령어를 통해 docker-compose를 실행시켜봅시다.
$ docker-copmpose up -d
정상적으로 컨테이너들이 실행된 것을 확인할 수 있습니다.
5. 동작 확인.
컨테이너가 정상적으로 실행되었다면 이제 테스트 로그를 남겨봅시다. 다음 주소로 이동해 swagger를 사용해 로그를 남깁시다.
mongo2와 mongo3은 mongo 이미지를 그대로 가져와 사용합니다. 볼륨은 각각 자신이 설정한 볼륨 경로 내의 "mongoRepl/mongo2"와 "mongoRepl/mongo3"에 컨테이너 내부의 mongodb 데이터가 저장됩니다.
세 컨테이너는 depends_on으로 연결되어 있으며 bridge 네트워크를 사용해 서로 서비스 이름을 통해 통신할 수 있습니다. 추가적으로 mongo2와 mongo3은 replSet 명령어로 "replication"이라는 이름으로 ReplicaSet을 생성합니다. 이 이름은 앞서 작성한 js의 _id에 입력한 값과 동일해야 합니다.
mongo1은 다른 두 서비스와 달리 mongo 이미지를 그대로 사용하지 않고 앞서 작성한 dockerfile을 이용해 새로운 이미지를 빌드합니다. 이 서비스가 실행되면 다른 두 mongodb 서비스와 같이 ReplicaSet을 구성하게 됩니다.
이제 다음 명령어를 통해 docker-compose를 실행시켜 봅시다
$ docker-compose up -d
이제 ReplicaSet이 정상적으로 동작하는지 확인하기 위해 컨테이너에 접속해 봅시다. 먼저 컨테이너의 ID를 확인합니다.
$ docker ps -a
컨테이너 ID를 확인 후 아무 데나 접속해 봅니다.
$ docker exec -u 0 -it 51 mongo
위와 같이 "replication:PRIMARY" 혹은 "replication:SECONDARY"라고 표시되면 정상적으로 ReplicaSet이 구성된 겁니다.
Visual studio 2019의 다른 여러가지 Web 프로젝트는 대부분 기본적으로 Docker Container 배포를 지원합니다. 하지만 안타깝게도 포스팅을 올리는 현재 Visual studio 2019에서 React.js 프로젝트는 Docker Container 배포를 기본적으론 지원하지 않습니다. 앞서 프로젝트를 만들 때 docker 지원을 보지 못하셨을 겁니다.
React.js 프로젝트에 수동으로 docker 지원을 추가해 봅시다. 먼저 프로젝트를 우클릭한 뒤 추가 -> Docker 지원을 클릭합니다.
대부분의 경우는 Linux container일겁니다. 자신이 원하는 대상 OS를 선택합니다.
이제 IDE가 자동으로 "Dockerfile"을 생성해 .Net5.0에 맞는 dockerfile을 작성해 줍니다. "Dockerfile"을 우클릭 해 "Docker 이미지 빌드"를 클릭해 도커 이미지를 생성해 봅시다.
빌드가 잘 되시나요? 아마 다음과 같은 오류와 함께 빌드가 실패했을 것입니다.
이제 빌드 출력창을 한번 뒤져봅시다. 아마 다음과 같은 에러 메시지를 마주 할 수 있을 겁니다.
1>#16 2.809 /bin/sh: 2: /tmp/tmp1d7d426dff82428db5f0845ee787658f.exec.cmd: npm: not found 1>#16 2.814 /src/DockerDeployExample/DockerDeployExample.csproj(37,5): error MSB3073: The command "npm install" exited with code 127. 1>#16 ERROR: executor failed running [/bin/sh -c dotnet publish "DockerDeployExample.csproj" -c Release -o /app/publish]: exit code: 1
누가 봐도 어디서 문제가 생긴건지 찾으실 수 있을 겁니다 npm: not found 우리 이미지엔 npm이 없었습니다.
리눅스 컨테이너 항목을 보면 수동으로 nodejs를 설치해야 하는 것을 알 수 있습니다. 이제 우리 "Dockerfile"에 다음과 같은 명령어를 추가해 주도록 합니다.
RUN curl -sL https://deb.nodesource.com/setup_15.x | bash - RUN apt-get install -y nodejs
* 현재 포스팅 일자 기준으로 nodejs의 최신 버전은 15.x 버전이며 LTS는 14.x 버전입니다.
MSDN페이지에선 base와 build 모두에 설치했지만 .Net5.0에선 base 이미지에 nodejs를 설치하면 에러가 발생하며 build 이미지에만 nodejs를 설치해 줘도 정상적으로 빌드가 됩니다.
수정한 Dockerfile은 다음과 같습니다.
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
RUN curl -sL https://deb.nodesource.com/setup_15.x | bash -
RUN apt-get install -y nodejs
WORKDIR /src
COPY ["DockerDeployExample/DockerDeployExample.csproj", "DockerDeployExample/"]
RUN dotnet restore "DockerDeployExample/DockerDeployExample.csproj"
COPY . .
WORKDIR "/src/DockerDeployExample"
RUN dotnet build "DockerDeployExample.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DockerDeployExample.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DockerDeployExample.dll"]
이제 다시 "Dockerfile"을 우클릭 해 "Docker 이미지 빌드"를 클릭해 도커 이미지를 생성해보면 정상적으로 이미지가 생성되는 것을 확인할 수 있습니다.
4. Docker image push
이제 빌드한 이미지를 "게시" 기능을 이용해 Docker Registry에 Push까지 해봅시다. Docker Registry는 DockerHub를 사용하거나 PrivateRegistry를 구축해 사용하시면 됩니다.
// public sealed class OpenFileDialog : System.Windows.Forms.FileDialog
var fileContent = string.Empty;
var filePath = string.Empty;
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
//Get the path of specified file
filePath = openFileDialog.FileName;
//Read the contents of the file into a stream
var fileStream = openFileDialog.OpenFile();
using (StreamReader reader = new StreamReader(fileStream))
{
fileContent = reader.ReadToEnd();
}
}
}
MessageBox.Show(fileContent, "File Content at path: " + filePath, MessageBoxButtons.OK);
위와 같이 이벤트를 수정해 봅시다.
슬프게도 에러가 발생합니다. 심지어 "System.Windows.Forms.FileDialog"도 찾을 수 없습니다.
. .Net 5.0 WPF 프로젝트에서 WinForm 사용하기.
사실 .Net5.0 WPF 프로젝트에서 WinForm은 사용할 수 없는 게 기본값입니다. WinForm을 사용하기 위해선 프로젝트 설정으로 가서 수동으로 WinForm을 사용하도록 설정해야 합니다.