[Laravel7.x] マルチテナントアーキテクチャで構築する2

8系がうまくいかないので、7系でやります。

$ composer create-project –prefer-dist laravel/laravel tenancy “7.*”
$ cd tenancy
$ php artisan -V
Laravel Framework 7.30.4

config/database.phpと.envを編集
$ composer require “hyn/multi-tenant:5.6.*”
$ php artisan vendor:publish –tag=tenancy
$ php artisan migrate –database=system
mysql> use tenancy
mysql> show tables;
mysql> describe users;
mysql> describe hostnames;
+————————-+—————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+————————-+—————–+——+—–+———+—————-+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| fqdn | varchar(255) | NO | UNI | NULL | |
| redirect_to | varchar(255) | YES | | NULL | |
| force_https | tinyint(1) | NO | | 0 | |
| under_maintenance_since | timestamp | YES | | NULL | |
| website_id | bigint unsigned | YES | MUL | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
| deleted_at | timestamp | YES | | NULL | |
+————————-+—————–+——+—–+———+—————-+

### テナントの作成
テナントを作成すると同時にデータベースにテーブルが作成されるが、テーブルの作成には前準備が必要となる。
テナント用のテーブルを作成するmigration fileはmigrations/tenant以下に作成する
https://tenancy.dev/docs/hyn/5.6/migrations

config/tenancy.php

'tenant-migrations-path' => database_path('migrations/tenant'),

'uuid-limit-length-to-32' => env('LIMIT_UUID_LENGTH_32', true),

$ cd database/migrations
$ mkdir tenant
$ ls
2014_10_12_000000_create_users_table.php
2014_10_12_100000_create_password_resets_table.php
2017_01_01_000003_tenancy_websites.php
2017_01_01_000005_tenancy_hostnames.php
2018_04_06_000001_tenancy_websites_needs_db_host.php
2019_08_19_000000_create_failed_jobs_table.php
tenant
// usersとpassword_resetsもtenantの中に入れる
$ cp 2014_10_12* tenant

### テナント作成
https://tenancy.dev/docs/hyn/5.6/creating-tenants
routes/web.php

use Hyn\Tenancy\Models\Hostname;
use Hyn\Tenancy\Contracts\Repositories\HostnameRepository;
use Hyn\Tenancy\Models\Website;
use Hyn\Tenancy\Contracts\Repositories\WebsiteRepository;


Route::get('create_tenant', function () {

    $website = new Website;
    app(WebsiteRepository::class)->create($website);

    $hostname = new Hostname;
    
    $hostname->fqdn = 'test.localhost';
    $hostname = app(HostnameRepository::class)->create($hostname);

    app(HostnameRepository::class)->attach($hostname, $website);
    
	return redirect('/');

});

$ php artisan serve –host 192.168.33.10 –port 8000
http://192.168.33.10:8000/create_tenant

mysql> select * from hostnames;
+—-+—————-+————-+————-+————————-+————+———————+———————+————+
| id | fqdn | redirect_to | force_https | under_maintenance_since | website_id | created_at | updated_at | deleted_at |
+—-+—————-+————-+————-+————————-+————+———————+———————+————+
| 1 | test.localhost | NULL | 0 | NULL | 1 | 2021-02-13 09:07:52 | 2021-02-13 09:07:52 | NULL |
+—-+—————-+————-+————-+————————-+————+———————+———————+————+
1 row in set (0.00 sec)

mysql> select * from websites;
+—-+———————————-+———————+———————+————+——————————–+
| id | uuid | created_at | updated_at | deleted_at | managed_by_database_connection |
+—-+———————————-+———————+———————+————+——————————–+
| 1 | 214594e5f86a418bbd990b6583d37131 | 2021-02-13 09:07:52 | 2021-02-13 09:07:52 | NULL | NULL |
+—-+———————————-+———————+———————+————+——————————–+
1 row in set (0.00 sec)
mysql> show databases;

OK, なんとなくマルチテナントの仕組みはわかったかも。
vagrantで開発している場合、mac側で名前解決せなあかんね。

[Laravel 8.27.0] マルチテナントアーキテクチャで構築する1

マルチテナントアーキテクチャで開発したい

$ php -v
PHP 7.4.11 (cli) (built: Oct 21 2020 19:12:26) ( NTS )
$ composer create-project laravel/laravel multi –prefer-dist
$ cd multi
$ php artisan -V
Laravel Framework 8.27.0
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire

multi tenant laravel
tenacy
https://tenancy.dev/
 L 簡単にマルチテナントを構築できるhyn/multi-tenant

### 概要
– テナントデータベースはテナント1つにつき1つとする
– テナントを作ると、テナントデータベース、専用ユーザが作られる。テナントを削除すると削除される
– システムのmigrationとテナントmigrationは分ける

### Tenancy Install
tenancy
mysql> CREATE DATABASE IF NOT EXISTS tenancy;
mysql> CREATE USER IF NOT EXISTS tenancy@localhost IDENTIFIED BY ‘hogehoge’;
mysql> GRANT ALL PRIVILEGES ON *.* TO tenancy@localhost WITH GRANT OPTION;
mysql> show databases;

config/database.php

    'connections' => [
        'system' => [
            'driver' => 'mysql',
            'host' => env('TENANCY_HOST', '127.0.0.1'),
            'port' => env('TENANCY_PORT', '3306'),
            'database' => env('TENANCY_DATABASE', 'tenancy'),
            'username' => env('TENANCY_USERNAME', 'tenancy'),
            'password' => env('TENANCY_PASSWORD', 'fugafuga'),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],

.envからDBに関する情報を削除する

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

config/database.php
L mysqlからsystemに変更する

    'default' => env('DB_CONNECTION', 'system'),

$ composer require “hyn/multi-tenant:5.6.*”
./composer.json has been updated
Running composer update hyn/multi-tenant
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

Problem 1
– hyn/multi-tenant[5.6.0, …, 5.6.1] require ramsey/uuid ^3.5 -> found ramsey/uuid[3.5.0, …, 3.x-dev] but the package is fixed to 4.1.1 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
– hyn/multi-tenant[5.6.2, …, 5.6.4] require laravel/framework ^7.0 -> found laravel/framework[v7.0.0, …, 7.x-dev] but it conflicts with your root composer.json require (^8.12).
– Root composer.json requires hyn/multi-tenant 5.6.* -> satisfiable by hyn/multi-tenant[5.6.0, …, 5.6.4].

Use the option –with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.

なにこれ? Laravel8系に対応してないってこと??

[PHP7.4.11] 再度composerを理解する

$ mkdir my-package
$ cd my-package
$ composer init
This command will guide you through creating your composer.json config.

Package name (/) [vagrant/my-package]: hpscript/my-package
Description []:
Author [hpscript <>, n to skip]:
Minimum Stability []: stable
Package Type (e.g. library, project, metapackage, composer-plugin) []: library
License []: MIT

composer.json

{
    "name": "hpscript/my-package",
    "type": "library",
    "license": "MIT",
    "minimum-stability": "stable",
    "require": {}
}

$ composer require vlucas/phpdotenv
$ composer require monolog/monolog
$ composer require phpunit/phpunit –dev
$ composer require psy/psysh:@stable –dev
-> dotenvとは、getenv()メソッドや、$_ENV変数、$_SERVER変数を使って、.envというファイルから環境変数を読み込む
-> REPLは対話型評価環境

### ディレクトリの作成
$ mkdir -p src/MyPackage/Exception
$ mkdir logs
$ mkdir tests
$ mkdir docs
$ mkdir example
$ mkdir bin

PSR-4: PHPでのファイルの読み込み・クラスのオートロードを行う仕様
composer.json

    "autoload": {
        "psr-4": {
            "hpscript\\MyPackage\\": "src/MyPackage/"
        }
    }

$ composer dump-autoload
Generating autoload files
Generated autoload files

.env

TEST_NAME=hpscript

.gitignore

/vendor
.env

logs/.gitignore

*
!.gitignore

psysh.php

#!/usr/bin/env php
<?php
// 名前空間
namespace hpscript\MyPackage;

require_once __DIR__ . '/vendor/autoload.php';

$dotenv = new \Dotenv\Dotenv(__DIR__.'/');
$dotenv->load();

echo __NAMESPACE__ . " shell\n";

$sh = new \Psy\Shell();

$sh->addCode(sprintf("namespace %s;", __NAMESPACE__));

$sh->run();

echo "Bye.\n";

$ chmod +x psysh.php

phpunit.xml

<?xml version="1.0" encoding="utf-8" ?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
	<testsuites>
		<testsuite name="All tests">
			<directory>tests/</directory>
		</testsuite>
	</testsuites>
</phpunit>

monolog

namespace hpscript\MyPackage;

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;

class LogSample{

	public static function write($message){
		$log = new Logger("my-package");
		$log_path = __DIR__. '/../../logs/test.log';
		$handler = new StreamHandler($log_path,Logger::DEBUG);
		$log->pushHandler($handler);
		$rotating_handler = new RotatingFileHandler($log_path, 30, Logger::DEBUG, true);
		$log->pushHandler($rotating_handler);

		return $log->addDebug($message);
	}
}

### phpunit
src/MyPackage/Arithmetic.php

namespace hpscript\MyPackage;

class Arithmetic {
	public function add($x, $y){
		return($x + $y);
	}

	public function subtract($x, $y){
		return($x - $y);
	}

	public function multiply($x, $y){
		return($x * $y);
	}

	public function divide($x, $y){
		return($x / $y);
	}
}

tests/ArithmeticTest.php

use PHPUnit\Framework\TestCase;
use hpscript\MyPackage\Arithmetic;

class ArithmeticTest extends TestCase {

	protected $object;

	protected function setUp(): void{
		$this->object = new Arithmetic();
	}

	public function add5(){
		$this->assertEquals(8, $this->object->add(3, 5));
	}

	public function add30(){
		$this->assertEquals(45, $this->object->add(15, 30));
	}

	public function subtract3(){
		$this->assertEquals(7, $this->object->subtract(10, 3));
	}

	public function subtract9(){
		$this->assertEquals(-6, $this->object->subtract(3, 9));
	}

	public function multiply6(){
		$this->assertEquals(24, $this->object->multiply(4, 6));
	}

	public function multiply5(){
		$this->assertEquals(-20, $this->object->multiply(4, -5));
	}

	public function testDivide(){
		$this->assertEquals(3, $this->object->divide(6, 2));
		$this->assertEquals(1, $this->object->divide(6, 6));

$ ./vendor/bin/phpunit
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:00.002, Memory: 6.00 MB

OK (1 test, 2 assertions)

$ php psysh.php
hpscript\MyPackage shell
Psy Shell v0.10.6 (PHP 7.4.11 — cli) by Justin Hileman
>>> $ar = new Arithmetic()
=> hpscript\MyPackage\Arithmetic {#2562}
>>> show $ar
5: class Arithmetic {
6: public function add($x, $y){
7: return($x + $y);
8: }
9:
10: public function subtract($x, $y){
11: return($x – $y);
12: }
13:
14: public function multiply($x, $y){
15: return($x * $y);
16: }
17:
18: public function divide($x, $y){
19: return($x / $y);
20: }
21: }

>>> ls $ar
Class Methods: add, divide, multiply, subtract
>>> q
Exit: Goodbye
Bye.

おお、なんか完璧には理解してないが、大まかな流れはわかった。

[Laravel 8.x] Composerのパッケージ作成手順しようと思ったが…

composer.json

{
    "name": "laravel/laravel",
    "type": "project",
    "description": "The Laravel Framework.",
    "keywords": [
        "framework",
        "laravel"
    ],
// 省略

composer.jsonから、name -> “laravel/laravel” となっていることがわかる。

composer.json

"autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/",
            "Laravel\\Laravel\\" : "app/Providers/" // 追加
        }
    },


    "extra": {
        "laravel": {
            "dont-discover": [],
            "providers": [
                "Laravel\\Laravel\\HogeServiceProvider"
            ]
        }
    },
[/code]

$ php artisan make:provider HogeServiceProvider
app/Providers/HogeServiceProvider.php

namespace Laravel\Laravel; // 修正
use Illuminate\Support\ServiceProvider;

class HogeServiceProvider extends ServiceProvider
{
    public function register()
    {}
    public function boot()
    {
        //
        dump("What's up Laravel!");
    }
}

composer.json

"repositories": [
        {
            "type": "path",
            "url" : "packages/username/hoge",
            "symlink": true
        }
    ],
    "require": {
        // 省略
        "hpscript/hoge": "dev-master"
    },

$ composer update

[RuntimeException]
The `url` supplied for the path (packages/username/hoge) repository does not exist

うーん、なんかちゃうな。。
packageの作り方を良く理解してないようだ。

[PHP7.4.11] Composerの仕組み

ComposerとはPHPのパッケージ管理システム。
ライブラリをzip等でダウンロードせずに利用できる。

$ php -v
PHP 7.4.11 (cli) (built: Oct 21 2020 19:12:26) ( NTS )
$ composer
______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 2.0.4 2020-10-30 22:39:11

composer.json

{
	"require": {
		"ircmaxell/random-lib": "1.0.0"
	}
}

$ composer install

require_once "vendor/autoload.php";

$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();

$randomInt = $generator->generateInt(5, 15);
echo $randomInt;

echo "\n";

$randomString = $generator->generateString(32, 'abcdef');
echo $randomString;

$ php app.php
12
cffccdccbcaadcffbbddbffbdadbfefd

requrire_onceした後にclassを作って読み込めば良いのね

/vendor/ircmaxell/random-lib/lib/RandomLib/Factory.php


public function getGenerator(\SecurityLib\Strength $strength) {
        $sources    = $this->getSources();
        $newSources = array();
        foreach ($sources as $source) {
            if ($strength->compare($source::getStrength()) <= 0) {
                $newSources[] = new $source;
            }
        }
        $mixer = $this->findMixer($strength);
        return new Generator($newSources, $mixer);
    }

    // 省略

    public function getMediumStrengthGenerator() {
        return $this->getGenerator(new Strength(Strength::MEDIUM));
    }

generateIntのclassも同様に、RandomLib/Generator.phpで書かれていますね。
なるほど。

[Apache] Logをローテーションさせたい

Apacheのログが増えると容量がどんどん増えていくため、ログをローテーションさせたい
-> rotatelogでローテーションさせる事ができる

$ cat /etc/logrotate.d/httpd

/var/log/httpd/*log {
    missingok
    notifempty
    sharedscripts
    delaycompress
    postrotate
        /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
    endscript
}

$ sudo su
$ cd /var/log/httpd
$ cat error_log-20210207

$ cat /etc/httpd/conf/httpd.conf
ErrorLog “logs/error_log”
-> ErrorLog “|/usr/sbin/rotatelogs /var/log/httpd/error_log.%Y-%m-%d 86400 540”
CustomLog “logs/access_log” combined
-> CustomLog “|/usr/sbin/rotatelogs /var/log/httpd/access_log.%Y%m%d 86400 540” combined

shellで例えば30日前などのaccess log, errorログを削除していく。
アプリログの場合でも、同様に指定したディレクトリのログをシェルで削除して行けば良い。

なるほど、注意しなければいけないポイントがわかった。

[AWS EBS] EC2でEBSの使用率が100%になった場合

EC2でEBSの使用率が100%になった場合、
1. AWSコンソールのEBSVolumes -> Action-> modify volume
2. EC2サーバにSSHログインし、”sudo xfs_growfs -d /”でボリューム拡張を反映
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/recognize-expanded-volume-linux.html

### EBSが100%になった時
$ df -h
ファイルシス サイズ 使用 残り 使用% マウント位置
devtmpfs tmpfs tmpfs tmpfs /dev/xvda1 tmpfs
482M 0 482M 0% /dev
492M 0 492M 0% /dev/shm 492M 25M 467M 6% /run
492M 0 492M 0% /sys/fs/cgroup
8.0G 8.0G 12M 100% /
99M 0 99M 0% /run/user/1000

-> サーバーにアクセスできなくなる。
-> MySQLが落ちる

サーバーのエラーログ
—–
(28)No space left on device: [client 61.206.21.97:9568] AH00646: Error writing to logs/access_log, referer:

## 対応法
sshログイン
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 12G 0 disk
└─xvda1 202:1 0 12G 0 part /
// 8Gから12Gに増やしたことが確認できるが、この時点ではまだ反映されていない
$ df -hT
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
devtmpfs devtmpfs 482M 0 482M 0% /dev
tmpfs tmpfs 492M 0 492M 0% /dev/shm
tmpfs tmpfs 492M 25M 467M 6% /run
tmpfs tmpfs 492M 0 492M 0% /sys/fs/cgroup
/dev/xvda1 xfs 8.0G 8.0G 12M 100% /
tmpfs tmpfs 99M 0 99M 0% /run/user/1000
$ sudo xfs_growfs -d /
meta-data=/dev/xvda1 isize=512 agcount=4, agsize=524159 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=1 spinodes=0
data = bsize=4096 blocks=2096635, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal bsize=4096 blocks=2560, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
data blocks changed from 2096635 to 3145211
$ df -h
ファイルシス サイズ 使用 残り 使用% マウント位置
devtmpfs 482M 0 482M 0% /dev
tmpfs 492M 0 492M 0% /dev/shm
tmpfs 492M 25M 467M 6% /run
tmpfs 492M 0 492M 0% /sys/fs/cgroup
/dev/xvda1 12G 8.0G 4.1G 67% /
tmpfs 99M 0 99M 0% /run/user/1000

### MySQL再起動
$ sudo systemctl restart mysqld.service
$ sudo systemctl start mysqld.service
$ sudo systemctl enable mysqld.service
$ systemctl status mysqld.service

Mackerelでファイル使用率が90%以上になってたのに、見逃してた。。
やっちまったわ。

[React] WebpackでReactの環境を構築する

$ node -v
v14.15.0
$ npm -v
6.14.8
$ cd sample

### npm
$ npm -y init
$ npm -D install webpack webpack-cli
$ npx webpack -v
webpack 5.21.2
webpack-cli 4.5.0
$ npm -D i sass-loader node-sass style-loader css-loader
$ npm -D i mini-css-extract-plugin optimize-css-assets-webpack-plugin
$ npm i -D webpack-dev-server
$ npm install –save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
$ npm install –save-dev react react-dom

package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "dev": "webpack --mode development",
    "build": "webpack --mode production",
    "watch": "webpack --mode development --watch --color --progress"
  },

ディレクトリ構成

webpack.config.js

var webpack = require('webpack');
var path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (env, argv)=> {

	const IS_DEVELOPMENT = argv.mode === 'development';

	return {
		mode: argv.mode,

		entry: {
			'index': './js/index.js',
		},

		devltool: IS_DEVELOPMENT ? 'source-map': 'none',

		output: {
			filename: '[name].js',
			path: path.resolve(__dirname,'../dist'),
		},

		plugins: [
			new MiniCssExtractPlugin({
				filename: '[name].css',
				path: path.resolve(__dirname,'../dist'),
			})
		],

		resolve: {
			extensions: [".ts"]
		},

		module: {
			rules: [
				{
					test:/\.ts$/,
					use: "ts-loader"
				},
				{
					test:/\.js$/,
					use: [
						{
							loader:"babel-loader",
							options: {
								presets: [
									"@babel/preset-env"
								]
							}
						}
						
					]
				},
				{
					test:/index\.scss$/,
					use: [
						MiniCssExtractPlugin.loader,
						{
							loader:'css-loader',
							options: {
								url: false,
							}
						},
						{
							loader: "postcss-loader",
							options: [
								require("autoprefixer")({
									grid: true,
								})
							]
						}
					]
				}
			]
		},

	}
};

$ webpack –mode development
[webpack-cli] ValidationError: Invalid options object. Mini CSS Extract Plugin has been initialized using an options object that does not match the API schema.
– options has an unknown property ‘path’. These properties are valid:
ん?
なんでや。。

var debug = process.env.NODE_ENV !== "production";
var webpack = require('webpack');
var path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
	context:path.join(__dirname, "src"),
	entry: "./js/client.js",
	output: {
		path: __dirname + "/dest/js",
		filename: "client.min.js"
	},
	module: {
		rules: [
			{
				test: /\.css$/i,
	        	use: ['style-loader', 'css-loader'],
			},
			{
			test: /\.jsx?$/,
				exclude: /(node_modules|bower_components)/,
				use: [{
					loader: 'babel-loader',
					options: {
						presets: ['@babel/preset-react', '@babel/preset-env']
					}
				}]
		},
		{
                test: /\.scss$/,
                use: [
                    { loader: MiniCssExtractPlugin.loader },
                    { loader: 'css-loader' },
                    { loader: 'sass-loader' },
                ],
            }
		]
	},
	
	devServer: {
	        contentBase: __dirname + '/src',
	        host: "0.0.0.0"
	},
	plugins: debug ? [] : [
		new webpack.optimize.OccurrenceOrderPlugin(),
		new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: false}),
	]
};

$ webpack-cli serve –mode development
ℹ 「wds」: Project is running at http://0.0.0.0:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /home/vagrant/dev/sample/src
ℹ 「wdm」: asset client.min.js 364 KiB [emitted] (name: main)
runtime modules 432 bytes 3 modules
cacheable modules 335 KiB
modules by path ../node_modules/webpack-dev-server/client/ 20.9 KiB 10 modules
modules by path ../node_modules/html-entities/lib/*.js 61 KiB 5 modules
modules by path ../node_modules/url/ 37.4 KiB 3 modules
modules by path ../node_modules/querystring/*.js 4.51 KiB
../node_modules/querystring/index.js 127 bytes [built]
../node_modules/querystring/decode.js 2.34 KiB [built]
../node_modules/querystring/encode.js 2.04 KiB [built]
modules by path ../node_modules/webpack/hot/*.js 1.42 KiB
../node_modules/webpack/hot/emitter.js 75 bytes [built]
../node_modules/webpack/hot/log.js 1.34 KiB [built]
../node_modules/webpack/hot/ sync nonrecursive ^\.\/log$ 170 bytes [built]
webpack 5.21.2 compiled successfully in 1271 ms
ℹ 「wdm」: Compiled successfully.

ん!
とりあえず作り始めるか。

[React] 基礎

<body>
	<div id="root"></div>
</body>
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
  <h1>Hello world</h1>,
  document.getElementById('root')
);
</script>

jsxはjsの拡張構文でタグを含められる
コンパイルすれば、babelは使用しなくて良い

<body>
	<div id="content">
		
	</div>
</body>
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class ComponentBox extends React.Component{
	render(){
		return (
			<div className="ComponentBox">
			Component Box
			</div>
		);
	}
};
ReactDOM.render(
	<ComponentBox/>,
	document.getElementById('content')
);
</script>
</html>

<script type="text/babel">
class Layout extends React.Component{
	render(){
		return (
			<h1>welcome!</h1>
		);
	}
};
const app = document.getElementById('app');
ReactDOM.render(
	<Layout/>, app
);
</script>

こういうこともできる

class Layout extends React.Component{
	constructor(){
		super();
		this.name = "Hpscript";
	}
	render(){
		return (
			<h1>It's {this.name}</h1>
		);
	}
};

なるほどー

[GCP] GCEを使ってみる

defaultでDebianとなっている。

とりあえず、ubuntu20.04にしておく。
createすると、internal ipとexternal ipが作られる

connect sshを押下すると、ターミナルが自動で起動する。

$ sudo apt-get install nginx
external ipを叩く

なるほど、GCPだろうが何だろうが、OSは同じですね。
vpc, subnet, route tableに変わるようなものはあるのだろうか?
AWSと二刀流が良いですが、慣れるまでは実験的に使っていきたいですな。