完全基于docker部署一个php通过rpc访问golang的环境。
基本架构
我们用PHP的Laravel框架来实现一个用户登录的Restful Api,地址为:
POST /user/login
返回信息为用户Id以及JWT token。
Golang用来实现两个服务,一个是用户信息服务,一个是登录的统计服务,PHP通过gRPC与Golang通讯。
最终部署完成后,共有4个docker的container,分别是:
- Nginx服务
- PHP-FPM服务
- 用户信息服务
- 登录统计服务
详细步骤
- 本地环境准备 我的home目录是/home/anakin
apt install protobuf apt install composer mkdir -p www/demo mkdir www/conf.d mkdir www/phpini
进入www目录,下载protobuf的代码,我们要用到其中的一个工具:
git clone https://github.com/grpc/grpc.git
进入www/demo目录,创建Laravel项目:
composer create-project laravel/laravel demo composer require grpc/grpc
稍后我们再来写业务代码。
进入www/conf.d,编写nginx-host.conf,用来解析PHP的服务,内容如下:
server { listen 80; server_name 192.168.32.131; set $root_path '/var/www/html/public'; root $root_path; index index.php index.html index.htm; try_files $uri $uri/ @rewrite; location @rewrite { rewrite ^/(.*)$ /index.php?_url=/$1; } location ~ \.php { fastcgi_pass 192.168.32.131:9000; # fastcgi_pass unix:///run/php/php7.2-fpm.sock; fastcgi_index /index.php; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* ^/(css|img|js|flv|swf|download)/(.+)$ { root $root_path; } location ~ /\.ht { deny all; } error_log /tmp/error.anakin.com.log error; }
其中,192.168.32.131,是宿主机的ip地址。 进入www/phpini,编写custom.ini文件,用来加载php需要的扩展,内容如下:
extension=grpc.so extension=protobuf.so
- 运行PHP的container
mkdir www/phpdocker cd www/phpdocker
编写Dockerfile,内容如下:
FROM php:7.2-fpm RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ procps \ && docker-php-ext-install -j$(nproc) iconv \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install -j$(nproc) gd \ && docker-php-ext-install pdo_mysql \ && docker-php-ext-install zip \ && pecl install grpc\ && pecl install protobuf RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ && composer config -g repo.packagist composer https://packagist.phpcomposer.com
创建image:
docker build . -t php-fpm:v1.0
运行起来:
docker run -d -p 9000:9000 -v /home/anakin/www/demo:/var/www/html -v /home/anakin/www/phpini:/usr/local/etc/php/conf.d --name php-fpm php72:v1.0
映射了我们保持Laravel项目代码的目录进去。
- 运行Nginx的container
docker run -d --name nginx -p 80:80 -v /home/anakin/www/demo:/var/www/html -v /home/anakin/www/conf.d:/etc/nginx/conf.d --link php-fpm:php-fpm nginx
映射了我们刚才存放host文件的目录进去
- 准备rpc服务 首先编写用户信息服务的userrpc.proto文件,内容如下:
syntax = "proto3"; package App.UserRpc; service User{ rpc UserLogin(LoginInfo) returns (UserInfo) {} } message LoginInfo{ string loginname= 1; string password= 2; } message UserInfo{ int32 code= 1; string err_msg= 2; string token= 3; int32 userid= 4; string username= 5; }
然后编写用于统计服务的trace.proto文件,内容如下:
syntax = "proto3"; package App.Trace; service Trace{ rpc Event(EventInfo) returns (TraceResult) {} } message EventInfo{ string eventtype= 1; int32 userid= 2; int32 timestamp= 3; } message TraceResult{ bool succ= 1; }
生成PHP的客户端代码:
protoc --php_out=./ --grpc_out=./ --plugin=protoc-gen-grpc=/home/aaa/grpc/bins/opt/grpc_php_plugin userrpc.proto
生成golang服务端代码:
protoc --go_out=./ userrpc.proto
trace的类似。
- 开发golang服务
在GOPATH下面创建一个user目录,user下面再创建一个userrpc目录,将刚刚生成的userrpc.pb.go放到userrpc目录中。 在user目录下编写main.go,内容如下:
package main import ( "context" pb "github.com/anakin/user/userrpc" "github.com/dgrijalva/jwt-go" "google.golang.org/grpc" "log" "net" "time" ) const ( port = ":50052" ) type User struct { } func (u *User) UserLogin(ctx context.Context, req *pb.LoginInfo) (*pb.UserInfo, error) { log.Printf("received login_name:%v;password:%v", req.Loginname, req.Password) //mock data userId:=123 userName := "anakin" //make jwt token claim := jwt.MapClaims{ "id": userId, "username": userName, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256,claim) tokenString,err := token.SignedString([]byte("token.secret")) if err !=nil{ log.Printf("signed error") } return &pb.UserInfo{Code: 200, ErrMsg: "sss", Userid: 1, Token: tokenString, Username: "anakin"}, nil } func main() { listener, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen:%v", err) } s := grpc.NewServer() pb.RegisterUserServer(s, &User{}) s.Serve(listener) }
编译
GOOS=linux GOARCH=amd64 go build -o UserRpcService
新建一个目录,把编译好的UserRpcService文件放进去,然后编写一个golang的Dockerfile,内容如下:
FROM golang WORKDIR /go/src/ COPY . . EXPOSE 50051 CMD ["/go/src/UserRpcService"]
把这个目录放到宿主机的某个位置,然后进入目录,执行创建image的操作:
docker build . -t userrpc:v1.0
运行服务:
docker run -p 50051:50051 -d userrpc:v1.0
trace服务和这个步骤完全一样,就不复述了。
- 开发php服务
进入www/demo目录,把上面生成的rpc客户端代码加入到Laravl项目中,首先把生成的GPBMetadata目录拷贝到www/demo目录,然后把生成的App下面的UserRpc目录拷贝到www/demo/app目录。
打开www/demo目录下的composer.json,在classmap下添加一行:
GPBMetadata
然后执行:
composer dump-autoload
编辑www/demo/routes/web.php文件,添加下面一行:
Route::post('user/login','UserController@login');
在www/demo/config目录下创建constants.php文件,内容如下:
<?php return [ "USER_RPC_SERVICE"=>"192.168.32.131:50051", "TRACE_RPC_SERVICE"=>"192.168.32.131:50052", ];
在app/Http/Controllers目录下创建UserController.php文件,内容如下:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Log; use Config; class UserController extends Controller { // public function login(Request $request){ $loginname = $request->input("loginname"); $password = (string)$request->input("password"); if($loginname == "" || $password == ""){ return response()->json(["code"=>400,"msg"=>"param error","data"=>[]]); } $user_rpc_service= Config::get('constants.USER_RPC_SERVICE'); $userrpc = new \App\UserRpc\UserClient($user_rpc_service,[ 'credentials' => \Grpc\ChannelCredentials::createInsecure() ]); $request = new \App\UserRpc\LoginInfo(); $request->setLoginname($loginname); $request->setPassword($password); list($recv,$status) = $userrpc->UserLogin($request)->wait(); $code = $recv->getCode(); $msg = $recv->getErrMsg(); $token = $recv->getToken(); $user_id = $recv->getUserid(); $username = $recv->getUsername(); if(200 !=$code){ return response()->json(["code"=>$code,"msg"=>$msg,"data"=>[]]); } $trace_rpc_service = Config::get('constants.TRACE_RPC_SERVICE'); $tracerpc = new \App\Trace\TraceClient($trace_rpc_service,[ 'credentials' => \Grpc\ChannelCredentials::createInsecure() ]); $info = new \App\Trace\EventInfo(); $info->setEventtype("user_login"); $info->setUserid("1"); $info->setTimestamp(time()); list($recv,$status) = $tracerpc->Event($info)->wait(); $trace_succ = $recv->getSucc(); $trace_res = $trace_succ == 1 ? "succ" : "fail"; Log::info("trace result:".$trace_res); return response()->json(["code"=>200,"data"=>["token"=>$token,"userid" =>$user_id,"username"=>$username]]); } }
现在,你可以POST宿主机ip上的80端口,来看看是不是可以访问了。希望我没有遗漏什么步骤。