728x90
반응형

개요 및 구성

 웹 페이지에서 여러 대의 SSH 서버가 연결이 가능한지 여부를  UI로 확인하고 싶어서 만들었다.

구성은 Front, Back, SSH Server로 되어 있고 Front, Back은 Ajax를 사용하여 Json으로 데이터를 주고 받는다.

Front는 HTML과 Javascript, jquery-3.6.3.js의 ajax, Back은 FastAPI로 작성해 두었고 서버의 SSH 연결 상태를 확인하기 위해서 paramiko 패키지를 사용한다.

 

아래는 서버의 1대의 상태를 체크하는 과정을 요약한 그림이다.

 

 


Front

 UI는 Server Count에 서버의 대수를 입력하면 아래처럼 해당 수에 맞춰 인터페이스가 추가된다.

localStorage를 사용했기 때문에 페이지가 새로 고침되어도 해당 인터페이스가 유지된다.

3을 입력한 경우
10을 입력한 경우

 

 서버 정보를 입력하고 check를 누르면 위 구성에 따라 요청을 보내고, 결과는 아래와 같이 분류되어 Connection Status에 작성된다.

  • 진행 중인 경우, Work in progress.
  • 제한 시간 내에 연결이 안된 경우, timeout
  • 연결이 성공한 경우, ok
  • Callback 순서(error > complete > fail > always)에 따라 fail되었는데 응답을 못받은 경우, Fail

예시

ssh_server_status_checker.html 파일 

<!DOCTYPE html>
<html lang="ko">
<head>
    <script src="static/js/jquery-3.6.3.js"></script>
    <link rel="stylesheet" href="test.css">
    <meta charset="UTF-8">
    <title>SSH Server Status Checker</title>

    <script>
        var timerId = null;
        var max_serverCount = localStorage.getItem('max_serverCount');
        const server_url = "http://127.0.0.1:8000";

        function set_ServerCount(){
            max_serverCount = $('#text_server_count').val();
            localStorage.setItem('max_serverCount', max_serverCount);
            window.location.reload();
        }

        function statusTimer() {
            for(var i = 1; i <= max_serverCount; i += 1){
                sshServer_statusCheck(i);
            }
        }
        
        function StartClock(interval=3000) {
            timerId = setInterval(statusTimer, interval);
            statusTimer();
        }
        
        function StopClock() {
            if(timerId != null) {
                clearInterval(timerId);
            }
        }

        function sshServer_statusCheck(target){
            var html_result_id = target + "_status_result";
            var server_ip = $('#text_' + target + '_ip').val();
            var server_port = $('#text_' + target + '_port').val();
            var server_id = $('#text_' + target + '_id').val();
            var server_pw = $('#text_' + target + '_pw').val();
            var send_data = {
                key: target,
                ip: server_ip,
                port: server_port,
                id: server_id,
                pw: server_pw,
            };

            $('#' + html_result_id).html("Work in progress.", status);
            document.getElementById(html_result_id).style.color = "#FFEE11";

            $.ajaxSetup({'cache':true});
            $.ajax({
                url : server_url + "/statusCheck",
                type : "POST",
                dataType : "json",
                timeout : 2000,
                contentType : "application/json",
                data : JSON.stringify(send_data),
                success: function(data){
                    $('#' + html_result_id).html(data.status, status);
                    if(data.status == "ok"){
                        document.getElementById(html_result_id).style.color = "green";
                    }
                    else{
                        document.getElementById(html_result_id).style.color = "red";
                    }
                },
                error: function(request, status, error){
                    if(error == "timeout"){
                        $('#' + html_result_id).html(status, 200);
                        document.getElementById(html_result_id).style.color = "red";
                    }
                }
            })
            .fail(function(xhr, textStatus, errorThrown) {
                if ($('#' + html_result_id).text() == "Work in progress."){
                    $('#' + html_result_id).html("Fail", status);
                    document.getElementById(html_result_id).style.color = "red";
                }
            });
        }
    </script>
</head>
<body>
    <h3>SSH Server Status Checker</h3>
    <form class="my-box">
        <div>
            <label><b>Server Count : </b></label> <input type="text" placeholder="number" id="text_server_count" style="width: 50px;"> <input type="button" value="set" onclick="set_ServerCount();"><br>
            <hr>
            <label><b>Input Data</b></label><br>
            <script>
                var tmp_value;

                for(var i = 1; i <= max_serverCount; i += 1){
                    tmp_value = "server" + i;
                    document.write('<label>' + tmp_value + ' Settings : </label>');
                    document.write('<input type="text" id="text_' + tmp_value + '_ip" placeholder="IP Address" style="width: 100px;"> ');
                    document.write('<input type="text" id="text_' + tmp_value + '_port" placeholder="Port" style="width: 50px;"> ');
                    document.write('<input type="text" id="text_' + tmp_value + '_id" placeholder="ID" style="width: 50px;"> ');
                    document.write('<input type="password" id="text_' + tmp_value + '_pw" placeholder="PW" style="width: 50px;"> ');
                    document.write('<input type="button" value="check" onclick="sshServer_statusCheck(' + "'" + tmp_value + "'" + ');"> ');
                    document.write('<br>');
                }
            </script>
        </div>
        <hr>
        <label><b>Connection Status</b></label><br>
        <script>
            for(var i = 1; i <= max_serverCount; i += 1){
                tmp_value = "server" + i;
                document.write('<label>' + tmp_value + ' : </label><label id="server' + i + '_status_result"></label><br>');
            }
        </script>
        <label>Repeat check : </label>
        <input type="button" value="Start" onclick="StartClock();">
        <input type="button" value="End" onclick="StopClock();">
    </form>
    <br>
    <br>
</body>
</html>

 

Back

 아래 파이썬 백엔드 서버는 프론트에서 서버로 전달해준 Json을 파싱하여 내부의 서버 정보로 ssh 연결을 시도한다.

MVC 패턴은 적용하지 않았고, DB도 따로 구축하진 않고 dict_server_info라는 딕셔너리 자료구조를 이용했다.

import paramiko

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from starlette.middleware.cors import CORSMiddleware

def ssh_command_sender(ip, port, user, pw, cmds):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
    ssh.connect(ip, port, user, pw, timeout=1)

    stdin, stdout, stderr = ssh.exec_command(";".join(cmds))

    lines = stdout.read()
    res = ''.join(str(lines))
    
    return res

def _health_check(ip, port, user, pw):
    try:
        ssh_command_sender(ip, port=port, user=user, pw=pw, cmds=[])
        return True
    except:
        return False


origins = ["*"]

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class serverSettings(BaseModel):
    key: str
    ip: str
    port: str
    id: str
    pw: str
    
dict_server_info = {
    #"server1":{"ip":"", "port":"22", "id":"", "pw":"", "status":""},
}

@app.post("/statusCheck")
async def statusCheck(info: serverSettings):
    key = info.key
    try:
        dict_server_info[key]["ip"] = info.ip
        dict_server_info[key]["port"] = info.port
        dict_server_info[key]["id"] = info.id
        dict_server_info[key]["pw"] = info.pw
    except:
        dict_server_info[key] = {"ip":info.ip, "port":info.port, "id":info.id, "pw":info.pw, "status":""}
        
    print(dict_server_info[key])

    print(f"{key} health check ...", end="")
    if _health_check(dict_server_info[key]["ip"], dict_server_info[key]["port"] , dict_server_info[key]["id"], dict_server_info[key]["pw"]) == False:
        print("error")
        return {"status":f"fail - {key} connect"}
    
    print("ok")
    dict_server_info[key]["status"] = "ok"
    return {"status":"ok"}
 

if __name__ == "__main__":
    uvicorn.run(app, port=8000, reload=False)

 

아래는 실제로 SSH 서버 vm을 실행하고 테스트해본 이미지이다.

1개의 서버를 체크할 경우 서버의 상태가 정상적으로 확인되었다.

1개 서버 체크
백엔드 로그 확인

 

 여러 개 서버의 경우로 우측 check 버튼을 통해 체크할 경우 다양한 현재 상태가 출력된다.

여러 개 서버를 각자 체크하기


주기적인 상태 체크

 위에서 체크 기능이 잘 동작하는걸 봤지만 프로그램이 내가 원하는 역할을 수행하게 하려면 입력한 정보를 일정 주기로 반복하여 체크할 수 있어야 한다. Repeat Check는 Timer를 활용하여 일정 주기(interval)로 ssh 연결을 시도한다.

 

 


후기

 간단한 기능을 제공하는 서버긴하지만 확장할 수 있는 내용들이 많다.

프론트에서는 html, css, js 파일을 분리하여 관리/ 확장성을 높이거나 React.js를 활용하여 더 깔끔하고 모던한 웹 사이트로 개선할 수 있고, 백엔드에서도 보안(openssl, oauth)과 디자인 패턴(MVC)을 적용하고 NoSQL DB와 연동해 볼 수 있을 듯 하다. 또한 DevOps 관점에서는 컨테이너화 시켜 쿠버네티스에 배포하고 GitOps를 적용해볼 수도 있다.

퇴근 후와 주말에만 가끔식 하는 프로젝트라 진행은 더디지만 하나씩 공부해서 Pluto에도 적용해보겠다.

728x90
반응형

'프로그래밍 > Web' 카테고리의 다른 글

[백엔드] Flask에서 JWT 토큰 생성하기  (0) 2022.09.18
백엔드 로드맵 정리  (0) 2022.09.12
[Django] 장고 기본 웹서버 띄우기  (0) 2022.03.28
728x90
반응형

flask_jwt_extended

 flask에선 flask_jwt_extended라는 이름으로 jwt 토큰에 대한 기능을 제공해준다. 

토큰 인증에 대한 테스트는 POSTMAN으로 했고, JWT에 대한 내용은 아래 글을 참고하면 좋을 것 같다. 

https://hwan001.tistory.com/277

 

[python] JWT 모듈

JWT (Json Web Token)  JWT는 웹 표준(RFC 7519) 으로 두 개체 사이에서 JSON을 사용하여 정보를 안전성 있게 전달해준다. 웹에서 로그인을 하거나 인증을 받게되면 보통 세션을 사용하여 인증 정보를 기

hwan001.co.kr


 

Access 토큰 생성하기

config.py

flaskJwt_secret_key = "secret_key"
flask_admin_id = "admin"
flask_admin_pw = "1234"

 

app.py

import sys
import subprocess

try:
    from flask import Flask
    from flask import request, render_template, make_response, jsonify, session, redirect, url_for, Response
    from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token, JWTManager

except:
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt'])


import config

app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = config.flaskJwt_secret_key
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1) # 기본설정은 15분

jwt = JWTManager(app)

# 메인 페이지
@app.route('/')
def main():
    return render_template('main.html')

# 로그인 정보가 일치할 경우, 토큰을 생성한다.
@app.route("/login", methods=['POST'])
def login():
    input_data = request.get_json()
    
    user_name = input_data['id']
    user_pw = input_data['pw']
	
    if (user_name == config.flask_admin_id) and (user_pw == config.flask_admin_pw):
        return jsonify(result = "True", access_token = create_access_token(identity = user_name))
        
    return jsonify(result = "False")

 

postman

 


 

토큰 사용하기

 

app.py

#from flask_jwt_extended import jwt_required, get_jwt_identity

@app.route("/my_jwt_test", methods=['GET'])
@jwt_required()
def my_jwt_test():
    current_user = get_jwt_identity() 
    
    return jsonify(logged_in_as=current_user), 200

 

postman

 

728x90
반응형
728x90
반응형

 백엔드 공부를 하다보면 볼 수 있는 로드맵 이미지이다.

각 내용을 정리하고 개인적으로 필요하다고 생각하는 내용들도 조금 추가해 보았다.

백엔드 로드맵

 

Internet

How does the internet work? 

  • 패킷과 프로토콜을 OSI 7을 기반으로 설명
  • A PC의 프로그램에서 B PC에 위치한 프로그램으로 패킷이 전달되는 과정
  • Private IP와 Public IP
  • 라우터와 스위치
  • 전송 계층의 전송 장비
  • CIDR, 서브네팅, 클래스

 

What is HTTP?

 HTTP가 무엇인지에 대한 설명, 아래 요소들을 설명하면 되는데 조금 더 나가면 브라우저에 대한 내용과 HTTPS와의 차이를 같이 말할 수 있을 듯 하다.  

  • 브라우저에 대한 이야기 (7계층에 위치한 프로그램의 일종)
  • HyperText Transfer Protocol, HTML과 관계
  • TCP 기반이지만 HTTP/3부턴 UDP 기반
  • GET, POST, PUT, DELETE 등의 메소드

 

Browsers and how they work?

 브라우저가 뭐고 어떻게 작동되는지를 물어본 질문이다.

부라우저의 구조를 설명하면서 작동원리를 말하면 될 것 같다.

  • UI/ UI 백엔드
  • 네트워킹
  • 렌더링 엔진
  • 브라우저엔진
  • 데이터 저장
  • 자바스크립트 인터프리터

** Jinja와 파이썬 인터프리터에 대한 이야기도 하면 좋을 듯

 

DNS and how it works?

 Domain Name Server에 대한 설명, 역할, 알려진 서버 (구글 8.8.8.8) 등

 

What is Domain Name?

 도메인과 아이피에 대한 개념

 

What is hosting?

 도메인 관리, 왜 필요하고 어떻게 하는지 예시를 들어 말하면 좋을 듯 함

(ex. 국내 호스팅 제공 업체에서 도메인 구입 후 이용 중), 레코드에 대한 개념도 추가로 공부하면 좋을 듯

 

 

Basic Fronted Knowledge

 프론트를 만들기 위한 기본 지식으로 아래 언어들을 공부해야 한다.

  • HTML : HyperText Markup Language
  • CSS : Cascading Style Sheets
  • JavaScript + JavaScript Library (jQuery, react)

 

Basic Backend Knowledge

 백엔드를 만들기 위한 기본 지식으로 아래 언어들을 공부해야 한다.

여러 방법으로 만들 수 있는데 본인이 자신 있는 언어를 기반으로 프레임워크를 다루는 게 좋을 것 같다.

(국내에선 자바 + 스프링 만한게 없다고 생각한다.)

  • 자바/jsp + Spring framework
  • Python + Django/Flask/FastAPI framework
  • Node js + Express

 

OS and General Knowledge

 Basic Terminal Commands, Terminal Usage, POSIX Basics은 직접사용하면서 익히고 나머지 운영체제에 대한 내용들은 공룡책보자..!

  • Terminal Usage
  • How OSs Work in General : OS 작동 원리
  • Process Management : 프로세스 관리
  • Threads and Concurrency
  • Basic Terminal Commands : Grep, awk, sed, lsof, curl, wget, tail, head, less find, ssh, kill
  • Memory Management
  • Interprocess Communication
  • I/O Managemanet
  • POSIX Basics : stdin, stdout, stderr, pipes
  • Basic Networking Concepts

 

Learn a Language

 Java, C#, PHP, RUST, GO, JavaScript, Python, Ruby, C++ 등 프로그래밍 언어

 

 

Version Control Systems 

 코드의 형상 관리와 버전 관리를 위해 Git 또는 Subversion(SVN) 을 사용한다.

각 도구의 차이점과 특징, 사용법 등을 알아두면 좋을 것 같다.

  • Basic Usage of Git
  • Repo hosting services : GitHub, GitLab, Bitbucket

 

Databases

 SQL 쿼리를 사용하는  RDB(관계형 데이터베이스)와 NoSQL DB의 차이를 알고 각 디비를 프로그램에서 연동해보면 좋을 듯 하다. 테이블 설계와 최적화, 샤딩 등을 추가로 해보면 좋다.

  • Relational Databases : PostgreSQL, MySQL, MariaDB, MS SQL, Oracle
  • NoSQL : MongoDB, RethinkDB, CouchDB, DynamoDB
  • More about Databases : ORMs, ACID, Transactions, N+1 Problem, Database Normalization, Indexes and how they work Data Replication, Sharding Strategies, CAP Theorem

 

Learn about APIs

REST와 GraphQL 차이와 사용법을 다른 글에서 작성 중이다.

Swagger와 JSON, JWT는 기본적으로 알아두어야된다고 생각한다.

  • HATEOAS
  • Open API Spec and Swagger
  • Authentication : twilio, opt, e-mail, JWT
  • REST 
  • Graph QL : Apollo, Relay Modern
  • JSON APIs
  • SOAP

 

Caching

CDN과 Redis에 대해서 알아볼 예정이다.

Redis는 In-Memory DB로 알고있는데 Caching으로 분류된게 의문이지만 일단 넣어두었다.

  • CDN
  • Server Side : Redis, Memcached
  • Client Side 

 

Web Security Knowledge

해싱, SSL/TLS, CA인증서 등에 대해서 알아보자. 좀 더 확장하면 RSA, ECC 등의 암호화 알고리즘을 공부해봐도 좋을 것 같다.(SSL 안에서 사용된다.)

  • Hashing Algorithms : MD5 and why not to use it, SHA Family, scrypt, bcrypt
  • HTTPS, CORS, Content Security Policy, SSL/TLS, OWASP Security Risks

 

Testing

테스트 기법과 테스트 자동화

  • Intergration Testing
  • Unit Testing
  • Functional Testing
  • Test Automation : 라노렉스, Python, Guitar 등

 

CI/ CD

DevOps와 더 큰 연관이 있다고 생각한다. 시간될 때 공부해두자.

  • GitOps : GitLab, ArgoCD
  • K8S 
  • Jenkins

 

Design Patterns and Development Principles

MVC, DDD, TDD 위주로 공부하자.

  • MVC (Model View Control)
  • GOF (Gang Of Four)
  • DDD (Domain Driven Design)
  • TDD (Test Driven Development)
  • SOLID
  • KISS
  • YAGNI
  • DRY

 

Architectural Patterns

아키텍쳐에 대한 내용을 알고 있는 것과 모르는 것의 차이는 크다고 생각한다. 

MA, MSA, Serverless 정도만 알면 될 것 같다.

  • MA (Monolithic Architecture)
  • MSA (Micro Services Architecture)
  • Serverless
  • SOA
  • CQRS and Event Sourcing

 

Search Engines

로그 등의 빅데이터를 수집하고 검색할 수 있는 엔진 (키바나는 가시화 도구)

  • Elasticsearch(+kibana)
  • Solr

 

Message Brokers

  • RabbitMQ
  • Kafka

 

Containerization vs Virtualization

컨테이너화와 가상화에 대한 내용인 듯 하다. 

Docker를 위주로 공부하자. (사용법, 컨테이너 개념)

  • Docker
  • rkt
  • LXC

 

Graph Databases

  • Neo4j

 

WebSockets

 

 

Web Servers

외부 사용자들의 접근을 위한 웹 서버들이다. config 작성방법과 백엔드(Python일 경우 Gunicon 또는 uwsgi) 연동을 해보면 좋을 것 같다.

  • Nginx
  • Apache
  • Caddy
  • MS IIS

 

Building for Scale

마이그레이션과 수직적, 수평적 확장을 제외하고 잘 모르겠다. 하나씩 알아보자.

  • Mitigation Strategies : Graceful, Degrodation, Throttling, Backpressure, LoadShifting, Circuit Breaker
  • Understand the Diff : Instrumentation, Monitoring, Telemetry
  • Migration Strategies
  • Horizontal vs Vertical Scaling
  • Building with Observability in mind

 

728x90
반응형
728x90
반응형

기본 Django 개발도구 설치 및 설정

Django가 설치된 환경은 Ubuntu 20.04 LTS 이다. python과 pip등 개발에 필요한 도구들을 설치해준다.

$ sudo apt-get install python3 pip3 venv vim

 

설치 후엔 좀 더 편하게 도구들을 사용하기 위해 .bashrc에 alias를 등록해준다.

각 사용자 계정에 위치한 .bashrc 파일을 열어 ll, la 등의 기본 alias 내용이 있는 곳에 같이 입력해주면 된다.

이 과정은 필수는 아니지만 개인적으로 해두면 편해서 만들어 줬다.

alias python='python3'
alias pip='pip3'
alias vi='vim'

 

입력 후엔 source .bashrc 명령어를 입력해준다.

$ source .bashrc

 


Venv 가상환경에 Django 패키지 설치하기

개발을 시작하기 전에 위에서 같이 설치해준 venv를 사용해서 파이썬 가상환경을 만들어준다.

가상환경을 만들면 프로젝트 단위로 패키지에 대한 관리가 자유롭다.

$ python -m venv test

 

test 라는 이름의 가상환경을 사용자 계정의 기본 경로 (/home/username/)에 만들어 줬다.

환경이 만들어지면 이미지처럼 기본적인 구조가 자동을 만들어진다.

하지만 아직 가상환경의 내부로 들어온 건 아니기 때문에 아래 명령어를 추가로 입력해 줘야 한다.

$ source bin/activate

 

bin 폴더 내부의 activate 스크립트를 실행하면 가상환경에 진입한다.

이제부터 pip로 원하는 패키지를 설치하면 해당 환경 내에서만 설치가된다.

django 설치가 완료되면 bin 폴더에 django-admin이 설치된걸 볼 수 있다.

$ pip install django

 


Django 프로젝트 생성 후 서버 실행

pip를 사용해서 장고를 설치하면 Django-admin이라는 툴이 같이 설치된 걸 볼 수 있다.

이 툴을 사용해 Django 프로젝트를 생성한다.

$ django-admin startproject project_name

 

projcet_name이라는 이름의 프로젝트가 잘 생성되었다.

 

웹서버를 실행해보자.

$ ./manage.py runserver 0:12345

 

빨간색 박스 부분에 migrate 경고가 뜬걸 볼 수 있는데, 실행은 되었기 때문에 다른 PC의 브라우저로 접속해보았다.

참고로 현재 서버는 다른 PC와 같은 공유기 밑에서 같은 c클래스 사설 아이피를 사용한다.

 

서버의 아이피와 포트를 입력해서 접속했지만 ALLOWED_HOSTS에 서버 IP를 추가하라는 경고 문구만 나온다.

위에서 만들어진 project_name/project_name 디렉토리 내부의 settings.py를 열어보면 아래 이미지와 같은 부분이 있다. ALLOWED_HOST =[] 에서 []사이에 서버 접속 시 사용할 주소를 'xxx.xxx.xxx.xxx' 형태로 넣어주면 된다. 

$ vi project_name/settings.py

 

서버를 실행할때 나왔던 경고도 없애주자.

아래 명령어로 migrate하면 된다.

$ ./manage.py makemigrations
$ ./manage.py migrate

 

서버를 실행해주고 다시 브라우저로 접속해보면 아래 이미지처럼 환영한다는 문구와 로켓 그림이 나온다.

 


 

 

728x90
반응형

+ Recent posts