14-Deployment-On-Heroku
在本章,我会将应用部署到Heroku云平台
许多云托管提供商提供了一个应用程序可以运行的托管平台。 你只需提供部署到这些平台上的实际应用程序,因为硬件,操作系统,脚本语言解释器,数据库等都由该服务管理。 这种服务称为平台即服务(PaaS)。
Heroku,这是一种流行的云托管服务,对Python、Go、Nodejs等应用程序支持都很好,关键还免费,而且默认支持HTTPS
本章的GitHub链接为: Source, Diff, Zip

Heroku

Heroku 是首批PaaS平台之一。 它以Ruby的应用程序的托管服务开始,随后逐渐发展到支持诸多其他语言,如Java,Node.js,Python 还有Go。
在 Heroku 中部署Web应用程序主要是通过git版本控制工具完成的,因此你必须将应用程序放在git代码库中。 在通过git将应用程序上传到 Heroku 的服务器之后,你的工作基本就完成了,只需等待几秒钟,应用程序就会上线。 整个操作流程就是这么简单。
Heroku提供不同的服务级别,允许你自主选择为应用程序提供多少计算能力和运行时间,随着用户群的增长,你需要购买更多的“dynos”计算单元。

创建Heroku账户

在部署应用到 Heroku 之前,你需要拥有一个帐户。 所以请访问 heroku.com 并创建一个免费账户。 一旦注册成功并登录到 Heroku,你将可以访问一个dashboard,其中列出了你的所有应用程序。

安装Heroku命令行工具

我们可以在 Heroku 的 dashboard 上完成所有操作,不过还有个更简便的方法,就是安装Heroku cli 工具
1
$ brew install heroku
Copied!
然后我们可以通过命令行来登陆 Heroku
1
$ heroku login
Copied!

创建Heroku应用

要用Heroku注册一个新应用,需要在应用程序根目录下使用apps:create子命令,并将应用程序名称作为唯一参数传递:
1
$ heroku apps:create go-mega
2
Creating ⬢ go-mega... done
3
https://go-mega.herokuapp.com/ | https://git.heroku.com/go-mega.git
Copied!

创建 Heroku 数据库

Heroku 只有Postgres数据库是免费的,Mysql是收费的,所以我们还是创建 Postgres 数据库
1
$ heroku addons:add heroku-postgresql:hobby-dev
2
Creating heroku-postgresql:hobby-dev on ⬢ go-mega... free
3
Database has been created and is available
4
! This database is empty. If upgrading, you can transfer
5
! data from another database with pg:copy
6
Created postgresql-angular-82467 as DATABASE_URL
7
Use heroku addons:docs heroku-postgresql to view documentation
Copied!

初始化数据

1
$ heroku config
2
=== go-mega Config Vars
3
DATABASE_URL: ******************
Copied!

针对Heroku做些代码优化

由于Heroku采用的是 Configvar 的设置环境变量的方式,而且我们把 config.yml git ignore了,所以加入 os.Getenv 的方式去获取Configvar的环境变量,包括 DBTYPE, EMAIL相关,以及Heroku postgres 提供的 DATABASE_URL
config/g.go
1
package config
2
3
import (
4
"fmt"
5
"log"
6
"os"
7
"strconv"
8
9
"github.com/spf13/viper"
10
)
11
12
func init() {
13
projectName := "go-mega"
14
dbType := GetDBType()
15
log.Println("OS DBTYPE:", dbType)
16
17
if IsHeroku() {
18
log.Println("Get Env from os.env")
19
} else {
20
log.Println("Init viper")
21
getConfig(projectName)
22
}
23
}
24
25
func getConfig(projectName string) {
26
viper.SetConfigName("config") // name of config file (without extension)
27
28
viper.AddConfigPath(".") // optionally look for config in the working directory
29
viper.AddConfigPath(fmt.Sprintf("$HOME/.%s", projectName)) // call multiple times to add many search paths
30
viper.AddConfigPath(fmt.Sprintf("/data/docker/config/%s", projectName)) // path to look for the config file in
31
32
err := viper.ReadInConfig() // Find and read the config file
33
if err != nil { // Handle errors reading the config file
34
panic(fmt.Errorf("Fatal error config file: %s", err))
35
}
36
}
37
38
// GetMysqlConnectingString func
39
func GetMysqlConnectingString() string {
40
usr := viper.GetString("mysql.user")
41
pwd := viper.GetString("mysql.password")
42
host := viper.GetString("mysql.host")
43
db := viper.GetString("mysql.db")
44
charset := viper.GetString("mysql.charset")
45
46
return fmt.Sprintf("%s:%[email protected](%s:3306)/%s?charset=%s&parseTime=true&loc=Local", usr, pwd, host, db, charset)
47
}
48
49
// GetHerokuConnectingString func
50
func GetHerokuConnectingString() string {
51
return os.Getenv("DATABASE_URL")
52
}
53
54
// GetSMTPConfig func
55
func GetSMTPConfig() (server string, port int, user, pwd string) {
56
if IsHeroku() {
57
server = os.Getenv("MAIL_SMTP")
58
port, _ = strconv.Atoi(os.Getenv("MAIL_SMTP_PORT"))
59
user = os.Getenv("MAIL_USER")
60
pwd = os.Getenv("MAIL_PASSWORD")
61
return
62
}
63
64
server = viper.GetString("mail.smtp")
65
port = viper.GetInt("mail.smtp-port")
66
user = viper.GetString("mail.user")
67
pwd = viper.GetString("mail.password")
68
return
69
}
70
71
// GetServerURL func
72
func GetServerURL() (url string) {
73
if IsHeroku() {
74
url = os.Getenv("SERVER_URL")
75
return
76
}
77
url = viper.GetString("server.url")
78
return
79
}
80
81
// GetDBType func
82
func GetDBType() string {
83
dbtype := os.Getenv("DBTYPE")
84
return dbtype
85
}
86
87
// IsHeroku func
88
func IsHeroku() bool {
89
return GetDBType() == "heroku"
90
}
Copied!
修改ConnectToDB函数,支持postgres的数据库形式
model/g.go
1
...
2
3
// ConnectToDB func
4
func ConnectToDB() *gorm.DB {
5
if config.IsHeroku() {
6
return ConnectToDBByDBType("postgres", config.GetHerokuConnectingString())
7
}
8
return ConnectToDBByDBType("mysql", config.GetMysqlConnectingString())
9
}
10
11
// ConnectToDBByDBType func
12
func ConnectToDBByDBType(dbtype, connectingStr string) *gorm.DB {
13
log.Println("DB Type:", dbtype, "\nConnet to db...")
14
db, err := gorm.Open(dbtype, connectingStr)
15
if err != nil {
16
panic("Failed to connect database")
17
}
18
db.SingularTable(true)
19
return db
20
}
Copied!
cmd/db_init/main.go
1
...
2
_ "github.com/jinzhu/gorm/dialects/postgres"
3
...
Copied!
main.go 与数据初始化无关,不过最后部署还是要做响应的调整
main.go
1
...
2
_ "github.com/jinzhu/gorm/dialects/postgres"
3
...
4
5
6
port := os.Getenv("PORT")
7
log.Println("Running on port: ", port)
8
http.ListenAndServe(":"+port, context.ClearHandler(http.DefaultServeMux))
Copied!

数据init

1
$ export DATABASE_URL=postgres://xxxxxxx
2
$ export DBTYPE=heroku
3
4
$ go run cmd/db_init/main.go
Copied!
然后我们会发现数据初始化就完成了,让我们确认下
1
# 安装 psql
2
$ brew install postgresql
3
4
$ heroku pg:psql
5
$ DATABASE=> select * from post;
6
id | user_id | body | timestamp
7
----+---------+---------------------------------+-------------------------------
8
1 | 1 | Beautiful day in Portland! | 2018-10-23 07:13:30.664214+00
9
2 | 2 | The Avengers movie was so cool! | 2018-10-23 07:13:36.118051+00
10
3 | 2 | Sun shine is beautiful | 2018-10-23 07:13:38.502164+00
11
12
# 退出
13
$ DATABASE=> \q
Copied!
本小节 Diff

部署 Heroku

设置Config Vars

可以访问 https://dashboard.heroku.com/apps/go-mega/settings dashboard 的Config Vars 进行设置
也可以通过heroku cli
1
$ heroku config:set DBTYPE=heroku
2
3
# 设置root url
4
$ heroku config:set SERVER_URL=https://go-mega.herokuapp.com
5
6
# 设置mail
7
$ heroku config:set MAIL_SMTP=smtp.zoho.com
8
$ heroku config:set MAIL_SMTP_PORT=587
9
$ heroku config:set MAIL_USER=your_username
10
$ heroku config:set MAIL_PASSWORD=your_password
11
12
# 查看config
13
$ heroku config
Copied!

Procfile

Procfile
1
web: go-mega-code
Copied!
现在我们可以通过 heroku local来在本地查看应用
1
$ heroku local
Copied!

Go dep

目前我们的代码和配置都已经完成了,不过部署 heroku 还需要我们提供依赖,我们这里使用 Godep
1
$ go get -u github.com/tools/godep
2
$ godep save ./...
3
4
# 结果可以看见多了两个文件夹: vendor/ 和 Godeps/
Copied!

Push heroku

1
# Remote add heroku
2
$ heroku git:remote -a go-mega
3
set git remote heroku to https://git.heroku.com/go-mega.git
4
$ git remote -v
5
heroku https://git.heroku.com/go-mega.git (fetch)
6
heroku https://git.heroku.com/go-mega.git (push)
7
origin [email protected]:bonfy/go-mega-code.git (fetch)
8
origin [email protected]:bonfy/go-mega-code.git (push)
9
10
# push branch to heroku master
11
$ git push heroku 14-Deployment-On-Heroku:master
Copied!
现在访问 https://go-mega.herokuapp.com/ 就能看见 Demo 了
Notice: Heroku 绑定域名要收费,所以这里就不绑定域名了
14-01
本小节 Diff

Links

Last modified 3yr ago