iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
DevOps

關於我幫新公司建立整套部屬流程那檔事系列 第 9

EP09 - 建立 Django 專案和 EC2 環境 並手動部署到 EC2

前幾天的打底,
把 Gitlab、Jenkins 建好,
但是仍然少了最重要的主角,
要部署的服務本身,
今天我們終於要建立一個 Portal 來部署囉,
以下 Portal 建立會以 Python Django 為例,
挑選 Python Django 是因為大多數 Web 服務器都不是用 Python 所撰寫,
需要另外設定接口才能夠讓服務器和 Python 溝通,
再加上 Python 有提供虛擬環境(Virtual Environment),
在套件管理上不需要仰賴全域的配置,
個人是覺得 Python 2 和 Python 3 有 Break 才會有這功能
很少在建置陽春的環境時,
就需要做這麼麻煩的設定,
因此很適合當作教學使用,
已經會建立專案的,
可以跳過專案建立步驟去看 EC2 和 WSGI 的配置,
如果兩個都會的,
估計手動部署到 EC2 也不成問題,
可以直接跳過今天的教學休息一天。

建立 Django 專案

check out code

前天 EP07
我們已經建立一個 portal 的 repository
接下來我們就需要再次進到 vagrant 的 project 中
把 code check out 下來

切換目錄

cd /vagrant_data/project/

check out code

git clone git@你的IP:ithome-ironman-2021/portal.git

建立 develop 分支

cd portal
git branch develop
git push -u origin develop

確認 python 環境

當時我們挑選的 Ubuntu 20.04
已經內建 python3

不過保險起見
還是先確認版本和位置

python --version
whereis python 3

如果不幸你的 Linux 版本比較舊
都沒有預先安裝 Python 3
建議可參考 在Linux上安装Python 3

建立虛擬環境

如果我們有一個以上的 Python 專案
此時我們就需要思考每個環境的 Python 版本和套件版本是否一致

建立新的目錄

我們在 /vagrant_data 中建立新的資料夾 venv
以便可以在 vagrant 建立的虛擬機械內外
都可以操作

cd /vagrant_data/
sudo mkdir vevn
cd venv

安裝 pip3 套件

sudo apt-get install python3-pip

確認 pip3 版本

安裝好後輸入 pip3 指令
查看指令是否可以正常運作

pip3 --version

安裝 virtualenv

sudo pip3 install virtualenv

新增一個 virtualenv

先查看 virtualenv 的位置後

pip3 show virtualenv

在用 python 執行建立 virtualenv
建立名為 ithome-ironman-portal 的虛擬環境

python3 /home/vagrant/.local/lib/python3.8/site-packages/virtualenv ithome-ironman-portal

啟動虛擬環境

使用 source 或是 . ithome-ironman-portal/bin/activate
都可以啟動虛擬環境

source ithome-ironman-portal/bin/activate

啟動後就可以看到 console 前會出現 (ithome-ironman-portal) 的字樣
https://ithelp.ithome.com.tw/upload/images/20210920/20141518MFFZ17P0zF.png

如果是使用 windows
無法正常啟用虛擬環境
或是啟用後安裝套件都會把全域的套件版本蓋掉的
可以參考這篇
有可能是因為在 windows 上的 path 設定了 python 的路徑
導致 virtualenv 運作有問題

確認目前的的套件

想知道是否有正常設置
還可以執行下列語法

pip3 list

一個乾淨的 virtualenv 環境
在初始化後應該只會看到兩三個基本套件
https://ithelp.ithome.com.tw/upload/images/20210920/20141518milHDhU2Bu.png

使用 django-admin 建立專案

安裝 Django

python -m pip install Django

https://ithelp.ithome.com.tw/upload/images/20210920/20141518JJ0XGmJQvH.png

使用 django-admin 建立 portal

其實只要下 django-admin startproject portal
就可以產生一個專案
不過因為我們專案是從 Gitlab 上 clone 下來的
又因為起專案時
只能指定空的資料夾
因此我們只能先在一個地方起專案後
再將專案內的東西搬移到 project 底下的 portal 裏面

cd /vagrant_data
django-admin startproject portal
sudo cp -R /vagrant_data/portal/*.* /vagrant_data/project/portal
sudo cp -R /vagrant_data/portal/portal/ /vagrant_data/project/portal/
sudo rm -rf /vagrant_data/portal/
cd /vagrant_data/project/portal/

啟動 server

接下來只要啟動
server 就可以運作了

python manage.py runserver 0.0.0.0:8000

https://ithelp.ithome.com.tw/upload/images/20210920/20141518S5OlrOK8b5.png

vagrant port forward

但我們使用瀏覽器依舊無法看到啟動的 server
此時別慌
電腦沒有壞,安裝過程也正常
是因為我們在虛擬機械裡面起 server
但是 port 沒有轉發到虛擬機械外面
此時我們需要修改 Vagrantfile
在 config.vm.box 下方
新增一個 port 轉發的設定

.
.
.
  config.vm.box = "ubuntu/focal64"
  config.vm.network "forwarded_port", guest: 8000, host: 8000
.
.
.

重啟 vagrant

重啟 vagrant 不需要下 vagrant halt 再 vagrant up

vagrant reload

啟動 server

source /vagrant_data/venv/ithome-ironman-portal/bin/activate
cd /vagrant_data/project/portal
python manage.py runserver 0.0.0.0:8000

https://ithelp.ithome.com.tw/upload/images/20210920/20141518A7gEqijsNO.png

https://ithelp.ithome.com.tw/upload/images/20210921/20141518zF3xFXHxAM.png

關於 vagrant 內為什麼要 runserver 0.0.0.0:8000
大家可參考「Connection Reset when port forwarding with Vagrant」和「Connection Reset when port forwarding with Vagrant」的討論

套件管理

剛剛我們建立虛擬環境
而且還安裝套件都只存在於本機
但是實際上多人協作
不會把 python 和虛擬環境進版控
而是會將套件版本記錄起來進版控
在 python 中的套件管理工具是 pip
而記錄這些套件的檔案則是 requirements.txt
只要下個指令就可以把目前虛擬環境中的套件記錄起來

pip freeze > requirements.txt

增加 .gitignore

# Django #
*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media

# Backup files # 
*.bak 

# If you are using PyCharm # 
.idea/**/workspace.xml 
.idea/**/tasks.xml 
.idea/dictionaries 
.idea/**/dataSources/ 
.idea/**/dataSources.ids 
.idea/**/dataSources.xml 
.idea/**/dataSources.local.xml 
.idea/**/sqlDataSources.xml 
.idea/**/dynamic.xml 
.idea/**/uiDesigner.xml 
.idea/**/gradle.xml 
.idea/**/libraries 
*.iws /out/ 

# Python # 
*.py[cod] 
*$py.class 

# Distribution / packaging 
.Python build/ 
develop-eggs/ 
dist/ 
downloads/ 
eggs/ 
.eggs/ 
lib/ 
lib64/ 
parts/ 
sdist/ 
var/ 
wheels/ 
*.egg-info/ 
.installed.cfg 
*.egg 
*.manifest 
*.spec 

# Installer logs 
pip-log.txt 
pip-delete-this-directory.txt 

# Unit test / coverage reports 
htmlcov/ 
.tox/ 
.coverage 
.coverage.* 
.cache 
.pytest_cache/ 
nosetests.xml 
coverage.xml 
*.cover 
.hypothesis/ 

# Jupyter Notebook 
.ipynb_checkpoints 

# pyenv 
.python-version 

# celery 
celerybeat-schedule.* 

# SageMath parsed files 
*.sage.py 

# Environments 
.env 
.venv 
env/ 
venv/ 
ENV/ 
env.bak/ 
venv.bak/ 

# mkdocs documentation 
/site 

# mypy 
.mypy_cache/ 

# Sublime Text # 
*.tmlanguage.cache 
*.tmPreferences.cache 
*.stTheme.cache 
*.sublime-workspace 
*.sublime-project 

# sftp configuration file 
sftp-config.json 

# Package control specific files Package 
Control.last-run 
Control.ca-list 
Control.ca-bundle 
Control.system-ca-bundle 
GitHub.sublime-settings 

# Visual Studio Code # 
.vscode/* 
!.vscode/settings.json 
!.vscode/tasks.json 
!.vscode/launch.json 
!.vscode/extensions.json 
.history

修改資料庫設定

settings.py 修改 DATABASE

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': '',
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '5432'
    }
}

requirements.txt 新增一條

psycopg2==2.8.6

為 Django 建立 EC2 環境

已經連續教好幾天要怎麼建置 EC2 了
不會還要教吧?
沒關係,送佛上西天
我們直接附上解答
讓你把 EC2、security group、security group rule 都配置好

Terraform 建立 EC2

比較特別的是
這裡有特別加入 port 3128
因為聽說 pip install 是走 3128

resource "aws_security_group" "ithome_ironman_portal" {
    name        = "ithome-ironman-portal"
    description = "It is used for ithome ironman 2021 portal."
    vpc_id      = data.aws_vpc.default.id
    tags        = { Name = "ithome ironman 2021" }
    revoke_rules_on_delete = null
}

resource "aws_security_group_rule" "ithome_ironman_igress_22" {
    type              = "ingress"
    from_port         = 22
    to_port           = 22
    cidr_blocks       = [var.personal_cidr,]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_22" {
    type              = "egress"
    from_port         = 22
    to_port           = 22
    cidr_blocks       = [var.personal_cidr,]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_igress_80" {
    type              = "ingress"
    from_port         = 80
    to_port           = 80
    cidr_blocks       = [var.personal_cidr,]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_80" {
    type              = "egress"
    from_port         = 80
    to_port           = 80
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_igress_443" {
    type              = "ingress"
    from_port         = 443
    to_port           = 443
    cidr_blocks       = [var.personal_cidr,]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_443" {
    type              = "egress"
    from_port         = 443
    to_port           = 443
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_ingress_3128" {
    type              = "ingress"
    from_port         = 3128
    to_port           = 3128
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "aws_security_group_rule" "ithome_ironman_egress_3128" {
    type              = "egress"
    from_port         = 3128
    to_port           = 3128
    cidr_blocks       = ["0.0.0.0/0",]
    protocol          = "tcp"
    security_group_id = aws_security_group.ithome_ironman_portal.id
}

resource "tls_private_key" "ithome_ironman_portal" {
    algorithm = "RSA"
    rsa_bits  = 4096
}

resource "aws_key_pair" "ithome_ironman_portal" {
    key_name   = "portal"
    public_key = tls_private_key.ithome_ironman_portal.public_key_openssh
}

resource "local_file" "ithome_ironman_portal" {
    content  = tls_private_key.ithome_ironman_portal.private_key_pem
    filename = format("%s.pem", aws_key_pair.ithome_ironman_portal.key_name)
}

resource "aws_instance" "ithome_ironman_portla" {
    ami                     = data.aws_ami.ubuntu.id
    instance_type           = "t3.small"
    subnet_id               = sort(data.aws_subnet_ids.subnet_ids.ids)[0]
    key_name                = aws_key_pair.ithome_ironman_portal.key_name
    vpc_security_group_ids  = [ aws_security_group.ithome_ironman_portal.id ]
    disable_api_termination = false
    ebs_optimized           = true
    hibernation             = false
    
    tags = {
        Name  = "ithome ironman 2021 portal"
        Usage = "portal"
        Creator = "Terraform"
    }

    root_block_device {
        delete_on_termination = true
        encrypted             = false
        throughput            = 0
        volume_size           = 9
        volume_type           = "gp2"
        tags                  = {
            Name     = "ithome ironman 2021 portal"
            Attached = "ithome ironman 2021 portal"
        }
    }
}

連到 EC2 portal

sudo chmod 400 portal.pem
ssh -i "portal.pem" ubuntu@你的HOST

配置環境

更新

sudo apt-get update
sudo apt-get upgrdae -y

安裝 apache2

sudo apt-get install apache2

安裝其他所需套件

sudo apt-get install python3 python3-virtualenv python3-pip libpq-dev python-dev

建立虛擬環境資料夾

sudo mkdir /var/www/venv

更改虛擬環境資料夾擁有者

如果不更改擁有者
或是沒使用 chmod 更改資料夾/檔案權限
等等在進行安裝 mod_wsgi 時會失敗

sudo chown -R ubuntu /var/www/venv

查看 virtualenv 位置

pip3 show virtualenv

建立虛擬環境

sudo python3 /usr/lib/python3/dist-packages/virtualenv /var/www/venv/portal

啟動虛擬環境

source /var/www/venv/portal/bin/activate

安裝 mod_wsgi

pip install -vvv mod_wsgi

查看 wsgi 模組位置

將 module 位置複製起來
等等在 confige apache 時會用上

pip show mod-wsgi

apache2 設定 wsgi

剛剛查到的 wsgi 模組位置
會用在 LoadModule wsgi_module 上
需要將路徑替換掉 你的路徑/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so
apache 的 conf 檔位於 /etc/apache2/apache2.conf 中

WSGIPythonHome 是設定剛剛 virtualenv 的路徑
WSGIPythonPath 則是設定專案的根目錄

在 conf 底下加相對應的參數大致如下

ServerName localhost
Alias /static/ /var/www/portal/static
<Directory /var/www/portal/static>
  Require all granted
</Directory>

LoadModule wsgi_module "/var/www/venv/portal/lib/python3.8/site-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so"
WSGIPythonHome /var/www/venv/portal
WSGIPythonPath /var/www/portal
WSGIScriptAlias / /var/www/portal/portal/wsgi.py
<Directory /var/www/portal/portal>
  <Files wsgi.py>
    Require all granted
  </Files>
</Directory>

部署

打包程式碼

如果直接將資料夾封存
會遇到一個問題
就是

git archive --format=tar.gz --output ./portal.tar.gz HEAD

scp

scp -i "/vagrant_data/project/terraform/stage/portal.pem" ./portal.tar.gz  ubuntu@你的HOST:~/portal.tar.gz

連到 EC2 portal

ssh -i "/vagrant_data/project/terraform/stage/portal.pem" ubuntu@你的HOST

移動程式碼

sudo mkdir /var/www/portal
sudo mv ~/portal.tar.gz /var/www/portal

解壓縮程式碼

cd /var/www/portal
sudo tar zxvf portal.tar.gz 

更改讀寫權限

sudo chmod 755 -R /var/www/portal

安裝套件

安裝套件前
記得要先啟動虛擬環境
source /var/www/venv/portal/bin/activate

pip install -r /var/www/portal/requirements.txt 

重啟 apache2

sudo service apache2 restart

如果重啟遇到這問題
表示已經離成功不遠了
這是 Django 的一個安全機制
需要在 settings.py 的 ALLOWED_HOSTS 裡面加 EC2 的 IP(或是連入的HOST NAME)
https://ithelp.ithome.com.tw/upload/images/20210921/201415184p6Q5diMPz.png

加入後再 restart apache2
就可以正常啟動囉

https://ithelp.ithome.com.tw/upload/images/20210921/20141518B16v7RwWXZ.png

參考資料:

  1. 在Linux上安装Python 3
  2. Python 使用 pip3 建立虛擬環境 venv
  3. Installing Multiple Python Versions on Windows Using Virtualenv
  4. Connection Reset when port forwarding with Vagrant
  5. Connection Reset when port forwarding with Vagrant
  6. LoadModule wsgi_module modules/mod_wsgi.so for Apache with Django
  7. How to use Django with Apache and mod_wsgi

上一篇
EP08 - 用 Terraform 建置 AWS RDS 服務(以 Aurora Postgres 為例)
下一篇
EP10 - Django 持續整合持續部署使用 Jenkins 和 AWS CodeDeploy
系列文
關於我幫新公司建立整套部屬流程那檔事30

尚未有邦友留言

立即登入留言