Overview

AWS EC2 인스턴스를 생성하고 Spring Boot 서버를 띄워보는 것까지 진행합니다.

주 목표는 서버를 외부에 제공하는 거라서 따로 배포 시스템을 구축하지 않고 단순히 빌드 파일을 복사해서 수동으로 띄울 겁니다.

글은 다음과 같은 순서로 진행합니다.

  • EC2 인스턴스 생성
  • 탄력적 IP (Elastic IP) 추가
  • 터미널에서 SSH 클라이언트로 EC2 접속
  • 보안 그룹 설정
  • Spring Boot 서버 띄우기

1. EC2 인스턴스 생성

우선 AWS 홈페이지 에 접속해서 계정을 생성 후 콘솔에 로그인 된 상태여야 합니다.

2022년 1월 16일 기준으로 작성되었으며 이후에 홈페이지 인터페이스가 변경될 수 있습니다.

2022년 4월 29일 기준으로 AWS EC2 인스턴스 생성 UI 가 바뀌어서 이부분을 수정합니다.


1.1. AWS Region 설정

우선 위치를 서울로 설정합니다.

리전에 따라서 인스턴스 위치가 결정되기 때문에 외국으로 하면 속도가 낮을 수도 있습니다.

만약 대한민국이 아닌 다른 나라에 서비스 하려면 그 도시를 선택해도 됩니다.


1.2. EC2 메뉴로 이동

처음에 대시보드가 나올텐데 만약 EC2 서비스를 찾기 힘들다면 검색해서 들어갑니다.


1.3. 새 인스턴스 생성

인스턴스 메뉴로 들어가 인스턴스 시작 버튼을 클릭합니다.


1.4. Amazon Machine Image(AMI) 및 인스턴스 유형 선택

AMI 는 어떤 서버로 구성할지 선택하는 겁니다.

여러 종류가 있어서 원하는 걸 선택하면 되고, 저는 프리 티어에 Ubuntu LTS 버전을 선택했습니다.

인스턴스 유형은 프리 티어를 사용한다면 다른 선택권이 없습니다.

스펙이 좋을수록 과금이 더 많이 되기 때문에 처음부터 좋은걸 고르기보다는 작게 시작했다가 스케일업 해나가는 걸 추천드립니다.


1.5. 키 페어 생성

키 페어는 EC2 서버에 SSH 접속을 하기 위해 필수라서 생성해야 합니다.

"새 키 페어 생성" 을 눌러 원하는 이름을 적고 생성합니다.

위 그림처럼 설정 후 생성하면 자동으로 my-key.pem 파일이 다운되며, SSH 환경에 접속하기 위해서는 해당 키 파일이 존재하는 위치로 가서 ssh 명령어를 실행하면 됩니다.

한번 다운받은 후에는 재다운 받을 수 없기 때문에 안전한 곳에 저장해둡니다.


생성한 후에는 이렇게 키 페어를 선택하면 됩니다.


1.6. 네트워크 및 스토리지 선택

네트워크 설정은 EC2 에 접속을 허용하는 ACL 을 생각하면 됩니다.

나중에 "보안 그룹" 이라는 걸로 별도 설정을 할 예정이기 때문에 SSH 트래픽만 허용해줍니다.

위 설정대로 하면 SSH 트래픽 접속 가능한 IP 가 내 IP 로 자동 설정 됩니다.


프리티어는 최대 30 까지 지원하기 때문에 해당 부분만 변경해줍니다.

볼륨 유형은 범용 SSD 로 선택해야 합니다.

만약 Provisioned IOPS SSD (프로비저닝된 IOPS SSD) 를 선택한다면 사용하지 않아도 활성화한 기간만큼 계속 비용이 발생하게 됩니다.


1.7. 인스턴스 설정 요약

다 설정하면 우측에 간단하게 지금까지 설정한 인스턴스 요약이 뜹니다.

이상한 부분이 없다면 인스턴스 시작을 눌러서 생성하면 됩니다.


1.8. 인스턴스 생성 완료

처음 화면으로 돌아오면 이렇게 인스턴스가 생성된 것을 확인할 수 있습니다.

이제 탄력적 IP 와 보안 그룹을 추가하기 위해 인스턴스 ID 를 클릭합니다.


2. 탄력적 IP (Elastic IP)

AWS EC2 인스턴스는 서버를 중지하고 다시 실행시키면 퍼블릭 IP 가 변경되기 때문에 클라이언트가 사용할 수 있는 변하지 않는 IP 가 필요합니다.

탄력적 IP (Elastic IP) 란 외부에서 인스턴스에 접근 가능한 고정 IP 입니다.

탄력적 IP 는 만들어놓고 사용하지 않더라도 과금이 되기 때문에 필요한 만큼만 생성하는 것이 중요합니다.

자세한 설명은 AWS Docs - 탄력적 IP 에서 확인하실 수 있습니다.


우선, 진짜 퍼블릭 IP 가 변경하는지 한번 확인해봅시다.

현재 생성된 인스턴스의 정보입니다.

퍼블릭 IP 로 3.35.238.69 가 할당된 것을 확인할 수 있습니다.



인스턴스를 중지 시켰다가 다시 실행시켜보았습니다.

인스턴스 ID, 프라이빗 IP 와 같은 정보는 동일한데 퍼블릭 IP 가 13.125.3.218 로 변경된 것을 볼 수 있습니다.

이렇게 퍼블릭 IP 는 변경될 가능성이 있기 때문에 변하지 않는 탄력적 IP 를 할당해주어야 합니다.



2.1. 탄력적 IP 메뉴 접근

메뉴에서 탄력적 IP 를 찾아서 들어갑니다.

아직 아무것도 할당받은 게 없기 때문에 새로운 IP 를 추가합니다.


2.2. 새로운 탄력적 IP 할당

딱히 변경할 건 없고 바로 할당을 선택해서 만들어줍시다.


2.3. 탄력적 IP 주소 선택

방금 생성한 탄력적 IP 를 선택해서 연결을 시도합니다.


2.4. 인스턴스 선택 및 연결

설정 화면에 들어가면 현재 내 인스턴스 목록을 선택할 수 있고 연결된 프라이빗 IP 까지 선택 가능합니다.


2.5. 인스턴스 정보 확인

탄력적 IP 를 연결하고 다시 인스턴스 정보를 확인해보면 IP 가 할당된 것을 볼 수 있습니다.

퍼블릭 IP 주소도 기존 값에서 탄력적 IP 주소로 자동으로 변경되었습니다.


3. SSH 클라이언트로 서버 접속

이제 내가 만든 EC2 인스턴스에 접속해봅니다.

인스턴스 정보에서 "연결" 버튼을 클릭하면 인스턴스에 연결 가능한 여러가지 방법을 알려줍니다.

여기서 "SSH 클라이언트" 로 접속하는 방법을 알아봅니다.


사실 너무 친절하게 알려주고 명령어도 복사할수 있게 되어있어서 따로 할건 없습니다.

저 방법대로 서버에 한번 접속해보고, 호스트를 등록해서 간편하게 등록하는 방법을 알아봅시다.


3.1. 터미널 실행 후 키 페어 파일 위치로 이동

터미널을 실행해서 이전에 다운 받은 키 페어 파일 위치로 이동합니다.

저는 다운로드 받은 후 따로 옮기지 않았기 때문에 Downloads 디렉토리에 들어갔습니다.

키 파일이 없으면 접속할 수 없고 다시 다운받을 수도 없기 때문에 잘 관리해야 합니다.


3.2. 키 파일 권한 변경

$ chmod 400 my-key.pem

키 파일의 권한을 변경해줍니다.


3.3. SSH 접속

가이드에 있는 대로 퍼블릭 DNS 또는 퍼블릭 IP 를 사용해서 인스턴스에 접속할 수 있습니다.

# 퍼블릭 DNS 로 접속
$ ssh -i "my-key.pem" ubuntu@ec2-3-37-206-248.ap-northeast-2.compute.amazonaws.com

# 퍼블릭 IP 로 접속
$ ssh -i "my-key.pem" ubuntu@3.37.206.248



3.4. 호스트 등록해서 간편하게 접속

이제 우리는 SSH 로 EC2 인스턴스 서버에 접속할 수 있습니다.

하지만 위에서 본 것처럼 매번 ssh -i {키 페어 파일} {ubuntu}@{탄력적 IP} 를 입력해야 합니다.

일일히 기억하기도 귀찮고 타이핑도 번거롭기 때문에 호스트로 등록해서 쉽게 접속할 수 있도록 변경해봅시다.


3.4.1. ~/.ssh 디렉터리로 키 페어 파일 복사

우선 키 페어 파일을 ~/.ssh/ 로 복사합니다.

$ cp my-key.pem ~/.ssh/

3.4.2. 키 페어 파일 권한 변경

키 페어 파일의 권한을 변경합니다.

$ chmod 600 my-key.pem



3.4.3. ~/.ssh/config 파일 생성

~/.ssh/ 디렉터리에 config 파일을 생성해서 다음과 같이 입력합니다.

이미 파일이 존재한다면 맨 아래에 입력하면 됩니다.

User 는 우분투를 선택했다면 ubuntu 고 그 외에는 전부 ec2-user 일겁니다.

$ vi ~/.ssh/config

# 아래는 파일 내용
# ssh -i {키 페어 파일} {유저 이름}@{탄력적 IP}
Host {원하는 호스트 이름}
User {유저 이름}
HostName {탄력적 IP}
IdentityFile {키 페어 파일 위치}

위 형식에 따라 저는 다음과 같이 작성했습니다.



3.4.4. 설정한 Host 이름으로 접속

이제 키 페어 파일이 없는 곳에서도 간단한 별칭으로 SSH 접속이 가능합니다.



4. 보안 그룹 설정

보안 그룹은 AWS 에서 제공하는 방화벽 모음입니다.

서비스를 제공하는 애플리케이션이라면 상관 없지만 RDS 처럼 외부에서 함부로 접근하면 안되는 인스턴스는 허용된 IP 에서만 접근하도록 설정이 필요합니다.

  • 인바운드 (Inbound): 외부 -> EC2 인스턴스 내부 허용
  • 아웃바운드 (Outbound): EC2 인스턴스 내부 -> 외부 허용

4.1. 현재 보안 그룹 확인

인스턴스 정보의 보안 탭에서 현재 설정된 보안 그룹을 확인할 수 있습니다.

처음 인스턴스를 생성할 때 아무런 보안 그룹을 설정하지 않았기 때문에 default 값만 들어가있습니다.

인바운드 규칙 해석해보면 22번 포트의 모든 IP 에 대해서 TCP 연결을 허용한다는 의미입니다.

SSH 접속을 위해 22번 포트는 기본으로 설정해준 것 같네요.

이제 새로운 보안 그룹을 만들어 봅시다.


4.2. 보안 그룹 ID 리스트 확인

인스턴스에서 보안 그룹 ID 를 보고 들어갈 수도 있지만 메뉴에서 직접 들어가면 이렇게 보안 그룹 ID 리스트를 볼 수 있습니다.

보안 그룹은 인스턴스와 별개로 존재하기 때문에 한번 만들어두면 새 인스턴스를 생성해도 한번에 적용할 수 있습니다.

우측 상단의 "보안 그룹 생성" 버튼을 눌러서 새 보안 그룹을 만들어봅시다.


4.3. 보안 그룹 생성

먼저 보안 그룹의 이름과 설명을 추가하고 인바운드 규칙과 아웃바운드 규칙을 편집합니다.


4.4. 인바운드 규칙

인바운드는 외부에서 EC2 로 요청할 때 허용할 IP 대역을 설정할 수 있습니다.

원하는 규칙을 추가하려면 "규칙 추가" 버튼을 누릅니다.

유형을 먼저 선택하면 자동으로 그에 맞는 프로토콜과 포트 범위가 고정됩니다.

웬만한 유형들이 이미 존재하기 때문에 편하게 설정할 수 있습니다.

우선 로컬 PC 에서 서버에 접속할 수 있게 SSH 를 추가하고 소스를 내 IP 로 추가합니다.

"소스" 를 선택하면 우측에 알아서 IP 가 추가되며, 특정 IP 나 대역을 넣으려면 "사용자 지정" 으로 추가할 수 있습니다.

만약 여러 사람이 함께 작업하는 프로젝트라면 각각의 로컬 PC IP 를 전부 추가해줘야 합니다.

SSH, HTTP, HTTPS 와 같은 기본 포트들을 열고 스프링 부트 프로젝트트까지 사용자 지정으로 추가해줍니다.


4.5. 아웃바운드 규칙

아웃바운드 규칙은 딱히 설정할 필요 없기 때문에 "모든 트래픽" 그대로 둡니다.


4.6. 인스턴스에서 보안 그룹 변경

다시 인스턴스로 돌아와서 "보안 그룹 변경" 버튼을 눌러줍니다.


방금 생성한 MySecurityGroup 을 추가해줍니다.

보안 그룹은 여러 개를 동시에 설정할 수 있기 때문에 기존에 설정된 launch-wizard-1 보안 그룹은 제거해줍니다.


4.7. 변경된 보안 그룹 확인

다시 인스턴스 설정을 보면 보안 그룹이 적용된 것을 볼 수 있습니다.

만약 제대로 보이지 않는다면 새로고침을 한번 해주세요.


5. EC2 인스턴스에 Spring Boot 서버 띄우기

배포 시스템 이런거 생략하고 진짜 단순하게 서버 띄우는 것만 확인해봅니다.

가장 간단한 방법은 두가지가 있는데 1번으로 진행하겠습니다.

  1. jar 파일을 빌드하여 EC2 복사 후 실행
  2. EC2 에서 프로젝트 git clone 후 실행

5.1. JDK 설치

# EC2 인스턴스
$ sudo apt-get update
$ sudo apt-get install openjdk-11-jdk

java -version 으로 명령어로 설치 여부를 확인할 수 있습니다.


5.2. Spring Boot 프로젝트 빌드

# 프로젝트 파일로 이동 후
$ gradle clean build

BUILD SUCCESSFUL in 5s
17 actionable tasks: 10 executed, 7 up-to-date


# 빌드 파일 복사
$ scp ./build/libs/api-0.0.1-SNAPSHOT.jar {호스트 이름}:/home/ubuntu

프로젝트를 빌드하면 ./build/libs 디렉토리에 jar 파일이 생성됩니다.

해당 파일을 EC2 서버로 복사합니다.

호스트 이름에는 ubuntu@{퍼블릭 IP} 또는 ubuntu@{퍼블릭 DNS} 가 들어가야 하는데 만약 ~/.ssh/config 에 호스트 이름을 등록해두었다면 간소화된 이름을 사용할 수 있습니다.

퍼블릭 IP (탄력적 IP) 또는 퍼블릭 DNS 를 그대로 사용한다면 키 페어 파일 (.pem) 이 명령어를 사용하는 위치에 존재해야 합니다.


5.3. EC2 인스턴스에서 실행

# EC2 인스턴스
$ nohup java -jar api-0.0.1-SNAPSHOT.jar &

nohup 명령어를 사용하면 로그를 nohup.out 파일에 남길 수 있습니다.


5.4. 퍼블릭 IP 또는 DNS 로 접근 확인

http://{탄력적 IP}로 접속하면 정상적으로 서버에 연결이 되는 걸 볼 수 있습니다.


Conclusion

지금까지 AWS EC2 인스턴스를 생성하고, 변하지 않는 탄력적 IP 를 할당해주고, 보안 그룹으로 방화벽을 설정한 후에 서버를 띄우는 것까지 진행해봤습니다.

다음에는 데이터베이스를 관리하는 RDS 인스턴스를 만들어보겠습니다.

'공부 > CI & CD' 카테고리의 다른 글

Github Actions CD: AWS EC2 에 Spring Boot 배포하기  (24) 2022.05.05
Github Actions CI: 자동 빌드 및 테스트 하기  (0) 2022.04.27
AWS 2편: RDS 생성 후 EC2 와 연동  (2) 2022.01.23
Docker 명령어  (0) 2022.01.05
kubectl 명령어  (0) 2021.12.23

1. docker run

$ docker run --name my-container -p 8080:8080 -d my-container-image
  • --name <name>: 도커 컨테이너 이름을 설정
  • -d: 백그라운드로 실행
  • -p <local port>:<container port>
    • 특정 포트로 연결
    • 로컬 머신의 8080 포트를 컨테이너 내부의 8080 포트와 매핑시킴
    • http://localhost:8080 으로 애플리케이션에 접근 가능

컨테이너 이미지로 컨테이너를 실행합니다.


2. docker exec

# my_continaer 컨테이너에 echo 명령어 실행
$ docker exec my_container echo "hello world!"

# my_container 컨테이너의 bash 접속
$ docker exec -it my_container bash
  • -i: 표준 입력(STDIN) 을 오픈 상태로 유지합니다. 셸에 명령어를 입력하기 위해 필요합니다.
  • -t: 의사 (PSEUDO) 터미널 (TTY) 을 할당합니다.

도커 컨테이너에 명령어를 전달하여 실행합니다.

i 옵션을 빼면 명령어를 입력할 수 없고, t 옵션을 빼면 명령어 프롬포트가 화면에 표시되지 않습니다.


3. docker images

$ docker images

도커 이미지 목록을 조회합니다.


4. docker rmi

$ docker rmi my-docker-image

도커 이미지를 삭제합니다.


Reference

1. gcloud (구글 클라우드) Cluster

클러스터는 Node 의 묶음입니다.

클러스터를 전환하면 kubectl get nodes 명령어의 정보도 달라집니다.

# 구글 클라우드 클러스터 리스트 조회
$ gcloud container clusters list

# 구글 클라우드의 클러스터를 kubeconfig 에 등록
$ gcloud container clusters get-credentials <클러스터 이름>

# 클러스터 생성 (노드 3개와 함께)
$ gcloud container clusters create <클러스터 이름> --num-nodes 3

# 구글 클라우드 클러스터 삭제
$ gcloud container clusters delete <클러스터 이름>

2. kubectl config

kubectl 명령어에 클러스터 정보, 인증 정보를 세팅해두고 편하게 사용할 수 있습니다.


2.1. Credential

# 인증 정보 조회
$ kubectl config get-users

# 인증 정보 생성
$ kubectl config set-credentials <이름> --옵션들

# 인증 정보 삭제
$ kubectl config unset users.<이름>

2.2. Cluster

# 클러스터 리스트 조회
$ kubectl config get-clusters

# 클러스터 생성
$ kubectl config set-cluster <클러스터 이름> --server <Host:Port>

# 클러스터 삭제
$ kubectl config unset clusters.<클러스터 이름>

2.3. Context

컨텍스트는 클러스터 정보와 인증 정보를 매핑해서 하나로 관리할 수 있게 해줍니다.

특정 클러스터를 사용하기 위해선 사용자 정보도 필요합니다.

만약 A 유저 정보를 사용하는 A 클러스터와 B 유저 정보를 사용하는 B 클러스터가 존재한다고 생각해 봅시다.

각 클러스터를 스위칭하기 위해선 유저 정보를 또 스위칭하는 과정을 매번 해야 합니다.

Context 는 클러스터와 유저 정보를 하나로 묶어서 컨텍스트를 스위칭 하는 것만으로도 클러스터 접근을 쉽게 할 수 있습니다.

# 컨텍스트 리스트 조회
$ kubectl config get-contexts

# 현재 컨텍스트 조회
$ kubectl config current-context

# 새로운 컨텍스트 생성
$ kubectl config set-context <컨텍스트 이름>

# 특정 컨텍스트 사용 (전환)
$ kubectl config use-context <컨텍스트 이름>

# 특정 컨텍스트 삭제
$ kubectl config unset contexts.<컨텍스트 이름>

2.4. 컨텍스트에 인증 정보와 클러스터를 매핑

# 1. username, password 로 인증 정보 생성
$ kubectl config set-credentials <Credential 이름> --username=<아이디> --password=<비밀번호>

# 2. token 으로 인증 정보 생성
$ kubectl config set-credentials <인증 정보 이름> --token=<토큰>

# 클러스터 정보 생성
$ kubectl config set-cluster <클러스터 이름> --server <Host:Port>

# 클러스터 정보와 인증 정보로 새로운 컨텍스트 생성
$ kubectl config set-context <Context 이름> --cluster=<Cluster 이름>  --user=<Credential 이름>

# 컨텍스트 사용
$ kubectl config use-context account-webapp-dev-context

3. 노드 (Node)

노드는 여러 개의 파드를 갖는 단위입니다.

$ kubectl get nodes

4. 파드 (Pod)

파드는 쿠버네티스의 가장 기본적인 배포 단위입니다.

파드는 하나 이상의 컨테이너를 포함하기 때문에 각 컨테이너를 각각 배포하지 않고 한번에 배포 가능합니다.

파드는 고유의 IP 와 Port 를 가지며, 파드 내의 컨테이너들은 localhost:<각 컨테이너 포트> 형식으로 서로 통신 가능합니다.

# 파드 조회 (-o wide 붙이면 좀더 자세한 정보 나옴) (pods = po)
$ kubectl get pods

# 좀더 자세히 조회
$ kubectl get po -o wide

# 파드 삭제
$ kubectl delete pod kubia

# 파드 전체 삭제
$ kubectl delete po --all

# 파드 상세한 설명 보기
$ kubectl describe pod <파드 이름>

# 로그 보기
$ kubectl logs <파드 이름>

# 여러 컨테이너를 포함한 파드인 경우에 컨테이너 이름 지정
$ kubectl logs <파드 이름> -c  <컨테이너 이름>

# 이전 컨테이너의 로그 확인
$ kubectl logs <파드 이름> --previous

# 파드의 환경변수 확인
$ kubectl exec <파드 이름> -- env

4.1. 새로운 파드 생성 (kubectl create vs apply)

  • create: 새로운 Object 생성. 이미 존재하면 에러
  • apply: 새로운 Obejct 생성. 이미 존재하면 변경된 부분만 반영

https://stackoverflow.com/questions/47369351/kubectl-apply-vs-kubectl-create


5. 서비스 (Service)

서비스는 외부에서 파드에 접근할 수 있게 해주는 로드 밸런서 역할을 합니다.

파드는 어떠한 이유에 의해 새로 생성되거나 지워질 수 있는데 이럴 때마다 파드의 IP 가 바뀌기 때문에 외부 클라이언트가 파드의 정확한 IP 를 추적하기 어렵습니다.

서비스는 여러 파드를 갖고 있으며 외부에서는 각 파드의 IP, Port 대신 서비스의 IP, Port 에 접근하면 서비스가 알아서 파드로 연결해줍니다.

# 서비스 생성
$ kubectl expose deployment kubia --type=LoadBalancer --port=8080 --name=kubia-http

# 서비스 조회
$ kubectl get svc

6. 레이블 (Label)

이름 그대로 라벨링의 역할을 합니다. (발음은 책마다 다른 것 같네요)

각 파드에 Key:Value 레이블을 설정해서 나중에 "레이블 셀렉터" 라는 걸로 특정 파드들을 동시에 제어할 수 있습니다.

주로 서비스가 목적에 따라 구분된 파드들을 동시에 실행하거나, 삭제하거나, 조회할 때 사용합니다.

# 레이블까지 조회
$ kubectl get po --show-labels

# 레이블 추가 (--overwrite 옵션을 추가하면 기존 레이블 수정)
$ kubectl label po <파드 이름> <레이블>=<설정>

# 특정 레이블들까지 같이 조회
$ kubectl get po -L creation_method=manual,env

# creation_method=manual 에 해당하는 파드 조회
$ kubectl get po -l creation_method=manual

# 값에 상관 없이 env 레이블을 갖고 있는 파드 조회
$ kubectl get po -l 'env'

# 위와 반대로 env 레이블을 갖고 있지 않은 파드 조회
$ kubectl get po -l '!env'

노드에도 파드처럼 레이블 세팅이 가능합니다.

# gpu=true 의 레이블을 노드에 세팅
$ kubectl label node <노드이름> gpu=true

6.1. 레이블 셀렉터 (Label Selector)

등록된 레이블 기반으로 특정 파드들을 그룹화 할 때 사용합니다.

쿼리처럼 사용 가능합니다.

  • creation_method!=manual: creation_method 레이블 갖고 있는 파드 중에 값이 manual 이 아닌 것
  • env in (prod,dev): env 레이블 값이 prod 또는 dev 로 되어 있는 파드
  • env notin (prod,dev): env 레이블 값이 prod, dev 가 아닌 파드

7. 어노테이션 (Annotation)

파드에 붙이는 주석 같은 개념입니다.

PR 번호, 작성자 등등.. 레이블처럼 키 값으로 사용할 수는 없지만 Description 역할을 합니다.

# 특정 파드에 어노테이션 추가
$ kubectl annotate pod <파드이름> mycompany.com/someannotation="foo bar"

8. 네임스페이스 (namespace)

클러스터를 논리적으로 구분하는 가상 클러스터라고 생각하면 됩니다.

Pod, Service 등을 네임스페이스 별로 생성하거나 관리할 수 있고 사용자의 권한 역시 별도로 부여할 수 있습니다.

처음 공부할 때는 레이블이랑 뭐가 다른건지 헷갈렸었는데..

레이블은 말 그대로 파드에 붙은 정보를 의미하는 거고 네임스페이스는 다음과 같은 일을 할 수 있습니다.

  • 특정 네임스페이스에 별도의 권한을 두어 파드, 서비스 등 접근 분리
  • dev, sandbox, prod 처럼 애플리케이션은 동일하지만 환경이 다른 경우 각 환경 별로 리소스를 다르게 할당 가능 (prod 환경은 좀더 많이 할당)

위에서 한번 언급했지만 네임스페이스는 논리적으로 구분하는 것이지 물리적인 구분은 아니라서 각 파드끼리 통신이 가능합니다.

# 클러스터에 있는 모든 네임스페이스를 나열
$ kubectl get ns

# 특정 네임스페이스의 파드 조회 (-n 으로 축약 가능)
$ kubectl get po --namespace <네임스페이스>

# 네임스페이스 생성 (yaml 파일로 만드는걸 추천)
$ kubectl create namespace <네임스페이스>

# 특정 네임스페이스에 파드 생성
$ kubectl apply -f kubia-manual.yaml -n <네임스페이스>

9. 레플리카셋 (ReplicaSet)

레플리카셋은 파드를 관리하는 역할을 합니다.

지정된 숫자보다 파드가 적으면 새로 생성하고, 더 많으면 삭제하는 등 항상 일정 갯수의 파드를 유지하도록 관리합니다.

파드에 문제가 생겨 갑자기 삭제되어도 레플리카셋이 부족한 파드를 새로 생성해주기 때문에 안정적으로 서비스를 운영할 수 있습니다.

과거에는 레플리케이션 컨트롤러를 사용했으나 이제 상위호환인 레플리카셋을 사용합니다.

# 레플리카셋 조회
$ kubectl get rs

10. 데몬셋 (DaemonSet)

데몬셋은 레플리카셋과 유사하지만 조금 다릅니다.

노드를 기준으로 파드 생성을 관리하며 노드 셀렉터를 사용해서 특정 노드들에만 파드를 생성시킬 수 있습니다.

# 데몬셋 조회
$ kubectl get ds

11. 잡 (Job)

잡은 지속적으로 실행되는 파드와 다르게 수행 후 종료되는 태스크입니다.

# 잡 조회
$ kubectl get jobs

Overview

보통 백그라운드에서 데이터를 실행할 때는 명령어 마지막에 & 를 붙여서 실행합니다.

$ java -jar my-app.jar &

하지만 위 명령어대로 하면 애플리케이션의 로그를 볼 수 없습니다.

따라서 nohup 을 사용합니다.

nohup 에 대한 자세한 설명과 & 와의 차이점은 https://joonyon.tistory.com/98 에 잘 정리되어 있습니다!


1. nohup 으로 백그라운드 실행

$ nohup java -jar my-app.jar &

[1] 97569
nohup: ignoring input and appending output to 'nohup.out'

2. nohup 로그 조회

이제 nohup.out 파일에 기록되는 로그를 확인할 수 있습니다.

# 로그 조회
$ cat nohup.out

# 로그 테일링
$ tail -f nohup.out

3. 백그라운드 Job 확인

백그라운드에서 실행되는 프로세스가 있는지 확인해볼 수 있습니다.

# 백그라운드에서 실행되는 프로세스 확인
$ bg
-bash: bg: job 1 already in background

# 출력
$ jobs
[1]+  Running                 nohup java -jar my-app.jar &

Reference

Issue

Using default tag: latest
The push refers to repository [docker.io/asdfaasdf/kubia]
6bb2a0932f1d: Preparing 
ab90d83fa34a: Preparing 
8ee318e54723: Preparing 
e6695624484e: Preparing 
da59b99bbd3b: Preparing 
5616a6292c16: Waiting 
f3ed6cb59ab0: Waiting 
654f45ecb7e3: Waiting 
2c40c66f7667: Waiting 
denied: requested access to the resource is denied

Docker Hub 에 이미지를 푸시하려려고 하는 데 아래와 같은 에러 로그가 나타났습니다.

로그를 보면 알 수 있듯이 권한 관련된 이슈인데 이유는 크게 두 가지가 있습니다.

  1. Docker Hub 로그인을 하지 않음
  2. Docker Hub 아이디와 태그된 이미지의 이름이 일치하지 않음

Solution

우선 Docker Hub 에 로그인합니다.

$ docker login           
Login with your Docker ID to push and pull images from Docker Hub. If you dont have a Docker ID, head over to https://hub.docker.com to create one.
Username: bcp0109
Password: 
Login Succeeded

Docker Tag 를 Docker Hub ID 와 동일하게 생성합니다.

$ docker tag kubia bcp0109/kubia

이미지를 푸시합니다.

$ docker push bcp0109/kubia     
Using default tag: latest
The push refers to repository [docker.io/bcp0109/kubia]
6bb2a0932f1d: Pushed 
ab90d83fa34a: Pushed 
8ee318e54723: Pushed 
e6695624484e: Pushed 
da59b99bbd3b: Pushed 
5616a6292c16: Pushed 
f3ed6cb59ab0: Pushed 
654f45ecb7e3: Pushed 
2c40c66f7667: Pushed 

Docker Hub 에 이미지가 올라간 걸 확인할 수 있습니다.

Issue

웹 페이지를 만들 때 고려해야 할 점 중 하나가 시각 장애인을 위한 웹 접근성입니다. (웹 접근성 포스트)

웹 접근성을 적용할 때 iOS 웹뷰에서 실시간으로 변하는 타이머에 초점이 잡히지 않는 이슈가 있었습니다.

처음에는 aria-live 속성을 적용해서 실시간 보이스 낭독을 기대했지만 문장을 다 읽기 전에 시간이 계속 바뀌어서 말이 끊기는 이슈가 있었습니다.

그래서 처음 초점 잡혔을 때의 시간만 안내하길 기대하면서 role="timer" 를 적용했지만 아예 초점이 잡히지 않았습니다.


Solution

답은 role="text" 를 적용하는 거였습니다.

role="text" 는 WAI-ARIA 공식 홈페이지에서도 설명이 없는 걸로 보아 정식 role 은 아닌 것 같습니다.

다만 이 role 만 적용하면 어떤 역할인지 보이스 안내만으론 알기 힘들기 때문에 aria-describedby 속성까지 추가하는 것을 추천합니다.

<span class="screen_out" id="timer_label" aria-hidden="true">남은 시간</span>
<div role="text" aria-describedby="timer_label">10:00</div>

Overview

HTML 페이지를 만들 때 고려해야 하는 것 중 하나가 웹 접근성입니다.

웹 접근성이란 시각장애인들이 웹 페이지를 원활하게 이용할 수 있도록 알려주는 가이드라인이라고 생각하면 됩니다.

대표적으로 input 과 label 이 있는데요.

일반 사람들은 별다른 설명 없이 input, button, a 등등의 태그만 있어도 어느정도 이용할 수 있지만 시각장애인들은 직접 클릭할 수도 없이 탭 같은 걸로 각 요소를 이동하는 수밖에 없습니다.

따라서 탭을 눌렀을 때 초점이 어느 순서로 이동하는지, 초점이 잡혔을 때의 안내 메시지가 제대로 나오는지 여부는 굉장히 중요합니다.

이런 설정들을 HTML native 요소만으로는 처리하기 어렵기 때문에 W3C 는 WAI-ARIA 라는 걸 정의했습니다.


1. WAI-ARIA

WAI-ARIA (Web Accessibility Initiative – Accessible Rich Internet Applications) 는 W3C 에서 정의한 기술로 웹 접근성을 위해 지원되는 여러 가지 특성들을 의미합니다.

일반 사용자가 보기에 정상인 화면들도 HTML 요소에 따라서 스크린 리더 등의 보조기기에서 제대로 읽히지 않을 수 있습니다.

WAI-ARIA 는 이를 개선하기 위해 웹 애플리케이션에 역할(Role), 속성(Property), 상태(State) 정보를 추가할 수 있습니다.


1.1. Role

Role 은 HTML 요소의 역할을 정의합니다.

일반적으로는 HTML native 요소만으로 이를 처리하는게 가장 이상적입니다.

버튼은 button, 링크는 a, 체크박스는 input checkbox 등등 이미 존재하는 요소들로도 충분히 표현할 수 있습니다.

하지만 이미지에 버튼 클릭 이벤트를 준다던가 좀더 세세하고 다양한 설정이 하고 싶을 때는 native 요소만으로 부족할 수 있습니다.

이럴 때 role 을 사용해서 해당 요소들의 역할을 명시합니다.

Role 의 종류는 W3C WAI ARIA 의 5.3 Categorization of Roles 에서 확인할 수 있습니다.

<!-- role example -->
<li role="menuitem">Open file…</li>

1.2. State and Property

속성 (Property) 는 해당 요소의 특징이나 상황을 정의하며 aria- 라는 접두사를 사용합니다.

상태 (State) 는 요소의 현재 상태를 나타냅니다.

<!-- property state example 1 -->
<li role="checkbox" aria-checked="true">체크박스 아이템</li>

<!-- property state example 2 -->
<div role="alert" aria-live="assertive">올바르지 않은 입력입니다.</div>

2. ARIA 속성들

공부한 속성들에 대해서 정리합니다.

앞으로도 생길때마다 조금씩 추가할 예정입니다.


2.1. role="checkbox" aria-checked="true"

<span role="checkbox" aria-checked="false" tabindex="0" aria-labelledby="chk1-label"></span> 
<label id="chk1-label">Remember my preferences</label>

<!-- native 요소로 사용할 수 있는 경우 -->
<input type="checkbox" id="chk1-label">
<label for="chk1-label">Remember my preferences</label>

특정 요소에 체크박스 역할을 부여합니다.

aria-checked 를 사용해서 체크됨, 체크안됨 여부를 판단할 수 있습니다.

focus 되지 않는 요소일 경우 tabindex 를 사용하기도 합니다.

가능하면 HTML native checkbox 를 사용하는게 권장되지만 사용할 수 없는 경우에 ARIA 속성을 사용합니다.


2.2. aria-label, aria-labelledby, aria-describedby

특정 요소를 설명하는 데 사용되는 ARIA 속성들입니다.

비슷해보이지만 조금씩 차이가 있습니다.


<button aria-label="menu" class="button"></button>

aria-label 은 우리가 흔히 알고 있는 Label 목적을 위한 속성입니다.

특정 요소에 대한 설명을 그대로 적습니다.

텍스트 대신 그래픽을 사용하는 경우처럼 추가 설명이 필요한 경우 사용할 수 있습니다.


<span id="rg-label">음료수 옵션</span>
<div role="radiogroup" aria-labelledby="rg-label"></div>

aria-labelledbyaria-label 과 비슷하지만 조금 다릅니다.

aria-label 은 어떤 요소에 대한 설명을 직접 적는 반면, aria-labelledby 은 다른 요소의 ID 값을 매칭시킵니다.

aria-labelledby 은 Label 자체를 재정의하기 때문에 다른 모든 Label 속성들, aria-label 또는 HTML native label 과 함께 쓰여도 항상 aria-labelledby 을 우선합니다.


<label for="pw">Password:</label>
<input type="password" id="pw" aria-describedby="pw-help">
<div id="pw-help">비밀번호는 12 자 이상으로 이루어져야 합니다</div>

aria-describedbyaria-labelledby 와 같은 방식으로 다른 요소의 ID 를 매칭하여 현재 요소에 대한 설명을 나타냅니다.

둘의 사용법과 쓰임새가 굉장히 비슷하여 헷갈릴 수도 있지만 다른 목적으로 사용됩니다.

MDN Web Docs - Using the aria-describedby attribute 에는 다음과 같이 나와있습니다.

This is very similar to aria-labelledby: a label describes the essence of an object, while a description provides more information that the user might need.

label 관련 속성들과의 가장 큰 차이점은 Label 이 요소의 필수 설명이라면 aria-describedby 은 어디까지나 부연 설명이라는 점입니다.

그래서 위 예시 코드에서처럼 label 태그와 함께 사용할 수 있습니다.


2.3. aria-live

JavaScript 를 사용하면 페이지를 새로 로드하지 않고 일부만 동적으로 변경하는게 가능합니다.

동적인 변경은 페이지를 볼 수 있는 사용자들은 알 수 있지만, 시각 장애인들은 알아채기 어렵습니다.

그래서 이런 동적인 (실시간) 변경들을 알려줄 수 있는 aria-live 라는 속성이 제공됩니다.


<input type="text" name="email">
<div role="alert" aria-live="assertive">이메일 형식이 올바르지 않습니다.</div>

회원가입 또는 로그인 페이지에서 사용하는 Email Input 은 실시간으로 Email 형식 검사를 하여 올바른 형식이 아니면 빨간색 에러 메시지를 사용자에게 노출합니다.

페이지를 볼 수 있는 사용자는 그 정보를 실시간으로 확인할 수 있지만 시각장애인은 에러 메시지가 노출되었다는 사실을 모를 수 있습니다.

이럴 때 role="alert" 로 경고 역할을 갖고 있는 요소를 만들고 실시간 변화를 감지해서 알려주는 aria-live 속성을 추가할 수 있습니다.

aria-live 의 값으로는 다음 값들이 올 수 있습니다.

  • off (default)
  • polite : 현재 진행중인 음성 또는 타이핑 이후에 알림
  • assertive : 현재 진행중인 알림을 중단하고 즉시 알림

assertive 값은 사용자의 현재 작업을 방해할 수 있기 때문에 신중하게 적용해야 합니다.


2.4. role="alert"

alert 역할은 사용자에게 동적인 변화를 알려줄 때 사용합니다.

스크린 리더는 alert 역할이 붙은 요소가 업데이트 되면 바로 읽기 시작합니다.

role="alert" 로 설정한다는 건 aria-live="assertive" aria-atomic="true" 와 동일합니다.


2.5. role="timer"

현재 요소가 timer 로 사용되고 있다는 걸 의미합니다.

초를 계속 세주려면 aria-live 속성을 켜주면 되지만.. 1초마다 계속 컨텐츠가 갱신되기 때문에 알림이 부자연스럽게 계속 끊기는 이슈가 존재합니다.

만약 timer 에 초점이 잡혔을 때의 남은 시간만 읽어주길 바란다면 role="timer" 설정만 추가해주면 됩니다.


Reference

'공부 > Web' 카테고리의 다른 글

JWT (Json Web Token)  (3) 2021.05.22
URI 란?  (0) 2020.08.26

에러 로그

$ yarn start  
yarn run v1.22.17
$ react-scripts start
node:internal/modules/cjs/loader:488
      throw e;
      ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/tokenize' is not defined by "exports" in /Users/Documents/my/projects/practice-codes/react-js-hello/node_modules/postcss-safe-parser/node_modules/postcss/package.json
    at new NodeError (node:internal/errors:371:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:416:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:669:3)
    at resolveExports (node:internal/modules/cjs/loader:482:36)
    at Function.Module._findPath (node:internal/modules/cjs/loader:522:31)
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/Users/Documents/my/projects/practice-codes/react-js-hello/node_modules/postcss-safe-parser/lib/safe-parser.js:1:17) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Node.js v17.0.1
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

원인

Mac OS 를 BigSur 로 업그레이드 후 연습중이던 React 프로젝트를 클론받아 실행했더니 에러가 발생했습니다.

복잡한 프로젝트도 아니어서 당황하던 중 검색해보니 node 17 버전을 사용해서 그렇다는 글을 봤습니다.

현재 제 노드 버전을 확인해보니 v17.0.1 을 사용하고 있었습니다.


해결

nvm (Node Version Manager) 을 설치한 후에 노드 버전을 다운그레이드 해서 해결했습니다.

처음에 brew install nvm 으로 설치했는데 잘 안되어서 nvm Github 을 보고 다시 설치했습니다.

# nvm 설치
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

설치 후에 ~/.zshrc 을 열어 맨 아래에 다음 내용을 붙여넣습니다.

기본 터미널을 bash 로 사용하고 있다면 ~/.bash_profile 에 추가하면 됩니다.

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

마지막으로 ~/.zshrc 를 갱신한 뒤 LTS 버전을 설치하고 다시 실행해보면 정상적으로 동작합니다.

# nvm command 가 안뜨면 갱신
$ source ~/.zshrc

# nvm 설치 확인
$ nvm -v
0.39.0

# node lts 버전 설치
$ nvm install --lts

# 16 버전이 설치됨
$ node -v
v16.13.0

Reference

+ Recent posts