astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Stylelint を導入したときのあれこれ

こんにちは、フロントエンドエンジニアの minamo です。

最近は夏映画の公開ラッシュで週2回映画館に通っています。忙しい!

3度目のブログは、最近導入してみた Stylelint についてです。

  • Stylelint を導入するメリット
  • おすすめのプラグイン
  • 導入するときに困ったこと・その解決法

などについて書いていきます。

Stylelint って何?

Stylelint は CSS の 構文チェックツール です。Javascript の ESLint の CSS 版とも言える存在です。

基本的な構文エラーや typo 、たまにやってしまうスタイル指定の重複など、構文チェックツールとしての機能はもちろんのこと、

  • プロパティの順番
  • 一行空けるか、空けないか
  • カラーコードは大文字か小文字か

などなど、個人間で書き方の差が激しい(ともすれば宗教戦争になりかねない)CSS の記述ルールを定め、統一してくれます。

チームでの開発など、複数人で作業する場合でも自動的にルールに則って記述を統一してくれるので、レビュー指摘の手間や無駄な争いがなくなり、開発にあてる時間を増やすことができます。

筆者はこのツールの存在を知ったとき、「ワシの若い頃はプロパティの順番を丸暗記していたもんじゃったがのう」と昭和の昔話おじいさんになってしまいましたが、令和に生きる開発者はそんな苦労をすることなく、プロパティの順番や書き方を気にせずのびのび開発し、あとでポチッと一発 Stylelint にかければいいのです。素晴らしいことだと思います。

インストール

本体と同時にプラグインもインストールしていきます。

npm install stylelint stylelint-config-standard stylelint-config-recess-order stylelint-config-prettier stylelint-scss --save-dev

今回選んだプラグインはこちら。

recommended or standard?

基本的な設定には stylelint-config-recommended もしくは stylelint-config-standard がありますが、 stylelint-config-standard を採用するのにも少し紆余曲折がありました。

まず導入対象のプロダクトでは SCSS を使っていたので、 stylelint-config-standard-scss を使おうとしたのですが、あまりメンテナンスされていないため見送りに。

次は stylelint-config-recommended-scss の使用を検討。しかし、 recommend よりは規則がより厳格な standard の方を使いたい。こちらは規則が少しゆるめの stylelint-config-recommended を extend しているため、standard にしたいのであれば自分でルールを足すしかありません。

結果として、stylelint-config-standard + stylelint-scss を入れて、推奨するルールを設定ファイルに記述しました。

設定ファイル

インストールが完了したら、設定ファイルを作成し、どのプラグインを使うか指定したり、細かいルールを足していったりとカスタマイズしていきます。

touch stylelint.config.js  // 設定ファイルの作成

設定ファイルの書き方も ESLint と似ていますね。ここでは導入したときの設定を転載します。(あまり細かい説明はしません)

extends

extends: ['stylelint-config-standard', 'stylelint-config-recess-order', 'stylelint-config-prettier'],

順番に注意しましょう!

plugins

plugins: ['stylelint-scss'],

ignoreFiles

ignoreFiles: ['**/node_modules/**'],

rules

rules: {
  'at-rule-no-unknown': null,
  'scss/at-rule-no-unknown': true,
  // ↑ここまでは stylelint-scss の推奨ルール。
  // ↓あとはお好みの設定を記述しましょう。
},

選択肢を選んでいくだけで Stylelint の rules を書き出してくれるジェネレーターもあります。便利〜! 例を見ながら回答していくので、ルールの内容の勉強にもなりますね。

maximgatilin.github.io

ただ全部答えていくと結構な量になりますし、 config とかぶっているルールは外してもいいかもしれませんね。 config でどういったルールを設定しているのか、内容に一度目を通しておくといいと思います。

stylelint-config-recommended

github.com

stylelint-config-standard

github.com

使い方

package.json

lint コマンドや fix コマンドで Stylelint が動作するように設定しましょう。

{
  "scripts": {
    "lint:css": "stylelint **/*.{css,scss,sass}"        // エラーを検知する
    “fix:css": "stylelint —fix **/*.{css,scss,sass}"    // エラーを自動的に修正する
  }
}

VSCode

VSCode をお使いであれば、拡張機能を入れるのがよいでしょう。

marketplace.visualstudio.com

ESLint と同じように保存時に動作するように設定すれば、自動的に修正してくれるので開発スピードが上がりますね。

"editor.codeActionsOnSave": {
  "source.fixAll.stylelint": true
}

導入時に困ったルールなど

selector-pseudo-element-no-unknown

導入対象のプロダクトはフレームワークに Vue.js (Nuxt.js) を使っていたので、 ::v-deep がこのルールに引っかかります。 設定ファイルで ::v-deep を対象から外すよう個別に設定しました。

declaration-block-trailing-semicolon

この設定が Prettier の「インラインスタイルで末尾のコロンを削除する」設定とコンフリクトします。これは調べた限り解決方法が見つからなかったです。。

no-descending-specificity

「詳細度の高いセレクタより後に詳細度の低いセレクタを定義することを禁止する」ルールです。これは lint fix でも修正できず、後から修正していこうと思うと結構大変です…!なのでこちらは泣く泣く無効化することに。

大事なこと

Stylelint は最初から導入する のが後で困らないのでおすすめです!!

astamuse ではいつでもエンジニア&デザイナーを募集しています。 もちろんリモートでの面接にも対応していますので、お話だけでもお気軽にどうぞ!

Gitlab CIからGoogle Cloud Buildを実行してみた

お久しぶりです。開発部のyanagitaです。
弊社ではGitリポジトリにGitlabを採用していて、CIはプロジェクトによってGitlab CIやJenkins、またその2つを組み合わせて使用しています。
今回は、Google CloudでCIサービスとして提供されているGoogle Cloud BuildをGitlab CIから実行するところまでを紹介します。

お詫び

Google Cloud Buildの実行には事前にサービスを有効にしておいたり、使用するアカウントにGoogle Cloud Buildの実行権限を与える必要がありますが、そのあたりは今回省略します。

Gitlab CIからGoogle Cloud Buildを実行する

以下は、.gitlab-ci.ymlです。下記の例はGoogle Cloud Buildを実行するただけの記述になります。

stages:
  - google_cloud_build

.google_cloud_build_template:
  stage: google_cloud_build
  image: google/cloud-sdk:313.0.0-alpine
  script:
    - echo $GCLOUD_AUTH_CODE | base64 -d > ./gcloud_auth.json
    - gcloud auth activate-service-account --key-file ./gcloud_auth.json
    - rm -f ./gcloud_auth.json
    - gcloud config set project $GCLOUD_PROJECT
    - gcloud builds submit . 

build_staging:
  variables:
    GCLOUD_PROJECT: stg_project
    GCLOUD_AUTH_CODE: $GCLOUD_AUTH_STG
  extends: .google_cloud_build_template
  rules:
    - if: $CI_COMMIT_BRANCH =~ /^(release|hotfix)_/

build_production:
  variables:
    GCLOUD_PROJECT: prd_project
    GCLOUD_AUTH_CODE: $GCLOUD_AUTH_PRD
  extends: .google_cloud_build_template
  rules:
    - if: $CI_COMMIT_TAG =~ /^\d{6}\.\d+-(release|hotfix)$/

流れに沿って解説します。
まずは、一番のキーとなるGoogle Cloud Buildの実行部分です。この部分はテンプレート形式で記述しています。理由についてはjobの解説時に説明します。

.google_cloud_build_template:
  stage: google_cloud_build
  image: google/cloud-sdk:313.0.0-alpine # 解説1
  script:
    - echo $GCLOUD_AUTH_CODE | base64 -d > ./gcloud_auth.json            #
    - gcloud auth activate-service-account --key-file ./gcloud_auth.json # 解説2
    - rm -f ./gcloud_auth.json                                           #
    - gcloud config set project $GCLOUD_PROJECT                          #
    - gcloud builds submit .                                             # 解説3

Google Cloud Buildの実行はgcloudコマンドを使用するためgoogle/cloud-sdkのイメージを使用します。(解説1)
gcloudコマンドの実行には事前に認証とGoogle Cloudのプロジェクトの紐付けを行う必要があります。ローカル等で認証を通す場合はブラウザの起動を挟んだりするのですが、gitlab ci上では行えないため認証情報が記述されたjsonファイルを使って認証を行います。その際、認証に必要なjsonを直接git上で管理するのは社内のGitリポジトリだとしても不安があるため、認証用のJsonの内容をBase64でエンコードした状態で環境変数($GCLOUD_AUTH_CODE)に格納しておき、認証直前でデコードした結果を認証用jsonとして復元する方式を取っています。 上記流れで復元した認証Jsonとプロジェクトの紐付けをgcloudコマンドで行います。(解説2)
記述中で2箇所環境変数を使用しています。理由についてはjobの解説時に説明します。
以下は、base64エンコードのコマンド例

$ base64 -i 認証用のjsonファイル
~~~~ エンコード結果 ~~~~~ # ← この結果を環境変数に実行時に格納しておきます。

最後にGoogle Cloud Buildの実行を行います。例だとcloudbuild.yamlをプロジェクトルートに配置しています。(解説3)

次に、jobの解説です。
弊社に限らずだと思いますが、ステージング環境と本番環境でGoogle Cloudのプロジェクトを分けて運用しているため、Google Cloud Buildを実行する環境ごとにプロジェクトや認証情報が変わってくることになります。
上記の環境差分を解消するため、Google Cloud Buildの実行環境ごとにJobを定義しています。例ではステージング環境向けと本番環境向けのJobを記述しています。

stages:
  - google_cloud_build

build_staging:  # ステージング環境向け
  variables:
    GCLOUD_PROJECT: stg_project
    GCLOUD_AUTH_CODE: $GCLOUD_AUTH_STG
  extends: .google_cloud_build_template  # 解説4
  rules:
    - if: $CI_COMMIT_BRANCH =~ /^(release|hotfix)_/

build_production: # 本番環境向け
  variables:
    GCLOUD_PROJECT: prd_project
    GCLOUD_AUTH_CODE: $GCLOUD_AUTH_PRD
  extends: .google_cloud_build_template # 解説4
  rules:
    - if: $CI_COMMIT_TAG =~ /^\d{6}\.\d+-(release|hotfix)$/

差分以外の定義に関しては、最初に解説したテンプレート形式での記述にすることで各Jobはテンプレートを継承し差分のみの記述で済むようになります。(解説4)
差分となる環境変数はvariablesで定義されています。

  • GCLOUD_PROJECT = Google Cloud Buildを実行するプロジェクト名
  • GCLOUD_AUTH_CODE = base64でエンコードされた認証Json

ここで、GCLOUD_AUTH_CODEは別の環境変数から値を定義する形を取っています。これはテンプレート側で同じ名前の環境変数を使用するために環境変数名の変更を行っています。
GCLOUD_AUTH_CODEに与えた環境変数の内容はProjectの Setting > CI/CD > Variablesで定義していて、内容はbase64でエンコードされた認証用のJson情報になっています。これで認証用のJsonをgit管理することなくGitla CI上の使用することができるようになります。

f:id:astamuse:20210707015648p:plain

以上でGitlab CIからGoogle Cloud Buildを実行する解説は終了です。

最後に

今回Gitlab CIからGoogle Cloud Buildの実行を解説しました。実際、Gitlab CIだけでも十分完結させることは可能です。ただ、うまく組み合わせることでより強力な環境でのビルドやGoogle Cloudの他サービスに柔軟に対応することができるようになります。

それではまたいつの日か

Docker環境でPXEブート環境を整えようとして苦しんだ話

f:id:astamuse:20181127232540j:plain

こんにちはnishikawaです。今回はDockerの勉強がてらにPXE環境の構築をしてみたので、そこでの気づきや結果などを書いていきます。

参考にしたのは、以前 弊社の元バンドマンであるgucci氏が書いてくれた記事を参考にしております。

lab.astamuse.co.jp

概要

Dockerを勉強するにあたり、PXE環境をDockerコンテナを用いて構築します。インストールするものはUbuntu Server 20.04 LTSですが、nginxの設定まで文章にしていたら記事が凄く長くなったので、この記事ではPXEでブートメニューを起動するところまでを対象とします。

最初やりたかったこと

自宅にPXE環境を構築するにあたり既存のネットワークを侵害しないようにNICが複数あるPCが必要だと思いました。理由はわざわざ資材をダウンロードしたりするのが面倒なのとSSH接続して設定などを行ったほうが楽だからです。で、自宅にワイヤレスインターフェースとイーサネットを備えたノートPCがあったので、これを利用して以下のような環境を構築しようと思いました。

これが悪夢の始まりと知らずに・・・

f:id:astamuse:20210526102758p:plain

結局構築したもの

で、多くの困難を乗り越えて最終的に構築したものが以下になります。見て分かるとおり、セグメントを物理的に分けてPXE環境を独立する予定が、同一セグメント内に集約されています。どうしてこうなったのかはこの後の文章をお読みください。

f:id:astamuse:20210526102804p:plain

最初にやった設定

では、最初構想していた環境を構築するための設定をつらつらと書いていきたいと思います。

gucci氏の記事ではtftpd-hpadhcpdを使用してましたが、同じものでやっても芸がないので今回はdnsmasqを使っていきたいと思います。

dnsmasqはDNSを提供するミドルウェアですが、DHCPやTFTPを提供する機能も持っています。なので、このコンテナを生成するためのDockerイメージを手始めに作成していきます。

Dockerイメージ作成

ディレクトリ構成は以下のような感じ

dnsmasq
├── Dockerfile
└── etc
    └── dnsmasq.conf

まずdnsmasq.confは以下のように設定を変更します。コンテナ内では root でプロセスを起動するのですが、デフォルトでは起動可能なユーザが設定されていないのでプロセスが立ち上がりません。そのためuser=rootを追加します。 あと、後ほど設定を追加するためにdnsmasq.dディレクトリ内の設定ファイルを取り入れられるようにしておきたいので、conf-dir=/etc/dnsmasq.dを有効にしておきます。

 # If you want dnsmasq to change uid and gid to something other
 # than the default, edit the following lines.
-#user=
+user=root
 #group=

・・・


 # Include another lot of configuration options.
 #conf-file=/etc/dnsmasq.more.conf
-#conf-dir=/etc/dnsmasq.d
+conf-dir=/etc/dnsmasq.d

設定を変更したらDockerfileを作成します。

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y dnsmasq
VOLUME /etc/dnsmasq.d
VOLUME /var/lib/tftp

COPY etc/dnsmasq.conf /etc/dnsmasq.conf

CMD /usr/sbin/dnsmasq --conf-file=/etc/dnsmasq.conf && tail -f /dev/null

ポイントは/etc/dnsmasq.d/var/lib/tftpにアタッチポイントを作っておくところです。この後説明しますが、PXE用のdnsmasqの設定を後から/etc/dnsmasq.dに追加するのと、ブートローダを/var/lib/tftp配下に配置するのでここは必ず設定します。

後は先ほど変更したdnsmasq.confをCOPYでイメージ内に配置します。

最後に/usr/sbin/dnsmasqをdnsmasq.confを指定して起動します。ただ、この方法で起動するとバックグラウンドでプロセスを立ち上げてしまうためコンテナがすぐに終了してしまいます。

そのため最後にtailをフォアグラウンドプロセスとして立ち上げコンテナが落ちるのを防ぎます。

Dockerイメージの作成は以下のコマンドで行います。

# docker image build -t test-dnsmasq:1.0.0 .

Dockerコンテナ作成

次にdocker-composeを使用してコンテナを作成・管理したいと思います。

ディレクトリ構成は以下です。

/opt/pxe
├── dnsmasq
│   ├── etc
│   │   └── dnsmasq.d
│   │       └── pxe.conf
│   └── var
│       └── lib
│           └── tftp
│               ├── boot.img.gz
│               ├── ldlinux.c32
│               ├── mini.iso
│               ├── netboot.tar.gz
│               ├── pxelinux.0
│               ├── pxelinux.cfg
│               │   └── default
│               ├── ubuntu-installer
│               │   └── amd64
│               │       └── ...
│               └── xen
│                   └── ...
└── docker
    └── docker-compose.yml

まずはdnsmasqに設定を追加するため/opt/pxe/dnsmasq/etc/dnsmasq.d/pxe.confを作成して以下の設定を追記します。

#DHCP
interface=enp9s0,lo                          #インターフェス名を設定(今回はenp9s0という名前だったのでそれを設定しています)
bind-interfaces
dhcp-range=192.168.1.100,192.168.1.200,12h   #セグメントに沿ってレンジ設定(今回は192.168.1.0というネットワークでリース期間は12hを想定)
dhcp-option=option:netmask,255.255.255.0     #多分いらないと思うが、一応サブネットマスクを設定
dhcp-option=option:router,192.168.1.1        #これも多分いらないと思うがルータのIPを設定
dhcp-boot=pxelinux.0                         #ブートローダのファイル名

#TFTP
enable-tftp                                  #TFTP機能有効
tftp-root=/var/lib/tftp                      #TFTPのルートディレクトリ

/opt/pxe/dnsmasq/var/lib/tftp配下に以下のURLから資材を取得

http://archive.ubuntu.com/ubuntu/dists/focal/main/installer-amd64/current/legacy-images/netboot/

以下、docker-compose.ymlの設定です。

---
version: "3"
services:
  pxe_server:
    image: test-dnsmasq:1.0.0
    volumes:
      - /opt/pxe/dnsmasq/etc/dnsmasq.d:/etc/dnsmasq.d
      - /opt/pxe/dnsmasq/var/lib/tftp:/var/lib/tftp
    network_mode: "host"

以上まで準備ができたらコンテナを起動します。

# docker-compose up -d

これで普通ならうまくいくはずだったのですが、色んな要因が重なり多くの苦しみを味わう羽目になりました。

苦しみポイント1:netboot用資材が見つからない

gucci氏の記事ではUbuntu Server 18.04 LTSのインストールイメージで行っていたのでネットブート用の資材がISO内にあったのでしょうが、今回私が行ったUbuntu Server 20.04 LTSのISOには それがありませんでした・・・。

と言うわけで悶絶とまでは行かないまでもそれなりに調査に時間がかかりました。

苦しみポイント2:TFTPでブートローダが取得できない

Dockerコンテナを起動したので、いざOSインストール対象のPCを起動してみたのですが、以下のようなエラーが出てブートメニューが表示されません。

TFTP
PXE-E11: ARP timeout

どうやらTFTPでブートローダを落として読み込もうとしているのですが、アクセス対象のMACアドレスが解決できなくてエラーになっているようです。

これはなんだ?とずっと考えていたのですが、コンテナにarpコマンドを入れてarpテーブルを見てみたらなんとなく分かりました。以下が問題の箇所です。

Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.11.29                    (incomplete)                              wlp7s0
192.168.1.112                    (incomplete)                              br-24cc64eb1e3c

・・・

Iface部分が本当は先程/opt/pxe/dnsmasq/etc/dnsmasq.d/pxe.confで設定したenp9s0になっていて欲しかったのですが、そうはなっていません。

ワイヤレスの方は正常に認識されているので、どうやらnetwork_mode: "host"によってコンテナに直接紐付けられたインターフェースはワイヤレスの方みたいです。これによりTFTPサーバへのアクセスができなかったのが原因だと考えます。

これを解決するため、docker-compose.ymlでインターフェースを指定できないか あれこれ調査しました。

苦しみポイント3:Dockerコンテナを起動するときにネットワークインターフェースを指定できない

という訳でDockerコンテナ起動時に紐付けるインターフェースを指定する方法を探したのですが、結論から言うと私は見つけることはできませんでした。

なので、複数の物理NICを持っているマシンでnetwork_mode: "host"を使用する時は思わぬ挙動になるため気をつけた方がよさそうというのが現時点での私の見解です。

以上から、当初想定していた環境の構築は抜本から考え直さないといけない状況になってしまいました。

そして改善へ

ひとしきり絶望した後、dnsmasqについてあれこれ調べていたらProxy DHCPという機能を見つけました。どうやらDHCPサーバが既に存在している環境においてPXEを提供するための機能らしいのでこれを使ってやってみることにしました。

やることは/opt/pxe/dnsmasq/etc/dnsmasq.d/pxe.confの設定変更で以下のように修正しました。

-#DHCP
-interface=enp9s0,lo
-bind-interfaces
-dhcp-range=192.168.1.100,192.168.1.200,12h
-dhcp-option=option:netmask,255.255.255.0
-dhcp-option=option:router,192.168.1.1
-dhcp-boot=pxelinux.0
+#Proxy DHCP
+port=0
+dhcp-range=192.168.11.0,proxy
+pxe-service=x86PC,"pxelinux",pxelinux

 #TFTP

・・・

これでコンテナを起動し直したらちゃんとブートメニューが表示されました。

まとめ

今回はDockerの勉強をしている過程で以下の知識を得ることができました。

  • Dockerコンテナに物理インターフェースを直接割り当てるにはnetwork_mode: "host"を使用する
  • 複数の物理NICを持っているマシンでDockerコンテナを起動する際network_mode: "host"を使用する場合はインターフェースを指定することができないので注意が必要
  • dnsmasqはDNSだけでなくDHCPとTFTPサーバとしても使える
  • Proxy DHCPという仕組みで既存のDHCPサーバがあるネットワーク環境下でPXEを追加で提供することができる

今回はgucci氏の記事を元にPXEサーバをdockerコンテナ化してみました。dockerをここまでしっかり触ったことがなかったので発見ばかりでしたが、とても苦しい・・・もとい、楽しい経験をしました。

今後はpreseedなどを駆使してOSのインストールも自動化できたら良いなと思ったので機会があったらやってみようと思います。

それでは。

Copyright © astamuse company, ltd. all rights reserved.