번호판을 위변조한 차량을 탐지하여 보완을 강화한 주차 관리 시스템을 설계한다.
깃 주소 : https://github.com/SHINDongHyeo/Anti-spoofing-car-project
AWS S3
AWS EC2
AWS RDS
위의 프로젝트 아키텍처를 구성하기 위한 AWS 서버 구축은 아래와 같이 구성하였습니다.
CIDR : Classless Inter Domain Routing
IP 주소를 할당하는 방법
CIDR 블록 : 할당된 IP 주소들의 모음 , 그룹
VPC가 사용할 IP 대역 혹은 범위
Tenancy : 해당 VPC에서 EC2 인스턴스를 생성할 때 전용 하드웨어를 사용 할 것인지 대한 여부 확인
전용 하드웨어를 통한 생성은 Dedicated ( 본 프로젝트는 x )
DNS Host Name 설정
DNS 설정 : 만드는 vpc 에서 생성되는 EC2 인스턴스 와 같은 리소스가 DNS Host Name을 사용할지 여부를 Check 하는 항목
Subnet 구성을 완성 !
그러나, EC2 인스턴스 등이 생성될 수 있는 위치 ! 는 생성되었으나, EC2를 기동할만한 인프라 작동하긴 어려움 리소스들 사이, 외부 인터넷 사이 트래픽이 이동할 경로를 구성해 주지 않았기 때문
그러한 서비스를 제공하는 것이 게이트웨이와 라우트 테이블임
3.Internet GateWays 생성 및 VPC Attach
( IGW 생성 방법과 Attach 방법에 대한 POST입니다. )
일반적으로 Private , Public Subnet으로 구성한 후 Route Table을 분리하여야 하지만, AWS 아키텍처 설계 시간 문제로 인해 본 프로젝트에서는 단일 라우팅을 통해 Associate 하였음.
목적지가 0.0.0.0/0 ( Destination ) : 외부로 향하는 트래픽은 인터넷 게이트웨이를 통해서 나가라 !
즉, 트래픽이 인터넷 게이트웨이를 통해서 나감
172.30.0.0 (내가 설정한 VPC CIDR 블록) Target이 Local으로 되어있음 기본적으로 내 VPC CIDR내에서의 트래픽 이동이 가능하다.
인터넷 게이트웨이를 통해 나갈 수 있는 환경이 구성됨
기본적인 네트워크 환경 셋팅 끝
TIP) VPC 설정 이유
EC2 인스턴스에 대한 기본적인 네트워크 요소를 만들거나 Setting 함
이전에 생성한 VPC와 Subnet을 선택했으며, 생성한 VPC의 보안그룹으로 설정하였다. 보안그룹이 없는 경우 보안그룹을 설정해야한다.
보안그룹(방화벽)은 인바운드 규칙과 아웃바운드 규칙으로 나뉘는데 인바운드는 요청 , 아웃바운드 응답으로 생각하면 된다.
대게, 요청에 까다로운 설정을 하도록 한다. 아래는 보안그룹 설정 예시.
Firewall ( security groups )
Instance Level 에서 Traffic을 제어하는 네트워크 요소
통신타입 , 프로토콜 , 포트번호
소스타입 : 액세스 권한 부여
등, 그 서버에서 요청하는 유형의 프로토콜의 출입통제를 설정한다.
AWS 블록 스토리지 서비스 EBS 볼륨 추가 → 용량과 타입 설정
상세한 옵션 값 확인이 필요한 경우 어드밴스드를 클릭하여 설정
※ 돈이 많이 나갈까봐 프리티어 + 낮은 볼륨의 스토리지를 구성하였으나,
서버 빌드 및 배포시에 많은 라이브러리들이 추가되어, 서버 운영 및 설치중 Out of Memory Error 발생 및 데이터 처리 시 부하심화가 확인되었다. 가급적.. micro , 8 보다 더 크고 좋은 리소스를 선택하도록 해야겠다.
어떻게든 지출을 아끼기 위해 옵션의 기능을 찾아보았다.
도메인 조인 디렉토리 : 생성한 인스턴스 사용자 , 그룹 , 디바이스 등에 대한 정보를 저장하고 access를 관리하는 AWS 디렉토리 서비스에 포함 시킬 것인지 여부 * 기본설정
IAM 인스턴스 프로파일
IAM : indentity Access Mangement : AWS 리소스에 대한 액세스 권한을 제어하고 관리하는 서비스
자격증명 및 사용권한을 정의하는 정책 ( Policy ) * 기본설정
EBS 의 IO ( Input / ouput )와 인스턴스 다른 트래픽 간의 경합을 최소화 해서 EBS의 볼륨에 개선된 성능을 제공하는 옵션 , 제한된 인스턴스 타입에서 활성화 됌
메타데이터 액세스 : 인스턴스 메타 데이터를 액세스 할 수 있는지 체크 ( Enable )
메타데이터 버전 : V1 , V2 혼용
홉 제한 : V2 사용 시 TOKEN 응답에 관한 옵션 ( 연습 1 으로 설정한다 )
메타데이터에서 태그 허용 : 인스턴스가 메타데이터에서 인스턴스 태그에 대한 리소스 허용에 관한 옵션 ( Enable )
User Data : 인스턴스가 launch 되는 과정에서 root 권한으로 실행되는 스크립트
인스턴스가 생성 된 후에 자동으로 스크립트를 실행시키도록 설정
인스턴스 생성 완료 !
퍼블릭 IP를 자동으로 할당하지 않았으므로 없음
Elastic IP설정을 통해 EC2 인스턴스에 퍼블릭 IP를 할당하는 작업
네트워크 보더 그룹 서울 리전을 선택 ( 노스이스트 )
이 후 생성한 인스턴스를 지정해서 연결하면 끝 !
TIP
엘라스틱 IP 사용 이유
출처 : https://any-ting.tistory.com/71
EC2 인스턴스에 접근하기 위해선 해당 인스턴스에 대한 Key Pair 가 필요하다!
인스턴스를 생성하는 과정에서 만들어진 key pair를 찾는다
PuTTy를 다운로드하세요 !
Windows의 64 bit Zip 으로 다운로드
https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
다운로드 완료 후
PUTTYGEN 실행
Key Pair 추가하기
Putty gen은 PPK 확장자만 확인하도록 되어있어 모든 확장자 파일을 통해 PEM 등록
Save Private Key 버튼 누르기
저장할 키페어 이름으로 PPK 확장자로 저장
Putty에서 사용할 PPK 생성 완료 !
Putty 실행
Accept를 누른다 !
Login as : ec2-user 를 입력한다.
ubuntu 인 경우 : ubuntu 입력한다.
서버 접속 끝 !
위와 같은 형식 그러나 보안그룹은 다르게 설정하여,
Spring , Flask 인스턴스를 생성한다 !
MySQL 8.0.30 버전으로 생성하였으며, 보안그룹의 인바운드 규칙은 Database와 연결할 ( Spring Server EIP와 , 작업PC의 Mysql 3306을 연결한다. )
Database의 초기 접근 ID , PW 처음 Mysql 설치 시 ( root / ??? ) 같은 아이디라 생각하면 된다.
생성한 VPC 선택
보안그룹 설정 시 Database와 접근 가능한 서버를 설정하도록 하여야 하지만, 개발 시에 IP 환경의 변화로 우선 모두 오픈해두고 작업함
이 후 다른설정은 두고 생성 완료 !
Spring Server 및 Local PC ( InteliJ 에서 DB 연결해보기 )
Spring Server 에서 접속해서 Database
데이터베이스 정상 접속 확인 !
기존
mysql -u root -p 에서 -h [Database의 엔드포인트이름]
으로 진행하면 됩니당 !
인텔리제이에서 해보기
버킷만들기
AWS S3에 접속 후 다음과 같은 화면에서 우측 상단에 버킷 만들기 클릭
버킷명, 지역 설정
권한 설정
기타 설정
기타 설정 후 버킷 만들기 클릭
버킷 설정
해당 작업을 통해 버킷 탭에 들어가면, 버킷 1개가 생성된 모습을 확인할 수 있을 것이다.(사진에서는 이미 만들어놓은 버킷들로 인해 4개가 이미 있는 모습이다)
그럼 해당 버킷을 클릭해 들어가면 다음과 같은 화면이 출력된다. 권한 탭을 클릭한다.
다음과 같이 버킷에 대한 권한을 설정해준다.
app.py 파일 안에 핵심 로직 설계
flask를 실행 시 가동될 파일을 app.py
로 설정하고 해당 파일 안에 핵심 로직을 설계했습니다. (자세한 내용은 포스트 위쪽 깃 주소에서 코드와 리드미 참조)
Flask - Spring Boot 서버간 통신하기 / curl 커맨드를 통해 데이터 입력받기
이번 프로젝트에서 설계한 구조로 인해 Flask와 Spring Boot가 통신하며 데이터를 주고 받아야 했기 위해 requests
모듈을 이용해 Spring Boot와 통신했다.
또한 자동차가 주차장에 입차하는 상황을 구현해야 했는데, 직접 차를 입차시키고 이를 사진으로 찍어서 Flask 서버에 전송하는 것은 개발 단계에서 불필요하다고 판단하여 미리 저장된 차량 입차 이미지들의 경로 정보를 curl 커맨드를 통해 Flask 서버에 전송하면 이를 restful API인 flask_restful
라이브러리로 처리하여 해결하는 방식을 이용했다.
ex) Flask - Spring Boot 서버간 통신하기
############# < app.py에 car_in() 일부 내용 > ################# import requests # 입차 함수 @app.route("/carin") def car_in(): ################### 내용 생략 ############################ try: flag="" ######## 이 부분에서 spring과 post통신하는 모습을 확인할 수 있다. response = requests.post('http://스프링주소', json={"accesscarnum": accesscarnum2, "inimg":inimg, "park_id":park_id}) if response.status_code == 200: print("첫번째 통신200확인") print(response.text) # false: 위변조 코드 실행 X, true: 위변조 코드 실행 O flag = response.text except: print("첫번째 통신에러")
( flask-restful documentation : https://flask-restful.readthedocs.io/en/latest/ )
response = requests.post('http://스프링주소', json=제이슨데이터)
이 명령어가 Spring Boot와 통신하는 핵심 코드다. 여기서 스프링 또한 EC2 인스턴스에 배포되었으므로스프링이 올라간 인스턴스의 퍼블릭 IPv4주소
+스프링에서 requestmapping으로 설정된 url값
을 넣어서 입력해주었다.(ex. "http://16.167.71.296/api/caraccess/test")
ex) curl 커맨드를 통해 데이터 입력받기
- cmd창에서 curl 커맨드를 이용해 json형식 데이터를 해당 주소로 GET 방식 통신하는 모습( 지금 생각해보니 해당 코드는 입차 이미지 경로값을 보내기만 하면 되는 부분이라 GET보단 POST방식이 더 알맞는 방식같다!)
curl -d "{\"accesscarnum\":\"01neo0680\", \"spoofing\":\"False\", \"park_id\":\"2\"}" -H "Content-Type: application/json" -X GET AWS_EC2_instance주소값/carin
- curl 커맨드에 적힌 주소로 실행되는 Flask 서버 속 함수 부분.
############# < app.py에 car_in() 일부 내용 > ################# from flask_restful import reqparse # 입차 함수 @app.route("/carin") def car_in(): ################### 내용 생략 ############################ # 특정 차량번호, 위변조여부 넣어주기 reqParser = reqparse.RequestParser() ########### curl로 전송한 json파일 받아들임! reqParser.add_argument("accesscarnum") reqParser.add_argument("spoofing", type=str, default="False") # False: 정상, True: 비정상 reqParser.add_argument("park_id", type=str, default="1") print("reqParser설정완료") reqArgs = reqParser.parse_args() print("parse_args()완료") accesscarnum = reqArgs["accesscarnum"] spoofing = reqArgs["spoofing"] park_id = reqArgs["park_id"] print("accesscarnum : ",accesscarnum,", spoofing : ", spoofing,", park_id : ", park_id)
( flask-restful documentation : https://flask-restful.readthedocs.io/en/latest/ )
S3에 이미지 업로드 / 다운로드
S3에 이미지를 업로드하거나 S3에서 이미지를 다운로드하는 등의 S3 관련 작업들을 함수화하고 이런 함수들을 묶어서 my_s3.py
라는 파일로 모듈화했다.
ex) S3에 이미지 업로드하기
######################### < my_s3.py 일부 내용 > > ######################## import boto3 from botocore.exceptions import ClientError import uuid def send_s3(id, pw, s3_img_name, upload_path1, upload_path2): """ [params] id = 아마존 엑세스 ID pw = 아마존 엑세스 PW s3_img_name = s3에(위 버킷 안에) 저장할 파일명 upload_path = s3에 업로드할 이미지가 저장된 로컬 경로 """ s3 = boto3.client( 's3', # 사용할 서비스 이름, ec2이면 'ec2', s3이면 's3', dynamodb이면 'dynamodb' aws_access_key_id=id, # 액세스 ID aws_secret_access_key=pw) # 비밀 엑세스 키 try: uuid_val = uuid.uuid1() s3.upload_file(upload_path1,"ecore-parking-record-in-car",f"{s3_img_name}_{uuid_val}.jpg") s3.upload_file(upload_path2,"encore-parking-record-crop-car",f"{s3_img_name}_{uuid_val}.jpg") except Exception as e: print("S3에서 파일을 업로드하는 도중 문제 발생!!!!!!!!!!!!!!!!!!!!") print(e) return f"{s3_img_name}_{uuid_val}.jpg"
- S3와 통신을 쉽게 하기 위해
boto3
라는 라이브러리를 다운받아 사용했다.
( boto3 documentation : https://boto3.amazonaws.com/v1/documentation/api/latest/index.html )- 또한 저장되는 이미지들의 이름을 랜덤으로 각기 다르게 설정하기 위해
uuid
모듈을 사용했다.
위에서 설명한 EC2 인스턴스 생성하기 참조
로컬 PC에서 완성한 Flask 파일을 EC2에 옮겨야 한다. 이때 해당 Flask 파일을 github에 업로드하고, EC2에서 git 명령어를 통해 Flask 파일을 다운로드받는 과정으로 파일을 옮긴다.
github에 새로운 repository를 생성하고 완성된 Flask 파일을 업로드한다.
ubuntu(사용할 EC2의 OS)에서 다음 명령어를 통해 설치 가능한 패키지 목록을 최신화해준다.
$ sudo apt-get update
ubuntu에 git 설치
$ sudo apt-get install git
git설치 확인
$ git --version
git clone해오기
$ git clone 깃주소입력
requirements.txt를 통해 필요한 라이브러리 설치
※ 발생했던 문제점 ※
예전부터 github에 있는 코드를 clone할 때requirements.txt
라는 파일을 본 적이 몇 번 있다. 이는 해당 코드를 실행하기 위한 패키지들을 정리해놓은 텍스트 파일이었고 readme에 작성된 가이드대로pip install -r requirements.txt
명령어를 통해 필요한 패키지들을 설치했었다.이번 프로젝트에서는 내가 코드를 다운받는 입장이 아닌 업로드하는 입장이여서 다운받아야 하는 패키지를 정리한
requirements.txt
파일을 만들어야 하는데, 이 텍스트 파일을 직접 모든 패키지명을 타이핑하는 것으로 생각했고 너무 비효율적인 작업이라고 판단해requirements.txt
파일을 만들지 않았고, 결국 직접 모든 패키지들을 각각 pip 명령어를 통해 다운받아서 프로젝트를 진행했었다.하지만 가상환경 내에 존재하는 패키지들을 텍스트 파일 형식으로 정리해
requirements.txt
파일을 만드는 명령어를 뒤늦게 알게 되었고, github에 코드를 올릴 때는 이 텍스트 파일을 올리는 것이 기본이라는 사실을 알게 되어 이를 공유하기 위해 사용법을 정리해본다.
- 가상환경의 패키지들 requirements.txt 파일로 정리하기
# 원하는 가상환경을 activate하고, 다음 명령어를 실행한다. # 그럼 현재 위치한 경로상에 패키지명들이 담긴 requirements.txt 파일이 생긴다. $ pip freeze > requirements.txt # 만들어진 requirements.txt 파일은 github에 코드를 업로드할 때 같이 올려주어 코드를 다운받는 사용자들이 코드에 사용된 패키지를 손쉽게 다운할 수 있게 해주어야 한다.
- requirements.txt 파일 속 패키지들 설치하기
$ pip install -r requirements.txt
Spring Boot 배포에 앞서 상기 Velog 개발자님의 포스팅을 기반으로 서버에 배포를 하였습니다.
$ sudo apt-get update
--> 실제로 메모리 스와핑을 하지 않은 경우 t2 Micro 버전에서 메모리 오버의 문제가 발생하였다.
작은 서버를 가용하게 된다면 적당한 가상메모리를 설정하는게 좋을 것 같다..!
먼저 스와핑을 진행할 파일을 생성해주자.
$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ sudo swapon -s
$ sudo vi /etc/fstab
vi 편집기에서 Insert(i) 모드로 들어간 후 수정 후 :wq를 통하여 나온다.
/swapfile swap swap defaults 0 0
이 후 free 를 서버에 입력하여 가상메모리가 적용 되었는지 확인
$ sudo apt list | grep openjdk
11버전에 맞는 jdk 설치
$ sudo apt-get install openjdk-11-jdk
ubuntu 서버에 환경 변수를 셋팅하는 방법은 아래 포스팅을 참조하였습니다.
https://unit-15.tistory.com/114
$ sudo apt-get update
$ sudo apt-get upgrade
$ java -version
$ javac -version
$ vim /etc/profile
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 // 본인의 자바 설치 경로
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=$CLASSPATH:$JAVA_HOME/jre/lib/ext:$JAVA_HOME/lib/tools.jar
$ source /etc/profile
$ vim ~/.bashrc
편집하고 나온다
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 // 본인의 자바 설치 경로
export PATH=${PATH}:${JAVA_HOME}/bin
$ source ~/.bashrc
$ echo $JAVA_HOME
/usr/lib/jvm/java-11-openjdk-amd64
$ sudo apt install zip
$ curl -s "https://get.sdkman.io" | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk version
$ sdk list gradle
$ sdk install gradle 7.5.1
github에서의 토큰 발급은 다음과 같이 진행한다.
main brunch에서 Clone 하는 경우 통상 Repo만 체크해서 발급...
$ git clone [프로젝트 코드 주소]
이 후 서버에서 git clone을 하게되면 ID와 Password를 치는데,
이때 Password는 발급받은 Token을 입력하면된다 !
gradlew 가 있는 디렉터리까지 이동한 뒤
$sudo chmod +x ./gradlew
권한 부여 후
클린 테스트 시작
$sudo ./gradlew clean test
대게 DDL 예약어 때문에 Warning이 발생하는 경우가 있다.
첫 배포시에 Hibernate 관련한 오류가 발생했다. 지정한 RDS Database가 연동이 되지 않아 계속 배포 실패했었다 ㅠㅠ 결국 AWS의 RDS 설정을 다시하게 되었는데 이러한 상황이 되지 않으려면 서버에서 먼저 RDS연동을 확인하고 배포하자!
테스트 빌드가 끝났다면 실제 빌드
$ sudo ./gradlew build
$ sudo java -jar [ jar파일명 ]
성공 ~
포트 포워딩 !
이걸 설정하면 80포트에서 8080 포트로 바로연결하게 끔 할 수 있다.
$ sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
백그라운드 배포
$ nohup java -jar [파일이름].jar &
스프링 배포 완료 !
React 서버 접속하여 React를 구동할 수 있는 환경을 셋팅한다
이 중 node js의 설치와 npm install이 필수 !
node js의 경우 최신의 16버전에서 진행 시 npm이 설치되지 않는 이슈가 발생하여,
14버전으로 Set up 하였다.
추가로 npm install 시 --verbose를 사용하여 install 시 발생하는 이슈에 대한 log를 확인 하도록 한다.
sudo apt-get install systemd
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install -y build-essential
sudo apt install npm
sudo npm install --verbose
로컬에서의 빌드이다보니, nginx , apach2 , jenkins등의 서버를 별도로 설치하지 않았으며,
설치가 완료된 이 후 sudo npm run build 을 통하여 빌드가 정상적으로 되는지 확인
이 후 serve -s build를 통하여 react 서버 기동을 완료하였다.
접속 성공 !