Docker+DockerCompose封装web应用的方法步骤

2025-05-27 0 18

这篇文章会介绍如何将后端、前端和网关通通使用 Docker 容器进行运行,并最终使用 DockerCompose 进行容器编排。

技术栈

前端

  • React
  • Ant Design

后端

  • Go
  • Iris

网关

  • Nginx
  • OpenResty
  • Lua
  • 企业微信

后端构建 api

这里虽然我们写了 EXPOSE 4182,这个只用在测试的时候,生产环境实际上我们不会将后端接口端口进行暴露,
而是通过容器间的网络进行互相访问,以及最终会使用 Nginx 进行转发。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18
FROM golang:1.15.5

LABEL maintainer="K8sCat <k8scat@gmail.com>"

EXPOSE 4182

ENV GOPROXY=https://goproxy.cn,direct \\

GO111MODULE=on

WORKDIR /go/src/github.com/k8scat/containerized-app/api

COPY . .

RUN go mod download && \\

go build -o api main.go && \\

chmod +x api

ENTRYPOINT [ "./api" ]

前端构建 web

这里值得一提的是,因为前端肯定会去调用后端接口,而且这个接口地址是根据部署而改变,
所以这里我们使用了 ARG 指令进行设置后端的接口地址,这样我们只需要在构建镜像的时候传入 --build-arg REACT_APP_BASE_URL=https://example.com/api 就可以调整后端接口地址了,而不是去改动代码。

还有一点,有朋友肯定会发现这里同时使用到了 Entrypoint 和 CMD,这是为了可以在运行的时候调整前端的端口,但实际上我们这里没必要去调整,因为这里最终也是用 Nginx 进行转发。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17
FROM node:lts

LABEL maintainer="K8sCat <k8scat@gmail.com>"

WORKDIR /web

COPY . .

ARG REACT_APP_BASE_URL

RUN npm config set registry https://registry.npm.taobao.org && \\

npm install && \\

npm run build && \\

npm install -g serve

ENTRYPOINT [ "serve", "-s", "build" ]

CMD [ "-l", "3214" ]

网关构建 gateway

Nginx 配置

这里我们就分别设置了后端和前端的上游,然后设置 location 规则进行转发。
这里有几个点可以说一下:

  • 通过 set_by_lua 获取容器的环境变量,最终在运行的时候通过设置 environment 设置这些环境变量,更加灵活
  • server_name 使用到了 $hostname,运行时需要设置容器的 hostname
  • ssl_certificate 和 ssl_certificate_key 不能使用变量设置
  • 加载 gateway.lua 脚本实现企业微信的网关认证
?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85
upstream web {

server ca-web:3214;

}

upstream api {

server ca-api:4182;

}

server {

set_by_lua $corp_id 'return os.getenv("CORP_ID")';

set_by_lua $agent_id 'return os.getenv("AGENT_ID")';

set_by_lua $secret 'return os.getenv("SECRET")';

set_by_lua $callback_host 'return os.getenv("CALLBACK_HOST")';

set_by_lua $callback_schema 'return os.getenv("CALLBACK_SCHEMA")';

set_by_lua $callback_uri 'return os.getenv("CALLBACK_URI")';

set_by_lua $logout_uri 'return os.getenv("LOGOUT_URI")';

set_by_lua $token_expires 'return os.getenv("TOKEN_EXPIRES")';

set_by_lua $use_secure_cookie 'return os.getenv("USE_SECURE_COOKIE")';

listen 443 ssl http2;

server_name $hostname;

resolver 8.8.8.8;

ssl_certificate /certs/cert.crt;

ssl_certificate_key /certs/cert.key;

ssl_session_cache shared:SSL:1m;

ssl_session_timeout 5m;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

ssl_ciphers AESGCM:HIGH:!aNULL:!MD5;

ssl_prefer_server_ciphers on;

lua_ssl_verify_depth 2;

lua_ssl_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;

if ($time_iso8601 ~ "^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2})") {

set $year $1;

set $month $2;

set $day $3;

}

access_log logs/access_$year$month$day.log main;

error_log logs/error.log;

access_by_lua_file "/usr/local/openresty/nginx/conf/gateway.lua";

location ^~ /gateway {

root html;

index index.html index.htm;

}

location ^~ /api {

proxy_pass http://api;

proxy_read_timeout 3600;

proxy_http_version 1.1;

proxy_set_header X_FORWARDED_PROTO https;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header Host $host;

proxy_set_header Connection "";

}

location ^~ / {

proxy_pass http://web;

proxy_read_timeout 3600;

proxy_http_version 1.1;

proxy_set_header X_FORWARDED_PROTO https;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header Host $host;

proxy_set_header Connection "";

}

error_page 500 502 503 504 /50x.html;

location = /50x.html {

root html;

}

}

server {

listen 80;

server_name $hostname;

location / {

rewrite ^/(.*) https://$server_name/$1 redirect;

}

}

Dockerfile

?

1

2

3

4

5

6

7

8

9

10
FROM openresty/openresty:1.19.3.1-centos

LABEL maintainer="K8sCat <k8scat@gmail.com>"

COPY gateway.conf /etc/nginx/conf.d/gateway.conf

COPY gateway.lua /usr/local/openresty/nginx/conf/gateway.lua

COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf

# Install lua-resty-http

RUN /usr/local/openresty/luajit/bin/luarocks install lua-resty-http

Lua 实现基于企业微信的网关认证

这里面的一些配置参数都是通过获取 Nginx 设置的变量。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179
local json = require("cjson")

local http = require("resty.http")

local uri = ngx.var.uri

local uri_args = ngx.req.get_uri_args()

local scheme = ngx.var.scheme

local corp_id = ngx.var.corp_id

local agent_id = ngx.var.agent_id

local secret = ngx.var.secret

local callback_scheme = ngx.var.callback_scheme or scheme

local callback_host = ngx.var.callback_host

local callback_uri = ngx.var.callback_uri

local use_secure_cookie = ngx.var.use_secure_cookie == "true" or false

local callback_url = callback_scheme .. "://" .. callback_host .. callback_uri

local redirect_url = callback_scheme .. "://" .. callback_host .. ngx.var.request_uri

local logout_uri = ngx.var.logout_uri or "/logout"

local token_expires = ngx.var.token_expires or "7200"

token_expires = tonumber(token_expires)

local function request_access_token(code)

local request = http.new()

request:set_timeout(7000)

local res, err = request:request_uri("https://qyapi.weixin.qq.com/cgi-bin/gettoken", {

method = "GET",

query = {

corpid = corp_id,

corpsecret = secret,

},

ssl_verify = true,

})

if not res then

return nil, (err or "access token request failed: " .. (err or "unknown reason"))

end

if res.status ~= 200 then

return nil, "received " .. res.status .. " from https://qyapi.weixin.qq.com/cgi-bin/gettoken: " .. res.body

end

local data = json.decode(res.body)

if data["errcode"] ~= 0 then

return nil, data["errmsg"]

else

return data["access_token"]

end

end

local function request_user(access_token, code)

local request = http.new()

request:set_timeout(7000)

local res, err = request:request_uri("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo", {

method = "GET",

query = {

access_token = access_token,

code = code,

},

ssl_verify = true,

})

if not res then

return nil, "get profile request failed: " .. (err or "unknown reason")

end

if res.status ~= 200 then

return nil, "received " .. res.status .. " from https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"

end

local userinfo = json.decode(res.body)

if userinfo["errcode"] == 0 then

if userinfo["UserId"] then

res, err = request:request_uri("https://qyapi.weixin.qq.com/cgi-bin/user/get", {

method = "GET",

query = {

access_token = access_token,

userid = userinfo["UserId"],

},

ssl_verify = true,

})

if not res then

return nil, "get user request failed: " .. (err or "unknown reason")

end

if res.status ~= 200 then

return nil, "received " .. res.status .. " from https://qyapi.weixin.qq.com/cgi-bin/user/get"

end

local user = json.decode(res.body)

if user["errcode"] == 0 then

return user

else

return nil, user["errmsg"]

end

else

return nil, "UserId not exists"

end

else

return nil, userinfo["errmsg"]

end

end

local function is_authorized()

local headers = ngx.req.get_headers()

local expires = tonumber(ngx.var.cookie_OauthExpires) or 0

local user_id = ngx.unescape_uri(ngx.var.cookie_OauthUserID or "")

local token = ngx.var.cookie_OauthAccessToken or ""

if expires == 0 and headers["OauthExpires"] then

expires = tonumber(headers["OauthExpires"])

end

if user_id:len() == 0 and headers["OauthUserID"] then

user_id = headers["OauthUserID"]

end

if token:len() == 0 and headers["OauthAccessToken"] then

token = headers["OauthAccessToken"]

end

local expect_token = callback_host .. user_id .. expires

if token == expect_token and expires then

if expires > ngx.time() then

return true

else

return false

end

else

return false

end

end

local function redirect_to_auth()

return ngx.redirect("https://open.work.weixin.qq.com/wwopen/sso/qrConnect?" .. ngx.encode_args({

appid = corp_id,

agentid = agent_id,

redirect_uri = callback_url,

state = redirect_url

}))

end

local function authorize()

if uri ~= callback_uri then

return redirect_to_auth()

end

local code = uri_args["code"]

if not code then

ngx.log(ngx.ERR, "not received code from https://open.work.weixin.qq.com/wwopen/sso/qrConnect")

return ngx.exit(ngx.HTTP_FORBIDDEN)

end

local access_token, request_access_token_err = request_access_token(code)

if not access_token then

ngx.log(ngx.ERR, "got error during access token request: " .. request_access_token_err)

return ngx.exit(ngx.HTTP_FORBIDDEN)

end

local user, request_user_err = request_user(access_token, code)

if not user then

ngx.log(ngx.ERR, "got error during profile request: " .. request_user_err)

return ngx.exit(ngx.HTTP_FORBIDDEN)

end

ngx.log(ngx.ERR, "user id: " .. user["userid"])

local expires = ngx.time() + token_expires

local cookie_tail = "; version=1; path=/; Max-Age=" .. expires

if use_secure_cookie then

cookie_tail = cookie_tail .. "; secure"

end

local user_id = user["userid"]

local user_token = callback_host .. user_id .. expires

ngx.header["Set-Cookie"] = {

"OauthUserID=" .. ngx.escape_uri(user_id) .. cookie_tail,

"OauthAccessToken=" .. ngx.escape_uri(user_token) .. cookie_tail,

"OauthExpires=" .. expires .. cookie_tail,

}

return ngx.redirect(uri_args["state"])

end

local function handle_logout()

if uri == logout_uri then

ngx.header["Set-Cookie"] = "OauthAccessToken==deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"

--return ngx.redirect("/")

end

end

handle_logout()

if (not is_authorized()) then

authorize()

end

使用 DockerCompose 进行容器编排

这里需要讲几个点:

  • 设置前端的 args 可以在前端构建时传入后端接口地址
  • 设置网关的 hostname 可以设置网关容器的 hostname
  • 设置网关的 environment 可以传入相关配置
  • 最终运行时只有网关层进行暴露端口
?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37
version: "3.8"

services:

api:

build: ./api

image: ca-api:latest

container_name: ca-api

web:

build:

context: ./web

args:

REACT_APP_BASE_URL: https://example.com/api

image: ca-web:latest

container_name: ca-web

gateway:

build: ./gateway

image: ca-gateway:latest

hostname: example.com

volumes:

- ./gateway/certs/fullchain.pem:/certs/cert.crt

- ./gateway/certs/privkey.pem:/certs/cert.key

ports:

- 80:80

- 443:443

environment:

- CORP_ID=

- AGENT_ID=

- SECRET=

- CALLBACK_HOST=example.com

- CALLBACK_SCHEMA=https

- CALLBACK_URI=/gateway/oauth_wechat

- LOGOUT_URI=/gateway/oauth_logout

- TOKEN_EXPIRES=7200

- USE_SECURE_COOKIE=true

container_name: ca-gateway

开源代码

GitHub https://github.com/k8scat/containerized-app
Gitee https://gitee.com/k8scat/containerized-app

到此这篇关于Docker+DockerCompose封装web应用的文章就介绍到这了,更多相关Docker+DockerCompose封装web应用内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

原文链接:https://juejin.cn/post/6933778061280149517

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 行业资讯 Docker+DockerCompose封装web应用的方法步骤 https://www.kuaiidc.com/63182.html

相关文章

发表评论
暂无评论