이전 내용
[Cloud] aws: Network LoadBalancer (feat. AWS CLI + yaml 파일)
이전 내용 [Cloud] aws: eksctl, kubectl 주요 명령어이전 내용 [Cloud] aws : aws cli 로 쿠버네티스 클러스터 만들기이전 내용 [Cloud] aws : aws console로 쿠버네티스 클러스터 만들기이전 내용 [Cloud] aws: ECR(Ama
puppy-foot-it.tistory.com
Small Project: 3 Tier 개요
직접 만든 login 페이지(jsp, css)와 회원가입 페이지(jsp, css), 그리고 DB(RDS)를 가지고 3 티어 서비스를 구현해본다.
[3 티어 서비스 관련 글]
[Cloud] 프록시 패스: 도메인 톰캣 화면으로 바꾸기
이전 내용 [aws] 프라이빗 서브넷 생성하기(feat. NAT 게이트웨이)이전 내용 [aws] Bastion Hub 만들기이전 내용 Bastion Hub 란? [Bastion Hub 개요]Bastion Hub는 클라우드 환경에서 보안과 관리의 편의성을
puppy-foot-it.tistory.com
◆ 3 티어 서비스 간단 설명
3티어 아키텍처는 사용자 인터페이스, 비즈니스 로직, 데이터 저장소를 서로 독립적으로 구성하여 효율적이고 확장 가능한 웹 애플리케이션을 지원하는 구조로, 여기서는
- 클라이언트 티어: Nginx - 오픈 소스 웹 서버 소프트웨어로, 주로 HTTP 및 reverse proxy 서버로 사용
- 어플리케이션 티어: Tomcat - 자바 서블릿과 JSP(JavaServer Pages)를 실행할 수 있는 웹 애플리케이션 서버
- 데이터 티어: MariaDB (AWS RDS) - 오픈 소스 관계형 데이터베이스 관리 시스템(RDBMS)
로 구성된 3티어 서비스를 구현해 보려 한다.
◆ 각 티어의 역할 설명
1. 클라이언트 티어: Nginx
- 정적 콘텐츠 제공: 이미지, CSS, JavaScript 파일 등 정적 파일을 클라이언트에게 직접 제공.
- 로드 밸런싱: 여러 애플리케이션 서버로 요청을 분산시켜 서버의 부하를 균일하게 유지.
- 리버스 프록시: 클라이언트의 요청을 내부 애플리케이션 서버(Tomcat 등)로 전달하고, 그 응답을 클라이언트에게 반환.
▶ 사용자와의 인터페이스를 담당한다. 사용자가 브라우저에서 웹 페이지를 요청하면 이를 처리하고, 결과를 사용자에게 반환한다. 은행으로 비유하면 고객의 통장으로, 통장을 통해 잔액을 확인하거나 거래 내역을 기록한다. 이는 고객이 자신의 금융 정보에 접근하고 요청하는 방식으로, 3티어 아키텍처의 클라이언트 역할을 수행한다.
2. 어플리케이션 티어: Tomcat
- 비즈니스 로직 처리: 클라이언트의 요청을 처리하며, 필요한 비즈니스 로직을 실행.
- 동적 웹 콘텐츠 관리: JSP 및 서블릿을 통해 동적인 웹 콘텐츠를 생성하고, 사용자의 요청에 적절히 응답.
- 세션 관리: 사용자의 세션을 관리하여 상태를 유지하고, 개인화된 서비스를 제공.
▶ 웹 서버와 데이터베이스 서버 간의 중개자 역할을 한다. 사용자 요청을 처리하고, 비즈니스 로직을 실행하며, 데이터베이스와의 상호작용을 담당한다. 은행으로 비유하면 은행원으로, 은행원은 고객의 요청을 처리하고 필요한 서비스를 제공한다. 고객이 돈을 찾거나 예금을 요청할 때, 은행원은 관련 정보를 확인하고 필요한 작업을 수행한다. 이는 어플리케이션 서버가 비즈니스 로직을 처리하는 것과 유사한 역할을 한다.
3. 데이터 티어: MariaDB (AWS RDS)
- 데이터 저장 및 관리: 사용자 및 애플리케이션에서 생성된 데이터를 안전하게 저장하고, 필요할 때 신속하게 접근할 수 있도록 관리.
- SQL 쿼리 처리: 데이터에 대한 읽기 및 쓰기 작업을 수행하는 SQL 쿼리 처리.
- 데이터 백업 및 복구: AWS RDS 기능을 통해 데이터의 자동 백업과 복구를 지원하여 데이터의 안전성 향상.
▶ 데이터를 저장하고 관리하는 역할을 수행한다. 웹 애플리케이션에서 필요한 정보를 제공하며, 데이터를 안전하게 관리한다. 은행으로 비유하면 금고로, 금고는 고객의 자산을 안전하게 보관하고 관리하는 장소다. 은행원이 고객의 요청에 따라 금고에서 돈을 꺼내거나 예금을 추가하는 등 정보에 대한 접근을 제공한다. 이는 데이터 티어가 정보를 안전하게 저장하고 관리하는 방식과 연결된다.
3티어 아키텍처는 각 부분이 전문화되어 서로 협력하면서 전체 시스템의 성능과 효율성을 극대화하는 구조로, 각 티어는 독립적으로 개발 및 유지 관리가 가능하여, 시스템의 확장성과 유연성을 높여준다.
대략적인 진행 순서
대략적인 진행 순서는 아래와 같다.
※ 일부 작업은 설명 생략
◆ 사전 작업
eksctl 명령을 이용해서 AWS EKS 클러스터를 생성하기 전 요구되는 선행 작업
※ 기본적으로 VPC, 퍼블릭 서브넷, 프라이빗 서브넷, IGW, NAT GW 등은 생성되어 있다고 가정
1. ubuntu 서버 필요: AWS EC2 인스턴스 생성 (필자의 경우 인스턴스명: aws-managed-server)
2. ubuntu 서버에 AWS CLI 설치 (eksctl, kubectl 포함)
3. XShell (또는 WSL)에서 ubuntu 서버 연결
4. XShell 에서 aws 관리 위한 CLI 로그인 (aws configure) - aws 액세스 키, 시크릿 키 필요
5. aws ECR에 컨테이너 이미지 등록 (이 글에서는 과정 중에 생성 예정)
6. EKS에 클러스터 생성
7. ubuntu 서버에서 루트 계정으로 접근 후 nginxweb, tomcatwas 폴더 생성
8. nginxweb 과 tomcatwas에 필요한 파일 옮겨넣기 * 필요한 파일은 하단의 directory structure 확인
9. AWS Console에서 RDS 및 데이터베이스 생성
◆ 본격 작업
1. root 접속
sudo -i
2. 데이터베이스 연결 및 테이블 및 임시 정보 입력
※ RDS 및 데이터베이스가 연결되어 있는 상태라면 데이터베이스, 테이블 생성 및 데이터 INSERT 부분 생략
# DB 로그인
mysql -u admin -p -h AWS RDS 엔드포인트
# 비밀번호 입력
# 데이터베이스 생성
CREATE DATABASE mydb;
# 데이터베이스 이용
USE mydb;
# 테이블 생성
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE
);
# 테이블에 데이터 넣기
INSERT INTO users (username, password, email)
VALUES
('user1', 'password123', 'user1@example.com'),
('user2', 'securepass456', 'user2@example.com');
# 해당 테이블 전체 조회
SELECT * FROM users;
3. tomcatwas 폴더에서 빌드하기
# tomcatwas로 이동
cd tomcatwas
# 도커 빌드
docker build -t tomcatwas:v1.0 .
4. 실행시켜서 잘 뜨는지 확인하기
docker run -d --name tc -p 8080:8080 tomcatwas:v1.0
# 컨테이너 생성되고, ubuntu 서버 인스턴스의 퍼블릭 IPv4주소:8080 인터넷 주소창에 복사 붙여넣기
# Login.jsp 화면 출력되면 도커 빌드업 성공
5. AWS ECR에 이미지 올리기
# AWS ECR - 프라이빗 레포지토리 - tomcat_was:v1.0 의 푸시 명령 복사 붙여넣기
# 단, 이미지 태그 'latest'를 버전에 맞게 바꿔줘야 함 (예. :v1.0)
docker tag
docker push
6. root 폴더로 이동 backend-tomcat.yaml 파일 실행 pod 생성
# 루트로 이동
sudo -i
# backend yaml 실행 (파드 생성 등)
kubectl apply -f backend-tomcat.yaml
7. nginxweb 폴더로 이동 빌드하기
# nginxweb 으로 이동
cd nginxweb
# 도커 빌드
docker build -t nginxweb:v1.0 .
8. AWS ECR에 이미지 올리기
# AWS ECR - 프라이빗 레포지토리 - nginxweb:v1.0 의 푸시 명령 복사 붙여넣기
# 단, 이미지 태그 'latest'를 버전에 맞게 바꿔줘야 함 (예. :v1.0)
docker tag
docker push
9. root 폴더로 이동
sudo -i
10. frontend-nginx.yaml 파일 실행
kubectl apply -f frontend-nginx.yaml
11. aws console에서 네트워크 로드밸런스 활성되면 로드밸런스 주소로 들어가서 확인
12. aws console에서 Route 53 각 호스팅 영역 접속
13. 레코드 생성 -> 트래픽 라우픽 대상: Network Load Balancer에 대한 별칭, 아시아 태평양(서울), 로드밸런스 주소 선택 -> 저장
14. 호스팅 url 접속 후 확인
(자세한 내용은 이전 포스팅에 나와있다.)
DB 연동도 잘 되는지 확인해 본다.
먼저 회원가입 페이지에서 새로운 계정을 등록하고
[REGISTER] (가입)을 누르면, 가입이 성공했다는 알람이 뜨고,
연동된 DB에도 새로운 데이터가 생성되는 것을 확인할 수 있다.
로그인 페이지에서 가입한 정보로 로그인도 성공!
Directory Structure
'firstgroup' 이라는 클러스터 안에 nginxweb 폴더 / tomcatwas 폴더 / yaml 파일들이 있고, 또 각 폴더 안에는 다양한 파일들이 존재한다.
[yaml 파일]
- cluster.yaml: 클러스터를 생성하는 데 필요한 설정과 스크립트가 담긴 파일이다. 이 파일은 여러 개의 노드를 포함할 수 있는 클러스터 환경을 정의하고, 클러스터의 기본적인 인프라를 구축하는 데 사용한다. ▶ 전체 인프라 환경 구축
- fronted-nginx.yaml: 클라이언트 티어를 구성하는 스크립트가 담긴 파일로, Nginx 웹 서버 설정을 포함한다. 이 파일은 정적 콘텐츠를 제공하고, 클라이언트의 요청을 처리하는 역할을 수행하는 Nginx 인스턴스를 설정한다. ▶ 클라이언트 요청을 처리하는 웹 서버 설정
- backed-tomcat.yaml: 어플리케이션 티어를 구성하는 스크립트가 담긴 파일로, Tomcat 서버 설정을 포함한다. 이 파일은 비즈니스 로직을 처리하고, 클라이언트의 요청에 따라 동적인 웹 콘텐츠를 생성하는 Tomcat 인스턴스를 설정한다. ▶ 비즈니스 로직을 수행하는 애플리케이션 서버 설정
[tomcatwas 폴더]
- web.xml; tomcat과 같은 서블릿 컨테이너가 애플리케이션을 올바르게 배포하고 실행하는 데 필요한 정보를 제공한다. ▶ 서블릿 및 JSP의 매핑 정보, 필터 및 리스너 설정, 보안 제약조건, 초기화 매개변수 (애플리케이션의 배치 및 구성 정보)
- context.xml: 애플리케이션의 다양한 설정을 관리할 수 있으며, Tomcat이 해당 웹 애플리케이션을 사용할 때 필요한 리소스를 찾을 수 있도록 도와준다. ▶ 데이터 소스 설정, 환경 변수 정의 (리소스 설정에 관한 정보)
- dockerfile: 컨테이너화를 통해 애플리케이션의 배포 및 관리를 자동화하고 일관되게 만들어 준다. ▶ 베이스 이미지 선택, 애플리케이션 소스 코드 복사, 필요한 라이브러리 및 패키지 설치, 서버 실행 명령어 설정 (컨테이너화를 위한 설정)
- login.jsp: 사용자가 로그인 정보를 입력할 수 있는 웹 페이지를 구성한다. ▶ 로그인 과정에서 서버로 전송된 정보는 백엔드에서 처리된다.
- register.jsp: 사용자가 새로운 계정을 등록할 수 있도록 하는 웹 페이지 ▶ 회원가입 과정에서 입력된 데이터는 서버에서 처리되어 새로운 계정이 생성
- css 폴더 - login.jsp와 register.jsp 를 꾸며줄 역할을 하는 style.css
[nginxweb 폴더]
- default.conf: Nginx의 기본 서버 구성 파일로, Nginx 서버의 동작 방식을 정의 ▶서버 블록, 리스닝 포트, 루트 디렉토리, 프록시 설정, 정적 파일 제공 등
- dockerfile: Nginx를 컨테이너화하기 위한 설정 파일로, 사용할 이미지, 구성 파일 복사, 시작 명령어 등을 정의 ▶ Nginx 웹 서버의 손쉬운 배포 및 관리 가능
3티어 아키텍처 &
도커, 쿠버네티스, EKS
3티어 아키텍처를 도커(Docker), 쿠버네티스(Kubernetes), 그리고 AWS EKS(Elastic Kubernetes Service)와 함께 활용하는 것은 여러 가지 장점을 제공한다. 각각의 기술이 어떻게 3티어 아키텍처의 효율성 및 관리성을 향상시킬 수 있을까?
1. 도커(Docker)
- 컨테이너화: 도커를 사용하면 각 티어(클라이언트, 어플리케이션, 데이터)를 서로 독립적인 컨테이너로 패키징할 수 있다. 이를 통해 애플리케이션의 각 구성 요소를 격리하므로, 이식성과 확장성이 높아진다.
- 일관된 환경: 모든 개발자와 서버가 동일한 환경에서 실행되므로, "작동하지 않는 경우" 문제를 줄일 수 있다.
- 빠른 배포: 컨테이너를 사용하는 덕분에 애플리케이션의 배포 및 업데이트가 신속하게 이루어질 수 있다. 새로운 버전을 더 쉽게 배포하고 롤백할 수 있는 기능이 제공된다.
2. 쿠버네티스(Kubernetes)
- 자동화된 관리: 쿠버네티스는 컨테이너의 배포, 운영, 확장 및 관리 기능을 자동화하여 효율성을 높인다. 이러한 자동화는 리소스 할당 및 스케일링을 지원하며, 3티어 아키텍처의 각 구성 요소의 수요에 따라 자동으로 조정 가능한 환경을 제공한다.
- 서비스 발견 및 로드 밸런싱: 쿠버네티스를 사용하면 클러스터 내의 컨테이너 간의 네트워크 통신을 간단하게 설정할 수 있다. 클라이언트 티어에서 어플리케이션 티어로의 요청이 쉽고 효율적으로 처리될 수 있다.
- 복원력: 쿠버네티스는 컨테이너가 실패할 경우 자동으로 다른 인스턴스를 생성하여 서비스 지속성을 보장한다. 이를 통해 애플리케이션의 가용성을 높일 수 있다.
3. AWS EKS (Elastic Kubernetes Service)
- 관리형 서비스: EKS는 AWS에서 제공하는 관리형 쿠버네티스 서비스로, 사용자가 직접 클러스터를 설치하고 관리할 필요가 없다. AWS가 클러스터의 운영과 보안을 책임지므로, 개발자는 애플리케이션 개발에만 집중할 수 있다.
- 확장성과 유연성: EKS는 AWS의 고급 인프라를 활용하여 자동으로 리소스를 조정할 수 있다. 필요에 따라 클러스터를 스케일 업하거나 스케일 다운하여 트래픽 변화에 유연하게 대응할 수 있다.
- 보안 및 컴플라이언스: EKS는 AWS의 보안 기능을 통해 클러스터와 애플리케이션의 보안을 강화한다. IAM(Identity and Access Management)과 통합하여 세밀한 접근 제어를 할 수 있다.
- 통합 생태계: EKS는 AWS의 다른 서비스(예: RDS, S3 등)와 통합되어, 데이터베이스와 스토리지 자원의 관리가 용이하다. 이를 통해 3티어 아키텍처 전반에 걸쳐 효율적인 데이터 통신 및 저장이 가능하다.
주요 스크립트, 코드 등
1. cluster.yaml: 클러스터 생성
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: groupfirst-cluster # 클러스터 이름
region: ap-northeast-2
vpc:
subnets:
private:
ap-northeast-2a: { id: subnet- } # 프라이빗 서브넷 2a id
ap-northeast-2c: { id: subnet- } # 프라이빗 서브넷 2c id
nodeGroups:
- name: VEC-PRD-NG-worker1
labels: { role: workers }
instanceType: t3.micro
desiredCapacity: 1
privateNetworking: true
subnets:
- subnet- # 프라이빗 서브넷 2a id
- name: VEC-PRD-NG-worker2
labels: { role: workers }
instanceType: t3.micro
desiredCapacity: 1
privateNetworking: true
subnets:
- subnet- # 프라이빗 서브넷 2c id
iam:
withAddonPolicies:
imageBuilder: true
2. backend-tomcat.yaml: 애플리케이션 서버 설정
▶ Kubernetes에서 Tomcat 애플리케이션을 효과적으로 배포하고 관리하기 위한 설정을 포함.
네임스페이스를 정의하여 리소스를 격리하고, Deployment를 통해 애플리케이션을 운영하며, Service로 네트워크 접근을 제공하고, HPA를 통해 자동으로 스케일 조정할 수 있는 기능을 제공.
apiVersion: v1
kind: Namespace
metadata:
name: prj-tomcat # 새로운 네임스페이스 생성
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: prj-tomcat
name: tomcatwas
labels:
app: tomcatwas
spec:
replicas: 2
selector:
matchLabels:
app: tomcatwas
template:
metadata:
labels:
app: tomcatwas
spec:
containers:
- name: tomcat
image: tomcat_was:v1.0 의 ECR URI
resources:
requests:
cpu: "30m"
limits:
cpu: "50m"
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-svc
namespace: prj-tomcat
spec:
selector:
app: tomcatwas
ports:
- protocol: TCP
port: 8080 # Service가 노출하는 포트
targetPort: 8080 # Pod 내부에서 사용하는 포트
type: ClusterIP
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: tomcat-hpa
namespace: prj-tomcat
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: tomcatwas
minReplicas: 2
maxReplicas: 6
targetCPUUtilizationPercentage: 50
3. frontend-nginx.yaml: Nginx 웹 서버를 배포하고 관리
▶ 네임스페이스를 정의하여 리소스를 분리하고, Deployment를 통해 Nginx 서버를 운영하며, Service로 외부 접근을 제공하고, HPA를 통해 자동으로 스케일 조정할 수 있는 기능을 제공
apiVersion: v1
kind: Namespace
metadata:
name: prj-nginx # 새로운 네임스페이스 생성
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: prj-nginx
name: deploy-nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-prj
image: nginxweb:v1.0 의 ECR URI
imagePullPolicy: Always
ports:
- containerPort: 80
protocol: TCP
resources:
limits:
cpu: 500m
requests:
cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
app: nginx
namespace: prj-nginx
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/target-type: instance
service.beta.kubernetes.io/subnets: 프라이빗 서브넷2a id, 프라이빗 서브넷 2c id
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: ACM 인증서 ARN
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30011
- name: https
protocol: TCP
port: 443
targetPort: 80
nodePort: 30012
type: LoadBalancer
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
namespace: prj-nginx
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: deploy-nginx
minReplicas: 2
maxReplicas: 6
targetCPUUtilizationPercentage: 50
4. context.xml: 리소스 설정에 관한 정보
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Resource name="jdbc/mydb"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="30"
maxWaitMillis="10000"
username="RDS 계정명"
password="RDS 비밀번호"
driverClassName="org.mariadb.jdbc.Driver"
url="jdbc:mariadb://RDS 엔드포인트:3306/mydb"/>
</Context>
5. dockerfile (tomcatwas 용): 애플리케이션의 배포 및 관리를 자동화하기 위한 작업
FROM alpine:latest AS builder
# 1. OpenJDK, curl, tar 설치
RUN apk update && \
apk add --no-cache openjdk8 curl tar
ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk
ENV CATALINA_HOME=/usr/local/tomcat
ENV TOMCAT_VERSION=9.0.100
# 2. Tomcat 다운로드 및 설정
RUN curl -O https://dlcdn.apache.org/tomcat/tomcat-9/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz && \
mkdir -p $CATALINA_HOME && \
tar -xvf apache-tomcat-${TOMCAT_VERSION}.tar.gz -C $CATALINA_HOME --strip-components=1 && \
rm apache-tomcat-${TOMCAT_VERSION}.tar.gz && \
rm -rf $CATALINA_HOME/webapps/* && \
mkdir -p $CATALINA_HOME/webapps/ROOT/
# 3. MariaDB JDBC 드라이버 다운로드
RUN curl -O https://downloads.mariadb.com/Connectors/java/connector-java-3.2.0/mariadb-java-client-3.2.0.jar && \
mv mariadb-java-client-3.2.0.jar $CATALINA_HOME/lib/
# 4. Sample index.html 추가
COPY ./login.jsp $CATALINA_HOME/webapps/ROOT/
COPY ./register.jsp $CATALINA_HOME/webapps/ROOT/
COPY ./css $CATALINA_HOME/webapps/ROOT/
COPY ./context.xml $CATALINA_HOME/conf/context.xml
COPY ./web.xml $CATALINA_HOME/webapps/ROOT/WEB-INF/web.xml
FROM alpine:latest
# 5. OpenJDK 설치
RUN apk add --no-cache openjdk8-jre-base && \
rm -rf /var/cache/apk/*
ENV JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk
ENV CATALINA_HOME=/usr/local/tomcat
ENV PATH=$JAVA_HOME/bin:$CATALINA_HOME/bin:$PATH
# 6. Build 단계에서 Tomcat 복사
COPY --from=builder $CATALINA_HOME $CATALINA_HOME
EXPOSE 8080
# 7. Tomcat 실행
CMD ["sh","-c","${CATALINA_HOME}/bin/catalina.sh run"]
6. login.jsp
<%@ page import="javax.naming.*, javax.sql.*, java.sql.*" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Login</title>
</head>
<body>
<div class="login">
<h1>Login</h1>
<form action="login.jsp" method="post">
<input type="email" name="email" placeholder="User-email" required="required" />
<input type="password" name="password" placeholder="Password" required="required" />
<button type="submit" class="btn btn-primary btn-block btn-large">SIGN IN</button>
</form>
<a href="register.jsp" class="btn btn-primary btn-block btn-large mt10">SIGN UP</a>
</div>
<%
String email = request.getParameter("email");
String password = request.getParameter("password");
String msg = null;
if (email != null && password != null) {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource) envCtx.lookup("jdbc/mydb");
con = ds.getConnection();
String query = "SELECT * FROM users WHERE email = ? AND password = ?";
pstmt = con.prepareStatement(query);
pstmt.setString(1, email);
pstmt.setString(2, password);
rs = pstmt.executeQuery();
if (rs.next()) {
msg = "Login Successful!";
} else {
msg = "Login Failed. Invalid Email or Password.";
}
} catch (Exception e) {
msg = "Error: " + e.getMessage();
e.printStackTrace();
} finally {
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (con != null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (msg != null) {
out.println("<h3 id='l_msg'>" + msg + "</h3>");
}
}
%>
<script>
document.addEventListener("DOMContentLoaded", function() {
const msgBox = document.getElementById("l_msg");
if (msgBox) {
msgBox.addEventListener("click", function(event) {
if (event.target === msgBox || event.target.matches("#l_msg::after")) {
msgBox.classList.add("hidden");
setTimeout(() => msgBox.remove(), 300);
}
});
// 자동 닫기 (5초 후)
setTimeout(() => {
msgBox.classList.add("hidden");
setTimeout(() => msgBox.remove(), 300);
}, 5000);
}
});
</script>
</body>
</html>
7. register.jsp
<%@ page import="javax.naming.*, javax.sql.*, java.sql.*" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Register</title>
</head>
<body>
<div class="register">
<h1>Register</h1>
<form action="register.jsp" method="post">
<input type="email" name="email" placeholder="Email" required="required" />
<input type="password" name="password" placeholder="Password" required="required" />
<input type="text" name="username" placeholder="Name" required="required" />
<button type="submit" class="btn btn-primary btn-block btn-large">REGISTER</button>
</form>
<a href="login.jsp" class="btn btn-primary btn-block btn-large mt10">LOGIN</a>
</div>
<%
String email = request.getParameter("email");
String password = request.getParameter("password");
String username = request.getParameter("username");
String msg = null;
if (email != null && password != null && username != null) {
Connection con = null;
PreparedStatement pstmt = null;
try {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource) envCtx.lookup("jdbc/mydb");
con = ds.getConnection();
// Check if the email already exists
String checkQuery = "SELECT COUNT(*) FROM users WHERE email = ?";
PreparedStatement checkStmt = con.prepareStatement(checkQuery);
checkStmt.setString(1, email);
ResultSet checkRs = checkStmt.executeQuery();
checkRs.next();
int count = checkRs.getInt(1);
checkRs.close();
checkStmt.close();
if (count > 0) {
msg = "Email already exists.";
} else {
String query = "INSERT INTO users (email, password, username) VALUES (?, ?, ?)";
pstmt = con.prepareStatement(query);
pstmt.setString(1, email);
pstmt.setString(2, password);
pstmt.setString(3, username);
int rowsAffected = pstmt.executeUpdate();
if (rowsAffected > 0) {
msg = "Registration Successful!";
} else {
msg = "Registration Failed.";
}
}
} catch (Exception e) {
msg = "Error: " + e.getMessage();
e.printStackTrace();
} finally {
try {
if (pstmt != null) pstmt.close();
if (con != null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (msg != null) {
out.println("<h3 id='l_msg'>" + msg + "</h3>");
}
}
%>
</body>
</html>
8. style.css
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
.btn { display: inline-block; padding: 4px 10px 4px; margin-bottom: 0; font-size: 13px; line-height: 18px; color: #333333; text-align: center;text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); vertical-align: middle; background-color: #f5f5f5; background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); background-image: linear-gradient(top, #ffffff, #e6e6e6); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); border-color: #e6e6e6 #e6e6e6 #e6e6e6; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border: 1px solid #e6e6e6; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; }
.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { background-color: #e6e6e6; }
.btn-large { padding: 9px 14px; font-size: 15px; line-height: normal; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
.btn:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; }
.btn-primary, .btn-primary:hover { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); color: #ffffff; }
.btn-primary.active { color: rgba(255, 255, 255, 0.75); }
.btn-primary { background-color: #4a77d4; background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); background-image: linear-gradient(top, #6eb6de, #4a77d4); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0); border: 1px solid #3762bc; text-shadow: 1px 1px 1px rgba(0,0,0,0.4); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); }
.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { filter: none; background-color: #4a77d4; }
.btn-block { width: 100%; display:block; }
* { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; -ms-box-sizing:border-box; -o-box-sizing:border-box; box-sizing:border-box; }
.mt10{
margin-top: 10px;
text-decoration: none !important;
}
html { width: 100%; height:100%; overflow:hidden; }
body {
width: 100%;
height:100%;
font-family: 'Open Sans', sans-serif;
background: #092756;
background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%),-moz-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -moz-linear-gradient(-45deg, #670d10 0%, #092756 100%);
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -webkit-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -webkit-linear-gradient(-45deg, #670d10 0%,#092756 100%);
background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -o-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -o-linear-gradient(-45deg, #670d10 0%,#092756 100%);
background: -ms-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -ms-linear-gradient(top, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -ms-linear-gradient(-45deg, #670d10 0%,#092756 100%);
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), linear-gradient(to bottom, rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), linear-gradient(135deg, #670d10 0%,#092756 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3E1D6D', endColorstr='#092756',GradientType=1 );
}
.login {
position: absolute;
top: 50%;
left: 50%;
margin: -150px 0 0 -150px;
width:300px;
height:300px;
}
.login h1 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; }
input {
width: 100%;
margin-bottom: 10px;
background: rgba(0,0,0,0.3);
border: none;
outline: none;
padding: 10px;
font-size: 13px;
color: #fff;
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
border: 1px solid rgba(0,0,0,0.3);
border-radius: 4px;
box-shadow: inset 0 -5px 45px rgba(100,100,100,0.2), 0 1px 1px rgba(255,255,255,0.2);
-webkit-transition: box-shadow .5s ease;
-moz-transition: box-shadow .5s ease;
-o-transition: box-shadow .5s ease;
-ms-transition: box-shadow .5s ease;
transition: box-shadow .5s ease;
}
input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgba(255,255,255,0.2); }
/* 기본 폰트 설정 */
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Open Sans', sans-serif;
background: #092756;
background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%, rgba(138,114,76,0) 40%), -moz-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -moz-linear-gradient(-45deg, #670d10 0%, #092756 100%);
background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%, rgba(138,114,76,0) 40%), -webkit-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -webkit-linear-gradient(-45deg, #670d10 0%, #092756 100%);
background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%, rgba(138,114,76,0) 40%), -o-linear-gradient(top, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -o-linear-gradient(-45deg, #670d10 0%, #092756 100%);
background: linear-gradient(to bottom, rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), linear-gradient(135deg, #670d10 0%, #092756 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3E1D6D', endColorstr='#092756',GradientType=1 );
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* 레지스터 폼 중앙 배치 */
.register {
background-color: rgba(255, 255, 255, 0.1);
padding: 20px 40px;
border-radius: 10px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 400px;
text-align: center;
}
.register h1 {
color: #fff;
margin-bottom: 20px;
}
/* 입력 필드 스타일 */
.register input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: none;
border-radius: 5px;
background: rgba(0, 0, 0, 0.3);
color: #fff;
font-size: 14px;
}
.register input::placeholder {
color: #ddd;
}
/* 버튼 스타일 */
.register .btn {
display: block;
width: 100%;
padding: 10px;
font-size: 16px;
font-weight: 600;
color: #fff;
background-color: #4a77d4;
border: none;
border-radius: 5px;
cursor: pointer;
margin-bottom: 10px;
}
.register .btn:hover {
background-color: #3762bc;
}
/* 로그인 링크 스타일 */
.register .mt10 {
margin-top: 10px;
text-decoration: none;
color: #fff;
display: block;
text-align: center;
padding: 10px;
background-color: #4a77d4;
border-radius: 5px;
}
.register .mt10:hover {
background-color: #3762bc;
}
/* 알림 메시지 스타일 */
#l_msg {
position: fixed;
top: 20px;
right: 20px;
background: rgba(57, 173, 219, 0.9);
padding: 15px 40px 15px 20px;
border-radius: 8px;
color: #fff;
font-size: 16px;
font-weight: 600;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
max-width: 350px;
z-index: 9999;
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}
/* X 버튼 (닫기 버튼) */
#l_msg::after {
content: "✖";
font-size: 14px;
font-weight: bold;
position: absolute;
top: 10px;
right: 10px;
width: 25px;
height: 25px;
background: #fff;
color: #092756;
border-radius: 50%;
text-align: center;
line-height: 25px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
/* X 버튼 hover 효과 */
#l_msg:hover::after {
background: #ff4d4d;
color: #fff;
}
/* 메시지 숨김 효과 */
.hidden {
opacity: 0;
transform: translateY(-20px);
pointer-events: none;
}
9. web.xml: 애플리케이션의 배치 및 구성 정보
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- MariaDB DataSource 설정 -->
<resource-ref>
<description>MariaDB DataSource</description>
<res-ref-name>jdbc/mydb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<!-- login.jsp 파일을 루트 URL에 매핑 -->
<servlet>
<servlet-name>Login</servlet-name>
<jsp-file>/login.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/</url-pattern> <!-- 루트 URL로 매핑 -->
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/style.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/js/*</url-pattern>
</servlet-mapping>
</web-app>
10. default.conf: Nginx 서버의 동작 방식을 정의
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://tomcat-svc.prj-tomcat.svc.cluster.local:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /healthcheck {
root /usr/share/nginx/html;
index index.html index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
11. dockerfile (nginxweb 용): Nginx 웹 서버의 손쉬운 배포 및 관리위한 파일
FROM nginx:1.14-alpine
COPY ./default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
다음 내용
[Cloud] Cloud 프로젝트: 관리대장, 시방서
이전 내용 [Cloud] aws: small project - 3 Tier 구현하기이전 내용 [Cloud] aws: Network LoadBalancer (feat. AWS CLI + yaml 파일)이전 내용 [Cloud] aws: eksctl, kubectl 주요 명령어이전 내용 [Cloud] aws : aws cli 로 쿠버네티
puppy-foot-it.tistory.com
'프로그래밍 및 기타 > Cloud : AWS' 카테고리의 다른 글
[Cloud] Cloud 프로젝트: 관리대장, 시방서 (2) | 2025.03.08 |
---|---|
[Cloud] aws: EKS Controller, 쿠버네티스 Service (0) | 2025.03.04 |
[Cloud] aws: Network LoadBalancer (feat. AWS CLI + yaml 파일) (1) | 2025.03.04 |
[Cloud] aws: eksctl, kubectl 주요 명령어 (0) | 2025.03.01 |
[Cloud] aws : aws cli 로 쿠버네티스 클러스터 만들기 (0) | 2025.03.01 |