- 投稿日:2021-07-20T23:06:44+09:00
安易に軽量DockerイメージにAlpineを採用するのをやめよう
ありのまま起こったことを話します。 _人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人_ > 何もしてないのに急にdockerイメージのビルドに失敗するようになった <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^^Y^^Y^^Y^ ̄ 事件 先日あるプロジェクトでphp:7.4-fpm-alpineを使っていたらこんなエラーを吐いてdockerのイメージビルドがコケました。 make: *** [Makefile:195: gd.lo] Error 127 エラーを吐いたのはこのスクリプトだったのですが、こいつをこねくり回しても意味がなかったです。 RUN docker-php-ext-configure gd ~~~ 以下で議論されていますがalpine3.14固有の現象のようです。 https://gitlab.alpinelinux.org/alpine/aports/-/issues/12396 https://github.com/docker-library/php/issues/1130 幸い3.13に固定したところビルドは通るようになりました。 なんでこうなった? musl-libcには一般的な互換性が不足しているらしいです。 Ruby, Python, Node.js, phpあたりなどはネイティブでモジュールをバンドルしてるアプリケーションの場合、パフォーマンス劣化や互換性の問題にぶち当たる可能性があるそうです。 superuser.com 確認例 最新のphp:7.4-fpm-alpine /var/www/html # ldd $(which php) /lib/ld-musl-x86_64.so.1 (0x7fe7cfb34000) libargon2.so.1 => /usr/lib/libargon2.so.1 (0x7fe7ce915000) libxml2.so.2 => /usr/lib/libxml2.so.2 (0x7fe7ce7eb000) libssl.so.1.1 => /lib/libssl.so.1.1 (0x7fe7ce76a000) libcrypto.so.1.1 => /lib/libcrypto.so.1.1 (0x7fe7ce4e9000) libsqlite3.so.0 => /usr/lib/libsqlite3.so.0 (0x7fe7ce3fb000) libz.so.1 => /lib/libz.so.1 (0x7fe7ce3e1000) libcurl.so.4 => /usr/lib/libcurl.so.4 (0x7fe7ce367000) libonig.so.5 => /usr/lib/libonig.so.5 (0x7fe7ce2dd000) libedit.so.0 => /usr/lib/libedit.so.0 (0x7fe7ce2ab000) libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fe7cfb34000) liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7fe7ce288000) libnghttp2.so.14 => /usr/lib/libnghttp2.so.14 (0x7fe7ce260000) libbrotlidec.so.1 => /usr/lib/libbrotlidec.so.1 (0x7fe7ce253000) libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x7fe7ce1f7000) libbrotlicommon.so.1 => /usr/lib/libbrotlicommon.so.1 (0x7fe7ce1d4000) Debian10 root@743553942da7:/# ldd $(which php) linux-vdso.so.1 (0x00007ffc13565000) libargon2.so.1 => /usr/lib/x86_64-linux-gnu/libargon2.so.1 (0x00007f5156c2f000) libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007f5156c15000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f51569f7000) librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f51569ed000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f515686a000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f5156865000) libxml2.so.2 => /usr/lib/x86_64-linux-gnu/libxml2.so.2 (0x00007f51566b8000) libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1 (0x00007f5156626000) libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007f515633d000) libpcre2-8.so.0 => /usr/lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f51562b8000) libsodium.so.23 => /usr/lib/x86_64-linux-gnu/libsodium.so.23 (0x00007f5156261000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f51560a0000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f515607d000) /lib64/ld-linux-x86-64.so.2 (0x00007f51570ea000) libicui18n.so.63 => /usr/lib/x86_64-linux-gnu/libicui18n.so.63 (0x00007f5155da2000) libicuuc.so.63 => /usr/lib/x86_64-linux-gnu/libicuuc.so.63 (0x00007f5155bd3000) libicudata.so.63 => /usr/lib/x86_64-linux-gnu/libicudata.so.63 (0x00007f51541e3000) liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007f51541bb000) libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5154035000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f515401b000) まとめ 何もしてないのに壊れたってあるんだなぁ 他のベースイメージの軽量化も進んでおり、Alpineが軽量イメージの定番というのは違うのかなと言う気がしています。 安直にAlpineに走らず、最初は言語の標準イメージを使って見るなどしてみるといいと思います。 ※Debian Slim等の軽量化されてイメージがそこそこあるので安易にApineを採用せず他のDockerイメージと比較検討したほうがいいかもしれません。 Alpineで採用されているmuslにはglibcと比較した際に互換性上の問題があることがある muslを採用したイメージを使った際には思わぬパフォーマンス劣化に遭遇することがあるので、該当しないかどうかよく確認した方が良い glibcを後から入れる手もあるが、それなら言語の標準イメージなどDebian Slimをベースに作られてるイメージを使った方が合理的と思われる 開発前にデリメリをよく把握した上で選定しよう 参考文献 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話
- 投稿日:2021-07-20T22:11:13+09:00
【AWS】ECS構築の基礎 −機密情報の参照−
はじめに Secrets Manager、SSMパラメータストア、S3に機密情報を環境変数として保存して、実行タスクから参照できるかどうか確かめてみました。 ECS初心者向けの記事を何本か書いていますので、どれか参考になれば幸いです。 AWS Secrets Manager AWS Secrets Managerは、AWS内リソース、オンプレミス環境、またはサードパーティアプリケーションにアクセスするための各種機密情報の管理を、簡単にするAWSのサービスです。 Secrets Managerを利用することで、AWS CLI、API、SDKから機密情報へアクセスすることができます。 アプリケーション内に機密情報をハードコードする必要がなくなり、情報漏えいのリスクを減らすことができるようになります。 また、スケジュールに基づいた機密情報のローテーションに対応しており、古くなった機密情報の更新の手間を軽減することができます。 RDSやAuroraでは、機密情報のローテーションとデータベース接続情報のローテーションが統合されています。 SSMパラメータストア SSMパラメータストアはSecrets Manager登場前からある機密情報管理サービスです。 Secrets Managerとの違いを比較すると以下のようになります。 Secrets Manager SSMパラメータストア 値段 高価 安価 ローテーション 可能 不可 KMS暗号化 必須 任意 CloudFormationとの統合 あり あり 実装 ECSで機密情報を取り扱うにあたり、以下の3つの方法あります。 下2つの方法について試しに実装してみます。 タスク定義にハードコーディング SSM/Secrets Managerで管理 S3で管理 準備 ecsTaskExecutionRoleのパーミッションの修正、および新しくタスク定義とサービスの作成を行います。 まずはecsTaskExecutionRoleにSecrets ManagerやS3に保管したパラメータの取得に関するインラインポリシーを追加します。 <account-id>にはアカウントIDを記述します。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue", "ssm:GetParameters", "kms:Decrypt" ], "Resource": [ "arn:aws:secretsmanager:ap-northeast-1:<account-id>:secret:*", "arn:aws:ssm:ap-northeast-1:<account-id>:parameter/*", "arn:aws:kms:ap-northeast-1:<account-id>:*" ] }, { "Effect": "Allow", "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::ecs-course-<account-id>/environment-demo.env" ] }, { "Effect": "Allow", "Action": [ "s3:GetBucketLocation" ], "Resource": [ "arn:aws:s3:::ecs-course-<account-id>" ] } ] } 名前を記述してポリシーをアタッチします。 次に、タスク定義を新しく作成します。 EC2起動タイプを選択して、タスク定義名の記述とタスク実行ロールの選択を行います。 コンテナの追加を行います。 今回はubuntu:latestのイメージを取得します。 エントリポイントを追加しタスク定義を作成します。 最後に、作成したタスク定義を元にサービスを新しく作成します。 SSMパラメータストア 下準備が整ったので、SSMパラメータストアから実装を行っていきます。 Systems Managerのパラメーターストアを開いて、"パラメータの作成"を押します。 パラメータストアでは機密データの暗号化が任意です。 暗号化の可否でそれぞれ検証してみます。 暗号化を行わないケースでは、タイプに"文字列"を選択します。 暗号化を行うケースでは、タイプに"安全な文字列"を選択します。 作成したタスク定義を開いて、"新しいリビジョンの作成"を押します。 コンテナ名をクリックして、環境変数を追加します。 "値を追加してください"には、以下のコマンドで取得したARNの中身を記述します。 $ aws ssm get-parameter --name plain-from-ssm { "Parameter": { "Name": "plain-from-ssm", "Type": "String", "Value": "plain variable from SSM parameter store", "Version": 1, "LastModifiedDate": "2021-07-13T07:04:13.100000+09:00", "ARN": "arn:aws:ssm:<<region>>:<<accountid>>:parameter/<<param-name>>", "DataType": "text" } } plain-ubuntu-serviceに最新リビジョンのタスク定義を反映させます(2(latest)を選択します)。 EC2を開いて、パブリック IPv4 DNSをコピーします。 EC2にSSHログインします。 $ ssh -i ~/.ssh/udemysample.pem ec2-user@ec2-18-183-76-54.ap-northeast-1.compute.amazonaws.com ubuntuの文字列を含むコンテナの中身を表示します。 [ec2-user@ip-172-16-0-156 ~]$ docker container ls | grep ubuntu b08bf03cc6c8 ubuntu:latest "sleep infinity" 17 minutes ago Up 17 minutes ecs-td-plain-ubuntu-2-plain-ubuntu-ac97cdd3abf9dfc1db01 以下のコマンドで指定したコンテナの環境変数envを参照します。 [ec2-user@ip-172-16-0-156 ~]$ docker exec -it b08bf03cc6c8 "env" plain-from-ssmとencrypted-from-ssmが確かに含まれています。 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=b08bf03cc6c8 TERM=xterm ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/f352209c-4de8-4214-9003-c66f42856952 ECS_CONTAINER_METADATA_URI_V4=http://169.254.170.2/v4/f352209c-4de8-4214-9003-c66f42856952 plain-from-ssm=plain variable from SSM parameter store encrypted-from-ssm=encrypted variable from SSM Parameter store AWS_EXECUTION_ENV=AWS_ECS_EC2 HOME=/root Secrets Manager 次に、Secrets Managerで実装を行います。 Secrets Managerを開いて、"新しいシークレットを保存する"を押すと以下の画面になります。 シークレットの種類を選択し、シークレットに格納するキーと値のペアを指定します。 シークレットの名前を記述します。 とりあえず自動ローテーションは無効にします。 最後のステップでシークレットを取得するためのサンプルコードが表示されます。 "保存"を押します。 作成したシークレットを開いて、シークレットのARNをコピーします。 作成したタスク定義を開いて、"新しいリビジョンの作成"を押します。 コンテナの編集から環境変数を追加します。 値にはコピーしたARNをペーストします。 サービスの設定で新しいリビジョン3(latest)を選択し、新しいデプロイの強制にチェックをつけて更新します。 SSMのときと同様に、SSHログインしてからdocker container ls | grep ubuntuでコンテナIDを調べ、docker exec -it [container-id] "env"を実行します。 すると、以下のように追加した環境変数secret-from-secrets-managerが表示されます。 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=04cf089cf8e0 TERM=xterm AWS_EXECUTION_ENV=AWS_ECS_EC2 ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/541d9a24-900e-4c23-bc22-2e055c039ee6 ECS_CONTAINER_METADATA_URI_V4=http://169.254.170.2/v4/541d9a24-900e-4c23-bc22-2e055c039ee6 plain-from-ssm=plain variable from SSM parameter store secret-from-secrets-manager={"key-from-secrets-manager":"This is the value from secrets manager"} encrypted-from-ssm=encrypted variable from SSM Parameter store HOME=/root Amazon S3 最後に、S3への環境変数の保存を行います。 以下のコマンドで、UserId、Account、Arnを確認します。 $ aws sts get-caller-identity もし別のIAMユーザが設定されていたら、以下のコマンドを実行して、アクセスキーIDやシークレットアクセスキーを設定します。 $ aws configure s3 mbコマンドで新しくバケットを作成します。 バケット名はグローバルで一意(世界に1つだけ)である必要があります。 今回はバケット名をecs-course-[Account]としました。 $ aws s3 mb s3://[バケット名] envファイルを新しく作成します。 $ vi environment-demo.env envファイルに環境変数を記述して保存します。 variable1-from-s3=value1 variable2-from-s3=value2 作成したenvファイルをS3バケットにアップロードします。 $ aws s3 cp ./environment-demo.env s3://ecs-course-s3://[バケット名]/ 以下のコマンドでバケット内のファイルを確認することができます。 $ aws s3 ls s3://[バケット名] 2021-07-18 22:17:24 50 environment-demo.env ここでECSのタスク定義から新しいリビジョンを作成します。 ”コンテナの定義”で環境ファイルを追加します。 ロケーションにはarn:aws:s3:::[バケット名]/environment-demo.envを記述します。 サービスの更新を行います。 リビジョンの変更と新しいデプロイの強制にチェックします。 docker container lsでコンテナIDを控え、docker exec -it [コンテナID] "env"を実行します。 以下のように、envファイルの中身を確認することができます。 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=3a39f9eef85b TERM=xterm ECS_CONTAINER_METADATA_URI_V4=http://169.254.170.2/v4/46e249e6-1a76-4259-a97d-7e6f1e46e5c7 encrypted-from-ssm=encrypted variable from SSM Parameter store plain-from-ssm=plain variable from SSM parameter store secret-from-secrets-manager={"key-from-secrets-manager":"This is the value from secrets manager"} variable1-from-s3=value1 variable2-from-s3=value2 AWS_EXECUTION_ENV=AWS_ECS_EC2 ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/46e249e6-1a76-4259-a97d-7e6f1e46e5c7 HOME=/root Secrets Manager活用例 -プライベートレジストリのDockerイメージの使用- Secrets Managerにクレデンシャルを保管して、プライベートレジストリのDockerイメージからタスクを起動することができます。 Secrets Manager開き、"その他のシークレット"を選択して、 usernameとpasswordを記述します。 シークレットの名前を記述します。 シークレット作成後、ARNをコピーします。 新しくタスク定義を作成します。 コンテナの追加でコンテナ名とイメージを記述します。 また、"プライベートレジストリの認証"にチェックをつけて、Secrets ManagerのARNをペーストします。 クラスターから新しいタスクを実行します。 無事にタスクが実行されれば成功です。 参考資料
- 投稿日:2021-07-20T18:55:39+09:00
Dockerの本を読んでみた -3章編-
前回までのおさらい 前回はDockerの本を読むと宣言し、2章まで読み切った 今回は3章から読み進めていくことを想定とし、まとめてみる アプリケーションとコンテナの粒度の話 この章では、アプリケーションを実際に構築するとなった場合、どのように構築していくことが望ましいのか、を考える章だった 1コンテナ=1プロセス? Dockerはアプリケーションのデプロイに特化した技術なので、1コンテナ=1プロセスが望ましいのではないかと考えるかもしれない。 しかし、結論から言うとそれは間違いで、厳密にこれを意識しすぎるとプロセスが逆に複雑化する よって、「スタック」を構築していくことが重要とあった。スタックの説明は以下に追記 スタックについて 1コンテナに1つの関心事 本の中では、公式の文章を引用している Each container should have only one concern コンテナは1つの関心ごとだけに集中すべきだ 1つの関心事、これは1つのコンテナはある1つの役割や問題領域(ドメイン)のみにフォーカスされるべきだとの主張とのこと。 コンテナを使って構築を考える時、 「それぞれのコンテナが担うべき役割を適切に見定め、かつそれがレプリカとして複製された場合でも副作用なくスタックとして正しく動作できる状態になるか?」 という考えを念頭に設計すると良い。と本内では述べている。 コンテナのポータビリティ Dockerのメリットとして、ポータビリティ(可搬性)が挙げられる ポータビリティを噛み砕いてまとめると、 ・アプリとインフラを「コンテナ」によって分離できる →管理がしやすい ・Dockerがインストールされているホストであれば、ホストOSも問わない →実行環境がオンプレでもクラウドでも関係なく動作する というメリットがあると述べているが、実際は上の通りのポータビリティを絶対的に持っている訳ではない。と締め括っている。 「スタティックリンク」だとか「ダイナミックリンク」だとか良くわからない単語がまた出てきたので、その時は以下サイトを見て対処した コンテナを使う動機 なぜDocker(コンテナ)が期待されるか Dockerフレンドリなアプリケーション アプリケーションはDocker化しやすいとのこと。この章ではアプリを構築する為必要な要素について述べられていた。 そもそもアプリを作る時の話 アプリを作る時、一般的に再利用性や柔軟性を持たせる為にオプションを持たせ、それによって動作を制御するのが一般的とのこと。 そして、柔軟性も兼ね備えたDockerを使い、コンテナにアプリを載せて挙動を制御するには、さまざまな方法がある。 ・実行時引数 ・設定ファイル ・アプリケーションの挙動を環境変数で制御 ・設定ファイルに環境変数を埋め込む 上記のような手法がある中で、本の中では 環境変数で制御することのが推奨されている。 アプリケーションの挙動を環境変数で制御するとは? 環境変数とはーの概念は以下から学びました。 この章の要点をまとめると、 メリット:「毎日Dockerイメージのビルドを必要としなくて良いこと」 ※環境変数はアプリとは別のリポジトリで管理し、運用していくのが望ましいとしている。 デメリット:「環境変数はシンプルさが特徴として上がっているので、階層構造を持っていないので、JSONやXMLなど回想的なデータ構造を持てるファイル方式に比べると、アプリケーション側でのオブジェクトへのマッピング処理が手間がかかりがちになる」 となる。 ただし、Go言語であれば、カンマ区切りの環境変数の値を配列変数にマッピングできるライブラリも存在するらしい。 永続データをどう扱うのか? Dockerコンテナを実行中に書き込まれたファイルは、ホスト側にファイル・ディレクトリをマウントしない限り、コンテナを破棄したタイミングでディスクから消去される。 一度コンテナを削除してしまうと、コンテナ自体再現性がない為、全く同じものを作ることもできない。 よって、削除は慎重に行う必要がある。 また、新しいバージョンのコンテナがデプロイされても、以前のバージョンのコンテナで利用していたファイル・ディレクトリをそのまま継続して利用できるようにする必要もある。 上記のような、継続して利用する為の手段として、「Data Volume」という概念が存在する。 Data Volume Dockerコンテナ内のディレクトリを、ディスクに永続化さする為の仕組みのこと 特徴を以下に挙げる ・ホスト・コンテナ間でのディレクトリの共有・再利用が可能になる! ・Docker Volumeを設定していると、初回コンテナ作成時にホスト側で指定したパスで共有され、コンテナを停止・破棄した後もパスは残る つまり、イメージを更新して新しくコンテナを作成しても、同じData Volumeを利用し続けることができるのだ。(作り直しにならないのは便利) コマンドで使うと以下のような構文になる #----- 構文 -----# # docker container run options -v ホスト側ディレクトリ:コンテナ側ディレクトリ リポジトリ名:タグ名 コマンド コマンド引数 #----- 例 -----# # docker container run -v ${PWD}:workspace gihyodocker/imagemagick:latest convert -size 100×100 xc:#000000 /workspace/gihyo.jpg [host側ディレクトリ(カレントディレクトリ)] [gihyo~リポジトリ名] [convert~から引数] 上記例の訳文としては、 「ImageMagickに.jpgという100×100の黒い画像ファイルを生成させる」という意味があり、convertから後ろがコンテナに渡すアプリケーションの引数を指している。 Data VolumeとData Volume コンテナの違い 以下にData VolumeとData Volumeコンテナの違いを記す 項目 Data Volume Data Volume コンテナ 概要 コンテナ内のディレクトリをディスクに永続化する為の仕組みのこと データだけを持つコンテナ 特徴 ホスト側の特定のディレクトリに依存する性質あり 管理領域であるホスト側の/var/lib/docker/volumes/に配置されるDocker管理下のディレクトリのみに影響する→コンテナに与える影響を最小限に抑えられる 共有方法 コンテナ間とホスト間で直接ディレクトリを共有する コンテナ間でディレクトリを共有する 役割 コンテナ間とホスト間でやりとりする Docker Volumeへの仲介役も担うので、必ずしもホスト側のディレクトリを知らなくてもよい データを他のDockerホストとも共有したい! 他のDockerホストでも共有したいとなった場合、求められる順として以下のようになる ①Data Volumeコンテナからエクスポートしたいデータをファイルとしてホストに取り出す →コマンドとしてはファイルを取り出すと同時に圧縮する ②取り出したデータを展開し、Data Volumeコンテナを作成する →新規で作成したコンテナを指定してマウントすると、ホストにアーカイブ(圧縮)されたデータを取り出すことが可能となる 注意 「docker image save」は、Dockerイメージファイルをアーカイブする機能の為、Data Volumeには適用されない コンテナ配置戦略 今まで1つのホストに配置することを考えてきたが、それでは多くのリクエストをさばく必要のある実用的なシステムには向かない そこで、どのように配置するのが望ましいのか、この章で説明していた。 Docker Swarm Docker Swarmは、一言で言うと、複数のDockerホストを束ねてクラスタ化する為のツールと言える。 ・コンテナオーケストレーションシステムの1つ また、コンテナオーケストレーションに関係する単語がいくつかあるので、以下に記す。 Dockerコンテナオーケストレーションに関わる単語 名称 役割 対応コマンド Compose 複数のコンテナを使うDockerアプリケーションの管理(主にシングルホスト) docker-compose Swarm クラスタの構築や管理を担う(主にマルチホスト) docker swarm Service Swarm前提、クラスタ内のService(1つ以上のコンテナの集まり)を管理する docker service Stack Swarm前提、複数のServiceをまとめたアプリケーション全体の管理 docker stack Docker swarmの使い方 正直、本に記載されている内容よりも、qiitaに記載されている内容を元に学習した方が渉ったので、こちらを紹介 Docker Swarm 入門ハンズオン Docker Service Serviceの特徴は以下の通り ・レプリカ数(コンテナ数)を制御することで、容易にコンテナを複製できる ・複数のノードに配置出来る為、スケールアウトしやすい ・Serviceによって管理される複数レプリカは、Service名で名前解決出来、かつServiceへのトラフィックはレプリカへ分散される サービスの使い方は以下サイトの方がわかりやすかったので、紹介 Dockerの全体像を理解する-後編- Docker Stack Docker Stackの特徴は以下の通り ・Docker Serviceをグルーピングできる ・複数のServiceで形成されるアプリケーションのデプロイに役立つ 以上が大まかな3章の流れでした。 次は4章についてまとめていこうと思う。
- 投稿日:2021-07-20T14:02:09+09:00
【Alpine】tar で「xz: Cannot exec: No such file or directory」エラーが解凍・展開すると返ってくる
Alpine Linux で .tar.zx が解凍・展開できない Docker の Alpine イメージに最新版の shellcheck をインストールしようとしました。しかし、ダウンロードした .tar.zx 形式のアーカイブ・ファイルを解凍・展開しようとすると No such file or directory エラーが出ます。でも、ファイルは確かにあるのです。いや、本当に。 TL; DR (今北産業) xz コマンドが入っていないので No such file or directory と叱られます。 xz コマンドが入っていないので、当然 exec(実行)できないのでエラーになります。 apk add --no-cache xz でインストールします。(xz のスペルに注意) TS; DR (xz-utils なんてなかった) tarも入ってる $ which tar /bin/tar エラー内容の例 #8 0.467 Install shellcheck #8 0.471 TEMP PATH: /tmp/tmp.mnPaFm TEMP FILE: /tmp/tmp.mnPaFm/shellcheck-latest.linux.x86_64.tar.xz #8 0.485 Connecting to github.com (xxx.xxx.xxx.xxx:xxx) #8 0.814 Connecting to github-releases.githubusercontent.com (xxx.xxx.xxx.xxx:xxx) #8 1.183 saving to '/tmp/tmp.mnPaFm/shellcheck-latest.linux.x86_64.tar.xz' #8 1.386 shellcheck-latest.li 100% |********************************| 1350k 0:00:00 ETA #8 1.386 '/tmp/tmp.mnPaFm/shellcheck-latest.linux.x86_64.tar.xz' saved #8 1.389 tar (child): xz: Cannot exec: No such file or directory // <- ここ #8 1.389 tar (child): Error is not recoverable: exiting now #8 1.390 tar: Child returned status 2 #8 1.390 tar: Error is not recoverable: exiting now よく確認もせず apk add xz-utils したら、そんなパッケージはないと叱られます。「んなこたーない」と、Alpine 本家のパッケージ一覧を検索したら出て来ました。xz だけで良かった。 xz | edge | Packages @ pkgs.alpinelinux.org apk add --no-cache xz 「"alpine" tar xz: Cannot exec: No such file or directory」でググっても apt や apt-get の情報ばかりで、Alpine の情報がすぐに出てこなかったので、自分のググラビリティとして。 おまけ (shellcheck を Alpine Docker に入れるワンライナー) shellcheckをAlpineのDockerにインストールするワンライナー RUN echo "Install shellcheck" \ && name_file_arch="shellcheck-latest.linux.$(uname -m).tar.xz" \ && url_download="https://github.com/koalaman/shellcheck/releases/download/latest/${name_file_arch}" \ && path_dir_tmp=$(mktemp -d) \ && path_file_arch="${path_dir_tmp}/${name_file_arch}" \ && wget -P "${path_dir_tmp}" "$url_download" \ && tar x -v -f "$path_file_arch" -C "$path_dir_tmp" \ && cp "${path_dir_tmp}/shellcheck-latest/shellcheck" "${GOPATH:?Undefined}/bin/shellcheck" \ && shellcheck --version \ && rm -rf "${path_dir_tmp:?Undefined}"
- 投稿日:2021-07-20T12:50:58+09:00
共同開発まとめ
初めに 実務に近い経験を積みたい!と思い、共同開発のコミュニティに参加しECサイトを作成しました。 共同開発するにあたって苦労した点や、各機能実装の際に参考にした記事や教材について記事にまとめました。 アプリの概要 ご当地の美味しいものをお取り寄せできるECサイト。 アプリURL https://www.tomtom-blog.net ※テストアカウント メールアドレス test@co.jp パスワード123456789123でログインできます。 GitHub https://github.com/KawataTsutomu/yanbaru_ec_traning 使用画面のイメージ 使用技術 フロントエンド HTML / CSS / Bootstrap バックエンド PHP 7.2.34 Laravel 6.18.36 インフラ Vagrant + VirtualBox + Docker CentOS 7.3.1611 Apache 2.4.6 MySQL 5.7 / PHPMyAdmin AWS ( VPC, EC2, RDS, IAM, IGW, ELB, Route53 ) 開発環境、インフラ構成図 AWSデプロイ参考記事 VPC構築参考 Docker環境構築 機能一覧 ログイン関連 ログイン、ログアウト機能 ユーザー登録関連 新規登録、ユーザー情報確認、編集、退会機能 商品関連 商品詳細、検索機能 注文関連 カート、注文詳細、キャンセル機能 私は主にマイグレーション・シーダーファイル作成、ユーザー情報確認・修正、注文詳細を担当しました。 DB設計 ER図 苦労したこと まず何をすればいいんだ...?と何も分からないところからスタート。考えているだけでは始まらない!とりあえず手を動かそう!とcodeを書いてみるもエラー連発で泣きそうになりました...w ここでは特に印象に残っている点をまとめます。 環境構築 Vagrantを用いてDocker動作環境を構築し、Laravel立ち上げに成功! 翌日、PCを再起動し、作業を始めようとするapacheだけ立ち上がらない... 原因は、正しい手順でDockerとVagrantを停止していなかったからで、コンテナを壊してしまっていました...。 docker-compose stop でDocker停止 vagrant suspend でvagrant停止 パソコンをシャットダウン することで防ぐことができました。パソコンの電源を切るのと同じように正しい手順があることを学びました。 Git操作 意図して変更していないファイルも差分としてpushしてしまっていました。 commit,pushする前に何を変更した確認することを怠っていました。 色々な方法がありますが、VSCodeで確認するのが自分にあっているように感じました。 ①サイドバーの「ソース管理」のアイコンをクリック ②変更に変更されたファイルが表示される ③ファイルをクリックすると変更内容が表示される。(左が変更前で右が変更後) 個人で開発する分にはそれほど問題にはならないが、複数人で作業する現場では注意しなくてはいけないということを学びました。 「git push origin master は悪魔のコマンド」という高額な損害賠償のエピソードを知り、個人で開発する時よく使ってしまっていたのでゾッとしました... DBからデータ取得 先に取りかかったユーザー情報確認画面では Controller UserController.php public function show() { $user = Auth::user(); return view('auth/user_info', ['user' => $user]); } View user_info.blade.php @extends('layouts.app') @section('content') <main> <div class="page-header mt-5 pt-5 text-center"> <h4>ユーザ情報</h4> </div> <div class="row mt-5 mb-5"> <div class="col-sm-6 mx-auto"> <div class="mb-3"> <p class="contents">ユーザID</p> <p class="contents-text">{{ $user->id }}</p> </div> <div class="mb-3"> <p class="contents">氏名</p> <p class="contents-text"> {{ $user->last_name }} {{ $user->first_name }}</p> </div> <div class="mb-3"> <p class="contents" id="content-address"> 住所 </p> <div class="contents-text"> <p class="mb-1">〒{{ $user->zipcode }}</p> <p class="mb-1">{{ $user->prefecture }}{{ $user->municipality }}{{ $user->address }}{{ $user->apartments }}</p> </div> </div> Authで欲しい情報(ログインしているユーザー情報)を取得できたので簡単だったので、次に担当した注文詳細画面で壁にぶち当たりました... Route web.php Route::resource('orders', 'OrderDetailController', ['only' => ['show', 'edit']]); Controller web.php class OrderDetailController extends Controller { private const SHIPMENT_STATUS_PREPARE = 1; private const SHIPMENT_STATUS_CANCEL = 2; public function show($id) { $orderDetail = OrderDetail::whereHas('order', function ($query) use($id) { $query->where('order_id', $id); }) ->select('order_detail_number') ->first(); // ログインしているユーザーの注文詳細、注文履歴で選んだ注文のキャンセル(注文状態)以外を取得 $ordersHistory = OrderDetail::with('Product.category', 'shipmentStatus') ->whereHas('order', function ($query) { $query->where('user_id', Auth::id()); }) ->where('order_detail_number', $orderDetail->order_detail_number) ->where('shipment_status_id', '<>', self::SHIPMENT_STATUS_CANCEL) ->get(); $userInfo = Auth::user(); $subtotal = 0; $total = 0; //注文状態の判定 $preparationOrder = OrderDetail::whereHas('order', function ($query) { $query->where('user_id', Auth::id()); }) ->where('order_detail_number', $orderDetail->order_detail_number) ->where('shipment_status_id', '=', self::SHIPMENT_STATUS_PREPARE) ->get(); //$shipment_status_flg = true なら準備中、false なら発送済 $preparationOrderFlg = $preparationOrder->isEmpty() ? false : true; return view('order/order_detail', compact('ordersHistory', 'userInfo', 'orderDetail', 'subtotal', 'total', 'preparationOrderFlg') ); } public function edit($id) { // ログインしたユーザーと同じuser_idなら選択した注文を準備中をキャンセルへ変更 $ordersHistory = OrderDetail::whereHas('order', function ($query) { $query->where('user_id', Auth::id()); }) ->where('order_detail_number', '=', $id) ->where('shipment_status_id', '=', self::SHIPMENT_STATUS_PREPARE) ->update(['shipment_status_id' => self::SHIPMENT_STATUS_CANCEL]); return redirect()->back();; } } しかしそのお陰で、クエリビルダを使ってデータベースを操作し、テーブルからデータを取得することや、定数を定義して数字に意味を持たせることによってcodeが読みやすくなることを学べました。 参考にしたURL https://readouble.com/laravel/5.7/ja/queries.html 終わりに 壁にぶち当たってメンバーに相談した際、嫌な顔せずに、dd( );を使ったデバッグの方法など色々教えてもらい、感謝してもしきれません。本当ありがとうございます。 自分もそんなチームを助けられるエンジニアになれるよう、勉強を続け知識を深めていきたいと思います。 動作確認した不備が見つかったので時間を作り直していきます。 技術的な記事ではありませんが、最後まで読んでいただきありがとうございました。
- 投稿日:2021-07-20T09:48:17+09:00
【Ansible on Docker】Ansibleのハンズオン環境をDockerで構築
Ansibleのハンズオン環境をDockerを用いて構築してみたいと思います。 構成は以下の通りです。 コントロールノード(Ansible) x 1 ターゲットノード x 3 ターゲットノードは調整可能ですので、適当な数に置き換えて実施して下さい。 ※説明不要の方は以下を参照してください https://github.com/Shoma-progr-0210/ansible-training 前提条件 Docker on Desktopがインストールされている Dockerの基礎知識がある Linuxコマンドの基礎知識がある ディレクトリ構成 ansible-training │ docker-compose.yml │ ├─ansible │ ansible.cfg │ Dockerfile │ ├─node │ Dockerfile │ └─work hosts playbook.yml コントロールノードの構築 ansible-training ├─ansible │ ansible.cfg │ Dockerfile コントロールノードのDockerfileを作成します。 今回はCentOS7のイメージを使用します。 最低限必要なものとして、sudoとSSH接続のためにopenssh-clientsをインストールしておきます。 CentOS7のイメージにはansibleが存在しないため、eptl-releaseをインストールしてから、ansibleのインストールを行います。 コントロールノードのDockerfile # CentOS7 FROM centos:7 # system update RUN yum -y update && yum clean all # install sudo RUN yum -y install sudo # install ssh clients RUN yum -y install openssh-clients # install ansible RUN yum -y install epel-release && \ yum -y install ansible && \ yum clean all # setttings ADD ansible.cfg /etc/ansible ハンズオンのためにAnsibleの設定変更を行います。 そのため、同じ階層にansible.cfgを作成して、DockerfileのADDで上書き出来るようにします。 内容は、以下です。 デフォルトのインベントリファイルを/root/work/hostsに変更 ssh接続の設定変更 Ansible.cfg [defaults] inventory = /root/work/hosts [ssh_connection] ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ターゲットノードの追加 ansible-training ├─node │ Dockerfile ターゲットノードのDockerfileを作成します。 こちらもCentOS7のイメージを使用します。 ssh接続のためにopenssh-serverをインストールします。 その他に以下を行います。 SSHの公開鍵作成 rootのパスワード設定 22番ポートの公開 SSHデーモンの起動 ターゲットノードのDcokerfile # CentOS7 FROM centos:7 # install ssh server RUN yum -y install openssh-server && yum clean all # create Public key RUN ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa RUN ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa # set to login as root RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config # set a password for root RUN echo "root:" | chpasswd # expose 22 port EXPOSE 22 # start up sshd CMD ["/usr/sbin/sshd", "-D"] サービスの定義(docker-compose.yml)の作成 ansible-training │ docker-compose.yml サービスの定義を記述するdocker-compose.ymlを作成します。 コントロールノードはansibleです。ボリュームとして、ホストの./workをコントロールノードの/root/workと共有します。 ターゲットノードはnode0x(例の場合、xは1-3)です。 version3.4よりサポートされたExtension Fieldを用いて、ターゲットノードの定義をx-node: &nodeと書くことで、変数のように使用しています。呼び出し側は*nodeです。 ターゲットノードのportsは必要ではありませんが、ハンズオンの結果の確認で使用するために定義しています。 ターゲットノード数を調整したい場合は、servicesを調整してください。 ターゲットノードは、ハンズオンの際にsystemdでhttpdを起動するため、/sbin/initで実行します。 docker-compose.yml version: '3.8' x-node: &node build: ./node privileged: true command: /sbin/init tty: true services: ansible: container_name: 'ansible' build: ./ansible tty: true working_dir: '/root/work' volumes: - ./work:/root/work node01: container_name: node01 <<: *node ports: - '8081:80' node02: container_name: node02 <<: *node ports: - '8082:80' node03: container_name: node03 <<: *node ports: - '8083:80' 作業ディレクトリの作成 ansible-training └─work hosts playbook.yml コントロールノードで作業するディレクトリを作成します。 このディレクトリをホスト側とDocker側で共有しますので、Dockerを削除しても作業内容が保存されます。 インベントリファイルの作成 hostsはAnsibleに設定するインベントリファイルです。 先ほど作成したansible.cfgでデフォルトのインベントリとして指定されおり、ターゲットノードをnodeグループとして設定しています。 docker-compose.ymlでターゲットノードを調整した場合は、hostsでも指定してください。 hosts [node] node01 node02 node03 playbookの作成 Ansibleでは、Playbookでターゲットノードの状態を定義することで管理を行います。 定義ファイルはYAML形式で行います。ファイル名は任意ですが、今回はわかりやすいようにplaybook.ymlとします。 以下のPlaybookは、Apache(httpd)をインストールして起動した状態を定義しています。 hosts: nodeで、先ほどのインベントリで定義したnodeグループに対して実行されます。 playbook.yml --- - name: deploy httpd server hosts: node become: yes gather_facts: no tasks: - name: install httpd yum: name: httpd state: latest - name: start & enabled httpd service: name: httpd state: started enabled: yes Ansibleハンズオン コンテナのビルド・起動 まずは、ansible-taining配下に移動してください。 (<BASE_DIR>はansible-trainingまでのフォルダパス置き換えてください。) ansible-trainingに移動 $ cd <BASE_DIR>/ansible-training 各ノードをビルドします。 コンテナのビルド $ docker-compose build --no-cache 各ノードを起動します。 コンテナの起動 $ docker-compose up -d コントロールノードの動作確認 起動が出来たら、コントロールノード(ansibleコンテナ)に入りましょう。 コントロールノードに入る $ docker-compose exec ansible bash インストールしたAnsibleの動作確認を行います。 pingモジュールを使用して、localhost(コントロールノード自身)に疎通確認を行います。 Ansibleの動作確認 $ ansible localhost -m ping 以下の通りに結果が表示されればAnsibleが正常にインストールされています。 確認結果 localhost | SUCCESS => { "changed": false, "ping": "pong" } ターゲットノードを操作 各ターゲットノードに対して、SSHで接続確認を行います。 この作業で、予めターゲットノードをSSHのknown_hostsに登録します。 node0xのxはターゲットノードの番号に置き換えてください。 接続を続けるかを聞かれた場合はyesと入力して下さい。 SSHのknown_hostsに登録 $ ssh node0x $ exit pingモジュールを使用して、ターゲットノードに対して疎通確認を行います。 ターゲットノードとの疎通確認 $ ansible node -m ping 以下のように、各ターゲットノードの結果が表示されれば接続は成功です。 確認結果 node01 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } node03 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } node02 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } Apache(httpd)をターゲットノードで起動 AnsibleはPlaybookという単位で、ターゲットノードの状態を定義することで操作を行えます。 今回は事前にhttpdのインストールと起動を行うPlaybookを作成してるので、実行しましょう。 playbook.yml を実行して、ターゲットノードに httpd をセットアップします。 Playbookの実行 $ ansible-playbook playbook.yml 実行結果 PLAY [deploy httpd server] ********************************************************************************************* TASK [install httpd] *************************************************************************************************** fatal: [node01]: FAILED! => {"changed": false, "msg": ["Could not detect which major revision of yum is in use, which is required to determine module backend.", "You can manually specify use_backend to tell the module whether to use the yum (yum3) or dnf (yum4) backend})"]} fatal: [node02]: FAILED! => {"changed": false, "msg": ["Could not detect which major revision of yum is in use, which is required to determine module backend.", "You can manually specify use_backend to tell the module whether to use the yum (yum3) or dnf (yum4) backend})"]} fatal: [node03]: FAILED! => {"changed": false, "msg": ["Could not detect which major revision of yum is in use, which is required to determine module backend.", "You can manually specify use_backend to tell the module whether to use the yum (yum3) or dnf (yum4) backend})"]} PLAY RECAP ************************************************************************************************************* node01 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 node02 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 node03 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 各ターゲットノードで、failed=1になるはずです。 エラーには色々書いてありますが、sudoがインストールされていないために、httpdのインストールで権限周りの問題が起きています。 ターゲットノードにsudoをインストールします。 Playbookを用意してもよいですが、ここではshellモジュールを使用して、直接sudoのインストールを実行します。 sudoのインストール ansible node -m shell -a 'yum -y install sudo' インストールが完了したら、再度Playbookを実行してみてください。 Ansibleは"冪等性"の概念があるため、Playbookの実行が途中で失敗しても、再実行することで整合性を保つことができます。切り戻しなどは必要ありません。 Playbookの再実行 $ ansible-playbook playbook.yml 実行結果 PLAY [deploy httpd server] ********************************************************************************************* TASK [install httpd] *************************************************************************************************** changed: [node02] changed: [node03] changed: [node01] TASK [start & enabled httpd] ******************************************************************************************* changed: [node03] changed: [node01] changed: [node02] PLAY RECAP ************************************************************************************************************* node01 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node02 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 node03 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 各ターゲットノードで、ok=2 changed=2になれば成功です。 これだけで全てのターゲットノードでhttpdのセットアップが完了です。 ターゲットノードで httpd が正常に動作しているかをブラウザで確認してみましょう。 xはターゲットノードの番号に置き換えてください。 node0x: http://localhost:808x この画面が開けば正常に動作しています。 さいごに Ansibleを手軽に触ることができるように、Dockerを用いてローカル環境構築する方法と簡単なハンズオンをまとめました。 ターゲットノード数の変更したり、httpdのセットアップ以外のPlaybookを作成したりして、Ansibleの理解に役立てていただければ幸いです。 参考