- 投稿日:2019-05-06T23:15:03+09:00
【Imagemagick対応】Dockerを利用して、RailsをHerokuに簡単デプロイ
はじめに
- 初心者の方でも簡単に再現可能
- Imagemagickが使用可能
- この記事は、以下の記事を参考にして作成しています
DockerComposeでコンテナベースのRailsアプリを作成してそのままHerokuにデプロイする
https://qiita.com/akirakudo/items/16a01271b0a39316e439
引用元記事での問題点
- rmagickが使用できない
- 引用元記事では、ベースイメージにalphineを採用
- このベースイメージでは、Imagemagick 7 が使用されており、gem install rmagick でエラーが発生する
- gem rmagick では、Imagemagick 6 までしか対応していないことが原因
対策
- ベースイメージを alphine から Debian へ変更
環境構築方法
- コンテナの概要
- RailsコンテナとPostgreSQLコンテナから構成
- 両者は、local環境のカレントディレクトリにマウントされている
(※カレントディレクトリ = Dockerfileが置いてあるディレクトリのこと)- Railsコンテナはポートフォワーディングにより、localhost:3000からアクセス可能
コンテナ構築方法
- 以下記載の手順をそのまま実行してください
1. フォルダ、ファイルの準備
- 好きな場所にフォルダを新規作成
- フォルダ内に4つのファイルを作成(中身は空でOK)
例)この記事では、フォルダ名を
hoge_folder
としています
- hoge_folder
├ Dockerfile
├ docker-compose.yml
├ Gemfile
├ Gemfile.lock
2. ファイルの編集
- 1. で作成した4つのフォルダに以下の内容をコピペする
① Dockerfile
/hoge_folder/DockerfileFROM ruby:2.6 ENV RUNTIME_PACKAGES="linux-headers libxml2-dev libxslt-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql" \ DEV_PACKAGES="build-base curl-dev" \ HOME="/myapp" WORKDIR $HOME # Counter Measure to Error:"Autoprefixer doesn’t support Node v4.8.2. Update it" RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN apt-get update && \ apt-get install -y mysql-client \ postgresql-client \ sqlite3 \ --no-install-recommends && \ rm -rf /var/lib/apt/lists/* ADD Gemfile $HOME/Gemfile ADD Gemfile.lock $HOME/Gemfile.lock RUN bundle install ADD ./ $HOME COPY ./ $HOME CMD ["rails", "server", "-b", "0.0.0.0"]② docker-compose.yml
/hoge_folder/docker-compose.ymlversion: '3' services: db: container_name: postgress_db image: postgres:latest web: container_name: rails_app build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db③ Gemfile
/hoge_folder/Gemfilesource 'https://rubygems.org' gem 'rails', '5.2.2' gem 'rmagick'④ Gemfile.lock
/hoge_folder/Gemfile.lock注)Gemfile.lock の中身は空です
- これでコンテナを立ち上げる準備ができました。
3. コンテナの立ち上げ準備
- CLIを起動する(bash,コマンドプロント等、ご自身の環境でお使いのものを起動してください。)
Dockerfileのあるディレクトリに移動する
- この記事で言えば、
cd /home/...(以下略).../hoge_folder
以下のコマンドを入力
コマンド$ docker-compose run web rails new . --force --database=postgresql※実際に入力する際は、
$マーク
を消してください
コマンドを実行すると、作業フォルダ内(hoge_folder)に複数のフォルダが生成される
Databaseの設定を変更する
- このままではpostgreSQLコンテナにアクセスできないため
config/database.yml
を開いて、下記内容を上書きする/hoge_folder/config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db # 上書き username: postgres # 上書き password: # 上書き pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
- docker-compose.ymlがあるフォルダ上でビルドする
コマンド$ docker-compose build
- データベースを作成し、マイグレーションする
コマンド$ docker-compose run web rails db:create $ docker-compose run web rails db:migrate
- ここまでで、コンテナの準備は終了です。
4. railsの起動確認
- docker-compose up でrailsは自動で立ち上がる
コマンド$ docker-compose up
コマンド(再起動する場合) $ docker-compose restart (停止する場合) $ docker-compose stop作 戦 完 了 !
- それでは、
Rails on Docker
ライフ をお楽しみください!謝辞
- @akirakudo さんの記事は、大変参考になりました
- 投稿日:2019-05-06T23:15:03+09:00
【Imagemagick対応】Dockerを利用して、Rails環境を作成
はじめに
- 初心者の方でも簡単に再現可能
- Imagemagickが使用可能
- この記事は、以下の記事を参考にして作成しています
Heroku にもビルド可能なイメージです
DockerComposeでコンテナベースのRailsアプリを作成してそのままHerokuにデプロイする
https://qiita.com/akirakudo/items/16a01271b0a39316e439引用元記事での問題点
- rmagickが使用できない
- 引用元記事では、ベースイメージにalphineを採用
- このベースイメージでは、Imagemagick 7 が使用されており、gem install rmagick でエラーが発生する
- gem rmagick では、Imagemagick 6 までしか対応していないことが原因
対策
- ベースイメージを alphine から Debian へ変更
環境構築方法
- コンテナの概要
- RailsコンテナとPostgreSQLコンテナから構成
- 両者は、local環境のカレントディレクトリにマウントされている
(※カレントディレクトリ = Dockerfileが置いてあるディレクトリのこと)- Railsコンテナはポートフォワーディングにより、localhost:3000からアクセス可能
コンテナ構築方法
- 以下記載の手順をそのまま実行してください
1. フォルダ、ファイルの準備
- 好きな場所にフォルダを新規作成
- フォルダ内に4つのファイルを作成(中身は空でOK)
例)この記事では、フォルダ名を
hoge_folder
としています
- hoge_folder
├ Dockerfile
├ docker-compose.yml
├ Gemfile
├ Gemfile.lock
2. ファイルの編集
- 1. で作成した4つのフォルダに以下の内容をコピペする
① Dockerfile
/hoge_folder/DockerfileFROM ruby:2.6 ENV RUNTIME_PACKAGES="linux-headers libxml2-dev libxslt-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql" \ DEV_PACKAGES="build-base curl-dev" \ HOME="/myapp" WORKDIR $HOME # Counter Measure to Error:"Autoprefixer doesn’t support Node v4.8.2. Update it" RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN apt-get update && \ apt-get install -y mysql-client \ postgresql-client \ sqlite3 \ --no-install-recommends && \ rm -rf /var/lib/apt/lists/* ADD Gemfile $HOME/Gemfile ADD Gemfile.lock $HOME/Gemfile.lock RUN bundle install ADD ./ $HOME COPY ./ $HOME CMD ["rails", "server", "-b", "0.0.0.0"]② docker-compose.yml
/hoge_folder/docker-compose.ymlversion: '3' services: db: container_name: postgress_db image: postgres:latest web: container_name: rails_app build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db③ Gemfile
/hoge_folder/Gemfilesource 'https://rubygems.org' gem 'rails', '5.2.2' gem 'rmagick'④ Gemfile.lock
/hoge_folder/Gemfile.lock注)Gemfile.lock の中身は空です
- これでコンテナを立ち上げる準備ができました。
3. コンテナの立ち上げ準備
- CLIを起動する(bash,コマンドプロント等、ご自身の環境でお使いのものを起動してください。)
Dockerfileのあるディレクトリに移動する
- この記事で言えば、
cd /home/...(以下略).../hoge_folder
以下のコマンドを入力
コマンド$ docker-compose run web rails new . --force --database=postgresql※実際に入力する際は、
$マーク
を消してください
コマンドを実行すると、作業フォルダ内(hoge_folder)に複数のフォルダが生成される
Databaseの設定を変更する
- このままではpostgreSQLコンテナにアクセスできないため
config/database.yml
を開いて、下記内容を上書きする/hoge_folder/config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db # 上書き username: postgres # 上書き password: # 上書き pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
- docker-compose.ymlがあるフォルダ上でビルドする
コマンド$ docker-compose build
- データベースを作成し、マイグレーションする
コマンド$ docker-compose run web rails db:create $ docker-compose run web rails db:migrate
- ここまでで、コンテナの準備は終了です。
4. railsの起動確認
- docker-compose up でrailsは自動で立ち上がる
コマンド$ docker-compose up
コマンド(再起動する場合) $ docker-compose restart (停止する場合) $ docker-compose stop作 戦 完 了 !
- それでは、
Rails on Docker
ライフ をお楽しみください!謝辞
- @akirakudo さんの記事は、大変参考になりました
- 投稿日:2019-05-06T23:10:52+09:00
KubernetesでWebアプリケーションを構築(サンプル編:SpringBoot)
前回は、Kubernetesの概要と背景にある考え方について、説明しました。
今回では、実際に簡単なWebアプリケーションをKubernetes上に構築するサンプルをご紹介します。
このサンプルアプリケーションは、SpringBootを使用したWebアプリケーションで、Kubernetesの構成を学ぶ上で、極力シンプルにしたものです。
アプリケーションの構成
前回の最後にアプリケーションの構成パターンとして、以下の3つを挙げましたが、いずれの場合も設定をどのように行うかは悩みどころです。
- ミドルウエア(+設定)のみ
- ミドルウエア(+設定)+データのみ
- ミドルウエア+アプリケーション+設定
基本的に、アプリケーションの設定の大部分はdockerイメージにまとめられています。しかし、すべての環境設定がDockerイメージに含まれている場合、特にテスト、ステージング、実稼働環境、または特定の目的のために異なる設定を行いたい場合は、それほど効率的ではありません。
その場合、イメージは共通またはデフォルト設定を持つ必要があり、特定の設定は環境変数またはKubernetesのConfigmapまたはSecretリソースを介して渡されるべきです。環境変数を使用する場合は、アプリケーション側が環境変数を読み取るような作りにする必要があります。configmapは単純な変数だけでなくnginx.confのような設定ファイルも扱うことができます。
データベースに関しては、データが更新されるため、コンテナ運用の原則である「Immutable」の対象にはなりませんし、またデータを永続的に保存する必要があることからPodで構築するのには向いていません。しかし、テスト環境であれば、KubernetesのPODでDBを構築するのもいいです。ここでは、サンプルのためPod上にDBを構築します。通常、DBにはテーブルや初期のマスタデータを登録する必要があります。ここでは、コンテナが作成されたときに初期化スクリプトを指定することによって実現しています。Dockerfileには通常使用されるENTRYPOINTとCMDがありますが、コンテナ起動時に多くのことをしたい場合は、イメージに依存するinitスクリプトを指定できます。このサンプルでは、postgresイメージを使い、シェルスクリプトを/docker-entrypoint-initdb.d/ディレクトリに置いてデータベースとテーブルを作成し、マスターデータを登録しています。
Kubernetesを使ったWebアプリケーションの例
それでは、Kubernetes上で動作するWebアプリケーションを作成します。
始める前に、Kubernetes環境を作成する必要があります。おすすめは以下のとおりです。
- GKE
- Minikube
- Docker Desktop(これにはKubernetesも含まれます)
この記事はkubernetes自体をインストールする方法を説明しません。公式ページ(上記のリンク)で、十分に理解できると思います。
GKEは12ヶ月間300ドルの無料枠があり、これで勉強やテストに十分です。使用しない場合は、kubernetes環境をシャットダウンして削除するようにしてください。でないと、課金が続き、あっという間に無料枠がなくなります。以下は、GKEの例で、gcloudコマンドをインストールし、GCPに接続できている状態のあとで、実行するもので、作成と削除は頻繁に使います(サンプルで課金が続かないようにするのと、Immutableアーキテクチャなので使い終わったら削除しましょう)。
gcloud init gcloud container get-server-config --zone asia-northeast1-a # 作成 gcloud container clusters create samplewebapp --cluster-version 1.12.7-gke.10 --zone asia-northeast1-a --num-nodes 3 gcloud container clusters get-credentials samplewebapp --zone asia-northeast1-a #削除 gcloud container clusters delete samplewebapp --zone asia-northeast1-astern
ログを表示するのに便利なsternも必ずインストールしてください。
構造
サンプルアプリケーションは、Kubernetes環境で動作することを目的としたWebアプリケーションの基本設定をテストするためのもので、以下のような構造となっています。
アプリケーションは次のサーバで構成されています。
- Webサーバ(nginxを使用)
- アプリケーションサーバ(tomcatを使用[スプリングブートに埋め込まれています])
- DBサーバ(postgresを使用)
- セッションサーバ(アプリケーションサーバのセッションを維持するためにredisを使用)
- NAS(PVCダイナミックプロビジョニングまたはnfsを使用)
Webサーバとアプリケーションサーバはステートレスなので、複数のPodに拡張できます。
DBとセッションサーバはステートフル(状態を保つ)です。特にDBサーバ上のデータは恒久的に保存する必要があります。セッションサーバは永続的である必要はありませんが、データが失われるとユーザーエクスペリエンスが悪くなり、ユーザーは再度ログインする必要があり、またユーザーが入力した情報が失われる可能性があります。しかしDBと比較すると、それほど重要ではありません。
セッション維持のために、前段のWebサーバにsticky sessionを設定して、セッションIDによって振り分け先のAPサーバを変える方法もありますが(この場合StatefulSetを使う)、Kubernetesでは、APサーバはいつ終了し再起動されるかわからないため、セッション情報はRedisなど外部のサーバに持たせるのがいいでしょう。Podは割当メモリを超過した場合、容易に削除・再作成されます。この点を考慮に入れておく必要があります。
このサンプルでは、DBサーバとセッションサーバの両方に単一のPodを使用します。実稼働環境で使用したい場合は、両方とも、少なくともDBサーバについてはPaaSを使用することを強くお勧めします。
アプリケーションサンプルはToDoを管理するためのもので、わずか3ページで構成されています。認証の機能とテーブルのCRUD機能を提供するだけです。
- ログインページ(ID / PW = admin / 111111)
- ToDoリストページ
- Todo編集ページ
Todoは、ストレージにファイルを保存するテストの目的で、画像のアップロードを含みます。
ここでの目的はKubernetesの設定を知ることです、テストとして、最小限の機能をカバーするのにちょうど十分な、できるだけ簡潔なソースコードを作りました。これをプロダクトに適用する場合は、ヴァリデーション、セキュリティ、保守性、フレームワークの使用などを検討する必要があります。
サンプルコード全体と展開のマニフェストは、次のサイトにあります。ここでソースコードや設定と説明を入手できます。詳細な手順は、こちらのReadme.mdの方に書いています。
https://github.com/dayan888/springdemo_k8s
上記のソースコードは2つで構成されています。
- アプリケーションのソースコード(Spring Boot)
- デプロイ関連ファイル(Dockerfile、kubernetesマニフェスト、confファイルなど)
基本的には、標準のSpringBootのプロジェクト構造に、Dockerとkubernetesのマニフェストを追加したものです。
ローカルマシン上に開発環境構築
アプリケーションを改修するためには、開発用にローカルでJavaアプリケーション開発環境、docker image構築環境が必要になり、以下がインストールされている必要があります。
- JDK 8以上
- Docker
- Postgres 9以上
- Redis
- IntelliJ(または他のIDE)
- Docker Hub(アカウントを登録が必要)
dockerをインストールすれば、docker pullで、postgresとredisをローカル環境に簡単にインストールできます。
Kubernetes環境にデプロイする
早くセットアップしたい場合を考慮し、Kubernetesにマニフェストを適用するためのシェルスクリプトを用意しています。GKEを使用している場合は、deploy/sh/gke.shを実行してください。MinikubeまたはDocker Desktopを使用している場合は、deploy/sh/local.shを実行してください。
このシナリオでは、私個人のdocker hubがイメージリポジトリとして使われています。Dockerfileで画像を作成することから始めて全プロセスを試す場合は、docker hubのようなレジストリを各自作成し、それに応じてマニフェストを修正する必要があります。
マニフェストの説明
以下はシステムを構築するための正確な指示ではなく、要点の説明になります。詳細はReadme.mdを参照してください。
実行する際は、適用順序は重要です。従属変数(特にServiceの名前)が事前に定義されていない場合、Podの作成は失敗します。デプロイは次の順序で行う必要があります。
- Redis(アプリケーションサーバからアクセス)
- DB(アプリケーションサーバからアクセス)
- アプリケーションサーバ(Webサーバからアクセス)
- Webサーバ(LB / Ingressからアクセス)
- ロードバランサまたは入力
もしくは、配置を作成する前に、まずすべてのServiceを作成します。Serviceは、selectorとして指定した振り分け先のPodが存在しない場合でも作成可能です。
Redisはそのまま公式イメージで作成されますが、マニフェストを適用してDeploymentとServiceを作成する必要があります。1.アプリケーションサーバ
Dockerfile
コンテナに何を入れるべきかを理解するためには、イメージを作成する方法を理解することが重要です。
FROM openjdk:8-jdk-alpine ARG buildver=0.5.0 #RUN ./gradlew build -x test COPY build/libs/springdemo-${buildver}.jar /app/springdemo.jar ENTRYPOINT ["java", "-jar", "/app/springdemo.jar"]まず適切なベースイメージを選択することが重要です。ゼロから作成することもできますが、DockerHubには既に作成済みのイメージがあります。イメージサイズを小さくしたい場合は、alpineとbusyboxが候補となります。しかし、これらは最小限のコマンドしか持っていないので、コンテナを調べてネットワークコマンドを実行したい場合は、適切ではありません。
Javaで実行されるアプリケーションサーバの場合、私はalpineベースのopenjdkイメージを選びました。次に、ビルドされたjarファイルをコピーして、コンテナの起動時に実行されるENTRYPOINTを設定します。このイメージ作成の際、gradleのビルドなどを一緒に行うことができます。このビルドおよびイメージ作成プロセスは、jenkinsなどのCI/CDサーバを使用した場合、若干異なります。
このサンプルでは、サーバ側のJavaのフレームワークとしてSpring Bootを使用しています。これはTomcatは埋め込まれているので、Tomcatをセットアップして設定する必要はなく、可搬性に優れているためコンテナと相性はいいです。ただしSpringのようなJavaのフレームワーク、DIコンテナは起動に時間がかかるため、その点コンテナ時代には向いていないと思います。最終的にネイティブにコンパイルするような仕組みが必要になってくるでしょう。
Spring Bootの設定に関しては、application.ymlを使用し、環境変数とデフォルト変数を以下のように設定します。
datasource: url: ${DB_URL:localhost} driverClassName: org.postgresql.Driver環境変数は、kubernetesマニフェストまたはconfigmapによって設定されます。
Deploymentマニフェスト
apiVersion: apps/v1 kind: Deployment metadata: name: sbdemo-apserver spec: replicas: 3 selector: matchLabels: app: sbdemo-apserver template: metadata: labels: app: sbdemo-apserver spec: containers: - name: apserver image: dayan888/springdemo:apserver imagePullPolicy: Always ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "prd" - name: DB_URL value: "jdbc:postgresql://sbdemo-postgres-service:5432/demodb?user=postgres&password=postgres" - name: PIC_DIR value: "/opt/picDir" - name: REDIS_HOST value: "sbdemo-redis-service" - name: REDIS_PORT value: "6379" volumeMounts: - mountPath: "/opt/picDir" name: apserver-pvc volumes: - name: apserver-pvc persistentVolumeClaim: claimName: sbdemo-apserver-pvcもしこれを初めて見ると、少し複雑に思えるでしょう。特にマニフェストの言葉や構造はわかりにくいです。しかし、実際には何も特別なことはありません。
このマニフェストの主なポイントは次のとおりです。
- 前工程で作成したイメージを使用する
- 冗長性のために3つのPodの複製を作成する
- application.ymlによって参照される環境変数が設定
そして、3つの冗長化されたPodでアップロードされた画像を共有するためのPersistent Volumeをマウントします。画像アップロード機能を加えたのはこのPVを使うためです。
各アプリケーションサーバはアップロードされたイメージを読み書きする必要があるため、永続的なボリュームはReadWriteManyである必要があります。ローカルのkubernetesを使用している場合は、ボリュームにアクセスするノードが1つしかないため、大きな問題にはなりません。しかし、GCEディスクでReadWriteManyをサポートしていないためにGKEを実行する場合は、別の方法を検討する必要があります。ここでは、k8s.gcr.io/volume-nfsイメージを使用してNFSサーバを作成します。NFSの内部では、Persistent Volume Claimが使用されます。nfsサーバ自体はアプリケーションサーバからマウントされるPersistent Volumeになります。ここは少しややこしいです。
ローカル用とGKE用に別のマニフェストを用意したので、サンプルコードをapplyするときは注意してください。
Serviceマニフェスト
これらのコンテナにアクセスするための「Service」を作成する必要があります。Kubernetesでは、サーバ(Pod)とそこへのアクセスを分けており、バッチ処理でない限り、通常セットで作成する必要があります。
apiVersion: v1 kind: Service metadata: name: sbdemo-apserver-service spec: type: ClusterIP ports: - name: "http-port" protocol: "TCP" port: 8080 targetPort: 8080 selector: app: sbdemo-apserver主なポイントは以下のとおりです。
- Serviceにアクセスするためのポートとアプリケーションサーバのポート(targetPort)を定義します。
- どのPodへ転送されるべきかに使用されるSelectorを定義します
- 他のPodからのアクセスに使用されるServiceの名前
その後、kubectlを実行してマニフェストを適用します。
kubectl apply -f deploy/app/pvc.yaml kubectl apply -f deploy/app/deployment.yaml kubectl apply -f deploy/app/service.yaml2. Webサーバ
Dockerfile
FROM nginx:latest COPY src/main/resources/static/css /usr/share/nginx/html/css COPY src/main/resources/static/js /usr/share/nginx/html/js COPY src/main/resources/static/img /usr/share/nginx/html/imgnginx画像を使用して、css、js、image、htmlファイルなどの静的コンテンツをコピーします。
構成マップ
nginx.confなどの設定ファイルはイメージに含めて、設定ファイルをソースコードで管理することができます。しかし、環境設定をソースコードから分離して再利用性を高めたい場合は、nginx.confのconfigmapを作成し、それをkubernetesマニフェストを介してコンテナに渡すようにします。
nginx.confでは、必ず出力ログを標準出力に設定してください。Kubernetesのプラクティスでは、ログは細分化せず、すべて標準出力に出し、分類はその後で行います。
access_log /dev/stdout; error_log /dev/stderr debug;そしてserver.confを使用してバックエンドのアプリケーションサーバを次のようなService名で設定します。
location / { ... proxy_pass http://sbdemo-apserver-service:8080; proxy_cookie_path / /; }それから、kubectlを実行してconfigmapを作成します。
kubectl create configmap nginx-conf --from-file=deploy/web/nginx.conf kubectl create configmap server-conf --from-file=deploy/web/server.confここで、configmapのためにマニフェストを使うこともできます、しかし、その内容がファイルであるならば、コマンドだけを使う方がより簡単であると思います。
Deploymentマニフェスト
apiVersion: apps/v1 kind: Deployment metadata: name: sbdemo-nginx spec: replicas: 3 selector: matchLabels: app: sbdemo-nginx template: metadata: labels: app: sbdemo-nginx spec: containers: - name: nginx image: dayan888/springdemo:nginx ports: - containerPort: 80 readinessProbe: httpGet: path: /css/style.css port: 80 volumeMounts: - name: nginx-conf mountPath: /etc/nginx/nginx.conf subPath: nginx.conf - name: server-conf mountPath: /etc/nginx/conf.d/server.conf subPath: server.conf volumes: - name: nginx-conf configMap: name: nginx-conf items: - key: nginx.conf path: nginx.conf - name: server-conf configMap: name: server-conf items: - key: server.conf path: server.conf主な点は次のとおりです。
- 前工程で作成したイメージを使用する
- ConfigMapからconfigファイルを直接直接マウントする
- 冗長性を定義する(3つのReplica)
- Readiness Probeを定義します(GKEで実行すると、/を200に戻す必要があるため、この設定は重要です)。
次に、このDeployment用のServiceを作成します。
apiVersion: v1 kind: Service metadata: name: sbdemo-nginx-service spec: type: ClusterIP ports: - name: "http-port" protocol: "TCP" port: 80 targetPort: 80 selector: app: sbdemo-nginxここでは特別なことはありません。Service名は後でIngresのマニフェストで使用されます。
3.データベース
Dockerfile
FROM postgres:9.6 COPY deploy/db/init_ddl.sh /docker-entrypoint-initdb.d/ RUN chmod +x /docker-entrypoint-initdb.d/init_ddl.shデータベースは既製のDockerイメージがあります。モジュールを構築したりコピーしたりする必要はありません。postgresql.confで定義されているデフォルトの振る舞いを変更したいかもしれません。その場合はconfigmapが便利です。
今回はデフォルト設定を使用しますが、データベース、テーブル、マスターデータなどを作成する必要があります。そのため、docker-entrypoint-initdb.dを使用して、シェルスクリプトをコピーします。
StatefulSetマニフェスト
今回は単一のdbサーバ構成のみが使用されますが、冗長性を設定する場合は、マスタとスレーブのdbを区別することが重要です。マスタの再起動時に、同じボリュームをマウントする必要があります。したがって、このサンプルではこれを確実にするためにStatefulSetを使用しています。StatefulSetはDeploymentとほぼ同じですが、違いは再起動時に同じPVC(永続ボリューム)がマウントされることです。
apiVersion: apps/v1 kind: StatefulSet metadata: name: sbdemo-postgres-sfs spec: serviceName: sbdemo-postgres-service replicas: 1 selector: matchLabels: app: sbdemo-postgres-sfs template: metadata: labels: app: sbdemo-postgres-sfs spec: containers: - name: postgres image: dayan888/springdemo:postgres9.6 ports: - containerPort: 5432 volumeMounts: - name: pvc-db-volume mountPath: /var/lib/postgresql volumeClaimTemplates: - metadata: name: pvc-db-volume spec: accessModes: - ReadWriteOnce resources: requests: storage: 1G主なポイントは以下のとおりです。
- StatefulSetを使用する
- 前工程で作成したDockerイメージを使用する
- このイメージのpostgresプロセスが参照する/var/lib/postgresqlにPVCがマウントされます。
Serviceマニフェスト
apiVersion: v1 kind: Service metadata: name: sbdemo-postgres-service spec: type: ClusterIP ports: - name: "db-port" protocol: "TCP" port: 5432 targetPort: 5432 selector: app: sbdemo-postgres-sfsService名はアプリケーションサーバのapplication.ymlに設定されています。このシナリオでは、Service名は事前に定義されている必要があります。何らかの命名規則が必要となるでしょう。
4.Ingres
IngresはHTTPプロトコルのようなL7で動作するロードバランサであり、ホスト名またはURLパスによってトラフィックを振り分けることができます。
ドメイン名ごとに(一意のグローバルIPアドレスを持つ)サービスを1つだけ運用する場合は、LoadBalancerだけで十分です(こちらはL4で動作するロードバランサ)。WebサーバのServiceのTypeを「LoadBalancer」に設定してから、グローバルIPアドレスをDNSサーバ上のドメインに設定します。
しかし、これは柔軟性と拡張性にかけるので、ingressを使用した方がいいでしょう。
まず、Ingressから転送される各WebサーバのServiceを作成する必要があります。
Ingresマニフェスト
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: sbdemo-ingress spec: rules: - host: sbdemo.example.com http: paths: - path: /* backend: serviceName: sbdemo-nginx-np servicePort: 80 - path: /v2/* backend: serviceName: web2 servicePort: 8080 backend: serviceName: sbdemo-nginx-np servicePort: 80 tls: - hosts: - sbdemo.example.com secretName: tls-sbdemoこのサンプルは1つのWebシステムでしかないため、イングレスの機能をテストするために、簡単なWebサーバ("Hello、xxx"を返すだけ)を追加してみます。
主なポイントは以下のとおりです。
- ホストとパスでルールを定義し、バックエンドのService名とポートにバインドします。
- ホストのSSL証明書を定義します
httpsは現在「必須」であるため、IngresはSSL証明書を管理するのに非常に役立ちます。具体的な手順はサンプルサイトのREADME.mdに書かれています。サンプルでは自己署名証明書を使用しているため、実稼働環境に適用する場合は正しく署名する必要があります。
重要:このサンプルをローカルのkubernetes環境で実行すると、ロードバランサーのみが適用されます。Ingresを有効にするために、IngresをサポートしているKubernetes環境を選ぶか、もしくは多少複雑ですが自身でnginxで構成されたIngress Podを作成する必要があります。
結論
このサンプルアプリケーションを作成することで、私自身たくさんのことを学びました。特にPersistent Volumeに苦労しました。驚いたことに、多くのマネージドサービスはReadWriteManyモードを提供していません。そのため、レガシーなNFSサーバを作成しなければなりませんでした。ストレージはまだもっと考慮する余地があります。
Kubernetesマニフェストは扱いが簡単ですが、マニフェストの数が増えたときは、「helm」を適用することを検討する必要があります。私の仕事場ではAKSとhelmを使っていますが、今回は作る時間がないので、シェルスクリプトを作成しました。
これは、あくまでもサンプルなので多くのことを省いています。実稼働環境を適用するために考慮すべきことがまだたくさんあります。
何か質問・フィードバックがあると嬉しいです。
- 投稿日:2019-05-06T23:10:10+09:00
PDF.jsでネット上のPDFファイルを一瞬にして開くワンライナー
はじめに
Mouse DictionaryをPDFで使うために、PDF.jsを用いるという方法がある。(@kroton様の参考記事)
しかし、私のようなめんどくさがりは、これすらも面倒くさいので以下のようなツールを作った。
- ワンラインでネット上のPDFをPDF.jsで開く
- ローカルに落としたpdfをわざわざ削除しなくてもよい
完成物
https://github.com/monado3/pdfjs-broker
開発環境
- Manjaro linux
- Python 3.7.3
- Docker 18.09.5-ce
実現方法
- Dockerコンテナ内にpdfを落とす。
- そのままDockerコンテナ内でPDF.jsが機能するPHPビルトインサーバを立てる。
- ローカルのブラウザからDockerコンテナ内のpdfを閲覧する。
- ダウンロードしたpdfはDockerコンテナが終了すれば(再起動時や明示的にコンテナを停止させた際)自動的に消える。
また、複数のpdfを同時閲覧することを前提とし、自動で動的にDockerコンテナとローカルのポートフォワーディングをするために、Pythonプログラムにコンテナ立ち上げを仲介させることにした。
課題点
DockerイメージのEntrypointとなっているシェルスクリプトが終了すると、例え
-d
オプションをつけてrunしてもコンテナはなぜか終了してしまう。
それにより、コンテナ内でPDFのサービングが開始したことをPython側にシンプルに伝える方法がなく、コンテナ内の必要処理が終了する前に、Pythonがブラウザを開いてしまう。最後に
実装があまりスマートではないので、どしどしアドバイスやPR待ってます。
- 投稿日:2019-05-06T23:10:10+09:00
PDF.jsでネット上のPDFファイルを一瞬にして開くツール
はじめに
Mouse DictionaryをPDFで使うために、PDF.jsを用いるという方法がある。(@kroton様の参考記事)
しかし、私のようなめんどくさがりは、これすらも面倒くさいので以下のようなツールを作った。
- ワンラインでネット上のPDFをPDF.jsで開く
- ローカルに落としたpdfをわざわざ削除しなくてもよい
完成物
https://github.com/monado3/pdfjs-broker
開発環境
- Manjaro linux
- Python 3.7.3
- Docker 18.09.5-ce
実現方法
- Dockerコンテナ内にpdfを落とす。
- そのままDockerコンテナ内でPDF.jsが機能するPHPビルトインサーバを立てる。
- ローカルのブラウザからDockerコンテナ内のpdfを閲覧する。
- ダウンロードしたpdfはDockerコンテナが終了すれば(再起動時や明示的にコンテナを停止させた際)自動的に消える。
また、複数のpdfを同時閲覧することを前提とし、自動で動的にDockerコンテナとローカルのポートフォワーディングをするために、Pythonプログラムにコンテナ立ち上げを仲介させることにした。
課題点
DockerイメージのEntrypointとなっているシェルスクリプトが終了すると、例え
-d
オプションをつけてrunしてもコンテナはなぜか終了してしまう。
それにより、コンテナ内でPDFのサービングが開始したことをPython側にシンプルに伝える方法がなく、コンテナ内の必要処理が終了する前に、Pythonがブラウザを開いてしまう。最後に
実装があまりスマートではないので、どしどしアドバイスやPR待ってます。
- 投稿日:2019-05-06T18:36:48+09:00
Docker for mac のエラー「unauthorized: incorrect username or password.」対処法
Docker Desktop for mac をインストール後のPullで「unauthorized: incorrect username or password.」が発生。少しハマったので備忘として共有します。
Docker Hubより「pull」時にエラー発生
$docker run hello-world Unable to find image 'hello-world:latest' locally docker: Error response from daemon: Get https://registry-1.docker.io/v2/library/hello-world/manifests/latest: unauthorized: incorrect username or password. See 'docker run --help'.ID/PWDが異なるとの警告。原因はDockerHub登録時に利用したメールアドレスでログインしていたことでした。
メールアドレスで、DockerHubにログインする必要があります。[対応]Docker HubにユーザIDでログインする。
メールアドレスではなくユーザIDでログインしてください!!
確認
DockerHubからPull出来ることを確認。
$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:92695bc579f31df7a63da6922075d0666e565ceccad16b59c3374d2cf4e8e50e Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with:
- 投稿日:2019-05-06T13:18:07+09:00
一足遅れて Kubernetes を学び始める - 07. workloads その3 -
ストーリー
- 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
- 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
- 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
- 一足遅れて Kubernetes を学び始める - 04. kubectl -
- 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
- 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
- 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
- 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -
前回
一足遅れて Kubernetes を学び始める - 06. workloads その2 -にて、DaemonSetとStatefulSet(一部)を学習しました。今回は、StatefulSetの続きとJob,CronJobを学習します。
StatefulSet
sample-statefulset.yamlapiVersion: apps/v1 kind: StatefulSet metadata: name: sample-statefulset spec: serviceName: sample-statefulset replicas: 3 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: nginx-container image: nginx:1.12 ports: - containerPort: 80 volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: - ReadWriteMany storageClassName: managed-nfs-storage resources: requests: storage: 1Gi永続的にデータが保存されるかどうか確認します。
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 -- df -h Filesystem Size Used Avail Use% Mounted on ... 192.168.3.35:/home/data/default-www-sample-statefulset-0-pvc-* 15G 1.1G 13G 8% /usr/share/nginx/html ... pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 touch /usr/share/nginx/html/sample.htmlsample.htmlというファイルを作りました。こちらが消えるかどうか確認します。
pi@raspi001:~/tmp $ k delete pod sample-statefulset-0 pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html /usr/share/nginx/html/sample.htmlpodを消してセルフヒーリングで復活した後、確認すると、sample.html残っています。
pi@raspi001:~/tmp $ k delete -f sample-statefulset.yaml pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml pi@raspi001:~/tmp $ k exec -it sample-statefulset-0 ls /usr/share/nginx/html/sample.html /usr/share/nginx/html/sample.htmlこちらも残っていますね。OKです。
スケーリング
StatefulSetでは、スケールアウトするときは、インデックスが小さいものから増えていきます。
逆にスケールインするときは、インデックスが大きいものから削除されていきます。
また、1つずつ増減します。そのため、一番始めに作られるPodは、一番最後に削除されることになります。
試してみます。pi@raspi001:~ $ k get pod | grep sample-statefulset sample-statefulset-0 1/1 Running 1 10h sample-statefulset-1 1/1 Running 1 10h sample-statefulset-2 1/1 Running 1 10h pi@raspi001:~/tmp $ vim sample-statefulset.yaml # replica:3→4 pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml pi@raspi001:~/tmp $ k get pod | grep sample-statefulset sample-statefulset-0 1/1 Running 1 10h sample-statefulset-1 1/1 Running 1 10h sample-statefulset-2 1/1 Running 1 10h sample-statefulset-3 0/1 ContainerCreating 0 6s pi@raspi001:~/tmp $ vim sample-statefulset.yaml # replica:4→2 pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml pi@raspi001:~/tmp $ k get pod | grep sample-statefulset sample-statefulset-0 1/1 Running 1 10h sample-statefulset-1 1/1 Running 1 10h sample-statefulset-2 1/1 Running 1 10h sample-statefulset-3 0/1 Terminating 0 2m4s pi@raspi001:~/tmp $ k get pod | grep sample-statefulset sample-statefulset-0 1/1 Running 1 10h sample-statefulset-1 1/1 Running 1 10h sample-statefulset-2 0/1 Terminating 0 10h期待通りですね。1つずつではなく、並列して作成したい場合は、spec.podManagementPolicyをparallelにすれば実現できます。
アップデート戦略
戦略は2通りあり、OnDeleteとRollingUpdateがあります。前者は、削除された(マニュフェスト更新ではなく、delete)タイミングに更新され、後者は、即時更新します。StatefulSetの更新では、アップデート中の過不足分の調整(maxUnavailable, maxSurge)は一切できません。また、partitionというフィールドのによって、どのインデックス以降を更新するかを調整することもできます。これは、ステートフルならではの機能です。
Deploymentでは試してませんでしたが、こちらで試してみようと思います。デフォルトの戦略はRollingUpdateです。これは何度も動作して確認できているので、OnDeleteを試そうと思います。(partitionは置いとく)
sample-statefulset.yaml... spec: updateStrategy: type: OnDelete ... template: spec: containers: - name: nginx-container image: nginx:1.13 ...アップデート戦略をOnDeleteにし、nginxイメージを1.12から1.13に更新しました。
pi@raspi001:~/tmp $ k delete -f sample-statefulset.yaml pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml pi@raspi001:~/tmp $ k describe pod sample-statefulset-0 | grep "Image:" Image: nginx:1.12 pi@raspi001:~/tmp $ k delete pod sample-statefulset-0 pi@raspi001:~/tmp $ k get pod | grep sample-statefulset sample-statefulset-0 0/1 ContainerCreating 0 5s sample-statefulset-1 1/1 Running 0 2m59s pi@raspi001:~/tmp $ k describe pod sample-statefulset-0 | grep "Image:" Image: nginx:1.13期待通りですね。明示的にpodを削除すればnginxが更新されました。
Job
一度限りの処理を実行させるリソース。
replicaSetのように複製ができる。
バッチ処理に向いている。10秒sleepするだけのjobを実行してみます。
sample-job.yamlapiVersion: batch/v1 kind: Job metadata: name: sample-job spec: completions: 1 parallelism: 1 backoffLimit: 10 template: spec: containers: - name: sleep-container image: nginx:1.12 command: ["sleep"] args: ["10"] restartPolicy: Neverpi@raspi001:~/tmp $ k apply -f sample-job.yaml pi@raspi001:~/tmp $ k get pod NAME READY STATUS RESTARTS AGE sample-job-d7465 0/1 Completed 0 3m17s pi@raspi001:~/tmp $ k get job NAME COMPLETIONS DURATION AGE sample-job 1/1 27s 4m8sjobの実行が終わると、podが消えていますね。そして、jobのCOMPLETIONSが1/1になっているので正常終了したみたいです。逆に正常終了しなかった場合、restartPolicyに沿って再実行することになります。種類としてNeverとOnFailureがあります。Neverは、新規にPodを作って再実行、OnFailureは、既存Podを使って再実行するそうです。ただし、データ自体は消失することになるので、ご注意下さい。
completionsは目標成功数で、parallelismは並列数、backoffLimitは失敗許容値です。
目的に合う設定にすれば良いですね。
また、completionsを未指定にするとjobを止めるまでずっと動き続けます。backoffLimitを未指定にすると6回までとなります。んー、特に興味が惹かれることもなく、終わります。笑
CronJob
Jobをスケジュールされた時間で実行するリソース。
DeploymentとReplicaSetの関係と似ていて、Cronjobがjobを管理する。1分毎に50%の確率で成功するjobを用意して、試してみます。
sample-cronjob.yamlapiVersion: batch/v1beta1 kind: CronJob metadata: name: sample-cronjob spec: schedule: "*/1 * * * *" concurrencyPolicy: Allow startingDeadlineSeconds: 30 successfulJobsHistoryLimit: 5 failedJobsHistoryLimit: 3 suspend: false jobTemplate: spec: completions: 1 parallelism: 1 backoffLimit: 1 template: spec: containers: - name: sleep-container image: nginx:1.12 command: - "sh" - "-c" args: # 約50%の確率で成功するコマンド - "sleep 40; date +'%N' | cut -c 9 | egrep '[1|3|5|7|9]'" restartPolicy: Neverpi@raspi001:~/tmp $ k apply -f sample-cronjob.yaml pi@raspi001:~/tmp $ k get all NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE cronjob.batch/sample-cronjob */1 * * * * False 0 <none> 9s時間がくるまで、job,podは作成されないようです。
数分待ってみました。pi@raspi001:~/tmp $ k get all NAME READY STATUS RESTARTS AGE pod/sample-cronjob-1557115320-dsdvg 0/1 Error 0 2m18s pod/sample-cronjob-1557115320-qkgtp 0/1 Completed 0 87s pod/sample-cronjob-1557115380-r57sw 0/1 Completed 0 78s pod/sample-cronjob-1557115440-2phzb 1/1 Running 0 17s NAME COMPLETIONS DURATION AGE job.batch/sample-cronjob-1557115320 1/1 105s 2m18s job.batch/sample-cronjob-1557115380 1/1 52s 78s job.batch/sample-cronjob-1557115440 0/1 17s 17s NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE cronjob.batch/sample-cronjob */1 * * * * False 1 20s 3m12s名前の命名ルールがあるので、どう関連しているのか一目瞭然ですね。
Podが残っているのは、failedJobsHistoryLimitとsuccessfulJobsHistoryLimitの値の影響ですね。
logで確認できるように残しておくそうですが、ログ収集基盤に集約した方が良いとも言われています。途中で止めたいときは、spec.suspendをtrueにすることで実現可能になります。
同時実行する制限として、concurrencyPolicyがあり、Allow,Forbid,Replaceがあります。
Allowは、特に制限しない。
Forbidは、前のjobが終わらない限り実行しない。
Replaceは、前のjobを削除し、jobを実行する。遅延がどのぐらい許容できるかは、startingDeadlineSecondsで指定します。
こちらも、特に何事もなく終わりました。笑
お片付け
pi@raspi001:~/tmp $ k delete -f sample-statefulset.yaml -f sample-job.yaml -f sample-cronjob.yaml pi@raspi001:~/tmp $ k delete pvc www-sample-statefulset-{0,1,2,3}終わりに
ようやく、workloadsが終わりました。最後はざっくり進めてしまった感がありました。
次回はこちらです。
- 投稿日:2019-05-06T13:01:24+09:00
Dockerではじめる『Everyday Rails - RSpecによるRailsテスト入門』
概要
Railsチュートリアルも完了し、さて次は何をしようかと思案していたところ、
『Everyday Rails - RSpecによるRailsテスト入門』がゴールデンウィークセール中でしたので、これはと思い購入。
Everyday Rails… Aaron Sumner 著 et al. [Leanpub PDF/iPad/Kindle]
Railsチュートリアルに引きづつき、Dockerで環境を構築。環境
PC:MacBookPro
docker for mac環境構築
RSpec用のコンテナ準備
まずは、ターミナルで適当なフォルダを作成して、カレントディレクトリに移動。
mkdir myRspec && cd $_続いてDockerコンテナを起動。
$ docker container run -it --name myRspec -p 3000:3000 -v `pwd`:/myapp ruby:2.4.6 /bin/bash内容について軽く補足
*-it
: 標準入出力を繋げる。要するにターミナルで起動したコンテナを操作できるようにする。
*--name
: 名前付け。あるとわかりやすい。
*-p 3000:3000
: ホストとなるOSとDockerイメージのポートを繋げる。
*-v `pwd`:/myapp
: ホストのカレントディレクトリと、Dockerのmyappディレクトリを同期させる。
(同時にコンテナ内のルート直下にmyappディレクトリも作成される)
*ruby:2.4.6
: 引っ張ってくるDocker Image。サンプルアプリはRuby2.4なので今回はタグで2.4.6を指定。
*/bin/bash
:実行するコマンド。ターミナルの最初の部分が
USERnoMacBook-Pro:~
からroot@:(12文字の英数字)
に変わっていたら成功。
そのまま、次のコマンドを実行。$ apt update $ apt install -y nodejsGithubからサンプルアプリケーションをクローン
サンプルアプリケーションはGitHubにあります。
https://github.com/everydayrails/everydayrails-rspec-2017
クローンの手順はやったことがなかったので、ググりながら進めます。$ git clone https://github.com/everydayrails/everydayrails-rspec-2017.git
everydayrails-rspec-2017
というディレクトが作成され、コードが取得できました。
まずはcd everydayrails-rspec-2017/
でディレクトリに移動。
この状態では、Masterブランチのみ取得している状態です。
ここから2章開始の状態にするために、本文に従ってブランチを切り替えます。$ git checkout -b my-02-setup origin/01-untestedこれで、ソースコードの準備が整いました。先述の通り、ホストの myRspecディレクトリはmyappディレクトリと同期しています。
ホストPCのmyRspec下のeverydayrails-rspec-2017
をVSCodeでひらけば、そのまま開発環境として利用できます。
ただし、Railsコマンドの実行などは、Dockerコンテナを動かしているターミナルからでないと使えません。仕上げ
ターミナルで最後の仕上げです。
$ bundle install $ bin/rails db:create:all $ bin/rails db:migrate RAILS_ENV=developmentこれで準備完了のはずです。ターミナルに
rails s
と入力し、ブラウザからhttp://localhost:3000/
に接続してみます。問題なく動いているようですね。それでは勉強開始!
参考
- 投稿日:2019-05-06T08:41:29+09:00
「docker-compose up」をしようとしたところ「OCI runtime create failed」
DockerでPHPの開発環境を構築していたところエラーが発生
環境
- macOS mojava(バージョン10.14.4)
- Docker version 18.09.2
エラーの内容
docker-compose up -dでコンテナを一斉に立ち上げた際に以下のエラーが発生
Creating php_mysql_nginx_db_1 ... error ERROR: for php_mysql_nginx_db_1 Cannot start service db: OCI runtime create failed: container_linux.go:344: starting container process caused "process_linux.go:424: container init caused \"rootfs_linux.go:58: mounting \\\"/Users/UserName/WorkSpace/php_mysql_nginx/mysql/data\\\" to rootfs \\\"/var/lib/docker/overlay2/51d8c0970ffca60b031d04ce28ec8224c6d81ec33635095cc3e2bb6b78cc1a53/merged\\\" at \\\"/var/lib/docker/overlay2/51d8c0970ffca60b031d04ce28ec8224c6d81ec33635095cc3e2bb6b78cc1a53/merged/var/lib/mysql\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type ERROR: for db Cannot start service db: OCI runtime create failed: container_linux.go:344: starting container process caused "process_linux.go:424: container init caused \"rootfs_linux.go:58: mounting \\\"/Users/UserUserName/WorkSpace/php_mysql_nginx/mysql/data\\\" to rootfs \\\"/var/lib/docker/overlay2/51d8c0970ffca60b031d04ce28ec8224c6d81ec33635095cc3e2bb6b78cc1a53/merged\\\" at \\\"/var/lib/docker/overlay2/51d8c0970ffca60b031d04ce28ec8224c6d81ec33635095cc3e2bb6b78cc1a53/merged/var/lib/mysql\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type ERROR: Encountered errors while bringing up the project.
試したこと
1. docker volumeの削除
未使用のvolumeを一括削除
docker volume rm $(docker volume ls -qf dangling=true)それでもって再度
docker-compose up -d
を試みるも同じエラー
2. エラーが起きている箇所
docker-compose.yml
ファイルのdb箇所のvolume部分だからその部分のパスを変えるdb: image: mysql:5.7 ports: - 13306:3306 volumes: - ./mysql/data:/var/lib/mysqlこうなっていたのを以下のようにしたところ上手くいった!
db: image: mysql:5.7 ports: - 13306:3306 volumes: - ./mysql:/var/lib/mysql
- 投稿日:2019-05-06T07:21:01+09:00
メモ PHP docker xdebug
何がしたい
dockerコンテナとしてnginxとphp-fpm動作させて、PhpStormを使ったxdebugによるリモートデバッグできるまでをやる
Overview
- mac
- dockerはdocker-for-mac
- dockerコンテナ上でnginxが動作
- dockerコンテナ上でphp-fpmが動作
- with Xdebug
- PhpStormでXdebugリモートデバッグをする
Source
https://github.com/tttreal/local-php-debug
HTTPリクエストの処理概要
- nginxにport:80でリクエストが飛ぶ
- nginxからphp-fpmにport:9000でリクエストが飛ぶ(9000はphp-fpmのデフォルト設定)
- php-fpmはphpを動作させてレスポンスする
- php上でxdebugが動作する (
xdebug.remote_autostart=1
なので、勝手に動作)- php上のxdebugが、phpの処理中に、
xdebug.remote_host
に対してDBGp接続(これなに?)を試みる- 公式の説明gif: https://xdebug.org/images/docs/dbgp-setup.gif
- PhpStormがDBGpをうまくやっている・・・んだと思う(曖昧・・・?)
- nginxがphp-fpmのレスポンスをクライアントに返す
(URLに
?XDEBUG_SESSION_START=xxx
とかつける必要はないよ)docker
- nginx用のコンテナ、php-fpm用のコンテナ を作成する
- docker-for-macだと、"dockerのホストマシン"というのはmacではなく、mac上で動いている仮想環境
- コンテナから見たmacは、docker.for.mac.host.internal
- (ちなみにホストマシンはhost.docker.internal)
- せっかくなので、docker-composeを使う
Xdebug
- なんでかXdebugがデフォルトで使うport:9000と、php-fpmがデフォルトで使うport:9000とが、被っているので、どちらかを変える必要がある
- なんでやねん
- 設定例
xdebug.idekey="PHPSTORM" xdebug.remote_enable=1 xdebug.remote_host=docker.for.mac.host.internal xdebug.remote_port=9001 xdebug.remote_autostart=1 ; xdebug.remote_connect_back=1
- 設定を参考にしたページ・・・は色々あったけど、どれもしっくりこなかった。公式は https://xdebug.org/docs/all_settings
PhpStorm
よくわかんなかったんで適当にやった・・・
- 投稿日:2019-05-06T00:59:51+09:00
一足遅れて Kubernetes を学び始める - 06. workloads その2 -
ストーリー
- 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
- 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
- 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
- 一足遅れて Kubernetes を学び始める - 04. kubectl -
- 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
- 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
- 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
- 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -
前回
一足遅れて Kubernetes を学び始める - 05. workloads その1 -では、Pod,ReplicaSet,Deploymentの3つを学習しました。今回はDaemonSet,StatefulSet(一部)を学びます。
DaemonSet
ReplicaSetとほぼ同じ機能のリソース。
ReplicaSetとの違いは、各ノードに1つずつ配置するのがDaemonSet,バラバラなのがReplicaSet。
用途として、モニタリングツールやログ収集のPodに使う。さっそく、試してみます。
sample-ds.yamlapiVersion: apps/v1 kind: DaemonSet metadata: name: sample-ds spec: selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: nginx-container image: nginx:1.12 ports: - containerPort: 80pi@raspi001:~/tmp $ k apply -f . --all --prune daemonset.apps/sample-ds created pi@raspi001:~/tmp $ k get all -o=wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/sample-ds-wxzbw 1/1 Running 0 60s 10.244.2.24 raspi003 <none> <none> pod/sample-ds-xjjtp 1/1 Running 0 60s 10.244.1.37 raspi002 <none> <none> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d1h <none> NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR daemonset.apps/sample-ds 2 2 2 2 2 <none> 60s nginx-container nginx:1.12 app=sample-appReplicaSetと大きく違いはありません。
また、各ノードに対してpodが作られていることがわかります。Deploymentと似ているアップデート戦略があり、OnDeleteとRollingUpdate(デフォルト)があります。前者は、podを明示的に削除した(
k delete
)際に更新する戦略です。DaemonSetは、死活監視やログ収集に使うので、手動でのタイミングが効くOnDeleteが好まれます。後者は、Deploymentと同じ動きで、即時更新していく戦略です。ReplicaSetと似ているようで、機能的にはDeploymentに近い感じですね。ReplicaSetはpodが削除されたら複製されますけど、アップデートされません。DaemonSetはpodが削除されたら複製するし、アップデートもされます。試してみます。
sample-ds.yamlapiVersion: apps/v1 kind: DaemonSet metadata: name: sample-ds spec: selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: nginx-container image: nginx:1.13 ports: - containerPort: 80nginxのバージョンを1.12から1.13に変更しました。
pi@raspi001:~/tmp $ k apply -f . --all --prune daemonset.apps/sample-ds configured pi@raspi001:~/tmp $ k get all -o=wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/sample-ds-sx4mv 0/1 ContainerCreating 0 5s <none> raspi003 <none> <none> pod/sample-ds-xjjtp 1/1 Running 0 12m 10.244.1.37 raspi002 <none> <none> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d2h <none> NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR daemonset.apps/sample-ds 2 2 1 1 1 <none> 12m nginx-container nginx:1.13 app=sample-appapplyしてみると、一台ずつupdateされています(containerCreating)。Deploymentと違うのは、最大pod数が1のために、一時的にpodが機能しなくなるタイミングが生まれます(超過分の設定不可)。
pi@raspi001:~/tmp $ k delete pod sample-ds-sx4mv pod "sample-ds-sx4mv" deleted pi@raspi001:~/tmp $ k get all -o=wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod/sample-ds-hgvtv 0/1 ContainerCreating 0 6s <none> raspi003 <none> <none> pod/sample-ds-k8cfx 1/1 Running 0 4m38s 10.244.1.38 raspi002 <none> <none> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d2h <none> NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR daemonset.apps/sample-ds 2 2 1 2 1 <none> 17m nginx-container nginx:1.13 app=sample-apppodを削除しても、セルフヒーリングで復活します。
StatefulSet
ステートレスなpodではなく、DBのようなステートフルなpod向けのリソース。
podを削除しても、データを永続的に保存する仕組みが存在。
動作自体は、replicaSetと似ている。さっそく、試してみます。
sample-statefulset.yamlapiVersion: apps/v1 kind: StatefulSet metadata: name: sample-statefulset spec: serviceName: sample-statefulset replicas: 3 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: nginx-container image: nginx:1.12 ports: - containerPort: 80 volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: - ReadWriteOnce resources: requests: storage: 1GmountPathで指定したマウントしたいパスを、volumeClaimTemplatesでマウントしてくれます。 どこに?
![]()
Storageに関しては別で学習することにします。
ひとまず、applyします。pi@raspi001:~/tmp $ k apply -f . --all --prune daemonset.apps/sample-ds unchanged statefulset.apps/sample-statefulset created pi@raspi001:~/tmp $ k get pod NAME READY STATUS RESTARTS AGE sample-ds-hgvtv 1/1 Running 0 54m sample-ds-k8cfx 1/1 Running 0 58m sample-statefulset-0 0/1 Pending 0 5m19s pi@raspi001:~/tmp $ k describe pod sample-statefulset-0 ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 70s (x3 over 2m28s) default-scheduler pod has unbound immediate PersistentVolumeClaims (repeated 2 times)おや、Pendingになってしまいました。
pod has unbound immediate PersistentVolumeClaims
![]()
PersistentVolumeとPersistentVolumeClaims
PersistentVolume(永続的ボリューム)は、名前の通りで、データを永続的に保存しておく場所のリソースです。
マネージドサービスを利用すると、デフォルトでPresistentVolumeが用意されているそうです。
私の環境は、マネージドサービスではなく、自作環境であるので、PresistentVolumeを用意する必要があります。PersistentVolumeClaims(永続的ボリューム要求)は、これも名前の通りで、「PresistentVolumeを使わせて」というリソースです。
このリソースで、PresistentVolumeのnameを指定し、applyすることで、初めてマウントができます。
例えば、PodからPersistentVolumeClaimsの名前を指定してあげると、そのPodはClaimしたPersistentVolumeをマウントすることができます。
volumeClaimTemplatesというのは、「わざわざPersistentVolumeClaimsを定義しなくてもテンプレートに沿って書けばClaimsできるよ」というものです。で、何が問題だったの?
pod has unbound immediate PersistentVolumeClaims
のとおりで、「PersistentVolumeの要求をしたけど、Volume割当できなかったよ」とのことです。PersistentVolume(pv)があるのか確認してみます。
pi@raspi001:~/tmp $ k get pv No resources found.
たしかにないです。PersistentVolumeを用意しないといけないのですが、どうしましょう。
解決手段として考えたのは3点です。
- GCPやAWS,Azureのサービスを使う
- LocalVolumeを使う
- NFSを使う
1は、書いておいてなんですが、却下です。理由は、せっかくraspberryPiで構築したのでクラウドサービスを利用したくないからです。
2は、Kubernetes: Local Volumeの検証の参考にして試したのですが、 記事にも書いてあるとおり「Local Volumeは他のPodから共有で利用することができない」ため、statefulsetが
replica:1
でなければ動きません。それはそれで動くので学習になり良いのですが、せっかくならreplicaの制限なしにしたいです(ReadWriteManyにしたい)。3は、もう一台raspberryPiを用意して、それをNFSと見立ててPersistentVolumeにしてみる方法です。
3を進めようと思います。
NFS導入
サーバ設定
NFS用の新たなraspberryPiを用意します。設定手順はこちらを参考にしました。
その後の続きは下記です。NFSのホスト名は
nfspi
とします。~ $ slogin pi@nfspi.local pi@nfspi:~ $ sudo apt-get install nfs-kernel-server pi@nfspi:~ $ sudo vim /etc/exports/etc/exports/home/data/ 192.168.3.0/255.255.255.0(rw,sync,no_subtree_check,fsid=0)意味としては、「指定範囲のIPアドレスからのマウントを許可する」。オプションは、こちらを参照。
host ip iMac 192.168.3.3 raspi001(master) 192.168.3.32 raspi002(worker) 192.168.3.33 raspi003(worker) 192.168.3.34 nfspi(NFS) 192.168.3.35 pi@nfspi:~ $ sudo mkdir -p /home/data pi@nfspi:~ $ sudo chmod 755 /home/data pi@nfspi:~ $ sudo chown pi:pi /home/data pi@nfspi:~ $ sudo /etc/init.d/nfs-kernel-server restart pi@nfspi:~ $ service rpcbind status pi@nfspi:~ $ systemctl status nfs-server.service正しく設定されたか、iMacから確認してみます。
~ $ mkdir share ~ $ sudo mount_nfs -P nfspi.local:/home/data ./share/ ~ $ sudo umount shareOK
クライアント設定
各ノードに対して下記を実行します。
pi@raspi001:~ $ sudo apt-get install nfs-commonnfs-client導入
raspberryPi環境では、真っ白な状態なので、一からPersistentVolumeを用意する必要があります。それにはVolumeとなるStorageの型を用意する必要もあるのですが、Storage Classesを見る限り、NFS用の型は標準で存在しません。そこで、nfs-clientを使ってNFS用のStorageClassを作成します。
pi@raspi001:~ $ git clone https://github.com/kubernetes-incubator/external-storage.git && cd cd external-storage/nfs-client/ pi@raspi001:~/external-storage/nfs-client $ NS=$(kubectl config get-contexts|grep -e "^\*" |awk '{print $5}') pi@raspi001:~/external-storage/nfs-client $ NAMESPACE=${NS:-default} pi@raspi001:~/external-storage/nfs-client $ sed -i'' "s/namespace:.*/namespace: $NAMESPACE/g" ./deploy/rbac.yaml pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/rbac.yamlrbac.yamlにあるnamespaceを現在動かしている環境のnamespaceに置換して、applyしています。
pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/deployment-arm.yaml pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/class.yamldeployment-arm.yamlでは、NFSサーバのIPアドレス(192.168.3.35)とマウントパス(/home/data)を設定しました。
class.yamlが、今回欲していたNFSのstorageClass(managed-nfs-storage)になります。※ raspberryPiのイメージはRaspbianを使っているので、arm用のdeployment-arm.yamlを使います。Wiki
これに随分とハマってしまいました...pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/test-claim.yaml -f deploy/test-pod.yaml試しにマウント先にファイルが作成できているのかテストしています。確認します。
nfspiに移動
pi@nfspi:~ $ ls /home/dataあれば成功です。あれば、下記で片付けます。
pi@raspi001:~/external-storage/nfs-client $ k delete -f deploy/test-pod.yaml -f deploy/test-claim.yamlstatefulsetをリトライ
以上で、StorageClassを用意できました。よって後は、PersistentVolume作って、PersistentVolumeClaim作って...となる予定でした。
しかし、nfs-clientには、dynamic provisioningという機能が備わっており、PersistentVolumeを作らなくても、PersistentVolumeClaimするだけで良くなります。この件については、storageを学習する際に書きます。raspi001に移動して、sample-statefulset.yamlをもう一度applyします。
(storageClassName: managed-nfs-storageを追加, ReadWriteOnce→ReadWriteManyに変更)sample-statefulset.yamlapiVersion: apps/v1 kind: StatefulSet metadata: name: sample-statefulset spec: serviceName: sample-statefulset replicas: 3 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: nginx-container image: nginx:1.12 ports: - containerPort: 80 volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: - ReadWriteMany storageClassName: managed-nfs-storage resources: requests: storage: 1Gipi@raspi001:~/tmp $ k apply -f sample-statefulset.yamlnfapiに移動して、あるか確認。
pi@nfspi:~ $ ls -la /home/data total 20 drwxrwxrwx 5 pi pi 4096 May 5 17:18 . drwxr-xr-x 4 root root 4096 May 4 15:50 .. drwxrwxrwx 2 nobody nogroup 4096 May 5 17:17 default-www-sample-statefulset-0-pvc-5911505b-6f51-11e9-bb47-b827eb8ccd80 drwxrwxrwx 2 nobody nogroup 4096 May 5 17:18 default-www-sample-statefulset-1-pvc-5f2fd68e-6f51-11e9-bb47-b827eb8ccd80 drwxrwxrwx 2 nobody nogroup 4096 May 5 17:18 default-www-sample-statefulset-2-pvc-69bee568-6f51-11e9-bb47-b827eb8ccd80ありました! マウントできています!
お片付け
--prune
でも良いのですが、下記のほうが使いやすかったです。pi@raspi001:~/tmp $ k delete -f sample-ds.yaml -f sample-statefulset.yaml pi@raspi001:~/tmp $ k delete pvc www-sample-statefulset-{0,1,2}※
k get pv
とk get pvc
を試して頂き、今回作ったリソースがありましたら削除お願いします。?おわりに
StatefulSetを使える状態にするまでに記事が大きくなってしまいました。次回に詳しく学んでいこうと思います。笑
あと、nfs-clientを見て思ったのが、kubernetesのパッケージマネージャであるhelmを導入した方が、遥かに便利だと思いつつ、手動設定しました。。。
次回は、こちらです。
- 投稿日:2019-05-06T00:57:00+09:00
Dockerとは?Dockerと仮想マシンの違い
今回はDockerとは何か、Dockerと仮想マシンの違いについてを簡単に紹介します。
プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。Docerって何?
開発者やシステム管理者 がコンテナを使用してアプリケーションを開発、デプロイ(いつでも使える状態にする)、および実行するためのプラットフォームのことです。
ここで、コンテナとは、Dockerイメージ(コンテナを作成するためのファイルシステムや設定をまとめたもの)を起動し、その内部でアプリケーションが稼動している状態のことです。
Dockerの特徴は、アプリケーションとその実行環境、展開/操作方法(スクリプト)などをまとめて1つのパッケージにし、保存/配布しているDockerイメージのダウンロードや起動が非常に軽量なことです。Dockerと仮想マシンの違い
仮想マシンは、ホストOS上でハイパーバイザと呼ばれるソフトウェアを利用し仮想マシンを作成後、ゲストOSを動かし、その上でミドルウェアなどを動かします。
一方、Dockerでは、各コンテナはホストOSのカーネルは共有しつつも、各コンテナ内のアプリケーションは分離された場所で実行されます。
このように、プロセスやユーザなどを隔離することで、あたかも別のマシンが動いているかのように動かすことができ、軽量で高速に起動、停止などが可能です。