├── .gitignore
├── deployments
├── app-caculate-fpm-separate.yaml
├── app-caculate-nginx-ingress.yaml
├── app-caculate-nginx-sidecar.yaml
└── ns.yaml
├── dockerfile
├── lumen.Dockerfile
└── runtime.Dockerfile
├── readme-img
├── mesh.jpg
├── nginx-ingress.jpg
├── separate.jpg
└── sidecar.jpg
├── readme.md
└── src
└── lumen-app
├── .dockerignore
├── .editorconfig
├── .env.example
├── .gitignore
├── .styleci.yml
├── Dockerfile
├── README.md
├── app
├── Console
│ ├── Commands
│ │ └── .gitkeep
│ └── Kernel.php
├── Events
│ ├── Event.php
│ └── ExampleEvent.php
├── Exceptions
│ └── Handler.php
├── Http
│ ├── Controllers
│ │ ├── Controller.php
│ │ └── ExampleController.php
│ └── Middleware
│ │ ├── Authenticate.php
│ │ └── ExampleMiddleware.php
├── Jobs
│ ├── ExampleJob.php
│ └── Job.php
├── Listeners
│ └── ExampleListener.php
├── Models
│ └── User.php
└── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ └── EventServiceProvider.php
├── artisan
├── bootstrap
└── app.php
├── composer.json
├── composer.lock
├── database
├── factories
│ └── UserFactory.php
├── migrations
│ └── .gitkeep
└── seeders
│ └── DatabaseSeeder.php
├── phpunit.xml
├── public
├── .htaccess
└── index.php
├── purecode.Dockerfile
├── resources
└── views
│ └── .gitkeep
├── routes
└── web.php
├── storage
├── app
│ └── .gitignore
├── framework
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
└── tests
├── ExampleTest.php
└── TestCase.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /tmp
2 |
--------------------------------------------------------------------------------
/deployments/app-caculate-fpm-separate.yaml:
--------------------------------------------------------------------------------
1 |
2 | # 为 php-fpm 部署 service
3 | apiVersion: v1
4 | kind: Service
5 | metadata:
6 | name: caculate-standalone
7 | namespace: php-test
8 | labels:
9 | app: caculate-standalone
10 | spec:
11 | ports:
12 | - port: 9000
13 | name: tcp
14 | selector:
15 | app: caculate-standalone
16 |
17 | ---
18 | # 部署具体应用
19 | apiVersion: apps/v1
20 | kind: Deployment
21 | metadata:
22 | name: caculate-standalone
23 | namespace: php-test
24 | labels:
25 | app: caculate-standalone
26 | spec:
27 | replicas: 1
28 | selector:
29 | matchLabels:
30 | app: caculate-standalone
31 | template:
32 | metadata:
33 | labels:
34 | app: caculate-standalone
35 | spec:
36 | containers:
37 | - image: cloudbeer/php-caculate:1.0
38 | name: caculate
39 | resources:
40 | requests:
41 | memory: "1Gi"
42 | cpu: "500m"
43 | limits:
44 | memory: "1Gi"
45 | cpu: "500m"
46 | ports:
47 | - containerPort: 9000
48 | protocol: TCP
49 | ---
50 | # nginx config
51 | apiVersion: v1
52 | kind: ConfigMap
53 | metadata:
54 | name: caculate-standalone-config
55 | namespace: php-test
56 | data:
57 | config: |-
58 | server {
59 | listen 8081;
60 | root /app/public;
61 | index index.php
62 | charset utf-8;
63 | # sendfile off;
64 |
65 | location / {
66 | try_files $uri $uri/ /index.php?$args;
67 | }
68 |
69 | location ~ \.php$ {
70 | # fastcgi_split_path_info ^(.+?\.php)(/.*)$;
71 | fastcgi_pass caculate-standalone:9000;
72 | fastcgi_index index.php;
73 | fastcgi_param SCRIPT_FILENAME /app/public/index.php;
74 | include fastcgi_params;
75 | }
76 |
77 | }
78 | ---
79 | # nginx service deploy
80 | apiVersion: v1
81 | kind: Service
82 | metadata:
83 | name: caculate-standalone-service
84 | namespace: php-test
85 | spec:
86 | type: LoadBalancer
87 | selector:
88 | app: nginx
89 | ports:
90 | - protocol: TCP
91 | port: 8081
92 | targetPort: 8081
93 |
94 | ---
95 | # nginx app deploy
96 | apiVersion: apps/v1
97 | kind: Deployment
98 | metadata:
99 | name: caculate-standalone-nginx-deployment
100 | namespace: php-test
101 | spec:
102 | selector:
103 | matchLabels:
104 | app: nginx
105 | template:
106 | metadata:
107 | labels:
108 | app: nginx
109 | spec:
110 | containers:
111 | - name: nginx
112 | image: nginx:alpine
113 | volumeMounts:
114 | - name: nginx-php-config
115 | mountPath: /etc/nginx/conf.d/caculate-standalone.conf
116 | subPath: caculate-standalone.conf
117 | volumes:
118 | - name: nginx-php-config
119 | configMap:
120 | name: caculate-standalone-config
121 | items:
122 | - key: config
123 | path: caculate-standalone.conf
--------------------------------------------------------------------------------
/deployments/app-caculate-nginx-ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: caculate-nginx-ingress-config
5 | namespace: php-test
6 | data:
7 | SCRIPT_FILENAME: "/app/public/index.php"
8 |
9 | ---
10 | apiVersion: v1
11 | kind: Service
12 | metadata:
13 | name: caculate-standalone-fcgi
14 | namespace: php-test
15 | labels:
16 | app: caculate-standalone-fcgi
17 | spec:
18 | ports:
19 | - port: 9000
20 | targetPort: 9000
21 | name: fastcgi
22 | selector:
23 | app: caculate-standalone
24 |
25 | ---
26 | apiVersion: networking.k8s.io/v1beta1
27 | kind: Ingress
28 | metadata:
29 | annotations:
30 | kubernetes.io/ingress.class: "ng-ingress-cal"
31 | nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
32 | nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
33 | # nginx.ingress.kubernetes.io/configuration-snippet: |
34 | # location / {
35 | # try_files $uri $uri/ /index.php?$args;
36 | # }
37 | nginx.ingress.kubernetes.io/fastcgi-params-configmap: "caculate-nginx-ingress-config"
38 | name: caculate-nginx-ingress
39 | namespace: php-test
40 | spec:
41 | rules:
42 | - http:
43 | paths:
44 | - backend:
45 | serviceName: caculate-standalone
46 | servicePort: 9000
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/deployments/app-caculate-nginx-sidecar.yaml:
--------------------------------------------------------------------------------
1 |
2 | # nginx config
3 | apiVersion: v1
4 | kind: ConfigMap
5 | metadata:
6 | name: caculate-sidecar-config
7 | namespace: php-test
8 | data:
9 | config: |-
10 | server {
11 | listen 8081;
12 | root /app/public;
13 | index index.php
14 | charset utf-8;
15 | # sendfile off;
16 |
17 | location / {
18 | try_files $uri $uri/ /index.php?$args;
19 | }
20 |
21 | location ~ \.php$ {
22 | # fastcgi_split_path_info ^(.+?\.php)(/.*)$;
23 | fastcgi_pass 127.0.0.1:9000;
24 | fastcgi_index index.php;
25 | fastcgi_param SCRIPT_FILENAME /app/public/index.php;
26 | include fastcgi_params;
27 | }
28 |
29 | }
30 | ---
31 | # 部署具体应用
32 | apiVersion: apps/v1
33 | kind: Deployment
34 | metadata:
35 | name: caculate-nginx-sidecar
36 | namespace: php-test
37 | labels:
38 | app: caculate-nginx-sidecar
39 | spec:
40 | replicas: 1
41 | selector:
42 | matchLabels:
43 | app: caculate-nginx-sidecar
44 | template:
45 | metadata:
46 | labels:
47 | app: caculate-nginx-sidecar
48 | spec:
49 | volumes:
50 | # web app 的工作目录,绑定到内存中
51 | - name: webapp-data
52 | emptyDir: {}
53 | # nginx 的配置文件,存在 cm 中
54 | - name: nginx-php-config
55 | configMap:
56 | name: caculate-sidecar-config
57 | items:
58 | - key: config
59 | path: caculate-sidecar.conf
60 | # 初始化,将 镜像中的源文件拷贝到容器内存。
61 | initContainers:
62 | - name: copy-code
63 | image: cloudbeer/php-caculate-purecode:1.0
64 | volumeMounts:
65 | - name: webapp-data
66 | mountPath: /webapp
67 | command: [cp, -R, /app/, /webapp]
68 | - name: copy-lumen
69 | image: cloudbeer/my-lumen:1.0
70 | volumeMounts:
71 | - name: webapp-data
72 | mountPath: /webapp
73 | command: [cp, -R, /app/, /webapp]
74 | containers:
75 | # fpm app
76 | - name: caculate
77 | image: cloudbeer/php-runtime:1.0
78 | resources:
79 | requests:
80 | memory: "1Gi"
81 | cpu: "500m"
82 | limits:
83 | memory: "1Gi"
84 | cpu: "500m"
85 | ports:
86 | - containerPort: 9000
87 | protocol: TCP
88 | volumeMounts:
89 | - name: webapp-data
90 | mountPath: /app
91 | subPath: app
92 | # nginx sidecar
93 | - name: nginx-sidecar
94 | image: nginx:alpine
95 | volumeMounts:
96 | - name: nginx-php-config
97 | mountPath: /etc/nginx/conf.d/caculate-sidecar.conf
98 | subPath: caculate-sidecar.conf
99 | - name: webapp-data
100 | mountPath: /app
101 | subPath: app
102 | # imagePullSecrets:
103 | # - name: wellxiecred
104 | ---
105 | # nginx service deploy
106 | apiVersion: v1
107 | kind: Service
108 | metadata:
109 | name: caculate-sidecar-service
110 | namespace: php-test
111 | spec:
112 | type: LoadBalancer
113 | selector:
114 | app: caculate-nginx-sidecar
115 | ports:
116 | - protocol: TCP
117 | port: 8081
118 | targetPort: 8081
119 |
--------------------------------------------------------------------------------
/deployments/ns.yaml:
--------------------------------------------------------------------------------
1 | # 创建命名空间
2 | apiVersion: v1
3 | kind: Namespace
4 | metadata:
5 | name: php-test
6 | spec:
7 | finalizers:
8 | - kubernetes
9 |
--------------------------------------------------------------------------------
/dockerfile/lumen.Dockerfile:
--------------------------------------------------------------------------------
1 | # FROM ccr.ccs.tencentyun.com/wellxie/php-runtime:1.0
2 | FROM cloudbeer/php-runtime:1.0
3 |
4 | RUN mkdir /app
5 | WORKDIR /app
6 |
7 | RUN echo '{\
8 | "require": {\
9 | "php": "^7.3|^8.0",\
10 | "laravel/lumen-framework": "^8.0"\
11 | }\
12 | }' > composer.json
13 |
14 | RUN composer i
15 |
16 |
--------------------------------------------------------------------------------
/dockerfile/runtime.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.0.3-fpm
2 |
3 |
4 | # 安装php模块,8.0.3 已内置了如下:
5 | # Core
6 | # ctype
7 | # curl
8 | # date
9 | # dom
10 | # fileinfo
11 | # filter
12 | # ftp
13 | # hash
14 | # iconv
15 | # json
16 | # libxml
17 | # mbstring
18 | # mysqlnd
19 | # openssl
20 | # pcre
21 | # PDO
22 | # pdo_sqlite
23 | # Phar
24 | # posix
25 | # readline
26 | # Reflection
27 | # session
28 | # SimpleXML
29 | # sodium
30 | # SPL
31 | # sqlite3
32 | # standard
33 | # tokenizer
34 | # xml
35 | # xmlreader
36 | # xmlwriter
37 | # zlib
38 |
39 | # 自定义类库示例 (composer 包安装依赖 git)
40 | RUN apt-get update && \
41 | apt-get install -y git zlib1g-dev libpng-dev libicu-dev
42 |
43 | RUN docker-php-ext-install \
44 | gd \
45 | intl
46 |
47 |
48 | # configure intl
49 | RUN docker-php-ext-configure intl
50 |
51 |
52 |
53 | # 安装 composer
54 | RUN curl -sS https://getcomposer.org/installer | php -- \
55 | --install-dir=/usr/local/bin --filename=composer \
56 | && chmod +x /usr/local/bin/composer
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/readme-img/mesh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/readme-img/mesh.jpg
--------------------------------------------------------------------------------
/readme-img/nginx-ingress.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/readme-img/nginx-ingress.jpg
--------------------------------------------------------------------------------
/readme-img/separate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/readme-img/separate.jpg
--------------------------------------------------------------------------------
/readme-img/sidecar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/readme-img/sidecar.jpg
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # php应用容器化部署实践
2 | 目前市场上 php 仍有一席之地。本文章将探讨如何将 php 应用容器化并迁移部署到 TKE。
3 |
4 | ## php 应用镜像准备
5 | 镜像的层次:基础依赖镜像->运行框架->应用/代码镜像
6 |
7 | 基于容器的单进程运行理念,下面的部署过程并未使用单体的 nginx+php-fpm 一体的容器运行方式,而是将 php-fpm 和 nginx 拆散。
8 |
9 | ### 基础镜像
10 | 安装基础系统依赖包和公司 php 应用中各个开发小组都会用到的扩展包。
11 | 下面的示例基于官方 fpm,安装了通用系统级的依赖和 php 包管理器。
12 | 如果可以,建议使用更基础的镜像从 php 源码进行编译。
13 |
14 | ```Dockerfile
15 | # runtime.Dockerfile
16 | FROM php:8.0.3-fpm
17 |
18 | # 自定义类库示例
19 | RUN apt-get update && \
20 | apt-get install -y git zlib1g-dev libpng-dev libicu-dev
21 | RUN docker-php-ext-install \
22 | gd \
23 | intl
24 | RUN docker-php-ext-configure intl
25 |
26 |
27 | # 安装 composer
28 | RUN curl -sS https://getcomposer.org/installer | php -- \
29 | --install-dir=/usr/local/bin --filename=composer \
30 | && chmod +x /usr/local/bin/composer
31 | ```
32 |
33 | 将上述文件编译成镜像,并 push 到仓库:
34 |
35 | ```shell
36 | docker build -t cloudbeer/php-runtime:1.0 -f runtime.Dockerfile .
37 | docker push cloudbeer/php-runtime:1.0
38 | ```
39 |
40 | ### 应用层框架镜像
41 | 如果开发框架比较稳定,建议直接把框架打包成基础镜像以避免后续部署过程中频繁安装依赖包,加速发布打包发布过程,如业务开发A组使用了 lumen 框架,我们可以专门为 lumen 打一个镜像。
42 |
43 | 如下镜像,安装了 lumen web 框架。
44 |
45 | ```Dockerfile
46 | # lumen.Dockerfile
47 | FROM cloudbeer/php-runtime:1.0
48 |
49 | RUN mkdir /app
50 | WORKDIR /app
51 |
52 | RUN echo '{\
53 | "require": {\
54 | "php": "^7.3|^8.0",\
55 | "laravel/lumen-framework": "^8.0"\
56 | }\
57 | }' > composer.json
58 |
59 | RUN composer i
60 | ```
61 |
62 | 上述镜像打包为:cloudbeer/my-lumen:1.0
63 |
64 | ### 应用层镜像
65 | 由于我们在应用层框架里已经把 lumen 运行时都安装好了,所以这个镜像里,只需拷贝纯源码即可。记得创建 .gitignore 或者 .dockerignore 文件,排除 vender,test 等目录。
66 | ```
67 | # .dockerignore
68 | /vendor
69 | /tests
70 | /.idea
71 | /.git
72 | .gitignore
73 | Dockerfile
74 | Homestead.json
75 | Homestead.yaml
76 | .env
77 | .ent.example
78 | .phpunit.result.cache
79 | ```
80 |
81 | 应用镜像里只需要拷贝脚本文件即可。这个镜像包含了完整的 php 运行时和业务代码,启动后可以直接接收 fastcgi 调用。
82 |
83 | ```Dockerfile
84 | FROM cloudbeer/my-lumen:1.0
85 | COPY ./ /app/
86 | ```
87 |
88 | 上面的镜像打包为: cloudbeer/php-caculate:1.0
89 |
90 | 另一种打包代码方式,我们使用纯的容器将源代码打包,后面会在 K8S 中部署时将文件拷贝到框架运行时容器中运行。
91 |
92 | ```Dockerfile
93 | FROM debian:buster-slim
94 | RUN mkdir /app
95 | COPY ./ /app/
96 | ```
97 |
98 | 上面的镜像打包为: cloudbeer/php-caculate-purecode:1.0
99 |
100 | 代码层在还可以有更多的打包方式,如上传到对象存储里,或者使用 NFS 存储,后期绑定到容器运行时运行。
101 |
102 |
103 | ### 本地测试
104 |
105 | 启动镜像 cloudbeer/php-caculate:1.0
106 |
107 | ```
108 | docker run --rm -it cloudbeer/php-caculate:1.0 sh
109 |
110 | # 容器内运行
111 | # 启动 php 测试
112 | php -S localhost:8000 -t public &
113 | # 查看结果
114 | curl http://localhost:8000/cpu
115 | ```
116 | 完美运行。
117 |
118 | 目前已经完成了应用镜像打包。其中前两层镜像可以复用,真正的业务应用只需拷贝代码。
119 |
120 | 上述代码中的镜像,我均已打包上传到 docker hub 官网,可以忽略 build 和 push 过程,直接进行测试。
121 |
122 |
123 | ## 部署到 K8S/TKE
124 |
125 | php 应用部署到容器环境,最自然的一种方式是:直接将 php 的运行环境和web server 以及业务源代码打包放在一个容器中运行。这个方式是最简单的方式,php 官方也提供了 php:nginx 这种镜像底包。
126 |
127 | 但 php 运行时和 web server 是在两个进程中运行,这个不符合容器的最佳实践。一般建议将这两个进程分别运行在不同的容器中。
128 |
129 | ### nginx 作为 sidecar 运行
130 |
131 | K8S 在同一个 pod 中,可以运行多个容器。我们将 php-fpm 的业务代码部署在一个容器中,与之相伴生的有一个 nginx 容器,nginx 作为fastcgi的调用方,并可以代理一些静态资源,这个模式类似 mesh 的sidecar 模式。架构图如下:
132 |
133 | 
134 |
135 | #### nginx 配置
136 | 由于 nginx 和 php-fpm 在一个 pod 中,所以只需发起 localhost 调用即可。 nginx 的配置如下,我们将这个配置写到 cm 中,后面通过 volume 绑定到容器中。这个配置有几点需要注意的:
137 | - 应用使用了 lumen 的 route 体系,所以需要将路由通过 try_files 全部指向 ./public/index.php 文件。
138 | - fastcgi_param SCRIPT_FILENAME /app/public/index.php 这里也将所有脚本指向这个文件。
139 |
140 | ```yaml
141 | # nginx config
142 | apiVersion: v1
143 | kind: ConfigMap
144 | metadata:
145 | name: caculate-sidecar-config
146 | namespace: php-test
147 | data:
148 | config: |-
149 | server {
150 | listen 8081;
151 | root /app/public;
152 | index index.php
153 | charset utf-8;
154 |
155 | location / {
156 | try_files $uri $uri/ /index.php?$args;
157 | }
158 |
159 | location ~ \.php$ {
160 | fastcgi_pass 127.0.0.1:9000;
161 | fastcgi_index index.php;
162 | fastcgi_param SCRIPT_FILENAME /app/public/index.php;
163 | include fastcgi_params;
164 | }
165 | }
166 | ```
167 |
168 | #### 应用部署脚本
169 |
170 | 在下面的部署脚本中,有几点值得关注一下:
171 | - 使用了 emptyDir:{} 作为容器的源代码存储介质,这样可以将应用读取到临时目录中,加速运行时 php 源码的加载。如果脚本文件不大,可以指定 emptyDir 使用内存运行,这个可以更加加速脚本加载。
172 | - pod 启动的时候使用了 2 个 初始化容器,使用的镜像分别是:极简源代码的镜像(php-caculate-purecode)和框架运行时镜像(my-lumen),在启动的时候分别将 /app 的代码拷贝到了 emptyDir 卷中,分别拷贝了lumen的 vendor 依赖和业务源代码。这里拷贝业务源代码过程亦可以使用 cfs 或者 cos 来实现。由于使用了宿主机存储源代码,当源代码过于庞大的时候,请注意超出宿主机存储容量风险。
173 | - 由于源码和依赖包都已经在 initContainers 组织好了,所以,只需要使用 php 基础容器来启动应用即可。
174 |
175 |
176 | ```yaml
177 | apiVersion: apps/v1
178 | kind: Deployment
179 | metadata:
180 | name: caculate-nginx-sidecar
181 | namespace: php-test
182 | labels:
183 | app: caculate-nginx-sidecar
184 | spec:
185 | replicas: 1
186 | selector:
187 | matchLabels:
188 | app: caculate-nginx-sidecar
189 | template:
190 | metadata:
191 | labels:
192 | app: caculate-nginx-sidecar
193 | spec:
194 | volumes:
195 | # web app 的工作目录
196 | - name: webapp-data
197 | emptyDir: {}
198 | # nginx 的配置文件,从 cm 中绑定过来
199 | - name: nginx-php-config
200 | configMap:
201 | name: caculate-sidecar-config
202 | items:
203 | - key: config
204 | path: caculate-sidecar.conf
205 | # 初始化,将镜像中的源文件拷贝到容器内存。
206 | initContainers:
207 | - name: copy-code
208 | image: cloudbeer/php-caculate-purecode:1.0
209 | volumeMounts:
210 | - name: webapp-data
211 | mountPath: /webapp
212 | command: [cp, -R, /app/, /webapp]
213 | - name: copy-lumen
214 | image: cloudbeer/my-lumen:1.0
215 | volumeMounts:
216 | - name: webapp-data
217 | mountPath: /webapp
218 | command: [cp, -R, /app/, /webapp]
219 | containers:
220 | # fpm 应用运行环境
221 | - name: caculate
222 | image: cloudbeer/php-runtime:1.0
223 | resources:
224 | requests:
225 | memory: "1Gi"
226 | cpu: "500m"
227 | limits:
228 | memory: "1Gi"
229 | cpu: "500m"
230 | ports:
231 | - containerPort: 9000
232 | protocol: TCP
233 | volumeMounts:
234 | - name: webapp-data
235 | mountPath: /app
236 | subPath: app
237 | # nginx sidecar
238 | - name: nginx-sidecar
239 | image: nginx:alpine
240 | volumeMounts:
241 | - name: nginx-php-config
242 | mountPath: /etc/nginx/conf.d/caculate-sidecar.conf
243 | subPath: caculate-sidecar.conf
244 | - name: webapp-data
245 | mountPath: /app
246 | subPath: app
247 | ---
248 | ```
249 |
250 | 挂一个 LoadBalancer 看看。
251 |
252 | ```yaml
253 | apiVersion: v1
254 | kind: Service
255 | metadata:
256 | name: caculate-sidecar-service
257 | namespace: php-test
258 | spec:
259 | type: LoadBalancer
260 | selector:
261 | app: caculate-nginx-sidecar
262 | ports:
263 | - protocol: TCP
264 | port: 8081
265 | targetPort: 8081
266 | ```
267 | 访问对应的 外部 ip:8081,完美运行。
268 |
269 | ### nginx 独立部署
270 |
271 | 通常情况下,运维部门希望将 web server 收敛并统一管理,开发也不太关心 nginx 的具体配置,将两者进行拆分众望所归,并且在微服务的横向扩展中,这种方式也更加“优雅”。
272 |
273 | 部署架构图如下:
274 |
275 | 
276 |
277 |
278 |
279 | #### 部署 fpm 业务应用
280 | - 此处部署了 php-caculate 镜像,此镜像里包含了源代码,Web框架以及 php 运行时,是一个完整的 php-fpm 业务应用。
281 | - 通过 service 发布应用,以便 nginx 能发现 fpm 服务,并解偶了 webserver 和 业务服务。后期可以做纯php 业务的横向扩展和 nginx 的集中管理。
282 |
283 | ```yaml
284 | # 为 php-fpm 部署 service
285 | apiVersion: v1
286 | kind: Service
287 | metadata:
288 | name: caculate-standalone
289 | namespace: php-test
290 | labels:
291 | app: caculate-standalone
292 | spec:
293 | ports:
294 | - port: 9000
295 | name: tcp
296 | selector:
297 | app: caculate-standalone
298 |
299 | ---
300 | # 部署具体应用
301 | apiVersion: apps/v1
302 | kind: Deployment
303 | metadata:
304 | name: caculate-standalone
305 | namespace: php-test
306 | labels:
307 | app: caculate-standalone
308 | spec:
309 | replicas: 1
310 | selector:
311 | matchLabels:
312 | app: caculate-standalone
313 | template:
314 | metadata:
315 | labels:
316 | app: caculate-standalone
317 | spec:
318 | containers:
319 | - image: cloudbeer/php-caculate:1.0
320 | name: caculate
321 | resources:
322 | requests:
323 | memory: "1Gi"
324 | cpu: "500m"
325 | limits:
326 | memory: "1Gi"
327 | cpu: "500m"
328 | ports:
329 | - containerPort: 9000
330 | protocol: TCP
331 | ```
332 |
333 | #### nginx 部署
334 |
335 | 此部分的 nginx 配置基本和上面一样,唯一的区别就是 fastcgi_pass 的调用目标变成了 php 业务的 service:caculate-standalone 了。
336 |
337 | ```yaml
338 | apiVersion: v1
339 | kind: ConfigMap
340 | metadata:
341 | name: caculate-standalone-config
342 | namespace: php-test
343 | data:
344 | config: |-
345 | server {
346 | listen 8081;
347 | root /app/public;
348 | index index.php
349 | charset utf-8;
350 |
351 | location / {
352 | try_files $uri $uri/ /index.php?$args;
353 | }
354 |
355 | location ~ \.php$ {
356 | fastcgi_pass caculate-standalone:9000;
357 | fastcgi_index index.php;
358 | fastcgi_param SCRIPT_FILENAME /app/public/index.php;
359 | include fastcgi_params;
360 | }
361 | }
362 | ```
363 |
364 | 下面为 nginx 配置单独 pod 启动。
365 |
366 | ```yaml
367 | apiVersion: apps/v1
368 | kind: Deployment
369 | metadata:
370 | name: caculate-standalone-nginx-deployment
371 | namespace: php-test
372 | spec:
373 | selector:
374 | matchLabels:
375 | app: nginx
376 | template:
377 | metadata:
378 | labels:
379 | app: nginx
380 | spec:
381 | containers:
382 | - name: nginx
383 | image: nginx:alpine
384 | volumeMounts:
385 | - name: nginx-php-config
386 | mountPath: /etc/nginx/conf.d/caculate-standalone.conf
387 | subPath: caculate-standalone.conf
388 | volumes:
389 | - name: nginx-php-config
390 | configMap:
391 | name: caculate-standalone-config
392 | items:
393 | - key: config
394 | path: caculate-standalone.conf
395 | ```
396 |
397 | 现在,给 nginx 应用挂一个 LoadBalancer 测试一下,亦完美运行。
398 |
399 |
400 | ### 使用 nginx-ingress 部署
401 |
402 | 上面的部署架构图中,ingress 和 nginx 分别进行了部署,但 nginx-ingress 其实已经合并了这两个部分,并且 TKE 提供了现成的 nginx-ingress。现在,我们试试使用 nginx-ingress 部署。
403 |
404 | 
405 |
406 |
407 | 在 TKE 集群内安装 nginx-ingress,参考这篇:
408 | https://cloud.tencent.com/document/product/457/50503
409 |
410 | 使用上面的已经部署好的 fpm 业务的service: caculate-standalone 。
411 |
412 | 部署脚本如下:
413 | - ingress.class 的值是创建 nginx-ingress 时候在控制台定义的。
414 | - 与纯 nginx 配置不同,nginx-ingress 无需配置 try_files 节点,下面的配置其实已经将全部请求转发到了 public/index.php。
415 |
416 | ```yaml
417 | apiVersion: v1
418 | kind: ConfigMap
419 | metadata:
420 | name: caculate-nginx-ingress-config
421 | namespace: php-test
422 | data:
423 | SCRIPT_FILENAME: "/app/public/index.php"
424 | ---
425 | apiVersion: networking.k8s.io/v1beta1
426 | kind: Ingress
427 | metadata:
428 | annotations:
429 | kubernetes.io/ingress.class: "your-ingress-class"
430 | nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
431 | nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
432 | nginx.ingress.kubernetes.io/fastcgi-params-configmap: "caculate-nginx-ingress-config"
433 | name: caculate-nginx-ingress
434 | namespace: php-test
435 | spec:
436 | rules:
437 | - http:
438 | paths:
439 | - backend:
440 | serviceName: caculate-standalone
441 | servicePort: 9000
442 | ```
443 | TKE 的 nginx-ingress 直接提供了一个外网地址,访问一下试试,完美运行。
444 |
445 |
446 |
447 | ## MORE:mesh 化
448 |
449 | 在 php mesh 化中,需要考虑的问题如下:
450 | - fastcgi 使用 TCP 协议,并且有自己的序列化方法,此特性并未在 istio 和 envoy 中支持,无法进行精细的流量控制。
451 | - fpm + envoy sidecar 可以实现服务级别的流量控制。 fpm + nginx-sidecar + envoy sidecar 将 fastcgi 调用转化为 http 调用,可以实现基于 http 协议的精细流量控制。
452 | - 实现调用链监控,需要使用 http 进行远程调用,有可能需要改造代码,在header 中封装 opentracing 。
453 |
454 | 当下,php mesh 的部署模式建议采用 fpm 业务层,nginx-sidecar, envoy-sidecar 三个容器为一个pod 的部署模式。
455 |
456 | 架构图如下:
457 |
458 | 
459 |
460 |
461 | 此处的部署与第一部分的内容 - nginx 作为 sidecar 运行类似,在腾讯云中需要开通 TCM,并注入 envoy 的 sidecar。
462 |
463 |
464 | ---
465 |
466 | 本文相关的源代码说明:
467 | 位置:https://github.com/cloudbeer/php-best-practice
468 |
469 | ## src/lumen-app/
470 | php 业务应用,映射了2个路径
471 | - /cpu: 两种方式计算了 pi
472 | - /mem: 在数组里塞了字符串为了撑爆内存
473 |
474 | ## deployments/
475 |
476 | 部署代码
477 |
478 | - app-caculate-fpm-separate.yaml: nginx 和 fpm 分在不同的 pod 中部署
479 | - app-caculate-nginx-ingress.yaml: nginx-ingress 直接代理 fpm 服务
480 | - app-caculate-nginx-sidecar.yaml: nginx 和 fpm 部署在同一个pod中的两个容器
481 |
482 | ## dockerfile/
483 |
484 | - lumen.Dockerfile:包含 lumen 框架的运行环境
485 | - runtime.Dockerfile:php 基础运行环境
486 |
487 | 业务代码的 Dockerfile 位于 src/lumen-app/Dockerfile 和 src/lumen-app/purecode.Dockerfile
488 |
489 |
490 |
491 |
492 |
--------------------------------------------------------------------------------
/src/lumen-app/.dockerignore:
--------------------------------------------------------------------------------
1 | # .dockerignore
2 | /vendor
3 | /.idea
4 | /.git
5 | .gitignore
6 | /tests
7 | Dockerfile
8 | Homestead.json
9 | Homestead.yaml
10 | .env
11 | .ent.example
12 | .phpunit.result.cache
--------------------------------------------------------------------------------
/src/lumen-app/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/src/lumen-app/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Lumen
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 | APP_TIMEZONE=UTC
7 |
8 | LOG_CHANNEL=stack
9 | LOG_SLACK_WEBHOOK_URL=
10 |
11 | DB_CONNECTION=mysql
12 | DB_HOST=127.0.0.1
13 | DB_PORT=3306
14 | DB_DATABASE=homestead
15 | DB_USERNAME=homestead
16 | DB_PASSWORD=secret
17 |
18 | CACHE_DRIVER=file
19 | QUEUE_CONNECTION=sync
20 |
--------------------------------------------------------------------------------
/src/lumen-app/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /.idea
3 | Homestead.json
4 | Homestead.yaml
5 | .env
6 | .phpunit.result.cache
7 |
--------------------------------------------------------------------------------
/src/lumen-app/.styleci.yml:
--------------------------------------------------------------------------------
1 | php:
2 | preset: laravel
3 | disabled:
4 | - unused_use
5 | js: true
6 | css: true
7 |
--------------------------------------------------------------------------------
/src/lumen-app/Dockerfile:
--------------------------------------------------------------------------------
1 | # FROM ccr.ccs.tencentyun.com/wellxie/my-lumen:1.0
2 | FROM cloudbeer/my-lumen:1.0
3 |
4 | COPY ./ /app/
5 |
--------------------------------------------------------------------------------
/src/lumen-app/README.md:
--------------------------------------------------------------------------------
1 | # Lumen PHP Framework
2 |
3 | [](https://travis-ci.org/laravel/lumen-framework)
4 | [](https://packagist.org/packages/laravel/lumen-framework)
5 | [](https://packagist.org/packages/laravel/lumen-framework)
6 | [](https://packagist.org/packages/laravel/lumen-framework)
7 |
8 | Laravel Lumen is a stunningly fast PHP micro-framework for building web applications with expressive, elegant syntax. We believe development must be an enjoyable, creative experience to be truly fulfilling. Lumen attempts to take the pain out of development by easing common tasks used in the majority of web projects, such as routing, database abstraction, queueing, and caching.
9 |
10 | ## Official Documentation
11 |
12 | Documentation for the framework can be found on the [Lumen website](https://lumen.laravel.com/docs).
13 |
14 | ## Contributing
15 |
16 | Thank you for considering contributing to Lumen! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
17 |
18 | ## Security Vulnerabilities
19 |
20 | If you discover a security vulnerability within Lumen, please send an e-mail to Taylor Otwell at taylor@laravel.com. All security vulnerabilities will be promptly addressed.
21 |
22 | ## License
23 |
24 | The Lumen framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
25 |
--------------------------------------------------------------------------------
/src/lumen-app/app/Console/Commands/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/src/lumen-app/app/Console/Commands/.gitkeep
--------------------------------------------------------------------------------
/src/lumen-app/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
26 | }
27 |
28 | /**
29 | * Handle an incoming request.
30 | *
31 | * @param \Illuminate\Http\Request $request
32 | * @param \Closure $next
33 | * @param string|null $guard
34 | * @return mixed
35 | */
36 | public function handle($request, Closure $next, $guard = null)
37 | {
38 | if ($this->auth->guard($guard)->guest()) {
39 | return response('Unauthorized.', 401);
40 | }
41 |
42 | return $next($request);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/lumen-app/app/Http/Middleware/ExampleMiddleware.php:
--------------------------------------------------------------------------------
1 | app['auth']->viaRequest('api', function ($request) {
34 | if ($request->input('api_token')) {
35 | return User::where('api_token', $request->input('api_token'))->first();
36 | }
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/lumen-app/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
16 | \App\Listeners\ExampleListener::class,
17 | ],
18 | ];
19 | }
20 |
--------------------------------------------------------------------------------
/src/lumen-app/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(
32 | 'Illuminate\Contracts\Console\Kernel'
33 | );
34 |
35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput));
36 |
--------------------------------------------------------------------------------
/src/lumen-app/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | bootstrap();
8 |
9 | date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
10 |
11 | /*
12 | |--------------------------------------------------------------------------
13 | | Create The Application
14 | |--------------------------------------------------------------------------
15 | |
16 | | Here we will load the environment and create the application instance
17 | | that serves as the central piece of this framework. We'll use this
18 | | application as an "IoC" container and router for this framework.
19 | |
20 | */
21 |
22 | $app = new Laravel\Lumen\Application(
23 | dirname(__DIR__)
24 | );
25 |
26 | // $app->withFacades();
27 |
28 | // $app->withEloquent();
29 |
30 | /*
31 | |--------------------------------------------------------------------------
32 | | Register Container Bindings
33 | |--------------------------------------------------------------------------
34 | |
35 | | Now we will register a few bindings in the service container. We will
36 | | register the exception handler and the console kernel. You may add
37 | | your own bindings here if you like or you can make another file.
38 | |
39 | */
40 |
41 | $app->singleton(
42 | Illuminate\Contracts\Debug\ExceptionHandler::class,
43 | App\Exceptions\Handler::class
44 | );
45 |
46 | $app->singleton(
47 | Illuminate\Contracts\Console\Kernel::class,
48 | App\Console\Kernel::class
49 | );
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Register Config Files
54 | |--------------------------------------------------------------------------
55 | |
56 | | Now we will register the "app" configuration file. If the file exists in
57 | | your configuration directory it will be loaded; otherwise, we'll load
58 | | the default version. You may register other files below as needed.
59 | |
60 | */
61 |
62 | $app->configure('app');
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Register Middleware
67 | |--------------------------------------------------------------------------
68 | |
69 | | Next, we will register the middleware with the application. These can
70 | | be global middleware that run before and after each request into a
71 | | route or middleware that'll be assigned to some specific routes.
72 | |
73 | */
74 |
75 | // $app->middleware([
76 | // App\Http\Middleware\ExampleMiddleware::class
77 | // ]);
78 |
79 | // $app->routeMiddleware([
80 | // 'auth' => App\Http\Middleware\Authenticate::class,
81 | // ]);
82 |
83 | /*
84 | |--------------------------------------------------------------------------
85 | | Register Service Providers
86 | |--------------------------------------------------------------------------
87 | |
88 | | Here we will register all of the application's service providers which
89 | | are used to bind services into the container. Service providers are
90 | | totally optional, so you are not required to uncomment this line.
91 | |
92 | */
93 |
94 | // $app->register(App\Providers\AppServiceProvider::class);
95 | // $app->register(App\Providers\AuthServiceProvider::class);
96 | // $app->register(App\Providers\EventServiceProvider::class);
97 |
98 | /*
99 | |--------------------------------------------------------------------------
100 | | Load The Application Routes
101 | |--------------------------------------------------------------------------
102 | |
103 | | Next we will include the routes file so that they can all be added to
104 | | the application. This will provide all of the URLs the application
105 | | can respond to, as well as the controllers that may handle them.
106 | |
107 | */
108 |
109 | $app->router->group([
110 | 'namespace' => 'App\Http\Controllers',
111 | ], function ($router) {
112 | require __DIR__.'/../routes/web.php';
113 | });
114 |
115 | return $app;
116 |
--------------------------------------------------------------------------------
/src/lumen-app/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/lumen",
3 | "description": "The Laravel Lumen Framework.",
4 | "keywords": ["framework", "laravel", "lumen"],
5 | "license": "MIT",
6 | "type": "project",
7 | "require": {
8 | "php": "^7.3|^8.0",
9 | "laravel/lumen-framework": "^8.0"
10 | },
11 | "require-dev": {
12 | "fakerphp/faker": "^1.9.1",
13 | "mockery/mockery": "^1.3.1",
14 | "phpunit/phpunit": "^9.3"
15 | },
16 | "autoload": {
17 | "psr-4": {
18 | "App\\": "app/",
19 | "Database\\Factories\\": "database/factories/",
20 | "Database\\Seeders\\": "database/seeders/"
21 | }
22 | },
23 | "autoload-dev": {
24 | "classmap": [
25 | "tests/"
26 | ]
27 | },
28 | "config": {
29 | "preferred-install": "dist",
30 | "sort-packages": true,
31 | "optimize-autoloader": true
32 | },
33 | "minimum-stability": "dev",
34 | "prefer-stable": true,
35 | "scripts": {
36 | "post-root-package-install": [
37 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
38 | ]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/lumen-app/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->name,
26 | 'email' => $this->faker->unique()->safeEmail,
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/lumen-app/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/src/lumen-app/database/migrations/.gitkeep
--------------------------------------------------------------------------------
/src/lumen-app/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call('UsersTableSeeder');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/lumen-app/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ./tests
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/lumen-app/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Handle Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/src/lumen-app/public/index.php:
--------------------------------------------------------------------------------
1 | run();
29 |
--------------------------------------------------------------------------------
/src/lumen-app/purecode.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:buster-slim
2 | RUN mkdir /app
3 | COPY ./ /app/
4 |
--------------------------------------------------------------------------------
/src/lumen-app/resources/views/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloudbeer/php-best-practice/bfb089288abf4f9d469eba1f80798c2ccd95f43d/src/lumen-app/resources/views/.gitkeep
--------------------------------------------------------------------------------
/src/lumen-app/routes/web.php:
--------------------------------------------------------------------------------
1 | get('/', function () use ($router) {
17 | return $router->app->version();
18 | });
19 |
20 |
21 | $router->get('/cpu', function () use ($router) {
22 | $pi = 4; $top = 4; $bot = 3; $minus = TRUE;
23 | $accuracy = 20000000;
24 |
25 | $start = microtime(true);
26 | for($i = 0; $i < $accuracy; $i++)
27 | {
28 | $pi += ( $minus ? -($top/$bot) : ($top/$bot) );
29 | $minus = ( $minus ? FALSE : TRUE);
30 | $bot += 2;
31 | }
32 | $end = microtime(true);
33 | $time1 = (($end-$start)*1000) . 'ms';
34 |
35 |
36 | $start = microtime(true);
37 | $bottom = 1;
38 | $pi2 = 0;
39 | for ($i = 1; $i < $accuracy; $i++) {
40 | if ($i % 2 == 1) {
41 | $pi2 += 4 / $bottom;
42 | } else {
43 | $pi2 -= 4 / $bottom;
44 | }
45 | $bottom += 2;
46 | }
47 | $end = microtime(true);
48 | $time2 = (($end-$start)*1000) . 'ms';
49 | return array(
50 | 'pi-01'=> $pi,
51 | 'pi-02'=> $pi2,
52 | 'loop'=>$accuracy,
53 | 'pi-01-duration'=>$time1,
54 | 'pi-02-duration'=> $time2
55 | );
56 | });
57 |
58 |
59 | $router->get('/mem', function () use ($router) {
60 | $accuracy = 100000;
61 | $container = array();
62 | $start = microtime(true);
63 | for ($i = 0; $i < $accuracy; $i++) {
64 | array_push($container, 'abcd' . microtime() ."-" . rand(), 'xyz' . microtime() ."-" . rand());
65 | }
66 | $end = microtime(true);
67 | return array(
68 | 'loop'=>$accuracy,
69 | 'len'=> count($container),
70 | 'duration' => (($end-$start)*1000) . 'ms'
71 | );
72 |
73 | });
--------------------------------------------------------------------------------
/src/lumen-app/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/src/lumen-app/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/src/lumen-app/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/src/lumen-app/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/src/lumen-app/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/src/lumen-app/tests/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
16 |
17 | $this->assertEquals(
18 | $this->app->version(), $this->response->getContent()
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/lumen-app/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |