クリエイター:メタボ兔

ウェブやアプリの開発者で利用する色な技術やサーバーや開発環境の設定について共有する場

8.開発環境の完成

完成しました。こちらで公開しております。

https://bitbucket.org/FattyRabbit/docker-local-server/src/45851c668f59982511278d4cf98a0184eb9d668e/?at=releases%2Fphp-fpm_in_centos

bitbucket.org

当初考えていた内容との差

DNSのコンテイナー

最初書いていた内容からhost情報を設定ファイルにまとめて定義する方法に変更。hosts-dnsmasq.sampleをコピーしてhosts-dnsmasqを作成します。

127.0.0.1  local
127.0.0.1  73.local

プライベートIPアドレスの逆引を上位のDNSへ問い合わせしない。

# Do not use /etc/hosts as nameserver
no-resolv

# Use this file as a hosts file
addn-hosts=/etc/hosts-dnsmasq

# プライベートIPアドレスの逆引を上位のDNSへ問い合わせしない(無駄なので)
bogus-priv

# Log all dns queries
log-queries

ウェブサーバー

DockfileがphpenvでインストールするVersionの変更や実行位置を変更して効率よくしました。

vhosts.conf.sampleをコピーしてvhosts.confを作成します。

Include conf.d/extra/7.3_virtual.conf

phpenvの新しいVersionのインストール用のShellをshellsフォールドを作成して入れました。

まだまだ改善します。

GithubのActionsでLaravelのデプロイ

背景

既にBitbucketのpipelineを使用してLaravelのアプリをデプロイしていましたが、Githubではどのように実装したら良いか試してみました。

前提条件

  • Laravelをローカルでデプロイヤーとしてdeployerを利用している(公開鍵を利用)

要求内容

  • ローカルでのdeployerを利用するデプロイはそのまま使用可能
  • Actionsを利用したデプロイもdeployerを利用
  • Staging環境のデプロイはbranchのpushで自動デプロイ
  • 本番環境のデプロイは手動デプロイ

事前設定

Githubの設定

利用するレポジトリの「Settings > Secrets」に入って、「New secret」クリックし公開鍵を登録します。

f:id:FattyRabbit:20201230124617p:plain

  • Name:SSH_DEPLOY_KEY
  • Value:ローカルのDeployerで使用している鍵(.pubでない方)の中身をコピーして入れる

f:id:FattyRabbit:20201230124708p:plain

サーバー(対象)

CIで実装されるので、前提条件で書いた公開鍵を利用したsshの接続することと同様にDeployerの実行の中でGithubからのソースのcloneする際にもパスワードが入力しなくても可能にする必要が有ります。

サーバーで以下の作業が必要です。

公開鍵の作成

鍵を入れるフォルダに移動しましょう。

$cd ~/.ssh

はじめて鍵を生成するときは何もないはずです。次のコマンドで鍵を生成します。

$ssh-keygen -t rsa

他のオプションを利用しても良いですが、ここでは一番簡単な感じでやります。

$ ssh-keygen -t rsa 
Generating public/private rsa key pair. 
Enter file in which to save the key (/Users/(username)/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:

鍵のファイル名やパスワードを聞かれますが、3回エンターを押して飛ばします。 id_rsaとid_rsa.pubの2つの鍵が生成されます。既にid_rsaが存在する人は上書きされてしまうので注意です。

鍵の登録

Githubに入って上記で作成した鍵を登録します。https://github.com/settings/sshで公開鍵の設定が出来ます。

ローカルのデプロイでも使用していれば既に登録されている物があると思います。画面右上の「Add SSH key」のボタンを押します。

f:id:FattyRabbit:20201230125126p:plain

  • Title:公開鍵名(任意)
  • Key:公開鍵の中身

なお、鍵の中身のクリップボードへのコピーは以下の感じです。鍵の名前は自分の作成したもの

$ pbcopy < ~/.ssh/id_rsa.pub (Mac)
$ clip < ~/.ssh/id_rsa.pub (Windows)

接続のテスト

サーバーで以下を実行してみます。

$ ssh -T git@github.com
Hi (account名)! You've successfully authenticated, but GitHub does not provide shell access.

で返ってきたら接続が可能とことです。

しかし、鍵を作るときに名前を指定していれば、うまくいかないかもしれません。それは、ssh接続の際「~/.ssh/id_rsa」、「~/.ssh/id_dsa」、「~/.ssh/identity」しかデフォルトでは見にいかないからです。 それに対応するためには~/.ssh/configを作成し、以下の内容を追記します。

Host github github.com
  HostName github.com
  IdentityFile ~/.ssh/id_rsa #ここに自分の鍵のファイル名
  User git

テスト

$ ssh -T github

Actionsのworkflowを作成

対象リポジトリの「Actions」に入って新しくTempleteを利用して作成しても良いです。

トリガー

Staging

Stagingは要求内容にも書いてますが、特にエンジニアーが管理する必要がないかと思いましてgitにPushするタイミングでデプロイが動くと良いかと思いました。

name: deploy dev

on:
  push:
    branches:
      - deploy/dev

production

本番の場合はGitのPushミス等の可能性でエンジニアーが手動でする必要があるかと思いましたので、以下のトリガーにしました。

name: deploy pro

on:
  workflow_dispatch:
    branches:
      - deploy/pro

テスト

他の記事や資料、Githubのテンプレートではphpunitを利用する例が多です。

jobs:
  laravel-tests:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"
    - name: Install Dependencies
      run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
    - name: Generate key
      run: php artisan key:generate
    - name: Directory Permissions
      run: chmod -R 777 storage bootstrap/cache
    - name: Create Database
      run: |
        mkdir -p database
        touch database/database.sqlite
    - name: Execute tests (Unit and Feature tests) via PHPUnit
      env:
        DB_CONNECTION: sqlite
        DB_DATABASE: database/database.sqlite
      run: vendor/bin/phpunit

でも今回はウェブの状態を確認するように構成してみました。以下のように書きました。

jobs:
  test:
    name: serve test
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mariadb:10.4.11
        ports:
          - 3306:3306
        options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10
        env:
          MYSQL_ROOT_PASSWORD: pass
          MYSQL_DATABASE: db_sample

    env:
      DB_CONNECTION: mysql
      DB_HOST: 127.0.0.1
      DB_PORT: 3306
      DB_DATABASE: db_sample
      DB_USERNAME: root
      DB_PASSWORD: pass

    steps:
      - uses: actions/checkout@v2
      - name: cache vendor
        id: cache
        uses: actions/cache@v1
        with:
          path: ./vendor
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-
      - name: composer install
        if: steps.cache.outputs.cache-hit != 'true'
        run: composer install -n --prefer-dist
      - name: copy .env
        run: cp .env.ci .env
      - name: generate key
        run: php artisan key:generate
      - name: Directory Permissions
        run: chmod -R 777 storage bootstrap/cache
      - name: migrate
        run: php artisan migrate
      - name: run serve
        run: |
          php artisan serve &
          sleep 5
      - name: test
        run: curl -vk http://localhost:8000

.env.ciファイルは既存のローカル環境で使用している物を再利用しました。

デプロイ

jobかstepに以下のような条件で分けることで共通化も可能でしたが、今回トリガーで分けるのでファイル次第が別にするしかなかったです。

if: github.ref == 'refs/heads/deploy/staging' <- ブランチが「deploy/staging」の場合

以下になりました。

  deploy:
    name: deploy
    runs-on: ubuntu-latest
    needs: test

    steps:
      - uses: actions/checkout@v2
      - name: cache vendor
        id: cache
        uses: actions/cache@v1
        with:
          path: ./vendor
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: |
            ${{ runner.os }}-composer-
      - name: composer install
        if: steps.cache.outputs.cache-hit != 'true'
        run: composer install -n --prefer-dist
      - name: ssh_key copy
        run: |
          mkdir -p /home/runner/.ssh
          touch /home/runner/.ssh/deployerで設定されているファイル名
          echo "${{ secrets.SSH_DEPLOY_KEY }}" > /home/runner/.ssh/deployerで設定されているファイル名
          chmod 600 /home/runner/.ssh/deployerで設定されているファイル名
      - name: add known hosts
        run: ssh-keyscan サーバーIP >> ~/.ssh/known_hosts
      - name: install deploy
        run: |
          curl -LO https://deployer.org/deployer.phar
          mv deployer.phar ./deployer/dep
          chmod +x ./deployer/dep
      - name: deploy dev
        run: cd ./deployer && ./dep deploy dev -vvv
  • .deployer:deployerの設定が入っているフォルダーで、laravelプロジェクト/deployerの感じで設定されている状況です。
  • dep deploy dev:deployerで設定されているstage名「->stage('dev')」です。

課題

本番のデプロイをトリガーで手動でしているのでファイルが別々になって共通的な部分が再利用されない状況です。手動でデプロイする他の方があるかとjob等を他のymlファイルをインクルード等出来れば良いかと思いました。

Macでphpenvを利用して7.4インストール

Macでインストールでエラー

krb5のインストールが必要な時

No package 'krb5-gssapi' found
No package 'krb5' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables KERBEROS_CFLAGS
and KERBEROS_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

opensslのインストール(Update)が必要な時

configure: error: Package requirements (openssl >= 1.0.1) were not met:

No package 'openssl' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables OPENSSL_CFLAGS
and OPENSSL_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

icu4cのインストールが必要な時

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Package requirements (icu-uc >= 50.1 icu-io icu-i18n) were not met:

No package 'icu-uc' found
No package 'icu-io' found
No package 'icu-i18n' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables ICU_CFLAGS
and ICU_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

libeditのインストールが必要な時

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: WARNING: libedit directory ignored, rely on pkg-config
configure: error: Package requirements (libedit) were not met:

No package 'libedit' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables EDIT_CFLAGS
and EDIT_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

libxml2のインストールやパス設定が必要な時

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
warning: unsupported relocation in debug_info section.
note: while processing /private/var/tmp/php-build/source/7.4.0/ext/opcache/.libs/zend_accelerator_util_funcs.o
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ext/opcache/.libs/opcache.a(shared_alloc_shm.o) has no symbols
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ext/opcache/.libs/opcache.a(shared_alloc_shm.o) has no symbols
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: /var/tmp/php-build/source/7.4.0/modules/opcache.a(shared_alloc_shm.o) has no symbols
/var/tmp/php-build/source/7.4.0/ext/libxml/libxml.c:34:10: fatal error: 'libxml/parser.h' file not found
#include <libxml/parser.h>
         ^~~~~~~~~~~~~~~~~
1 error generated.
make: *** [ext/libxml/libxml.lo] Error 1

bzip2のインストールやパス設定が必要な時

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Please reinstall the BZip2 distribution

libiconvのインストールやパス設定な時

configure: WARNING: unrecognized options: --with-png-dir, --with-libxml-dir, --with-icu-dir
configure: error: Please specify the install prefix of iconv with --with-iconv=<DIR>

解決

% brew install autoconf bzip2 icu4c krb5 libedit libiconv libjpeg libpng libxml2 libzip oniguruma openssl@1.1 pkg-config tidy-html5
% PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig:/usr/local/opt/icu4c/lib/pkgconfig:/usr/local/opt/libedit/lib/pkgconfig:/usr/local/opt/libjpeg/lib/pkgconfig:/usr/local/opt/libpng/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig:/usr/local/opt/libzip/lib/pkgconfig:/usr/local/opt/oniguruma/lib/pkgconfig:/usr/local/opt/openssl@1.1/lib/pkgconfig:/usr/local/opt/tidy-html5/lib/pkgconfig" \
PHP_BUILD_CONFIGURE_OPTS="--with-bz2=/usr/local/opt/bzip2 --with-iconv=/usr/local/opt/libiconv" \
phpenv install 7.4snapshot

PHPのDTOの作成:カプセル化

背景

Javaのエンジニアの経験から見るとPhpのクラスの甘さが良いとも悪いとも言えます。その中でDTOで使用するクラスをもうちょっとある属性&関数のみ使用するようにしてカプセル化をしたいところです。

定義してない属性の設定や参照の禁止

    public function __set($key, $value){
        if (property_exists($this, $key)) {
            $this->{$key} = $value;
        }
        throw new \BadMethodCallException('Method "'.$key.'" does not exist.');
    }

    public function __get($key)
    {
        if (property_exists($this, $key)) {
            return $this->{$key};
        }
        throw new \BadMethodCallException('Method "'.$key.'" does not exist.');
    }

初期ソースは簡単に考えましたが、ちょっと待って!! 「property_exists」ってpublicだけじゃない〜〜〜

それでこんな感じにしました。

    public function __set($key, $value){
        if ($this->existsProperty($key)) {
            $this->{$key} = $value;
        }
        throw new \BadMethodCallException('Method "'.$key.'" does not exist.');
    }

    public function __get($key)
    {
        if ($this->existsProperty($key)) {
            return $this->{$key};
        }
        throw new \BadMethodCallException('Method "'.$key.'" does not exist.');
    }

    private function existsProperty($propertyName) {
        $reflection = new ReflectionObject($this);
        $properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
        foreach ($properties as $property) {
            if ($property->getName() === $propertyName) return true;
        }
        return false;
    }

Setter/Getterの自動化

Javaの実装でgetter/setterの作成の面倒です。Lombokのような物もありますね。それで以下のソースを用意しました。

    public function __call($name, $args) {
        if (strncmp($name, 'get', 3) === 0) {
            $pascalCName = substr($name, 3);
            $snakeCName = $this->pascalCase2SnakeCase($pascalCName);

            if (property_exists($this, $snakeCName)) {
                return $this->{$snakeCName};
            }
        } else if (strncmp($name, 'set', 3) === 0) {
            $pascalCName = substr($name, 3);
            $snakeCName = $this->pascalCase2SnakeCase($pascalCName);

            if (property_exists($this, $snakeCName)) {
                return $this->{$snakeCName} = reset($args);
            }
        }
        throw new \BadMethodCallException('Method "'.$name.'" does not exist.');
    }

    private function pascalCase2SnakeCase($input) {
        preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
        $ret = $matches[0];
        foreach ($ret as &$match) {
            $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
        }
        return implode('_', $ret);
    }

このソースではpublic以外のも設定可能にしないといけないので「property_exists」を利用しました。

完成

abstract class Dto
{
    public function __call($name, $args) {
        if (strncmp($name, 'get', 3) === 0) {
            $pascalCName = substr($name, 3);
            $snakeCName = $this->pascalCase2SnakeCase($pascalCName);

            if (property_exists($this, $snakeCName)) {
                return $this->{$snakeCName};
            }
        } else if (strncmp($name, 'set', 3) === 0) {
            $pascalCName = substr($name, 3);
            $snakeCName = $this->pascalCase2SnakeCase($pascalCName);

            if (property_exists($this, $snakeCName)) {
                return $this->{$snakeCName} = reset($args);
            }
        }
        throw new \BadMethodCallException('Method "'.$name.'" does not exist.');
    }

    public function __set($key, $value){
        if ($this->existsProperty($key)) {
            $this->{$key} = $value;
        }
        throw new \BadMethodCallException('Method "'.$key.'" does not exist.');
    }

    public function __get($key)
    {
        if ($this->existsProperty($key)) {
            return $this->{$key};
        }
        throw new \BadMethodCallException('Method "'.$key.'" does not exist.');
    }

    private function pascalCase2SnakeCase($input) {
        preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
        $ret = $matches[0];
        foreach ($ret as &$match) {
            $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
        }
        return implode('_', $ret);
    }

    private function existsProperty($propertyName) {
        $reflection = new ReflectionObject($this);
        $properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
        foreach ($properties as $property) {
            if ($property->getName() === $propertyName) r[f:id:FattyRabbit:20201230123544j:plain]eturn true;
        }
        return false;
    }
}

クラス名のセンスはないので。。。

phpenvでインストール:configure: error: Cannot find libz

f:id:FattyRabbit:20201229193310p:plain

インストール環境

OS

macOS Catalina
version 10.15.3

Error

$ phpenv install 7.1.19
[Info]: Loaded extension plugin
[Info]: Loaded apc Plugin. [Info]: Loaded composer Plugin.
[Info]: Loaded github Plugin. [Info]: Loaded uprofiler Plugin.
[Info]: Loaded xdebug Plugin. [Info]: Loaded xhprof Plugin.
[Info]: Loaded zendopcache Plugin.
[Info]: php.ini-production gets used as php.ini
[Info]: Building 7.1.19 into /Users/******/.anyenv/envs/phpenv/versions/7.1.19
[Skipping]: Already downloaded and extracted https://secure.php.net/distributions/php-7.1.19.tar.bz2 [Preparing]: /var/tmp/php-build/source/7.1.19
-----------------
| BUILD ERROR |
-----------------
Here are the last 10 lines from the log:
-----------------------------------------
configure: error: Cannot find libz
-----------------------------------------
The full Log is available at '/tmp/php-build.7.1.19.20200227165241.log'. [Warn]: Aborting build.

解決

$ brew install autoconf bison bzip2 curl icu4c libedit libjpeg libiconv libpng libxml2 libzip openssl re2c tidy-html5 zlib
$ CONFIGURE_OPTS="--with-zlib-dir=$(brew --prefix zlib) --with-bz2=$(brew --prefix bzip2) --with-curl=$(brew --prefix curl) --with-iconv=$(brew --prefix libiconv) --with-libedit=$(brew --prefix libedit) --with-readline=$(brew --prefix readline) --with-tidy=$(brew --prefix tidy-html5)" phpenv install [version]

環境変数等をまとめて「phpenv install [version]」だけ出来る方法がまだ見つからない状態です。