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

<<:  Day 7 - 使用 AES-CBC 机制对 Message 内文进行加密

>>:  [区块链&DAPP介绍 Day13] Solidity 教学 - contracts-2

Day21 样式变化(动画)5

列表移动过渡(List move transition) 不仅可以做出淡出与淡入,还可以改变位置,只...

【领域展开 14 式】 Favicon 的好助手!Canva 使用与 5 下搞定网站设定

今天需要疗癒的视觉效果,Menu 再等等.. 跳脱进度,想先做网站 Favicon 的设计 (其实是...

叉烧包老板盗号事件处理与预防再发生

事发经过简述如下: 某天叉烧包老板收到一简讯要求回复密码,回复後发现知名 App 被入侵且友人帐号也...

html表格-合并储存格

想要在html表格中完成合并储存格的效果,需要用到rowspan和colspan分别为垂直和水平合并...

Day 24 - Shiaoji.Login踩坑经验及修正

今天原本想开始抓个股的kbar资料及後续处理,结果在清洗Contract资料时,发现抓出来的TSE+...