tag:crieit.net,2005:https://crieit.net/tags/AWS/feed 「AWS」の記事 - Crieit Crieitでタグ「AWS」に投稿された最近の記事 2024-03-05T17:01:50+09:00 https://crieit.net/tags/AWS/feed tag:crieit.net,2005:PublicArticle/18786 2024-03-05T17:01:50+09:00 2024-03-05T17:01:50+09:00 https://crieit.net/posts/AWS-EC2-IPv4-Route-53-IMDSv2 【AWS】EC2インスタンスに割当されたパブリックIPv4アドレスをRoute 53に登録するシェルスクリプト(IMDSv2版) <h2 id="参考記事"><a href="#%E5%8F%82%E8%80%83%E8%A8%98%E4%BA%8B">参考記事</a></h2> <p>【出典】クラスメソッド:【Route53】EC2 から自身に付与されたパブリックIP を Route53 に設定したい<br /> <a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/articles/route53-record-ip-change-by-aws-cli/">https://dev.classmethod.jp/articles/route53-record-ip-change-by-aws-cli/</a></p> <h2 id="IMDSv2が有効だと参考記事のシェルスクリプトは動かない!"><a href="#IMDSv2%E3%81%8C%E6%9C%89%E5%8A%B9%E3%81%A0%E3%81%A8%E5%8F%82%E8%80%83%E8%A8%98%E4%BA%8B%E3%81%AE%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AF%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%84%EF%BC%81">IMDSv2が有効だと参考記事のシェルスクリプトは動かない!</a></h2> <p>IMDSv1はこれで良いが、IMDSv2が有効だと以下ではパブリックIPv4アドレスが取得不可です</p> <pre><code class="bash">IP_ADDRESS=`curl -s http://169.254.169.254/latest/meta-data/public-ipv4` </code></pre> <p>IMDSv2でパブリックIPv4アドレスを取得する方法は以下が参考になります</p> <p>【出典】Amazon公式:インスタンスメタデータの取得<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html">https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html</a></p> <h2 id="IMDSv2対応版シェルスクリプト"><a href="#IMDSv2%E5%AF%BE%E5%BF%9C%E7%89%88%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88">IMDSv2対応版シェルスクリプト</a></h2> <p>Amazon公式情報を参考に修正したシェルスクリプトがこちら</p> <pre><code class="bash">#!/bin/bash DOMAIN_NAME="example.me" SUB_NAME="test01" TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` IP_ADDRESS=`curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/public-ipv4` HOSTED_ZONE_ID="ZONE-IDを入力" BATCH_JSON='{ "Changes": [ { "Action": "UPSERT", "ResourceRecordSet": { "Name": "'${SUB_NAME}'.'${DOMAIN_NAME}'", "Type": "A", "TTL" : 300, "ResourceRecords": [ { "Value": "'${IP_ADDRESS}'" } ] } } ] }' aws route53 change-resource-record-sets --hosted-zone-id ${HOSTED_ZONE_ID} --change-batch "${BATCH_JSON}" </code></pre> <h2 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h2> <p>IMDSv2についてよくわかっていなかったが、今回の件でIMDSv1との違いが少し理解できました。</p> arohajiro tag:crieit.net,2005:PublicArticle/18638 2023-11-02T10:39:54+09:00 2023-11-02T10:39:54+09:00 https://crieit.net/posts/AWS-AWS-IP-EC2-Instance-Connect 【AWS】AWS IPアドレスの範囲から、東京リージョンの EC2 Instance Connect の範囲を検索する <h2 id="確認済み環境"><a href="#%E7%A2%BA%E8%AA%8D%E6%B8%88%E3%81%BF%E7%92%B0%E5%A2%83">確認済み環境</a></h2> <ul> <li>EC2、AWS CloudShell、Macのターミナル</li> </ul> <h2 id="以下のコマンドを実行"><a href="#%E4%BB%A5%E4%B8%8B%E3%81%AE%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%92%E5%AE%9F%E8%A1%8C">以下のコマンドを実行</a></h2> <p>(EC2やmacは、curlやjqがインストールされていない場合にはインストールが必要です)<br /> 実行することで東京リージョンの EC2 Instance Connect の範囲を絞り込んで出力できます</p> <pre><code class="bash">$ curl -s https://ip-ranges.amazonaws.com/ip-ranges.json| jq -r '.prefixes[] | select(.region=="ap-northeast-1") | select(.service=="EC2_INSTANCE_CONNECT") | .ip_prefix' </code></pre> <h2 id="出力結果(2023/11現在)"><a href="#%E5%87%BA%E5%8A%9B%E7%B5%90%E6%9E%9C%EF%BC%882023%2F11%E7%8F%BE%E5%9C%A8%EF%BC%89">出力結果(2023/11現在)</a></h2> <pre><code>3.112.23.0/29 </code></pre> <h2 id="ふと思ったこと"><a href="#%E3%81%B5%E3%81%A8%E6%80%9D%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">ふと思ったこと</a></h2> <p>EC2 Instance Connect の範囲をマネージドプレフィックスリストに登録して、セキュリティグループで参照するようにしておけば、もし範囲が変わってもリストを更新するだけで済みそうですね。</p> arohajiro tag:crieit.net,2005:PublicArticle/18277 2022-08-12T19:16:44+09:00 2022-08-12T19:16:44+09:00 https://crieit.net/posts/Redshift-Serverless Redshift Serverless について見かけた課題と対応策 <p>Redshift Serverless について見かけた課題と対応策(2022年7月19日時点)をまとめました。GitHubリポジトリ「<a target="_blank" rel="nofollow noopener" href="https://yuzutas0.github.io/awesome-redshift-jp/docs/setup/faq.html">Awesome Redshift JP</a>」にまとめた内容の転記です。</p> <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <ul> <li>プレビュー料金に見える→金額あってそう</li> <li>名前空間やワークグループの削除エラー→英語表示</li> <li>ProvisionedとAPIが違う→ラッパーを使う</li> <li>時間課金でストリーミング不向き→二台構成 or マイクロバッチ化</li> </ul> <h1 id="Redshift Serverless の料金表が「プレビュー」のままに見える(2022年7月19日時点)"><a href="#Redshift+Serverless+%E3%81%AE%E6%96%99%E9%87%91%E8%A1%A8%E3%81%8C%E3%80%8C%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E3%80%8D%E3%81%AE%E3%81%BE%E3%81%BE%E3%81%AB%E8%A6%8B%E3%81%88%E3%82%8B%EF%BC%882022%E5%B9%B47%E6%9C%8819%E6%97%A5%E6%99%82%E7%82%B9%EF%BC%89">Redshift Serverless の料金表が「プレビュー」のままに見える(2022年7月19日時点)</a></h1> <p>日本語だと「プレビュー」の記載がありますが、英語だとPreviewの記載は外れています。 料金表の内容も同じなので、日本語の翻訳が間に合っていないようです。</p> <p>日本語の料金表:</p> <p><img src="https://camo.qiitausercontent.com/128c346fa77be45412ac0f1e49ed02bc701da75b/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3330383936312f62353232343065652d633930662d653561662d363238652d3332336430653436376162362e706e67" alt="日本語の料金表" /></p> <p>英語の料金表:</p> <p><img src="https://camo.qiitausercontent.com/83c7b4bc6caa3299f10be6889acc6d42f07a8469/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3330383936312f66656266336539302d616136352d326235632d646330642d3137376635636137336236322e706e67" alt="英語の料金表" /></p> <h1 id="Redshift Serverless の名前空間やワークグループの削除ができない(2022年7月19日時点)"><a href="#Redshift+Serverless+%E3%81%AE%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93%E3%82%84%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E5%89%8A%E9%99%A4%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%EF%BC%882022%E5%B9%B47%E6%9C%8819%E6%97%A5%E6%99%82%E7%82%B9%EF%BC%89">Redshift Serverless の名前空間やワークグループの削除ができない(2022年7月19日時点)</a></h1> <p>日本語表示だとコンソールでエラーになってしまう。</p> <blockquote> <p>Amazon Redshift Serverlessで名前空間もワークグループもコンソールのエラーで削除できない<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/Korosuke512tr/status/1547783609666265089">https://twitter.com/Korosuke512tr/status/1547783609666265089</a></p> </blockquote> <p>解決策1) 管理コンソールを日本語から英語に変える。</p> <blockquote> <p>管理コンソールの言語を英語に変更して試していただくと、削除できるようになるかもしれません。<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/simosako/status/1547790642926870528">https://twitter.com/simosako/status/1547790642926870528</a></p> </blockquote> <p>解決策2) CLI経由で削除する。</p> <blockquote> <p>CLI で消せた<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hayaok3/status/1547086353489620992">https://twitter.com/hayaok3/status/1547086353489620992</a></p> </blockquote> <h1 id="Redshift Serverless の credentials はiamが別"><a href="#Redshift+Serverless+%E3%81%AE+credentials+%E3%81%AFiam%E3%81%8C%E5%88%A5">Redshift Serverless の credentials はiamが別</a></h1> <p><code>redshift:GetClusterCredentials</code> ではなく <code>redshift-serverless:GetCredentials</code> が対象となる。</p> <blockquote> <p>一時的な認証情報を使用してサーバーレスワークグループへの認証を行う場合は、ポリシーで redshift-serverless:GetCredentials アクションの使用が許可されていることを確認します。 > <a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/redshift/latest/mgmt/data-api.html">https://docs.aws.amazon.com/ja_jp/redshift/latest/mgmt/data-api.html</a></p> </blockquote> <p>非公式ラッパーとして <a target="_blank" rel="nofollow noopener" href="https://github.com/mashiike/redshift-credentials">https://github.com/mashiike/redshift-credentials</a> が作られた。</p> <blockquote> <p>ServerlessなのかProvisionedなのか意識して、更にAPIの投げわけしないといけないの? そう思った人(私)向けにServerlessでもProvisionedでも、どっちのエンドポイントをパラメータに渡してもいい感じに一時認証してDB USERとDB Passwordを返してくれる君を書いてみました。<br /> <a target="_blank" rel="nofollow noopener" href="https://datatech-jp.slack.com/archives/C03MHCZS2GG/p1657977637298559">https://datatech-jp.slack.com/archives/C03MHCZS2GG/p1657977637298559</a></p> </blockquote> <h1 id="Redshift Serverless は時間課金なのでバッチ向き"><a href="#Redshift+Serverless+%E3%81%AF%E6%99%82%E9%96%93%E8%AA%B2%E9%87%91%E3%81%AA%E3%81%AE%E3%81%A7%E3%83%90%E3%83%83%E3%83%81%E5%90%91%E3%81%8D">Redshift Serverless は時間課金なのでバッチ向き</a></h1> <p>ストリーミング連携など、高頻度にデータ連携するときは以下の構成だとコスト効率が良い。</p> <ul> <li>小さめの常駐インスタンスにデータを流す。</li> <li>Redshift ServerlessからData Sharingで常駐インスタンスを参照する。</li> </ul> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/mashiike/status/1547032218140192768">https://twitter.com/mashiike/status/1547032218140192768</a></p> </blockquote> <p>Redshift Serverlessで完結したい場合は、ログをストリーミングでS3に反映しつつ、 Redshiftへのロードは1時間に1回のマイクロバッチで実現するような設計が考えられる。</p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/yuzutas0/status/1547011222998220800">https://twitter.com/yuzutas0/status/1547011222998220800</a></p> </blockquote> <h1 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h1> <p>「<a target="_blank" rel="nofollow noopener" href="https://yuzutas0.github.io/awesome-redshift-jp/docs/setup/faq.html">Awesome Redshift JP</a>」では、Amazon Redshift によるデータ活用を実現するためのノウハウや参考資料をまとめています。主に、Slackコミュニティ「<a target="_blank" rel="nofollow noopener" href="https://datatech-jp.github.io/">datatech-jp</a>」の「<a target="_blank" rel="nofollow noopener" href="https://datatech-jp.slack.com/archives/C03MHCZS2GG">#redshift</a>」チャンネルで会話しています。オススメの記事があればリポジトリに反映したいのでぜひ教えてください。</p> ゆずたそ tag:crieit.net,2005:PublicArticle/17872 2021-12-19T01:01:44+09:00 2021-12-19T01:01:44+09:00 https://crieit.net/posts/aws-ecs-fargate-amp-grafana 📔 ECS Fargate のメトリクスを Prometheus Agent 使って AMP に送って Grafana で監視する <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p><strong>この記事は <a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2021/aws">AWS Advent Calendar 2021</a> の 5 日目の記事です。</strong></p> <p><a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/fargate/">Fargate</a> で Node.js アプリのメトリクスを <a target="_blank" rel="nofollow noopener" href="https://prometheus.io/blog/2021/11/16/agent/">Prometheus Agent</a> をサイドカーコンテナとして動かして、<a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/prometheus/">Amazon Managed Service for Prometheus (AMP)</a> に送信して <a target="_blank" rel="nofollow noopener" href="https://grafana.com/">Grafana</a> で見られるようにしてみました。</p> <p>ちなみに Promethus Agent はまだ <a target="_blank" rel="nofollow noopener" href="https://prometheus.io/blog/2021/11/16/agent/#prometheus-agent-mode">実験的な機能</a> なため、<strong>実務での利用は推奨しません。</strong></p> <p>本記事の環境構築には <a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/cdk/">AWS CDK</a> を利用しています。</p> <h1 id="動作環境"><a href="#%E5%8B%95%E4%BD%9C%E7%92%B0%E5%A2%83">動作環境</a></h1> <ul> <li>Node.js v16.13.0</li> <li>AWS CDK 2.0.0 (build 4b6ce31)</li> <li>Prometheus 2.32.1</li> </ul> <h1 id="環境構築"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89">環境構築</a></h1> <h2 id="AWS CDK で環境構築する"><a href="#AWS+CDK+%E3%81%A7%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B">AWS CDK で環境構築する</a></h2> <p>CDK で構築作業を進めます。まずは下記コマンドで CDK プロジェクトを作成します。使用言語は <code>TypeScript</code> を選択します。</p> <pre><code class="bash">mkdir prometheus-agent-test && cd prometheus-agent-test cdk init --language typescript </code></pre> <p>まず CDK でインフラ構築を進めていく前に、メトリクス収集テスト用の Node.js アプリを準備します。</p> <h3 id="ECS Fargate で動かす Node.js アプリを準備する"><a href="#ECS+Fargate+%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%99+Node.js+%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E6%BA%96%E5%82%99%E3%81%99%E3%82%8B">ECS Fargate で動かす Node.js アプリを準備する</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/siimon/prom-client">prom-client</a> を利用して、Node.js のメトリクスが取得できるだけの Node.js アプリを準備します。<code>prometheus-agent-test</code> フォルダで下記コマンドを実行します。</p> <pre><code class="bash">mkdir metrics-app && cd metrics-app npm init -y npm install --save prom-client </code></pre> <p>次に <code>metrics-app</code> フォルダ内に <code>index.js</code> を作成して下記を編集します。</p> <pre><code class="javascript">// metrics-app/index.js "use strict"; const http = require("http"); const server = http.createServer(); const client = require("prom-client"); const register = new client.Registry(); // 5秒間隔でメトリクスを取得する client.collectDefaultMetrics({ register, timeout: 5 * 1000 }); server.on("request", async function (req, res) { // /metrics にアクセスしたら、Prometheus のレポートを返す if (req.url === "/metrics") { res.setHeader("Content-Type", register.contentType); const metrics = await register.metrics(); return res.end(metrics); } else { return res.writeHead(404, { "Content-Type": "text/plain" }); } }); server.listen(8080); </code></pre> <p><code>node index.js</code> コマンドを実行して <code>http://localhost:8080/metrics</code> にアクセスしてみます。下記のように各種メトリクスが出力されている様子が確認できれば OK です。</p> <p><img src="https://i.gyazo.com/a5feae6dba9a9f4eaecae0055dd9be9e.png" alt="Prometheus のレポートが正常に出力されている様子" /><br /> <strong>Prometheus のレポートが正常に出力されている様子</strong></p> <p>今回は ECS 上で Node.js アプリを動作させるため、<code>Dockerfile</code> も作成します。</p> <pre><code class="docker"># metrics-app/Dockerfile FROM public.ecr.aws/docker/library/node:16-alpine3.12 AS builder EXPOSE 8080 WORKDIR /usr/src/app COPY package*.json ./ RUN npm install --max-old-space-size=4096 COPY . . CMD [ "node", "index.js" ] </code></pre> <p>上記 <code>Dockerfile</code> 作成後、再び動作検証のため下記コマンドを実行してから、<code>http://localhost:8080/metrics</code> にアクセスしてみます。</p> <pre><code class="bash">docker build -t prometheus-agent-test/metrics-app . docker run -p 8080:8080 prometheus-agent-test/metrics-app:latest </code></pre> <p>先ほどと同様に <code>http://localhost:8080/metrics</code> アクセス時に各種メトリクスが出力されている様子を確認できれば OK です。</p> <h3 id="Node.js アプリを監視する Prometheus Agent を準備する"><a href="#Node.js+%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E7%9B%A3%E8%A6%96%E3%81%99%E3%82%8B+Prometheus+Agent+%E3%82%92%E6%BA%96%E5%82%99%E3%81%99%E3%82%8B">Node.js アプリを監視する Prometheus Agent を準備する</a></h3> <p>まずは Prometheus 関連ファイルを配置するためのフォルダを作成します。<code>prometheus-agent-test</code> フォルダ内で下記コマンドを実行します。</p> <pre><code class="bash">mkdir prometheus-agent && cd prometheus-agent </code></pre> <p>次に Prometheus の設定テンプレートファイルを作成します。テンプレートファイルは <a target="_blank" rel="nofollow noopener" href="https://atmarkit.itmedia.co.jp/ait/articles/1610/18/news008.html"><code>sed</code></a> を利用して中身の <code>__TASK_ID__</code> および <code>__REMOTE_WRITE_URL__</code> を書き換えて利用します。</p> <pre><code class="yaml"># prometheus-agent/prometheus.tmpl.yml global: scrape_interval: 5s external_labels: monitor: "prometheus" scrape_configs: - job_name: "prometheus-agent-test" static_configs: - targets: ["localhost:8080"] labels: # デフォルトの localhost:8080 がインスタンスとして利用されると、 # メトリクスの判別がしづらくなるため ECS Task の ID を利用する instance: "__TASK_ID__" remote_write: # AMP ワークスペース作成時に控えておいた、 # `エンドポイント - リモート書き込み URL` を設定する箇所 - url: "__REMOTE_WRITE_URL__" sigv4: region: ap-northeast-1 queue_config: max_samples_per_send: 1000 max_shards: 200 capacity: 2500 </code></pre> <p>設定ファイルの作成が完了したら、テンプレートファイルを利用して Prometheus の設定ファイルを作成し、Prometheus Agent を起動させるためのシェルスクリプトを作成します。</p> <pre><code class="sh"># prometheus-agent/docker-entrypoint.sh #!/bin/sh while [ -z "$taskId" ] do # ECS Fargate で起動したタスク ID を取得する taskId=$(curl --silent ${ECS_CONTAINER_METADATA_URI}/task | jq -r '.TaskARN | split("/") | .[-1]') echo "waiting..." sleep 1 done echo "taskId: ${taskId}" echo "remoteWriteUrl: ${REMOTE_WRITE_URL}" # タスク ID `taskId` および、環境変数 `REMOTE_WRITE_URL` で、 # Prometheus のテンプレートファイル `prometheus.tmpl.yml` の内容を書き換え、 # その結果を `/etc/prometheus/prometheus.yml` に出力する cat /etc/prometheus/prometheus.tmpl.yml | \ sed "s/__TASK_ID__/${taskId}/g" | \ sed "s>__REMOTE_WRITE_URL__>${REMOTE_WRITE_URL}>g" > /etc/prometheus/prometheus.yml # --enable-feature=agent で Prometheus を Agent モードで起動する # Prometheus のコンフィグファイルには上記で出力した `/etc/prometheus/prometheus.yml` を利用する /usr/local/bin/prometheus \ --enable-feature=agent \ --config.file=/etc/prometheus/prometheus.yml \ --web.console.libraries=/etc/prometheus/console_libraries \ --web.console.templates=/etc/prometheus/consoles </code></pre> <p>これで Prometheus Agent 起動のための準備は整ったため、最後に <code>Dockerfile</code> を準備します。ちなみに Prometheus Agent は <code>v2.32.0</code> 以降で利用可能です。<strong>本記事では <code>v2.32.1</code> を利用します。</strong></p> <pre><code class="docker"># prometheus-agent/Dockerfile FROM --platform=arm64 alpine:3.15 ADD prometheus.tmpl.yml /etc/prometheus/ RUN apk add --update --no-cache jq sed curl # ARM64 で動作する Prometheus v2.32.1 を curl でダウンロード展開する RUN curl -sL -O https://github.com/prometheus/prometheus/releases/download/v2.32.1/prometheus-2.32.1.linux-arm64.tar.gz RUN tar -zxvf prometheus-2.32.1.linux-arm64.tar.gz && rm prometheus-2.32.1.linux-arm64.tar.gz # `prometheus` コマンドを `/usr/local/bin/prometheus` に移動する RUN mv prometheus-2.32.1.linux-arm64/prometheus /usr/local/bin/prometheus COPY ./docker-entrypoint.sh / RUN chmod +x /docker-entrypoint.sh CMD ["/docker-entrypoint.sh"] </code></pre> <p>ここまでで CDK でインフラ整備を進めていくための下準備は完了です。</p> <h3 id="ECS Fargate 上で Node.js アプリおよび Prometheus Agent を動作させる"><a href="#ECS+Fargate+%E4%B8%8A%E3%81%A7+Node.js+%E3%82%A2%E3%83%97%E3%83%AA%E3%81%8A%E3%82%88%E3%81%B3+Prometheus+Agent+%E3%82%92%E5%8B%95%E4%BD%9C%E3%81%95%E3%81%9B%E3%82%8B">ECS Fargate 上で Node.js アプリおよび Prometheus Agent を動作させる</a></h3> <p>あとは CDK で ECS Fargate 上で Node.js アプリおよび Prometheus Agent、Grafana を動作させるための環境を整備していきます。</p> <p><code>lib/prometheus-agent-test-stack.ts</code> の内容を書き換えます。</p> <pre><code class="typescript">// lib/prometheus-agent-test-stack.ts import { Construct } from "constructs"; import { Stack, StackProps, aws_ecs as ecs, aws_logs as logs, aws_aps as aps, aws_ecs_patterns as ecs_patterns, aws_iam as iam, aws_elasticloadbalancingv2 as elbv2, Duration, CfnOutput, } from "aws-cdk-lib"; export class PrometheusAgentTestStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // Node.js アプリに ecs_patterns.ApplicationLoadBalancedFargateService を利用して ALB 経由でアクセス可能にする const projectName = "prometheus-agent-test"; const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService( this, `${projectName}-fargate-service`, { serviceName: `${projectName}-fargate-service`, cpu: 256, desiredCount: 3, listenerPort: 80, taskImageOptions: { family: `${projectName}-taskdef`, image: ecs.ContainerImage.fromAsset("metrics-app"), containerPort: 8080, logDriver: ecs.LogDrivers.awsLogs({ streamPrefix: `/${projectName}/metrics-app`, logRetention: logs.RetentionDays.ONE_DAY, }), }, cluster: new ecs.Cluster(this, `${projectName}-cluster`, { clusterName: `${projectName}-cluster`, }), memoryLimitMiB: 512, } ); fargateService.targetGroup.configureHealthCheck({ path: "/metrics", timeout: Duration.seconds(8), interval: Duration.seconds(10), healthyThresholdCount: 2, unhealthyThresholdCount: 4, healthyHttpCodes: "200", }); // 本質ではないが、Gravition2 で動作させたいために RuntimePlatform のプロパティを上書きしている const fargateServiceTaskdef = fargateService.taskDefinition.node .defaultChild as ecs.CfnTaskDefinition; fargateServiceTaskdef.addPropertyOverride("RuntimePlatform", { CpuArchitecture: "ARM64", OperatingSystemFamily: "LINUX", }); // AMP への書き込み権限を付与する fargateService.taskDefinition.taskRole.addManagedPolicy( iam.ManagedPolicy.fromAwsManagedPolicyName( "AmazonPrometheusRemoteWriteAccess" ) ); // (2021/12/18) Amazon Managed Service for Prometheus のワークスペースを作成して、Prometheus の remote-write URL を取得する const apsWorkspace = new aps.CfnWorkspace( this, `${projectName}-prom-workspace`, { alias: `${projectName}-prom-workspace`, } ); const apsWorkspaceRemoteUrl = `${apsWorkspace.attrPrometheusEndpoint}api/v1/remote_write`; // (2021/12/18) 本記事で頻出する "エンドポイント - リモート書き込み URL" をコンソールに出力する new CfnOutput(this, "prom-remote-write-url", { value: apsWorkspaceRemoteUrl, description: "Prometheus Workspace の remote-write URL", exportName: "PromRemoteWriteURL", }); // AMP へメトリクス情報を送信するための Prometheus Agent コンテナを追加する const containerName = `${projectName}-prometheus-agent`; fargateService.taskDefinition.addContainer(containerName, { containerName, image: ecs.ContainerImage.fromAsset("prometheus-agent"), memoryReservationMiB: 128, environment: { // (2021/12/18) CDK 経由で作成した Prometheus の remote-write URL を設定する REMOTE_WRITE_URL: apsWorkspaceRemoteUrl, }, logging: new ecs.AwsLogDriver({ streamPrefix: `/${projectName}/prometheus-agent`, logRetention: logs.RetentionDays.ONE_DAY, }), }); // Grafana のタスク定義を作成する const grafanaDashboardTaskDefinition = new ecs.FargateTaskDefinition( this, `${projectName}-grafana-taskdef`, { family: `${projectName}-grafana-taskdef`, } ); // Grafana のタスクが Prometheus Query を叩けるように権限付与する grafanaDashboardTaskDefinition.taskRole.addManagedPolicy( iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonPrometheusQueryAccess") ); // Grafana のコンテナを追加する。パスプレフィクスには dashboard を設定する const grafanaDashboardContainerName = `${projectName}-grafana-dashboard`; grafanaDashboardTaskDefinition.addContainer(grafanaDashboardContainerName, { containerName: grafanaDashboardContainerName, image: ecs.ContainerImage.fromRegistry("public.ecr.aws/ubuntu/grafana"), environment: { AWS_SDK_LOAD_CONFIG: "true", GF_AUTH_SIGV4_AUTH_ENABLED: "true", GF_SERVER_SERVE_FROM_SUB_PATH: "true", GF_SERVER_ROOT_URL: "%(protocol)s://%(domain)s/dashboard", }, portMappings: [{ containerPort: 3000 }], memoryLimitMiB: 512, logging: new ecs.AwsLogDriver({ streamPrefix: `/${projectName}/grafana-dashboard`, logRetention: logs.RetentionDays.ONE_DAY, }), }); const grafanaDashboardServiceName = `${projectName}-grafana-dashboard-service`; const grafanaDashboardService = new ecs.FargateService( this, grafanaDashboardServiceName, { serviceName: grafanaDashboardServiceName, cluster: fargateService.cluster, taskDefinition: grafanaDashboardTaskDefinition, desiredCount: 1, } ); // Grafana のタスクを ALB のターゲットグループに紐づける fargateService.listener.addTargets( `${projectName}-grafana-dashboard-target`, { priority: 1, conditions: [elbv2.ListenerCondition.pathPatterns(["/dashboard/*"])], healthCheck: { path: "/dashboard/login", interval: Duration.seconds(10), timeout: Duration.seconds(8), healthyThresholdCount: 2, unhealthyThresholdCount: 3, healthyHttpCodes: "200", }, port: 3000, protocol: elbv2.ApplicationProtocol.HTTP, targets: [grafanaDashboardService], } ); } } </code></pre> <p>その後、<code>cdk deploy</code> でインフラを構築します。</p> <p><img src="https://i.gyazo.com/c7da0f6c6b5a57edee47ae20a8026f8f.png" alt="CDK によるインフラ構築が正常に実行された時の様子" /><br /> <strong>CDK によるインフラ構築が正常に実行された時の様子</strong></p> <p>デプロイが正常に完了したのを確認したら、<code>Outputs</code> に出力されている <strong><code>PrometheusAgentTestStack.prometheusagenttestfargateserviceServiceURL<識別子></code> の URL 末尾に <code>/metrics</code> を付与してアクセスしてみます。</strong> 出力されている URL のフォーマットは <code>http://<識別子>.ap-northeast-1.elb.amazonaws.com</code> になります。</p> <p>つまり、<strong><code>http://<識別子>.ap-northeast-1.elb.amazonaws.com/metrics</code> にアクセスします。</strong></p> <p><img src="https://i.gyazo.com/c13a2b0efc3bb96e79b4f1f5a2886a8a.png" alt="ALB 経由で Node.js アプリにアクセス可能なことを確認する" /><br /> <strong>ALB 経由で Node.js アプリにアクセス可能なことを確認する</strong></p> <p>また、<code>Outputs</code> に出力されている <strong><code>PrometheusAgentTestStack.promremotewriteurl</code> は後に利用する <code>エンドポイント - リモート書き込み URL</code> で使用するので控えておきます。</strong></p> <p>ここまでで AWS CDK でのインフラ構築作業は完了しました。最後に Grafana で AMP のメトリクスを可視化するための作業を進めていきます。</p> <h1 id="Grafana で Prometheus (AMP) のメトリクスを可視化する"><a href="#Grafana+%E3%81%A7+Prometheus+%28AMP%29+%E3%81%AE%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E3%82%92%E5%8F%AF%E8%A6%96%E5%8C%96%E3%81%99%E3%82%8B">Grafana で Prometheus (AMP) のメトリクスを可視化する</a></h1> <p>先ほどの <code>/metrics</code> パスへのアクセス同様、<code>Outputs</code> に出力されている URL の末尾に <code>/dashboard/login</code> を付与してアクセスします。<strong>Grafana の初期ユーザおよびパスワードは <code>admin</code> となります。</strong></p> <p>つまり、<code>http://<識別子>.ap-northeast-1.elb.amazonaws.com/dashboard/login</code> にアクセスしてみます。</p> <p><img src="https://i.gyazo.com/fe4ea6515f54dec23234e10a93023a36.png" alt="ログインを行う" /></p> <p>ログイン情報が正しければ、新しいパスワードを設定する画面に遷移するので新たなパスワードを入力してログインを終えます。ログイン後は、Prometheus (AMP) をデータソースとして追加するために下記の操作を行います。</p> <p><img src="https://i.gyazo.com/599d49e4167ccc35c5d44d5df7678036.png" alt="1. 歯車アイコンをクリックして <code>Data sources</code> をクリックする" /><br /> <strong>1. 歯車アイコンをクリックして <code>Data sources</code> をクリックする</strong></p> <p><img src="https://i.gyazo.com/e88bfc58a35deaa16a16920a5e551837.png" alt="2. <code>Add data source</code> ボタンをクリックする" /><br /> <strong>2. <code>Add data source</code> ボタンをクリックする</strong></p> <p><img src="https://i.gyazo.com/1c8031a3182f03e20194bf0b76e66e5a.png" alt="3. データソースとして Prometheus を選択する" /><br /> <strong>3. データソースとして Prometheus を選択する</strong></p> <p><img src="https://i.gyazo.com/1684c1fca9decb29e60bd654400fd06b.png" alt="4. Prometheus をデータソースとして追加する" /></p> <p><img src="https://i.gyazo.com/2119361eac350044e783ed0c9280580a.png" alt="4. Prometheus をデータソースとして追加する" /></p> <p><img src="https://i.gyazo.com/00cc7edbfc45dbd43ad3b0cccea72c7d.png" alt="4. Prometheus をデータソースとして追加する" /><br /> <strong>4. Prometheus をデータソースとして追加する</strong></p> <p>Prometheus (AMP) に送信したメトリクスを Grafana で可視化するための準備が整ったので、実際に Grafana のダッシュボードでメトリクスを可視化してみます。手っ取り早くメトリクスを可視化するため、ダッシュボードには <a target="_blank" rel="nofollow noopener" href="https://grafana.com/grafana/dashboards/11159">NodeJS Application Dashboard</a> を利用します。</p> <p><img src="https://i.gyazo.com/b36c0281e273e21fc9474666786281c3.png" alt="1. + アイコンをクリックして、<code>Import</code> をクリックする" /><br /> <strong>1. + アイコンをクリックして、<code>Import</code> をクリックする</strong></p> <p><img src="https://i.gyazo.com/130718dff8b86758acb415764bd37338.png" alt="2. <code>NodeJS Application Dashboard</code> の ID を入力して <code>Load</code> ボタンをクリックする" /><br /> <strong>2. <code>NodeJS Application Dashboard</code> の ID を入力して <code>Load</code> ボタンをクリックする</strong></p> <p><img src="https://i.gyazo.com/2aa8903f3f64d8021699cd5d4bfa9757.png" alt="3. 必要な情報を入力して <code>NodeJS Application Dashboard</code> のインポートを完了する" /><br /> <strong>3. 必要な情報を入力して <code>NodeJS Application Dashboard</code> のインポートを完了する</strong></p> <p><img src="https://i.gyazo.com/1378b09c281da28653917ee40494dee8.png" alt="4. ダッシュボードから Prometheus のメトリクスが確認できる" /><br /> <strong>4. ダッシュボードから Node.js アプリのメトリクスが確認できる</strong></p> <p>ここまでの手順でメトリクスの可視化は完了しましたが、負荷に応じて実際にメトリクスが変化する様子も確認してみます。<a target="_blank" rel="nofollow noopener" href="https://github.com/tsenart/vegeta">Vegeta</a> を利用して、実際に負荷をかけてみます。下記コマンドを実行します。</p> <pre><code class="bash">echo 'GET http://<識別子>.ap-northeast-1.elb.amazonaws.com/metrics' | vegeta attack -duration=5s | vegeta report </code></pre> <p>その後、再び Grafana のダッシュボードを見にいきます。負荷をかけた時間帯のみグラフに変化があることを確認できるはずです。</p> <p><img src="https://i.gyazo.com/d019d33d3e4bd321ae4d1f4bbecc6ef8.png" alt="ダッシュボードの CPU 使用率のグラフに変化があったことを確認できる" /><br /> <strong>ダッシュボードの CPU 使用率のグラフに変化があったことを確認できる</strong></p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>今回は ECS Fargate のメトリクスを Prometheus Agent で Amazon Managed Service for Prometheus (AMP) に送信し、それを Grafana で可視化する方法について紹介しました。</p> <p>ECS のサービスでタスクを実行する場合は <a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-discovery.html">サービスディスカバリ</a> の利用が可能なため、Prometheus の <a target="_blank" rel="nofollow noopener" href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config">サービスディスカバリの設定</a> を行うことで、単一の Prometheus で全てのコンテナのメトリクスを扱うことも可能です。</p> <p>また Node.js アプリを作成する際に利用した <code>prom-client</code> で <a target="_blank" rel="nofollow noopener" href="https://github.com/siimon/prom-client#custom-metrics">カスタムメトリクス</a> を作成することで、監視したい項目を自由に増やすことも可能です。</p> <p>本記事が ECS Fargate を監視する際の検討材料の 1 つとなれたら幸いです。</p> <h1 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/fargate/">AWS Fargate(サーバーやクラスターの管理が不要なコンテナの使用)| AWS</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://prometheus.io/blog/2021/11/16/agent/">Introducing Prometheus Agent Mode, an Efficient and Cloud-Native Way for Metric Forwarding | Prometheus</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/prometheus/">Amazon Managed Service for Prometheus | フルマネージド Prometheus | Amazon Web Services</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://grafana.com/">Grafana: The open observability platform | Grafana Labs</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/cdk/">AWS クラウド開発キット – アマゾン ウェブ サービス</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/siimon/prom-client">siimon/prom-client: Prometheus client for node.js</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/tsenart/vegeta">tsenart/vegeta: HTTP load testing tool and library. It's over 9000!</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://grafana.com/grafana/dashboards/11159">NodeJS Application Dashboard dashboard for Grafana | Grafana Labs</a></li> </ul> nikaera tag:crieit.net,2005:PublicArticle/17721 2021-10-25T15:29:45+09:00 2021-10-25T15:31:36+09:00 https://crieit.net/posts/Nuxt-js-Rails-OSS-GitHub-web 【個人開発】(Nuxt.js + Rails)OSSやプロジェクトの、ソースコードを管理するGitHubのように、web上で結合テストをチームで同時に管理・実行・記録出来るプラットフォームを開発しました! <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>今回、個人で開発したWebサービス「Itamaster」の本番稼働版をリリースすることが出来ました!<br /> こちらのサービスを開発しようと思った経緯は、ある日会社で、統合テストの管理・実行を行っていたときに、<strong>「何故ソースコードの管理プラットフォーム(GitHub等)はあるのに、結合テストの管理プラットフォームは無いんだろう」</strong>と思った事がきっかけです。<br /> 開発期間は八ヶ月ほど、作業工数は大体700時間です。</p> <p>弊社、私の所属しているチームは結合テストはExcelによって管理しており、実行記録もExcelを利用しています。<br /> Excelでの管理・実行には、以下のような不便な点がありました。</p> <ul> <li><p>Excelでの作業は、PCにかかる負荷が大きかったこと。<br /> 会社のPCは自宅で扱うようなハイスペックなものではないため、Excelで作業を行っていると、セルをコピーしたりした時に<strong>PCがフリーズしたり、突然Excelが落ちて、保存していないデータが復旧しなかったり</strong>と、作業中に感じる<strong>ストレス</strong>がとても大きかったです。</p></li> <li><p>Excelだと、複数人で一つのファイルに対する作業が出来ないこと。<br /> 共有設定にして複数人で同時に作業すると、Excelが一層重くなったりして、とても実用出来るものではありませんでした。<br /> Excelだと、<strong>一つのファイルは同時に一人でしか作業が出来ず、待ち時間の発生</strong>するメンバーがいました。</p></li> <li><p>ローカルで作業しているメンバーの作業状況が把握出来ないこと。<br /> 私は管理者経験はありませんが、毎日の定時報告会や、Slackでのメッセージを見る限り、作業状況の把握が少しアナログに感じました。<br /> Redmine等の進捗管理ツールも利用していますが、結局は自己申告であり、<strong>「どのくらいの作業が終わっていて、残っているのか」</strong>を管理者が一目で把握することが出来れば、課題を解決できると考えました。</p></li> <li><p>サマリーレポートや、結果一覧表の作成が面倒<br /> Excelから情報を整理して、Excelに表を作って、割合を求めて、グラフ化して......<br /> テストが追加されたり、状況が変わると、また、情報を整理して、Excelに表を作って、割合を求めて、グラフ化して......<br /> <strong>「あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"あ"!!!!!!!」</strong><br /> ってなりました。</p></li> </ul> <p>開発告知記事を書くのは初めてで、至らない点が多々あるかと思いますが<br /> サービスの紹介、開発時の苦労や反省、所感を統括して書きたいと思います。<br /> 最後まで読んでいただければ幸いです。</p> <p>【Twitterリンク】<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/@itamaster_">https://twitter.com/@itamaster_</a></p> <p>【Itamaster】<br /> <a target="_blank" rel="nofollow noopener" href="https://itamaster.work">https://itamaster.work</a><br /> PC推奨です。</p> <p>【Itamaster 操作ガイド】(Itamasterのヘッダーと、フッターにも操作ガイドリンクを設置しています。)<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/Itamaster/items/3c271181e0c9c06d1d08">https://qiita.com/Itamaster/items/3c271181e0c9c06d1d08</a></p> <h1 id="使用技術"><a href="#%E4%BD%BF%E7%94%A8%E6%8A%80%E8%A1%93">使用技術</a></h1> <ul> <li>npm 7.6.3</li> <li>nuxtjs 2.15.3 (SPA モード) <ul> <li>サクサクと、Excelのようなもっさり感やストレスの無いUXにしたかったため、SPAによって構築しました。</li> </ul></li> <li>@nuxtjs/storybook 4.0.2 <ul> <li>コンポーネントのレイアウト・動作を開発中にリアルタイムで確認出来る、コンポーネントカタログ作成ツール。<br /> フロントエンドの単体テストの役割も果たします。</li> </ul></li> <li>vuetify (@nuxtjs/vuetify 1.12.1) <ul> <li>MaterialDesignベースのデザインライブラリです。<br /> 少し挙動が不安定な点が目立ちました。</li> </ul></li> <li>ruby 2.6.8</li> <li>Ruby on Rails 6.1.4 (APIモード)</li> <li>Device <ul> <li>ログイン周り。フロントエンドにはトークンを渡します。フロントエンドではトークンをsession storageに一時保存し、axiosのインターセプターに噛ませることで、ログイン機能付きの認証認可を実現しました。</li> </ul></li> <li>EC2(t2.small)</li> <li>S3</li> <li>ALB</li> <li>ACM</li> <li>RDS (mysql)</li> <li>Route53</li> <li>nginx</li> <li>Stripe <ul> <li>クレジットカード決済機能をAPI利用できます。<br /> 顧客情報や、クレジットカード情報を自サーバーで持たなくてよいため、セキュアなシステムの構築が可能です。<br /> また、同様に、決済機能のAPI利用はリスク管理の面からも有効です。</li> </ul></li> </ul> <h1 id="アプリ概要"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E6%A6%82%E8%A6%81">アプリ概要</a></h1> <p><strong>結合テストの自動化を行わないチームや、手動でのテストを行いたい状況が対象のWebサービス</strong>です。<br /> ユーザーをメールアドレスで招待し、チームを結成。<br /> プロジェクトを立ち上げて、テストスイート毎にテストケースを管理・実行が出来ます。</p> <p>⇓チームの情報を表示するダイアログ<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/865d4a70-6ed5-c5ba-391f-ced31d721833.png" alt="image.png" /></p> <p>⇓プロジェクト、プロジェクト内のテストスイートの管理を行う画面<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/b55c1892-1c76-b9aa-37aa-b0eabc7842b4.png" alt="image.png" /></p> <p>⇓テストスイートの情報を表示するダイアログ<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/c64f28f2-eab8-c765-a40e-284b299735bc.png" alt="image.png" /></p> <p>⇓テストケースの情報を表示するダイアログ<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/e43628c2-0dec-5a4d-c14c-fe672a860915.png" alt="image.png" /></p> <p>⇓テストスイート内のテストケースを実行する画面<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/002c37d0-8c55-ab2d-6415-e45e3d331894.png" alt="image.png" /></p> <p>また、テスト結果一覧表や、テストサマリーレポートも出力可能です。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/5ce63e70-6a27-8f4c-65a9-f1da499f672f.png" alt="image.png" /></p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/c230b738-0ed5-be27-1e32-2983c6c48d98.png" alt="image.png" /><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/d80d35c5-4b9e-204f-d960-f9f4a9ececad.png" alt="image.png" /></p> <h1 id="こだわった箇所"><a href="#%E3%81%93%E3%81%A0%E3%82%8F%E3%81%A3%E3%81%9F%E7%AE%87%E6%89%80">こだわった箇所</a></h1> <h2 id="全体的に、明るい色を起用すること"><a href="#%E5%85%A8%E4%BD%93%E7%9A%84%E3%81%AB%E3%80%81%E6%98%8E%E3%82%8B%E3%81%84%E8%89%B2%E3%82%92%E8%B5%B7%E7%94%A8%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8">全体的に、明るい色を起用すること</a></h2> <p>テストという地味な作業は、設計書作成の次に気が滅入り易くなります。<br /> 暗い気持ちになってしまわぬよう、明るい色を起用しつつ、<br /> 緑や薄めの青色をメインに使う事で、ユーザーのモチベーションを維持します。</p> <h2 id="アイコンを多用し、直感的な操作を可能にすること"><a href="#%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%82%92%E5%A4%9A%E7%94%A8%E3%81%97%E3%80%81%E7%9B%B4%E6%84%9F%E7%9A%84%E3%81%AA%E6%93%8D%E4%BD%9C%E3%82%92%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8">アイコンを多用し、直感的な操作を可能にすること</a></h2> <p>画面にはアイコンを多用して、文字を読まなくても色とアイコンから、ボタンの機能を推測することが可能です。<br /> 直感的に操作することが可能で、作業の効率を向上します。</p> <h2 id="進捗バーをプロジェクトと、テストスイート毎に表示可能にし、一目で状況を確認可能に"><a href="#%E9%80%B2%E6%8D%97%E3%83%90%E3%83%BC%E3%82%92%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A8%E3%80%81%E3%83%86%E3%82%B9%E3%83%88%E3%82%B9%E3%82%A4%E3%83%BC%E3%83%88%E6%AF%8E%E3%81%AB%E8%A1%A8%E7%A4%BA%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%97%E3%80%81%E4%B8%80%E7%9B%AE%E3%81%A7%E7%8A%B6%E6%B3%81%E3%82%92%E7%A2%BA%E8%AA%8D%E5%8F%AF%E8%83%BD%E3%81%AB">進捗バーをプロジェクトと、テストスイート毎に表示可能にし、一目で状況を確認可能に</a></h2> <p>色分けされた進捗バーは、ホバーすることでそれぞれのステータスの件数を確認可能です。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/7046daf8-49c5-af8c-1ab4-6df7b1d35f1c.png" alt="image.png" /><br /> チームの管理者が、「現在プロジェクト全体で、どれだけのテストが完了しているのか」を一目で確認することが可能です。</p> <h2 id="自分独自のアイコンを設定可能にすること"><a href="#%E8%87%AA%E5%88%86%E7%8B%AC%E8%87%AA%E3%81%AE%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3%E3%82%92%E8%A8%AD%E5%AE%9A%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8">自分独自のアイコンを設定可能にすること</a></h2> <p><strong>Itamasterは、GitHubのように、OSSのテストをオープンに行う事の出来るプラットフォームを目指します。</strong><br /> アイコンを設定可能にし、画面に多用することで、<strong>今後実装するSNS的な機能が違和感なく利用できます。</strong><br /> (Publicなプロジェクトを表示・検索し、運営しているチームとダイレクトにメッセージをやり取りし、テストを世界中のメンバーと共同して行うような)<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/ffa5c1bf-3579-4550-5ba6-0f913b99be1d.png" alt="image.png" /><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/875d80d2-7653-19ce-0beb-9829b3f94ab6.png" alt="image.png" /></p> <h1 id="サービス改善告知"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E6%94%B9%E5%96%84%E5%91%8A%E7%9F%A5">サービス改善告知</a></h1> <p style="color:red;"> 2021/10/17 サービスに、操作ガイドページ・Demoページを追加実装しました! </p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/bacb5f22-edac-0a85-d19d-26cf94088f9a.png" alt="image.png" /><br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/a4f28d96-928e-6da4-52be-6153e3a6488f.png" alt="image.png" /></p> <p>操作ガイドページでは、サービス内の全てのボタンや、項目の意味、操作方法やユースケースを記述しております。<br /> Demoページでは、実際の画面にモックデーターを流し込んだものを使い、操作感や使用イメージを持っていただくことを目的としています。<br /> どちらも無料でご利用いただける機能ですので、よろしければお楽しみください!</p> <p style="color:red;"> 2021/10/20 トップページのレイアウトを変更しました! </p> <p>旧:<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/d16ed7d0-f360-5ebf-1ae2-76809f111587.png" alt="image.png" /><br /> 新:<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/5752c7cd-dd2e-784d-1ac9-fec76e29f3a8.png" alt="image.png" /></p> <p><strong>変更にあたって、具体的に意識した点</strong></p> <ul> <li>設計思想をメインコンセプトとして表示</li> <li>注目線を意識し、印象付けたい文言を左側に配置する</li> </ul> <p><strong>アニメーション</strong>が少しだけ配置されているので、実際に見ていただければ、と存じます!</p> <h1 id="サービスのこれから"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E3%81%93%E3%82%8C%E3%81%8B%E3%82%89">サービスのこれから</a></h1> <h2 id="トップページのレイアウト改善"><a href="#%E3%83%88%E3%83%83%E3%83%97%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E6%94%B9%E5%96%84">トップページのレイアウト改善</a></h2> <p>とりあえず、第一課題です。<br /> 現状注目線があっちこっちに行ってしまっており、とてもわかりにくいページになっています。<br /> デザインを勉強しつつ、わかりやすく、「使ってみたい」と思っていただけるようなトップページにしたいと思っております。</p> <h2 id="チーム公開機能の実装"><a href="#%E3%83%81%E3%83%BC%E3%83%A0%E5%85%AC%E9%96%8B%E6%A9%9F%E8%83%BD%E3%81%AE%E5%AE%9F%E8%A3%85">チーム公開機能の実装</a></h2> <p>public設定のチームをいくつか表示し、興味の出たOSSのテスト等に世界中から参画希望を出せるようにします。<br /> 本来の設計思想である、「何故OSSをGitで管理するように、テストを世界中のエンジニアと管理実行するプラットフォームは無いのか」という疑問を解決する機能なので、じっくり作り込みたいと思っています。<br /> 現状は、<br /> ・Topics<br /> ・Search<br /> ・Explore<br /> の三画面を開発して、世界中のチームを見られるようにしたいと思っています。</p> ふぁる@個人開発 tag:crieit.net,2005:PublicArticle/17717 2021-10-20T03:32:37+09:00 2022-08-15T02:03:36+09:00 https://crieit.net/posts/OCI-AWS OCIやAWS上のサーバの環境構築 <h1 id="1.公開鍵認証方式でssh接続"><a href="#1%EF%BC%8E%E5%85%AC%E9%96%8B%E9%8D%B5%E8%AA%8D%E8%A8%BC%E6%96%B9%E5%BC%8F%E3%81%A7ssh%E6%8E%A5%E7%B6%9A">1.公開鍵認証方式でssh接続</a></h1> <h2 id="1-1.キーの作成(おまけ)"><a href="#1-1%EF%BC%8E%E3%82%AD%E3%83%BC%E3%81%AE%E4%BD%9C%E6%88%90%28%E3%81%8A%E3%81%BE%E3%81%91%29">1-1.キーの作成(おまけ)</a></h2> <p>Windowsのコマンドプロンプトで,</p> <pre><code>>ssh-keygen -b 4096 </code></pre> <p>(4096は任意鍵長)と打つと,<code>~/.ssh</code>に公開鍵と秘密鍵が作成される.例えばユーザー名がTanakaさんなら.<code>C:\Users\Tanaka\.ssh</code>に作成される.<br /> 秘密鍵のファイル名が<code>id_rsa</code>で,公開鍵が<code>id_rsa.pub</code>となる.</p> <p>以降の話ではこの節はあまり関係ない.</p> <h2 id="1-2.OCIでインスタンス作成"><a href="#1-2%EF%BC%8EOCI%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E4%BD%9C%E6%88%90">1-2.OCIでインスタンス作成</a></h2> <p>sshキーにスポットをあてて話を進める.<br /> <a href="https://crieit.now.sh/upload_images/01951106d2320a385b15633467219304616f061fd5cf3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/01951106d2320a385b15633467219304616f061fd5cf3.png?mw=700" alt="image.png" /></a></p> <h3 id="1-2-1.キーペアを持っていない場合"><a href="#1-2-1%EF%BC%8E%E3%82%AD%E3%83%BC%E3%83%9A%E3%82%A2%E3%82%92%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88">1-2-1.キーペアを持っていない場合</a></h3> <p>[ SSHキーの追加 ]欄で,[キー・ペアを自動で生成 ]を選択し,<br /> [公開キーの保存]→[秘密キーの保存]をクリックすることでキーペア取得できる.<br /> この2つのファイルは大切に保管しましょう.(今後も使いまわせるので)</p> <p>保存された2つのファイルは,<br /> 公開鍵が<code>~.key.pub</code><br /> 秘密鍵が<code>~.key</code><br /> となっている.</p> <p>この2つのファイルはどこか好きなディレクトリに格納しとこう.<br /> 例:<code>C:\Users\Tanaka\Documents\Oracle</code></p> <p>インスタンスを作成.</p> <h3 id="1-2-2.キーペアを持っている場合"><a href="#1-2-2%EF%BC%8E%E3%82%AD%E3%83%BC%E3%83%9A%E3%82%A2%E3%82%92%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E5%A0%B4%E5%90%88">1-2-2.キーペアを持っている場合</a></h3> <p>OCIでインスタンスを作成する際,[sshキーの追加]欄がある.<br /> ここで,[公開キー・ファイル(.pub)のアップロード]を選択し,<br /> 自身の公開鍵のファイル(~.key.pub)をドラッグ&ドロップでアップロード.<br /> インスタンスを作成.</p> <h2 id="1-3.ssh接続"><a href="#1-3%EF%BC%8Essh%E6%8E%A5%E7%B6%9A">1-3.ssh接続</a></h2> <p>sshクライアントを起動してssh接続を試みる.</p> <h3 id="1-3-1.TeraTermの場合"><a href="#1-3-1%EF%BC%8ETeraTerm%E3%81%AE%E5%A0%B4%E5%90%88">1-3-1.TeraTermの場合</a></h3> <p>TeraTermを起動させたら[ ホスト(T) ]にIPアドレスを書いて,<br /> その下の[ TCPポート ]欄に「22」と書く※1.<br /> [ ok ]ボタンを押す.</p> <p>[ ユーザ名 ]に指定されたユーザ名を記入.<br /> OCIのCentOSの初期ユーザ名は「opc」※2.</p> <p>[ 認証方式 ]で,[ RSA/DSA/ECDSA/ED25119鍵を使う]を選択し,<br /> 1-1節で作成した秘密鍵のパスを指定.<br /> 例:</p> <pre><code>C:\Users\Tanaka\Documents\Oracle\~.key </code></pre> <p>これで接続できる.</p> <p>※1:sshのデフォルトのport番号が22.これは後で利用できるport番号内で任意番号に変更可能<br /> ※2:AWSのRHEL8の場合は「ec2-user」</p> <h3 id="1-3-2.PowerShellの場合"><a href="#1-3-2%EF%BC%8EPowerShell%E3%81%AE%E5%A0%B4%E5%90%88">1-3-2.PowerShellの場合</a></h3> <p>これだけ.</p> <pre><code>> ssh -i [秘密鍵のパス] [ユーザ名]@[IPアドレス] </code></pre> <p>以上。</p> <h1 id="2. 環境設定"><a href="#%EF%BC%92.+%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A">2. 環境設定</a></h1> <h2 id="2-1.パスワード設定"><a href="#2-1%EF%BC%8E%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E8%A8%AD%E5%AE%9A">2-1.パスワード設定</a></h2> <p>ログインできたら,rootユーザとログインユーザ(今回であればopc)のPassを任意に設定しとく.<br /> まずはroot</p> <pre><code>$sudo passwd root (パスワード聞かれるので適当に打つ) (切り替わるか確かめる) $ su - </code></pre> <p>次は別のユーザ</p> <pre><code>#passwd opc </code></pre> <h2 id="2-2.ssh接続のconfファイル書き換え"><a href="#2-2%EF%BC%8Essh%E6%8E%A5%E7%B6%9A%E3%81%AEconf%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88">2-2.ssh接続のconfファイル書き換え</a></h2> <p>セキュリティの観点から,ssh接続のconfigファイルを書き換える.</p> <pre><code>$ sudo vi /etc/ssh/sshd_config </code></pre> <p>とやるといろいろ書き換えられる.<br /> 英語読めば何の設定なのか想像つくと思うのでてきとーにやっとく.</p> <pre><code>Port 11326 (←port番号を22から変更) PermitRootLogin no (←Rootユーザでログインできないようにする) PubkeyAuthentication yes (←公開鍵認証方式) PasswordAuthentication no (←パスワード認証を無効化) などなど </code></pre> <p>これではまだ反映されないので,reloadしてメモリの設定を読み込ませる.<br /> (restartじゃないよ)</p> <pre><code>$ sudo systemctl reload sshd.service </code></pre> <h2 id="2-3.タイムゾーンを変更"><a href="#2-3%EF%BC%8E%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%BE%E3%83%BC%E3%83%B3%E3%82%92%E5%A4%89%E6%9B%B4">2-3.タイムゾーンを変更</a></h2> <p>タイムゾーンを東京に変更しとく.</p> <pre><code>$ sudo timedatectl set-timezone Asia/Tokyo </code></pre> <h2 id="2-4.変更確かめ"><a href="#2-4%EF%BC%8E%E5%A4%89%E6%9B%B4%E7%A2%BA%E3%81%8B%E3%82%81">2-4.変更確かめ</a></h2> <p>Windows端末のPowerShellでPortして疎通確認.</p> <pre><code>> Test-NetConnection [ IPアドレス ] -Port [ Port番号 ] </code></pre> <h1 id="3.yum"><a href="#3%EF%BC%8Eyum">3.yum</a></h1> <p>参考:https://access.redhat.com/ja/articles/3221791</p> <p>/etc/yum.confと/etc/yum.repos.dの二つの領域に分かれる。<br /> ※rhel8からはyumは廃止されdnfに置き換わっている。使い方はほぼ同じ。<br /> 実際、/etc/をみてみると、</p> <pre><code>[root@publicpc1 etc]# ll|grep yum drwxr-xr-x. 2 root root 57 Mar 24 12:32 yum lrwxrwxrwx. 1 root root 12 Nov 12 00:18 yum.conf -> dnf/dnf.conf drwxr-xr-x. 2 root root 217 Mar 24 12:39 yum.repos.d [root@publicpc1 yum]# pwd /etc/yum [root@publicpc1 yum]# ll total 0 lrwxrwxrwx. 1 root root 14 Nov 12 00:18 pluginconf.d -> ../dnf/plugins lrwxrwxrwx. 1 root root 18 Nov 12 00:18 protected.d -> ../dnf/protected.d lrwxrwxrwx. 1 root root 11 Nov 12 00:18 vars -> ../dnf/vars </code></pre> <p>となっている。</p> <h2 id="3-1. yum.conf(dnf.conf)"><a href="#3-1.+yum.conf%28dnf.conf%29">3-1. yum.conf(dnf.conf)</a></h2> <pre><code>[root@publicpc1 etc]# cat yum.conf [main] gpgcheck=1 installonly_limit=3 clean_requirements_on_remove=True best=True skip_if_unavailable=False </code></pre> <p>[main]はセクション名で、ここに記載されているのはyumのグローバルな設定。</p> <pre><code>gpgcheck </code></pre> <p>は署名チェック。1が有効で、0が無効。</p> <h2 id="3-2. yum.repos.d"><a href="#3-2.+yum.repos.d">3-2. yum.repos.d</a></h2> <pre><code>[root@publicpc1 yum.repos.d]# ll total 32 -rw-r--r--. 1 root root 495 May 5 2021 ksplice-ol8.repo -rw-r--r--. 1 root root 759 Apr 13 2021 mysql-ol8.repo -rw-r--r--. 1 root root 253 Mar 20 2021 oci-included-ol8.repo -rw-r--r--. 1 root root 252 Mar 24 12:39 oracle-epel-ol8.repo -rw-r--r--. 1 root root 694 Mar 24 12:39 oraclelinux-developer-ol8.repo -rw-r--r--. 1 root root 2740 Mar 24 12:39 oracle-linux-ol8.repo -rw-r--r--. 1 root root 470 Mar 11 01:09 uek-ol8.repo -rw-r--r--. 1 root root 243 Mar 11 01:09 virt-ol8.repo </code></pre> <p>oracle-linux-ol8.repoの中身を見てみると、ダウンロードする際に参照するURLが記載されている。<br /> ここが間違っていると適切にインストールできなくなる。<br /> ただ、ここを編集することはなさそう。</p> <p>もし社内にあるレポジトリサーバを利用したい場合、対応は2つある。<br /> 1つは、 yum.repos.d配下に~.repoファイルを追加する。<br /> 2つ目は、<code>yum -y install ~</code>で追加する方法。</p> <p>1つ目の場合</p> <pre><code># vim /etc/ yum.repos.d/test.repo [test] name=test baseurl=http://xxx.xxx/xxx.xxx/xxx gpgcheck=0 </code></pre> <p>など。</p> <h1 id="4.踏み台サーバの準備"><a href="#4%EF%BC%8E%E8%B8%8F%E3%81%BF%E5%8F%B0%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E6%BA%96%E5%82%99">4.踏み台サーバの準備</a></h1> <h2 id="4-1.キーペアの準備"><a href="#4-1%EF%BC%8E%E3%82%AD%E3%83%BC%E3%83%9A%E3%82%A2%E3%81%AE%E6%BA%96%E5%82%99">4-1.キーペアの準備</a></h2> <h2 id="4-2.scpでファイル転送"><a href="#4-2%EF%BC%8Escp%E3%81%A7%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E8%BB%A2%E9%80%81">4-2.scpでファイル転送</a></h2> <h2 id="4-3.公開鍵認証の読み込みパス指定"><a href="#4-3%EF%BC%8E%E5%85%AC%E9%96%8B%E9%8D%B5%E8%AA%8D%E8%A8%BC%E3%81%AE%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF%E3%83%91%E3%82%B9%E6%8C%87%E5%AE%9A">4-3.公開鍵認証の読み込みパス指定</a></h2> <p><a href="https://crieit.net/posts/OCI-CentOS">https://crieit.net/posts/OCI-CentOS</a></p> <h1 id="5. Port ForwardingでRDP接続"><a href="#5.+Port+Forwarding%E3%81%A7RDP%E6%8E%A5%E7%B6%9A">5. Port ForwardingでRDP接続</a></h1> <h2 id="5.1 sshトンネリング"><a href="#5.1+ssh%E3%83%88%E3%83%B3%E3%83%8D%E3%83%AA%E3%83%B3%E3%82%B0">5.1 sshトンネリング</a></h2> <h1 id="6.Pythonインストール"><a href="#6%EF%BC%8EPython%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">6.Pythonインストール</a></h1> <h1 id="7.OCI CLIの利用方法"><a href="#7%EF%BC%8EOCI+CLI%E3%81%AE%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95">7.OCI CLIの利用方法</a></h1> <p>公式ドキュメントを参考に進めていくとインストールできる。</p> <h2 id="7.1 Oracle Linux8でオンラインインストール"><a href="#7.1+Oracle+Linux8%E3%81%A7%E3%82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">7.1 Oracle Linux8でオンラインインストール</a></h2> <p>参考<br /> OCIのクイックインストール:https://docs.oracle.com/ja-jp/iaas/Content/API/SDKDocs/cliinstall.htm#InstallingCLI__linux_and_unix</p> <h3 id="7.1.1 dnfでインストール"><a href="#7.1.1+dnf%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">7.1.1 dnfでインストール</a></h3> <p>dnfを使用してCLIをインストール</p> <pre><code>$ sudo dnf -y install oraclelinux-developer-release-el8 We trust you have received the usual lecture from the local System Administrator. It usually boils down to these three things: #1) Respect the privacy of others. #2) Think before you type. #3) With great power comes great responsibility. [sudo] password for opc: Ksplice for Oracle Linux 8 (x86_64) 16 MB/s | 1.2 MB 00:00 MySQL 8.0 for Oracle Linux 8 (x86_64) 9.1 MB/s | 2.4 MB 00:00 MySQL 8.0 Tools Community for Oracle Linux 8 (x86_64) 5.2 MB/s | 308 kB 00:00 MySQL 8.0 Connectors Community for Oracle Linux 8 (x86_64) 214 kB/s | 23 kB 00:00 Oracle Software for OCI users on Oracle Linux 8 (x86_64) 38 MB/s | 46 MB 00:01 Oracle Linux 8 BaseOS Latest (x86_64) 62 MB/s | 49 MB 00:00 Oracle Linux 8 Application Stream (x86_64) 48 MB/s | 37 MB 00:00 Oracle Linux 8 Addons (x86_64) 21 MB/s | 4.9 MB 00:00 Latest Unbreakable Enterprise Kernel Release 6 for Oracle Linu 63 MB/s | 53 MB 00:00 Package oraclelinux-developer-release-el8-1.0-7.el8.x86_64 is already installed. Dependencies resolved. Nothing to do. Complete! </code></pre> <p>続いて、cliをインストール</p> <pre><code>$ sudo dnf install python36-oci-cli Last metadata expiration check: 0:00:34 ago on Sat 13 Aug 2022 03:23:12 PM GMT. Dependencies resolved. =============================================================================================== Package Arch Version Repository Size =============================================================================================== Installing: python36-oci-cli noarch 3.14.0-1.el8 ol8_oci_included 14 M Upgrading: python36-oci-sdk x86_64 2.78.0-1.el8 ol8_oci_included 23 M Installing dependencies: python3-arrow noarch 1.1.1-1.el8 ol8_oci_included 119 k python3-jmespath noarch 0.10.0-1.el8 ol8_oci_included 48 k python3-prompt-toolkit noarch 3.0.29-1.0.2.el8 ol8_oci_included 669 k python3-terminaltables noarch 3.1.0-1.0.1.el8 ol8_oci_included 31 k python3-typing-extensions noarch 3.7.4.2-1.el8 ol8_oci_included 47 k python3-wcwidth noarch 0.2.5-3.el8 ol8_oci_included 48 k Transaction Summary =============================================================================================== Install 7 Packages Upgrade 1 Package Total download size: 38 M Is this ok [y/N]: y Downloading Packages: (1/8): python3-jmespath-0.10.0-1.el8.noarch.rpm 520 kB/s | 48 kB 00:00 (2/8): python3-prompt-toolkit-3.0.29-1.0.2.el8.noarch.rpm 4.2 MB/s | 669 kB 00:00 (3/8): python3-arrow-1.1.1-1.el8.noarch.rpm 740 kB/s | 119 kB 00:00 (4/8): python3-terminaltables-3.1.0-1.0.1.el8.noarch.rpm 298 kB/s | 31 kB 00:00 (5/8): python3-typing-extensions-3.7.4.2-1.el8.noarch.rpm 739 kB/s | 47 kB 00:00 (6/8): python3-wcwidth-0.2.5-3.el8.noarch.rpm 711 kB/s | 48 kB 00:00 (7/8): python36-oci-cli-3.14.0-1.el8.noarch.rpm 46 MB/s | 14 MB 00:00 (8/8): python36-oci-sdk-2.78.0-1.el8.x86_64.rpm 37 MB/s | 23 MB 00:00 ----------------------------------------------------------------------------------------------- Total 45 MB/s | 38 MB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Upgrading : python36-oci-sdk-2.78.0-1.el8.x86_64 1/9 Installing : python3-wcwidth-0.2.5-3.el8.noarch 2/9 Installing : python3-prompt-toolkit-3.0.29-1.0.2.el8.noarch 3/9 Installing : python3-typing-extensions-3.7.4.2-1.el8.noarch 4/9 Installing : python3-arrow-1.1.1-1.el8.noarch 5/9 Installing : python3-terminaltables-3.1.0-1.0.1.el8.noarch 6/9 Installing : python3-jmespath-0.10.0-1.el8.noarch 7/9 Installing : python36-oci-cli-3.14.0-1.el8.noarch 8/9 Cleanup : python36-oci-sdk-2.75.1-1.el8.x86_64 9/9 Running scriptlet: python36-oci-sdk-2.75.1-1.el8.x86_64 9/9 Verifying : python3-arrow-1.1.1-1.el8.noarch 1/9 Verifying : python3-jmespath-0.10.0-1.el8.noarch 2/9 Verifying : python3-prompt-toolkit-3.0.29-1.0.2.el8.noarch 3/9 Verifying : python3-terminaltables-3.1.0-1.0.1.el8.noarch 4/9 Verifying : python3-typing-extensions-3.7.4.2-1.el8.noarch 5/9 Verifying : python3-wcwidth-0.2.5-3.el8.noarch 6/9 Verifying : python36-oci-cli-3.14.0-1.el8.noarch 7/9 Verifying : python36-oci-sdk-2.78.0-1.el8.x86_64 8/9 Verifying : python36-oci-sdk-2.75.1-1.el8.x86_64 9/9 Upgraded: python36-oci-sdk-2.78.0-1.el8.x86_64 Installed: python3-arrow-1.1.1-1.el8.noarch python3-jmespath-0.10.0-1.el8.noarch python3-prompt-toolkit-3.0.29-1.0.2.el8.noarch python3-terminaltables-3.1.0-1.0.1.el8.noarch python3-typing-extensions-3.7.4.2-1.el8.noarch python3-wcwidth-0.2.5-3.el8.noarch python36-oci-cli-3.14.0-1.el8.noarch Complete! </code></pre> <p>インストール完了したらバージョン確認。</p> <pre><code>$ oci --version 3.14.0 </code></pre> <h3 id="7.1.2 構成ファイルの設定"><a href="#7.1.2+%E6%A7%8B%E6%88%90%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E8%A8%AD%E5%AE%9A">7.1.2 構成ファイルの設定</a></h3> <pre><code>$ oci setup config This command provides a walkthrough of creating a valid CLI config file. The following links explain where to find the information required by this script: User API Signing Key, OCID and Tenancy OCID: https://docs.cloud.oracle.com/Content/API/Concepts/apisigningkey.htm#Other Region: https://docs.cloud.oracle.com/Content/General/Concepts/regions.htm General config documentation: https://docs.cloud.oracle.com/Content/API/Concepts/sdkconfig.htm Enter a location for your config [/home/opc/.oci/config]:特に指定しなくてok Enter a user OCID: OCIコンソールよりユーザのOCIDをコピペ Enter a tenancy OCID: OCIコンソールよりコピペ Enter a region by index or name(e.g. 1: af-johannesburg-1, 2: ap-chiyoda-1, 3: ap-chuncheon-1, 4: ap-dcc-canberra-1, 5: ap-hyderabad-1, 6: ap-ibaraki-1, 7: ap-melbourne-1, 8: ap-mumbai-1, 9: ap-osaka-1, 10: ap-seoul-1, 11: ap-singapore-1, 12: ap-sydney-1, 13: ap-tokyo-1, 14: ca-montreal-1, 15: ca-toronto-1, 16: eu-amsterdam-1, 17: eu-frankfurt-1, 18: eu-marseille-1, 19: eu-milan-1, 20: eu-paris-1, 21: eu-stockholm-1, 22: eu-zurich-1, 23: il-jerusalem-1, 24: me-abudhabi-1, 25: me-dcc-muscat-1, 26: me-dubai-1, 27: me-jeddah-1, 28: mx-queretaro-1, 29: sa-santiago-1, 30: sa-saopaulo-1, 31: sa-vinhedo-1, 32: uk-cardiff-1, 33: uk-gov-cardiff-1, 34: uk-gov-london-1, 35: uk-london-1, 36: us-ashburn-1, 37: us-gov-ashburn-1, 38: us-gov-chicago-1, 39: us-gov-phoenix-1, 40: us-langley-1, 41: us-luke-1, 42: us-phoenix-1, 43: us-sanjose-1): 13(OCIコンソールをブラウザで開いてURLを見るとわかる Do you want to generate a new API Signing RSA key pair? (If you decline you will be asked to supply the path to an existing key.) [Y/n]: Y Enter a directory for your keys to be created [/home/opc/.oci]: Enter a name for your key [oci_api_key]: Public key written to: /home/opc/.oci/oci_api_key_public.pem Enter a passphrase for your private key (empty for no passphrase): Private key written to: /home/opc/.oci/oci_api_key.pem Fingerprint: ~なんか記載されている~ Config written to /home/opc/.oci/config </code></pre> <p>完了すると、~/.oci配下にoci_api_key_public.pemが作成されているはず。このキーをOCIコンソールで<br /> アイデンティティ>ユーザー>ユーザーの詳細<br /> に行くと、画面サブに「APIキー」があるのでクリックし、oci_api_key_public.pemの中身を張り付ける。</p> <p>試しに実行してみる。</p> <pre><code>$ oci compute instance list --all Usage: oci compute instance list [OPTIONS] Error: Missing option(s) --compartment-id. </code></pre> <p>コンパートメントIDをオプションで追記しろとのこと。</p> <pre><code>$ oci compute instance list --compartment-id <OCID貼り付け> いっぱい表示された </code></pre> <p>ただ、毎回コンパートメントIDを打ち込むのは面倒なので、oci_cli_rcファイルに書き込むことでコマンド入力時にオプション付ける必要がないようにする。</p> <pre><code>$ vi ~/.oci/oci_cli_rc [DEFAULT] compartment-id=<コンパートメントID> </code></pre> <h3 id="7.1.3 プライベートサブネットからOCI CLIを実行する方法"><a href="#7.1.3+%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%82%B5%E3%83%96%E3%83%8D%E3%83%83%E3%83%88%E3%81%8B%E3%82%89OCI+CLI%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95">7.1.3 プライベートサブネットからOCI CLIを実行する方法</a></h3> <p>OCI CLIでコマンドを実行するとAPIエンドポイントと通信を行う。そのため、プライベートサブネットからAPIを発行してエンドポイントへ到達させるにはいくつかの設定が必要になる。方法は以下3つ。<br /> ①NATゲートウェイを利用してインターネットに出ていく。②サービスゲートウェイを使用して、OCI環境内に閉じた通信を行う。③プロキシを使用してインターネットへ出ていくルートを作る。</p> <p>なずは何も設定しない状態でプライベートサブネットからコマンドを実行してみる。その際、APIの実行履歴を出力させるためにデバックオプション--debug(または-d)をつけて実行する。</p> <pre><code>$ oci compute instance list --all -d ~省略~ DEBUG:oci.base_client.140243454499864:Endpoint: https://iaas.ap-tokyo-1.oraclecloud.com/20160918 INFO:oci.base_client.140243454499864: 2022-08-14 15:05:12.072064: Request: GET https://iaas.ap-tokyo-1.oraclecloud.com/20160918/instances INFO:oci.base_client.140243454499864: 2022-08-14 15:05:23.487363: Request: GET https://iaas.ap-tokyo-1.oraclecloud.com/20160918/instances ~つづく~ </code></pre> <p>となり、エンドポイントへの接続を所定の回数試行して失敗すると、以下のようなエラーが返ってくる。</p> <pre><code>oci.exceptions.ConnectTimeout: (MaxRetryError("OCIConnectionPool(host='iaas.ap-tokyo-1.oraclecloud.com', port=443): Max retries exceeded with url: /20160918/instances?compartmentId=ocid1.compartment.oc1..aaaaaaaantybfae5pn7h77jmgcwgzg2hm27gv6whsiwhmo54ycefc3sxyiwa (Caused by ConnectTimeoutError(<oci.base_client.OCIConnection object at 0x7f8cf8e2f2b0>, 'Connection to iaas.ap-tokyo-1.oraclecloud.com timed out. (connect timeout=10.0)'))",), 'Request Endpoint: GET https://iaas.ap-tokyo-1.oraclecloud.com/20160918/instances See https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdk_troubleshooting.htm for help troubleshooting this error, or contact support and provide this full error message.') </code></pre> <p>実際、エンドポイント(iaas.ap-tokyo-1.oraclecloud.com)にpingを打っても返ってこない。</p> <h4 id="7.1.3.1 NATゲートウェイの利用"><a href="#7.1.3.1+NAT%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E5%88%A9%E7%94%A8">7.1.3.1 NATゲートウェイの利用</a></h4> <p>NATゲートウェイを作成して、プライベートサブネットのルート表に追加させる。</p> <p>まずは、<br /> ネットワーキング>仮想クラウド・ネットワーク>仮想クラウド・ネットワークの詳細>NATゲートウェイ<br /> から「NATゲートウェイの作成」を行う。作成したNATゲートウェイをルート表のルート・ルールに追加する。<br /> <a href="https://crieit.now.sh/upload_images/01951106d2320a385b1563346721930462f9118f303fa.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/01951106d2320a385b1563346721930462f9118f303fa.png?mw=700" alt="image.png" /></a></p> <p>追加できたら、まずはエンドポイント(iaas.ap-tokyo-1.oraclecloud.com)にpingを打つ。きっと返ってくるはずだ。<br /> エンドポイントへの疎通が確認できたら、OCIコマンドを実行してみよう。</p> <pre><code>$ oci compute instance list --all -d </code></pre> <p>インスタンス情報が出力されたと思う。</p> <h4 id="7.1.3.2 サービスゲートウェイの利用"><a href="#7.1.3.2+%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%B2%E3%83%BC%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A4%E3%81%AE%E5%88%A9%E7%94%A8">7.1.3.2 サービスゲートウェイの利用</a></h4> <p>社内ルールで、むやみにインターネットに出ていける設定をしてはいけない場合もあるはず。そういった場合には、OCIのVCN内に閉じた環境でエンドポイントへの通信できるサービスゲートウェイを利用するといい。<br /> ネットワーキング>仮想クラウド・ネットワーク>仮想クラウド・ネットワークの詳細>サービス・ゲートウェイ<br /> からサービスゲートウェイを作成する。<br /> <a href="https://crieit.now.sh/upload_images/01951106d2320a385b1563346721930462f914cd84d54.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/01951106d2320a385b1563346721930462f914cd84d54.png?mw=700" alt="image.png" /></a></p> <p>作成したら、さきほどと同じようにプライベートサブネットのルート表のルート・ルールに追加する。<br /> <a href="https://crieit.now.sh/upload_images/01951106d2320a385b1563346721930462f9151e5f6a7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/01951106d2320a385b1563346721930462f9151e5f6a7.png?mw=700" alt="image.png" /></a></p> <p>追加できたら、まずはエンドポイント(iaas.ap-tokyo-1.oraclecloud.com)にpingを打つ。きっと返ってくるはずだ。<br /> エンドポイントへの疎通が確認できたら、OCIコマンドを実行してみよう。</p> <pre><code>$ oci compute instance list --all -d </code></pre> <p>インスタンス情報が出力されたと思う。</p> <h4 id="7.1.3.3 プロキシを使用してインターネット経由のルーティング"><a href="#7.1.3.3+%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%8D%E3%83%83%E3%83%88%E7%B5%8C%E7%94%B1%E3%81%AE%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0">7.1.3.3 プロキシを使用してインターネット経由のルーティング</a></h4> <p>https通信を行う際にプロキシ経由するように設定をするだけでよい。</p> <pre><code>$ export HTTPS_PROXY=http://10.1.1.45:3128 </code></pre> <p>設定した後はOCI CLIコマンドを実行すると成功するはずだ。</p> <h3 id="7.1.4 インスタンスプリンシパルの設定"><a href="#7.1.4+%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%82%B7%E3%83%91%E3%83%AB%E3%81%AE%E8%A8%AD%E5%AE%9A">7.1.4 インスタンスプリンシパルの設定</a></h3> <h2 id="7.2 インスタンスプリンシパルの設定"><a href="#7.2+%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%82%B7%E3%83%91%E3%83%AB%E3%81%AE%E8%A8%AD%E5%AE%9A">7.2 インスタンスプリンシパルの設定</a></h2> <p>さきほどまではAPIを用いてユーザ認証を実行してきた。それとは別に、インスタンスに対して実行権限を付与させる方法がある。それがインスタンスプリンシパル。プリンシパルは操作の実行主体を指す。</p> <h3 id="7.2.1 別ユーザで実行する"><a href="#7.2.1+%E5%88%A5%E3%83%A6%E3%83%BC%E3%82%B6%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B">7.2.1 別ユーザで実行する</a></h3> <h2 id="7.3 仮想環境の作成"><a href="#7.3+%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%81%AE%E4%BD%9C%E6%88%90">7.3 仮想環境の作成</a></h2> <p>システム全体にCLIをインストールするのは非推奨。</p> <h3 id="7.3.1 仮想環境を構成するディレクトリ作成"><a href="#7.3.1+%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%82%92%E6%A7%8B%E6%88%90%E3%81%99%E3%82%8B%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E4%BD%9C%E6%88%90">7.3.1 仮想環境を構成するディレクトリ作成</a></h3> <p>仮想環境を構成するディレクトリを作成する。<br /> このディレクトリは、仮想環境をアクティブ化する際に打つパスになるので<br /> よく考えてディレクトリ名はつけるように。</p> <pre><code>[root@publicpc oci]# pwd /opt/oci </code></pre> <h3 id="7.3.2 仮想環境作成"><a href="#7.3.2+%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E4%BD%9C%E6%88%90">7.3.2 仮想環境作成</a></h3> <p>まずはPythonのバージョンを確認する。</p> <pre><code>[opc@publicpc ~]$ python --version Python 3.6.8 </code></pre> <p>バージョンに応じた仮想環境作成のコマンドを打つ。</p> <pre><code>python<Pythonのバージョン> -m venv <仮想環境名> [root@publicpc oci]# python3.6 -m venv venv1 </code></pre> <p>/opt/oci配下にvenv1という仮想環境が作成される。</p> <h3 id="7.3.3 仮想環境をアクティブ化"><a href="#7.3.3+%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%82%92%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E5%8C%96">7.3.3 仮想環境をアクティブ化</a></h3> <p>OCI CLIのコマンドを打つときは、仮想環境をアクティブ化したうえで実行させる。<br /> アクティブ化させるコマンドは以下の通り。</p> <pre><code>[root@publicpc oci]# source /opt/oci/venv1/bin/activate (venv1) [root@publicpc oci]# </code></pre> <p>ちなみに、非アクティブ化は以下でできる。</p> <pre><code>(venv1) [root@publicpc oci]# deactivate [root@publicpc oci]# </code></pre> <h2 id="7.4 OCI-CLIのオフラインインストール"><a href="#7.4+OCI-CLI%E3%81%AE%E3%82%AA%E3%83%95%E3%83%A9%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">7.4 OCI-CLIのオフラインインストール</a></h2> <p>会社の社内規則などによってLinuxサーバがインターネット接続できない場合、自分がWindows端末にインストールファイルをダウンロードし、<br /> そのファイルをLinuxサーバに配置して読み込ませることでCLIの実行環境を準備することができる。</p> <p>Github(https://github.com/oracle/oci-cli/releases)から、oci-cli.zipをローカルにダウンロード。<br /> oci-cli.zipを作Linuxサーバに配置し解凍する。</p> <h1 id="8.Ansibleのインストール"><a href="#8%EF%BC%8EAnsible%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">8.Ansibleのインストール</a></h1> <h1 id="9.BlockVolumeのパーティション切りとマウント"><a href="#9%EF%BC%8EBlockVolume%E3%81%AE%E3%83%91%E3%83%BC%E3%83%86%E3%82%A3%E3%82%B7%E3%83%A7%E3%83%B3%E5%88%87%E3%82%8A%E3%81%A8%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88">9.BlockVolumeのパーティション切りとマウント</a></h1> <h1 id="10.コンテナ"><a href="#10%EF%BC%8E%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A">10.コンテナ</a></h1> kawai_mizugorou tag:crieit.net,2005:PublicArticle/17503 2021-07-13T02:44:33+09:00 2021-07-13T02:44:33+09:00 https://crieit.net/posts/8dbd7e242deb435cd79ca8fdedbdfb4e 100万円失いながらハッキングを乗り越え誰でも1分で切り抜きを作れるサービスを公開するまでの失敗と学び <p>先日、<strong>誰でも最短1分でYouTubeの切り抜きを作れる</strong>ウェブサービスを公開しました。</p> <p>私はプログラミングの勉強を始めて1年半の初学者ですが、個人開発でサービスを公開するまでに、数多くの失敗と苦労をしてきました(そして今もしてます笑)。後ほど詳しく書きますが、以下のような経験をしました。</p> <ol> <li>ハッキングを受けデータを盗まれる</li> <li>α版をリリースするも作り直しを決意する</li> <li>巻き返しのため海外フリーランサーを雇うも無駄金となる</li> <li>公開前に本家が同じ機能を発表し諦めかける</li> <li>β版をリリースするも使われない</li> </ol> <p>同じようにプログラミングの勉強をし始めたばかりの方や、個人開発でいつかはサービスを公開したいと考えている方の「転ばぬ先の杖」として、私の経験が役に立てばと思っております。</p> <h1 id="開発したサービス"><a href="#%E9%96%8B%E7%99%BA%E3%81%97%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">開発したサービス</a></h1> <p>YouTubeの公式APIを利用してウェブ上で切り抜きを作成・紹介できるウェブサービス「<strong>YouClip</strong>」を開発しました。</p> <div class="iframe-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/N801yLTnhbo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div> <p>ここから実際に触れます→https://youclip.app</p> <p>私自身が所属するスポーツチームの動画をYouTubeにアップすることが多くあり、試合やプレーの振り返りをするのにあったら便利だなと思ったのが着想のきっかけです。考えてみると、これはスポーツのプレー分析以外にも色々と用途があるのではないかと思い、プログラミングの学習も兼ねて一般向けのサービスとして開発を始めました。</p> <h1 id="今までの失敗と学び"><a href="#%E4%BB%8A%E3%81%BE%E3%81%A7%E3%81%AE%E5%A4%B1%E6%95%97%E3%81%A8%E5%AD%A6%E3%81%B3">今までの失敗と学び</a></h1> <h3 id="1.ハッキングを受けデータを盗まれる"><a href="#%EF%BC%91%EF%BC%8E%E3%83%8F%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0%E3%82%92%E5%8F%97%E3%81%91%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E7%9B%97%E3%81%BE%E3%82%8C%E3%82%8B">1.ハッキングを受けデータを盗まれる</a></h3> <p>開発を始めて間もないある日、いつものように本番サイトを開こうとするとエラーでページが表示されません。おかしいなと思いサーバ上のDBを確認すると、あったはずのテーブルが全て消えており、見慣れない<strong>WARNING</strong>と言う名のテーブルが1つだけありました。そこにはデータが1つだけ入っており、「データを復元して欲しければビットコインを振込め」との脅迫文が書かれていました。<br /> <img width="100%" alt="YouClip demo" title="YouClip demo" src="https://youclip-storage.s3.ap-northeast-1.amazonaws.com/thumbs/warning.png"><br /> (テーブルに入っていた全文)</p> <pre><code>To recover your lost databases and avoid leaking it: visit xxxx and enter your unique token xxxx and pay the required amount of Bitcoin to get it back. Databases that we have: xxxx, xxxx. Your databases are downloaded and backed up on our servers. If we dont receive your payment in the next 9 Days, we will sell your database to the highest bidder or use them otherwise. </code></pre> <blockquote> <p>(日本語訳)<br /> 失ったデータを復元し漏洩したくなければ、xxxxのサイトを訪れ、記載のトークンを入力した上で指定された金額のビットコインを支払え。お前のデータベースはダウンロードし、我々のサーバにバックアップしてある。9日以内に支払いがなければ、誰か高く買ってくれる人に売るか、別の方法で利用するだろう。</p> </blockquote> <p>そこではじめて何者かが<strong>DBをハッキングし、データを人質にビットコインを身代金として要求している</strong>のだと分かりました。幸いまだ開発初期で、ユーザーは身内と知り合い数人しかいなく、個人情報もメールアドレスくらいだったので、彼らにはお詫びをしてデータは諦めることにしました。まさかサーバに上げてすぐにハッキングされるとは思ってもおらず、衝撃でした。</p> <p>原因は、あまり深く考えずサーバにphpMyAdminを入れていたのですが、<strong>アカウント名やパスワードが単純</strong>だったため<strong>ブルートフォースアタック</strong>(総当り攻撃)でハッキングされたのだと思います(アホすぎるorz)。セキュリティに無知だった自分が悪いのですが、これが正式なリリース後だったら・・とゾッとしました。リリースする際には十二分にセキュリティ対策をしようと、苦い教訓となりました。(当然現在はphpMyAdminも入れてません)</p> <h3 id="2.α版をリリースするも作り直しを決意する"><a href="#%EF%BC%92%EF%BC%8E%CE%B1%E7%89%88%E3%82%92%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%99%E3%82%8B%E3%82%82%E4%BD%9C%E3%82%8A%E7%9B%B4%E3%81%97%E3%82%92%E6%B1%BA%E6%84%8F%E3%81%99%E3%82%8B">2.α版をリリースするも作り直しを決意する</a></h3> <p>当初作ろうと思っていた機能とデザインを一通り実装し終えたので、α版として身近な友人や親戚に使ってもらうことにしました。Adobe XDでUI案を作った段階で友人などからフィードバックはもらっていたし、自分でも使いやすく作ったつもりだったので、それほど大きな問題はないだろうと思ってました。しかし、いざ使ってもらうと<strong>肝心の切り抜き機能が使いづらい</strong>と感じる人が多いことが分かりました。そのため、<strong>切り抜き機能のフローやUIを抜本的に見直す</strong>ことを決意し、その影響はサービス全体にも及んで結果として大改修をする羽目になりました。</p> <p>Adobe XDでUI案を作った段階で友人からフィードバックをもらっていたのですが、それでも今回の問題に気付けなかったのは、2つ陥りがちな罠にはまっていたと思います。</p> <ul> <li>画面デザインを見せて意見を聞くだけで、<strong>ユーザーが操作しているところを観察しなかった</strong></li> <li>元からサービスの内容を知っている友人に意見を聞くだけで、<strong>完全に初見の人からフィードバックをもらわなかった</strong></li> </ul> <p>やはり実際に<strong>操作するのを見て初めて気が付く</strong>ことが多くあり、XDの画面デザインを見せるだけでなく、<strong>プロトタイプ機能</strong>を使って模擬的に操作してもらうべきでした。また、サービスの前提知識がある人やITリテラシーが高い人だと、多少分かりにくくても使いこなせてしまいます。実際にこれから使うターゲットユーザーに合わせ、<strong>初見でITリテラシーもそこまで高くない人</strong>からもフィードバックをもらうべきでした</p> <h3 id="3.巻き返しのため海外フリーランサーを雇うも無駄金となる"><a href="#%EF%BC%93%EF%BC%8E%E5%B7%BB%E3%81%8D%E8%BF%94%E3%81%97%E3%81%AE%E3%81%9F%E3%82%81%E6%B5%B7%E5%A4%96%E3%83%95%E3%83%AA%E3%83%BC%E3%83%A9%E3%83%B3%E3%82%B5%E3%83%BC%E3%82%92%E9%9B%87%E3%81%86%E3%82%82%E7%84%A1%E9%A7%84%E9%87%91%E3%81%A8%E3%81%AA%E3%82%8B">3.巻き返しのため海外フリーランサーを雇うも無駄金となる</a></h3> <h4 id="3−1.Crowdworks経由で中国人のエンジニアと働く"><a href="#%EF%BC%93%E2%88%92%EF%BC%91%EF%BC%8ECrowdworks%E7%B5%8C%E7%94%B1%E3%81%A7%E4%B8%AD%E5%9B%BD%E4%BA%BA%E3%81%AE%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E5%83%8D%E3%81%8F">3−1.Crowdworks経由で中国人のエンジニアと働く</a></h4> <p>大改修により、当初の予定よりだいぶリリース時期が遅れることが見込まれたため、巻き返しに<strong>スポットで外部のエンジニアを雇えないか</strong>考えました。知り合いをはじめ、Twitterや、<a target="_blank" rel="nofollow noopener" href="https://crowdworks.jp/">クラウドワークス</a>、<a target="_blank" rel="nofollow noopener" href="https://www.lancers.jp/">ランサーズ</a>などのクラウドソーシングサイトを利用して、フリーランスエンジニアの方々に連絡してみましたが、いいなと思う人はやはり時給にすると5,000円以上といった感じでした。<strong>自腹を切る上に、どれぐらい開発に時間がかかるかも読み切れない</strong>中で、言い方は悪いですが少しでもコストパフォーマンスの良さそうな人を必死に探しました。</p> <p>そんな中、<a target="_blank" rel="nofollow noopener" href="https://crowdworks.jp/">クラウドワークス</a>に登録していたエンジニアの1人が、技術スタックや経験、公開しているポートフォリオの観点からも良さそうで、かつ時給3,000円程度のオファーだったので、その人にお願いしてみることにしました。やりとりしている段階で気が付きましたが、登録してある情報は日本語でしたが実際には彼は<strong>中国人</strong>で、英語と中国語しか使えないようでした。私自身は留学経験もあり、英語でやりとりするのに抵抗はなかったのでそのまま彼と契約することにしました。</p> <p>彼とは作業時間に応じて支払いをする形(プロジェクト形式)で、途中休暇を挟みながらも足掛け2ヶ月程度リモートで一緒に働いたのですが、<strong>完成前に契約を打ち切りました</strong>。今も使っている有用なライブラリを紹介してくれたり、技術力はある程度期待通りだったのですが、次第に<strong>胡散臭いところや、面倒な交渉が多く発生するようになった</strong>のが原因です。</p> <p>例えば、タスク量はそれほど変わっていないはずなのに、<strong>後半になるにつれ作業時間がなぜか右肩上がり</strong>で増えていました。また、兄弟がコロナにかかって重症で大変なので<strong>ボーナスをくれないか</strong>と交渉されたり、契約の終わりの方には次のクライアントを見つけるのに海外のフリーランスサイトに登録したいが出来ないので、<strong>アカウントを貸してくれないか</strong>とお願いされたりしました。このようなことが積み重なって、次第に信用できなくなっていきました。</p> <h4 id="3−2.海外のクラウドソーシングサイトでインド人・ウクライナ人・スペイン人と働く"><a href="#%EF%BC%93%E2%88%92%EF%BC%92%EF%BC%8E%E6%B5%B7%E5%A4%96%E3%81%AE%E3%82%AF%E3%83%A9%E3%82%A6%E3%83%89%E3%82%BD%E3%83%BC%E3%82%B7%E3%83%B3%E3%82%B0%E3%82%B5%E3%82%A4%E3%83%88%E3%81%A7%E3%82%A4%E3%83%B3%E3%83%89%E4%BA%BA%E3%83%BB%E3%82%A6%E3%82%AF%E3%83%A9%E3%82%A4%E3%83%8A%E4%BA%BA%E3%83%BB%E3%82%B9%E3%83%9A%E3%82%A4%E3%83%B3%E4%BA%BA%E3%81%A8%E5%83%8D%E3%81%8F">3−2.海外のクラウドソーシングサイトでインド人・ウクライナ人・スペイン人と働く</a></h4> <p>ただ、<strong>作業自体はある程度スピードアップ</strong>したのは確かだったので、もう少し試してみようと、今度は直接海外のクラウドソーシングサイトを利用してみることにしました。前回の反省を活かして、数時間で出来る小さいタスクに対するコンペ方式で支払いを抑えつつ、何人か試してみていい人がいたら継続しようと考えました。</p> <p>海外のクラウドソーシングサイトで有名な<a target="_blank" rel="nofollow noopener" href="https://www.freelancer.com/">Freelancer.com</a>や<a target="_blank" rel="nofollow noopener" href="https://www.upwork.com/">Upwork</a>を使って、<strong>インド人、ウクライナ人、スペイン人</strong>と働いてみることにしました。確かに金額的には日本よりだいぶ低く抑えられましたが、<strong>お願いした仕様と全く違うもの</strong>を作ってきたり、その多くが自分でやり直さなければいけないことになりました。</p> <p>結局<strong>トータル100万円</strong>近く掛けましたが、果たしてその価値があったかと聞かれると「はい」とは素直に言えない感じになりました。日本のフリーランスの方と働いたことがないため一概には言えないものの、<strong>(中国・インド等の)海外エンジニアだからコスパ的に良いといったことはない</strong>というのが今回の感想です。</p> <h3 id="4.公開前に本家が同じ機能を発表し諦めかける"><a href="#%EF%BC%94%EF%BC%8E%E5%85%AC%E9%96%8B%E5%89%8D%E3%81%AB%E6%9C%AC%E5%AE%B6%E3%81%8C%E5%90%8C%E3%81%98%E6%A9%9F%E8%83%BD%E3%82%92%E7%99%BA%E8%A1%A8%E3%81%97%E8%AB%A6%E3%82%81%E3%81%8B%E3%81%91%E3%82%8B">4.公開前に本家が同じ機能を発表し諦めかける</a></h3> <p>改修も終えそろそろリリースできそうだなと考えていたある日、<strong>YouTube本家</strong>が動画の一部を切り取ってSNSでシェアできる<strong>クリップ</strong>と言う機能を実験的に米国で開始したと言う<a target="_blank" rel="nofollow noopener" href="https://japan.cnet.com/article/35165756/">ニュース</a>が流れてきました。</p> <p>本家が、名前もほぼそのまま、同等の機能を出してくるとは思ってもおらず、リリースしても本家に勝てるはずがないと諦めかけました。しかし少し冷静になって考えてみると、まだ実験的な位置付けで本当に追加されるかもわからないことに加え、そもそも<strong>本家は1つの場面しか切り抜けないが、YouClipは複数の場面を切り抜いて繋げられる</strong>ことなど、機能面でも違いがありました。</p> <p>そこで、この機能面の違いに目を向ければ、特定のユースケースでYouClipにも需要がまだあるのではないかと考え直し、そのユースケースに集中しようと考え直しました。具体的には、複数の場面を切り抜いて繋げる必要があるような、<strong>長時間のライブ配信</strong>がメインの<strong>VTuber動画</strong>や、<strong>ゲーム実況配信</strong>にサービスの主眼を置くことにしました。</p> <h3 id="5.β版をリリースするも使われない"><a href="#%EF%BC%95%EF%BC%8E%CE%B2%E7%89%88%E3%82%92%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%99%E3%82%8B%E3%82%82%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%AA%E3%81%84">5.β版をリリースするも使われない</a></h3> <p>そうやって何とかβ版のリリースに漕ぎ着け、知り合いやTwitter経由でユーザーを集め始めたのですが、一度はサイトに訪問してくれるものの、そのまま何もせずに帰ってしまう人がほとんどでした。Google Analyticsで見てみると、ホーム画面や再生画面の<strong>直帰率が70%近く</strong>あり、全く以て<strong>穴の開いたバケツ</strong>のような状態であることがわかりました。</p> <p>そこからは直接ユーザーに意見を聞いたり、Analyticsで取れる各種KPIや<a target="_blank" rel="nofollow noopener" href="https://clarity.microsoft.com/">Microsoft Clarity(※)</a>の画面レコーディングデータを見つつ、仮説を立てて離脱率が下がるよう改善を繰り返しました。</p> <p>例えば、再生画面の画面レコーディングを見ると、ミュート解除ボタンに気づかず、そのままミュート状態で見続けているユーザーが多くいることに気づきました。ミュート状態だと面白さは半減するため、<strong>ミュート解除ボタンをより目立つ位置と見た目に変える</strong>ことで、再生画面の離脱率を10pt%近く下げることができました。<br /> <img width="90%" alt="ミュート解除の改善" title="ミュート解除の改善" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23090/e846cb3a-c2b6-011e-3540-1b60dd190bd0.png"></p> <p>まだまだ十分とは全く言えない状態ですが、このような小さな改善を繰り返すことで、バケツの穴を少しずつ塞いでいっています。</p> <blockquote> <p>(※)Microsoftの無料ヒートマップツール「Clarity」について<br /> Clarityは、ユーザーの行動を無料で把握できる分析ツールで、匿名化されたユーザーの画面操作記録を見ることが出来ます。</p> </blockquote> <div class="iframe-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/KAibwlJnx9Y" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div> <blockquote> <p>詳しくは以下のサイトなどを見てもらうと良いと思いますが、実際にユーザーがどんな風にサービスを使っているのかを知るのにとても役立ちます!<br /> https://sogyotecho.jp/clarity/</p> </blockquote> <h1 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h1> <p>このようにたくさん失敗をし、正直こんなことやって意味あるのか自問自答することもありましたが、長年やってみたいと思っていた個人開発でサービスを作れた事は楽しかったし良い経験となりました。</p> <p>こんなこと出来ないかと空想することから始まり、そこからどうやったら実現出来るか1つずつ考え・調べて・学びながら実装していく作業はとても楽しかったです。そうやって苦労して作ったサービスが誰かに使われているのを見るのはとても嬉しく、新しく投稿があると思わず全部いいねしてしまいます笑</p> <p>また、当然サービスをリリースしても知ってもらわなければ意味がないので、苦手なSNSの運用やマーケティングについても現在進行系で試行錯誤しながら学んでいます。(こちらも現在進行系で失敗?!しているので、いつか振り返りたい)</p> <p>以上が個人開発でサービスを公開するまでの私の失敗と学びです。<br /> 何かしら個人開発をしている人、これからしたい人の参考になれば嬉しいです!</p> Kendai tag:crieit.net,2005:PublicArticle/17393 2021-06-12T18:01:20+09:00 2021-06-12T18:01:20+09:00 https://crieit.net/posts/DynamoDB DynamoDBの多対多で隣接リストやった時の冗長性対策どうしよう <p>DynamoDBウキウキで初めて早速引っかかる問題の一つが多対多とn+1問題だと思うんだが、これについてはAWSのドキュメントに一応解決方法が乗っている。それについて書いた記事がこれ→「<a target="_blank" rel="nofollow noopener" href="https://hack-le.com/dynamodb-many-to-many/">DynamoDBで多対多のテーブル設計 – 或る阿呆の記</a>」。</p> <p>隣接リストというデザインパターンで、初めて見た時はギョッとしたけど、実際やってみるとたしかに効率的にクエリが書ける。テーブルの構造についても、ああつまりRDBの中間テーブルに要素をもたせるとこうなるのか、ということで、個人的には納得もした。</p> <p>ただこの方式は、冗長である。したがって、同一であるべきアイテムの情報に不整合が発生する可能性がある。それについて、どう対応したもんなんだろうか。</p> <h2 id="トランザクション処理"><a href="#%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E5%87%A6%E7%90%86">トランザクション処理</a></h2> <p>真っ先に思い浮かぶのはトランザクション処理だ。進化を続けるDynamoDBは、一定の制約はあるもののトランザクション処理もサポートするようになった。試しに使ってみたのが「<a target="_blank" rel="nofollow noopener" href="https://hack-le.com/transact-write-items-sample/">DynamoDBにboto3でトランザクションな書き込み – 或る阿呆の記</a>」の記事。</p> <p>ただ数的な制約とか、boto3.resource()だと使えなくて低レベルなboto3.client()を使う必要があったとか、Lambda関数のboto3が古くてzipあげる謎工程が必要だったとか(さすがにこれはもう解決しているだろうが)あって、結局実際には使わなかった。</p> <p>トランザクション処理はDynamoDBにおいては最終手段の気もする。</p> <h2 id="バッチによる定期メンテ"><a href="#%E3%83%90%E3%83%83%E3%83%81%E3%81%AB%E3%82%88%E3%82%8B%E5%AE%9A%E6%9C%9F%E3%83%A1%E3%83%B3%E3%83%86">バッチによる定期メンテ</a></h2> <p>次に思い浮かぶのは、不整合を確認するバッチによる定期メンテだ。一日に一回くらいテーブルをフルスキャンして、おかしなところがないか確認する。</p> <p>というとなんか阿呆っぽいんだけれど、実際こういうバッチを走らせてなんとかしているシステム世の中たくさんあると思う。。。</p> <p>リアルタイム性を犠牲にできるユースケースであれば、なんだかんだでこれが一番ラクな気がする。しかしそうやって謎のバッチスクリプトがあちこちで走り始めると、何がなんだかわからなくなってくるので、管理の問題は別途出てくるけど……。</p> <h2 id="ほっといてもそのうち直る仕組み"><a href="#%E3%81%BB%E3%81%A3%E3%81%A8%E3%81%84%E3%81%A6%E3%82%82%E3%81%9D%E3%81%AE%E3%81%86%E3%81%A1%E7%9B%B4%E3%82%8B%E4%BB%95%E7%B5%84%E3%81%BF">ほっといてもそのうち直る仕組み</a></h2> <p>このケースはけっこうある。たとえば最新値などがそうで、最新値は常に変わり続けるから、たとえ1回ミスって不整合があっても、ほっといたら再更新かかるのでなおっている。それだったらわざわざコストかけて面倒見るより、あえて放っておく、で十分だったりする。</p> <h2 id="結局……"><a href="#%E7%B5%90%E5%B1%80%E2%80%A6%E2%80%A6">結局……</a></h2> <p>ということで、自分はといえば、結局</p> <ul> <li>リアルタイム性が必要→更新が多いはず→対応しない。ほっといたらまぁだいたい合ってるでしょって感じ</li> <li>リアルタイム性はそんなでもない→あんまり更新もない→バッチで確認したら十分</li> </ul> <p>のあわせ技でなんとかしている感じ。とはいえ、実際不整合はほとんど発生していなくて、たまに見つかるとだいたいアプリケーションのロジックがそもそもポカしていたりするので、まぁこれくらいでいいのかなぁと思いつつ、みんなどうしているんだろうというのは気になっている。</p> tama tag:crieit.net,2005:PublicArticle/17369 2021-06-07T23:51:13+09:00 2021-06-12T20:43:51+09:00 https://crieit.net/posts/AWS-EC2-Docker 【メモ】AWSでEC2を立ててDockerを起動してみる。 <h3 id="AWSで、EC2(仮想サーバー)を立てて、その上でDocker(ドッカー)を起動してみる。"><a href="#AWS%E3%81%A7%E3%80%81EC2%EF%BC%88%E4%BB%AE%E6%83%B3%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%EF%BC%89%E3%82%92%E7%AB%8B%E3%81%A6%E3%81%A6%E3%80%81%E3%81%9D%E3%81%AE%E4%B8%8A%E3%81%A7Docker%EF%BC%88%E3%83%89%E3%83%83%E3%82%AB%E3%83%BC%EF%BC%89%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B%E3%80%82">AWSで、EC2(仮想サーバー)を立てて、その上でDocker(ドッカー)を起動してみる。</a></h3> <p><strong>ブラウザからEC2のIPアドレスにアクセスして「Hello from Docker!!!」を表示させる手順。</strong></p> <ol> <li>VPCを作成する。</li> <li>サブネットを作る。</li> <li>インターネットゲートウェイをくっつける。(VPCにアタッチする)</li> <li>サーバー(EC2)を立てる。<br /> (インスタンス作成でVPCとサブネットを紐付け、自動割り当てパブリックIPを有効にする)</li> <li>ファイアウォールを設定する。(セキュリティグループの設定(ポート:SSH=22とhttp=80))</li> <li>ルーティングを設定する。(VPC→ルートテーブル→ルートの編集→サブネットの関連付け)</li> <li>SSHでサーバーにアクセスする。(Tera Termを使った)</li> </ol> <pre><code>ssh://[email protected](パブリックIPv4アドレスと予めDLしておいたキーペア) </code></pre> <ol start="8"> <li>Dockerをインストールする。</li> </ol> <pre><code>sudo yum -y update (パッケージ更新) sudo yum -y install docker (Dockerインストール) sudo service docker start (Docker起動) sudo docker info (Docker状態) mkdir hello-docker (作業用フォルダ作成) cd hello-docker (作業用フォルダ移動) vi hello-docker.html (表示用HTML作成) Hello from Docker !! vi Dockerfile (dockerのイメージを作成) FROM nginx COPY ./hello-docker.html /usr/share/nging/html/ (/usr/share/nging/html/直下が初期表示らしい) sudo docker build -t hello-docker-ec2 . (tオプションで名前を付けれる。最後の「.」を忘れない) sudo docker run --rm -d -p 80:80 hello-docker-ec2 (イメージを元にdockerのコンテナを起動) ※--rm(コンテナ停止と同時にコンテナ削除)-d(バックグラウンド起動)-p(どのポート使うか) sudo docker ps (dockerが起動しているか確認) </code></pre> <ol start="9"> <li>EC2にアクセスして「Hello from Docker!!」を表示させる。<br /> (http://52.15.152.1xx/hello-docker.html)</li> </ol> しんじ。 tag:crieit.net,2005:PublicArticle/17118 2021-05-13T23:16:42+09:00 2021-05-13T23:16:42+09:00 https://crieit.net/posts/Lambda-DeadLetterQueue-DLQ Lambda関数でDeadLetterQueue(DLQ)を試す <p>AWSのLambda関数をイベントで起動した時、複数回エラーを起こしたら、DeadLetterQueue、すなわちDLQを飛ばしたい。DLQを試すもの。Cloudformation使う。DLQはSNSとSQSが指定できるが、今回はSNSを使う。</p> <h2 id="系"><a href="#%E7%B3%BB">系</a></h2> <p>以下の系を試す。</p> <p>CloudWatch -> Lambda -(error)-> SNS -> Email</p> <h2 id="やってみる"><a href="#%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">やってみる</a></h2> <p>CloudWatchで1分ごとにLambda関数を起動するようなものを、Cloudformationwと使って用意する。SNS Topicについては、コンソールから作成する。</p> <h3 id="SNS Topicを作る"><a href="#SNS+Topic%E3%82%92%E4%BD%9C%E3%82%8B">SNS Topicを作る</a></h3> <p>コンソールからSNS Topicを作成する。名前は仮に DeadLetterQueueTopicとする。とりあえずEメールに飛ばすようにしておくと、確認しやすいと思う。</p> <h3 id="ディレクトリ構成"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90">ディレクトリ構成</a></h3> <p>ディレクトリ構成は以下。__init__.pyは空。</p> <p>.<br /> ├── __init__.py<br /> ├── hello_world_function<br /> │   ├── __init__.py<br /> │   ├── hello_world<br /> │   │   ├── __init__.py<br /> │   │   └── app.py<br /> │   └── requirements.txt<br /> ├── samconfig.toml<br /> └── template.yaml</p> <p>という構成でやってみる。</p> <h3 id="template.yaml"><a href="#template.yaml">template.yaml</a></h3> <p>TargetArn に SNS Topic のARNを入れる。</p> <pre><code>AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-app Sample SAM Template for sam-app Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world_function Handler: hello_world/app.lambda_handler Runtime: python3.8 Policies: - AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AmazonSNSFullAccess Events: HelloWorld: Type: Schedule Properties: Schedule: rate(1 minute) DeadLetterQueue: Type: SNS TargetArn: arn:aws:sns:xxxxxxxxxxxxxxxxxx:DeadLetterQueueTopic </code></pre> <h3 id="samconfig.toml"><a href="#samconfig.toml">samconfig.toml</a></h3> <p>sam build –use-container の後に sam deploy –guided で作るとよい。以下のようになる。</p> <pre><code>version = 0.1 [default] [default.deploy] [default.deploy.parameters] stack_name = "sam-app" s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-1miie23rwqpqg" s3_prefix = "sam-app" region = "ap-northeast-1" capabilities = "CAPABILITY_IAM" </code></pre> <h3 id="requirement.txt"><a href="#requirement.txt">requirement.txt</a></h3> <p>空でもいいけど。</p> <pre><code>requests six regex </code></pre> <h3 id="hello&#95;world/app.py"><a href="#hello%26%2395%3Bworld%2Fapp.py">hello_world/app.py</a></h3> <pre><code>def lambda_handler(event, context): return True </code></pre> <h3 id="DLQ"><a href="#DLQ">DLQ</a></h3> <pre><code>sam build --use-container sam deploy </code></pre> <p>デプロイすると、1分間にLambda関数が起動する。で、このままだと正常に終了してDLQが飛ばないので、わざとエラーを起こす。そのためには、上記コードの raise Exception のエラーを外してデプロイする。しばらく待っていたら、DLQがSNSに飛ぶ。SNSのSubscriberにEmailを用意しておけば、すぐに確認できる。</p> <h3 id="後片付け"><a href="#%E5%BE%8C%E7%89%87%E4%BB%98%E3%81%91">後片付け</a></h3> <p>SNSのトピックは手動で作成したので、手動で消す。</p> <p>忘れずにスタックを消す。消し忘れると延々とLambda関数が起動し続けることになってしまうので注意。<br /> <code>aws cloudformation delete-stack --stack-name sam-app</code></p> <h2 id="DLQ -&gt; SNS -&gt; Lambdaにした時のproperty"><a href="#DLQ+-%26gt%3B+SNS+-%26gt%3B+Lambda%E3%81%AB%E3%81%97%E3%81%9F%E6%99%82%E3%81%AEproperty">DLQ -> SNS -> Lambdaにした時のproperty</a></h2> <p>Slackとかに飛ばしたい時は、DLQのSNS経由でLambda関数に飛ばすことになる。Lambda関数のeventのpropertyをメモしておく。</p> <pre><code>{ "Records": [ { "EventSource": "aws:sns", "EventVersion": "1.0", "EventSubscriptionArn": "xxxxx", "Sns": { "Type": "Notification", "MessageId": "xxxx", "TopicArn": "xxxx" "Subject": None, "Message": "xxxxxxx", "Timestamp": "2021-05-13T03:02:52.129Z", "SignatureVersion": "1", "Signature": "xxxx", "SigningCertUrl": "xxxx", "UnsubscribeUrl": "xxxx", "MessageAttributes": { "RequestID": { "Type": "String", "Value": "xxxx", }, "ErrorCode": { "Type": "String", "Value": "200" } } } } ] } </code></pre> <p>気になるのは event.Sns.Message だが、これはシリアライズされたJSON。json.loads()でデシリアライズすると以下のようになる。</p> <pre><code>{ "version": "0", "id": "xxxx", "detail-type": "Scheduled Event", "source": "aws.events", "account": "xxxx", "time": "2021-05-13T02:47:42Z", "region": "xxxx", "resources": [ "xxxx", ], "detail": {} } </code></pre> <p>resourcesとidから、エラーを起こした元のLambda関数を辿ることができる。</p> <h2 id="参考URL"><a href="#%E5%8F%82%E8%80%83URL">参考URL</a></h2> <p>ありがとうございました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-scheduledevents-example-use-app-spec.html">CloudWatch イベント アプリケーションの AWS SAM テンプレート – AWS Lambda</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-function-deadletterqueue.html">DeadLetterQueue – AWS Serverless Application Model</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-policies">AWS::Serverless::Function – AWS Serverless Application Model</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/articles/eventbridge-supports-dead-letter-queue/">[アップデート]EventBridgeのデッドレターキュー(DLQ)を使ってみた | DevelopersIO</a></li> </ul> <p>関連コンテンツ</p> <h2 id="関連記事"><a href="#%E9%96%A2%E9%80%A3%E8%A8%98%E4%BA%8B">関連記事</a></h2> <p>スポンサーリンク</p> tama tag:crieit.net,2005:PublicArticle/16764 2021-03-22T15:13:28+09:00 2021-03-22T15:13:28+09:00 https://crieit.net/posts/AWS-60583588bcedf AWS導入支援 <p><strong>AWSとは</strong><br /> AWS(Amazon Web Services)はAmazon.comが提供するクラウドサービスとして、企業や組織のより迅速な活動やITコスト削減、アプリケーション拡張を実現するために役立つ幅広いコンピューティング、ストレージ、データベース、分析ツール、アプリケーション、デプロイサービスです。提供されているサービスは165以上となり、コンピューティング、ストレージ、データベース、分析、ネットワーキングや管理ツール、IoT、AI、セキュリティなどの幅広いクラウドベースの製品を利用できます。<br /> <strong>AWS導入のメリット</strong><br /> <strong>初期コストの削減</strong><br /> ハードウェアやソフトウェアの購入が不要なため、初期の調達コストを大幅に削減できます。<br /> <strong>セキュリティと法規制対応</strong><br /> AWSのクラウドサービスは、ISO 27001、SOC、PCI DSSなど世界中の様々なセキュリティ標準の要件を満たしています。<br /> <strong>スピードと高い伸縮性</strong><br /> ハードウェア機器などの調達が不要なため、迅速に利用開始できます。<br /> リソースが必要になれば必要な分だけ、サーバのリソースを増強可能です。また、不要になったリソースの縮小もできます。<br /> <strong>BCP(事業継続計画)対策</strong><br /> AWSクラウドの活用により、自然災害などに罹災した際のリスクを軽減できます。インターネットに接続できれば、AWSにある保管データやサービス提供を活用することで、事業継続が可能です。<br /> AWSは165以上のサービスを提供しており、それで、<a target="_blank" rel="nofollow noopener" href="https://kaopiz.com/ja-aws-cloud-service/">AWS導入</a>を検討するなら、導入や移行には幅広い知識と豊富な経験を持っている会社を探してください。</p> hanhnh tag:crieit.net,2005:PublicArticle/16650 2021-01-26T03:25:25+09:00 2021-01-26T03:43:15+09:00 https://crieit.net/posts/AWS-Lambda AWS Lambdaのロギングを考える <p>ロギングをどうするかで困っていた。AWS Lambdaでは、プリント出力したものがCloudWatchに保存されてとても便利なのだが、考えもなしにとりあえずポンポン入れていたところ、確かに情報はあるので追えないことはないんだが、地道に時間にあたりをつけて検索するなど、非常に泥臭い作業が要求され、なにかとつらかった。</p> <p>APIのコール回数など集計したいという要件も出てきて、重い腰をあげてロギングについて頑張って考えました、という話。</p> <h2 id="ユースケースを考える"><a href="#%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%92%E8%80%83%E3%81%88%E3%82%8B">ユースケースを考える</a></h2> <p>AWS Lambdaで、何も考えずにとにかく必要そうなのをログ出力しているが、フォーマットも何も整っていないため、ほしい情報を探し当てるのも一苦労だ。</p> <p>ログをjson形式に構造化させてやると、CloudWatch Insightsでクエリを投げて検索・集計することができる。ということで、ユースケースに対応できるように、ログ出力について考えたい。</p> <p>ユースケースとして以下を考慮。</p> <ul> <li>ERRORだけ抜き出す</li> <li>ユーザー別に叩いたAPIについて集計する</li> <li>詳細な検索ができるように、ハンドラに渡された情報は全部取っておく</li> </ul> <p>前提条件として、node.jsを使っていたりPythonを使っていたりする。そのため、特定の言語のライブラリに依存した解決はあまり好ましくない。</p> <h2 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h2> <p>以上の条件を考えて色々調査し、必要なことを考えた結果、以下のような感じになった。</p> <pre><code>{ "msg": string // フィルタリングできるような固有のメッセージ "funcName": string // 実行関数 "dateTime": string // ログ出力した時刻 RFC3339 に則る(jsならDateオブジェクトをconsole出力すればよい) "level": INFO | WARN | ERROR, "event": object // Lambda関数のeventパラメータをそのまま "input": object // 必要なやつ } </code></pre> <p>levelでログレベルを設定。inputとmsgでフィルタリング、集計を容易に。困ったらeventとfuncNameで頑張って追えるようにしておいた。また、独自フォーマットなので言語やライブラリに依存しない。</p> <p>このフォーマットはほとんど「<a target="_blank" rel="nofollow noopener" href="https://www.scalyr.com/blog/aws-lambda-logging-best-practices/">AWS Lambda Logging: An Intro How-To and Best Practices | Scalyr</a>」を参考にしています。</p> <p>以下のような使い方を想定(node.js)。</p> <pre><code>function logging(msg, funcName, event, level, inputValues) { const logMsg = { msg: msg, funcName: funcName, dateTime: new Date(), level: level, event: event, input: inputValues } if (level === "INFO") { console.info(JSON.stringify(logMsg)) } else if (level === "WARN") { console.warn(JSON.stringify(logMsg)) } else if (level === "ERROR") { console.error(JSON.stringify(logMsg)) } else { console.log(JSON.stringify(logMsg)) } } exports.handler = async (event, context, callback) => { logging("CallApi", "functions/users/app.handler", event, "INFO", {userId: "testUser"}); } </code></pre> <p>で、CloudWatch Insightsを使うとたとえば以下のようなクエリを投げて集計できるわけだ。</p> <pre><code>filter msg like /CallApi/ | stats count(input.userId) by input.userId </code></pre> <p>運用を通じてブラッシュアップしていきたい。</p> <h2 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h2> <p>この記事は、開発中のWebサービスQnQで草稿を作りました。よかったら見ていってね(宣伝)。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qnqtree.com/tree/dB3cNjWRbkAeTZsvcpJs">Lambda関数のロギング設計を考える | QnQ</a></p> <p>その他、参考にさせていただいた記事です。ありがとうございました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.scalyr.com/blog/aws-lambda-logging-best-practices/">AWS Lambda Logging: An Intro How-To and Best Practices | Scalyr</a> <ul> <li>一番参考にした記事</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://recipe.kc-cloud.jp/archives/9968">Lambdaの本番業務利用を考える① – ログ出力とエラーハンドリング – ナレコムAWSレシピ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-logging.html">Node.js の AWS Lambda 関数ログ作成 - AWS Lambda</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tonluqclml/items/780370a4575781eb19df">AWS Lambda/PythonでJSON形式でログを出すベストプラクティス - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://scrapbox.io/tasuwo/Node.js_%E3%81%AE_Lambda_%E3%83%AD%E3%82%B0%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Node.js の Lambda ログのフォーマットについて - tasuwo's notes</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://medium.com/@jyotti/node-bunyan%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Faws-lambda%E3%81%A7%E3%81%AE%E3%81%AE%E3%83%AD%E3%82%AE%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86-cd45faa2b261">node-bunyanを使ったAWS Lambdaでのロギング処理 | by Atsushi Nakajo | Medium</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/articles/how-to-cloudwatch-logs-insights/">CloudWatch Logs Insightsでログを調査する前に読む記事 | Developers.IO</a></li> </ul> tama tag:crieit.net,2005:PublicArticle/16645 2021-01-23T21:12:04+09:00 2021-01-23T21:12:04+09:00 https://crieit.net/posts/aws-lightsail-containers-rust-actix-web AWS Lightsail Containers に Actix web をデプロイする <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/actix/actix-web">Actix web</a> で Web アプリケーションを作ったのですが、技術勉強も兼ねていたので、デプロイ先も今まで試したことがないものを試そうとしていました。そこで、日頃業務でも AWS を利用しているということもあり、去年末に発表された <a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/about-aws/whats-new/2020/11/announcing-amazon-lightsail-containers/">AWS Lightsail Containers</a> をデプロイ先に採用しました。</p> <p>AWS Lightsail Containers へのデプロイ自体は非常に簡単でした。また、デプロイにあたり Rust の Docker イメージ作成のやり方も学べました。今回はそのあたりの手順をまとめる形で記事として書き残しておくことにしました。</p> <h1 id="Actix web の Docker イメージを作成する"><a href="#Actix+web+%E3%81%AE+Docker+%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">Actix web の Docker イメージを作成する</a></h1> <p>開発したアプリケーションでは React でフロントエンド開発をしていて、ビルドしたものを Actix web の public フォルダに配置する形で公開しています。そのため、下記の Dockerfile ではマルチステージビルドを利用しておりますが、本質的には <code>FROM rust:1.49</code> 以降の記述が Actix web に関するものとなります。</p> <pre><code class="Dockerfile"># React ビルド用のイメージ FROM node:14.15.4-alpine3.10 as client_builder ARG REACT_APP_API_URL ARG REACT_APP_GYAZO_AUTH_URL ARG REACT_APP_GA_UNIVERSAL_ID WORKDIR /client COPY ./client/package*.json . RUN yarn install ADD ./client . RUN yarn build # Actix web ビルド用のイメージ FROM rust:1.49 # Actix web にアクセスするためのポートを公開する EXPOSE 8080 # Actix web プロジェクトのフォルダをイメージに追加する WORKDIR /server ADD ./server . # プロジェクトフォルダ内で `cargo install` してビルドを生成する RUN cargo install --path . # 不要になったファイル群を削除する RUN ls | grep -v -E 'templates' | xargs rm -r # React ビルド用のイメージでビルドした内容を Actix web ビルド用イメージに追加する COPY --from=client_builder /client/build ./build RUN mkdir tmp # `cargo install` コマンドで生成したビルドを実行して Actix web を起動する # 下記のコマンド名称は Cargo.toml 内の [package.name] に準ずる CMD ["bloggimg-server"] </code></pre> <p>また、<strong>Docker ビルド時のオプション管理を楽にするため、Docker Compose を利用しました。単一の Docker イメージをビルドする際にも利用しておくことで、後々コンテナを追加して連携させたいときにも即座に対応できたりでオススメです。</strong></p> <pre><code class="yml"># docker-compose.yml # context に Actix web プロジェクトのパスを指定する # args に Docker ビルド時に利用したい ARGS の値を環境変数で設定する # image に Docker の &lt;イメージ名:タグ名&gt; を指定する (今回は Docker Hub にデプロイする想定) # env_file に開発/動作検証時に利用したい dotenv ファイルを指定する # ports にポートマッピングの設定を書く version: '3.8' services: app: build: context: ./ args: - REACT_APP_API_URL=${REACT_APP_API_URL} - REACT_APP_GYAZO_AUTH_URL=${REACT_APP_GYAZO_AUTH_URL} - REACT_APP_GA_UNIVERSAL_ID=${REACT_APP_GA_UNIVERSAL_ID} image: n1kaera/bloggimg:v1.0.0 env_file: - ./server/.env ports: - 8080:8080 </code></pre> <p>上記を自分の Actix web プロジェクトに応じて改変し <code>docker-compose up</code> して動作検証します。動作検証ができ次第、<code>docker-compose build</code> を実行して Docker イメージをビルドします。ビルドに成功したら次は Docker Hub にイメージを push します。</p> <h1 id="Docker Hub にビルドしたイメージを push する"><a href="#Docker+Hub+%E3%81%AB%E3%83%93%E3%83%AB%E3%83%89%E3%81%97%E3%81%9F%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92+push+%E3%81%99%E3%82%8B">Docker Hub にビルドしたイメージを push する</a></h1> <p>今回は AWS Lightsail Containers で使用するイメージの管理に <a target="_blank" rel="nofollow noopener" href="https://hub.docker.com/">Docker Hub</a> を利用します。Docker Hub へ push する前に <code>docker login --username=<Docker Hub のユーザ名></code> コマンドで Docker Hub へのログインを済ませておきます。</p> <p>その後 <code>docker-compose push</code> コマンドで Docker イメージを Docker Hub に push します。</p> <p><img src="https://i.gyazo.com/06ce4ca43a26c73c227b9eb768f65685.png" alt="スクリーンショット 2021-01-23 20.37.39.png" /><br /> <strong>Docker Hub のページから、正常に Docker イメージが push できていそうか確認する</strong></p> <p>Docker イメージの push が成功していることを確認できたら、残りは AWS Console 上での作業になります。</p> <h1 id="AWS Console から Lightsail Containers Service を作成する"><a href="#AWS+Console+%E3%81%8B%E3%82%89+Lightsail+Containers+Service+%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">AWS Console から Lightsail Containers Service を作成する</a></h1> <p>AWS Console にログイン後、<a target="_blank" rel="nofollow noopener" href="https://lightsail.aws.amazon.com/ls/webapp/home/containers">Lightsail サービス</a> を選択して Lightsail サービスのトップページへ遷移します。遷移したら Containers タブを選択し、<code>Create container services</code> ボタンから Container Service を作成します。</p> <p><img src="https://i.gyazo.com/42a8e6fa6224a6a52212cdb7461a8515.png" alt="スクリーンショット 2021-01-23 18.55.16.png" /><br /> <strong>AWS Console へログイン後 Lightsail のページに遷移して、Containers タブを選択する</strong></p> <p><img src="https://i.gyazo.com/25c1b55863c4d77249d3105ba7a97afd.png" alt="スクリーンショット 2021-01-23 18.57.06.png" /><br /> <strong>Containers タブを選択すると出てくる、<code>Create container services</code> ボタンをクリックする</strong></p> <p><code>Create container services</code> ボタンをクリックした遷移先の画面で、リージョンやキャパシティ (Micro であれば 3ヶ月間のみ無料で利用可能) 等を選択して、名称を入力します。今回は最初にデプロイのための準備をすでに済ませているので、Container Service を作成するついでにデプロイ設定も行います。</p> <p>デプロイ設定は <code>Set up your first deployment</code> の項目から行うことが可能です。</p> <p><img src="https://i.gyazo.com/e8e4ab4e3b29afaf6298f7eb75355c34.png" alt="スクリーンショット 2021-01-23 19.07.53.png" /><br /> <strong><code>Set up deployment</code> の部分をクリックして、デプロイの設定項目を表示する</strong></p> <p><img src="https://i.gyazo.com/da0fd3ce440f441082a367a0dfe350b6.png" alt="スクリーンショット 2021-01-23 19.12.14.png" /><br /> <strong>Docker Hub イメージを利用してデプロイする際に必要な設定項目を入力する</strong></p> <p><img src="https://i.gyazo.com/eefec162ec66adab937d5139f70763cc.png" alt="スクリーンショット 2021-01-23 19.16.16.png" /><br /> <strong>コンテナのヘルスチェックのための情報を入力する</strong></p> <p><img src="https://i.gyazo.com/0c532eb818754d554a61028f6a177dde.png" alt="スクリーンショット 2021-01-23 19.19.17.png" /><br /> <strong>すべての情報入力が完了したら <code>Create container services</code> ボタンをクリックする</strong></p> <p><img src="https://i.gyazo.com/102978a025c8babbba40886e1b551ae6.png" alt="スクリーンショット 2021-01-23 19.22.48.png" /><br /> <strong>遷移後の画面下部の <code>Deployment versions</code> からデプロイ状況の確認が行える</strong></p> <p><img src="https://i.gyazo.com/eaaf0b1f1d2b2d37807d49764cafa681.png" alt="スクリーンショット 2021-01-23 19.39.55.png" /><br /> <strong>正常にデプロイできていれば <code>Deployment versions</code> の項目が Active になる</strong></p> <p>デプロイが完了したら <code>Public domain</code> が発行されているはずなので、正常にアクセスして Web アプリケーションが利用できそうか確認します。<code>Public domain</code> は該当する Container Service のトップページから確認できます。</p> <p><img src="https://i.gyazo.com/9ee36e7f1018c47a9c12e51ebe6df6d0.png" alt="スクリーンショット 2021-01-23 19.48.23.png" /><br /> <strong>AWS Lightsail Containers のトップページにある <code>Public domain</code> から動作検証する</strong></p> <p><img src="https://i.gyazo.com/a381f52745c002ce47addf7721b7ec0b.png" alt="スクリーンショット 2021-01-23 19.51.27.png" /><br /> <strong>一通りの動作検証を行い、正常にデプロイできていそうか確認する</strong></p> <p>これで作業は完了です。新しい Docker イメージでデプロイし直したい場合は、<code>Deployments</code> タブの <code>Modify your deployment</code> リンクをクリックすれば可能です。</p> <h1 id="(おまけ) 独自ドメインで Container Service へアクセス可能にする"><a href="#%28%E3%81%8A%E3%81%BE%E3%81%91%29+%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%A7+Container+Service+%E3%81%B8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%99%E3%82%8B">(おまけ) 独自ドメインで Container Service へアクセス可能にする</a></h1> <p>AWS Lightsail Containers では独自ドメインの紐付け及び、HTTPS 化も簡単に設定できます。<code>Custom domains</code> タブを選択した後、画面下部にある <code>Create certificate</code> リンクをクリックすることで設定画面を表示します。</p> <p><img src="https://i.gyazo.com/93019714418209872be82c396931beee.png" alt="スクリーンショット 2021-01-23 20.07.22.png" /><br /> <strong><code>Custom domain</code> タブをクリックしてから、<code>Create certificate</code> リンクをクリックする</strong></p> <p><img src="https://i.gyazo.com/b6cc81261f5d3bec4b236e04c0409372.png" alt="スクリーンショット 2021-01-23 20.10.22.png" /><br /> <strong>各種設定項目の入力が完了したら <code>Create</code> ボタンをクリックする</strong></p> <p><img src="https://i.gyazo.com/d8075a0e1eedeb1e518e7f0ae22554a1.png" alt="スクリーンショット 2021-01-23 20.16.02.png" /><br /> <strong>ドメイン検証のために、CNAME レコードの設定を求められるので各自で設定作業を行う</strong></p> <p><img src="https://i.gyazo.com/3aa4608c22ab83193d75b3e968d47b77.png" alt="スクリーンショット 2021-01-23 20.20.26.png" /><br /> <strong>正常に CNAME レコードを設定した後、しばらく経つと Status が Valid になる</strong></p> <p>上記まで確認したら、<code>Create certificate</code> で設定したドメインの CNAME レコードに <code>Public domain</code> の値を設定しておきます。設定内容が反映され次第、独自ドメインへアクセスすることで HTTPS 経由で Container Service へアクセスできるようになります。</p> <p><img src="https://i.gyazo.com/21d69cbc2370b588e01cfae43afa50ca.png" alt="スクリーンショット 2021-01-23 20.27.26.png" /><br /> <strong><code>Custom domains</code> で Container Service で起動しているサービスにアクセスできることを確認する</strong></p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>AWS Lightsail Containers を利用して Actix web プロジェクトをデプロイする手順について簡単にまとめてみました。便利ではあるものの、個人開発で利用する分には価格面及び性能面で Lightsail Instance のほうが良いなと現時点では感じてしまいました。</p> <p>しかし、日本リージョンが用意されていたりロードバランサーを備えていたり、簡単にスケールさせやすくかつ定額で利用可能なサービスであるというメリットを活かせる場面があれば有効活用できそうだなと感じました。</p> <h1 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/blogs/news/lightsail-containers-an-easy-way-to-run-your-containers-in-the-cloud/">Lightsail コンテナ: クラウドでコンテナを実行する簡単な方法 | Amazon Web Services ブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://hub.docker.com/_/rust">rust - Docker Hub</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-enabling-distribution-custom-domains">Enabling custom domains for your Amazon Lightsail distributions | Lightsail Documentation</a></li> </ul> nikaera tag:crieit.net,2005:PublicArticle/16540 2021-01-06T01:15:51+09:00 2021-01-11T02:06:56+09:00 https://crieit.net/posts/aws-lambda-cron 📝 AWS Lambda で cron みたいに定期実行する <p>コンテナをホットスタンバイさせるために EC2 でインスタンス起動して cron で ping 飛ばしていたのですが、コスト的に勿体ないなーと思っていました。しかし、「AWS Lambda 使えばいいじゃん」という指摘を受け、確かにってなったので cron で定期実行していた ping 処理を AWS Lambda + EventBridge で置き換えました。</p> <p>実は <a target="_blank" rel="nofollow noopener" href="https://devcenter.heroku.com/articles/scheduler">Heroku Scheduler</a> とか使って同様のことをしていた時期もあったのですが、10分毎しか実行できない制約があったりして使い勝手が悪かったので、後々も使っていけそうな知見な気がしたのでメモがてら記事で残しておくことにしました。</p> <p>まず、AWS Console から Lambda サービスを選択して関数を新たに作成します。</p> <p><img src="https://i.gyazo.com/b4b591876af8519f9b22cfa35131327c.png" alt="AWS Lambda のトップ画面から関数を新たに作成する" /><br /> <strong>1. AWS Lambda のトップ画面から関数作成のための画面に遷移する</strong></p> <p><img src="https://i.gyazo.com/00a4e4415827a6e745f47e2ca5d39e1d.png" alt="必要な情報を入力して AWS Lambda の関数を作成する" /><br /> <strong>2. 必要な情報を入力して Lambda の関数を作成する</strong></p> <p>関数が作成でき次第、ping 処理を書いていきます。http リクエストを行うためのライブラリとして Node.js の標準モジュール(https) を利用します。</p> <p>Lambda 関数作成直後の <code>index.js</code> は下記のような記述になっていると思います。</p> <pre><code class="javascript">// index.js exports.handler = async (event) => { // TODO implement const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; return response; }; </code></pre> <p>こちらを Node.js の標準モジュール(https) を利用する形で下記のように書き換えます。</p> <pre><code class="javascript">// index.js // "https://www.google.com/" に HTTP リクエストを実行する (ping) const https = require('https'); const httpRequest = async (url) => { return new Promise((resolve, reject) => { const req = https.request(url, (res) => { let body = ''; res.on('data', (chunk) => { body += chunk; }); res.on('end', () => { console.log(`response: ${body}`); resolve(body); }); }) req.on('error', (e) => { console.error(e); reject(e); }); req.end(); }); } exports.handler = async (event) => { const body = await httpRequest("https://www.google.com/"); return { statusCode: 200, body }; }; </code></pre> <p>その後、右上にある <code>Deploy</code> ボタンをクリックして関数にソースコードを反映します。実際に関数が意図したとおりに動作するか、<code>Test</code> ボタンをクリックして動作検証してみます。</p> <p><img src="https://i.gyazo.com/ebbf0c5d9188faea7c49f04da63067fa.png" alt="1. <code>Test</code> ボタンをクリックします" /><br /> <strong>1. <code>Test</code> ボタンをクリックします</strong></p> <p><img src="https://i.gyazo.com/cc4099af0014d6f5ea0701c0e630af63.png" alt="2. 動作検証時のパラメーターを入力してテスト環境を作成する" /><br /> <strong>2. 動作検証時のパラメーターを入力してテスト環境を作成する</strong></p> <p><img src="https://i.gyazo.com/8cee96e8065baa486c966c6596b2f36c.png" alt="3. 2. のテスト環境で関数の動作検証を行い正常に実行できていることを確認する" /><br /> <strong>3. 2. のテスト環境で関数の動作検証を行い正常に実行できていることを確認する</strong></p> <p>正常に関数が実行できていること確認できれば、後は定期実行可能にすれば作業完了です。定期実行するためのスケジューラには EventBridge を利用します。<code>Add trigger</code> ボタンから EventBridge を追加します。</p> <p><img src="https://i.gyazo.com/f2b87343ebe5a32901b60721c8ef98e8.png" alt="1. <code>Add trigger</code> ボタンからトリガー追加画面に遷移する" /><br /> <strong>1. <code>Add trigger</code> ボタンからトリガー追加画面に遷移する</strong></p> <p><img src="https://i.gyazo.com/de52cedffde5acbf38bec0ad55f7a98c.png" alt="2. EventBridge トリガーを追加して定期実行の設定を行う" /><br /> <strong>2. EventBridge トリガーを追加して定期実行の設定を行う</strong></p> <p><img src="https://i.gyazo.com/c519d210f18b58ff4f9aeded662ee995.png" alt="3. EventBridge トリガーの追加が無事に完了したことを確認する" /><br /> <strong>3. EventBridge トリガーの追加が無事に完了したことを確認する</strong></p> <p>また <code>2.</code> では 1分毎に実行するスケジュールを設定しましたが、<a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/scheduled-events.html">EventBridge の書式</a> を用いてより複雑なスケジュール設定を行うことも可能です。</p> <p>最後に本当に定期実行されていて、関数の実行も正常に行われていそう確認します。<code>Monitoring</code> タブをクリックして、関数の実行状況を確認していきます。</p> <p><img src="https://i.gyazo.com/54672fc613642b52471db2f51d006b37.png" alt="1. <code>Monitoring</code> タブをクリックする" /><br /> <strong>1. <code>Monitoring</code> タブをクリックする</strong></p> <p><img src="https://i.gyazo.com/b76a3b7bc3de06c47756b7eed62559d1.png" alt="2. Lambda 関数が定期実行されていることを確認する" /><br /> <strong>2. Lambda 関数が定期実行されていることを確認する</strong></p> <p><img src="https://i.gyazo.com/836da1ed016618aa791397bcd609e9a3.png" alt="3. Lambda 関数の実行結果が正しいことも確認する" /><br /> <strong>3. Lambda 関数の実行結果が正しいことも確認する</strong></p> <p>これで作業完了です。お疲れさまでした。</p> nikaera tag:crieit.net,2005:PublicArticle/15898 2020-05-14T10:35:03+09:00 2020-05-14T10:35:03+09:00 https://crieit.net/posts/YouDev 個人開発者をもっと知りたい。YouDevをリリースしました。 <p><img width="610" alt="eyecatch.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/275b16c8-3136-198f-fa76-a17a4cbb41e1.png"></p> <p><a target="_blank" rel="nofollow noopener" href="https://youdev.tech">YouDev</a>をリリースしました。 2020.5.01</p> <p>YouDevは個人開発者にスポットを当てて、個人開発者をもっと知ってもらう(僕自身が知りたい。。)サービスです。</p> <p>サービスは認知されていても開発者にはスポットが当たってないなと思い、個人開発者は自分がリリースしたサービスをサービスだけでなく開発者自身をもっとアピールしてもいいんじゃないかな?と思い作成しました。</p> <p>常に個人開発者を募集してるので、<strong>自分は個人開発者だ!</strong>って思う人は登録お願いします!</p> <p>なお、改善点もできる、できないに関わらず受け付けております!</p> <h1 id="僕のスペック"><a href="#%E5%83%95%E3%81%AE%E3%82%B9%E3%83%9A%E3%83%83%E3%82%AF">僕のスペック</a></h1> <p>・<strong>へっぽこエンジニア</strong><br /> ・開発歴:3年<br /> ・個人開発歴:半年(2020.01に<strong>今年は個人開発をやる!</strong>と決めた。)<br /> ・開発言語:python(半年)<br /> ・デザインセンス:<strong>皆無</strong> (フレームワーク使わずにデザインは、まじ無理。 )<br /> ・昨今の新技術のスピードにはついていけてない。<br /> ・pythonって熱いんじゃね!?ってただ思ってる<strong>にわか</strong>。<br /> ・個人開発始めた時にQitaに投稿してこう!と思ったが、なんか雰囲気が違った。(へっぽこエンジニアの出る幕なさそう。。)<br /> ・QitaってMarkDownでしか書けないの!?なにそれ無理!</p> <h1 id="YouDevの目指すもの"><a href="#YouDev%E3%81%AE%E7%9B%AE%E6%8C%87%E3%81%99%E3%82%82%E3%81%AE">YouDevの目指すもの</a></h1> <p>とにかく個人開発者ってのを知りたくなった。</p> <p>個人開発者ってどんな人がいてどんな活動してるんだろう?って思った時に役立つようなサービス</p> <p>個人開発者にとって有益な認知拡大の活動の場を目指す。</p> <h1 id="本番環境"><a href="#%E6%9C%AC%E7%95%AA%E7%92%B0%E5%A2%83">本番環境</a></h1> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/1bcbb6ea-5688-7434-64bf-ff4024c58342.png" alt="youdev構成図.png" /></p> <p>こんな感じ。</p> <p>DBはPostgresSQLをあまり利用したことがなかったのでMySQLを利用。<br /> 本当はAWSのRDSを利用したかったが、収益化してるサービスでも無いのでEC2の中に大人しくインストールしました。<br /> でも、やっぱりRDS使ってみたい。。</p> <p>S3もできれば利用を避けたかったが、ユーザが登録する画像データもあるので、S3を使うことにしました。<br /> そもそもEC2のストレージ容量が8GBしか無いのでここもでも大人しくS3にしました。</p> <p>ただ、サービス内で共通して利用する画像データやCSS,スクリプト系はEC2側にstaticフォルダを新規で作成してそこから配信してます。(最悪、壊れても良いファイルなので。)</p> <p>ソースコードはprivateのgithubで管理してます。</p> <h1 id="DjangoでAmazon S3を使う"><a href="#Django%E3%81%A7Amazon+S3%E3%82%92%E4%BD%BF%E3%81%86">DjangoでAmazon S3を使う</a></h1> <p>僕は本番環境稼働直後はmediaファイルをEC2のstaticと同じディレクトリで運用していたのでまずはそれらをS3へ移行しなくてはならない。。</p> <h2 id="S3バケットの作成"><a href="#S3%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90">S3バケットの作成</a></h2> <p>まじはS3のバケット作成をする。</p> <p>ここは基本的につまずきポイントは無かったです。</p> <p>1.バケットの作成<br /> 2.IAMユーザの追加(ここでアクセスキーはコピーしといたほうがいいかも。)<br /> 3.グループの作成(ポリシーはAmazonS3FullAccessを適用)<br /> 4.2で作成したユーザを3で作成したグループに追加<br /> 5.バケットのユーザに2で作成したユーザを追加</p> <h2 id="S3への移行"><a href="#S3%E3%81%B8%E3%81%AE%E7%A7%BB%E8%A1%8C">S3への移行</a></h2> <p>まずは以下で認証の確認</p> <pre><code>$ aws configure list Name Value Type Location ---- ----- ---- -------- profile <not set> None None access_key <not set> None None secret_key <not set> None None region <not set> None None </code></pre> <p>設定がされてないと、S3に接続できないので、先ほど作成したユーザのアクセスキーとシークレットキーを設定する</p> <pre><code>$ aws configure AWS Access Key ID [None]: <アクセスキー> AWS Secret Access Key [None]:<シークレットキー> Default region name [None]: <なにも入力してない> Default output format [None]:<なにも入力してない> </code></pre> <p>これでS3に接続できるようになるのでいざ、アップロード!</p> <pre><code><br />$ cd /usr/share/nginx/html/media/ #ここは移行したいファイルがあるディレクトリ $ aws s3 cp --recursive uploads s3://<バケット名>/<フォルダ名> </code></pre> <p>これでいけました!</p> <p>ちなみに<br /> ・--recursive をつけるとディレクトリが対象になります。<br /> ・cp ではなくsyncを使用した場合はコピーではなく同期になります。</p> <h2 id="Django側の設定"><a href="#Django%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9A">Django側の設定</a></h2> <p>Django側でmediaファイルのアップロード、参照先をS3に向くように変更して上げなければいけない。</p> <p>DjangoでS3を利用するには以下のモジュールが必要なのでインストール</p> <p>```python:インストール<br /> $ pip install django-storages<br /> $ pip install boto3</p> <pre><code><br />settingに以下を追加する ```python:settings.py INSTALLED_APPS = [ ... 'storages', #追加 ] ... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', 'django.template.context_processors.media', #追加 #これを追加してあげることでtemplateから<span>{</span><span>{</span>MEDIA_URL<span>}</span><span>}</span>でS3を参照できるようになります。 ], }, }, ] ... # MEDIA_URL = '/media/' AWS_ACCESS_KEY_ID = '<アクセスキー>' AWS_SECRET_ACCESS_KEY = '<シークレットキー>' AWS_STORAGE_BUCKET_NAME = '<バケット名>' AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME AWS_LOCATION = 'media' DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION) </code></pre> <p>これだけでS3へのアップロードおよびS3の参照ができるようになります。</p> <h2 id="使用したライブラリ系"><a href="#%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E7%B3%BB">使用したライブラリ系</a></h2> <h3 id="social-auth-app-django"><a href="#social-auth-app-django">social-auth-app-django</a></h3> <p>Djangoで作成したサービスにソーシャル系サービス(twitter,google,facebook等)でログインできるようにするライブラリです。</p> <p>twitterアカウントでの認証設定</p> <p>```python:インストール<br /> $ pip install social-auth-app-django</p> <pre><code><br />```python:settings.py INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'social_django', #ここを追加 ] ... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'social_django.context_processors.backends',#ここを追加 'social_django.context_processors.login_redirect',#ここを追加 ], }, }, ] ... SOCIAL_AUTH_URL_NAMESPACE = 'social' #AUTHENTICATION_BACKENDSを追加 AUTHENTICATION_BACKENDS = [ 'social_core.backends.twitter.TwitterOAuth', 'django.contrib.auth.backends.ModelBackend', ] #以下は本番環境では環境変数に設定すること! SOCIAL_AUTH_TWITTER_KEY = 'xxxxxxxx' # Consumer Key (API Key) SOCIAL_AUTH_TWITTER_SECRET = 'xxxxxxxxx'# Consumer Secret (API Secret) SOCIAL_AUTH_LOGIN_REDIRECT_URL = '' # ログイン時のリダイレクトURL LOGOUT_REDIRECT_URL = '' #ログアウト時のリダイレクトURL </code></pre> <p>```python:project/urls.py<br /> urlpatterns = [<br /> ...<br /> path('', include('social_django.urls')), #追加#<br /> #↑を追加することで アプリ側から以下URLにアクセスすることで各処理が可能になります。<br /> #ログイン:/login/twitter<br /> #ログアウト:/disconnect/twitter/<br /> #complete:/complete/twitter/<br /> ...<br /> ]</p> <pre><code>/complete/twitter/はtwitterAPIのCallbackのURLになります。 例) ![スクリーンショット 2020-05-12 22.04.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634341/9372ed3a-53ce-966f-9242-fad1e712c676.png) あとはmigrateすることで利用ができるようになります ```python:migrate $ python manage.py makemigrations social_django $ python manage.py migrate </code></pre> <h3 id="requests-oauthlib"><a href="#requests-oauthlib">requests-oauthlib</a></h3> <p>これはTwitterAPI利用の際のOAuthを数コードで実装できるようになるライブラリです。</p> <p>```python:インストール<br /> $ pip install requests requests_oauthlib</p> <pre><code><br /><br />```python:settings.py TWITTER_ACCESS_TOKEN = 'XXXXXX' #追加 TWITTER_ACCESS_TOKEN_SECRET='XXXXX' #追加 </code></pre> <p>ここでは例として、ユーザ情報を取得する方法を記載します。</p> <p>上記をsettings.pyの任意の場所に記入します。</p> <p>```python:views.py</p> <p>CK = settings.SOCIAL_AUTH_TWITTER_KEY<br /> CS = settings.SOCIAL_AUTH_TWITTER_SECRET<br /> AT = settings.TWITTER_ACCESS_TOKEN<br /> ATS = settings.TWITTER_ACCESS_TOKEN_SECRET</p> <p>def getAccount(request,screen_name):<br /> url = "https://api.twitter.com/1.1/users/show.json" #エンドポイントは公式のドキュメント参照<br /> params = {<br /> 'screen_name':screen_name<br /> }<br /> req = twitter.get(url, params = params)</p> <pre><code>if req.status_code == 200: res = json.loads(req.text) else: res="" print("Failed: %d" % req.status_code) return res </code></pre> <pre><code><br />###MaterializeCSS(デザイン) [公式ドキュメント](https://materializecss.com/) 冒頭で述べたとおり、ライブラリ使わなきゃCSSなんてやってられないです。。。 scssもインストールできるので気に入らなければメインカラー等は一括で変更できます。 **※注意:セレクトボックスは不具合があり、セレクトの選択欄が下のセレクトボックスに重なる場合 正しく選択できない不具合がありました。** 絶望していましたが、以下のgithubに改修コードが上がっていたのでこれを利用すれば解消します! [改修版git hub](https://github.com/Dogfalo/materialize/issues/6444) ###cropper.js 画像をトリミングしてくれるライブラリです。 もちろん、僕が自前でトリミングの処理を実装できるわけもないので利用しました。 何回、ドキュメント読んでも実装方法が謎でしたが、数十回目を通してようやく理解できたのが以下のサイトです。 [へっぽこでも数十回目を通せばわかるはず!](https://simpleisbetterthancomplex.com/tutorial/2017/03/02/how-to-crop-images-in-a-django-application.html) だいたい以上が使用したライブラリです。 #つまずいた ###mysqlのバージョン Django3.0からはmysqlへ接続するドライバはmysqlclient推奨と公式ドキュメントに書いてあるもんだから、大人しくインストールしてみたがエラー。。 ```python: $ pip install mysqlclient > ~ check the manual that corresponds to your MySQL server version for the right syntax to use near 'rsion' at line 1 ~ 謎のエラー 原因:mysqlのバージョンが古いことに起因してるみたい </code></pre> <p>開発環境はAWSのCloud9を利用しているんですが、cloud9だと初期状態でmysqlがインストールされている。</p> <p>が、バージョンが5.5でmysqlclientは5.7からしか使えないみたいだったので、5.5を削除して5.7をインストールしました。</p> <pre><code class="python:"> sudo yum remove mysql* #mysqlの削除 #(慎重な方はsudo yum -y remove mysql-config mysql55-server mysql55-libs mysql55) sudo yum -y install mysql57-server mysql57 sudo yum -y install mysql-devel </code></pre> <h3 id="絵文字をmysqlへ登録"><a href="#%E7%B5%B5%E6%96%87%E5%AD%97%E3%82%92mysql%E3%81%B8%E7%99%BB%E9%8C%B2">絵文字をmysqlへ登録</a></h3> <p>もともとcharsetをutf-8にしていたので絵文字が登録できなかった。</p> <p>設定ファイルの変更</p> <p>```/etc/my.cnf<br /> [mysqld]<br /> character-set-server=utf8mb4</p> <p>[client]<br /> default-character-set=utf8mb4</p> <pre><code><br />mysqlの再起動 ```/etc/my.cnf sudo service mysqld restart </code></pre> <p>すでにテーブルがutf-8で作成済みだったので変更</p> <pre><code class="python:mysql">ALTER TABLE targettable CONVERT TO CHARACTER SET utf8mb4; </code></pre> <p>Djangoの場合、Django側にも設定が必要</p> <p><code>python:settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'xxx', 'USER': 'xxx', 'PASSWORD': 'xxx', 'HOST': '', 'PORT': '', #OPTIONSを追加 'OPTIONS': { 'charset': 'utf8mb4', }, } }</code></p> <p>これで絵文字の出力、登録ができるようになりました!</p> <h1 id="さいごに !"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB%E3%80%80%EF%BC%81">さいごに !</a></h1> <p>今後も色々、頑張って機能追加して行きますので是非とも登録お願いします!<a target="_blank" rel="nofollow noopener" href="https://youdev.tech">Youdev</a></p> ふーど。@個人開発 tag:crieit.net,2005:PublicArticle/15858 2020-04-21T13:51:26+09:00 2020-04-21T14:04:20+09:00 https://crieit.net/posts/AWS-Lambda-cx-Oracle-OracleDB AWS Lambdaでcx_Oracleを使ってOracleDBへ接続する <h1 id="前書き"><a href="#%E5%89%8D%E6%9B%B8%E3%81%8D">前書き</a></h1> <p>Pythonで書かれた Lambdaファンクションから、OracleDBへ接続する方法を記載します。</p> <p>LambdaファンクションからMySQLへの接続はたくさん記事があるんだけど、OracleDBへの接続はあまり記事がなく、実現まで結構時間がかかったので、自分用の備忘録に。</p> <h1 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h1> <ul> <li>LambdaファンクションからOracleDBに接続する時には、cx_Oracleのモジュールだけではなく、Oracle Instant Clientのモジュールも、デプロイパッケージに入れてあげる必要がある。</li> <li>すべてのモジュールを一つのデプロイパッケージに入れると、大きすぎてAWS Lambdaのインラインエディタで編集できなくなる。なので、cx_OracleとOracle Instant Clientのモジュールはレイヤに分けると良い。</li> </ul> <h1 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h1> <h2 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h2> <p>2020年4月時点で、使用した環境は下記の通りです。<br /> python3のインストールまで完了している状態で、後続の作業を開始します。</p> <ul> <li>Amazon Linux 2(AWS EC2上で作業)</li> <li>python3.7</li> <li>cx_Oracle7.3</li> <li>Oracle Instant Client 19.6</li> </ul> <h2 id="手順の流れ"><a href="#%E6%89%8B%E9%A0%86%E3%81%AE%E6%B5%81%E3%82%8C">手順の流れ</a></h2> <ol> <li>必要なモジュールをインストールする</li> <li>デプロイパッケージを作る</li> <li>Lambdaファンクションを作成する</li> <li>レイヤ用のデプロイパッケージを作成する</li> <li>Lambdaファンクションにレイヤを適用する</li> </ol> <h2 id="1. 必要なモジュールをインストールする"><a href="#1.+%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B">1. 必要なモジュールをインストールする</a></h2> <h3 id="Oracle Instant Client"><a href="#Oracle+Instant+Client">Oracle Instant Client</a></h3> <p>まずは、Oracle Instant Client から。<br /> 最新版のOracle Instant Clientを公式サイトからダウンロードします。作業環境が Amazon Linux 2 なので、Linux用の64ビット版を使います。<br /> * <a target="_blank" rel="nofollow noopener" href="https://www.oracle.com/technetwork/jp/database/database-technologies/instant-client/overview/index.html">Oracle Instant Client</a><br /> * <a target="_blank" rel="nofollow noopener" href="https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html">Instant Client Downloads for Linux x86-64 (64-bit)</a></p> <p>インストールしたのは、とりあえずbasicとsqlplusのふたつだけ。LambdaファンクションからのDB接続だけを考えるなら、sqlplusは不要かも。</p> <pre><code>$ sudo yum -y localinstall ./oracle-instantclient19.6-basic-19.6.0.0.0-1.x86_64.rpm $ sudo yum -y localinstall ./oracle-instantclient19.6-sqlplus-19.6.0.0.0-1.x86_64.rpm </code></pre> <p>インストール完了後、SQL*PLUSでDBに接続できることを確認しておきます。<br /> なお、Oracle Instant Clientを使用する分には、ORACLE_HOMEなどの設定は不要らしいです。</p> <pre><code>$ sqlplus <user>/<pass>@<hostname>:<port>/<sid> SQL*Plus: Release 19.0.0.0.0 - Production on xxx xxx xx xx:xx:xx xxxx Version 19.6.0.0.0 Copyright (c) 1982, 2019, Oracle. All rights reserved. Last Successful login time: xxx xxx xx xxxx xx:xx:xx +09:00 Connected to: Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production SQL> select TNAME from tab; TNAME -------------------------------------------------------------------------------- SAMPLE_EMP SQL> select * from SAMPLE_EMP; EMPNO EMPNAME GENDER_F ---------- -------------------------------------------------- ---------- 0000000001 EMP_A 1 0000000002 EMP_B 2 0000000003 EMP_C 1 SQL> quit Disconnected from Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production </code></pre> <p>サンプルで作っておいたテーブルの中身が見えており、Oracle Instant Clientのインストールは完了です。</p> <h3 id="cx_Oracle"><a href="#cx_Oracle">cx_Oracle</a></h3> <p>PythonでOracleDBに接続するために、cx_Oracleもインストールしておきます。</p> <pre><code>$ sudo pip3 install cx_oracle </code></pre> <h3 id="EC2からcx_Oracleを使ってOracleDBへ接続する"><a href="#EC2%E3%81%8B%E3%82%89cx_Oracle%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6OracleDB%E3%81%B8%E6%8E%A5%E7%B6%9A%E3%81%99%E3%82%8B">EC2からcx_Oracleを使ってOracleDBへ接続する</a></h3> <p>ここまで完了したら、pythonでOracleDBへ接続できるようになっているはずなので、簡単なサンプルでOracleDBに接続できるか確認します。例えば下記のような感じ。ファイル名は<code>dbconnect_oracle.py</code>とします。</p> <pre><code>import json import cx_Oracle def lambda_handler(event, context): # TODO implement # cx_oracleバージョン確認 verOraCli = cx_Oracle.clientversion() print( verOraCli ) # oracle接続 print( "DB connect" ) connection = cx_Oracle.connect("<user>", "<pass>", "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=<hostname>) (PORT=<port>))(CONNECT_DATA=(SID=<sid>)))") # select実行 print( "select execute" ) try: cursor = connection.cursor() sql = "select * from sample_emp" cursor.execute( sql ) result = cursor.fetchall() print( result ) finally: cursor.close() connection.close() return { 'statusCode': 200, 'body': json.dumps( 'cx_oracle clientversion = ' + str(verOraCli) ) } </code></pre> <p>本来はハンドラの中でDB接続処理を行わない方が良いけど、まあとりあえずということでひとつ。早速実行してみます。</p> <pre><code>$ python3 -c "import dbconnect_oracle; dbconnect_oracle.lambda_handler(0,0)" (19, 6, 0, 0, 0) DB connect select execute [('0000000001', 'EMP_A', 1), ('0000000002', 'EMP_B', 2), ('0000000003', 'EMP_C', 1)] </code></pre> <p>Oracle Instant Client 19.6 を使用してOracleDBでselectを実行できていることが確認できました。</p> <h2 id="2. デプロイパッケージを作る"><a href="#2.+%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E3%82%8B">2. デプロイパッケージを作る</a></h2> <p>EC2上での動作は確認できたので、AWS Lambdaの関数として定義するためにデプロイパッケージを作成します。</p> <h3 id="デプロイパッケージ構成"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%A7%8B%E6%88%90">デプロイパッケージ構成</a></h3> <p>Lambdaファンクションで外部ライブラリのimportを行う場合、外部ライブラリを含んだデプロイパッケージを作る必要があります。今回はさらに、cx_OracleがOracle Instant Clientを利用するので、Oracle Instant Clinentもデプロイパッケージに含める必要があります。</p> <p>というわけで、目指すべき構成は下記の形。作業ディレクトリは<code>/tmp/dbconnect_oracle_pack</code>とします。</p> <pre><code>/tmp/dbconnect_oracle_pack ├ dbconnect_oracle.py ├ cx_Oracle.cpython-37m-x86_64-linux-gnu.so ├ cx_Oracle-7.3.0.dist-info │ ├ INSTALLER │ ├ METADATA │ ├ RECORD │ ├ WHEEL │ └ top_level.txt └ lib ├ libaio.so -> ./libaio.so.1 ├ libaio.so.1 ├ libclntsh.so -> ./libclntsh.so.19.1 ├ libclntsh.so.19.1 ├ libclntshcore.so -> ./libclntshcore.so.19.1 ├ libclntshcore.so.19.1 ├ libipc1.so ├ libmql1.so ├ libnnz19.so ├ libocci.so -> ./libocci.so.19.1 ├ libocci.so.19.1 └ libociei.so </code></pre> <h3 id="デプロイパッケージ作成"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">デプロイパッケージ作成</a></h3> <p>デプロイパッケージにcx_Oracleを含めるため、作業ディレクトリへcx_Oracleを持ってきます。作業ディレクトリへのインストールなので、今回は<code>sudo</code>は不要です。</p> <pre><code>$ pip3 install cx_Oracle -t /tmp/dbconnect_oracle_pack </code></pre> <p>Oracle Instant Clientのモジュールは、インストール先からコピーします。<code>libaio.so.1</code>だけは、<code>/usr/lib64/</code>から持ってきます。</p> <p>なお、記事を書く際に再度確認したら、<code>libaio.so.1</code>はシンボリックリンクで、実体は<code>libaio.so.1.0.1</code>でした。<br /> 下記の通りでも動作はしますが、気になる方は、<code>libaio.so.1.0.1</code>をコピーしてそこからシンボリックリンクを作成する方が良いかもしれません。</p> <pre><code>$ mkdir /tmp/dbconnect_oracle_pack $ cp /usr/lib/oracle/19.6/client64/lib/libclntsh.so.19.1 /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib/oracle/19.6/client64/lib/libclntshcore.so.19.1 /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib/oracle/19.6/client64/lib/libipc1.so /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib/oracle/19.6/client64/lib/libmql1.so /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib/oracle/19.6/client64/lib/libnnz19.so /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib/oracle/19.6/client64/lib/libocci.so.19.1 /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib/oracle/19.6/client64/lib/libociei.so /tmp/dbconnect_oracle_pack/lib $ cp /usr/lib64/libaio.so.1 /tmp/dbconnect_oracle_pack/lib </code></pre> <p>最初に挙げたデプロイパッケージ構成に不足している部分を、シンボリックリンクで作成します。<br /> デプロイパッケージのサイズ上限は250MBのため、絞れるところは絞りましょう。</p> <pre><code>$ cd /tmp/dbconnect_oracle_pack $ ln -s ./libaio.so.1 libaio.so $ ln -s ./libclntsh.so.19.1 libclntsh.so $ ln -s ./libclntshcore.so.19.1 libclntshcore.so $ ln -s ./libocci.so.19.1 libocci.so </code></pre> <p>ここまで完了すると、デプロイパッケージ構成通りの状態となっているはずです。zip圧縮して、デプロイパッケージを作成します。圧縮後のファイルは70MB弱くらいになると思います。<br /> シンボリックリンクを含んでいるので、<code>--symlinks</code>オプションを忘れないように。また、圧縮対象は<code>/tmp/dbconnect_oracle_pack</code>ディレクトリではなく、その配下であることに注意してください。</p> <pre><code>$ cd /tmp/dbconnect_oracle_pack $ zip -r --symlinks dbconnect_oracle_sample.zip ./dbconnect_oracle.py ./cx_Oracle.cpython-37m-x86_64-linux-gnu.so ./cx_Oracle-7.3.0.dist-info ./lib </code></pre> <h2 id="3. Lambdaファンクションを作成する"><a href="#3.+Lambda%E3%83%95%E3%82%A1%E3%83%B3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">3. Lambdaファンクションを作成する</a></h2> <p>完成したデプロイパッケージ元にLambdaファンクションを作成します。</p> <p>ですが、作成したデプロイパッケージが50MBを超えているため、Lambdaファンクションとして直接アップロードを行う事ができません。一旦S3上にアップロードした後、LambdaファンクションにS3からアップロードする形になります。なお、Lambdaファンクションのサイズ上限は諸々含めて250MB。cx_OracleとOracle Instant Client だけで200MBを超えていて、結構カツカツです。</p> <p>Lambdaファンクションとしてのアップロードが完了すると、インラインエディタで確認ができる…のが通常なのですが、サイズが大きすぎてインラインエディタでは編集できない旨の警告が表示されます。実行はできるようなので、良しとしておきます。</p> <p><a href="https://crieit.now.sh/upload_images/97382e5555931319dd5fd600612effab5e9e5b7d04327.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/97382e5555931319dd5fd600612effab5e9e5b7d04327.png?mw=700" alt="デプロイパッケージが大きすぎてインラインコード編集を有効にできません" /></a></p> <p>OracleDBに限らず、DBに接続するLambdaファンクションにはIPアドレスの付与が必要です。VPC設定を行い、VPC設定、サブネット、セキュリティグループを指定します。</p> <p>また、初回実行に限り、そこそこの時間がかかります。デフォルトのタイムアウト値は3秒となっていますが、10秒程度に増やしておいたほうがよいでしょう。</p> <p>上記の設定を完了したら、テスト実行を行います。サンプルは引数不要のイベントハンドラなので、空のテストイベントを作成してテストを行いましょう。</p> <p><a href="https://crieit.now.sh/upload_images/1874ff95cb0fe728e14c95e0dc913dae5e9e5d40e3989.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1874ff95cb0fe728e14c95e0dc913dae5e9e5d40e3989.png?mw=700" alt="実行結果:成功" /></a></p> <p>ヨシ! ちゃんと、Oracle Instant Client 19.6が使われているようです。<br /> 初回は時間がかかりましたが、2回目からは1秒かからずに実行されます。時間がかかるのは、IP付与によるものかしら。</p> <h2 id="4. レイヤ用のデプロイパッケージを作成する"><a href="#4.+%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">4. レイヤ用のデプロイパッケージを作成する</a></h2> <p>当初の目的である、cx_Oracleを使ってOracleDBへ接続するLambdaファンクションの作成は、ここまでで実現できました。</p> <p>ただ、インラインエディタでの編集ができないのはちょっといただけません。サイズが大きい原因はcx_OracleとOracle Instant Clientなので、この部分をcx_Oracleレイヤとして切り出せば良いのではないか?</p> <h3 id="cx_Oracleレイヤ用のデプロイパッケージ構成"><a href="#cx_Oracle%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%A7%8B%E6%88%90">cx_Oracleレイヤ用のデプロイパッケージ構成</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html">AWS Lambda レイヤー</a></p> <blockquote> <p>関数コードからランタイム依存関係を移動するには、それらをレイヤーに配置します。Lambda ランタイムには、関数コードを使用して、レイヤーに含まれるライブラリにアクセスできることを確認する、<code>/opt</code>ディレクトリのパスが含まれます。</p> <p>ライブラリをレイヤーに含めるには、ランタイムでサポートされているいずれかのフォルダにそれらを配置します。<br /> * <strong>Python</strong> – <code>python</code>、<code>python/lib/python3.8/site-packages</code>(サイトディレクトリ)</p> </blockquote> <p>レイヤ用のデプロイパッケージを作る場合、pyhon関連のライブラリは<code>python</code>配下に、配置します。なので、cx_Oracle関連のモジュールはこちらに置きます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-envvars.html">AWS Lambda 環境変数の使用</a></p> <blockquote> <p>予約されていない環境変数<br /> (略)<br /> <code>LD_LIBRARY_PATH</code> – システムライブラリのパス<br /> (<code>/lib64:/usr/lib64:$LAMBDA_RUNTIME_DIR:$LAMBDA_RUNTIME_DIR/lib:$LAMBDA_TASK_ROOT:$LAMBDA_TASK_ROOT/lib:/opt/lib</code>)。</p> </blockquote> <p>Oracle Instant Clientのモジュールについては、<code>LD_LIBRARY_PATH</code>で指定されたパスに配置する必要があります。Lambdaファンクションの設定で編集しても良いのですが、デフォルトで指定されたパス<code>/opt/lib</code>があるので、そちらを使いましょう。レイヤに含まれるライブラリは<code>/opt</code>配下に配置されるとのことなので、<code>lib</code>配下です。</p> <p>というわけで、レイヤ用デプロイパッケージの構成は下記の通りになります。作業ディレクトリは<code>/tmp/dbconnect_oracle_pack/layer</code>とします。</p> <pre><code>/tmp/dbconnect_oracle_pack/layer ├ python │ ├ cx_Oracle.cpython-37m-x86_64-linux-gnu.so │ └ cx_Oracle-7.3.0.dist-info │ ├ INSTALLER │ ├ METADATA │ ├ RECORD │ ├ WHEEL │ └ top_level.txt └ lib ├ libaio.so -> ./libaio.so.1 ├ libaio.so.1 ├ libclntsh.so -> ./libclntsh.so.19.1 ├ libclntsh.so.19.1 ├ libclntshcore.so -> ./libclntshcore.so.19.1 ├ libclntshcore.so.19.1 ├ libipc1.so ├ libmql1.so ├ libnnz19.so ├ libocci.so -> ./libocci.so.19.1 ├ libocci.so.19.1 └ libociei.so </code></pre> <h3 id="レイヤ用デプロイパッケージ作成"><a href="#%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">レイヤ用デプロイパッケージ作成</a></h3> <p>配置ができたらzip圧縮し、レイヤ用デプロイパッケージを作成します。ファイル名は何でも良いですが、<code>oraclelib.zip</code>としてみました。</p> <pre><code>$ cd /tmp/dbconnect_oracle_pack/layer $ zip -r --symlinks oraclelib.zip ./python ./lib </code></pre> <h2 id="5. Lambdaファンクションにレイヤを適用する"><a href="#5.+Lambda%E3%83%95%E3%82%A1%E3%83%B3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E3%83%AC%E3%82%A4%E3%83%A4%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B">5. Lambdaファンクションにレイヤを適用する</a></h2> <p>レイヤを作るため、AWS Lambdaの左側メニューから「レイヤー」を選択し、レイヤの作成を行います。</p> <p>先程と同様に、S3経由でアップロードする形になります。また、「互換性のあるランタイム」には、作業環境であるAmazon Linux 2にインストールしたのと同じ Python3.7 を指定します。</p> <p><a href="https://crieit.now.sh/upload_images/731c2c355d8a949923189d7ba3ba8e7b5e9e7566a330b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/731c2c355d8a949923189d7ba3ba8e7b5e9e7566a330b.png?mw=700" alt="レイヤー作成設定" /></a></p> <p>レイヤの作成が完了したら、Lambdaファンクションにレイヤを適用します。</p> <p>作成したLambdaファンクションの画面から「レイヤーの追加」を行う事ができます。<br /> Lambdaファンクションのランタイムを「Python3.7」にした上で、「レイヤーの追加」を行ってください。</p> <p>選択できるレイヤーは、Lambdaファンクションで設定しているランタイムを「互換性のあるランタイム」で指定したレイヤーだけです。<br /> <a href="https://crieit.now.sh/upload_images/505ac0668a3516271d2a73ce9c2b569a5e9e765c9c085.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/505ac0668a3516271d2a73ce9c2b569a5e9e765c9c085.png?mw=700" alt="関数にレイヤーを追加" /></a></p> <p>こうしておくと、Lambdaファンクションそのものは、今回のサンプルの<code>dbconnect_oracle.py</code>だけにしても大丈夫です。インラインコード編集も可能となります。</p> <p><a href="https://crieit.now.sh/upload_images/7d20015f8e9283b81e552707518b66645e9e77bc2ef5e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7d20015f8e9283b81e552707518b66645e9e77bc2ef5e.png?mw=700" alt="OK" /></a></p> <p>動作も問題ありません。</p> <h1 id="後書き"><a href="#%E5%BE%8C%E6%9B%B8%E3%81%8D">後書き</a></h1> <p>一番わからなかったのは、Oracle Instant Client 19 だとどのlibをデプロイパッケージに含めればよいのか、ということです。今回記事に残そうとしたのも、それが要因です。</p> <p>それにしても、Oracle Instant Client だけで200MBオーバーなんだけど、そのうちOracle Instant ClientだけでLambdaのサイズ制限を超えてしまうのではないだろうか。</p> <h1 id="参考サイト"><a href="#%E5%8F%82%E8%80%83%E3%82%B5%E3%82%A4%E3%83%88">参考サイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/aidy91614/items/92987d547c318e0483f5">Lambda から RDS にアクセスする方法 (python)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ikeisuke/items/45ba4d88b887d793c444">AWS Lambda (Node.js-v4.3.2)からOracleに接続する(ORA-21561への対応)</a></li> <li><p><a target="_blank" rel="nofollow noopener" href="http://wiki.nikhil.io/Lambda,_Python,_and_Oracle/raw">Lambda, Python, and Oracle</a></p> <p>その他、検索に引っかかったフォーラムの記事とか色々参考にしました。</p></li> </ul> tag:crieit.net,2005:PublicArticle/15073 2019-06-07T01:07:02+09:00 2019-06-07T01:08:27+09:00 https://crieit.net/posts/AWS-ELB-ALB-Socket-IO-websocket-Stickiness-Sticky-session AWSのELB(ALB)におけるSocket.IOのwebsocket接続と負荷分散 - Stickiness(Sticky session)の活用 <p>AWSではELB(Elastic Load Balancing)を活用し、アクセスの振り分けや負荷分散を行なうのがメジャーです。<br /> ロードバランサーを使った構成は、通常のWebシステムではごくごく一般的です。</p> <p>ただしSocket.IOを使ったウェブソケット(websocket)接続では、問題となる場合があります。<br /> これには理由がありまして、私も調査に苦労しました。</p> <p>まずは前提となる知識を共有しつつ、備忘録を兼ねて解説します。</p> <h2 id="Socket.ioの仕組みをざっくりと理解する"><a href="#Socket.io%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%82%92%E3%81%96%E3%81%A3%E3%81%8F%E3%82%8A%E3%81%A8%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B">Socket.ioの仕組みをざっくりと理解する</a></h2> <p>websocketのライブラリでSocket.ioが多用されているのは、いくつか理由があります。</p> <p>その大きな理由のひとつに<strong>「HTTPの仕組みを使っているため、企業のファイアウォールやセキュリティーソフトのブロックに対して耐性がある」</strong>という点が挙げられます。</p> <p>[https://socket.io/:embed:cite]</p> <h3 id="ロングポーリング(polling)"><a href="#%E3%83%AD%E3%83%B3%E3%82%B0%E3%83%9D%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%28polling%29">ロングポーリング(polling)</a></h3> <p>Socket.ioでは、まずロングポーリングを使ってサーバーと接続します。<br /> ロングポーリングは、初期のLINEアプリでも採用されていた実績のある方式です。</p> <p>ひとことで言ってしまえば、<strong>「通常のHTTPリクエストに対するレスポンスを引き伸ばすことで、長時間の接続を維持する」</strong>のがロングポーリングです。<br /> サーバーはHTTPリクエストのレスポンスをすぐに終わらせず留保するため、クライアントとのコネクションを維持します。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/masarufuruya/items/2bd5dfe03096057af63f" target="_blank" rel="noopener">Socket.ioとは何か?リアルタイムWebアプリケーションを実現する技術をまとめてみた #JavaScript - Qiita</a></p> <p>ロングポーリングは、一般的なHTTPリクエストの延長線上にある技術です。<br /> そのためセキュリティー制約の強い企業のファイアウォールや、セキュリティーソフトのブロックに対して強みがあります。</p> <h3 id="ロングポーリングからウェブソケット(websocket)へ"><a href="#%E3%83%AD%E3%83%B3%E3%82%B0%E3%83%9D%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%8B%E3%82%89%E3%82%A6%E3%82%A7%E3%83%96%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%28websocket%29%E3%81%B8">ロングポーリングからウェブソケット(websocket)へ</a></h3> <p>次にSocket.ioが実施するのは、プロトコルの昇格です。</p> <p>ロングポーリングは素晴らしい仕組みですが、基本的にHTTPの接続を維持できる時間には限界があります。<br /> もちろん接続が切れてしまったら再接続すれば良いのですが、これでは一定間隔で切れ目が生じてしまいます。</p> <p>そこで登場するのがwebsocketです。<br /> Socket.ioは<strong>「ロングポーリングからwebsocketへプロトコルを昇格(Upgrade)」</strong>しようと試みます。</p> <blockquote> <p>Once all the buffers of the existing transport (XHR polling) are flushed, an upgrade gets tested on the side by sending a probe.</p> <p><em>引用:<a target="_blank" rel="nofollow noopener" href="https://socket.io/docs/internals/#Upgrade">https://socket.io/docs/internals/#Upgrade</a></em></p> </blockquote> <h3 id="Connection: Upgrade"><a href="#Connection%3A+Upgrade">Connection: Upgrade</a></h3> <p>HTTP/1.1にはConnectionというヘッダーがあり、<strong>コネクションのプロトコルをアップグレード</strong>することができます。</p> <blockquote> <p>Upgrade: websocket<br /> Connection: Upgrade<br /> <em>引用:<a target="_blank" rel="nofollow noopener" href="http://jxck.hatenablog.com/entry/20120725/1343174392">http://jxck.hatenablog.com/entry/20120725/1343174392</a></em></p> </blockquote> <p>もしコネクションのアップグレードに成功したならば、Socket.ioはウェブソケットでの通信を開始します。<br /> 一方でwebsocket接続に失敗したとしても、ロングポーリングでの接続を継続することができます。</p> <p>このプロトコル選択の柔軟性が、Socket.ioのすごいところです。<br /> websocketが使える環境下ではwebsocketを利用し、そうでない環境下でもロングポーリングで接続を維持できます。</p> <h2 id="ロードバランスされるとセッション(sid)が引き継げない問題"><a href="#%E3%83%AD%E3%83%BC%E3%83%89%E3%83%90%E3%83%A9%E3%83%B3%E3%82%B9%E3%81%95%E3%82%8C%E3%82%8B%E3%81%A8%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%28sid%29%E3%81%8C%E5%BC%95%E3%81%8D%E7%B6%99%E3%81%92%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C">ロードバランスされるとセッション(sid)が引き継げない問題</a></h2> <p>socket.ioがウェブソケット接続を開始する場合、以下のようなリクエストを送信します。<br /> ここで注意してほしいのが「sid」という名のパラメーターです。</p> <pre><code class="txt">GET wss://myhost.com/socket.io/?EIO=3&transport=websocket&sid=36Yib8-rSutGQYLfAAAD with: "EIO=3" # again, the current version of the Engine.IO protocol "transport=websocket" # the new transport being probed "sid=36Yib8-rSutGQYLfAAAD" # the unique session id </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://socket.io/docs/internals/#Upgrade">https://socket.io/docs/internals/#Upgrade</a></p> <p>「sid」とはセッションIDのことですが、ここに大きな問題があります。<br /> <strong>最初にアクセスしたサーバーと違うサーバーに振り分けられてしまった場合、発行されたセッションが見つからなくなってしまう</strong>のです。</p> <p>その際は「Session ID unknown」のようなエラーが発生します。</p> <p>つまり最初にリクエストを送信してからウェブソケットの接続を確立するまで、常に同じサーバーにロードバランスされないと困ってしまうのです。</p> <h3 id="Redisによるセッションの共有"><a href="#Redis%E3%81%AB%E3%82%88%E3%82%8B%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%85%B1%E6%9C%89">Redisによるセッションの共有</a></h3> <p>Socket.ioのセッション情報を引き継げない問題でよく選択されてきた対応策は、インメモリデータベースのRedisによるセッション情報の共有です。<br /> AWSで言うならば、Redis用のElastiCacheを使ってセッション情報を保存します。</p> <blockquote> <p>複数立っているsocket.ioサーバのセッション情報をRedisを介して共有することができるようなので、そうすれば複数のリクエストが異なるサーバに繋がっても問題なくなります。<br /> <em>引用:<a target="_blank" rel="nofollow noopener" href="https://qiita.com/shunjikonishi/items/526e6181efecf83a5f5b">https://qiita.com/shunjikonishi/items/526e6181efecf83a5f5b</a></em></p> </blockquote> <p>「socket.io-redis」というRedis向けのライブラリがあり、これを使います。<br /> 2年ほど前の話ではありますが、実際に試してみたところ確かに問題ありませんでした。</p> <ul> <li>socketio/socket.io-redis</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/socketio/socket.io-redis">https://github.com/socketio/socket.io-redis</a></li> </ul> <p>ElastiCacheでなく自前でRedisを用意しても構わないですが、兎にも角にもRedisが必要です。</p> <h2 id="Application Load Balancer"><a href="#Application+Load+Balancer">Application Load Balancer</a></h2> <p>さてAWSには、Application Load BalancerというL7のロードバランサーがあります。<br /> 単なるELBとの違いがややこしいですが、OSI参照モデルで言えば<strong>最上位の第7層(アプリケーション層)でリクエストを振り分ける</strong>ことが可能です。</p> <p><a href="https://crieit.now.sh/upload_images/88927757e552ab546697edb0b21408565cf93a76822df.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/88927757e552ab546697edb0b21408565cf93a76822df.png?mw=700" alt="20190606030658.png" /></a></p> <h3 id="Stickiness(Sticky session)"><a href="#Stickiness%28Sticky+session%29">Stickiness(Sticky session)</a></h3> <p>ここで登場するのが、Stickiness(Sticky session)というALBの仕組みです。<br /> スティッキーセッションを使うと、ロードバランサーのアクセスの振り分けを固定することができます。</p> <p>具体的な手順として、<strong>ELB(ALB)経由でサーバーにアクセスすると、HTTPレスポンスに「AWSELB」という名前のCookieが追加</strong>されます。<br /> このクッキーはALBが自動に付与する設定もできますし、手動でサーバーから追加する選択肢も可能です。</p> <blockquote> <p>ELBのスティッキーセッションはELBがサーバにリクエスト振り分ける際、特定のCookieを確認することで、特定のクライアントからのリクエストを特定のサーバに紐付けることが出来る機能です。<br /> ELBのスティッキーセッションの設定は以下3つのパターンが選択出来ますので、それぞれどのような動作となるか確認してみます。</p> <ul> <li><p>維持無し</p></li> <li><p>ELBによって生成されたCookieの維持</p></li> <li><p>アプリケーションによって生成されたCookieの維持</p></li> </ul> <p><em>引用: <a target="_blank" rel="nofollow noopener" href="http://blog.serverworks.co.jp/tech/2017/01/05/elb-sticky/">http://blog.serverworks.co.jp/tech/2017/01/05/elb-sticky/</a></em></p> </blockquote> <p>HTTPレベルの仕組みであるCookieを読み取ってアクセスを振り分けるため、高機能なアプリケーション層レベルのL7ロードバランサーであると言えます。</p> <p>なお私は、実際のスティッキーセッションを使ったsocket.ioのアクセスの振り分けは、uorat様のブログ記事を参考に行ないました。<br /> (注釈:ものすごく参考になりました。ありがとうございます)</p> <p><a target="_blank" rel="nofollow noopener" href="http://uorat.hatenablog.com/entry/2016/09/26/070000" target="_blank" rel="noopener">WebSocket対応した噂のALB (Application Load Balancer) を試してみた - tail my trail</a></p> <h3 id="ELB(ALB)のwebsocket対応"><a href="#ELB%28ALB%29%E3%81%AEwebsocket%E5%AF%BE%E5%BF%9C">ELB(ALB)のwebsocket対応</a></h3> <p>従来のELBには、websocket接続できない欠点がありました。<br /> そのためsocket.ioでwebsocket接続してるのかと思いきや、実際はロングポーリングでしたという事態が発生します。</p> <p>これを解消したのが、2016年8月の新機能です。なんと<strong>websocket対応が追加</strong>されました。<br /> これによりELBの負荷分散の恩恵を受けつつ、よりリアルタイム性の高い接続を維持することができるようになりました。</p> <blockquote> <p>WebSocketサポート:WebSocketのリクエストを受けられるようになりました。<br /> <em>引用: <a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/cloud/aws/alb-application-load-balancer/" target="_blank" rel="noopener">【新機能】新しいロードバランサー Application Load Balancer(ALB)が発表されました | DevelopersIO</a></em></p> </blockquote> <h2 id="さいごに"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">さいごに</a></h2> <p><strong>Stickiness(Sticky session)の活用とELB(ALB)のwebsocket対応</strong>により、Socket.ioでも通常のWebシステムと同じようにロードバランスすることができます。</p> <p>Cookieを使ってクライアント毎にロードバランサーの振り分け先を固定すれば良いなんて、当初は思いもしませんでした。<br /> とても便利な機能なので、今後も活用していきたいです!</p> このすみ tag:crieit.net,2005:PublicArticle/14863 2019-03-10T12:46:46+09:00 2019-03-10T12:46:46+09:00 https://crieit.net/posts/Docker3-AWS 湊川あいさんのマンガでわかるDocker3 〜AWS編〜を読みました & 寄稿しました <p>湊川あいさんの「マンガでわかるDocker3 〜AWS編〜」を読みましたので、簡単な概要と感想を書くことにしました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://llminatoll.booth.pm/items/1242542" target="_blank" rel="noopener">#マンガでわかるDocker ③ 〜AWS編〜 【ダウンロード版】#jawsdays #技術書典 - 湊川あいの、わかば家。 #技術書典 #わかばちゃんと学ぶ シリーズ - BOOTH</a></p> <p>執筆が忙しいのでさらっとしか読めてないのですが、マンガやイメージ図で概要を理解した後に、実際に構築をチャレンジしてみる本格派です。</p> <p>余談ですが、この本はRe:VIEWで書かれていると一瞬で判別できるくらいには、技術同人誌に習熟した気がします。<br /> 「マンガでわかるDocker3」は、おそらく「TechBoosterのReVIEW-Template」で書かれている!</p> <h2 id="本の概要"><a href="#%E6%9C%AC%E3%81%AE%E6%A6%82%E8%A6%81">本の概要</a></h2> <p>次に書いてあるような内容を、ひととおり学べます。<br /> どんどん「わかばちゃん」が賢くなってきている!</p> <h3 id="AWS CLIのインストール"><a href="#AWS+CLI%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">AWS CLIのインストール</a></h3> <p>まず初めに行なうのは、AWS CLIのインストールです。画面からだけではなく、ターミナルからも操作できるとやっぱり便利です。</p> <h3 id="請求アラートの設定"><a href="#%E8%AB%8B%E6%B1%82%E3%82%A2%E3%83%A9%E3%83%BC%E3%83%88%E3%81%AE%E8%A8%AD%E5%AE%9A">請求アラートの設定</a></h3> <p>AWSは従量課金制なので、ガラケー時代で言うパケ死のような状況が起こり得ます。<br /> なので、真っ先に請求アラートを設定しましょう。本書でもそうしてます。</p> <p>オンプレミスのサーバーではあまり発生しないので、この辺りはクラウドならではかもしれません。</p> <h3 id="IAMによるアクセス制御"><a href="#IAM%E3%81%AB%E3%82%88%E3%82%8B%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%88%B6%E5%BE%A1">IAMによるアクセス制御</a></h3> <p>何でもできるユーザーでAWSにアクセスしてしまうと、誤操作によるトラブルが起こり得ます。<br /> そこで、できることを制限したAWSユーザーを作成することが望ましいです。</p> <p>AWSのIAMは設定できる項目が多すぎて、個人的に挫折ポイントだと思います。<br /> なので、こうやってIAMを学べる機会は貴重です!</p> <h3 id="ECRへのイメージデプロイからECSへ!"><a href="#ECR%E3%81%B8%E3%81%AE%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%8B%E3%82%89ECS%E3%81%B8%EF%BC%81">ECRへのイメージデプロイからECSへ!</a></h3> <p>ローカル上で作成したDockerイメージを、AWS上へとアップロードします。<br /> なお、イメージをアップロードする先は「ECR(EC2 Container Registry)」です。</p> <p>アップロードしたDockerイメージが実際に稼働するのは「ECS(EC2 Container Service)」で、ECSを管理することでサービスを稼働させます。</p> <h2 id="いろいろな現場でのDocker活⽤事例"><a href="#%E3%81%84%E3%82%8D%E3%81%84%E3%82%8D%E3%81%AA%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%81%AEDocker%E6%B4%BB%E2%BD%A4%E4%BA%8B%E4%BE%8B">いろいろな現場でのDocker活⽤事例</a></h2> <p>私がYYPHPでもお世話になっている、株式会社クラフトマンソフトウェアの@reoringさんの事例などが載ってました。<br /> イメージをアップロードするだけのDockerは、やっぱりお手軽なので私ももっと活用したいです。</p> <h3 id="私(このすみ)の活用事例も載ってます"><a href="#%E7%A7%81%EF%BC%88%E3%81%93%E3%81%AE%E3%81%99%E3%81%BF%EF%BC%89%E3%81%AE%E6%B4%BB%E7%94%A8%E4%BA%8B%E4%BE%8B%E3%82%82%E8%BC%89%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99">私(このすみ)の活用事例も載ってます</a></h3> <p>本のサンプルコードの動作確認でDockerを使っているので、そのことについて寄稿しました。</p> <p><a href="https://crieit.now.sh/upload_images/e0686746dccc5065bf9b4469289069505c84888deeb1b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e0686746dccc5065bf9b4469289069505c84888deeb1b.png?mw=700" alt="20190310120016.png" /></a></p> <p>ちなみに私は、技術同人誌の執筆はすべてDockerに統一しています。<br /> Windowsで書いているときも、Macで書いている時も、Dockerを使って同じ手順でPDFを生成できるので便利です。</p> <p>他の方の採用事例よりもライトな内容ですので、ぜひ気楽な気持ちでお読みいただけますと幸いです。<br /> Dockerは実際のサービスでも役に立ちますが、手元の環境を汚さなくて済むので、日常的に活用していくと真価を発揮するような気がしています。</p> <h2 id="さいごに:賢くなるわかばちゃん"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB%EF%BC%9A%E8%B3%A2%E3%81%8F%E3%81%AA%E3%82%8B%E3%82%8F%E3%81%8B%E3%81%B0%E3%81%A1%E3%82%83%E3%82%93">さいごに:賢くなるわかばちゃん</a></h2> <p>ECS用のタスク定義でJSONファイルを扱い始めたりと、わかばちゃんが多方面に賢くなり始めています(笑)。</p> <p>私はどちらかと言うとアプリケーション側が得意なエンジニアなので、こうやってインフラをわかりやすく学べる機会は貴重だと思ってます。<br /> お値段もお手頃ですので、オススメではないでしょうか。今よりインフラ力を上げたないなぁ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://llminatoll.booth.pm/items/1242542" target="_blank" rel="noopener">#マンガでわかるDocker ③ 〜AWS編〜 【ダウンロード版】#jawsdays #技術書典 - 湊川あいの、わかば家。 #技術書典 #わかばちゃんと学ぶ シリーズ - BOOTH</a></p> <h3 id="おまけ:湊川さん関連の過去記事"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9A%E6%B9%8A%E5%B7%9D%E3%81%95%E3%82%93%E9%96%A2%E9%80%A3%E3%81%AE%E9%81%8E%E5%8E%BB%E8%A8%98%E4%BA%8B">おまけ:湊川さん関連の過去記事</a></h3> <p>「しがないラジオ」に湊川さんが出演された回の感想と「初心者系VTuberわかばちゃん」の配信を観たときの話を過去に書きました!</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.konosumi.net/entry/2017/12/14/013753" target="_blank" rel="noopener">スキルを組み合わせてオンリーワンへ 湊川あいさん(しがないラジオsp.7a) - このすみノート</a></p> <p>[https://www.konosumi.net/entry/2018/08/27/020415:embed:cite]</p> このすみ tag:crieit.net,2005:PublicArticle/14700 2018-12-28T19:11:14+09:00 2018-12-28T19:11:14+09:00 https://crieit.net/posts/Puppeteer-on-AWS-Lambda Puppeteer on AWS Lambda のスクリーンショットで文字が豆腐になるので、ウェブフォントと絵文字フォントをインストール <h2 id="状況"><a href="#%E7%8A%B6%E6%B3%81">状況</a></h2> <p>現在開発中の https://poiit.me で puppeteer のスクリーンショットを使って動的OGPをつくっています。<br /> CSSで動的な画像をデザインできるし、一つ lambda 関数を作っておけば、いろんなところで応用ができるのでいいですね。</p> <p>が、今回は絵文字が、豆腐になってしまったのでその対処法。</p> <p><img src="https://qiita-image-store.s3.amazonaws.com/0/6531/6a6ea738-cf5e-15cb-4eb3-acb2dc85e5d8.jpeg" alt="yahsan2-ogp.jpg" /></p> <h2 id="参考記事"><a href="#%E5%8F%82%E8%80%83%E8%A8%98%E4%BA%8B">参考記事</a></h2> <p>ベースはこちらの記事を参考にしました。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/zyyx-matsushita/items/c33f79e33f242395019e">https://qiita.com/zyyx-matsushita/items/c33f79e33f242395019e</a></p> <p>基本は参考Qiita をベースに対応できましたが、絵文字フォントがどこにあるのか結構探したので、備忘録的にメモ。</p> <h2 id="絵文字フォントダウンロードと設置"><a href="#%E7%B5%B5%E6%96%87%E5%AD%97%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89%E3%81%A8%E8%A8%AD%E7%BD%AE">絵文字フォントダウンロードと設置</a></h2> <p>Google の Noto Color Emoji はこちらから ダウンロードできました。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.google.com/get/noto/#noto-emoji-zsye-color">https://www.google.com/get/noto/#noto-emoji-zsye-color</a></p> <p><strong>他のダウンロード可能な絵文字フォントを知っていたら、是非ダウンロード場所教えてください!</strong></p> <p>で、ダウンロードしたファイルを解答し <code>/.font/NotoColorEmoji.ttf</code> に設置</p> <h2 id="WEBフォント"><a href="#WEB%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88">WEBフォント</a></h2> <p>こちらは絵文字以外のフォントについては、WEBフォントをつかっています。</p> <p>screenshot を撮る前に head に追加してやれば反映されるはず。</p> <pre><code class="js"> await page.evaluate(() => { var style = document.createElement('style') style.textContent = ` @import url('//fonts.googleapis.com/css?family=M+PLUS+Rounded+1c|Roboto:300,400,500,700|Material+Icons'); div, input, a, p{ font-family: "M PLUS Rounded 1c", sans-serif; };` document.head.appendChild(style); document.body.style.fontFamily = "'M PLUS Rounded 1c', sans-serif"; }) </code></pre> <p>その後、長めに wait させたないとフォントが反映されていないことがあるので注意</p> <pre><code> await page.waitFor(3000); </code></pre> <p>こちらの記事を参考にしています。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/chimame/items/04c9b45d8467cf32892f">https://qiita.com/chimame/items/04c9b45d8467cf32892f</a></p> <h2 id="関数実行"><a href="#%E9%96%A2%E6%95%B0%E5%AE%9F%E8%A1%8C">関数実行</a></h2> <p>lambada で呼び出す関数で、フォントをサーバーにインストールする <code>fc-cache</code> コマンド実行してから、<br /> puppeteer の screenshot などを実行すれば、ちゃんと絵文字が反映されているはずです。</p> <p>アップロードすれば終了です。</p> <p><img src="https://qiita-image-store.s3.amazonaws.com/0/6531/54eae76b-6c6d-e567-d400-0c18bc462718.jpeg" alt="yahsan2-ogp-1.jpg" /></p> <p>めでたし!</p> <p>serverless を使ったベーシックな puppeteer on lambda は github に置いてあるので必要あれば。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/yahsan2/puppeteer-example-on-serverless-lambda">https://github.com/yahsan2/puppeteer-example-on-serverless-lambda</a></p> <p>この記事のフォント反映バージョンも要望あればで公開しますので、コメントください〜!</p> <h3 id="poiit というサービスつくっています!"><a href="#poiit+%E3%81%A8%E3%81%84%E3%81%86%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%EF%BC%81">poiit というサービスつくっています!</a></h3> <p>この記事がもし参考になれば、50円からサポートお願いします〜!<br /> <a target="_blank" rel="nofollow noopener" href="https://poiit.me/yahsan2/"><img src="http://res.cloudinary.com/dcsqopmuq/image/upload/v1545924831/poiit-production/yahsan2/yahsan2-ogp.jpg" alt="yahsan2-ogp-1.jpg" title="画像タイトル" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://poiit.me/yahsan2/">https://poiit.me/yahsan2/</a></p> yahsan2 tag:crieit.net,2005:PublicArticle/14695 2018-12-26T14:22:38+09:00 2018-12-26T17:34:10+09:00 https://crieit.net/posts/Lambda-Edge Lambda@Edgeで画像をリアルタイムにリサイズできる仕組みを作った <p>今作成中のサービスで画像をリアルタイム変換できる仕組みを AWS の Lambda@Edge を用いて実現してみたので紹介します。</p> <p>参考:<a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/">https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/</a></p> <h2 id="Lambda@Edge とは"><a href="#Lambda%40Edge+%E3%81%A8%E3%81%AF">Lambda@Edge とは</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-edge.html">Lambda@Edge</a> とは、一言で言うと CloudFront (CDN) の HTTP リクエスト・レスポンスを操作できる Lambda 関数です。</p> <p>下記のタイミングで CloudFront リクエスト・レスポンスを変更することができます。<br /> * CloudFront がビューワーからリクエストを受信した後 (ビューワーリクエスト)<br /> * CloudFront がリクエストをオリジンサーバーに転送する前 (オリジンリクエスト)<br /> * CloudFront がオリジンからレスポンスを受信した後 (オリジンレスポンス)<br /> * CloudFront がビューワーにレスポンスを転送する前 (ビューワーレスポンス)</p> <p><a href="https://crieit.now.sh/upload_images/47b4cad07dd3d07a60ea9ee3d38d59665c23022c30410.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/47b4cad07dd3d07a60ea9ee3d38d59665c23022c30410.png?mw=700" alt="a.png" /></a></p> <h2 id="作った仕組み"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E4%BB%95%E7%B5%84%E3%81%BF">作った仕組み</a></h2> <p>CloudFront のオリジンサーバーに S3 を指定し、S3 からの画像レスポンスを Lambda@Edge で変換する仕組みを構築しました。</p> <p>URLのパラメータを変えることでリサイズされた画像を取得できます。<br /> 例えば下記のように幅と高さを指定してリクエストするとそれぞれのサイズの画像が返却されるようにしています。</p> <p>・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=100x100 → 100x100の画像<br /> ・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=300x300 → 300x300の画像<br /> ・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=1000x1000 → 許可しないサイズはエラー</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <p>いろいろ説明をはしょってコードだけ貼っときます。。</p> <h3 id="ビューワーリクエスト"><a href="#%E3%83%93%E3%83%A5%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">ビューワーリクエスト</a></h3> <pre><code>'use strict'; const querystring = require('querystring'); const allowedDimensions = [ {w: 100, h: 100}, {w: 300, h: 300} ]; exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; // URIが特定の画像パスに一致するかチェック(特定の画像以外はクエリストリングを削除してリクエスト) const match = request.uri.match(/\/images\/user\/\d+\/avatar\/\d+\.jpg/); if (match == null) { request.querystring = ''; callback(null, request); return; } // クエリストリングを key-value のペアにパース const params = querystring.parse(request.querystring); // dimensionパラメータがない場合、クエリストリングを削除してリクエスト if (!params.d) { request.querystring = ''; callback(null, request); return; } // dimensionパラメータを'x'で分割 const dimensionMatch = params.d.split('x'); const width = dimensionMatch[0]; const height = dimensionMatch[1]; // dimensionパラメータが、許可リストに一致するかチェック let matchFound = false; for (let dimension of allowedDimensions) { if (width == dimension.w && height == dimension.h) { matchFound = true; break; } } // 許可リストにない場合、クエリストリングを削除してリクエスト if (!matchFound) { request.querystring = ''; callback(null, request); return; } // クエリストリングにd={width}x{height}を設定してリクエスト request.querystring = querystring.stringify({ d: width + 'x' + height }); callback(null, request); }; </code></pre> <h3 id="オリジンレスポンス"><a href="#%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%B3%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9">オリジンレスポンス</a></h3> <pre><code>'use strict'; const querystring = require('querystring'); const AWS = require('aws-sdk'); const S3 = new AWS.S3(); const Sharp = require('sharp'); const BUCKET = 'XXXXXXXX'; exports.handler = (event, context, callback) => { let response = event.Records[0].cf.response; if (response.status == 200) { const request = event.Records[0].cf.request; // クエリストリングを key-value のペアにパース const params = querystring.parse(request.querystring); // dimensionパラメータがない場合、そのままレスポンスを返す if (!params.d) { callback(null, response); return; } // S3キー用にuriの最初のスラッシュを除いた文字列を取得 const key = request.uri.substring(1); // dimensionパラメータを'x'で分割 const dimensionMatch = params.d.split("x"); const width = parseInt(dimensionMatch[0], 10); const height = parseInt(dimensionMatch[1], 10); // S3から画像を読み込み S3.getObject({ Bucket: BUCKET, Key: key }).promise() // リサイズ .then(data => Sharp(data.Body) .resize(width, height) .toBuffer() ) .then(buffer => { // レスポンスを生成 response.status = 200; response.body = buffer.toString('base64'); response.bodyEncoding = 'base64'; response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/jpeg' }]; callback(null, response); }) .catch( err => { callback(null, response); }); } else { // 200以外のステータスコードの場合、そのままレスポンスを返す callback(null, response); } }; </code></pre> <h2 id="なぜ作ったか"><a href="#%E3%81%AA%E3%81%9C%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B">なぜ作ったか</a></h2> <p>普通なら「非同期ジョブでリサイズ処理してS3に複数サイズ保存」とかで事足りるのではと思います。</p> <p>今回は、<br /> * アップロード時間を短くしたい(アップロード時はバリデートのみで変換処理しない)<br /> * 画像をアップロードした直後の画面でリサイズされた画像を表示したい<br /> * リサイズ画像をキャッシュしたい<br /> * 画像サイズあとで変えたいかも</p> <p>といった理由で、CDNを通してリアルタイム変換という選択肢を選びました。</p> <h2 id="感想"><a href="#%E6%84%9F%E6%83%B3">感想</a></h2> <p>AWS Lambda 自体初めて触りましたが、サーバレスは便利だな〜と感じました。<br /> ただ、Lambdaの開発やテストやデプロイをどうやるのが正解なのかよくわからず、追加開発があるような比較的規模の大きいプロジェクトだとLambdaは大変そう(?)と思いました。</p> <p>今回は画像リサイズだけでしたが、透かしを入れたり合成したり、画像以外にも動的にキャッシュできるので、いろいろ使い所ありそうです。</p> <p>早くサービスリリースできるよう頑張ります。。</p> オクムラダイキ