RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

Ansible の cron モジュールで環境差分を減らしてみる

ご無沙汰してます

先日、大好きな女性アーティストである aiko の楽曲がサブスクリプションで提供されて狂喜乱舞しております DevOps チームのおくさんです
好きな aiko の曲は「ジェット」の再録音 ver. です
これまでライブには 3 回程しか行けていませんが、一度もライブで「ジェット」を聴いたことはないです・・・

さて、前回記事を書いてから約半年経過し、久々の執筆となります

前回は AWS の CloudWatch に関する記事を書きましたが、今回は Ansible の利用において便利だなと思った「cron モジュール」について書いてみようと思います

rarejob-tech-dept.hatenablog.com

目次

Ansible の cron モジュール

Ansible では、サーバやネットワーク機器等の設定に関する構成管理を行うことができますが、行う操作によってはモジュールという形で提供されています
たとえば、yum でパッケージをインストールするには「yum モジュール」を利用して各種パッケージのインストール等を行います

「cron モジュール」は文字通り cron 設定を管理するためのモジュールで、以下のドキュメントに利用方法が記載されています
こちらのモジュールを利用することにより、cron 設定の管理を Ansible にて行うことが可能になります

docs.ansible.com

やってみよう

cron モジュールのドキュメントに Examples がありますので、これを参考にしつつ動かしてみましょう

今回はサンプルとして、以下のような Playbook を作成してみました
設定内容は、毎日 0:00 にroot ユーザで date コマンドを実行するものとなります

test_cron.yml

---
- hosts: test
  become: yes
  tasks:
    - name: Test
      cron:
        name: 'Test'
        minute: '0'
        hour: '0'
        day: "*"
        month: "*"
        weekday: "*"
        job: 'date'
        state: 'present'
        user: 'root'

なお、今回利用する Ansible のバージョンは以下です

/work/ansible # ansible --version
ansible 2.9.2

では、作成した Playbook を実行してみましょう

/work/ansible # ansible-playbook -i inventory.ini test_cron.yml

PLAY [test] ********************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************
ok: [x.x.x.x]

TASK [Test] ********************************************************************************************************************************************************************************************************
changed: [x.x.x.x]

PLAY RECAP *********************************************************************************************************************************************************************************************************
x.x.x.x              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

サーバの crontab を確認すると、無事に root ユーザで cron の設定が行われたことを確認できます

[root@x.x.x.x ~]# crontab -l -u root
#Ansible: Test
0 0 * * * date

環境にあわせて cron の有効無効を変更する

さて、本題です

Ansible を利用する際には、本番環境や開発環境でなるべく同じ Playbook を利用したい場合があると思います
しかし、「本番環境では cron を動かしたいが、開発環境では cron を動かしたくない」といったように、環境によって行うべき挙動に差が生まれることも多々あると思います

その場合、cron モジュールにおいては、「disabled パラメータ」を利用することにより環境の差分を吸収することが可能です
disabled パラメータはドキュメントに以下のように記載されています

disabled 
boolean

Choices:
no ←
yes

If the job should be disabled (commented out) in the crontab.
Only has effect if state=present.

したがって、本番環境で cron を有効にする場合は「disabled: no」、開発環境で cron を無効にする場合は「disabled: yes」とすることにより、開発環境のみ cron のコメントアウトを行うことができます
これにより、同一の Playbook を利用して環境によって挙動を変更することができます

せっかくなので、上述で利用した Playbook を変更して試してみましょう
本番環境で cron を動かしたいときは以下のような Task になります

- hosts: test
  become: yes
  tasks:
    - name: Test
      cron:
        name: 'Test'
        minute: '0'
        hour: '0'
        day: "*"
        month: "*"
        weekday: "*"
        job: 'date'
        state: 'present'
        user: 'root'
        disabled: 'no'

また、開発環境で cron を動かしたくないときは、以下のような Task になります

- hosts: test
  become: yes
  tasks:
    - name: Test
      cron:
        name: 'Test'
        minute: '0'
        hour: '0'
        day: "*"
        month: "*"
        weekday: "*"
        job: 'date'
        state: 'present'
        user: 'root'
        disabled: 'yes'

違いは disabled パラメータだけであり、このままだと冗長になってしまうので、変数を利用して切り替えましょう
変数を利用することにより、1 つのタスクで動作の切り替えが可能になります

- hosts: test
  become: yes
  tasks:
    - name: Test
      cron:
        name: 'Test'
        minute: '0'
        hour: '0'
        day: "*"
        month: "*"
        weekday: "*"
        job: 'date'
        state: 'present'
        user: 'root'
        disabled: "{{ DISABLED }}"

それでは、変更した Task を利用して Playbook を実行してみましょう

今回はサンプルなので、extra-vars でサクッと変数に値を代入して Playbook を実行します
まずは DISABLED 変数に yes を代入します

/work/ansible # ansible-playbook -i inventory.ini test_cron.yml -e DISABLED=yes

PLAY [test] ********************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************
ok: [x.x.x.x]

TASK [Test] ********************************************************************************************************************************************************************************************************
changed: [x.x.x.x]

PLAY RECAP *********************************************************************************************************************************************************************************************************
x.x.x.x              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

実行した結果、cron をコメントアウトができました

[root@x.x.x.x ~]# crontab -l -u root
#Ansible: Test
#0 0 * * * date
[root@x.x.x.x ~]#

今度は、DISABLED 変数に no を代入します

/work/ansible # ansible-playbook -i inventory.ini test_cron.yml -e DISABLED=no

PLAY [test] ********************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************************************************
ok: [x.x.x.x]

TASK [Test] ********************************************************************************************************************************************************************************************************
changed: [x.x.x.x]

PLAY RECAP *********************************************************************************************************************************************************************************************************
x.x.x.x              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

実行した結果、cron 有効にすることができました

[root@x.x.x.x ~]# crontab -l -u root
#Ansible: Test
0 0 * * * date

まとめ

ジョブについては各種 OSSクラウドベンダーのサービスを利用して管理することも多いと思いますが、まだまだ各サーバ内で cron として実行している環境も多いと思います
Ansible を利用することでも cron の管理は可能ですので、 この記事がどなたかのお役に立ちますと幸いです

また、Ansible で提供されているモジュールは非常に便利なので、各種ドキュメントをしっかり読みながら、今後もいろいろ探して導入検証をしてみようと思います

レイヤードアーキテクチャを導入した開発

バックエンドエンジニアをしております。久住です。
「レアジョブ英会話 スマートメソッド®コース」ではLaravelとVue.jsを使ったSPAの開発をしておりバックエンドではDDDを意識した実装を行なってます。
その中の「レイヤードアーキテクチャ」について今回は書かせていただきます。

なぜレイヤードアーキテクチャを採用しようとしたのか

よくあるMVCの問題としては以下のようなものがあると思っております。

  • Controllerにビジネスロジックを書くなど、FAT Controllerになる
  • ModelにビジネスロジックとDBアクセスが混在して、メンテナンス性が悪くなる
  • 共通のModel間で自由にコールし合い、互いの依存性が高くなる
  • 共通のModelに、特定のControllerからしか使われないような汎用性の低いメソッドができる
  • 共通のModelに、同じような役割のメソッドが複数できる

他のMVCを採用したプロダクトではこれらの問題がたびたびエンジニアのミーティングでも課題として挙げられる事がありました。

  • 修正時に影響範囲がわかりにくい
  • どこに何を実装すべきかチーム間で齟齬が出る 等

作成したアーキテクチャ

以下のような実装イメージをチームで統一しております。

  • ソースをいくつかの層に分離し、実装すべき箇所を明確にする
  • 各層の依存関係を限定的にする

f:id:daisuke6666:20200227162137p:plain

  • 各層の役割について
    • Controller・・・Requestから入力値を取得し、Domain.Serviceに処理を移譲、結果をResponseに渡す
    • Request・・・アクセス認証、入力値のvalidation、入力値のデータ型キャストを行う
    • Response・・・APIレスポンスの構築、Viewファイル
    • Domain・・・ビジネスロジックがここに集約される
      • Service・・・Controllerからデータを受け取り、Entity、ValueObjectの生成、RepositoryへEntityの取得、作成、更新、削除を移譲する
      • Entity・・・一意に識別できるIDを持ち、その属性が変化しても同じものだと判定する、もしくは同じ属性でも別のものだと判定する場合があるもの
        例:ユーザー → 名前が変わっても同じ人である、同姓同名の人がいても別の人の場合がある
      • ValueObject・・・その属性やふるまいだけを表現し、それが「どの」人なのか、「どの」ものなのかは気にしないもの
        どれかを識別するIDは持っていない。また、オブジェクト自体は不変であり、setterは絶対にない
    • Infrastructure・・・DB、他サービスAPI、Platformなどの外部アクセスを行う(Platformとはデータ基盤の事です)

f:id:daisuke6666:20200227161905p:plain

例えばDBから取得しているデータをAPIから取得するようにしたい。
となった場合infrastructureを修正すれば良いとすぐにわかります。

導入してわかったこと

  • メリット
    • どこかを修正した時に上位層を修正すれば良いので、影響範囲がわかりやすく、これが最大のメリットに感じています。
    • おかげで開発者間での実装の相談のしやすさや見積もりの精度も上がりました。
    • コードが増えてくるとどこに何をするものがあるのか不明確になる事が多いと思いますが、役割を明確にする事によって解決し変化に強いシステムが作れます。
  • デメリット
    • 開発メンバーの入れ替わり等があると共有認識を作るのが大変かもしれません。ドキュメントやサンプルコードを見る事でチーム間で共有しています。
    • 初期段階の仕組み作りは少し大変かもしれません。(Laravelのディレクトリ構造も変えたような記憶があります)

今後もメリットを増やせるように仕組みをアップデートしていこうと思います。

ちょっとしたデザインの経験談

こんにちは、デザインチームのキョウと申します。
最近はスパイスとカレーにはまっていて、毎日寝る前にインスタで美味しそうなカレーの投稿写真をひたすら眺めることで、日々の癒やしの時間として過ごしています。
そろそろスパイスマスターの資格を取ろうかなとも考えております(・∀・)

さてさて、本題に入ります。

web業界の流行りのビジュアルデザインとUIデザインの変革をずっと見てきましたが、自分がデザインする時によくやっていることや、使っているツールについて、ちょっと話をしようと思います。


- ひたすら参考サイトを見る

デザイナーとして働いてもうすぐ9年になりますが、新人として働き始めた頃は、ひたすら参考サイトを探して、いいと思ったデザインをひたすら真似して作るという経験は、今でも生かしています。
特にデザインのアイディアがどうしても浮かばない時や、複雑なUIはどうやって作っていくのかは悩んでいる時に、とにかく参考書のようにいろいろなサイトを見ていました。

優れたものをひたすら眺めて、考えて、いざ手を動かして作ってみると、徐々に自分のものになっていく感じがとても実感できるようになります。
そしてこの過程をやっているうちに、自分なりのアレンジもできるようになり、最終的にデザイン感性の磨きとスキルアップには繋がったと、私はそう感じています。

最近では、作る前にもう既に頭の中にイメージが湧いたり、こういう場合はこういうUIだー、この改修ならこのサイトのUIを参考にすればいいよー、みたいなことができるようになりました。

私がよく参考にしているサイトはこちらになります。

webサイト系ならここらへん:
https://muuuuu.org/
https://bookma.torch.blue/
https://1guu.jp/

バナー、チラシ系ならここらへん:
https://retrobanner.net/
http://bannermatome.com/

配色に悩んだら:
https://colorhunt.co/
https://colors.muz.li/
https://coolors.co/

そしてみんなご存知のピンタレスト(何でも参考になります):
https://www.pinterest.jp/


- このデザインは難しい、どうしたらいいかは分からない時は

最初の一歩が難しく、なかなか手を付けられないと感じているデザイナーはいると思いますが、そんな時に、とにかくめっちゃラフな状態でもいいので、紙にワイヤーを書いたり、FigmaやSketch、Adobe UXなどのUIが作りやすいツールで簡単なレイアウトを作ったり、コンテンツの整理から始まるといいと思いますね(σ・∀・)σ

もちろん会社にちゃんとしたディレクターがいて、ディレクターがキレイなワイヤフレームを書いてくれるケースも多いと思いますが、うちの会社の場合は、正式なディレクターという役割を持っている人はいないので、大きなコンテンツ改修や、新規ページを作成する際に、デザイナーはディレクターの役割として働かないといけない時は結構あります。
そういう時、やはり作業依頼者に要望を聞きながら、まず最初はコンテンツと情報の整理から、ワイヤフレームを作っていくというケースが多いです。

ワイヤフレームができたら、自分も作業依頼者も頭の中でイメージができるようになり、フィードバックのやりとりもしやすくなりますね。
そして最初からデザインガイドラインが決まっていれば、わりとデザインにすんなり入れるようになります。

もし最初から色もデザインのテイストも決まってないであれば、やはり先程申し上げた通り、参考サイトをとにかく真似して作ってみよう、そして細かいデザイン調整や、難しいパーツは後回しにしよう、と私はよくこのやり方でやっています。
そしたら、デザインのスピードもかなり上げられて、最初のデザインから何回もブラッシュアップして行くうちに、どんどんいいデザインが出来上がっていきます。


- 常識にとらわれない、時には変則も必要

デザインガイドラインは既に決まっていて、サイトで使う色やフォントも決まっている案件は多いと思いますが(特に自社の保守案件の場合)
明らかにここはこの色を使うと変だよー、でもデザインガイドライン上ではこのボタンの場合はこの色を使えと書かれているよー、この部分はデザインガイドラインに載せた色と文字サイズだとすごく見づらいよー、みたいな時は結構あると思います。
そういう時、きっちりデザインガイドラインを沿って作るより、やはりユーザー目線から見て、ユーザー層を考えた上で、見やすい、使いやすいデザインにするのが一番大事だと、私は思いますね。

もちろん、デザインガイドラインや、サイト全体の統一感からすごく離れたテイストや色を使うのは駄目だと思います。
でも、同じ色トーン、似たような構成であれば、ちょっとした変化を出したり、色を鮮やかにしたり暗くしたり、文字サイズもちょっと上げたり下げたりするなら、全然ありだと思いますね(σ・∀・)σ

そして世の中のデザインルールでは、同じサイトでは最大3-4種類のフォントスタイルと色を使わないという傾向があると感じてますが、もちろん統一感とスッキリ度は大事だと思います、自分もごちゃごちゃしたデザインより、スッキリしたデザインの方が好きです。
でも時には、どうしてもユーザーに見てもらいたい、どうしても特別感を出したい、どうしてもスペシャルなコンテンツにしたい時ってありますよね?
そういう場合、きちんとルールを守るより、時にはちょっと外れたデザインをするのもありだと思いますけどねヽ(^o^)丿


以上。
ちょっとした自分なりの経験談でした。
参考になれたら幸いでございます。

そして、スパイスの力でみんなが健康な体に作れたらいいなと、願います(`・ω・´)ノ

AWS CodePipelineを用いたデプロイ

DevOps チームの うすい と申します。よろしくお願いします。 昨年11月にハーフマラソンに出てから膝に矢を受けてしまった状態が続いています。困っています。

今回は最近作ったシステムにおけるデプロイについてお話したいと思います。

前提

弊社の主だった環境は AWS 上にあります。また、ソースコード管理には GitLab を使用していて、GitLab CI も元気に稼働しています。 本システムは小規模で、リリース後のデプロイは多くないことが想定されています。

デプロイフロー

フローとしては下記のようになっています。

  1. master マージ
  2. GitLab CI で成果物を S3 にアップロード
  3. S3 にファイルが作成されたことをトリガーとして CodePipeline がスタート
  4. ステージング環境にて CodeDeploy でデプロイ
  5. 本番環境デプロイ承認のためのメール送信と Slack 通知
  6. 承認権限を持った人が CodePipeline の中で承認
  7. 本番環境にデプロイ

少し細かく見ていきましょう。

GitLab CI

GitLab CI の中でcomposer installnpm installをしています。各ツールのバージョンを簡単に固定したかったため、今回は PHP 用のイメージの中で npm をインストールしたりするのではなく、バージョンを指定したイメージを使用し作成した成果物を次のジョブに引き継ぐ方式をとりました。 .gitlab-ci.yml の概要です。

stages:
  - npm
  - composer
  - upload

npm:
  stage: npm
  image: node:12.14.1-alpine3.11
  script:
    - npm install
    - npm audit fix
    - npm run production
    - tar czf node_modules.tar.gz node_modules
  artifacts:
    paths:
      - node_modules.tar.gz

composer:
  stage: composer
  image: composer:1.9
  script:
    - composer install
    - zip -r ./${CI_PIPELINE_ID}.zip .
  artifacts:
    paths:
      - ./${CI_PIPELINE_ID}.zip

s3upload:
  stage: upload
  image: alpine:latest
  before_script:
    - apk add --no-cache python3
    - pip3 install awscli
  script:
    - aws s3 cp ./${CI_PIPELINE_ID}.zip s3://${S3BUCKET}/${APP}.zip

実際にはbefore_scriptと呼ばれる文字通りscriptの前に実行されるセクションで環境変数ファイルに秘匿情報を埋め込んだりもしています。それらの秘匿情報は GitLab のSecretに保存しています。 このようにして作成された成果物を最終的に S3 にアップロードしています。

また、これまでは nginx や php-fpm の設定ファイルは DevOps チームの Ansible のリポジトリで管理されていることが多く、開発チームで変更したい場合には

依頼&マージリクエスト作成(開発チーム) → マージ&反映(DevOps チーム) → 確認(開発チーム)

となっていましたが、開発チームで使用しているリポジトリに必要なファイルをすべて含め、CodeDeploy の中で反映を行うようにしました(後述)。

CodePipeline

CodePipeline は S3 にファイルが置かれたことをトリガーとしてスタートします。特に込み入ったことはしておらず、シンプルなパイプラインとなっています。

イメージ図

f:id:goode:20200210174116p:plain
CodePipeline

SNS との連携も上記画像の真ん中の上にあるNotifyから簡単に作成することができて、今回はパイプラインの成功スタート失敗、そして承認時に SNS に通知を送って Lambda を起動し Slack の Incoming Webhooks で通知、といったことをしていますが、AWS Chatbot でも検証中です(下記図)。Lambda 内でメッセージをパースする必要もないので、こだわりがなければ AWS Chatbot で良いかと思います。 承認部分に関してはメールでの通知も行っています。

Chatbot

CodeDeploy

今回は AutoScaling と EC2 を組み合わせたシステム構成になっているので、コンピューティングプラットフォームEC2/オンプレミス、またデプロイ設定はリリース後の修正が多くないこと、また利用される時間があらかじめ把握することができることから、時間の節約のためデプロイタイプインプレースにしました。

CodeDeployはイベントという形で順々に処理を実行していきます。

イベント

こちらのAfterInstallの中で設定ファイルのアップデートや nginx の再起動などを行っています。

また、余談ですがインスタンス起動時のユーザーデータに登録したスクリプトで CloudWatch Alarm の作成を行い、ステージング環境など夜間休日にインスタンスを停止する環境において残り続けてしまう CloudWatch Alarm は AWS Batch で削除しています。 AWS Batch のコンテナイメージはこんな感じで作って ECR に push しています。

delete-insufficient-cloudwatch-alarm.sh

#!/usr/bin/env bash

aws cloudwatch describe-alarms --state-value INSUFFICIENT_DATA --region ap-northeast-1 | grep AlarmName | awk '{print $2}' > ./result.txt

sed -i -e 's/"//g' ./result.txt
sed -i -e 's/,$//g' ./result.txt

for name in $(cat ./result.txt)
do
  aws cloudwatch delete-alarms --alarm-names ${name} --region ap-northeast-1
done

Dockerfile

FROM amazonlinux:latest
 
ENV LANG C.UTF-8
RUN yum -y install unzip aws-cli
ADD delete-insufficient-cloudwatch-alarm.sh /usr/local/bin/delete-insufficient-cloudwatch-alarm.sh
 
ENTRYPOINT ["/usr/local/bin/delete-insufficient-cloudwatch-alarm.sh"]

まとめ

今回は EC2 を選択しましたが、別のプロジェクトでは ECS(Fargate)を使用していたり他にも IaC や CI / CD の強化といったこともしています。DevOps チームのメンバーは現在絶賛募集中ですので、興味を持たれた方はご連絡いただければと思います。また、もっといいやり方や改善点を指摘してくれる方もカジュアル面談でお越しいただければと思います。

Composer対応していないパッケージをComposerで定義する

こんにちは。
厳しい寒さが続いておりますが、いかがお過ごしでしょうか。
サービス開発チームのすずきです。

みなさんComposerは使っていますか?(何をいまさら)
弊社の英会話システムはPHPで作られており、パッケージの依存管理にはComposerを利用しています。
新しいパッケージを追加したいときは

$ composer require monolog/monolog

みたいな感じでPackagistから取得することが多いと思うのですが、
今回やりたいこととしてはS3バケットに上がっているパッケージを追加し利用できるようにする ことです。

元々は、弊社オンプレのGitlab上に配置されており、以下の様にcomposer.jsonに記載することで利用しておりました。

{
    "repositories": [
        {
            "type": "vcs",
            "url": "git@bitbucket.org:vendor/my-private-repo.git"
        }
    ],
    "require": {
        "vendor/my-private-repo": "dev-master"
    }
}

上記のサンプルはこちらから拝借🙏

今回は、パッケージをZipしたものがS3にアップロードされている状態で、Composerを利用してパッケージ追加したいと思います。
そのためには自分でpackageを定義する必要があり、私たちの場合はpackageに対して、元々パッケージが定義していたcomposer.jsonの内容を記載することで対応しました。

パッケージが定義していたcomposer.json

{
    "name": "vendor/my-private-repo",
    "description": "this is a private package",
    "type": "library",
    "require": {
        "php": "^7.2.0",
        "ext-json": "*",
    },
    "require-dev": {
        "phpunit/phpunit": "< 7.2",
    },
    "autoload": {
        "psr-4": {
            "Vendor\\Private\\": "src/private"
        }
    }
}

パッケージを利用する側のcomposer.json

{
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "vendor/my-private-repo",
                "version": "3.1.7",
                "dist": {
                    "url": "https://path-to-aws-s3-bucket/my-private-repo-v.3.1.7.zip",
                    "type": "zip"
                },
                "require": {
                    "php": "^7.2.0",
                    "ext-json": "*",
                },
                "autoload": {
                    "psr-4": {
                        "Vendor\\Private\\": "src/private"
                    }
                }
            }
        }
    ],
    "require": {
        "vendor/my-private-repo": "3.1.*"
    }
}

パッケージが依存している他のパッケージをrequireに記載することでComposerが依存関係を解決してくれます。 また、パッケージを利用する側で不要なrequire-devは削除しています。

参考

getcomposer.org

寒い日が続きますが、来週には春の暖かさがくるようです🤗

Androidのダークテーマ

こんにちは、ネイティブアプリ開発を担当している杉山です。

今回は、Android 10 から利用可能となった「ダークテーマ」のお話です。

ダークテーマとは

Android 10 から利用可能となった黒基調の UI です。

ダークテーマのメリット

  • 電力消費を節約!
  • 視覚障がいのある方、明るい光が苦手な方にとって見やすさ向上!

ダークテーマを使う

1. アプリのテーマに DayNight Theme を継承させる

/res/values/styles.xml にて以下の設定を行う。

<style name="AppTheme" parent="Theme.AppCompat.DayNight">

MaterialComponents のダークテーマも利用可能です。

<style name="AppTheme" parent="Theme.MaterialComponents.DayNight">

2. リソースディレクトリを切り分ける

修飾子として、日中用には notnight を付け、夜間用には night を付けて、リソースを管理。( notnight が付いていない場合、通常の values からリソースを取り出す。)

- res
  - values-night
  - values-notnight
  - values
  - drawable-night
  - drawable

3. アプリ内に適用するモードを設定

アプリケーションクラスを継承させたクラスの中で以下の設定を行う。

AppCompatDelegate.setDefaultNightMode([設定するモード])

モード一覧

  • MODE_NIGHT_YES ... 常に夜間モードを使用。リソースはnight修飾子の付いたものを使用。

  • MODE_NIGHT_NO ... 夜間モードは使用しない。リソースはnotnight修飾子の付いたものを使用。

  • MODE_NIGHT_FOLLOW_SYSTEM ... システムの判断に従って判別

  • MODE_NIGHT_AUTO ... 自動で判別。リソースは日中の場合 notnight修飾子の付いたものを、夜間の場合は night修飾子の付いたものを使用。

モードが切り替わったことをハンドリング

AndroidManifest.xml 内で以下の設定を行う。

<activity
    android:name=".Activity"
    android:configChanges="uiMode" />

onConfigurationChanged を用いて、設定されているテーマを確認する。

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    val isDarkTheme = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
    when (isDarkTheme) {
        Configuration.UI_MODE_NIGHT_NO -> Log.d("Mode : ", "通常モード")
        Configuration.UI_MODE_NIGHT_YES -> Log.d("Mode : ", "ダークテーマ")
    }
}

おまけ

ダークテーマに対応しない

styles.xml で、DayNight Theme を継承しない

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

ダークテーマをレイアウトから確認

  1. レイアウトファイルを開き、Design タブに切り替える。

  2. 画像のようなアイコンが、左上にあるのでクリック。

  3. Night Mode をNightに設定。( Not Night = 日中、Night = 夜間 )

f:id:r_sugiyama:20200130123302p:plain

まとめ

ダークテーマの対応は意外と簡単にできますが、イメージなどのリソースを作る対応が入ると大変だなと感じました。

ER図 の自動作成を CI に組み込んだ話

塚田です。新人の頃からドキュメントの管理が大の苦手です。

ドキュメントの更新は滞り陳腐化してしまい、必要な時に見直すと使い物にならなかったりします。誰もが経験あるのではないでしょうか。

少しでもその呪縛から解放されるのであれば全力を注ぎたくなる性分なので、 ER図 を自動作成してくれる SchemaSpy を社内で使っている GitLab CI に組み込みました。

今回は SchemaSpy の紹介と、CI に組み込んだ話をしたいと思います。

SchemaSpy とは

DBに接続してテーブル構成などをスキャンして、html としてアウトプットしてくれる機能を持っています。 MySQLPostgreSQL などをサポートしています。

http://schemaspy.org/

CI への組み込み

弊社のCIのパイプラインでは、GitLab に push すると、自動でテストなどが動くような作りになっています。 そのテストの一環で、DBにスキーマ情報を migration する動きが既にありました。 そこに処理を追加し、CI で作られる DB へ SchemaSpy を繋げるようにしました。

SchemaSpy によって作られる成果物を S3 へ copy、 S3では Static website hosting を有効化にして、静的コンテンツを動かす Webサーバ のように使い、社内IPからのみ繋がるようにしました。

設定内容

.gitlab-ci.yml に追加したのは以下の部分

schemaspy:
  <<: *go_setup
  <<: *tags
  stage: schemaspy
  services:
    - mysql:5.7
  script:
    - make ci-migrate
    - apt-get update
    - apt-get install -y openjdk-11-jre/stable unzip busybox build-essential graphviz
    - wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.48.zip -qO - | busybox unzip -
    - wget https://github.com/schemaspy/schemaspy/releases/download/v6.0.0-rc2/schemaspy-6.0.0-rc2.jar -O schemaspy.jar
    - java -jar schemaspy.jar -configFile ./ci/schemaspy.properties -vizjs
    - mkdir -p ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/schemaspy
    - mv output/* ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/schemaspy/.
  artifacts:
    paths:
      - ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/schemaspy
    expire_in: 3 days
  only:  
    - master
    - development

ER図の作成は全ての branch で動かす必要もないので 現在は限られた branch のみ、SchemaSpy を動かすようにしています。

DBの接続情報は schemaspy.properties に書いています。

# type of database. Run with -dbhelp for details
schemaspy.t=mysql
# optional path to alternative jdbc drivers.
schemaspy.dp=mysql-connector-java-5.1.48/mysql-connector-java-5.1.48-bin.jar
# database properties: host, port number, name user, password
schemaspy.host=mysql
schemaspy.port=3306
schemaspy.db=database
schemaspy.u=root
schemaspy.p=password
# output dir to save generated files
schemaspy.o=output
# db scheme for which generate diagrams
schemaspy.s=public

その後、AWS S3 への push は以下のように定義して、gitlab runner が動くインスタンス自体に適切な権限を付与しています。

document:
  <<: *awscli_definition
  <<: *tags
  stage: document
  before_script:
    - export REF_NAME=`echo ${CI_COMMIT_REF_NAME} | sed -e 's%/%_%g' -e 's%-%_%g'`
  script:
    - aws s3 cp ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/schemaspy s3://${s3-backet}/schemaspy/${repository}/${REF_NAME}/ --recursive
  only:  
    - master
    - development

S3 ではパブリックアクセスを拒否しております f:id:sumito1984:20200123114004p:plain

バケットポリシーで社内の IP のみアクセスできるよう設定を入れています。

{
    "Version": "2012-10-17",
    "Id": "document from designated SourceIP",
    "Statement": [
        {
            "Sid": "document from designated SourceIP",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::xxxx-document/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "xxx.xxx.xx.xx/32"
                }
            }
        }
    ]
}

パイプライン が pass することを確認すると

f:id:sumito1984:20200123015435p:plain

S3 にファイルが置かれています。 ブラウザ で以下のような スキーマ情報 を見ることができます。

テーブル情報 f:id:sumito1984:20200123020019p:plain

自動生成されたER図 f:id:sumito1984:20200123020012p:plain

これで DB に関係するドキュメント、特に ER図 のメンテナンスから解放されそうです。

ちなみに、mysqldump から読み込ませるやり方はこちらに記載していますので、必要であればご覧いただけますと幸いです。

SchemaSpy で SQLファイル からスキーマ情報を出せるようにした - Tech Tips

それでは!