This article will show you how to run the back end, front end, and gateway using Docker containers, and ultimately DockerCompose for container orchestration.
Technology stack
The front end
- React
- Ant Design
The back-end
- Go
- Iris
The gateway
- Nginx
- OpenResty
- Lua
- Enterprise WeChat
Back-end build API
Although we’ve written EXPOSE 4182 here, which is only for testing, production environments don’t actually EXPOSE the back-end ports, but instead access each other through a network of containers, and eventually use Nginx for forwarding.
FROM Golang: 1.15.5
LABEL maintainer="K8sCat <[email protected]>"
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" ]
Copy the code
Front-end Building the Web
It is worth mentioning here that because the front end will definitely call the back end interface and the address of the interface will change according to the deployment, we use the ARG directive to set the back end interface address. Instead of changing the code, just pass –build-arg REACT_APP_BASE_URL=https://example.com/api when building the image to adjust the address of the backend interface.
One more thing that you will notice is that Entrypoint and CMD are used at the same time, so that you can adjust the port of the front end at runtime, but there is no need to do that because Nginx is ultimately used for forwarding.
FROM node:lts
LABEL maintainer="K8sCat <[email protected]>"
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" ]
Copy the code
Gateway Construction Gateway
Nginx configuration
Here we set the upstream of the back end and the front end respectively, and then set the location rule for forwarding.
Here are a few points to make:
- through
set_by_lua
Gets the container’s environment variables, which are finally set at run timeenvironment
Setting these environment variables gives you more flexibility server_name
Using the$hostname
At runtime, the container needs to be sethostname
ssl_certificate
和ssl_certificate_key
Cannot use variable Settings- loading
gateway.lua
Script to achieve enterprise wechat gateway authentication
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; }}Copy the code
Dockerfile
FROM Openresty/openresty: 1.19.3.1 - centos
LABEL maintainer="K8sCat <[email protected]>"
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
Copy the code
Lua implements gateway authentication based on enterprise wechat
Some of these configuration parameters are set by fetching Nginx variables.
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_urilocal redirect_url = callback_scheme .. ": / /". callback_host .. ngx.var.request_urilocal 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: ". (error "unknown reason"))
end
if res.status~ =200 then
return nil."received ". res.status." from https://qyapi.weixin.qq.com/cgi-bin/gettoken: ". res.bodyend
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: ". (error "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: ". (error "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(a)
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(a)then
return true
else
return false
end
else
return false
end
end
local function redirect_to_auth(a)
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(a)
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=". expiresif 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(a)
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
Copy the code
Use DockerCompose for container orchestration
Here are a few points:
- Set front-end
args
The back-end interface address can be passed in at front-end build time - Gateway setting
hostname
The gateway container can be sethostname
- Gateway setting
environment
Relevant configurations can be passed in - Ultimately, only the gateway layer exposes ports at runtime
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
Copy the code
Open source code
- GitHub
- Gitee
Original link: k8scat.com/posts/conta…