Open gusev-genotek opened 7 years ago
Indeed, it would be great to get info about it. I haven't deployed Yii 2 to GAE so if you did, please share your experience.
@samdark The above is basically my experience. To that I could add that the deployment is done via a Dockerfile and app.yaml files. What other details shall I provide?
Well, examples of such files would be cool.
I am attaching 2 Dockerfiles:
Base image with our dependencies which are not changing often. Important for this discussion is the gcsfuse part. Currently this image can be found in docker hub as vladgen/yii2-apache-php7-bcm-gd-r-gcs Dockerfile-yii2-gae-base.txt
Actual application image using 1. with the application php code that changes often. Dockerfile-yii2-gae-app.txt
app.yaml file app.yaml.txt
to deploy this to GAE, place second Dockerfile and app.yaml into your yii2 project, change the settings to suit your app and run, for example
gcloud app deploy app.yaml --quiet --verbosity debug
Once it's deployed, ssh to your GAE instance (via a google web console), run
container_exec gaeapp /bin/bash
to get into your worker instance and then execute one of the gcsfuse mount commands. Since I am using fstab with gcsfuse I do
mount -a
to mount all gs buckets as folders, as described in the /etc/fstab.
The latter is done, by, for example
MY_BUCKET_NAME MY_LOCAL_PATH gcsfuse rw,noauto,allow_other,implicit_dirs,dir_mode=777,file_mode=777
I don't know much about either GAE or dockers but I've seen a talk about a custom docker images they made for running PHP on their App Engine Flexible Runtime: https://github.com/GoogleCloudPlatform/php-docker
They where also talking about PHP security patches they added to their images. see it here: https://youtu.be/9PedC_6ZC3Q?t=7m19s
I just deployed a similar template to this to a flex engine which is same as the advanced one except that it has api
and auth
folders as restful entries so I use no assets or session. But I use Redis within RedisLab (free up to 30mb and they are in the same datacenter) to store my short-living access tokens + I need it for later use with yii2-queue extension.
My steps where:
GAE account + create new project + enable billing + download their SDK (better described here: https://cloud.google.com/php/getting-started/hello-world) NOTE: I use their Flexible environment instead of the Standard one as the latter uses php 5.5. read more about differences and tradeoffs of each here: https://cloud.google.com/php/quickstarts
I used their SQL Cloud for DB which auto scales. You have to follow any of those steps and note your connectionName
somewhere:
For my 2 folders api
and auth
I created 2 files. Respectively api.yaml
and auth.yaml
as I wanted to deploy them as 2 separate services. They look pretty much the same. This is one of them:
runtime: php
env: flex
api_version: 1
service: auth
runtime_config:
document_root: auth/web
beta_settings:
cloud_sql_instances: "[connectionName]"
env_variables:
# framework
YII_DEBUG: false
YII_ENV: prod
#db
POSTGRES_DSN: pgsql:dbname=alpha;host=/cloudsql/[connectionName]
POSTGRES_USER: [user]
POSTGRES_PASSWORD: [your-pass]
POSTGRES_PORT: 5432
# This sample incurs costs to run on the App Engine flexible environment.
# The settings below are to reduce costs during testing and are not appropriate
# for production use. For more information, see:
# https://cloud.google.com/appengine/docs/flexible/python/configuring-your-app-with-app-yaml
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
handlers:
- url: /.*
script: index.php
NOTE: I don't think that is the proper way to create microservices. You may better have an app.yaml
file with common scaling parameters and a dispatch.yaml
file linking the other services. Read more about it here:
environments/prod/common/main-local.php
to be used after executing the init
bat:'db' => [
'class' => 'yii\db\Connection',
'dsn' => getenv('POSTGRES_DSN'),
'username' => getenv('POSTGRES_USER'),
'password' => getenv('POSTGRES_PASSWORD'),
'charset' => 'utf8',
'enableSchemaCache' => true,
'schemaCacheDuration' => 3600,
'schemaCache' => 'cache',
],
You can also change the entry script to this in case you think you may need to enable debug mode in their server:
defined('YII_DEBUG') or define('YII_DEBUG', getenv('YII_DEBUG') === 'true');
defined('YII_ENV') or define('YII_ENV', getenv('YII_ENV') ?: 'prod');
I created an nginx-app.conf
file (same level with yaml files: app root) with the following content:
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
Which I guess should be auto merged with their nginx configs that you can see here: https://github.com/GoogleCloudPlatform/php-docker/blob/4454562b9be8134d630feded98a5bdb206e686ab/php-base/nginx.conf#L106
See a list of enabled/disabled php extensions here: https://cloud.google.com/appengine/docs/flexible/php/runtime. The ones you need to activate add them to the require
section of your package.json
file:
"require": {
"php": "7.2.*",
"ext-gd": "*",
"ext-redis": "*",
"ext-intl": "*",
...
},
NOTE: That documentation says you can enable them by adding a php.ini
file. Don't do that. It doesn't work with Flex environment. (see https://github.com/GoogleCloudPlatform/php-docs-samples/issues/446) use the composer file instead.
Use scripts in package.json
similar to how it was done in Laravel and Symfony tutorials here and here to add Yii related scripts under any event you need like flashing db or like in my first deploy which was:
"post-install-cmd": [
"php init --env=Production --overwrite=All",
"php yii migrate/up --interactive=0"
],
Respectively (in my case):
gcloud app deploy api.yaml
gcloud app deploy auth.yaml
There is many options you can also add like manually set version --version=alpha0
or --verbosity=info
... It will take long the first time and the script will give you the final url. You can also SSH to your instance within GAE interface and do stuff like docker exec -it gaeapp /bin/bash
to see your code. I had to go there the first time to manually run the init script as I didn't know about composer post-install-cmd
.
There is also a list of disabled functions here:
exec
passthru
proc_open
proc_close
shell_exec
show_source
symlink
system
These needed could be whitelisted in the Yaml file within a comma separated string. I had to add this to make the yii2-queue extension work by runnig ./yii queue/listen
:
runtime_config:
document_root: api/web
whitelist_functions: proc_open <--
@tunecino I just found your posts here about your experiences with yii2 and Google Cloud Platform... It is really helpful!!
Actually we are running Craft CMS. But since it is based on yii2 I thought you might be able to help.
Are you still running yii2 on GCP Appengine Flex? Do you have any reccomendations about the needed resources (manual_scaling, automatic_scaling, cpu, memory)?
We are currently experiencing issues with the image transform queue. After a deployment we do a php craft clear-caches/all
. After that the queue has to process thousands of jobs. During this time our site is almost unresponsive.
Right now I'm trying https://plugins.craftcms.com/async-queue as a solution.
Did you have similar issues?
Our app.yaml:
runtime: php
env: flex
manual_scaling:
instances: 1
resources:
cpu: 2
memory_gb: 4
disk_size_gb: 10
runtime_config:
operating_system: "ubuntu22"
runtime_version: "8.3"
document_root: web
nginx_conf_include: "nginx-app-PROD.conf"
nginx_conf_http_include: "nginx-http-PROD.conf"
whitelist_functions: proc_open
beta_settings:
cloud_sql_instances: deft-reflection-319018:europe-west3:craft-db-instance
build_env_variables:
NGINX_SERVES_STATIC_FILES: true
We are running Craft 4.
@michaelhunziker I remember that I did opt out from Flex and switched to GCP AppEngine Standard instead because with Flex I was paying for a 24h running servers while the Standard one scales down to 0 instances so it costed nothing when there was no traffic.
For Standard I remember starting with this template:
https://github.com/prawee/yii2-gae-api
Which worked for what I needed (RESTful APIs, 3 of them, each as an independent service/app: api, auth & shell for running migrations and console scripts). 2 things I remember I had to change with that template were:
logs: configured them to use yii\log\SyslogTarget
instead which worked fine with Google Logs for a start, later I built my own LogTarget
class that properly used Google\Cloud\Logging\LoggingClient
for more organized/controlled logs.
cache: I switched to yii\redis\Cache instead, at the time they offered a 30mb free tier in RedisLab (I think it is called redis.io now), when creating the instance, they had the option to get it hosted in the Google Cloud near your code (Goodle had Redis instance, but started with 1gb for $40/month at the time. They had MemCached but only worked with PHP 5 I think)
The main idea is to keep everything standalone ready to scale either up or down. So avoid using any hard disk, use bucket for uploaded stuff & services (either from Google like logs or external when needed).
Don't know much about Craft CMS but it is built on top of Yii, and what is good with Yii is that pretty much every class can be extended to make it do stuff in a different manner.
For queued tasks, I simply used their built-in TaskQueue service, it looked something like this in my API code:
use google\appengine\api\taskqueue\PushTask;
$task = new PushTask("/firebase-sync/$id");
$task_name = $task->add('queue-firebase-sync');
The same project, had a queue.yaml
file with following content:
# Set the total storage limit for all queues to 120MB
total_storage_limit: 120M
queue:
- name: queue-firebase-sync
target: 00.shell
rate: 20/s
bucket_size: 40
max_concurrent_requests: 10
retry_parameters:
task_retry_limit: 5
min_backoff_seconds: 10
And in SHELL, which was a different AppEngine service, I had this controller code to execute coming tasks:
public function actionFirebaseSync($id)
{
$migration = new \app\commands\FirebaseSyncController('firebase-sync', Yii::$app);
$output = $migration->runAction('index', [$id, 'interactive' => false]);
return $this->buffer($output);
}
protected function buffer($output)
{
fclose(\STDOUT);
$buffer = ob_get_clean();
if ($output) {
if (YII_ENV === 'prod') {
$fp = fopen("gs://stdout0/stderr-".uniqid(), 'w');
fwrite($fp, $buffer);
fclose($fp);
}
throw new ServerErrorHttpException($buffer);
}
return $output;
}
Which pretty much, runs Yii console/commands scripts & logs the output.
I am roughly copying random code from an older version of an app I've worked on a long time ago, so please take it with a grant of salt, thinks may have changed overtime. I hope it helps.
I have deployed yii2 based app to GAE. GAE can autoscale the instances horizontally. This presents some challenges:
It would be great to have comments on the above and best practice recommendations.