2020-09-19

Centos7 下 pyenv 環境中的 Python 升級記錄。含 mod-wsgi

 [升級 python / pyenv]

本篇流程在於你以前已經安裝過舊 python(例如3.5), 現在想升級新版(例如3.7)。全程都是使用 virtualenv 進行,也建議大家儘量都用 virtualenv 

* 前置安裝 (升級前必裝,之前有裝則免)

安裝一些compile時會用到的 development tools

#yum groupinstall "Development Tools"  -y

#yum install -y python-devel libevent-devel python-pip gcc xz-devel openssl-devel readline-devel sqlite-devel bzip2-devel

安裝 libffi-devel 避免產生 ctype 問題. httpd-devel 避免 mod-wsgi compile 問題
#yum install -y libffi-devel httpd-devel


* git 更新 pyenv 的版本成最新,以獲取最新 OS list

假設你原先的 pyenv 安裝在 /var/pyenv

#cd /var/pyenv/plugins/python-build/../.. && git pull && cd -
反正意思就是 git pull 最新就對了


*重要! 讓 python lib compile 成 shared 。 在pyenv 安裝 python 前必做!

因為我們 python 要在 apache 多網站多環境下使用,因此共用 python lib 是絕對必要的,因此要用 pyenv 安裝 python 時 --enable-shared

#PYTHON_CONFIGURE_OPTS="--enable-unicode=ucs4 --enable-shared"
#export PYTHON_CONFIGURE_OPTS 


*安裝 python3.7

#pyenv install  3.7.9


* 到 專案目錄下 建立 env37

切換到你的專案,建立虛擬環境

#cd /path/to/ur/project

將 global 環境切換成我們要安裝的版本,等全部大功告成後看需求是否再切回去
#pyenv global 3.7.9

檢查目前環境版本正確與否
#pyenv versions

安裝 virtualenv
#pip install virtualenv

升級 pip
#python -m pip install --upgrade pip

建立 envirtual environment 目錄
#virtualenv env37


* 啟用虛據環境,安裝所有 package

注意,有個「.」
# . env37/bin/activate

上傳我們之前 pip freeze > requirement.txt 的檔案
#pip install -r requirements.txt

再修改你的 apache/services/celery..等 環境變數成最新的 env37 即可

* 最後的最後,更新 mod_wsgi 成以目前 python 版本 build 的 wsgi

因為我們 requirements.txt 已經有 install mod-wsgi==4.7.1

若你沒有可以手動安裝

# pip install mod-wsgi

故我們只要將 虛擬環境下的 lib 直接 copy 到 httpd 的 modules 下即可(centos7 LoadModule 位置)

先刪舊資料(如果你之前有舊版已安裝的話,可 list httpd/modules 查看)

# rm -f  /etc/httpd/modules/mod_wsgi.so
# rm -f  /etc/httpd/modules/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so

再 copy 新 share library 到 httpd/modules (註:httpd/modules 是個目錄捷徑,實際位置在 /usr/lib64/httpd/modules , 但無關緊要)

copy 我們安在 env37 下的已 compiled wsgi 到 httpd modules
# cp /var/django/[你的專案]/env37/lib/python3.7/site-packages/mod_wsgi/server/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so  /etc/httpd/modules/mod_wsgi.so

# cp /var/django/[你的專案]/env37/lib/python3.7/site-packages/mod_wsgi/server/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so  /etc/httpd/modules

註1:由於我們對系統 link 來 link 去的 shared library 非常瞭解,所以才有100%信心做上述動作,絕對安全沒問題。若你有疑慮不放心,請看這篇安裝 mod_wsgi(https://missions5.blogspot.com/2014/06/centos-modwsgi.html)

註2:不放心的同鞋可以用 ldd 指令分別檢查你的舊 so 及新 so 檔比較看看即可明瞭

舊so
# ldd /etc/httpd/modules/mod_wsgi.so

新so
# ldd /var/django/[你的專案]/env37/lib/python3.7/site-packages/mod_wsgi/server/mod_wsgi-py37.cpython-37m-x86_64-linux-gnu.so

* 更改 /etc/ld.so.conf 下的 pyenv path 以便讓 wsgi 抓到 python shared library
例如原為
/var/pyenv/versions/3.5.1/lib/
改為
/var/pyenv/versions/3.7.9/lib/
載入
#ldconfig
註:若非要全域,個人可設環境變數 LD_LIBRARY_PATH,將 python library path 加入即可

重新啟動 apache,看 /var/log/httpd 下的 error_log
# systemctl restart httpd

[mpm_prefork:notice] [pid 9850] AH00163: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_wsgi/4.7.1 Python/3.7 configured

顯示 wsgi4.7 + python3.7 正常運作, 即表示你的 so 檔已正常運行

完成!!!





2020-05-28

Centos7 Mysql5.7 安裝

為什麼不用 mysql80 ?
別不信邪,因為很多實驗數據結果顯示,在 < 300 threads 時
效能 mysql5.6 > mysql5.7 > mysql8
不是版本愈高就愈好,而是要選擇適合你的。小型網站或使用者不多的不一定要拿牛刀,MySQL 在Oracle 接管後進步很緩慢,差別真的不大。
但本安裝步驟不分版本,稍改一下版本字元即可通用


如果你只想安裝 mysql,不 care 版本,
1. 安裝前看一下版本
# yum info mysql-community-server
Available Packages
Name        : mysql-community-server
Arch        : x86_64
Version     : 5.6.48
Release     : 2.el7
....
....

2. 安裝
# yum install mysql-community-server



安裝指定版本,這裡是 mysql 5.7

0.檢查 repo source 是否已有 mysql57
# yum repolist enabled | grep "mysql.*-community.*"
!mysql-connectors-community/x86_64 MySQL Connectors Community                153
!mysql-tools-community/x86_64      MySQL Tools Community                     110
!mysql56-community/x86_64          MySQL 5.6 Community Server                530
不符合我們版本


1. 取 mysql community 5.7 版最新安裝檔
毋須指定 release no, architecture independ (例 mysql57-community-release-el7-xx.rpm)
# wget https://repo.mysql.com/mysql57-community-release-el7.noarch.rpm

2. 安裝 (# 提示字 就是 root 身分,你應該知道的)
安裝 mysql5.7 安裝源(repo source)
# yum localinstall mysql57-community-release-el7.rpm

3. 檢查資源狀態
# yum repolist all | grep mysql5
mysql55-community/x86_64            MySQL 5.5 Community Server   disabled
mysql55-community-source            MySQL 5.5 Community Server - disabled
mysql56-community/x86_64            MySQL 5.6 Community Server   enabled:    530
mysql56-community-source            MySQL 5.6 Community Server - disabled
mysql57-community-dmr/x86_64        MySQL 5.7 Community Server D disabled
mysql57-community-dmr-source        MySQL 5.7 Community Server D disabled
enable  的還是 mysql56, 意指目前還是預設指向 mysql56

4. 切換 mysql 安裝來源
關56
# yum-config-manager --disable mysql56-community
開57
# yum-config-manager --enable mysql57-community-dmr
切換完你可以再檢查一下 (可略)
# yum repolist all | grep mysql5 是否已切換了
或第一步驟指令再檢查一下 (可略)
# yum repolist enabled | grep "mysql.*-community.*"
mysql-connectors-community/x86_64 MySQL Connectors Community                 153
mysql-tools-community/x86_64      MySQL Tools Community                      110
mysql57-community-dmr/x86_64      MySQL 5.7 Community Server Development     424

5. 安裝
# yum install mysql-community-server -y

6. 啟動服務 & 檢查服務狀態
# systemctl start mysqld
# systemctl status mysqld

Active: active (running)


6-1. root 的預設密碼在哪裡?
根據官網給的資訊,預設密碼在 /var/log/mysqld.log ,用以下指令
# sudo grep 'temporary password' /var/log/mysqld.log

# sudo grep 'temporary password' /var/log/mysqld.log

2020-05-28T09:47:04.213709Z 1 [Note] A temporary password is generated for root@localhost: #c3/f5KDwtko


登入試試
# mysql -uroot -p

# mysql -uroot -p

Enter password: 

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 2

Server version: 5.7.30


Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.


Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.


Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


mysql> 

成功!!!

6-2. 變更密碼
在 mysql> 下變更密碼

mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('我的新密碼');

Query OK, 0 rows affected, 1 warning (0.00 sec)

下次登入即可使用該密碼

6-3. 開機自動執行
# systemctl enabled mysqld
安裝步驟到此全部完成!!!



7. 追加,安裝附屬套件!!!
安裝 Shared compat libraries for MySQL ,我們程式 compile 所需 lib 及 python mysql connector 需要。
# yum install mysql-community-libs-compat

註:libmysqlclient.so.18、libmysqlclient.so



2020-05-18

那些年我們走過的 AWS 的坑 - Volume, Snapshot, AMI, Image 有何不同?

AWS 是我見過唯一一個把簡單的事情搞得複雜化,但是股價卻是老高在上,下不來。通常把簡單事情搞複雜化會因學習曲線太高不易使用,最後公司倒閉。而亞馬遜卻不然,可見 AWS 有它不為人知的某方面優點。


Volume:儲存媒體,就俗稱它硬碟啦。
Snapshot:映像快照。
AMI(Image):亞馬遜機器映像檔(Amazon Machine Images),內容為 硬碟映像檔+機器描述資料(OS,主機裝置規格)。


[產生 EC2 的方法]
1. 從 ec2 界面 lanuch
   即從標準 ec2 package 清單,經一系列設定啟用 ec2
2. 利用 AMI 啟動
  2-1. 若沒 AMI 可從 Snapshot 或正在 run 的 EC2 產生 AMI
       Snapshot --create image--> Image
       EC2 Instance --create image--> Image
  2-2. 若也沒 Snapshot 可從 Volume 產生
       亦即 Volume ==可產生==>> Snapshot ==可產生==>> Image(AMI) ==launch==> EC2
       Volume 要輸入一些機械描述資料。

[產生 AMI(Image) 方法不同的差異]
方法1. Snapshot --create--> Image
方法2. EC2 Instance --create--> Image
兩者填的資料不一樣,Snapshot 沒主機資料,故需填 Architecture、Virtualization type、RAM disk ID、Root device name、Kernel ID。為什麼需要這些資料?Snapshot(volume)不是有OS Image 了嗎?猜測是因為 AWS 系統的關係,在 launch 時需配合它原先的主機指令集(x86_64,arm..)、Kernel Image ID 以配合開機,若選錯,例如 window 選 redhat 可能開不了機?arm 選 i386 不能跑?未測!




[ moreover Lanuch EC2 from AMI pools ]
當你從標準 ec2 package list lanuch ec2 後,可以在 EC2 詳細看到 AMI。若你刪除 AMI 的 snapshot, AMI,這時 EC2 詳細的 AMI 即永久消失,即使你後來再從 EC2 create Image(AMI),此 AMI 非原 AMI,故依舊不會顯示。




AWS EC2 一般用執行個體類型比較



[ 計點計費現象 ]
t2.large => 41.98 USD/月 0.058 USD/h   470 USD/預付一年
t2.medium => 20.95 USD/月  0.029 USD/h  235 USD/預付一年
t2.small => 10.51 USD/月   0.014 USD/h   118 USD/預付一年

在同為 t2 下,cpu計點積分,large型 = medium型 + samll型 = 16(積分/小時)

如果我們有一台 small +一台 medium,是否合適換成只要一台 Large?
vCPU比較:  large (2vCPU) < medium(2vCPU) + small(1vCPU)  少一顆
Memory比較: large (memory8GB) > medium(4GB) + small(2GB)  多 2GB 記憶體
所以在 cpu 不那麼強烈要求下, 與其將系統分為雙層 web server(meidum) + database(small),不如合在同一台 large。

測試環境 web server cpu 常態負載在 5%~10%, database server 在 1%~4% ,使用 Large,將系統 合在同一台,少一顆vcpu反而表現較佳


[ 同條件下比對 t2, t3, t3a ]
t2.large => 41.98 USD/月 0.058 USD/h   470 USD/預付一年
t3.large => 38.11 USD 0.052 USD  426 USD/預付一年
t3a.large => 34.38 USD 0.047 USD  385 USD/預付一年
結論為:貴賤順序為 t2(貴) > t3 > t3a(便宜)
t2 使用 Intel CPU(3GHZ) 且有穩定cpu算力;t3 使用 Intel CPU(2.5GHZ) 但為 lazy cpu burst模式;t3a 同 t3 但使用 AMD CPU(2.5GHZ),
只要是 cpu 負責不高的情況下 t3a 為較優選擇;反之則結果相反。


[同樣執行個體下,不同地區]
同樣執行個體下,不同地區以美國為最便宜,而亞洲便宜至貴依序:
首爾 每小時 0.0065 USD (便宜)
新加坡 每小時 0.0066 USD
東京 每小時 0.0068 USD
香港 每小時 0.0073 USD




無廢話 crontab 設定


[排程]
* 檔案位置
nano /etc/crontab

* 內容格式
# ┌───────────── 分鐘   (0 - 59)
# │ ┌─────────── 小時   (0 - 23)
# │ │ ┌───────── 日     (1 - 31)
# │ │ │ ┌─────── 月     (1 - 12)
# │ │ │ │ ┌───── 星期幾 (0 - 7,0 是週日,6 是週六,7 也是週日)
# │ │ │ │ │
# * * * * *
# 台灣時間半夜3點15分 (系統為格林威治時間)
15 19 * * *  root  /bin/mysqldump -umyname -p密碼 mytest_db | gzip > /aws/share/mysql/mytest_db_auto_bak_`date '+\%Y\%m\%d'`.sql.gz




goofsy 的設定與自動/手動掛載

安裝

$ go get github.com/kahing/goofys
$ go install github.com/kahing/goofys


掛載 AWS S3 bucket 的 package

[goofsy 設定]
在 [HOME]/.aws/ 下有兩個檔案要設,格式如下 (參考 https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
1. ~/.aws/credentials
[default]
aws_access_key_id=AKxxxxxxx12345QCQCEE 
aws_secret_access_key=y8lkHqIxxxxxxxzq54dd
2. ~/.aws/config
[default]
region=us-west-2
output=json

3. 手動 mount 指令
$ $GOPATH/bin/goofys <bucket> <mountpoint>
例如
$ $GOPATH/bin/goofys my-s3-bucket /aws/s3

4. 手動 umount 指令
指令記得離開該目錄,然後 umount 該目錄
$ umount /aws/s3

5. copy goofys 至系統目錄(path for all users)以供執行
$ cp ~/go/bin/goofys /usr/bin

6. 掛入 fstab
  * 安裝 fuse (Filesystem in Userspace) 
  $ yum install fuse
  * 位址 /etc/fstab
  * 格式
    goofys#bucket   /mnt/mountpoint        fuse     _netdev,allow_other,--file-mode=0666,--dir-mode=0777    0       0
  * 實例
    goofys#aws-share   /aws/share        fuse     _netdev,allow_other,--file-mode=0666,--dir-mode=0777    0       0
  * 掛載
  $ mount -a

其它注意事項,可參考 https://github.com/kahing/goofys/issues/433#issuecomment-512903339

go 安裝及資訊


[go 安裝]
* 下載位置
$ wget https://dl.google.com/go/go1.14.3.linux-amd64.tar.gz
* 解壓至 /user/local
tar -C /usr/local -xzf go1.14.3.linux-amd64.tar.gz
* 將 go 註冊於系統路徑
export PATH=$PATH:/usr/local/go/bin
* 測試

檔案 hello.go
package main

import "fmt"

func main() {
   fmt.Println("Hello, World!")
}

執行
$ go run hello.go
Hello, World!



[資訊]
* 安裝位置
/usr/local/go/
* bin
/usr/local/go/bin/go
* go env
GOPATH="/root/go"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"

2020-04-28

regular expression 的 Lookahead 與 Lookbehind

[regular expression]
這裡不教導簡單的 regular expression, 首先你必需瞭解一些基本的 RE。例如 g 是指全域尋找,[56]是指 5 或 6,[2-8]是指 2 到 8。
基本的 RE 很簡單,到處都有資料,要瞭解沒有任何困難。

【開始】

所謂 regular expression 的 expression 是指表達式,也就是說尋找「符合整個表達式的規則」的資料的意思。
對程式設計師來說落落長的「說明」是沒太大用處的,程式碼才能表達一切。但是上面這句話卻是例外,不領會這句話的意義就代表不懂或一知半解 regular expression 的意思。

0. 基礎說明

在這裡我們將 RE 的格式 (?=c)m 中,c 我們稱其為「條件表達式」或「條件式」,而 m 稱之為「主表達式」
各種格式例如以下:
(?=c)m、m(?=c)、(?<=c)m、m(?<=c)

Lookahead符合條件時從前面開始抓資料。當條件式在主表達式「前面」時則取值判斷時包含該條件式,在後面時則看取多少「長度」。
Lookbehind符合條件時從後面開始抓資料。 當條件式在主表達式「後面」時則取值判斷時包含該條件式,在後面時則看取多少「長度」。
positive: 要符合條件式,「必需」符合條件式
negative: 要不符合條件式, 「不可」符合條件式(限制不能同條件式所述)
front: 條件式在主表達式「前面」的這種 expression
behind: 條件式在主表達式「後面」的這種 expression

RE 條件比對順序:RE 條件比對順序為由左至右比對,所以前後順序很重要

1. lookahead 及 lookbehind

   很多人跟你說 lookahead 就是往前看,所以格式 m(?=c) 就是找 m 後面有 c 的資料。
   誰跟你說的?
   包含外國人都跟你說 m followed by c, 其實這是大大的誤解了。
   難道我不能這樣找 (?=c)m ,意指找 c 在 m 前的「符合這個表達式規則」的資料? 這時後難道要叫「往後看」?lookbehind?
   很顯然的並不是這樣的,因為 lookbehind 在 RE 裡是另一個意思。
   RE 最重要的是「表達式順序」,
   lookahead 意指若條件式(c)在前面時,則取值判斷時要包含 c 值,但條件式若在後面時取值判斷時則不包含 c值。即符合條件時從前面開始抓資料。相反的,
   lookbehind 意指若條件式(c)在前面時,則取值判斷時不包含 c 值,但條件式若在後面時取值判斷時則要包含 c值。即符合條件時從後面開始抓資料。

2. 用例子證明一切

所以到這裡我們總共有 lookahead/lookbehind, positive/negative, condition position front/behind 共 2 x 2 x 2 = 8 種狀況,
再加上我們再用長度 1 及 2 讓觀念更清楚,於是共有 8 x 2 = 16 種狀況
# Lookahead
## positive Lookahead (positive ?=)
### positive Lookahead, 1 char, condition in front
"01234567890".match(/(?=[5])[2-8]/g)
結果: ["5"]
分析: 因為 lookahead 在 front 時取值判斷要包含其值,又我們只要1個字元,故直接只有 5 符合其值,毫無懸念。
### positive behind, 1 char, condition in behind
"01234567890".match(/[2-8](?=[5])/g)
結果: ["4"]
分析: 因為 lookahead 在 behind 時取值判斷"不"包含其值,又我們只要1個字元,因 4 的下1位5符合,取值不取5,故答案為 4
### positive Lookahead, 2 chars, condition in front
"01234567890".match(/(?=[5])[2-8]{2}/g)
結果: ["56"]
分析: 因為 lookahead 在 front 時取值判斷要包含其值,取2個字元,故直接只有 [5]6 符合其值
### positive behind, 2 chars, condition in behind
"01234567890".match(/[2-8]{2}(?=[5])/g)
結果: ["34"]
分析: 因為 lookahead 在 behind 時取值判斷"不"包含其值,取2個字元,故答案為 34[5]
## negative lookahead (negative ?!)
### negative lookahead, 2 chars, condition in front
"01234567890".match(/(?![6])[2-8]{2}/g)
結果: ["23", "45", "78"]
### negative lookahead, 2 chars, condition in behind
"01234567890".match(/[2-8]{2}(?![6])/g)
結果: ["23", "56", "78"]
## negative 1
### negative lookahead, 1 char, condition in front
"01234567890".match(/(?![5])[2-8]/g)
結果: ["2", "3", "4", "6", "7", "8"]
### negative lookahead, 1 chars, condition in behind
"01234567890".match(/[2-8](?![5])/g)
結果: ["2", "3", "5", "6", "7", "8"]


# Lookbehind
## positive
### positive 2 front
"01234567890".match(/(?<=[5])[2-8]{2}/g)
結果: ["67"]
分析: 因為 Lookbehind 在 front 時取值判斷"不"包含其值,取2個字元,5為前置不取,[5]67,故答案為 67
### positive 2 behind
"01234567890".match(/[2-8]{2}(?<=[5])/g)
結果: ["45"]
分析: 因為 Lookbehind 在 behind 時取值判斷要包含其值,取2個字元,故答案為 4[5] (含5)
### positive 1 front
"01234567890".match(/(?<=[5])[2-8]/g)
結果: ["6"]
### positive 1 behind
"01234567890".match(/[2-8](?<=[5])/g)
結果: ["5"]
## negative
### negative 2 front
"01234567890".match(/(?<![5])[2-8]{2}/g)
結果: ["23", "45", "78"]
分析: 因為 Lookbehind 在 front 時取值判斷不包含其值,這裡取2個字元,故第1組23,第2組45,第3組67時因前面為5,故不符合,跳至下一字元78
### negative 2 behind
"01234567890".match(/[2-8]{2}(?<![5])/g)
結果: ["23", "56", "78"]
分析: 因為 Lookbehind 在 behind 時取值判斷要包含其值,這裡取2個字元,故第1組23,當第2組45中遇到 5不符規則,跳至下一位元,第2組變56,第3組78 
### negative 1 front
"01234567890".match(/(?<![5])[2-8]/g)
結果: ["2", "3", "4", "5", "7", "8"]
分析: 因為 Lookbehind 在 front 時取值判斷不包含其值,這裡僅取1個字元,故「前值為5的6不合規則」
### negative 1 behind
"01234567890".match(/[2-8](?<![5])/g)
結果: ["2", "3", "4", "6", "7", "8"]
分析: 因為 Lookbehind 在 behind 時取值判斷要包含其值,這裡取1個字元,故當 「非5」 時即為答案