栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > 服务器

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

服务器 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

目录
  • 技术栈
  • 后端构建 api
  • 前端构建 web
  • 网关构建 gateway
    • Nginx 配置
    • Dockerfile
    • Lua 实现基于企业微信的网关认证
  • 使用 DockerCompose 进行容器编排

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

    技术栈

    前端

    • React
    • Ant Design

    后端

    • Go
    • Iris

    网关

    • Nginx
    • OpenResty
    • Lua
    • 企业微信

    后端构建 api

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

    FROM golang:1.15.5
    
    LABEL maintainer="K8sCat "
    
    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 进行转发。

    FROM node:lts
    
    LABEL maintainer="K8sCat "
    
    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 脚本实现企业微信的网关认证
    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
    FROM openresty/openresty:1.19.3.1-centos
    
    LABEL maintainer="K8sCat "
    
    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 设置的变量。

    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 可以传入相关配置
    • 最终运行时只有网关层进行暴露端口
    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应用内容请搜索考高分网以前的文章或继续浏览下面的相关文章希望大家以后多多支持考高分网!

    转载请注明:文章转载自 www.mshxw.com
    本文地址:https://www.mshxw.com/it/304673.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号