20210503のdockerに関する記事は14件です。

JekyllをRemote - Containersで簡単に開発を始めるために知っておきたかった9つのこと

結論 リポジトリ内にある3つのファイルを確認すると、大体のイメージがつかめるかと思います。 GitHub: hydrangeas/jekyll-remote-container-template .devcontainer/devcontainer.json docker-compose.yml Dockerfile 背景 GitHub Pagesを更新するためにJekyllの環境を入れることにしました。 最近は使っていなかったのでRubyの開発環境は手元になく、0から構築する必要がありました。 とりあえず、手元の環境をなるべく汚さないようDockerの構築から開始したのですが、途中でVisual Studio Code拡張のRemote Containersを知ったので、そちらも同時に構築しました。 前提条件 GitHub PagesにデプロイするためだけのJekyll開発環境を作成する Dockerfile/docker-compose.ymlは開発時にのみ使用する (1) DockerはDocker Desktopをインストールするだけで良い Windows 10 HomeへのDocker Desktop (ver 3.0.0) インストールが何事もなく簡単にできるようになっていた (2020.12時点) よく、PowerShellを使った設定やコントロールパネルの「Windowsの機能の有効化または無効化」で「Hyper-Vを有効にする」「仮想マシンプラットフォームを有効にする」などの事前準備が書かれた記事がありますが実は必要ないです。Docker Desktopのインストーラが自動でやってくれます。 全部手動でやってしまったよ・・。 (2) Visual Studio Codeの拡張機能「Remote - Containers」をインストールする (3) GitHub Pages専用のDockerイメージを使用しない プロジェクトディレクトリを作成&初期化します。 docker run --rm --volume="$PWD\:/srv/jekyll" -it -p 4000:4000 jekyll/jekyll jekyll new yourpage Docker HubにはJekyllのビルドに使えそうなイメージが2つほどあります。 jekyll/jekyll:pages (Compressed Size: 94.33MB) jekyll/jekyll:latest (Compressed Size: 241.25MB) 展開後は3倍くらいのサイズになるので、jekyll/jekyll:pagesを使いたいところですが、bundle installでエラーになっちゃうので使わない方がいいです。(あまり深くエラーを見てません) 加えて注意点を3つ $PWDの後ろに\が必要 初期化だけならjekyll/jekyll:latestでもjekyll/jekyll:pagesでもOK yourpageでディレクトリも作成されますが、カレントディレクトリ(.)にファイルがあるとエラーになります(カレントディレクトリのファイルがDockerの/srv/jekyllに転送されるため) 以下を参照させていただきました。 JekyllをDokcer上で動かしてGitHub Pagesのローカル環境での確認を楽する (4) ワークスペースを保存する [ファイル] → [名前を付けてワークスペースを保存] ここ、とても大事。 保存しておかないと、Remote Containersで接続後にファイルが読み込めない旨のエラーがでます。 (5) Visual Studio Codeにdevcotainer.jsonを追加する Visual Studio CodeのRemote Containers用に(3)で作成したyourpage直下に.devcontainerディレクトリを作成し、その下にdevcontainer.jsonを追加します。 { "dockerComposeFile": [ "../docker-compose.yml" ], "service": "jekyll", "workspaceFolder": "/srv/jekyll", "shutdownAction": "stopCompose" } workspaceFolderはVisual Studio Codeの設定を保存する先になります。docker-compose.ymlで指定するPROJECT_ROOTDIRと同じにしておくのが簡単でいいと思います。(たぶん) dockerComposeFileかbuild.Dockerfileかはお好みで。 (6) サイト内リンクのURLを0.0.0.0ではなくlocalhostにする設定を追加する 色々調べていると、Windowsでは0.0.0.0は使えないのでlocalhostに上書きする(_config.ymlと同階層に_config_dev.ymlファイルを追加して、docker runするときに--config指定する)といった情報が多く出てきます。 必要ないです。 DockerファイルにENV JEKYLL_ENV dockerを指定してください。コマンドからdocker runするなら、-e "JEKYLL_ENV=docker"を指定してください。 ここ、とても大事。 (7) Dockerfile/docker-compose.ymlを追加する カレントディレクトリのファイルを/srv/jekyllディレクトリへ転送するDockerfileです。 FROM jekyll/jekyll:pages # Docker side ENV PROJECT_ROOTDIR /srv/jekyll WORKDIR $PROJECT_ROOTDIR # Copy current directory files COPY $PWD $PROJECT_ROOTDIR RUN bundle install RUN jekyll build EXPOSE 4000 ENV HOST 0.0.0.0 # !! IMPORTANT !! ENV JEKYLL_ENV docker CMD ["jekyll", "serve", "--watch", "--force_polling"] docker-compose.ymlでは、ポートの指定とボリュームのマウントだけしています。 version: "3" services: jekyll: build: . ports: - 4000:4000 volumes: - ".:/srv/jekyll" (8) ウィンドウ左下の><からRemote-Containersを起動する ちなみに、Ctrl + Shift + Pメニューだと、Remote-Containers: Rebuild and Reopen in Containerというのも選べます。Dockerfileを変更したときなどは、こちらから起動するのが便利。 (9) 公式情報をチェックする Developing inside a Container .. Microsoft社の公開情報 vscode-dev-containers .. Microsoft社のJekyll+Remote Containersのサンプルコード How to Develop Inside a Container Using Visual Studio Code Remote Containers .. Docker社の公開情報 その他の情報 VSCode Remote Containerが良い VSCode Remote Containers を利用して最強のローカル開発環境を作りたい DockerとRemote Containersでの開発環境が最高過ぎる MS Learn Visual Studio Code を使用して Docker コンテナーを開発環境として使用する あったんか..。 以上です。 ご覧いただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Phalconをdocker imageに詰め込みたいとき

前提 DockerHubにあるfpmイメージをベースとして考えています。(debianベース) Phalcon4です。psrを入れる辺りを消せば3系でも使えたと思います。 とりあえず先に結論 素直にソースからビルドしましょう。(正しくはPhalconをCの形式にビルドされた後のソース) 以下は私が作っているdocker imageからphalconの本質的なインストール部分だけ持ってきたものです。 # install phalcon RUN pecl install -s psr \ && docker-php-ext-enable psr \ && pecl clear-cache \ && curl -LO https://github.com/phalcon/cphalcon/archive/v${PHALCON_VERSION}.tar.gz \ && tar xzf v${PHALCON_VERSION}.tar.gz \ && docker-php-ext-install -j$(nproc) ${PWD}/cphalcon-${PHALCON_VERSION}/build/php7/64bits \ && rm -rf v${PHALCON_VERSION}.tar.gz cphalcon-${PHALCON_VERSION} 色々試していたのですが、これが確実です。。。 少し前だともっとめんどくさくて、zephirのパーサーから入れないと駄目な感じでしたので、良くなったほうです。 もっとめんどくさかった頃(クリックで開く) 当時書いてたやつなので、ちょいちょい最適化されてないところもありますが。。。 # install php-psr RUN pecl install psr && docker-php-ext-enable psr # install zephir-parser RUN curl -LO https://github.com/phalcon/php-zephir-parser/archive/v${ZEPHIR_PARSER_VERSION}.tar.gz \ && tar xzf v${ZEPHIR_PARSER_VERSION}.tar.gz \ && docker-php-ext-install ${PWD}/php-zephir-parser-${ZEPHIR_PARSER_VERSION} \ && rm -rf php-zephir-parser-${ZEPHIR_PARSER_VERSION} v${ZEPHIR_PARSER_VERSION}.tar.gz # install phalcon RUN curl -LO https://github.com/phalcon/cphalcon/archive/v${PHALCON_VERSION}.tar.gz \ && tar xzf v${PHALCON_VERSION}.tar.gz \ && cd cphalcon-${PHALCON_VERSION} \ && curl -LO https://github.com/phalcon/zephir/releases/download/${ZEPHIR_VERSION}/zephir.phar \ && php zephir.phar fullclean && php zephir.phar compile \ && docker-php-ext-install ${PWD}/ext \ && cd .. \ && rm -rf v${PHALCON_VERSION}.tar.gz cphalcon-${PHALCON_VERSION} 色々試した いまはpearのライブラリも結構更新されるようになって、phalconのreleaseにもpearパッケージのソースがつくようになったりしてます。 そこで、peclで入れれないのかと試したのですが、途中でコンパイルが止まって最終的にコンパイル失敗になってしまうようでした。ディストリビューションによってはうまく動くのかも知れません。。。(私がやろうとしたのはdebianのbuster) 他にPackageCloudというサービスにもphalconのリポジトリはあります。 https://packagecloud.io/phalcon こちらで試しに入れようとしましたが、うまく入ったかな?とおもってapt-cacheコマンドでインストール出来るパッケージを探してみましたが無いことになってしまったので断念。 ppa:ondrej/phpとかも試してみようかとしましたが、そもそもppaがUbuntu用だったし、GPGのエラーが出たりといろいろ苦戦してしまい、結局入れれそうにありませんでした。。。 自分で用意するのが面倒な人に 自分の方で作っているイメージがあります。それを元にしてもいいと思います。 phalconはセマンティックバージョニングなのでメジャーバージョンとしてしかバージョンを付けてないですが。。。 最後に 結果的に、ソースからビルドして入れるのが最適だという結論になったのですが、もしもっと簡単に入れれる方法があったら教えてほしいです。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Dockerfile】DockerにConda環境を構築し、仮想環境をActivateする

CUDAイメージ上に Miniconda or Anaconda 環境を構築し、conda or pip を仮想環境にインストール、アクティベートするところまでをDockerfileで完結させるためのDockerfileを記載します。 実現したいこと CUDAイメージ上にMiniconda(Anaconda)環境を構築 Dockerfile内で、任意の仮想環境を作成し、パッケージをインストール Docker run (attach)した際に、Dockerfileに記載した仮想環境下に入る(conda activateする必要がない) Dockerfile Dockerfile FROM nvidia/cuda:11.2.1-devel-ubuntu20.04 RUN apt-get update && apt-get install -y \ sudo \ wget \ vim WORKDIR /opt RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \ sh Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3 && \ rm -r Miniconda3-latest-Linux-x86_64.sh ENV PATH /opt/miniconda3/bin:$PATH COPY <env_file_name>.yml . RUN pip install --upgrade pip && \ conda update -n base -c defaults conda && \ conda env create -n <env_name> -f <env_file_name>.yml && \ conda init && \ echo "conda activate <env_name>" >> ~/.bashrc ENV CONDA_DEFAULT_ENV <env_name> && \ PATH /opt/conda/envs/<env_name>/bin:$PATH WORKDIR / CMD ["/bin/bash"] NVIDIA Containers / Miniconda NVIDIA CUDA Image 一覧はこちらから Miniconda 一覧はこちらから
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go言語】ホットリロード可能なHTTPサーバのDocker環境構築手順

先日、こちらの記事でAirを利用したホットリロード可能なHTTPサーバの紹介をしました。 今回は『Air + Go言語』のDocker環境を構築する手順について紹介します。 今回利用するサンプルコード localhost:3000にアクセスするとレスポンスが返ってくるHTTPサーバをサンプルとして利用します。 main.go package main import ( "log" "net/http" ) func rootHander(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/html; charset=utf8") w.Write([]byte("こんにちは")) } func main() { http.HandleFunc("/", rootHander) log.Fatal(http.ListenAndServe(":3000", nil)) } 『Air + Go言語』のDocker環境の構築手順 Docker環境の構築手順について紹介します。 下準備: 作業ディレクトリの用意 作業ディレクトリとgo.modファイルの作成をします。 $ mkdir go-docker-example && cd $_ $ go mod init `basename $PWD` 各種ファイルの作成 Dockerfileファイルは以下の通りです。 Dockerfile FROM golang:1.16.3-buster # コンテナの作業ディレクトリにローカルのファイルをコピー WORKDIR /app COPY . /app # 必要なパッケージをインストール RUN go mod tidy # Airをインストール RUN go install github.com/cosmtrek/air@v1.27.3 # airコマンドでGoファイルを起動 CMD ["air"] docker-compose.ymlは以下の通りです。 HTTPサーバがlistenしているポートと、開放するコンテナのポートは一緒にする必要があります。(今回でいうところの3000番) docker-compose.yml version: '3' services: app: build: . ports: - '3030:3000' # ローカルの3030番ポートでコンテナの3000番ポートに接続 volumes: - .:/app # ローカルとコンテナのディレクトリをバインドマウント(同期) - go_path:/go # パッケージやバイナリファイルのインストール先($GOPATH)を永続化 volumes: go_path: 動作確認 コンテナ起動後、ローカルからcurlを実行してレスポンスが返ってくればOKです。 ### 作業ディレクトリへ移動 $ cd go-example ### バックグラウンドで起動 $ dokcer-compose up -d ### 接続の確認 $ curl localhost:3030 こんにちは 参考: 依存パッケージ不足でコンテナ起動が失敗する問題を解決する 上記で紹介したDocerfileとdocker-compose.ymlでは、go.modに不備があるとコンテナ起動に失敗します。 たとえば、以下のようにコードを修正したとします。 main.go package main import ( "log" "net/http" + "rsc.io/quote" ) func rootHander(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/html; charset=utf8") - w.Write([]byte("こんにちは")) + w.Write([]byte(quote.Hello()) } func main() { http.HandleFunc("/", rootHander) log.Fatal(http.ListenAndServe(":3000", nil)) } Goファイル修正後go mod tidyを実行せずにコンテナを起動すると、rsc.io/quoteがgo.modに追加されていないため失敗します。 $ docker-compose up Creating air_app_1 ... done Attaching to air_app_1 app_1 | app_1 | __ _ ___ app_1 | / /\ | | | |_) app_1 | /_/--\ |_| |_| \_ , built with Go app_1 | app_1 | watching . app_1 | !exclude tmp app_1 | building... app_1 | main.go:7:2: no required module provides package rsc.io/quote; to add it: app_1 | go get rsc.io/quote app_1 | failed to build, error: exit status 1 go mod tidyをコンテナ起動時、つまりairコマンドの直前に実行することで、この問題が解決できます。 start.sh #!/bin/bash -eu go mod tidy air start.shを利用してコンテナを起動するようにdocker-compose.ymlを修正します。 docker-compose.yml version: '3' services: app: build: . ports: - '3030:3000' volumes: - .:/app - go_path:/go + command: ["./start.sh"] volumes: go_path: airコマンドの直前にgo mod tidyが実行されるようになったため、問題なくコンテナが起動できます。 $ docker-compose up Creating air_app_1 ... done Attaching to air_app_1 app_1 | go: finding module for package rsc.io/quote app_1 | go: downloading rsc.io/quote v1.5.2 app_1 | go: found rsc.io/quote in rsc.io/quote v1.5.2 app_1 | go: downloading rsc.io/sampler v1.3.0 app_1 | go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c app_1 | app_1 | __ _ ___ app_1 | / /\ | | | |_) app_1 | /_/--\ |_| |_| \_ , built with Go app_1 | app_1 | watching . app_1 | !exclude tmp app_1 | building... app_1 | running... さいごに 認識違いや補足があればコメントいただけるとうれしいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerメモ

Dockerメモ docker使ってみたのでメモ。あとはm1 macで詰まったところメモ。 Dockerとは 概要 仮想環境作れるやつ virtualboxなどとは少し違う(OSは元のを使用) その結果、動作が軽い 用語 イメージ: 元になる環境 コンテナ: イメージを基にして作った環境 コマンド docker ps : (実行中の)コンテナを表示 -aオプションで全て (停止中) のコンテナも表示 docker pull : docker hubにあるイメージをローカルにダウンロード e.g. docker pull ubuntu:20.04 この時m1 macの場合には、docker pull amd64/ubuntu:20.04 のように明示的にcpuアーキテクチャを指定可能 (指定しないとarm版がpullされて、利用できないライブラリとかあって面倒) docker images : ダウンロード済みのimageを表示 docker container run : イメージから新しいコンテナを作成して実行 -it オプション: コンテナ内で標準入力を受け付ける(& 擬似ターミナル) -p XXXX:XXXX : 「ホストOS側:コンテナ側」のポート番号を指定。jupyter notebookを使用した場合など -v: ホストOSとコンテナのディレクトリを接続 --name XXXX : 立ち上げるコンテナの名前を指定 ctrl + p, q : コンテナから抜ける(実行は継続) exit : コンテナから抜ける(コンテナは停止) docker attach <コンテナ名 (or name)> : 実行中のコンテナに再接続 docker start <コンテナ名 (or name)> : 停止中のコンテナを再実行 docker rm <コンテナ名>: コンテナの削除 docker rmi <イメージ名>: イメージの削除 その他 ubutntuイメージでpython & jupyter notebook環境 これをdockerfileなるものに書くと手実行不要 apt upgrade -y apt update -y apt install python3 apt install python3-dev python3-pip -y jupyter notebook --ip=0.0.0.0 もっと良い記事 いまさらだけどDockerに入門したので分かりやすくまとめてみた 【図解】Dockerの全体像を理解する 前編, 中編, 後編 Dockerコマンドメモ 【連載】世界一わかりみが深いコンテナ & Docker入門 dockerfileやdocker composeについても記載 今さら人に聞けない Kubernetes とは? 数時間で完全理解!わりとゴツいKubernetesハンズオン!! dockerfileとdocker-compose, Kubernetes (k8s) で何ができるか dockerfile: 作成したコンテナ上で実行する処理の自動化(dockerfileに書いてdocker build) docker-compose: 複数のコンテナからなるシステム構築 Kubernetes (k8s): 複数サーバー & 複数コンテナでの運用(通信・デプロイ順序・障害時の回復等色々)をいい感じにするシステム
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerメモ (1)

Dockerメモ docker使ってみたのでメモ。あとはm1 macで詰まったところメモ。 Dockerとは 概要 仮想環境作れるやつ virtualboxなどとは少し違う(OSは元のを使用) その結果、動作が軽い 用語 イメージ: 元になる環境 コンテナ: イメージを基にして作った環境 コマンド docker ps : (実行中の)コンテナを表示 -aオプションで全て (停止中) のコンテナも表示 docker pull : docker hubにあるイメージをローカルにダウンロード e.g. docker pull ubuntu:20.04 この時m1 macの場合には、docker pull amd64/ubuntu:20.04 のように明示的にcpuアーキテクチャを指定可能 (指定しないとarm版がpullされて、利用できないライブラリとかあって面倒) docker images : ダウンロード済みのimageを表示 docker container run : イメージから新しいコンテナを作成して実行 -it オプション: コンテナ内で標準入力を受け付ける(& 擬似ターミナル) -p XXXX:XXXX : 「ホストOS側:コンテナ側」のポート番号を指定。jupyter notebookを使用した場合など -v: ホストOSとコンテナのディレクトリを接続 --name XXXX : 立ち上げるコンテナの名前を指定 ctrl + p, q : コンテナから抜ける(実行は継続) exit : コンテナから抜ける(コンテナは停止) docker attach <コンテナ名 (or name)> : 実行中のコンテナに再接続 docker start <コンテナ名 (or name)> : 停止中のコンテナを再実行 docker rm <コンテナ名>: コンテナの削除 docker rmi <イメージ名>: イメージの削除 その他 ubutntuイメージでpython & jupyter notebook環境 これをdockerfileなるものに書くと手実行不要 apt upgrade -y apt update -y apt install python3 apt install python3-dev python3-pip -y jupyter notebook --ip=0.0.0.0 もっと良い記事 いまさらだけどDockerに入門したので分かりやすくまとめてみた 【図解】Dockerの全体像を理解する 前編, 中編, 後編 Dockerコマンドメモ 【連載】世界一わかりみが深いコンテナ & Docker入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

公開鍵1つに対して、実行可能コマンドを1つだけ紐付ける

scpコマンドをcronjobで定時実行させる ※「シス環系女子 season2 第3話」の備忘録です。 公開鍵1つに実行可能なコマンド列1つだけ設定しておけば、万が一秘密鍵が流出しても実行できるコマンドは設定したコマンド1つだけなので安心できる。 そのやり方の記事。 以下記事よりssh接続可能なコンテナ2つを前もって準備しておくこと。 https://qiita.com/neo_fukafukafukka/items/34ece05e14547d847450 crontabによる自動scpが失敗するケース 以下のscpコマンドを、手動で実行するだけなら成功する。 # scp -P 2222 root@172.17.0.2:/root/master_file.txt ./ master_file.txt 100% 15 2.3KB/s 00:00 ※便宜上、上記コマンドを以下のようにシェルスクリプトにまとめておく。 #!/bin/bash scp -P 2222 root@172.17.0.2:/root/master_file.txt ./ 以下のようなソースで定時に自動でscp実行させようとすると失敗するケースがある。 // ※crontabs入れてなかったので入れる。 # yum -y install crontabs # crontab -l MAILTO="" */2 * * * * /bin/bash get_master_file.sh // cronを起動する # systemctl start crond // 秘密鍵にパスフレーズがある場合は、いつまで経ってもmasterコンテナのファイル(master_file.txt)が取得できない。(crontabが失敗してる。) 対策 秘密鍵にパスフレーズを入力せず、公開鍵にコマンド列1つを関連づける。 ※コマンド列は1つしか関連付けられない。 公開鍵にコマンド列1つを関連づける slaveコンテナ内で公開鍵を新しく生成・コピーする。 # mkdir .ssh/for_get_master_file # cd .ssh/for_get_master_file/ # ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/for_get_master_file/id_rsa Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/for_get_master_file/id_rsa. Your public key has been saved in /root/.ssh/for_get_master_file/id_rsa.pub. The key fingerprint is: // 省略 # vi id_rsa.pub // 公開鍵をコピー 上記でコピーした公開鍵を、masterコンテナ内のauthorized_keysに追加する。 # vi authorized_keys // slave側の公開鍵を既存の公開鍵の下にペーストする // 以下のように「echo hoge」だけ使えるように設定してみる。 ssh-rsa AAAAB3Nza~~~省略~~~14G06GK7 root@6b3305967d37 command="echo hoge" ssh-rsa AAAAB3Nza~~~省略~~~4BgA/iOol root@6b3305967d37 すると以下のようにslaveコンテナ側ではcrontabがいつまで経っても成功しない。(master_file.txtが取得できない。) # ls anaconda-ks.cfg get_master_file.sh 次にslaveコンテナ側の公開鍵に対応するコマンドを以下のように修正してみる。 # vi authorized_keys // slave側の公開鍵をペーストする // 以下のように「/bin/bash get_master_file.sh」の処理の中身(scpコマンド)を使えるように設定してみる。 ssh-rsa AAAAB3Nza~~~省略~~~14G06GK7 root@6b3305967d37 command="scp -f /root/master_file.txt" ssh-rsa AAAAB3Nza~~~省略~~~4BgA/iOol root@6b3305967d37 すると以下のようにslaveコンテナ内で、masterコンテナのmaster_file.txtが取得できていることが確認できる。(crontabが実行できた。) # ls anaconda-ks.cfg get_master_file.sh master_file.txt なぜ公開鍵へ関連付けたコマンドが「scp -f /root/.ssh/master_file.txt」なのか? master側で実行されているscpコマンドの実態が「scp -f /root/.ssh/master_file.txt」だから。 slaveコンテナからmasterコンテナに対してscp実行した時、実はmaster側でもscpが実行されていて、それが上記のコマンド。 設定するのはあくまでサーバー側で実行されるコマンドであることに注意。(「scp -P 2222 root@172.17.0.2:/root/master_file.txt ./」を設定するのではないことに注意。)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker+Seleniumによるブラウザテストでエラー( Selenium::WebDriver::Error::WebDriverError: tab crashed )

開発環境 Ruby 2.7.2 Docker 3 RSpec Selenium 背景 seleniumによるchromeのブラウザテストで、Ajaxによるボタン押下処理の自動テストを作成していました。 発生したエラー Failures: 1.1) Failure/Error: first("input[value='ログイン']").click Selenium::WebDriver::Error::WebDriverError: tab crashed (Session info: chrome=89.0.4389.82) beforeで記述したログイン処理にてエラーが起きました。 頻度としては2回に1回程度で、ブラウザテストの不安定さによるものが原因と考えられました。 調査したところ、メモリーが切れた場合に、生じるエラーとのことでした。 https://bugs.chromium.org/p/chromedriver/issues/detail?id=1884 解決策 docker環境で確保されるメモリサイズが小さいことが原因と予想しました。 docker-compose.ymlからメモリーサイズを変えられることがわかったため、下記のように修正しました。 docker-compose.yml chrome: image: selenium/standalone-chrome ports: - "4444:4444" shm_size: '2gb' #追加 結果 無事に治りました。 これでも治らない場合は、以下の対応が考えられます。 ① メモリサイズを2gbよりも大きく変更 ② 表示ブラウザの画面サイズを小さくする設定に変更 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テストサーバ立ててサブドメイン割り当ててBasic――ではなくSAML認証でSSO(Keycloak)に組み込む

概要 Apacheのmod_auth_mellonモジュールとKeycloakを使用し、静的WebサイトやCMSテンプレート等の開発でありがちな「サブドメインを割り当てた案件/用途別テストサーバ」をSAML認証でSSOに組み込む。 本稿ではサブドメイン毎に個別のSP(サービスプロバイダ、Keycloakにおけるクライアント)に分割せず、複数のサブドメインを一つのSPで扱う。これにより運用フェーズにおいてKeycloakのクライアント設定の操作が不要になり1、設定手順をBasic認証並に簡略化できる。 アクセス許可の条件は「アカウントを持っているかどうか」からSAML属性と正規表現を使った複雑なものまで柔軟に設定可能 条件はApacheのLocationディレクティブ毎に指定できる サブドメインの追加はApache側の設定だけで完結 SAML属性はApacheの環境変数に入る 作業環境 RHEL8互換 Keycloak (12.0.4) 稼働中のもの Apache (2.4.46) Docker公式イメージ(Debianベース)、非SSL(リバースプロキシでSSLオフロードする構成のため) libapache2-mod-auth-mellon (0.17.0-1) Docker (20.10.6), docker-compose (1.29.1) ファイル構成 あくまで一例として。confや配信ファイルの配置はお好みで。カッコ内は対応する手順を示す。 httpd |-- Dockerfile (1-1) |-- docker-compose.yml (1-2) `-- mount |-- httpd.conf |-- conf.d | `-- mellon.conf (1-4) |-- mellon | |-- <CLIENT_ID>.cert (1-3) | |-- <CLIENT_ID>.key (1-3) | |-- <CLIENT_ID>.xml (1-3) | |-- client-cert.pem (2-2) | `-- idp-metadata.xml (2-2) `-- vhost.d |-- include | `-- mellon.conf (3-1) |-- metadata | |-- subdomain1.xml (3-2-1) | |-- subdomain2.xml (3-2-1) | `-- subdomain3.xml (3-2-1) |-- subdomain1.conf (3-2-2) |-- subdomain2.conf (3-2-2) `-- subdomain3.conf (3-2-2) 1. Apache mod_auth_mellonモジュールが動けばなんでもよい。 1-1. Dockerイメージ Apacheの公式イメージには mod_auth_mellon モジュールが入っていないため、latestタグ(Debianベース)を元にモジュールをインストールするだけのDockerfileを用意する。 Dockerfile FROM httpd:latest RUN apt-get update RUN apt-get install -y libapache2-mod-auth-mellon RUN apt-get clean パッケージ詳細 1-2. Composeファイル 設定ファイルと配信するファイルをマウントして起動するだけのもの。 docker-compose.yml version: '3.8' services: httpd: image: <DOMAIN>/httpd:<VERSION> build: context: . container_name: httpd hostname: httpd-server environment: - TZ=Asia/Tokyo volumes: - ./mount/httpd.conf:/usr/local/apache2/conf/httpd.conf - ./mount/conf.d:/usr/local/apache2/conf.d - ./mount/vhost.d:/usr/local/apache2/vhost.d - ./mount/mellon:/usr/local/apache2/mellon - /var/www/html:/var/www/html expose: - '80' restart: always networks: - container-link logging: options: max-size: "10m" max-file: "3" networks: default: external: name: bridge container-link: name: docker.internal <DOMAIN> は自家製イメージだとわかる文字列を適当に設定すればよい <VERSION> もなんでも構わないが、その時点のApacheのバージョンを入れておくと更新の確認に便利 コンテナ間リンク上のリバースプロキシでSSLオフロードしているため非SSLかつ外向きには閉じている 単独動作時は expose を適宜 ports に読み替えのこと 1-3. 証明書・鍵・メタデータ Apacheのコンテナ上(または依存関係をインストールした環境)でmod_auth_mellon公式が用意している生成用シェルスクリプト mellon_create_metadata.sh を実行する。 以下はコンテナに入って生成して取り出す場合の手順。 shell docker exec -it httpd /bin/bash # ここからApacheコンテナ内 apt-get install -y wget cd /tmp wget https://raw.githubusercontent.com/latchset/mod_auth_mellon/master/mellon_create_metadata.sh chmod +x mellon_create_metadata.sh ./mellon_create_metadata.sh <CLIENT_ID> https://*.<DOMAIN>/mellon exit # Apacheコンテナ内ここまで docker cp httpd:/tmp/<CLIENT_ID>.key /path/to/httpd/mount/mellon docker cp httpd:/tmp/<CLIENT_ID>.cert /path/to/httpd/mount/mellon docker cp httpd:/tmp/<CLIENT_ID>.xml /path/to/httpd/mount/mellon <CLIENT_ID> はSP名(Keycloakにおける「クライアントID」) / や : などが含まれる場合、ファイル名ではアンダースコアに変換される( mellon_create_metadata.sh の出力するログを参照のこと) <DOMAIN> は使用するドメイン 生成されるファイルは以下の通り。 <CLIENT_ID>.key : SP鍵, <CLIENT_ID>.cert : SP証明書 mod_auth_mellonが使用する <CLIENT_ID>.xml : SPメタデータ Keycloakのクライアント設定にインポートする サブドメイン毎にコピーし一部を書き換えたものをmod_auth_mellonが使用する 1-4. mod_auth_mellonグローバル設定 LoadModule でモジュールを読み込み、サーバ共通のグローバル設定を行う。 モジュール公式リポジトリのReadmeで大半を占めているサンプル設定の記述の内、上の方にある「Global configuration」の部分に項目と説明が記載されている。 mellon.conf LoadModule auth_mellon_module /usr/lib/apache2/modules/mod_auth_mellon.so <IfModule auth_mellon_module> # MellonCacheSize 100 # MellonCacheEntrySize 196608 # MellonLockFile "/var/run/mod_auth_mellon.lock" # MellonPostDirectory "/var/cache/mod_auth_mellon_postdata" # Default: None # MellonPostTTL 900 # MellonPostSize 1048576 # MellonPostCount 100 # MellonDiagnosticsFile logs/mellon_diagnostics # MellonDiagnosticsEnable Off </IfModule> 本稿では httpd.conf でimportする想定(各自のポリシーに沿って適宜読み替えのこと) サーバ共通の設定であり、ディレクティブの外に記述する 特にデフォルト値から変更する必要はないと思われる 利用人数が多い場合に MellonCacheSize の調整が必要になる程度? 今回の導入方法ではDiagnostics関連の設定項目は存在しているだけでエラーを吐く 依存関係がインストールされていないため 2. Keycloak Keycloakの公式ドキュメントにmad_auth_mellonの導入ガイドがあるので参考に。 2-1. クライアント設定 使用するレルムのクライアント設定の権限を持つユーザーで管理コンソールにログインし、サイドメニューから「クライアント」→右側の「作成」ボタンと進む 「ファイルを選択」ボタンを押し、ファイル選択ダイアログで手順1-3で作成した <CLIENT_ID>.xml を選択してから「保存」(クライアントIDは変更不可) 「有効なリダイレクトURI」を * に変更する 「SAMLエンドポイントの詳細設定」アコーディオン配下の入力欄を全て空にする 「保存」する 修正後の設定内容 2-2. メタデータと証明書の取得 手順2-1を完了したら「インストール」タブに移動する フォーマット・オプションで「Mod Auth Mellon files」を選択 出現した「ダウンロード」ボタンを押してzipファイルをダウンロードする zipに含まれる client-cert.pem と idp-metadata.xml をApacheで使用する 3. VirtualHost サブドメイン(≒VirtualHost)の追加時に必要な作業はこの項の 3-2 のみで、Keycloakを触る必要はない。(アクセス制御のためにマッパーやユーザー情報を弄る必要がある場合は別として) 3-1. mod_auth_mellon共通設定 VirtualHostにおける共通の設定項目。これまでの手順で取得したファイル類のうち、共通で読み込むものの指定。 mellon.conf MellonSPPrivateKeyFile /usr/local/apache2/mellon/<CLIENT_ID>.key MellonSPCertFile /usr/local/apache2/mellon/<CLIENT_ID>.cert MellonIdPMetadataFile /usr/local/apache2/mellon/idp-metadata.xml MellonIdPCAFile /usr/local/apache2/mellon/client-cert.pem 本稿では各VirtualHostのconfでimportする想定(各自のポリシーに沿って適宜読み替えのこと) 詳細はモジュール公式リポジトリのReadmeを参照のこと 3-2. 各VirtualHostの設定 3-2-1. mod_auth_mellon用メタデータ 手順1-3で取得したメタデータ <CLIENT_ID>.xml をコピーし、使用するサブドメインに合わせて <SUBDOMAIN> の部分を修正する。 SUBDOMAIN.xml <EntityDescriptor entityID="<CLIENT_ID>" xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="true"> <KeyDescriptor use="signing"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:X509Data> <ds:X509Certificate>MIID......</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </KeyDescriptor> <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://<SUBDOMAIN>.<DOMAIN>/mellon/logout"/> <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://<SUBDOMAIN>.<DOMAIN>/mellon/postResponse" index="0"/> </SPSSODescriptor> </EntityDescriptor> 3-2-2. conf 概ね AuthType Basic が AuthType Mellon になるだけで、他は普段通りでよい。 SUBDOMAIN.conf <VirtualHost *:80> DocumentRoot /var/www/html/<SUBDOMAIN> ServerName https://<SUBDOMAIN>.<DOMAIN> <Location /> AllowOverride none # ここからmod_auth_moellonの設定 Include vhost.d/include/mellon.conf MellonSPMetadataFile vhost.d/metadata/<SUBDOMAIN>.xml AuthType Mellon MellonEnable auth Require valid-user </Location> </VirtualHost> この設定ではルート配下が全てアクセス制御の対象となり、Keycloak上にアカウントが存在していれば閲覧できる ServerName には実際にアクセスする時のプロトコルを含める プロトコルを省略するとmod_auth_mellonがリダイレクトURLを生成する際にApacheの設定を元に補完されるが、本稿の構成ではApacheは非SSLのため、SSLの表側と一致せずエラーになる 設定項目の詳細はモジュール公式リポジトリのReadmeを参照のこと 4. アクセス制御 4-1. Keycloak側(マッパー) アクセス制御に使用するKeycloakのユーザー情報(プロパティ・属性・グループ・ロール等)は、Keycloakのクライアント設定でマッパーを作成して受け渡す。 SAMLとKeycloakの仕様の範囲内であればなんでも渡せて、JavaScriptマッパーで加工もできるため、mod_auth_mellon側の制限や機能も勘案しつつ使いやすい形でフォーマットを整える。 例 「閲覧可能なサブドメインをカンマ区切りテキストで指定する」という運用ルールを想定した subdomain 属性をユーザー属性に追加。 subdomain という属性名で送信するマッパーを作成。 認証成功時にmod_auth_mellonがこのSAML属性を受け取ると、内容がApache環境変数 MELLON_subdomain に格納される。mod_auth_mellonのディレクティブからは属性名のまま扱える。(環境変数の接頭辞は変更可能) ※mod_auth_mellon側での使用例は後述 4-2. Apache側(conf) mod_auth_mellonの MellonRequire と Melloncond ディレクティブを使用する。 4-2-3. MellonRequireディレクティブ 「特定のSAML属性の値が1個以上の列挙した値のいずれかに一致するかどうか」による判定を行う。 MellonRequire <SAML_ATTR> <VALID_VALUE_1> [<VALID_VALUE_2> ...] MellonRequireは複数記述することができる 複数のMellonRequireはAND条件として処理される 同じ属性名を指定したMellonRequireが複数ある場合は最後に記述されたものが使用される 例えば「属性名 role の値が admin か moderator なら許可する」であれば以下のようになる。 MellonRequire "role" "admin" "moderator" 4-2-2. MellonCondディレクティブ 特定のSAML属性の値と、特定の値ないし正規表現との一致・不一致その他による判定を行う。 MellonCond <SAML_ATTR> <VALUE> [[<OPTION>, ...]] MellonCondは複数記述することができる オプションは大括弧で囲う 詳しくはモジュール公式リポジトリのReadmeを参照のこと VALUEで展開できる変数 %n, %{num} 正規表現使用時のマッチ結果の参照、1桁は %n 、2桁以上は %{num} %{ENV:env_name} Apache環境変数の展開 % をエスケープしたい場合は %% とする オプション OR そのMellonCondの評価結果がfalseだった場合、次のMellonCondを評価する NOT 評価結果を反転する(値と一致しない場合にtrueを返す) SUB 一致条件を部分一致にする REG VALUEを正規表現として扱い評価する(正規表現はApacheの LocationMatch 互換?) NC 大文字・小文字を区別しない MAP MellonSetEnvによってリマップされた属性名を探して判定する?(詳細不明) REF REGと同時に使用し、次のMellonCondにおけるマッチ結果の参照に今回のマッチ結果を利用する?(詳細不明) 例 手順4の例で設定したカンマ区切りテキストを格納しているSAML属性 subdomain に、 asdf-preview に一致するフィールドが含まれていたらアクセスを許可する。 MellonCond "subdomain" "(^|,)asdf-preview(,|$)" [REG] Tips Apache側の認証用エンドポイント 標準では /mellon/* が各種エンドポイントとして使用されるため、/mellon とその配下に実体ファイルを置くことはできない。 変更する場合は MellonEndpointPath ディレクティブを使用し、手順3-2-1のmod_auth_mellon用メタデータ内のエンドポイントを合わせる。 参考 One Service Provider and multiple Apache VirtualHosts? 単一SPマルチサブドメイン行けそうかも、と思った発端 mod_auth_mellon を使ってみた 【Keycloak】Apache の VirtualHost で分けられた複数のサイトをまとめてシングルサインオンしよう 操作で事故ると甚大な被害が生じる可能性がある ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者の0から始めるDocker生活[Ubuntu18.04LTS, MySQL編 feat.Windows WSL2]

はじめに こんちゃー 前回は,Dockerでpythonのコンテナを建てました. まぁ,pythonのコンテナ建てたとこでつまんないんで,今回はUbuntuのコンテナを建てて運用していきます. それじゃ,れっつごー 環境 OS: Windows10 Pro CPU: intel Corei9-9900k GPU: Nvidia GeForce RTX 2080 Super RAM: 32GB DockerFile DockerFileでやります. 僕はこれ以外やり方を知らないので,これでやりますが他にいい方法がありそうなので,ggってみてください. SQLも入れてますが,後の記事で説明するので,いらん人は,消しちゃってください. (注意) userの部分は,自分なりのusernameにしてください. このままやると,userというusernameになってしまいます. パスワード設定で,passとなっている部分は,ubuntuのコンテナ内で使用するパスワードです.  (後述するSQLのパスワードではありません.) # ========= FROM ubuntu:18.04 RUN apt-get update RUN apt-get upgrade -y RUN apt-get install python3 python3-pip -y RUN apt-get install -y software-properties-common RUN apt-get install curl emacs wget sudo -y --fix-missing # rootだと色々と不便なので,ユーザーを作成 RUN useradd -m user # ルート権限を付与 RUN gpasswd -a user sudo # パスワード設定 passの部分も編集してください. RUN echo 'user:pass' | chpasswd WORKDIR /home/user/app COPY --chown=user:user app/ /home/user/app RUN pip3 install PyMySQL #===========SSH============= #sshは今回は使用していません.以下3行は無くても大丈夫です. RUN apt-get install -y openssh-server RUN mkdir /var/run/sshd EXPOSE 22 #==========emacs========== RUN echo "(setq make-backup-files nil)" >> ~user/.emacs RUN echo "(set-default-coding-systems 'utf-8-unix)" >> ~user/.emacs RUN echo "export LC_CTYPE='C.UTF-8'" >> ~user/.bashrc RUN . ~user/.bashrc RUN chown user:user /home/user/.emacs #chown -R user ~/.emacs.d/ #===========sql============= RUN apt-get install mysql-server mysql-client -y --fix-missing RUN apt-get install --reinstall systemd -y RUN systemctl enable mysql こんな感じですね.(自分を沼に沈めた人のgistをいじくってます.) コンテナを建ててゆくぅ なんだかんだとDockerFileができましたら,まずはイメージ(型)を作成していきましょう. docker build -f ./Dockerfile -t イメージ名 . で,イメージができましたら,run(コンテナ生成)していきましょ. localhost(127.0.0.1)の後の3306:3306は,データの入口と出口(田口)のポートを指定しています. このあとやるSQLいじりの時に使うので,入れといて損はないと思います. userの部分を自分のusernameに変更してください. コンテナの中でGPUを使用したい方は,3行目を使用してください. docker run -itd -p 127.0.0.1:3306:3306 -u user --name コンテナ名 イメージ名 #docker上でgpu動かす docker run -itd --gpus all -p 127.0.0.1:3306:3306 -u user --name コンテナ名 イメージ名 できたら,コンテナの中に入っていきましょう. docker exec -i -t コンテナ名 bash で入れると,OKです. 補足 SQLをやりたい場合は,コンテナの中に入って以下の文を1行ずつ入れていきましょう. #動いているか確認 sudo systemctl status mysql #有効化 sudo systemctl enable mysql #起動 sudo /etc/init.d/mysql start #セキュリティの設定 sudo mysql_secure_installation 全部yes #SQLに入る sudo mysql -u root -p #SQL内で設定 #ユーザー一覧の確認 SELECT user,authentication_string,plugin,host FROM mysql.user; #rootパスワードを変更(PASSの部分を自分のパスワードに変更してください.SQLに入るときのパスワードになります) ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'PASS'; #設定の反映 FLUSH PRIVILEGES; 最後に 取り急ぎ作ったんでボロがありますので,適宜検索等をしながら進めていただけると幸いです. このままやってみて,動かない等ありましたら連絡いただけますと助かります. 次は初心者SQL編でお会いしましょう.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

備忘録!Windows Home でも簡単にDocker環境を作る

今や開発では欠かせないDockerについて、この記事で簡単に解説します。 基本をひととおり学んだ後に、実際にWinsows Homeで環境開発を構築していきます。 以前はDockerをWindows HomeではHyper-Vが使えずでDocker Toolboxが主な選択でしたが、WSL2、Hyper-V対応と進みDocker Desktopが使えるようになりました。 今ではHomeでも簡単にDockerを導入できます。 Dockerは開発者なら必ず全員知っておくべき内容です 開発環境 OS : Windows 10 Home Docker Desktop for Windows 3.0.0 Dockerとは? 「コンテナ型」の仮想化サービスを提供しています。 非常に軽量なコンテナ型の実行環境です。 Dockerについて詳しくは別記事で解説します Dockerのメリット 開発が効率的になり早くなる インストール手順 1.Docker Desktopをインストールする Docker Desktop Installer ダウンロード 上記よりダウンロードしたDocker Desktop Installer.exeを実行します ※チェックはデフォルトのままでOK インストールの終了後に再起動します 2. WSL2 インストール追加作業 上記の手順でWindowsを再起動すると「WSL 2 installation is incomplete.」という次のダイアログが表示されます。 WSL2のインストールで追加の作業が必要です。 ダイアログをそのままにしてリンクを開きます。 リンク先から[x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ]をダウンロードして実行します 3. Dockerを再起動して完了 ダイアログのRestartをクリックしてください。 特に問題がなければこれでインストール作業は完了です。 エラー時の確認 下記によりDockerの起動時にエラーがでるので注意してください BIOSの仮想化機能が有効になっていない場合 "レガシーコンソールモード(従来のコンソール)"にチェックがはいっている場合 Dockerをはじめて体験してみる hello-worldコンテナを実行します docker runコマンドでイメージを取得、コンテナの実行をしてみましょう。 powershell >docker run hello-world 現在のイメージとコンテナを表示して確認してみます。 hello-worldイメージが確認できるはずです powershell >docker images 起動中のコンテナを確認します 何も入っていないでしょう (hello-worldコンテナはメッセージ出力後に終了します) powershell >docker ps オプションに-aをつけることで停止中のコンテナが表示されます。 powershell >docker ps -a コンテナを削除します powershell >docker rm [コンテナID] つづいてイメージを削除します powershell >docker [イメージ名] これでhello-worldのチュートリアルを完了します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

備忘録!Windows Home でもDocker環境をつくる

今や開発では欠かせないDockerについて、この記事で簡単に解説します。 基本をひととおり学んだ後に、実際にWinsows Homeで環境開発を構築していきます。 以前はDockerをWindows HomeではHyper-Vが使えずでDocker Toolboxが主な選択でしたが、WSL2、Hyper-V対応と進みDocker Desktopが使えるようになりました。 今ではHomeでも簡単にDockerを導入できます。 Dockerは開発者なら必ず全員知っておくべき内容です 開発環境 OS : Windows 10 Home Docker Desktop for Windows 3.0.0 Dockerとは? 「コンテナ型」の仮想化サービスを提供しています。 非常に軽量なコンテナ型の実行環境です。 Dockerについて詳しくは別記事で解説します Dockerのメリット 開発が効率的になり早くなる インストール手順 1.Docker Desktopをインストールする Docker Desktop Installer ダウンロード 上記よりダウンロードしたDocker Desktop Installer.exeを実行します ※チェックはデフォルトのままでOK インストールの終了後に再起動します 2. WSL2 インストール追加作業 上記の手順でWindowsを再起動すると「WSL 2 installation is incomplete.」という次のダイアログが表示されます。 WSL2のインストールで追加の作業が必要です。 ダイアログをそのままにしてリンクを開きます。 リンク先から[x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ]をダウンロードして実行します 3. Dockerを再起動して完了 ダイアログのRestartをクリックしてください。 特に問題がなければこれでインストール作業は完了です。 エラー時の確認 下記によりDockerの起動時にエラーがでるので注意してください BIOSの仮想化機能が有効になっていない場合 "レガシーコンソールモード(従来のコンソール)"にチェックがはいっている場合 Dockerをはじめて体験してみる hello-worldコンテナを実行します docker runコマンドでイメージを取得、コンテナの実行をしてみましょう。 powershell >docker run hello-world 現在のイメージとコンテナを表示して確認してみます。 hello-worldイメージが確認できるはずです powershell >docker images 起動中のコンテナを確認します 何も入っていないでしょう (hello-worldコンテナはメッセージ出力後に終了します) powershell >docker ps オプションに-aをつけることで停止中のコンテナが表示されます。 powershell >docker ps -a コンテナを削除します powershell >docker rm [コンテナID] つづいてイメージを削除します powershell >docker [イメージ名] これでhello-worldのチュートリアルを完了します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Aerospikeことはじめ ~DockerイメージでAerospikeをローカル環境構築~

はじめに お仕事でAerospikeを使うことになったので勉強めも? まずはAerospikeとはなんぞやを知ることとと ローカル環境で簡単に動作確認するところまで。 Aerospikeとは? 概要 高速な分散KVS型のNoSQLデータベース。 SSDに最適化されてるから早いのとスケーラビリティの機能があるよというようなことがウリらしい。 【引用】 Aerospike概要から Aerospikeユースケースから また、MemcacheやRedisと比較すると、Aerospikeにはクラスタリング機能が組み込まれており、高性能なフラッシュストレージ(SSD)を使用できます。ベンチマークによると、Aerospikeの単一サーバの速度はRedisやMemcacheの両方に匹敵することが示されていますが、Aerospikeには自動クラスタリングと透過的なリシャーディングの機能が含まれています。これにより、ノードを起動するだけで容量を追加できます。継続的なデフラグとデータ消去、Memcacheのようなチェック&セット操作により、使い慣れた、かつ、必要な機能が提供されます。 データ構造 公式ページ Data Modelより RDBに置き換えると下記のようなイメージらしい Aerospike RDB Namespace Database Set Table Record Row Bin Column とりあえず今回はローカルで動かすだけなので この程度の知識だけ押さえておく。 インストール環境 ソフトウェア バージョン OS Windows10 Docker Engine 20.10.5 インストール 1. DockerイメージDL 今回はAerospike公式が配布しているDockerイメージをDLして構築する。 Aerospike公式DockerHub 以下が無料のCommunityEditionっぽいのでdocker pull。 コマンド docker pull aerospike:ce-5.5.0.9 実行結果 ce-5.5.0.9: Pulling from library/aerospike 62deabe7a6db: Pull complete 6b52c9d72afa: Pull complete 6d38b2c09d49: Pull complete 7bcce1b84cb4: Pull complete Digest: sha256:a26a8b17bded550b130952bc012d435e52326de11fbe003f7e2dbc4d42006e9d Status: Downloaded newer image for aerospike:ce-5.5.0.9 docker.io/library/aerospike:ce-5.5.0.9 docker imagesで以下のaerospikeがあればOK。 DockerイメージDL確認 >docker images REPOSITORY TAG IMAGE ID CREATED SIZE aerospike ce-5.5.0.9 76214490c04c 2 weeks ago 195MB 2. Aerospike起動 以下のdocker runコマンドで起動。 EnterpriseEditionならFEATURE_KEY_FILEなるものが必要だが、CommunityEditionは不要。 起動コマンド docker run -d -v DIR:/opt/aerospike/etc/ --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 aerospike:ce-5.5.0.9 docker psでaerospikeが動いてたらOK。 起動確認 >docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8064986acda1 aerospike:ce-5.5.0.9 "/usr/bin/dumb-init …" 16 minutes ago Up 16 minutes 0.0.0.0:3000-3002->3000-3002/tcp, 3003/tcp aerospike 3. Aerospike動作確認 以下のコマンドでコンテナにログイン ログインコマンド docker exec -it aerospike /bin/bash Keyを指定してデータの登録、参照などをするだけなら ascliというCLIを使えばシンプルなコマンドで確認できるのだが、このDockerイメージに入ってなかった?ので その代わりにSQLのようなAerospike独自のクエリ言語AQL(Aerospike Query Language)を使用する。 コンテナ内でaqlコマンドを叩けばaqlプロンプトモードになる。 aqlコマンド実行 # aql Seed: 127.0.0.1 User: None Config File: /etc/aerospike/astools.conf /root/.aerospike/astools.conf Aerospike Query Client Version 5.0.1 C Client Version 4.6.17 Copyright 2012-2020 Aerospike. All rights reserved. aql> AQLの各クエリ構文を知りたかったら、 aqlプロンプトモードでHELP 〇〇〇を叩けば説明文が出る。 例えば、INSERTクエリを知りたければ下記の通り。 HELP文実行 aql> HELP INSERT DML INSERT INTO <ns>[.<set>] (PK, <bins>) VALUES (<key>, <values>) DELETE FROM <ns>[.<set>] WHERE PK = <key> TRUNCATE <ns>[.<set>] [upto <LUT>] <ns> is the namespace for the record. <set> is the set name for the record. <key> is the record's primary key. <bins> is a comma-separated list of bin names. <values> is comma-separated list of bin values, which may include type cast expressions. Set to NULL (case insensitive & w/o quotes) to delete the bin. <LUT> is last update time upto which set or namespace needs to be truncated. LUT is either nanosecond since Unix epoch like 1513687224599000000 or in date string in format like "Dec 19 2017 12:40:00". ~省略~ こんな感じでメチャクチャ長い説明文が出てくる。 他のクエリのHELPコマンドは下記を参照。 https://docs.aerospike.com/docs/tools/aql#starting-aql-and-running-commands とりあえず、挿入・参照・削除をしてみる。 挿入 [構文] INSERT INTO <ns>[.<set>] (PK, <bins>) VALUES (<key>, <values>) 挿入例 aql> INSERT INTO test.NintenGames (PK, Name, Release) VALUES ('SMB', 'SuperMarioBros', 1983) OK, 1 record affected. aql> INSERT INTO test.NintenGames (PK, Name, Release) VALUES ('LOZ', 'LegendOfZelda', 1986) OK, 1 record affected. aql> INSERT INTO test.NintenGames (PK, Name, Release) VALUES ('PKM', 'Pockemon', 1998) OK, 1 record affected. 参照 [構文] SELECT <bins> FROM <ns>[.<set>] WHERE PK = <key> 参照例 aql> SELECT Name, Release FROM test.NintenGames WHERE PK = 'LOZ' +-----------------+----------+ | Name | Release | +-----------------+----------+ | "LegendOfZelda" | 1986 | +-----------------+----------+ 1 row in set (0.000 secs) OK 削除 [構文] DELETE FROM <ns>[.<set>] WHERE PK = <key> 削除例 aql> DELETE FROM test.NintenGames WHERE PK = 'PKM' OK, 1 record affected. aql> SELECT Name, Release FROM test.NintenGames +------------------+----------+ | Name | Release | +------------------+----------+ | "LegendOfZelda" | 1986 | | "SuperMarioBros" | 1983 | +------------------+----------+ 2 rows in set (0.094 secs) OK おぉ~できた!? これだけならRBDにSQLクエリを投げてるのと同じ感覚。 上記クエリ文中のtestがNamespaceで、インストール時のデフォルトのもの。 以下の設定ファイルで確認、および各種設定の追加・変更などができる。 /etc/aerospike/aerospike.conf ちなみにquitコマンドでaqlプロンプトから抜けられる。 ひとまず今日はインストールまでなのでここまで。 次回はプログラムからAerospikeにアクセスしてみたいところ。 参考 https://aerospike.com/jp/home/ https://docs.aerospike.com/docs/tools/aql/#starting-aql-and-running-commands https://blog.idcf.jp/entry/2016/08/10/122055 https://recruit.gmo.jp/engineer/jisedai/blog/introducing-high-speed-kvs-aerospike-features-and-user-cases/ https://tech-blog.fancs.com/entry/aerospike-introduction https://qiita.com/amotz/items/b8f52e9e09bce1ddea6b
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語でtailコマンドを作ってみる

Go言語でtailコマンドを作ってみる 最近Go言語を勉強し始めたが、その勉強の一環でtailコマンドをGo言語で作ってみる。 実装から、testingを用いたユニットテスト、GitHub Actionsを使ったCI/CDまでをやってみたいと思う。 バージョンアップなどにより、手順が異なる可能性があるので注意してください 流れ DockerによるGoの開発環境構築 マルチステージビルドの実現 大まかな仕様を考えてみる 実装 ユニットテスト テストカバレッジの確認 GitHub ActionsによるCI/CD(Go,Docker) 開発環境 macOS BigSur 11.2.3(20D91) Docker Desktop for Mac 20.10.5, build 55c4c88 DockerによるGoの開発環境構築 まず、今までGoを使ったことがないので、今後のことも考えて、DockerによるGoの開発環境を構築したい。 Dockerについては以前の記事を参考にしてください。 https://qiita.com/k_yuda/items/c9f48dbbdff70302c698 Dockerのインストール Docker for Macをインストール 公式サイトからDockerのアカウントを作ってログインし、DockerHubからダウンロードしてインストールする。 https://hub.docker.com/editions/community/docker-ce-desktop-mac インストール後CLIで確認してみる。 $ docker -v Docker version 20.10.5, build 55c4c88 このようにバージョンが表示されたら完了。 試しにubuntuを使ってみる。 まず、testフォルダー等を作ってDockerfileを作成する。 $ mkdir test $ cd test $ echo "From ubuntu" > Dockerfile 次にビルドして、shellに入ってみます。 $ docker build -t test . $ docker run -it test bash $ cat /etc/os-release 無事に起動できればこのようなメッセージが表示されると思います。 NAME="Ubuntu" VERSION="20.04.2 LTS (Focal Fossa)" これでDockerの動作を確認できました。 このコンテナは不要なので一旦キャッシュを含めて削除しましょう。 $ docker system prune -a マルチステージビルドの実現 名前だけ聞くと難しそうなイメージですが、簡単にまとめると 本来複数のDockerfileが必要な場合に対して、一つのDockerfileから複数のイメージBuildができる Fromをトリガーとして作用させ、複数ステージの生成物を継承できるということ ということです、メリットとしては、複数のコンテナを一つのDockerfileで管理できることです。 マルチステージビルドの設定の前に、まずファイル構成を決めましょう。 local gotail ├── .github │ └── workflows │ └── go.yml ├── README.md ├── Dockerfile ├── Makefile ├── main.go ├── main_test.go ├── test.txt ├── cmd.sh └── covercheck.sh 次にマルチステージビルドにおける、コンテナー内のファイル構成を考えてみる。 コンテナ1:go stage1 go └── src ├── main.go ├── main_test.go ├── test.txt └── cmd.sh コンテナ2:alpine linux stage2 root ├── main ├── test.txt └── cmd.sh 上記のようなファイル構成のマルチステージビルドを実現するには次のように記述します。 /local/Dockerfile FROM golang:latest WORKDIR /go/src COPY main.go . COPY main_test.go . COPY test.txt . COPY cmd.sh . RUN go test main_test.go main.go -v RUN go build main.go FROM alpine:latest RUN apk --no-cache add ca-certificates && \ apk add bash WORKDIR /root COPY --from=0 /go/src/main . COPY --from=0 /go/src/test.txt . COPY --from=0 /go/src/cmd.sh . CMD ["./cmd.sh"] 以上でマルチステージビルドは完了です。 詳しくは公式サイトをみてください。 https://docs.docker.com/develop/develop-images/multistage-build/ 大まかな仕様を決める 今回はtailコマンドのデフォルトの動作と、-nオプションの機能を実装したいと思う。 tailとは ファイルの最終行から数行を表示するコマンド、 標準では10行を表示する。 tail オプション -n 出力する行数を指定する また、-nオプションを実現するために、FIFOアルゴリズムを使って実装する。 FIFOとは FIFOとは、First In First Outの略称です。 FIFOを用いることによって、全てのデータをメモリに格納することなくファイルの読み込みが可能になります。 Wikiに詳しく載っています。 https://ja.wikipedia.org/wiki/FIFO このアルゴリズムを使ってtailを実現します。 使用するライブラリ コマンド本体のライブラリ一覧 main.go import ( "bufio" "flag" "fmt" "math" "os" ) testのライブラリ一覧 main_test.go import ( "bufio" "fmt" "os" "reflect" "strconv" "testing" ) まず、初めはイメージしやすいように細かく実装します。 キューの初期化 init_queue func init_queue() ([]string, int) { queue := []string{} cursor := 0 return queue, cursor } エンキュー enqueue func enqueue(queue []string, value string) []string { queue = append(queue, value) return queue } デキュー dequeue func dequeue(queue []string) []string { queue = queue[1:] return queue } キューの取り出し show_queue func show_queue(queue []string, n int) []string { if len(queue) == n { for i := n; i > 0; i-- { if len(queue) != 0 { fmt.Println(queue[0]) } queue = dequeue(queue) } } else { for i := len(queue); i > 0; i-- { if len(queue) != 0 { fmt.Println(queue[0]) } queue = dequeue(queue) } } return queue } 一連の流れをtailとして定義 tail func tail(stream *os.File, err error, n int) []string { queue, cursor := init_queue() scanner := bufio.NewScanner(stream) for scanner.Scan() { if n < 1 { n = int(math.Abs(float64(n))) if n == 0 { n = 10 } } queue = enqueue(queue, scanner.Text()) if n-1 < cursor { queue = dequeue(queue) } cursor++ } return queue } 実行してみるとこのような感じになります。※ここではわかりやすいようにqueueを表示しています。 test.txtには1~100の連番が一行ずつ入っているファイルです。 bash for i in `seq 100` do for> echo $i >> test.txt main.go $ go run main.go test.txt [1] [1 2] [1 2 3] [1 2 3 4] [1 2 3 4 5] [1 2 3 4 5 6] [1 2 3 4 5 6 7] [1 2 3 4 5 6 7 8] [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9 10] [1 2 3 4 5 6 7 8 9 10 11] [2 3 4 5 6 7 8 9 10 11 12] [3 4 5 6 7 8 9 10 11 12 13] [4 5 6 7 8 9 10 11 12 13 14] [5 6 7 8 9 10 11 12 13 14 15] [6 7 8 9 10 11 12 13 14 15 16] [7 8 9 10 11 12 13 14 15 16 17] [8 9 10 11 12 13 14 15 16 17 18] [9 10 11 12 13 14 15 16 17 18 19] [10 11 12 13 14 15 16 17 18 19 20] [11 12 13 14 15 16 17 18 19 20 21] [12 13 14 15 16 17 18 19 20 21 22] [13 14 15 16 17 18 19 20 21 22 23] [14 15 16 17 18 19 20 21 22 23 24] [15 16 17 18 19 20 21 22 23 24 25] [16 17 18 19 20 21 22 23 24 25 26] [17 18 19 20 21 22 23 24 25 26 27] [18 19 20 21 22 23 24 25 26 27 28] [19 20 21 22 23 24 25 26 27 28 29] [20 21 22 23 24 25 26 27 28 29 30] [21 22 23 24 25 26 27 28 29 30 31] [22 23 24 25 26 27 28 29 30 31 32] [23 24 25 26 27 28 29 30 31 32 33] [24 25 26 27 28 29 30 31 32 33 34] [25 26 27 28 29 30 31 32 33 34 35] [26 27 28 29 30 31 32 33 34 35 36] [27 28 29 30 31 32 33 34 35 36 37] [28 29 30 31 32 33 34 35 36 37 38] [29 30 31 32 33 34 35 36 37 38 39] [30 31 32 33 34 35 36 37 38 39 40] [31 32 33 34 35 36 37 38 39 40 41] [32 33 34 35 36 37 38 39 40 41 42] [33 34 35 36 37 38 39 40 41 42 43] [34 35 36 37 38 39 40 41 42 43 44] [35 36 37 38 39 40 41 42 43 44 45] [36 37 38 39 40 41 42 43 44 45 46] [37 38 39 40 41 42 43 44 45 46 47] [38 39 40 41 42 43 44 45 46 47 48] [39 40 41 42 43 44 45 46 47 48 49] [40 41 42 43 44 45 46 47 48 49 50] [41 42 43 44 45 46 47 48 49 50 51] [42 43 44 45 46 47 48 49 50 51 52] [43 44 45 46 47 48 49 50 51 52 53] [44 45 46 47 48 49 50 51 52 53 54] [45 46 47 48 49 50 51 52 53 54 55] [46 47 48 49 50 51 52 53 54 55 56] [47 48 49 50 51 52 53 54 55 56 57] [48 49 50 51 52 53 54 55 56 57 58] [49 50 51 52 53 54 55 56 57 58 59] [50 51 52 53 54 55 56 57 58 59 60] [51 52 53 54 55 56 57 58 59 60 61] [52 53 54 55 56 57 58 59 60 61 62] [53 54 55 56 57 58 59 60 61 62 63] [54 55 56 57 58 59 60 61 62 63 64] [55 56 57 58 59 60 61 62 63 64 65] [56 57 58 59 60 61 62 63 64 65 66] [57 58 59 60 61 62 63 64 65 66 67] [58 59 60 61 62 63 64 65 66 67 68] [59 60 61 62 63 64 65 66 67 68 69] [60 61 62 63 64 65 66 67 68 69 70] [61 62 63 64 65 66 67 68 69 70 71] [62 63 64 65 66 67 68 69 70 71 72] [63 64 65 66 67 68 69 70 71 72 73] [64 65 66 67 68 69 70 71 72 73 74] [65 66 67 68 69 70 71 72 73 74 75] [66 67 68 69 70 71 72 73 74 75 76] [67 68 69 70 71 72 73 74 75 76 77] [68 69 70 71 72 73 74 75 76 77 78] [69 70 71 72 73 74 75 76 77 78 79] [70 71 72 73 74 75 76 77 78 79 80] [71 72 73 74 75 76 77 78 79 80 81] [72 73 74 75 76 77 78 79 80 81 82] [73 74 75 76 77 78 79 80 81 82 83] [74 75 76 77 78 79 80 81 82 83 84] [75 76 77 78 79 80 81 82 83 84 85] [76 77 78 79 80 81 82 83 84 85 86] [77 78 79 80 81 82 83 84 85 86 87] [78 79 80 81 82 83 84 85 86 87 88] [79 80 81 82 83 84 85 86 87 88 89] [80 81 82 83 84 85 86 87 88 89 90] [81 82 83 84 85 86 87 88 89 90 91] [82 83 84 85 86 87 88 89 90 91 92] [83 84 85 86 87 88 89 90 91 92 93] [84 85 86 87 88 89 90 91 92 93 94] [85 86 87 88 89 90 91 92 93 94 95] [86 87 88 89 90 91 92 93 94 95 96] [87 88 89 90 91 92 93 94 95 96 97] [88 89 90 91 92 93 94 95 96 97 98] [89 90 91 92 93 94 95 96 97 98 99] [90 91 92 93 94 95 96 97 98 99 100] 91 92 93 94 95 96 97 98 99 100 次にイメージができたら、リファクタリングを行う 関数をできるだけまとめてみる。 tail func tail(stream *os.File, n int) []string { queue := []string{} scanner := bufio.NewScanner(stream) for scanner.Scan() { queue = append(queue, scanner.Text()) if n <= len(queue)-1 { queue = queue[1:] } } return queue } func show(queues []string) { for _, queue := range queues { fmt.Println(queue) } } 引数、flagを設定する mainにはファイルを読み込んだり、標準入力を読む処理や、オプションフラグをパースする処理を書きます。 複数ファイルを読み込む必要があるので、その処理も書きます。 main func main() { const USAGE string = "Usage: gotail [-n #] [file]" intOpt := flag.Int("n", 10, USAGE) flag.Usage = func() { fmt.Println(USAGE) } flag.Parse() n := int(math.Abs(float64(*intOpt))) if flag.NArg() > 0 { for i := 0; i < flag.NArg(); i++ { if i > 0 { fmt.Print("\n") } if flag.NArg() != 1 { fmt.Println("==> " + flag.Arg(i) + " <==") } fp, err := os.Open(flag.Arg(i)) if err != nil { fmt.Println("Error: No such file or directory") os.Exit(1) } defer fp.Close() show(tail(fp, n)) } } else { show(tail(os.Stdin, n)) } } ユニットテスト 次にテストを書く。 ここでは、あらかじめ用意しているtest.txtを読み込んで検証する。 また、ファイルに対して考えうるオプションを一通り試行できるようにテストする。 今回はtest.txtは100行の連番数字のデータで、-n 1 ~ -n 100まで順にテストしていく。 testTail func TestTail(t *testing.T) { var actual_all []string var expected_all []string count := 0 fp, err := os.Open("./test.txt") if err != nil { fmt.Println("Error: No such file or directory") os.Exit(1) } defer fp.Close() scanner := bufio.NewScanner(fp) for scanner.Scan() { count++ } n_all := 1 for i := count; i > 0; i-- { fp, err = os.Open("./test.txt") actual_all = tail(fp, n_all) expected_all = append([]string{strconv.Itoa(i)}, expected_all...) if reflect.DeepEqual(actual_all, expected_all) { t.Log(reflect.DeepEqual(actual_all, expected_all)) } else { t.Errorf("got %v\nwant %v", actual_all, expected_all) } n_all++ } } テストカバレッジの確認 また、テストカバレッジを確認する。 次のコマンドで確認できる。 ユーザーが使用できるリソースが制限されている場合があるので、 $ ulimit -aで確認する。必要に応じて$ ulimit -n 500等を実行しよう。 testcover $ go test main_test.go main.go -coverprofile=cover.out $ go tool cover -html=cover.out -o cover.html $ open cover.html このような形で確認できる。 だいたい8割を超えているので次に進む。 dockerを立ち上げてbuildを実行してみる。 先程のDockerfileの設定によって、testの実施も行う。 cmd.shに、stage2で実行したいコマンドを記述しよう。 cmd.sh #!/bin/sh -eux ./main < test.txt 記述できたら、コンテナーを起動する。 コマンドをいちいち打つのは面倒なので、Makefileでコマンドを単純化する。 Makefile NAME := gotail .PHONY: all all: docker-build docker-run .PHONY: docker-build docker-build: docker build -t $(NAME) . .PHONY: docker-run docker-run: docker run --rm $(NAME) $ make これで、一連の実行が確認できればOKです。 GitHub ActionsによるCI/CD(Go,Docker) GitHub ActionsではGitHub上でtestを走らせたり、buildをしたりできるので大変便利です。 今回はgo単体で検証するActionとDockerを起動するActionを試してみる。 まず、次のディレクトリ構成にしておく必要がある。 gotail └─.github └── workflows └── go.yml Actionの設定はYAMLファイルで記述する。 まずは、go.ymlを記述しよう。 go.yml on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.15 - name: Build run: go build -v ./main.go - name: Test run: go test -v ./main_test.go main.go 対象のブランチ上へpushとpullrequestを行った際に、Actionが走る設定になっている。 また、buildとtestを実行できる。 次にdockerの起動も試してみる。 gotail └─.github └── workflows └── docker.yml docker.yml name: CI to Docker Hub on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - name: Check Out Repo uses: actions/checkout@v2 - name: Login to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 - name: Build and push id: docker_build uses: docker/build-push-action@v2 with: context: ./ file: ./Dockerfile push: true tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} - name: Run run: make このsecretsはGithubやDockerhub上での設定が必要です。 まず、Docker Hubにアクセスします。 サインインができたら、次に右上にあるアイコンをクリックし、メニューを表示、以下の項目をクリックします。 次に、Security→New Access Tokenの順にクリック。 Tokenが表示されるので、適当にtitleなどを入力し、Copyしてウインドウを閉じます。 次にGithubにアクセスします。 Setting→Secret→New repository secretをクリックしてそれぞれ先程のTokenやDocker Hubのユーザー名を設定します。 以上で設定は完了です。 最後にリポジトリにpushかpullrequestを行うと自動でActionが実行されます。 今回のコード等は、この記事を執筆している段階では、まだmasterにマージはしていませんが、Githubにもアップしているので、参考になるかもしれません、リンク貼っておきます。 https://github.com/Iovesophy/gotail まとめ お疲れ様でした、ここまで読んでいただきありがとうございます! Go言語は初めてでしたが、Go言語のメリットとしてよく挙げられる、初心者でも理解しやすい、処理の速度が速い、少ないコード実装できる、ライブラリが豊富、並行処理が可能、安全性が高いというのはまさにその通りであると感じました。 次は簡単なアプリケーション制作や並行処理を試してみたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む