- 投稿日:2019-12-14T16:14:57+09:00
新卒1年目がCircleCIに振り回される話
こんにちは!新卒エンジニア1年目の@k_mattunです。
この記事は、All About Group(株式会社オールアバウト) Advent Calendar 2019 15日目の記事となっております。はじめに
つい先日入社したように感じますが、気づいたらクリスマスが迫っているんですね・・・。
社会人の方々が言っていた、あっという間に時間が経っている現象を身を以て体験して焦りを感じています。この記事では、新卒エンジニアとして仕事を始めてハマった事を書いていこうかなーって思います。
※Advent Calendarに書く内容がなかったわけではありませんので、誤解なきようお願いします基本的な開発環境
私が日頃仕事を行う上での基本的な開発環境としては、ざっくり以下の通りです。
使用言語
- PHP5系 or PHP7系
- JavaScript
使用フレームワーク
- Laravel5系
インフラ周り・CIツール
- GCP
- k8s
- Docker
- CircleCI
ハマった事その1
まずこのエラーについて説明する前に、やっていた作業について簡単に説明します。
CIツールの変更について
みなさん改修作業など行なった際に改修内容をリリースしますよね?
そんな時大体の方がCIツールを利用されていると思います。
もちろん、うちの会社でもCIツールは使用していて。私が入社したタイミングではJenkinsおじさんことJenkinsと、Werckerを利用していました。
※入社当初CIツール?Wercker??って感じでした。自分が参考にしたサイトはこちらです。https://deeeet.com/writing/2014/10/16/wercker/しかし、入社後CIツールをJenkins + WerckerからCircleCIに変更する事となりました。
Werckerを撤廃した後、あるアプリの改修を行なったのですが。
そのアプリはCircleCIでデプロイした事がなかったため、改修内容を反映させるためにもCircleCIでリリースを行えるように対応を行う事となりました。
※CircleCIについても無知だったので、自分が参考にした記事を載せておきます(大変助かりました、ありがとうございます)https://qiita.com/gold-kou/items/4c7e62434af455e977c2ハマった事その1については、こちらのCircleCI移行中に起こった出来事になります。
起きたエラー
CircleCIってなんぞやって状況ながらも、社内のCircleCI移行に関するドキュメントであったりネット上の記事であったり。
手当たり次第探しては読み、探しては読みを繰り返しながらなんとかconfig.yamlを記述していきます。ふんふん、大体ステージング用とプロダクション用にjobを作ってそれぞれどんなタイミングで実行させるかworkflowsで指定していくのね・・・って感じで順調に設定ファイルを書き上げていく。
wercker.ymlを参考にしたのもあって意外とすんなり書けたので、よっしゃ案外CircleCI移行も簡単か?って思っていました。そして、いざ!CircleCIでデプロイできるかテストや!!ってやってみると。
php artisan clear-compiled [ErrorException] ~ エラーメッセージ ~出ました出ました、画面に表示される無慈悲な文言。
きっと今の自分ならこのエラーが出ると、あっ多分この辺りで怒られているんだなって察しがつきますが。
※きっとそうであると信じたい
業務を始めて1ヶ月ぐらいだった自分は頭の中が???でいっぱいでした笑エラーの調査
何はともあれ調べないとわからないので、このエラーをコピーして調べまくりました。
どうやらphp artisan clear-compiled
はLaravelのコマンドっぽいぞ?ってことがわかり。
エラーメッセージを読み解くと、なんか使おうとしてるけど該当のモジュール無いよ的な意味でした。しかしWerckerと同じ設定にしているため、Werckerでは動いていてCircleCIで動かないって現象が理解できず。
何故うまく動作しないのか、考えました。composer installするタイミングのそれぞれのイメージの違い
Wercker時代のビルドフローを全て見直してみて気づきました。
Werckerで行なっていた時はcomposer installするタイミングのイメージの状態がCircleCIの時と違ったのです。Wercker時代は、ベースイメージを元にDockerfileでアプリ用のイメージを生成します。Wercker.ymlで扱うイメージはアプリ用に作成した色々モジュールがインストールされた状態のイメージを使用しています。
その為Werckerでcomposer installするタイミングでは該当のモジュールが入っており、正しく動作していたのだと思います。逆にCircleCIの場合該当のモジュールが入っていないイメージのタイミングでcomposer installを行い、その後デプロイするタイミングでDockerのビルドを行ないアプリ用のイメージを作成していました。
その為、Laravelの環境が立ち上がるタイミングで先ほどのエラーが発生していました。config.ymlで該当モジュールをinstallしてみる
composer installするタイミングでモジュールが入ってればいいのなら、config.yamlで入れたらこのエラー解決するんじゃね?って考え、実行してみることに。
command: | ~ 他のいろんな処理 ~ install ~ 該当モジュール ~ ~ 他のいろんな処理 ~これで問題なく次のステップに進める!って思っていましたが、またしても画面に表示される無慈悲な文言。
php artisan clear-compiled [ErrorException] ~ また別のエラーメッセージ ~・・・何やねん!!って心境ですね。
再びエラー調査
今度は
php artisan clear-compiled
について集中的に調べることに。
ここで触れておきますが、私は入社して始めてフレームワークを触りました。
なので当然Laravelについても無知です。一生懸命ネットの海を渡りながら調べた結果、どうやらcomposer.jsonに記述されているscriptの
php artisan clear-compiled
が動作。
それによってvendor配下にあるHogeServiceProvider.php(composerでautoloadしている)のメソッドが動作し、該当のエラーが発生していることがわかった。エラーの原因
エラーの事象として、CircleCIでデプロイの最適化を行うscript(php artisan clear-compiledやphp artisan optimize)が走った際に、該当のエラーが発生することがわかりました。
これは最適化のタイミングでLaravelのServiceProviderが動作し、とあるメソッドが動作していました。
メソッドの中の処理として他のサーバと通信を行う処理が書いてあったのですが、この際に通信できていないのが実際の原因。
というのも、今回このエラーが発生するタイミングは
環境を構築している段階(ステージングのビルド時)でCircleCI上のコンテナで動いています。
コンテナはまだGKEにデプロイされていない状態です。ビルドが実行されているCircleCI上コンテナはIPアドレスもポートも毎回異なります。
セキュリティの観点から接続できるIPアドレスは絞っており、該当のサーバへ通信が通らなかった為上記エラーが発生していました。対処方法
envの環境変数に登録してあるIPに通信を行うので、envが無い場合はその通信が発生しません。
なのでconfig.yamlのコマンドの順番を変更しました。steps: - envを配置する処理 - composer_installする処理この処理の流れから
steps: - composer_installする処理 - envを配置する処理こう変更した。
この事によりcomposer install実行して該当のscriptが走っても、サーバに通信する事もなく、ビルドしてしまった後にenvを配置するのでデプロイしても無事に動作するようになりました。
実はWerckerでのデプロイも同様の処理フローでした。
(あんなに見比べていたのに一体何をみていたんだ??)このおかげで、無事CircleCIでのデプロイができるようになり、本来の改修内容もリリースすることが可能となりました。
ハマった事その2
はい、今回のハマった事ですがまたもやCircleCI移行でハマりました。
CircleCI移行って、言ってしまえばデプロイに関する設定を行うので、ちょっとした間違いで直ぐエラーが起こります。
今回も結論としてはしょうもないところでした。CircleCI移行を済ませ、ステージング環境をデプロイ。
今度は念入りに動作の確認を行って、問題ないことを確認しました。
よっし、これでプロダクションリリースできる!!
そう、こんな時にいつもあの無慈悲な文言が現れるんです・・・バグの内容
内容としてはCircleCIでデプロイして、ステージング環境プロダクション環境共にリリースできたがプロダクション環境だけアクセスするとステータスコードの500が帰って来るといった問題です。
※社内ツールだった為、今回もビジネス影響はなく九死に一生を得ました500が帰ってきた際GCPにあるStackdriverのLoggingで該当アプリのログを確認したのですが、アクセスログしか吐かれておらずエラーログがわからない状態でした。
エラーの調査のためにバグの再現
500で繋がらなくなってからすぐにKubernetesで切り戻しを行なったので調べようにも、バグが発生する環境がない状態でした。なぜかステージングは正常に動作しているため調査には使えず。
そこでCircleCIがイメージのビルド時にGCPのContainer Registryにpushしているのを思い出したので、そのイメージを手元に落としてきてDockerでコンテナを建てて再現することにしました。
簡単に設定などをローカル用に合わせて、無事立ち上げることができ環境も現象も再現することができました。apacheのconfファイルを書き換えてaccess_log、error_log共にStackdriverではなく手元に吐くようにして、確認できなかったエラーログを確認しました。
エラーの原因
再現した環境で取得したaccess_logとerror_logです
※パスは架空ですaccess_log
hoge - - [03/Sep/2019:10:02:53 +0900] "GET / HTTP/1.1" 500 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"error_log
[Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP Fatal error: Cannot declare class Illuminate\\Support\\Facades\\Facade, because the name is already in use in /home/hogehoge/bootstrap/cache/compiled.php on line 6005 [Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP Stack trace: [Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP 1. {main}() /home/hogehoge/public/index.php:0 [Tue Sep 03 10:10:24 2019] [error] [client hoge] PHP 2. require() /home/hogehoge/public/index.php:21アクセスログは現象の500が帰ってきています。
エラーログではIlluminate\Support\Facades\Facadeを宣言しようとしてるけど、すでに/bootstrap/cache/compiled.phpで使われてるよって怒られてます。これが原因で処理が中断されステータス500が返されているようでした。
エラーが発生する原因として、composer installもしくはupdateが実行された際にcomposer.jsonで記述されているscript(php artisa optimize)が実行され、Laravelの最適化が走っていたのがエラーの発生箇所。(またお前たちか)
composer.json
"scripts": { "post-root-package-install": [ "php -r \"copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "php artisan key:generate" ], "post-install-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postInstall", "php artisan optimize" ], "post-update-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postUpdate", "php artisan optimize" ]
php artisan optimizeについて
php artisan optimizeコマンドで、Laravelの最適化を行います。
フレームワークのコードを結合して1つのファイルに纏める
該当のクラスなどを利用する際はこのキャッシュファイルを参照するため読み込み速度が上がってパフォーマンスが向上する
実際に手元で動かしているpodからcompiled.phpを取得し、error_logで指摘されていたコードが記述されていることを確認できました。
対応
結論から言って、compiled.phpを生成しないことにしました。
compiled.phpの必要性について
あくまでLaravelの最適化(処理速度向上)のため必要なファイル
生成するコマンドphp artisan optimize
について、Laravel5.5辺りから非推奨となり、公式からも削除されている(5.4まで存在)
https://github.com/laravel-shift/laravel-5.5/blob/master/composer.json#L40該当のシステムで使用されているphpは7系。
かつ、OPcacheがインストールされいることも確認できパフォーマンスにも影響がないと判断しました。実際に、この対応を行うと無事プロダクションも動作するようになりました。
ステージングとプロダクションの動作が違った件について
APP_DEBUG
STGとPROのenvに記述してあるAPP_DEBUGが原因
今までステージング環境をAPP_DEBUG=trueでデプロイしていたため今回の現象が発生しなかった。※APP_DEBUG=trueの場合、optimizeコマンドを実行してもcompiled.phpは生成されない
教訓
開発中はデバッグモードで作業してもいいと思うが、ステージングやプロダクションではデバッグモードを解除してリリースするべき。今回の件のように今までなんとなく動いていた、なんて事になり兼ねないし本番とテスト環境が異なる状況も避けるべき。
終わりに
本当は他にも色々ハマった事あったんですけど、文量が多くなりそうなので、またいつかの機会にでも・・・。
わかった事としてLaravel全般的な知識、特にLaravelが建ち上がるまでの動きとか知らなすぎ問題。
あとはサーバーサイドの事知らなすぎ問題。
今まで全く触れてこなかったので、やっぱり勉強あるのみだなって感じです。
今回のこれらの失敗は様々な事を知るいい機会でした。少しでも早く一端のエンジニアになれるよう精進あるのみです。
- 投稿日:2019-12-14T15:14:04+09:00
clound9にPHP、Laravelの環境構築をする
はじめに
PHP、Laravelを学習するために環境構築の備忘録として記載
環境
AWS Cloud9
インストール
PHP 7.2
Laravel 5.51.PHPのセットアップ
リポジトリのインストール
$ sudo yum -y install http://rpms.famillecollet.com/enterprise/remi-release-6.rpm <中略> Installed: remi-release.noarch 0:6.10-1.el6.remi Complete!PHP 7.2と必要なパッケージをインストールする
$ sudo yum -y install php72 php72-cli php72-common php72-devel php72-gd php72-intl php72-mbstring php72-mysqlnd php72-pdo php72-pecl-mcrypt php72-opcache php72-pecl-apcu php72-pecl-imagick php72-pecl-memcached php72-php-pecl-redis php72-php-pecl-xdebug php72-xml <中略> policycoreutils-python.x86_64 0:2.1.12-5.25.amzn1 python27-IPy.noarch 0:0.75-1.6.6.amzn1 scl-utils.x86_64 0:20120229-1.el6 selinux-policy.noarch 0:3.10.0-98.26.amzn1 setools-libs.x86_64 0:3.3.7-34.23.amzn1 setools-libs-python.x86_64 0:3.3.7-34.23.amzn1 tcl.x86_64 1:8.5.7-6.9.amzn1 Complete!デフォルトでPHP 7.2を使えるように設定する
cloud9には最初から古いバージョンのPHPがあるため先ほどインストールしたPHP 7.2をデフォルトで使えるようにする
$ sudo alternatives --set php /usr/bin/php-7.2バージョンを確認し、7.2.*がインストールされているのかを確認する
$ php -v PHP 7.2.24 (cli) (built: Oct 31 2019 18:03:13) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.24, Copyright (c) 1999-2018, by Zend Technologies2.composerのインストール
Laravelをインストールするためには、composerというパッケージ管理ツールを利用する必要がある
$ curl -sS https://getcomposer.org/installer | php $ sudo mv composer.phar /usr/local/bin/composercomposerの確認
$ composer about Composer - Dependency Manager for PHP Composer is a dependency manager tracking local dependencies of your projects and libraries. See https://getcomposer.org/ for more information.※Your environment is running out of quota. Please make some free space.という警告
もし上記のような警告がCloud9の右上に出てきたら下記コマンドでメモリを開放すると解決するかもしれない
$ sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"3.Laravelプロジェクトを作成する
home(environment)で
$ composer create-project laravel/laravel ./プロジェクト名(任意) "5.5.*" --prefer-dist※
"5.5.*"
と指定することで5.5の中での最大のバージョンがインストールされる。以上
- 投稿日:2019-12-14T13:05:47+09:00
vuetifyをlaravelに入れる
vuetifyとは
公式サイトでは、全てのユーザーにリッチなWeb開発体験をお届けする、「one of the most popular JavaScript frameworks in the world」であると謳っている。
定期的なupdateやサポートも行っている模様。他のvue.jsフレームワークとの比較の表が下記。なかなか魅力的なライブラリであるように思えます。
まんまと公式の謳い文句にのせられた感じはしますが、それじゃ使ってみようじゃないかと思い、自身の勉強用に作成したwebアプリに導入してみました。(laravel+vue+docker)
導入方法
閑話休題、本題の導入方法ですが、基本公式ページのやり方に沿えば簡単にインストールできますが、備忘録も兼ねて下記に手順を記載します。
(*ちなみに私の環境はlaravel6.6.2です。)1.npmでvuetifyをダウンロード
npm install vuetify npm install sass sass-loader fibers deepmerge -D2.src/resources/js/app.jsに下記を追加
src/resources/js/app.jsimport Vuetify from 'vuetify'; import 'vuetify/dist/vuetify.min.css'; Vue.use(Vuetify); const app = new Vue({ el: '#app', vuetify: new Vuetify() });これだけです。
試しに、Example-component内に、公式にある[cards]コンポーネントをサンプルのまま導入してみます。
src/resources/js/components/ExampleComponent.vue<v-app> <template> <v-card class="mx-auto" max-width="400" > <v-img class="white--text align-end" height="200px" src="https://cdn.vuetifyjs.com/images/cards/docks.jpg" > <v-card-title>Top 10 Australian beaches</v-card-title> </v-img> <v-card-subtitle class="pb-0">Number 10</v-card-subtitle> <v-card-text class="text--primary"> <div>Whitehaven Beach</div> <div>Whitsunday Island, Whitsunday Islands</div> </v-card-text> <v-card-actions> <v-btn color="orange" text > Share </v-btn> <v-btn color="orange" text > Explore </v-btn> </v-card-actions> </v-card> </v-app> </template>
- 投稿日:2019-12-14T12:20:54+09:00
Laravelで二次元配列をキーつきで転置する方法
LaravelにおけるCollectionについて
LaravelにはCollectionという便利なAPIがあって、例えば次のように配列をColllectionに変換すると、メソッドチェーンのようにこの配列を変換することができます。C#におけるLINQみたいで素敵ですね。
$collection = collect(['taylor', 'abigail', null])->map(function ($name) { return strtoupper($name); }) ->reject(function ($name) { return empty($name); });上記の例は、Laravelのリファレンスに紹介された例で、これ$collectionの中身は
['TAYLOR','ABIGAIL']
になります。
コレクション 5.8 Laravel
https://readouble.com/laravel/5.8/ja/collections.html意外とハマりどころになるのが、CollectionはあくまでCollcetionなので配列ではないということです。なので、配列しか受け付けないPHPネイティブのメソッドとか、LaravelのメソッドでもRequestのパラメータみたいな、Collectionが来ることを前提としていないメソッドだと、たまに「あれ??」ってなります。そのときは、
$collection->all()
で普通の配列に戻してあげるといいでしょう。ユースケース
例えば次のようなフォーム画面を作って、データを送信すると……
<form> <div> <label>名前</label><input type="text" name="vocaloids[name][]"> <label>色</label><input type="text" name="vocaloids[color][]"> <label>誕生日</label><input type="text" name="vocaloids[birthday][]"> </div> <div> <label>名前</label><input type="text" name="vocaloids[name][]"> <label>色</label><input type="text" name="vocaloids[color][]"> <label>誕生日</label><input type="text" name="vocaloids[birthday][]"> </div> <div> <label>名前</label><input type="text" name="vocaloids[name][]"> <label>色</label><input type="text" name="vocaloids[color][]"> <label>誕生日</label><input type="text" name="vocaloids[birthday][]"> </div> </form>次のようなパラメータが送信されるでしょう。
{ vocaloids: { name: ['初音ミク', '鏡音リン', '巡音ルカ'], color: ['青','黄','桃'], birthday: ['2007/8/31', '2007/12/27', '2009/1/30'] } }ミクの色って青なんでしょうか緑なんでしょうか。
これはこれで便利です。が、しかし、例えばVocaloidモデルのCollectionをforeachなどでまとめて作ってsaveなどが難しいです。
できればこんな感じのパラメタの配列がほしい。
{ vocaloids: [ { name: '初音ミク', color: '青', birthday: '2007/8/31' }, { name: '鏡音リン', color: '黄', birthday: '2007/12/27' }, { name: '巡音ルカ', color: '桃', birthday: '2009/1/30' } ] }実は、送信するname属性を
vocaloids[0][name]
みたいな感じでインデックスをつけるとこのような形で送信されみたいですが、input要素が動的に増える場合とかですと、フロント側で頑張るほかありません。惜しかった解決策
要は、「配列の連想配列」を「連想配列の配列」に転置したいです。
Collectionには自分でメソッドを定義するmacroという方法があります。
Laravelでtranspose - Qiita
https://qiita.com/kangyoosam/items/f5c4514ad34d9a5c201c上記の記事では、このmacroを定義して配列を見事に転置しています。しかし、できれば……キーの情報も残したい! 「この配列の何番目にはこのキーの情報が入っている」というのは危なっかしいです。配列ではなく連想配列を転置したい!
解決策
macroの定義の仕方は上記のページを見ていただくとして、macroの定義を次のようにしました。
Collection::macro('transpose', function () { $transpose = array_map(null, ...$this->values()); return collect($transpose)->map(function($v, $k){ return array_combine($this->keys()->all(), $v); }); });まず、PHPネイティブのarray_map関数を利用して配列を転置します。第一引数のコールバック関数にnullを指定し、第二引数以降に複数の配列を指定することで、配列の配列を構築します。結果的にこれが配列を転置することになります。この使い方の例はPHPの公式リファレンスでも触れられています。
PHP: array_map - Manual
https://www.php.net/manual/ja/function.array-map.phpこれでできた配列は連想配列の配列ではなく配列の配列です。次にできた配列の配列をCollectionに戻し、mapメソッドを適用します。mapメソッドの中でPHPのネイティブのarray_combine関数を適用します。array_combine関数は第一引数をkey、第二引数をvalueとする連想配列を作る関数です。
PHP: array_combine - Manual
https://www.php.net/manual/ja/function.array-combine.php転置前の配列の連想配列のkeyが、新しく作りたい配列の中に入っているそれぞれの連想配列のkeyになるので、
$this->keys()
でそのkeyを取り出し、それをkey、$transposeの要素をvalueとして、新たに連想配列を構築します。これで望むようなmacroを定義できました。使うときは、
collect($request->input('vocaoids'))->transpose();で簡単。あとは、foreachで回すなり、Modelのcollectionに変換するなり好きにするといいです。
- 投稿日:2019-12-14T11:28:47+09:00
[Laravel] スプレッドシート → マイグレーション
ExcelやGoogle Spreadsheetなんかに記述したテーブル定義からマイグレーションファイルを作成する際に使っている式をシェアします。
必要に応じてカスタマイズして使ってください。こんなレイアウトの表を想定しています。
A B C D E F G H フィールド名(英語) 型 フィールド名(日本語) 備考 桁1 桁2 デフォルト? Null許可? name string 名称 256 o =IF($A5="created_at", "$table->timestamps()", IF($A5="updated_at", "//", IF($A5="deleted_at", "$table->softDeletes()",( IF($A5="id", "$table->bigIncrements('id')", IF($B5="long", "$table->bigInteger('" & $A5 & "')", IF($B5="boolean", "$table->boolean('" & $A5 & "')", IF($B5="datetime", "$table->dateTime('" & $A5 & "')", IF($B5="double", "$table->double('" & $A5 & "', " & $E5 & ", " & $F5 & ")", IF($B5="float", "$table->float('" & $A5 & "', " & $E5 & ", " & $F5 & ")", IF($B5="int", "$table->integer('" & $A5 & "')", IF($B5="string", "$table->string('" & $A5 & "', " & $E5 & ")", IF($B5="text", "$table->text('" & $A5 & "')", IF($B5="timestamp", "$table->timestamp('" & $A5 & "')", "// NO MATCH")))))))))) & IF(ISTEXT($G5), "->default('" & $G5 & "')", IF(ISNUMBER($G5), "->default(" & $G5 & ")", "")) & IF(ISTEXT($H5), "->nullable()", "") & "->comment('" & $C5 & "')" )))) & ";"こんな感じのテキストが生成されるので、これをマイグレーションファイルにぺたり!
$table->string('name', 256)->nullable()->comment('名称');
- 投稿日:2019-12-14T00:00:25+09:00
主キーを使わないテーブルをLaravelのEloquentで対応する方法
概要
主キーを使わないテーブルをLaravelのEloquentで対応した時のメモです。
環境
- Laravel 5.5
対応内容
- Eloquentを使う場合、主キーは
id
になるのでこの設定を無効- 上記に合わせてオートインクリメントの設定も無効
ソース
<?php namespace App\Models use Illuminate\Database\Eloquent\Model; class Sample extends Model { /** * The primary key for the model. * * @var string */ protected $primaryKey = null; // 無効 /** * Indicates if the IDs are auto-incrementing. * * @var bool */ public $incrementing = false; // 無効 }これでEloquentで主キーのないテーブルを扱うことができるはず
参考