본문 바로가기
구글 클라우드

구글 클라우드 가상머신 배포하기

by 미토리야 2022. 5. 19.

안녕하세요, 내일배움캠프 클라우드 실시간 강의 자료입니다.

 

목차

1. 구글 클라우드 ssh 인증서 생성 및 연결

2. 웹서버와 DB설치

3. FTP접속방법

4. 배포하기(예제파일 소스)

 

구글 클라우드 가상머신(VM:Virtual Machine)에서 배포 방법입니다.

가상머신 설치가 안되어 있다면 아래 링크에서 설치하시면 됩니다.

이번 시간에는 flask 웹서버를 설치해서 배포하는 방법을 알아보겠습니다.

VM 설치할 때 생성한 인증서를 사용합니다.

 

1. 구글 클라우드 ssh 인증서 생성 및 연결

 

*맥사용자의 경우, 인증서방식말고 브라우저 쉘 접속방법으로 진행합니다(하단참조).

먼저 로컬에서 ssh 명령어를 통해 인증서를 생성합니다. 

인증서를 생성하기 위해 putty gen이라는 프로그램을 이용합니다.

Putty gen 다운로드 링크 : https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

설치를 끝내고 설치된 항목 중에 putty gen 프로그램을 실행합니다.

아래와 같은 화면이 나오면, 정상적으로 키쌍이 생성된 것입니다.

Save public key 버튼을 누르면 public 키가 저장이 됩니다.

Save private key 버튼을 누르면 private 키가 저장이 됩니다.

정상적으로 인증키가 생성이 되었을까요? 해당 폴더에서 디렉토리 조회를 해봅니다.

첫번째 빨간색 박스에 생성된 키값을 GCP 콘솔에 접속->메타데이터에 입력합니다.

GCP의 경우, 메타데이터 등록과정 후에 ssh 접속을 하실 수 있습니다.

아래 GCP 콘솔 사이트로 접속합니다.

https://console.cloud.google.com/

 

Google Cloud Platform

하나의 계정으로 모든 Google 서비스를 Google Cloud Platform을 사용하려면 로그인하세요.

accounts.google.com

정상적으로 로그인이 된다면, 검색창에 가상머신 리소스를 검색합니다.

왼쪽에 있는 메타데이터를 클릭합니다.

 

메타데이터에 메뉴접속해서 SSH키값에 복사한 키값을 입력 후, 저장 버튼을 누릅니다.

 

정상적으로 저장이 되었다면,  브라우저 명령창에서 라이브러리를 설치합니다.

 

*맥 사용자의 경우, 브라우저 명령창을 이용해서 웹서버 설치와 파일업로드를 진행합니다.

아래 가상머신을 설치한 화면에서 오른쪽 동그라미세개를 누르면 선택창이 아래와 같이 뜹니다.

아래와 같이 명령어 창이 뜨게 됩니다.

명령어창에서 상단에 업로드 버튼을 누릅니다. 

파일 선택을 한 후, 업로드할 파일을 입력합니다.

 

정상적으로 업로드가 완료되면, ls 명령어로 조회합니다.

 

2. 웹서버와 DB설치

 

명령어 창을 콘솔에서 실행해 보겠습니다.

가상머신을 선택한 후, 오른쪽에 동그라미 세개를 클릭하고 브라우저 창에서 열기를 클릭합니다.

브라우저 명령어창 접속을 확인합니다.

명령어창에 접속이 되었다면, 가상머신에 웹서버 flask를 설치합니다.

 

<로컬에서 ssh 인증키 접속 방법>
생성한 키쌍중에서 public key(*.ppk)를 pem 파일로 변환합니다.

putty gen을 실행합니다. Load 버튼을 누릅니다.

생성한 키쌍 중에서 ~.ppk파일을 불러옵니다.

불러온 상태입니다.

메뉴 Conversions->Export OpenSSH key 선택

~.pem 파일로 저장합니다.

아래와 같은 명령어로 접속하여 웹서버와 DB 설치를 합니다.

ssh -i gcpkey.pem rsa-key-20220519@{IP주소}

 

먼저 pip을 설치합니다.

* pip : 파이썬 패키지 소프트웨어를 설치하거나 관리하는

패키지 관리 라이브러리입니다.

sudo apt install python3-pip

웹서버 사용을 위해 다음 명령어로 flask를 설치합니다.

 

pip install flask

DB사용을 위해 다음 명령어로 pymongo를 설치합니다.

pip install pymongo

 

로컬에서 설치한 flask와 pymongo가

이제는 GCP클라우드 VM에 설치가 되었습니다.

이제 로컬에서 처럼 브라우저 URL에 VM의 IP를 입력하시면 됩니다.

하지만, 먼저 배포를 해야 실행이 되겠죠?

정상적으로 웹서버와 DB가 설치되었다면, 

다음 순서로 넘어갑니다.

 

3. FTP접속방법

 

웹서버 셋팅도 끝나고, 이제 웹서버에 파일을 올려서 배포를 해봐야 겠죠?

파일 ftp접속을 위해 파일질라 프로그램을 실행합니다.

 

웹서버에 배포하기 위해 ftp 접속 설정을 합니다.

 

<FTP 연결 방법>
① 보안SFTP로 설정합니다.
② GCP클라우드 가상머신(VM) IP주소를 설정합니다.
③ 보안 22번 포트번호를 설정합니다.
④ 키파일 접속방식으로 변경합니다.
⑤ GCP클라우드 가상머신(VM) 접속 아이디를 입력합니다. 
⑥ GCP클라우드 가상머신(VM) 생성시에
    만들었던 키파일(abc.pem) 경로를 입력합니다.
⑦ 입력이 끝났으면, 연결을 클릭합니다.

 

'연결' 버튼을 누른 후, 아래와 같이 접속이 되면 정상적으로 연결된 것입니다.

FTP 접속이 되었나요?

웹서버와 DB가 설치되었으므로, 이제 프로그램 파일을 올리시면 됩니다.

 

4. 배포하기(예제파일 소스)

 

예제파일 소스입니다.

예제파일은 3가지로 구성되어 있습니다.

 

app.py - 웹서버 실행

index.html - 프론트페이지

login.html - 프론트페이지

 

app.py

from pymongo import MongoClient
import jwt
import datetime
import hashlib
from flask import Flask, render_template, jsonify, request, redirect, url_for
from werkzeug.utils import secure_filename
from datetime import datetime, timedelta


app = Flask(__name__)
app.config["TEMPLATES_AUTO_RELOAD"] = True
app.config['UPLOAD_FOLDER'] = "./static/profile_pics"

SECRET_KEY = 'SPARTA'

client = MongoClient('18.188.71.218', 27017, username="sparta", password="qwer1234")
db = client.dbsparta_plus_week4


@app.route('/')
def home():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.users.find_one({"username": payload["id"]})
        return render_template('index.html', user_info=user_info)
    except jwt.ExpiredSignatureError:
        return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
    except jwt.exceptions.DecodeError:
        return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))


@app.route('/login')
def login():
    msg = request.args.get("msg")
    return render_template('login.html', msg=msg)


@app.route('/user/<username>')
def user(username):
    # 각 사용자의 프로필과 글을 모아볼 수 있는 공간
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        status = (username == payload["id"])  # 내 프로필이면 True, 다른 사람 프로필 페이지면 False

        user_info = db.users.find_one({"username": username}, {"_id": False})
        return render_template('user.html', user_info=user_info, status=status)
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))


@app.route('/sign_in', methods=['POST'])
def sign_in():
    # 로그인
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']

    pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    result = db.users.find_one({'username': username_receive, 'password': pw_hash})

    if result is not None:
        payload = {
         'id': username_receive,
         'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24)  # 로그인 24시간 유지
        }
        token = jwt.encode(payload, SECRET_KEY, algorithm='HS256').decode('utf-8')

        return jsonify({'result': 'success', 'token': token})
    # 찾지 못하면
    else:
        return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})


@app.route('/sign_up/save', methods=['POST'])
def sign_up():
    username_receive = request.form['username_give']
    password_receive = request.form['password_give']
    password_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
    doc = {
        "username": username_receive,
        "password": password_hash,
        "profile_name": username_receive,
        "profile_pic": "",
        "profile_pic_real": "profile_pics/profile_placeholder.png",
        "profile_info": ""
    }
    db.users.insert_one(doc)
    return jsonify({'result': 'success'})


@app.route('/sign_up/check_dup', methods=['POST'])
def check_dup():
    username_receive = request.form['username_give']
    exists = bool(db.users.find_one({"username": username_receive}))
    # print(value_receive, type_receive, exists)
    return jsonify({'result': 'success', 'exists': exists})


@app.route('/update_profile', methods=['POST'])
def save_img():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        username = payload["id"]
        name_receive = request.form["name_give"]
        about_receive = request.form["about_give"]
        new_doc = {
            "profile_name": name_receive,
            "profile_info": about_receive
        }
        if 'file_give' in request.files:
            file = request.files["file_give"]
            filename = secure_filename(file.filename)
            extension = filename.split(".")[-1]
            file_path = f"profile_pics/{username}.{extension}"
            file.save("./static/"+file_path)
            new_doc["profile_pic"] = filename
            new_doc["profile_pic_real"] = file_path
        db.users.update_one({'username': payload['id']}, {'$set':new_doc})
        return jsonify({"result": "success", 'msg': '프로필을 업데이트했습니다.'})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))


@app.route('/posting', methods=['POST'])
def posting():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        # 포스팅하기
        user_info = db.users.find_one({"username": payload["id"]})
        comment_receive = request.form["comment_give"]
        date_receive = request.form["date_give"]
        print(type(date_receive))
        doc = {
            "username": user_info["username"],
            "profile_name": user_info["profile_name"],
            "profile_pic_real": user_info["profile_pic_real"],
            "comment": comment_receive,
            "date": date_receive
        }
        db.posts.insert_one(doc)
        return jsonify({"result": "success", 'msg': '포스팅 성공'})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))


@app.route("/get_posts", methods=['GET'])
def get_posts():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        username_receive = request.args.get("username_give")
        if username_receive=="":
            posts = list(db.posts.find({}).sort("date", -1).limit(20))
        else:
            posts = list(db.posts.find({"username":username_receive}).sort("date", -1).limit(20))
        for post in posts:
            post["_id"] = str(post["_id"])
            post["count_heart"] = db.likes.count_documents({"post_id": post["_id"], "type": "heart"})
            post["heart_by_me"] = bool(db.likes.find_one({"post_id": post["_id"], "type": "heart", "username": my_username}))

        return jsonify({"result": "success", "msg": "포스팅을 가져왔습니다.", "posts": posts, "my_username":payload["id"]})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))


@app.route('/update_like', methods=['POST'])
def update_like():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        # 좋아요 수 변경
        user_info = db.users.find_one({"username": payload["id"]})
        post_id_receive = request.form["post_id_give"]
        type_receive = request.form["type_give"]
        action_receive = request.form["action_give"]
        doc = {
            "post_id": post_id_receive,
            "username": user_info["username"],
            "type": type_receive
        }
        if action_receive =="like":
            db.likes.insert_one(doc)
        else:
            db.likes.delete_one(doc)
        count = db.likes.count_documents({"post_id": post_id_receive, "type": type_receive})
        print(count)
        return jsonify({"result": "success", 'msg': 'updated', "count": count})
    except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError):
        return redirect(url_for("home"))


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

 

index.html

<!doctype html>
<html lang="en">
    <head>

        <!-- Webpage Title -->
        <title>Home | GCP 클라우드 웹앱</title>
        <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
        <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
        <meta property="og:title" content="GCP 클라우드 웹앱"/>
        <meta property="og:description" content="mini project for Web Plus"/>
        <meta property="og:image" content="{{ url_for('static', filename='ogimg.png') }}"/>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <!-- Bulma CSS -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
        <!-- Font Awesome CSS -->
        <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

        <link rel="preconnect" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Gamja+Flower&family=Stylish&display=swap" rel="stylesheet">
        <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
        <script src="{{ url_for('static', filename='myjs.js') }}"></script>


        <script>
            $(document).ready(function () {
                get_posts()
            })

        </script>

    </head>
    <body class="has-navbar-fixed-top">
        <nav class="navbar is-fixed-top is-white" role="navigation" aria-label="main navigation">
            <div class="navbar-brand">
                <a class="navbar-item" href="/">
                    <img src="{{ url_for('static', filename='logo.png') }}">
                    <strong class="is-sparta"
                            style="font-family: 'Stylish', sans-serif;font-size: larger;">GCP 클라우드 웹앱</strong>
                </a>
            </div>
        </nav>
 <script>
$(document).ready(function(){
  var fileTarget = $('.filebox .upload-hidden');

    fileTarget.on('change', function(){
        if(window.FileReader){
            var filename = $(this)[0].files[0].name;
        } else {
            var filename = $(this).val().split('/').pop().split('\\').pop();
        }

        $(this).siblings('.upload-name').val(filename);
    });
});

 </script>

        <section class="section">
            <article class="media">
                <figure class="media-left" style="align-self: center">
              <a class="image is-32x32" href="/user/{{ user_info.username }}">
                  <img class="is-rounded" src="{{ url_for('static', filename=user_info.profile_pic_real) }}">
              </a>
                </figure>
                <div class="media-content">
                    <div class="field">
                        <p class="control">
                            <input id="input-post" class="input is-rounded" placeholder="무슨 생각을 하고 계신가요?"
                                   onclick='$("#modal-post").addClass("is-active")'>
                        </p>
                    </div>
                </div>
            </article>
            <div class="modal" id="modal-post">
                <div class="modal-background" onclick='$("#modal-post").removeClass("is-active")'></div>
                <div class="modal-content">
                    <div class="box">
                        <article class="media">
                            <div class="media-content">
                                <div class="field">
                                    <p class="control">
                                        <textarea id="textarea-post" class="textarea"
                                                  placeholder="무슨 생각을 하고 계신가요?"></textarea>
                                    </p>
                                </div>
                                <nav class="level is-mobile">
                                    <div class="level-left">

                                    </div>
                                    <div class="level-right">
                                        <div class="level-item">
                                            <a class="button is-sparta" onclick="post()">포스팅하기</a>
                                        </div>
                                        <div class="level-item">
                                            <a class="button is-sparta is-outlined"
                                               onclick='$("#modal-post").removeClass("is-active")'>취소</a>
                                        </div>
                                    </div>
                                </nav>
                            </div>
                        </article>
                    </div>
                </div>
                <button class="modal-close is-large" aria-label="close"
                        onclick='$("#modal-post").removeClass("is-active")'></button>
            </div>

        </section>
        <section class="section">
            <div id="post-box" class="container">
                <div class="box">
                    <article class="media">
                        <div class="media-left">
                            <a class="image is-64x64" href="#">
                                <img class="is-rounded"
                                     src={{ url_for("static", filename="profile_pics/profile_placeholder.png") }} alt="Image">
                            </a>
                        </div>
                        <div class="media-content">
                            <div class="content">
                                <p>
                                    <strong>홍길동</strong> <small>@username</small> <small>10분 전</small>
                                    <br>
                                    글을 적는 칸
                                </p>
                            </div>
                            <nav class="level is-mobile">
                                <div class="level-left">
                                    <a class="level-item is-sparta" aria-label="heart"
                                       onclick="toggle_like('', 'heart')">
                                        <span class="icon is-small"><i class="fa fa-heart"
                                                                       aria-hidden="true"></i></span>&nbsp;<span
                                            class="like-num">2.7k</span>
                                    </a>
                                </div>

                            </nav>
                        </div>
                    </article>
                </div>

            </div>
        </section>
    </body>
</html>

 

login.html

<!doctype html>
<html lang="en">
    <head>

        <!-- Webpage Title -->
        <title>Log In | GCP 클라우드웹앱</title>
        <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
        <link rel="icon" href="{{ url_for('static', filename='favicon_new.ico') }}" type="image/x-icon">
        <meta property="og:title" content="GCP클라우드 - 웹앱"/>
        <meta property="og:description" content="mini project for Web Plus"/>
        <meta property="og:image" content="{{ url_for('static', filename='ogimg.png') }}"/>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <!-- Bulma CSS -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
        <!-- Font Awesome CSS -->
        <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

        <link rel="preconnect" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Gamja+Flower&family=Stylish&display=swap" rel="stylesheet">
        <link href="{{ url_for('static', filename='mystyle.css') }}" rel="stylesheet">

        <!-- JS -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>


        <style>


            .title {
                font-weight: 800;
                font-size: 5rem;
                font-family: 'Stylish', sans-serif;
            }

            .subtitle {
                font-family: 'Gamja Flower', cursive;
                font-size: 2rem;

            }

            .help {
                color: gray;
            }
        </style>
        <script>
            // {% if msg %}
                //    alert("{{ msg }}")
                // {% endif %}

            function toggle_sign_up() {
                $("#sign-up-box").toggleClass("is-hidden")
                $("#div-sign-in-or-up").toggleClass("is-hidden")
                $("#btn-check-dup").toggleClass("is-hidden")
                $("#help-id").toggleClass("is-hidden")
                $("#help-password").toggleClass("is-hidden")
                $("#help-password2").toggleClass("is-hidden")
            }

            function is_nickname(asValue) {
                var regExp = /^(?=.*[a-zA-Z])[-a-zA-Z0-9_.]{2,10}$/;
                return regExp.test(asValue);
            }

            function is_password(asValue) {
                var regExp = /^(?=.*\d)(?=.*[a-zA-Z])[0-9a-zA-Z!@#$%^&*]{8,20}$/;
                return regExp.test(asValue);
            }

            function check_dup() {
                let username = $("#input-username").val()
                console.log(username)
                if (username == "") {
                    $("#help-id").text("아이디를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-username").focus()
                    return;
                }
                if (!is_nickname(username)) {
                    $("#help-id").text("아이디의 형식을 확인해주세요. 영문과 숫자, 일부 특수문자(._-) 사용 가능. 2-10자 길이").removeClass("is-safe").addClass("is-danger")
                    $("#input-username").focus()
                    return;
                }
                $("#help-id").addClass("is-loading")
                $.ajax({
                    type: "POST",
                    url: "/sign_up/check_dup",
                    data: {
                        username_give: username
                    },
                    success: function (response) {

                        if (response["exists"]) {
                            $("#help-id").text("이미 존재하는 아이디입니다.").removeClass("is-safe").addClass("is-danger")
                            $("#input-username").focus()
                        } else {
                            $("#help-id").text("사용할 수 있는 아이디입니다.").removeClass("is-danger").addClass("is-success")
                        }
                        $("#help-id").removeClass("is-loading")

                    }
                });
            }

            function sign_up() {
                let username = $("#input-username").val()
                let password = $("#input-password").val()
                let password2 = $("#input-password2").val()
                console.log(username, password, password2)


                if ($("#help-id").hasClass("is-danger")) {
                    alert("아이디를 다시 확인해주세요.")
                    return;
                } else if (!$("#help-id").hasClass("is-success")) {
                    alert("아이디 중복확인을 해주세요.")
                    return;
                }

                if (password == "") {
                    $("#help-password").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-password").focus()
                    return;
                } else if (!is_password(password)) {
                    $("#help-password").text("비밀번호의 형식을 확인해주세요. 영문과 숫자 필수 포함, 특수문자(!@#$%^&*) 사용가능 8-20자").removeClass("is-safe").addClass("is-danger")
                    $("#input-password").focus()
                    return
                } else {
                    $("#help-password").text("사용할 수 있는 비밀번호입니다.").removeClass("is-danger").addClass("is-success")
                }
                if (password2 == "") {
                    $("#help-password2").text("비밀번호를 입력해주세요.").removeClass("is-safe").addClass("is-danger")
                    $("#input-password2").focus()
                    return;
                } else if (password2 != password) {
                    $("#help-password2").text("비밀번호가 일치하지 않습니다.").removeClass("is-safe").addClass("is-danger")
                    $("#input-password2").focus()
                    return;
                } else {
                    $("#help-password2").text("비밀번호가 일치합니다.").removeClass("is-danger").addClass("is-success")
                }
                $.ajax({
                    type: "POST",
                    url: "/sign_up/save",
                    data: {
                        username_give: username,
                        password_give: password
                    },
                    success: function (response) {
                        alert("회원가입을 축하드립니다!")
                        window.location.replace("/login")
                    }
                });

            }

            function sign_in() {
                let username = $("#input-username").val()
                let password = $("#input-password").val()

                if (username == "") {
                    $("#help-id-login").text("아이디를 입력해주세요.")
                    $("#input-username").focus()
                    return;
                } else {
                    $("#help-id-login").text("")
                }

                if (password == "") {
                    $("#help-password-login").text("비밀번호를 입력해주세요.")
                    $("#input-password").focus()
                    return;
                } else {
                    $("#help-password-login").text("")
                }
                $.ajax({
                    type: "POST",
                    url: "/sign_in",
                    data: {
                        username_give: username,
                        password_give: password
                    },
                    success: function (response) {
                        if (response['result'] == 'success') {
                            $.cookie('mytoken', response['token'], {path: '/'});
                            window.location.replace("/")
                        } else {
                            alert(response['msg'])
                        }
                    }
                });
            }


        </script>

    </head>
    <body>
        <section class="hero is-white">
            <div class="hero-body has-text-centered" style="padding-bottom:1rem;margin:auto;">
                <h1 class="title is-sparta">GCP 클라우드</h1>
                <h3 class="subtitle is-sparta">웹앱</h3>
            </div>
        </section>
        <section class="section">
            <div class="container">
                <div class="box" style="max-width: 480px;margin:auto">
                    <article class="media">
                        <div class="media-content">
                            <div class="content">
                                <div class="field has-addons">
                                    <div class="control has-icons-left" style="width:100%">
                                        <input id="input-username" class="input" type="text" placeholder="아이디">
                                        <span class="icon is-small is-left"><i class="fa fa-user"></i></span>
                                    </div>
                                    <div id="btn-check-dup" class="control is-hidden">
                                        <button class="button is-sparta" onclick="check_dup()">중복확인</button>
                                    </div>

                                </div>
                                <p id="help-id" class="help is-hidden">아이디는 2-10자의 영문과 숫자와 일부 특수문자(._-)만 입력 가능합니다.</p>
                                <p id="help-id-login" class="help is-danger"></p>

                                <div class="field">
                                    <div class="control has-icons-left">
                                        <input id="input-password" class="input" type="password" placeholder="비밀번호">
                                        <span class="icon is-small is-left"><i class="fa fa-lock"></i></span>
                                    </div>
                                    <p id="help-password" class="help is-hidden">영문과 숫자 조합의 8-20자의 비밀번호를 설정해주세요.
                                        특수문자(!@#$%^&*)도 사용 가능합니다.</p>
                                    <p id="help-password-login" class="help is-danger"></p>

                                </div>


                            </div>
                            <div id="div-sign-in-or-up" class="has-text-centered">
                                <nav class="level is-mobile">
                                    <button class="level-item button is-sparta" onclick="sign_in()">
                                        로그인
                                    </button>

                                </nav>
                                <hr>
                                <h4 class="mb-3">아직 회원이 아니라면</h4>
                                <nav class="level is-mobile">

                                    <button class="level-item button is-sparta is-outlined"
                                            onclick="toggle_sign_up()">
                                        회원가입하기
                                    </button>
                                </nav>
                            </div>

                            <div id="sign-up-box" class="is-hidden">
                                <div class="mb-5">
                                    <div class="field">
                                        <div class="control has-icons-left" style="width:100%">
                                            <input id="input-password2" class="input" type="password"
                                                   placeholder="비밀번호 재입력">
                                            <span class="icon is-small is-left"><i class="fa fa-lock"></i></span>
                                        </div>
                                        <p id="help-password2" class="help is-hidden">비밀번호를 다시 한 번 입력해주세요.</p>

                                    </div>
                                </div>
                                <nav class="level is-mobile">
                                    <button class="level-item button is-sparta" onclick="sign_up()">
                                        회원가입
                                    </button>
                                    <button class="level-item button is-sparta is-outlined" onclick="toggle_sign_up()">
                                        취소
                                    </button>
                                </nav>
                            </div>


                        </div>
                    </article>
                </div>

            </div>
        </section>

    </body>
</html>

 

웹서버에서 실행 후, 사이트 실행화면입니다.