TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

546

はじめに こんにちはサイオステクノロジーの小野です。今回はRancherを構築後にWebUIにアクセスしたらリダイレクトが繰り返されるエラーが発生して解決に時間がかかったので、その解決方法を共有します。 今回の構成 前提条件 OS:Ubuntu 24.04.2 LTS Rancher:v2.11.1 RKE2:v1.32.3+rke2r1 HAProxy:2.8.5-1ubuntu3.3 構成 今回構築したRancherの構成は以下になります。 今回構築したRancherの構成 RKE2クラスタを3台冗長構成で構築して、これをRancherのRMSクラスタとして利用します。 また、外部にLBサーバー(HAProxy)を置いて、それによってRMSクラスタをロードバランスします。その際にRancherへのアクセス時のTLS終端をHAProxyで行います。 TLS終端はopensslによって自己署名証明書を発行して、それを利用します。 HAProxyの設定ファイルは以下のように設定しました。LBサーバーにHTTPSアクセス(Port:443)を行ったら、RMSノードにHTTPアクセス(Port:80)を行うようにロードバランスします。 frontend rancher_front bind *:443 ssl crt <TLS終端の証明書> mode http option forwardfor http-request add-header X-Forwarded-Proto https if { ssl_fc } default_backend rancher_back backend rancher_back mode http balance roundrobin server cp_node1 <CPノード1のIPアドレス>:80 check server cp_node2 <CPノード2のIPアドレス>:80 check server cp_node3 <CPノード3のIPアドレス>:80 check HAProxy設定後、RKE2クラスタに以下のようにHelmでRancherをインストールしました。 helm install rancher rancher-latest/rancher \ --namespace cattle-system \ --set hostname= <LBサーバーのドメイン> \ --set replicas=3 \ --set bootstrapPassword=<初期パスワード> \ --set tls=external 問題点 Rancherインストール後、RancherWebUIにアクセスするとリダイレクトし続けて、アクセスできません。 RancherWebUIを開くとリダイレクトし続けてエラーが出る RancherのGithubのIssusesでも関連した問題が報告されています: https://github.com/rancher/rancher/issues/35088 解決方法 公式ドキュメントに解決方法が載っていました。 Rancher Helm Chart Options > External TLS Termination 外部でTLS終端する場合は、Nginx ingressに「use-forwarded-headers」というオプションを有効にする必要があります。したがって、RancherをHelmでインストールする前に以下のようなyamlをRancherをインストールするRKE2クラスタにapplyする、もしくは同様のリソースを編集してconfigに「use-forwarded-headers: “true”」を追加してください。 apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: rke2-ingress-nginx namespace: kube-system spec: valuesContent: |- controller: config: use-forwarded-headers: "true" 問題の原因 問題が発生する流れは以下になります。 ユーザーがLBにHTTPSアクセスする LBでTLS終端する LBからRancherにHTTPアクセスする RancherがHTTPアクセスを安全でないと判断して、HTTPSアクセスするようにLBにリダイレクトする LBはRancherからのリダイレクトをユーザーに転送する 1に戻る このような処理が繰り返されることでリダイレクトの無限ループが発生します。 通常、TLS終端を行ったHTTPアクセスのヘッダーには、ユーザーがLBに接続するために使用したプロトコル(HTTP、HTTPS)の情報が含まれています。Nginx ingressに設定した「use-forwarded-headers: “true”」とは、Rancherがそのヘッダー情報を参照する設定になります。 これによってLBからRancherへHTTPアクセスしても、ヘッダー情報を基にユーザーのリクエスト元がHTTPSアクセスであったことを認識できるようになります。その結果、Rancherはリダイレクトを行わなくなり、無限ループが解消されます。 終わりに コンテナプラットフォームを構築する際に、外部サーバーでLBを設定して、TLS終端することはごく一般的な構成になります。しかし、今回構築を行って、細かい部分でたくさんのエラーに遭遇してとても苦労しました。公式ドキュメントはしっかりと読むことを心掛けたいと思います。 参考 Rancher公式ドキュメント: https://ranchermanager.docs.rancher.com/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 外部TLS終端のRancher構築におけるリダイレクト問題の解決法 first appeared on SIOS Tech. Lab .
アバター
概要 こんにちは。サイオステクノロジーの塙です。 今回は、閉域網環境のOpenShift(以下、OCP)で、ミラーレジストリーの構成について説明したいと思います。 OpenShiftの詳細については、本ブログでは省略しています。OpenShiftに関する内容についてはサイオスの他ブログ等をご参照下さい。 SIOS Tech. Lab – OpenShift ミラーレジストリーの概要 ミラーレジストリー OCPを構築する際には、外部のQuay等のリポジトリからOCPやOperatorのイメージを取得して構築を行います。 閉域網環境のOCPでは、ノードからインターネットに接続出来ない環境のためミラーレジストリーを作成して対応します。 ミラーレジストリーでOCPやOperatorのイメージを取得して、OCPの構築時にミラーレジストリーのイメージを利用するという流れで構築を行います。 ミラーレジストリーは、OCPサブスクリプションに含まれているため、特に追加費用なく利用できます。 また、既に環境内でRed Hat Quay, Artifactory, NexusリポジトリなどDockerv2-2 をサポートする任意のコンテナーレジストリーを使用している場合は、ミラーレジストリーの代わりに使用することができます。 ミラーレジストリー利用のシナリオ ミラーレジストリーを利用する場合、以下の2つのシナリオで利用できます。 ミラーレジストリーを導入するノードが、インターネットとOCPノードに接続できるパターン ミラーレジストリーを導入するノードが、インターネット非接続であり、OCPノードと接続できるパターン 1. の場合だと、イメージをインターネットから直接ミラーレジストリーにミラーすることができます。 2. の場合だと、インターネット接続可能なノードでイメージを取得してから、リムーバブルメディア経由で転送してミラーレジストリーにミラーする方法になります。 これらのシナリオは主に完全な閉域網環境として構築するかといった要件で方式が変わってくる部分となります。 それぞれのシナリオでは、イメージのミラーリング方法について少し手順が異なってくるので注意が必要になります。 今回は、2.の場合の内容は扱いません。 参考: ・ OpenShift Container Platform – 2.1. ミラーレジストリーについて 事前準備 ミラーレジストリーを導入するノードを用意します。 今回は、AWS環境のベアメタルインスタンスでKVMを構築して、KVM上のVMでノードを準備します。 VMの用意までの手順はここでは割愛します。 ミラーレジストリーの前提条件 に従って、VMのリソースは以下で用意をします。 vCPU: 2コア メモリ:8GB ストレージ:500GB   また参考の資料に従って、VMにインストールするRHEL OSを用意しておきます。 Web上からRHEL ISOイメージをダウンロードする手順に則り、KVMホスト上に展開します。 インストール手順としてカスタマーポータルからダウンロードする手順と、curlを用いてダウンロードする手順がありますのでどちらかの手順を用いて下さい。 参考: ・ 2.2. RHEL インストール ISO イメージのダウンロード 事前準備手順 VMの用意までが出来たら、VMにSSHで接続して事前準備を行います。 OCコマンドをインストールします。 インストールするバージョンを確認して、それに合わせる形でURLにバージョンを記載します。 $ curl -L -o - "https://mirror.openshift.com/pub/openshift-v4/$(uname -m)/clients/ocp/4.xx.xx/openshift-client-linux.tar.gz" | sudo tar -C /usr/local/bin -xvzf - oc $ oc version 必要なパッケージをインストールします。 インストールするバージョンを確認して、それに合わせる形でURLにバージョンを記載します。 # podmanのインストール $ sudo dnf install podman -y # oc-mirrorのインストール $ curl -LO https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/4.xx.xx/oc-mirror.rhel9.tar.gz $ tar xvzf oc-mirror.rhel9.tar.gz $ chmod +x oc-mirror $ sudo mv oc-mirror /usr/local/bin/ $ oc mirror --v2 --help ミラーレジストリーのインストール 次に、ミラーレジストリーをインストールします。 インストールでは、mirror-registry コマンドを使用してインストール作業を行います。 mirror-registry install コマンドでは、ミラーレジストリーの初期ユーザー名とパスワードが出力されますので、このタイミングで保存をしておきます。 ミラーレジストリーのインストールパラメータ: quayHostname:クライアントがミラーレジストリーへの接続に使用するドメイン名を決定します。 quayRoot:ミラーレジストリーのルートディレクトリを決定します。該当ディレクトリ上にデータの保存等が行われます。 $ curl -LO https://mirror.openshift.com/pub/cgw/mirror-registry/2.0.6/mirror-registry-amd64.tar.gz $ tar xf mirror-registry-amd64.tar.gz $ sudo ./mirror-registry install --quayHostname $(hostname -f) --quayRoot /var/lib/quay ミラーレジストリーのCA証明書をOSに登録します。CA証明書はインストール時に指定したquayRootのディレクトリ配下に保存されています。 $ sudo cp /var/lib/quay/quay-rootCA/rootCA.pem /etc/pki/ca-trust/source/anchors/ $ sudo update-ca-trust ミラー用の認証情報を作成 ミラーレジストリーのプルシークレットと、外部レジストリのプルシークレットの情報を持ったミラー用の認証情報(以下、Authファイル)を作成していきます。 ユーザー名とパスワードを用いて、ミラーレジストリーにログインを行います。この時authfileにミラーレジストリーへのアクセス情報としてプルシークレットを生成するようにします。 $ podman login -u init \ -p <password> \ --authfile ./mirror-pull-secret.txt \ <quayHostname>:8443 \ --tls-verify=false Authファイルのディレクトリを作成します。 $ mkdir -p $XDG_RUNTIME_DIR/containers   プルシークレットの情報を作成します。 Red Hat Hybrid Cloud Console にRed Hatアカウントを用いてログインします。プルシークレットをダウンロードまたはコピーして保存しておきます。 https://console.redhat.com/openshift/install/pull-secret 保存したプルシークレットをAuthファイルとして保存します。 $ cat pull-secret.txt | jq . > $XDG_RUNTIME_DIR/containers/auth.json Authファイルを編集して、ミラーレジストリーのプルシークレットmirror-pull-secret.txtの内容を連結します。 最終的に下記のような形式に編集していきます。 $ vi $XDG_RUNTIME_DIR/containers/auth.json === { "auths": { "<quayHostname>:8443": {  # ミラーレジストリーのプルシークレット情報を連結する "auth": "<auth>", "email": "<email>" }, "quay.io": {         # 以下、外部レジストリのプルシークレット情報 "auth": "<auth>", "email": "<email>" }, ..(omit).. } } === OpenShiftのイメージのミラー ミラーレジストリーのノードで、必要なイメージをミラーしていきます。 ミラーリングの方法として、oc admコマンドを使用する方法と、oc-mirrorプラグインを使用した方法があります。 今回はoc-mirrorプラグインを使用した方法でミラーリングを実施します。oc-mirrorプラグインを使用するために事前準備で必要なパッケージをインストールしています。 OpenShiftのクラスターリソースの生成先のディレクトリーを作成します。 $ mkdir mirror-image ImageSetConfiguration ファイルを作成します。 このファイルはOpenShift、Operatorの各イメージをミラーレジストリで取得する時の設定を記述します。 OpenShiftのイメージ:mirror.platform.channels で指定したバージョンのOpenShiftイメージを記載 Operatorのイメージ:mirror.operators でOperatorイメージを記載 追加のイメージ:mirror.additionalImages で追加のイメージを記載 Helm:helm でHelmチャート等を記載 $ vi ImageSetConfiguration.yaml 記載例) === kind: ImageSetConfiguration apiVersion: mirror.openshift.io/v2alpha1 mirror: platform: channels: - name: stable-4.18 minVersion: 4.18.11 maxVersion: 4.18.11 operators: - catalog : registry.redhat.io/redhat/redhat - operator - index : v4.18 additionalImages: - name: registry.redhat.io/ubi8/ubi:latest - name: registry.redhat.io/ubi9/ubi@sha256:20f695d2a91352d4eaa25107535126727b5945bff38ed36a3e59590f495046f0 - name: registry.redhat.io/rhel8/support-tools:latest - name: registry.redhat.io/rhel9/support-tools:latest helm: {} === ミラーリングを実施します。 ImageSetConfiguration ファイルに記載した各イメージのミラーリングを出力結果から確認することができます。 また、この時クラスターリソースの生成が行われ、ImageDigestMirrorSet、ImageTagMirrorSet、および CatalogSource リソースの YAML ファイルが生成されます。 $ oc mirror -c ImageSetConfiguration.yaml --workspace file:/// < file_path > /mirror-image docker://<quayHostname>:8443 --v2 ここまでで、ミラーレジストリーにイメージの格納までを紹介しました。 この後は、OpenShiftのセットアップでこのミラーレジストリーのイメージを利用して構築していく形になります。   参考: OpenShift Container Platform – 第3章 非接続環境でのミラーリング まとめ 今回は、閉域網環境のOCPで、ミラーレジストリーの構成について説明しました。 閉域網環境でのミラーリングの手順は少々難しい点もあり、うまくミラーリングのセットアップが出来ていなかったりしてエラーにハマることも多いので改めて整理した内容を記載してみました。 この記事が読者の参考になれば幸いです。 参考文献 OpenShift Container Platform – 2.1. ミラーレジストリーについて ミラーレジストリーの前提条件 2.2. RHEL インストール ISO イメージのダウンロード OpenShift Container Platform – 第3章 非接続環境でのミラーリング ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 閉域網環境のOpenShiftでミラーレジストリーを構成する first appeared on SIOS Tech. Lab .
アバター
ご挨拶 ども!3日で3本もブログを執筆するという狂ったことをやってしまいました。ちょっとやりすぎちゃったなと感じている龍ちゃんです。シリーズだったので、投稿するという達成感が得られない中、よく書いたなと思います。 勉強もかねてBicepに入門したので、学習のログをシリーズのブログにしてみました。タイトルが誇大広告になっている気がするのは否めないですが、Claudeさんが書いてくれたのでここは信じて投稿します。 さて、今回は初のシリーズを執筆しました。その記事をPDFで読み込ませてシリーズ紹介ブログをAIに依頼して、ほぼAIに執筆していただこうかと思います!正直、このブログが伸びたらちょっとだけしょげちゃいますねw ではAIにバトンタッチです! AIからのご挨拶 こんにちは!Anthropic社のClaude(Claude Sonnet 4)です。⿓ちゃんから「Azure Static Web Apps × Bicepシリーズの紹介記事を書いてほしい」とご依頼をいただき、この記事を執筆させていただきました。 ⿓ちゃんが実際に検証された内容をもとに、技術的な構成や学習の流れを整理し、読者の皆さんにとって分かりやすい形でお届けできるよう心がけました。私自身、このシリーズの段階的な学習設計や実践的なアプローチに感銘を受けており、多くの開発者の方に役立つ内容だと確信しています。 AIとして、客観的な視点から技術記事の価値をお伝えできれば幸いです。もし内容に不明な点や改善点がございましたら、ぜひコメントでお聞かせください。皆さんのフィードバックが、より良い技術コンテンツ作成の糧になります。 このシリーズで目指すもの このシリーズは「 Azure Static Web Apps を Bicep で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する 」という、現代的な開発フローを 基礎から実践まで段階的に学べる ことを目標としています。 単なるチュートリアルではなく、 実際のプロジェクトで使える知識とスキル を身につけられるよう、以下の点にこだわって構成しました: 実践重視のアプローチ 理論だけでなく、手を動かして学べる内容 実際のコマンド例とトラブルシューティング 本番運用を想定した設定と実践的なテクニック 段階的な学習設計 環境構築 → ローカル開発 → CI/CD自動化の自然な流れ 前回の知識を活用しながら次のステップへ進む構成 各回で完結しつつ、シリーズ全体で大きな成果を得られる設計 現実的な開発フロー DevContainerによるチーム開発環境の統一 Infrastructure as Codeによる再現可能なインフラ管理 GitHub Actionsを使った現代的なCI/CDパイプライン シリーズ構成の紹介 このシリーズは3部構成となっており、それぞれが独立して学べる内容でありながら、連続して読むことでより深い理解が得られる設計になっています。 第一回: DevContainerで統一環境構築 第二回: 実践編:Infrastructure as Code実践!Bicepテンプレートでローカル開発環境構築 第三回: CI/CD編:GitHub Actions で自動化!Bicepで実践CI/CDパイプライン構築 DevContainer実践入門:Azure CLI+GitHub CLI環境をチーム全体で統一 2025-06-26 作業時間を70%短縮!BicepによるAzure Static Web Apps自動構築 2025-06-26 GitHub Actions自動化実践:Azure SWA×Bicep CI/CDパイプライン構築 2025-06-26 【第1回】環境構築編:DevContainer で統一開発環境構築 この回の目標 DevContainerによる開発環境の統一化 Azure CLI、GitHub CLI、SWA CLIが使える状態の構築 後続の作業に必要な認証設定の完了 学べること DevContainerの設定方法と実践的なテクニック 各種CLIツールのセットアップと認証 Azure Static Web Appsの基本的な作成・デプロイ・削除操作 チーム開発での環境差異を解消するテクニック 実践内容 # 主要なコマンド操作を体験 az staticwebapp create --name $SWA_NAME --resource-group $RESOURCE_GROUP swa deploy --app-location out --env production --deployment-token $TOKEN az staticwebapp delete --name $SWA_NAME --resource-group $RESOURCE_GROUP この回を読み終える頃には、 チーム全体で同じ開発環境を共有 でき、Azure CLIでのリソース管理の基本的な流れが理解できるようになります。 【第2回】実践編:Infrastructure as Code実践!Bicepテンプレートでローカル開発環境構築 この回の目標 ローカル開発用のBicepテンプレート作成 SWA CLIを使った手動デプロイの実行 開発サイクルでの迅速なデプロイフローの確立 学べること Bicepテンプレートの構造と書き方 パラメータファイルによる設定の分離とセキュリティ向上 リソースグループスコープでの実践的な運用方法 自動化スクリプトによる開発効率の大幅向上 実践内容 // GitHub Actions連携用Static Web Apps resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: '${projectName}-swa-${environment}-local' location: location properties: { buildProperties: { appLocation: '/out' apiLocation: '' outputLocation: '' } } } この回では、 手動作業を自動化スクリプトで効率化 し、Bicepを使ったInfrastructure as Codeの基本をマスターできます。特に、10分かかっていた作業を2-3分に短縮できる自動化の威力を体感していただけるはずです。 【第3回】CI/CD編:GitHub Actions で自動化!Bicepで実践CI/CDパイプライン構築 この回の目標 GitHub Actions デプロイを前提としたBicepテンプレートの作成 GitHub CLIを使った自動化によるSecretsとVariablesの設定 GitHubリポジトリへのプッシュをトリガーとした自動デプロイパイプライン 学べること 本番環境を想定したワークフローファイルの構築 GitHub Environment を使ったSecrets/Variables管理 エラーハンドリングから前提条件の確認まで、本番運用を意識した自動化 skipGithubActionWorkflowGeneration: true による手動ワークフロー管理 実践内容 # GitHub Actions ワークフロー - name: Build And Deploy uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} output_location: ${{ env.OUTPUT_LOCATION }} 最終回では、 GitHub CLIを活用した環境設定の自動化 の威力を実感できるはずです。従来だと手動でSecretsを登録する必要がありましたが、bashスクリプトで一気に設定できるのは開発効率の大幅な向上につながります。 各記事への導入ガイド 初心者の方へ 第1回から順番に読むことを強くおすすめします 。特にDevContainerの環境構築は、後続の記事で前提となる重要な基盤です。Azure初心者の方でも、段階的に学べるよう丁寧に解説しています。 中級者の方へ Bicepには馴染みがあるけれど、GitHub Actionsとの連携は初めて という方は、第2回から読み始めても大丈夫です。ただし、DevContainerの恩恵は大きいので、第1回も一読されることをおすすめします。 上級者の方へ すでにInfrastructure as CodeやCI/CDに慣れ親しんでいる 方は、第3回の自動化スクリプトのテクニックに注目してください。エラーハンドリングやリトライ機能、シーケンス図を使った処理フローの可視化など、実践的なノウハウが満載です。 とりあえずbashファイルを捧げます! 間違いがあればコメントください。糧にします。 学習効果を最大化するコツ 実際に手を動かす 記事を読むだけでなく、 必ず実際にコマンドを実行 してみてください。エラーが出てもそれが学習の一部です。トラブルシューティングの経験こそが、実践力を大きく向上させます。 コマンドをメモする 各記事で紹介するコマンドは、 README.mdなどにメモ しておくことをおすすめします。後から「あのコマンドなんだったっけ?」となることを防げます。 応用してみる 基本を理解できたら、 自分なりのカスタマイズ を試してみてください。パラメータファイルの値を変えたり、別のフレームワークに適用したりすることで、理解が深まります。 まとめ このAzure Static Web Apps × Bicepシリーズは、現代的な開発フローを段階的に学べる実践的な技術記事です。DevContainerによる環境統一から始まり、Bicepでのインフラ管理、最終的にはGitHub Actionsでの完全自動化まで、実際の開発現場で使える知識とスキルを身につけることができます。 各記事は独立して読むこともできますが、連続して学習することで、より深い理解と実践力を得られる構成になっています。特に自動化スクリプトのテクニックや、パラメータファイルによる設定分離など、実務で役立つノウハウが多数含まれています。 ぜひ実際に手を動かしながら学習し、皆さんの開発フローの改善にお役立てください。質問や改善点があれば、コメントでお聞かせください。皆さんのフィードバックが、より良い技術コンテンツ作成の原動力になります。 龍ちゃん いや~フルで書いてもらいましたけど、1分でこの分量はすごいわ~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Infrastructure as Code実践:Azure SWA×Bicep×GitHub Actions first appeared on SIOS Tech. Lab .
アバター
ご挨拶 ども!最近はClaudeのトークン数の消費が多くてちょいちょい制限にかかって、GitHub CopilotとGeminiも巡回ルートに入った龍ちゃんです。AIサービスにぶん投げている時間で、別の作業ができるってのが最高ですよね。チャットの代わりに音声入力したいですが、ずっと起動してるとPCパワーが足りなくなるのが難点です。 基礎から学ぶ Azure Static Web Apps × Bicep 入門 今回は「Azure Static Web Apps を Bicep(Infrastructure as Code)で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する」というテーマで3部構成の「GitHub Action連携編」となります。各回のコンテンツの内容としては以下になります。 シリーズのまとめ記事はこちら になります。 Azure Static Web Apps + Bicep環境構築編 ローカル開発でのBicep + SWA CLIデプロイ実践編 Bicep+GitHub Actions連携デプロイ編 ←  今回はここ この回での目標は以下となります。 GitHub Actions デプロイを前提としたBicepテンプレートの作成 GitHub Environment とSecrets の設定 : BicepでStatic Web Appsを作成した後に取得できるデプロイトークンやアプリ設定情報をGitHubに登録 GitHub CLIを使った自動化 : ローカルからGitHub CLIでSecrets登録を行うbashスクリプトの作成 GitHub Actions ワークフローの作成 : GitHubリポジトリへのプッシュをトリガーとした自動デプロイパイプライン それでは、ローカルからのBicepとGitHub CLIでの環境設定とGitHub Actionsを用いたデプロイを始めましょう。 前回の振り返り 前回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門 #2」として、Bicepテンプレートを使ったローカル開発環境の構築とSWA CLIデプロイの実践を行いました。 前回の記事では、DevContainer環境を活用して、Azure CLI、GitHub CLI、SWA CLIがすべて使える状態から、実際にBicepテンプレートでのInfrastructure as Codeに挑戦しました。特にAzure CLIの認証情報がマウントされていたおかげで、コンテナ内での作業がとてもスムーズに進められましたね。 Bicepテンプレートの作成では、パラメータファイル( parameters.local.json )を使った設定値の分離を学びました。これによって、同じテンプレートを使って複数の環境を管理できるようになり、機密情報をBicepテンプレート本体から分離できるセキュリティ面でのメリットも大きかったです。 手動でのazコマンド実行から、最終的には一連の作業を自動化するbashスクリプト( deploy.local.sh )まで作成して、開発効率の向上を実感していただけたかと思います。 今回の記事は前回作成したBicepテンプレートとローカル開発環境をもとに進めていきます。もし環境が手元にない方は下の記事を先に読んでください。 環境構築については 第一回の記事 をご参照ください。 ローカル実行をターゲットとしたBicepに関しては 第二回の記事 をご参照ください。 ローカルからBicepとGitHub Actionsを使ったデプロイ Bicep × GitHub Actionsで環境を作成するシナリオ まずは、シナリオの整理から始めようと思います。Bicepでは ターゲットスコープを指定 することができ、実行時に影響を及ぼす範囲を限定することができます。 resourceGroup: 既存のリソースグループに対するアクション subscription: サブスクリプション全体での管理 managementGroup: 複数サブスクリプションをまとめて管理 tenant: テナント全体への最上位レベル操作 resourceGroup → subscription → managementGroup → tenant という具合に上位の権限が必要になります。一般的な開発者権限の場合は、resouceGroupスコープしか扱うことができないかと思います。 以上を踏まえて、今回のシナリオとしては「resouceGroupスコープで実行し、ローカルから既存のリソースグループに対してBicepを使用してStatic Web Appsを管理し、GitHub Acitonsでデプロイに必要なキーをGitHub CLIを使用して設定」で解説を進めていきます。 ディレクトリ構成 今回、検証に使用するプロジェクトのディレクトリ構成になります。 . ├── .devcontainer/ │ └── devcontainer.json ├── .github/ │ └── workflows/ │ └── deploy.yml # GitHub Actions workflow ⭐ ├── infra/ │ ├── bicep/ │ │ ├── main.bicep # GitHub Actions用Bicep ⭐ │ │ ├── main-local.bicep │ │ ├── parameters.json # GitHub Actions用パラメータ ⭐ │ │ └── parameters-local.json │ └── scripts/ │ ├── deploy.sh # 本番環境デプロイスクリプト ⭐ │ └── deploy-local.sh ├── out/ │ └── index.html ├── .env.local ├── .env.deploy # スクリプト用環境変数 ├── .gitignore # 機密情報を守る砦 ├── swa-cli.config.json └── README.md # コマンドをメモするのに使ってね! 今回作成するファイルとしては、以下の3つになります。 deploy.yml:GitHub Actions用ワークフローファイル parameters.json:ローカル開発用パラメータファイル main.bicep:ローカル開発用Bicepテンプレート パラメータファイルで値を管理する infra > bicep > parameters.json こちらは、Bicepテンプレートへ値を渡すパラメーターファイルとなります。 { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "projectName": { "value": "bicep-swa-prod" }, "environment": { "value": "production" }, "location": { "value": "East Asia" }, "repositoryUrl": { "value": "https://github.com/USER_MAME/REPOSITORY_NAME" }, "branch": { "value": "main" }, "appLocation": { "value": "/out" }, "apiLocation": { "value": "" }, "outputLocation": { "value": "" }, "deploymentProvider": { "value": "GitHub" } } } 設定項目を切り出しておくことで、パラメーターファイルを作成するだけで別の環境を作成することができます。また、リソース管理の観点からも変更差分が追跡しやすくなるため、使用する値はパラメーターファイルとして管理することが必須となります。特に機密情報(パスワードやAPI キーなど)をBicepテンプレート本体から分離できるセキュリティ面でのメリットも大きく、GitHubなどへの誤った機密情報のコミットを防止できます。 GitHub Actions用の設定項目として、リポジトリの情報とSWAへのデプロイ情報が追加されています。こちらは、Bicepテンプレートファイルで使用するため追加しています。 Bicepテンプレート infra > Bicep > main.bicep Bicepのテンプレートは、以下の3つのパーツで構成されています。 パラメータ受け取り部分 Static Web Apps定義部分 出力用設定 @description('プロジェクト名(リソース名のプレフィックスとして使用)') param projectName string @description('デプロイ環境 (dev, staging, prod)') param environment string = 'dev' @description('デプロイ先のリージョン') param location string = resourceGroup().location @description('GitHubリポジトリのURL') param repositoryUrl string @description('GitHubリポジトリのブランチ名') param branch string = 'main' @description('アプリファイルの場所') param appLocation string = '/out' @description('APIファイルの場所(使用しない場合は空文字)') param apiLocation string = '' @description('出力ファイルの場所') param outputLocation string = '' @description('デプロイプロバイダー (GitHub, DevOps)') @allowed(['GitHub', 'DevOps']) param deploymentProvider string = 'GitHub' // GitHub Actions連携用Static Web Apps resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: '${projectName}-swa-${environment}' location: location sku: { name: 'Free' tier: 'Free' } properties: { provider: deploymentProvider repositoryUrl: repositoryUrl branch: branch buildProperties: { appLocation: appLocation apiLocation: apiLocation outputLocation: outputLocation skipGithubActionWorkflowGeneration: true // 手動管理 } } tags: { Environment: environment Purpose: 'Production' CreatedBy: 'GitHub-Actions' Repository: repositoryUrl Branch: branch } } // 出力値 output staticWebAppName string = staticWebApp.name output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}' output staticWebAppId string = staticWebApp.id output resourceGroupName string = resourceGroup().name output subscriptionId string = subscription().subscriptionId output repositoryUrl string = repositoryUrl パラメーター受け取り部分は、先ほど設定したパラメータの受け取りと初期値の設定を行います。 Static Web Apps定義部分は、SWAの構築に必要な情報が記載されています。必須のパラメータとしては、以下になります。 name:SWAの名前(Azure内でユニークな名前の必要あり) location:デプロイリージョン(SWAの場合 East US 2 , West Europe , Central US , East Asia , West US 2 ) sku:価格プラン name-tierは一致させる tags はAzure Protalやazコマンドでの検索を容易にしてくれます。設定しなくてもよいですが、検証中は助かる設定項目になります。将来的に複数のSWAを運用する際に、設定することで管理が容易になります。 tags にpreviewやtestなどをつけておけば、必要なくなったら一気に削除なんてスクリプトを実行することも可能です。 今回のブログで重要になるのは properties 設定となります。こちらの設定項目が、GitHubリポジトリとStatic Web Appsを接続しています。 プロパティ名 型 説明 provider string デプロイメントプロバイダー repositoryUrl string GitHubリポジトリのURL branch string デプロイ対象ブランチ provider はGitHubと連携するのでGitHub固定でもよいですし、Azure DevOpsと連携するならばDevOpsを設定します。 repositoryUrl と branch はGitHub Actionsでデプロイ運用と合わせた形で設定してください。 次に buildProperties の中の設定を見ていきます。 プロパティ名 型 説明 appLocation string 静的ファイルの格納ディレクトリ apiLocation string Azure Functions(API)のソース場所 outputLocation string ビルド後の成果物出力先 skipGithubActionWorkflowGeneration boolean GitHub Actionsワークフロー自動生成の無効化 appLocation 、 outputLocation は使用するフレームワークによって変動する値になります。特に appLocation は必須で設定が必要になる値です。リポジトリのルートで基本問題ありませんが、リポジトリの構成によってはサブディレクトリに静的アプリのソースコードがある場合もあります。その場合は、リポジトリの構成に沿って適切に設定をしてください。 apiLocation は同一リソースでAzure Functionsを作成していなければ設定しなくてよくなります。 skipGithubActionWorkflowGeneration はStatic Web AppsがGitHub Actionsを構成してくれるかの設定になります。Azure PortalからStatic Web Appsを構築した経験がある方はStatic Web Appsの構築中にGitHubの連携を行えば、自動でGitHub Actionsを構成してくれた経験があるかもしれません。便利な機能ですが、今回は自前でワークフローファイルを構成するため true に設定します。 GitHub Actionsの設定 .github > workflows > deploy.yml 次にワークフローファイルの構築を行いましょう。 name: Deploy to Azure Static Web Apps on: workflow_dispatch: # 手動実行を可能にする push: branches: [main] paths: - 'out/**' # outディレクトリ内のファイルが変更された場合のみ実行 permissions: contents: read pull-requests: write jobs: build_and_deploy: environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || '.' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || 'out' }} API_LOCATION: ${{ vars.API_LOCATION || '' }} runs-on: ubuntu-latest name: Build and Deploy steps: - uses: actions/checkout@v4 with: submodules: true - name: Build And Deploy uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} api_location: ${{ env.API_LOCATION }} output_location: ${{ env.OUTPUT_LOCATION }} skip_app_build: true ワークフローの実行タイミングとしては以下の二つを設定してます。 workflow_dispatch :GitHub Actionsタブで手動実行 push > branches :mainにpushされ、かつ変更が out ディレクトリに加えられた際に実行 これは検証目的で設定した内容です。 paths で実行タイミングを制御することでソース以外の変更(READMEなどのドキュメント更新)の際は、ビルドを実行しないなどの制御が可能になります。これは、本場環境でのワークフローファイルでも使用する可能性があります。 注目すべき点としては、 environment として production を設定して設定済みの Secret と Variable にアクセスしている点です。次のセクションでBicepで作成したリソースと登録情報から、GitHub CLIを使用して設定する項目になります。Variableに関しては、未設定の可能性も考慮して未設定の場合は空文字もしくは定数を指定しています。 Static Web Appsは、 Azure/static-web-apps-deploy@v1 という便利なアクションを提供してくれています。こちらのアクションは、アクション使用時にディレクトリ構成を判断してビルドから配信までを行ってくれます。今回は、デモ用にビルドが不必要な静的ファイル(HTML)を使用しているため、 skip_app_build を設定してビルド自体をスキップしています。そのほかにも使用しているフレームワークに合わせて設定することで効率的な静的ファイルの配信を行うことができます。 設定項目の公式ドキュメントはこちら になります。 !先ほどのBicepのparameterファイルと違うじゃないか!と思った方素晴らしいです。 結論としてこれでも動きます。 skip_app_build を設定してビルドが発生しない環境において、 app_location : . ・ output_location : out の設定は app_location : out 、 output_location が空文字と挙動としては同じになります。設定の方法の豊富なのも考え物ですが、本来であればBicepで設定した値と同等にしておくほうが良いです。今回は、こちらの解説をしたかったのであえて異なる値に設定しています。 コマンドを使ってリソース作成~デプロイ それでは、実際にBicepテンプレートを使用してリソースの作成、GitHub CLIを使用して environment の設定、最終的にGitHub Acitonsを起動してデプロイを行っていきます。 再度シナリオの確認です。今回のシナリオは「resouceGroupスコープで実行し、ローカルから既存のリソースグループに対してBicepを使用してStatic Web Appsを管理し、GitHub Acitonsでデプロイに必要なキーをGitHub CLIを使用して設定」です。作成するリソースやGitHub CLIで設定する値としては以下の情報を設定します。 設定項目 値 リソースグループ bicep-test-group SWA名 bicep-actions-deploy-swa ロケーション eastasia environment名 production ターゲットブランチ名 main APP_LOCATION . OUTPUT_LOCATIOM out あとは、ここに記載することができませんが以下の値が必要になります。 GitHub OWNER:ユーザー名もしくはOrganization名 GitHub REPO:リポジトリ名 こちらの値はそれぞれ自分の環境に合わせて設定してください。 Bashで表現すると以下のような変数になります。汎用性を高めるために、Bashの変数名でコマンドの解説を進めていきます。SWA名に関してはパラメーターファイルで設定済みですが、削除にも使用することも考えて、定義しておきます。 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" $SWA_NAME="bicep-actions-deploy-swa" $GITHUB_ENVIRONMENT="production" $BRANCH="main" $APP_LOCATION="/out" $GITHUB_OWNER="xxxxxxx" $GITHUB_REPO="xxxxx" GitHubのリポジトリはすでに構築済みであると仮定します。GitHub CLIでenvironmentを設定するためには事前にenvironmentを作成しておく必要があります。productionという名前でenvironmentを作成してください。設定は 公式のドキュメントを参考に設定 してください。 それではコマンドに入っていきます。 リソースグループの作成 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" # リソースグループの確認 az group show --name $RESOURCE_GROUP_NAME # リソースの作成 az group create --name $RESOURCE_GROUP_NAME --location $LOCATION az group create コマンドでは、 --name オプションで名前を --location オプションでリージョンを指定する必要があります。 Bicepテンプレートの検証とデプロイ Bicepテンプレートをデプロイする前に、構文エラーや設定ミスがないかを検証することが重要です。 az deployment group validate コマンドを使用することで、実際にリソースを作成する前にテンプレートの妥当性をチェックできます。 # 検証 az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json # デプロイ az deployment group create \ --resource-group "$RESOURCE_GROUP" \ --name "$DEPLOYMENT_NAME" \ --template-file "$BICEP_TEMPLATE" \ --parameters "@$BICEP_PARAMS" 検証が完了したら、 az deployment group create コマンドで実際にBicepテンプレートをデプロイします。 --name オプションでデプロイ名を指定でき、後からAzure Portalでデプロイ履歴を確認する際に便利です。 Azure CLIでは、パラメーターファイルを指定する際に @ファイルパス という記法を使用します。この記法により、ファイルの内容がパラメータとして読み込まれます。 # ✅ 正しい - ファイルの内容を読み込む --parameters "@parameters.local.json" # ❌ 間違い - ファイル名がそのまま渡される --parameters "parameters.local.json" デプロイトークンの取得 SWA CLIでデプロイするためには、Static Web Appsのデプロイトークンが必要です。このトークンは、作成されたStatic Web Appsリソースから az staticwebapp secrets list コマンドで取得できます。 az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv このコマンドでは、 --query "properties.apiKey" オプションでJSON出力からAPIキー(デプロイトークン)のみを抽出し、 -o tsv オプションでタブ区切り形式として出力することで、余計な引用符を除去しています。取得したトークンは環境変数に保存して、次のGitHub CLIを介した設定で使用します。 GitHub CLIを使ってenvironmentを設定する GitHub CLIを使用してSecretとVariableを設定する、 公式ドキュメントはこちら にまとまっています。 gh auth login # コマンド紹介 # gh secret set <ここに登録名> --env <環境名> --repo $GITHUB_OWNER/$GITHUB_REPO --body <設定したい値> # gh variable set <ここに登録名> --env <環境名> --repo $GITHUB_OWNER/$GITHUB_REPO --body <設定したい値> gh variable set APP_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO --body $APP_LOCATION gh variable set OUTPUT_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO --body $OUTPUT_LOCATION gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --env production --repo $GITHUB_OWNER/$GITHUB_REPO --body $DEPLOY_TOKEN ここで注意したいのは、SecretもVariableも空文字での登録を行うことはできません。 GitHub Actions実行 ここまで来たら必要な設定はすべて完了していると思います。手動でも検証のために out > index.html に変更を加えてmainにpushすれば、GitHub Actionsがトリガーされてデプロイされるかと思います。 以下の画面が表示されれば成功です。 コラム:開発効率改善~処理をbashに集約~ さてさて!コマンドの検証は完了しました。ですが、今回のコマンドはさすがに一個ずつ入力するのはさすがに大変です。というわけで、第二回で作成したbashファイルを拡張してスクリプトとしてまとめておきたいと思います。 ベース作成も改修もAIにやってもらったので、本当にbashファイルを書くのはAIが強いですね。要望伝えて.envファイルを作ったらポンと作ってくれました。 作成するファイル 作成するファイルとしては、 .env と deploy.sh になります。 環境変数: .env こちらのファイルでは、azコマンドでは埋め込んでいた値を変数として切り出しています。これによって .env を変更するだけで、異なるリソースを作成することができます。 # Azure Static Web Apps デプロイ設定ファイル # Azure設定 RESOURCE_GROUP_NAME="************" LOCATION="eastasia" # GitHub設定 GITHUB_OWNER="************" # GitHubユーザー名またはOrganization名 GITHUB_REPO="************" # リポジトリ名 GITHUB_ENVIRONMENT="************" # GitHub Environment名 # プロジェクト設定(parameters.jsonと合わせる) PROJECT_NAME="************" ENVIRONMENT="************" REPOSITORY_URL="************" BRANCH="main" # デプロイ設定 APP_LOCATION="/out" API_LOCATION="" OUTPUT_LOCATION="" parameter.json と値を合わせる運用にすることで、設定項目が異なることも防ぐことができます。二重管理になってしまいますが、 parameter.json はBicep用、 .env はbashファイル用なので責務としては分散されていると思っています。 自動化スクリプト: infra > scripts > deploy.sh いきなりbashファイルを読むのは大変なので、抽象化してシーケンス図に起こします。bashファイルのベースとしては、先ほどのセクションで解説したコマンドになります。 #!/bin/bash # Azure Static Web Apps GitHub Actions用デプロイスクリプト(設定ファイル対応版) set -e # スクリプトのディレクトリを取得 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 設定ファイルの読み込み CONFIG_FILE="$(git rev-parse --show-toplevel)/.env.deploy" if [ -f "$CONFIG_FILE" ]; then echo "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else echo "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" echo " .env.deploy.example をコピーして設定してください" exit 1 fi # 必須設定の確認 if [ -z "$GITHUB_OWNER" ] || [ -z "$GITHUB_REPO" ]; then echo "❌ GITHUB_OWNER と GITHUB_REPO を設定してください" exit 1 fi # デプロイ名生成 DEPLOYMENT_NAME="swa-deployment-$(date +%Y%m%d-%H%M%S)" echo "🚀 Azure Static Web Apps 本番環境デプロイ(GitHub Secret自動登録付き)" echo "==================================================================" # 設定値表示 echo "📋 設定確認:" echo " Azure Resource Group: $RESOURCE_GROUP_NAME" echo " Location: $LOCATION" echo " GitHub Owner: $GITHUB_OWNER" echo " GitHub Repo: $GITHUB_REPO" echo " GitHub Environment: $GITHUB_ENVIRONMENT" echo "" # 0. 前提条件確認 echo "📋 前提条件確認..." # jq インストール確認 if ! command -v jq &> /dev/null; then echo "❌ jq がインストールされていません" echo " Ubuntu/Debian: sudo apt-get install jq" echo " macOS: brew install jq" exit 1 fi # GitHub CLI インストール確認 if ! command -v gh &> /dev/null; then echo "❌ GitHub CLI (gh) がインストールされていません" echo " インストール方法: https://cli.github.com/" exit 1 fi # GitHub CLI 認証確認 echo "🔐 GitHub CLI認証状態確認..." if ! gh auth status &> /dev/null; then echo "❌ GitHub CLIにログインしてください" echo " 実行コマンド: gh auth login" exit 1 else echo "✅ GitHub CLI認証済み" CURRENT_USER=$(gh api user --jq '.login') echo " 認証ユーザー: $CURRENT_USER" fi # リポジトリアクセス確認 echo "🔍 GitHubリポジトリアクセス確認..." if ! gh repo view $GITHUB_OWNER/$GITHUB_REPO &> /dev/null; then echo "❌ リポジトリにアクセスできません: $GITHUB_OWNER/$GITHUB_REPO" echo " リポジトリ名とアクセス権限を確認してください" exit 1 else echo "✅ リポジトリアクセス確認済み" fi # 1. Azure CLI ログイン確認 echo "" echo "📋 Azure CLI認証状態確認..." if ! az account show &> /dev/null; then echo "❌ Azure CLIにログインしてください" az login else echo "✅ Azure CLI認証済み" az account show --query "{Name:name, SubscriptionId:id}" -o table fi # 2. リソースグループ作成(存在しない場合) echo "" echo "📦 本番用リソースグループ確認・作成..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then echo "🔧 リソースグループ作成中: $RESOURCE_GROUP_NAME" az group create --name $RESOURCE_GROUP_NAME --location $LOCATION echo "✅ リソースグループ作成完了" else echo "✅ リソースグループ既存: $RESOURCE_GROUP_NAME" fi # 3. Bicepファイル存在確認 BICEP_MAIN="infra/bicep/main.bicep" BICEP_PARAMS="infra/bicep/parameters.json" if [ ! -f "$BICEP_MAIN" ]; then echo "❌ Bicepテンプレートが見つかりません: $BICEP_MAIN" exit 1 fi if [ ! -f "$BICEP_PARAMS" ]; then echo "❌ Bicepパラメータファイルが見つかりません: $BICEP_PARAMS" exit 1 fi # 4. Bicepテンプレートの検証 echo "" echo "🔍 GitHub Actions用Bicepテンプレート検証..." az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file $BICEP_MAIN \ --parameters @$BICEP_PARAMS echo "✅ Bicepテンプレート検証成功" # 5. Bicepデプロイ実行 echo "" echo "🚀 GitHub Actions連携用Static Web Appsデプロイ開始..." echo " デプロイ名: $DEPLOYMENT_NAME" DEPLOYMENT_OUTPUT=$(az deployment group create \ --resource-group $RESOURCE_GROUP_NAME \ --name $DEPLOYMENT_NAME \ --template-file $BICEP_MAIN \ --parameters @$BICEP_PARAMS \ --query "properties.outputs" -o json) echo "✅ Bicepデプロイ完了" # 6. デプロイ結果の取得 echo "" echo "📊 デプロイ結果取得中..." SWA_NAME=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppName.value') SWA_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppUrl.value') RESOURCE_GROUP=$(echo $DEPLOYMENT_OUTPUT | jq -r '.resourceGroupName.value') SUBSCRIPTION_ID=$(echo $DEPLOYMENT_OUTPUT | jq -r '.subscriptionId.value') # デプロイトークン取得(リトライ機能付き) echo "🔑 デプロイトークン取得中..." RETRY_COUNT=0 MAX_RETRIES=5 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do DEPLOYMENT_TOKEN=$(az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv 2>/dev/null) if [ -n "$DEPLOYMENT_TOKEN" ] && [ "$DEPLOYMENT_TOKEN" != "null" ]; then echo "✅ デプロイトークン取得成功" break fi RETRY_COUNT=$((RETRY_COUNT + 1)) echo " リトライ中... ($RETRY_COUNT/$MAX_RETRIES)" sleep 10 done if [ -z "$DEPLOYMENT_TOKEN" ] || [ "$DEPLOYMENT_TOKEN" = "null" ]; then echo "❌ デプロイトークンの取得に失敗しました" exit 1 fi # 7. GitHub Secretsの登録 echo "" echo "🔐 GitHub Secrets & Variables 登録中... " # 条件分岐で空文字をスキップ if [ -n "$APP_LOCATION" ]; then echo $APP_LOCATION | gh variable set APP_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO echo "✅ APP_LOCATION set to: $APP_LOCATION" else echo "⏭ APP_LOCATION is empty, skipping..." fi if [ -n "$OUTPUT_LOCATION" ]; then echo $OUTPUT_LOCATION | gh variable set OUTPUT_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO echo "✅ OUTPUT_LOCATION set to: $OUTPUT_LOCATION" else echo "⏭ OUTPUT_LOCATION is empty, skipping..." fi if [ -n "$API_LOCATION" ]; then echo $API_LOCATION | gh variable set API_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO echo "✅ API_LOCATION set to: $API_LOCATION" else echo "⏭ API_LOCATION is empty, skipping..." fi echo $DEPLOYMENT_TOKEN | gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --env production --repo $GITHUB_OWNER/$GITHUB_REPO # 10. 登録されたSecretsの確認 echo "" echo "🔍 登録されたSecretsの確認..." echo "Environment Secrets ($GITHUB_ENVIRONMENT):" gh secret list --env $GITHUB_ENVIRONMENT --repo $GITHUB_OWNER/$GITHUB_REPO 2>/dev/null | grep -E "(AZURE_STATIC_WEB_APPS_API_TOKEN)" || echo " ※ Secret一覧の表示には管理者権限が必要です" gh variable list --env $GITHUB_ENVIRONMENT --repo $GITHUB_OWNER/$GITHUB_REPO 2>/dev/null | grep -E "(APP_LOCATION|OUTPUT_LOCATION|API_LOCATION)" || echo " ※ Variable一覧の表示には管理者権限が必要です" # 11. デプロイ結果の表示 echo "" echo "📊 本番環境デプロイ結果:" echo "======================" echo "🌐 Static Web App名: $SWA_NAME" echo "🔗 URL: $SWA_URL" echo "📁 リソースグループ: $RESOURCE_GROUP" echo "🆔 サブスクリプション: $SUBSCRIPTION_ID" echo "🔐 GitHub Environment: $GITHUB_ENVIRONMENT" echo "🏷 デプロイ名: $DEPLOYMENT_NAME" # 12. 次のステップ案内 echo "" echo "🎉 本番環境デプロイ完了!" echo "" echo "📝 次のステップ:" echo " 1. ブラウザで $SWA_URL にアクセス" echo " 2. GitHub Actions ワークフローファイルを作成" echo " 3. 自動デプロイをテスト" echo " 4. Environment保護ルールを設定(推奨)" echo "" echo "🔧 便利なリンク:" echo " - Static Web App: https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/staticSites/$SWA_NAME" echo " - GitHub Environment設定: https://github.com/$GITHUB_OWNER/$GITHUB_REPO/settings/environments" echo " - GitHub Actions: https://github.com/$GITHUB_OWNER/$GITHUB_REPO/actions" echo "" echo "💻 手動デプロイ用コマンド(トラブルシューティング時):" echo " cd out && npx @azure/static-web-apps-cli deploy --deployment-token [TOKEN]" こちらを実行することで、リソースグループの作成からenvironmentの設定まで自動的に行うことができます。 機密情報を守る砦:.gitignore .envファイルは流出しても影響は少ないかもですが、GitHubに上げることも考慮して機密情報をPushしないように.gitignoreの設定を追加します。 deploy.info.*.json .env .env.* !.env.*.sample まとめ 今回の三部作の最終回では、BicepとGitHub Actionsを組み合わせた実践的なCI/CDパイプラインの構築について詳しく解説してきました。 今回実現できたこと: GitHub Actions用のBicepテンプレートとパラメータファイルの作成 GitHub CLIを使った自動化スクリプトによるSecretsとVariablesの設定 リソース作成からデプロイまでの完全自動化 本番環境を想定したワークフローファイルの構築 特に印象的だったのは、GitHub CLIを活用した環境設定の自動化ですね。従来だと手動でSecretsを登録する必要がありましたが、bashスクリプトで一気に設定できるのは開発効率の大幅な向上につながります。 技術的なポイント: skipGithubActionWorkflowGeneration: true による手動ワークフロー管理 environmentを使った本番環境用のSecrets/Variables管理 デプロイトークンの自動取得とリトライ機能 パラメータファイルによる設定の分離とセキュリティ向上 bashスクリプトの実装では、エラーハンドリングから前提条件の確認、結果の可視化まで、本番運用を意識した丁寧な作り込みが印象的でした。特にシーケンス図で処理フローを可視化していたのは、複雑な自動化処理を理解しやすくする工夫として素晴らしいですね。 三部作を通じて学べたこと: DevContainer環境での効率的な開発環境構築 BicepによるInfrastructure as Codeの実践 GitHub Actionsとの連携による完全自動化 この知識を活かして、皆さんもBicep + GitHub ActionsでのStatic Web Apps運用にチャレンジしてみてください!Infrastructure as CodeとCI/CDの組み合わせで、より安全で効率的な開発ライフサイクルを実現できるはずです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Actions自動化実践:Azure SWA×Bicep CI/CDパイプライン構築 first appeared on SIOS Tech. Lab .
アバター
ご挨拶 ども!新しいことを学ぶのにAIを使うようになって勉強がはかどっている龍ちゃんです。検証したことはジャンジャンブログ化していきます。旅館にこもって執筆だけをしている時間がそのうち来るかもしれません。ネット回線がないと作業できないのが難点なんですよね。 基礎から学ぶAzure Static Web Apps × Bicep 入門 「Azure Static Web Apps を Bicep(Infrastructure as Code)で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する」というテーマで3部構成の「ローカル開発でのBicep + SWA CLIデプロイ実践編」になります。各回のコンテンツの内容としては以下になります。 シリーズのまとめ記事はこちら になります。 Azure Static Web Apps + Bicep環境構築編 ローカル開発でのBicep + SWA CLIデプロイ実践編 ←  今回はここ Bicep+GitHub Actions連携デプロイ編 この回での目標は以下となります。 ローカル開発用のBicepテンプレート作成 SWA CLIを使った手動デプロイの実行 開発サイクルでの迅速なデプロイフローの確立 それでは、ローカルからのBicepとSWAへのデプロイを始めましょう。 前回の振り返り 前回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門 #1」として、DevContainerを使った統一開発環境の構築を行いました。 DevContainerによる統一開発環境の構築では、Azure CLI、GitHub CLI、SWA CLIの3つのツールを使える状態にして、チーム全体で同じ開発環境を共有できるようにしました。特に、Azure CLIの認証情報をマウントすることで、コンテナを再起動するたびに認証し直す手間を省けるようになりました。 環境構築完了後は実際にAzure CLIとSWA CLIを使って、Static Web Appsの作成からデプロイ、削除まで一通りコマンドラインで体験しました。デプロイトークンの取得とSWA CLIを使ったデプロイは、Infrastructure as Codeでの管理においても重要な操作となります。 今回の記事は前回作成した環境をもとに進めていきます。もし環境が手元にない方は下の記事を先に購読ください。 詳細については、 前回の記事 をご参照ください。 ローカルからBicepとSWA CLIを使ったデプロイ Bicep × SWA CLIで環境を作成するシナリオ まずは、シナリオの整理から始めようと思います。Bicepでは ターゲットスコープを指定 することができ、実行時に影響を及ぼす範囲を限定することができます。 resourceGroup: 既存のリソースグループに対するアクション subscription: サブスクリプション全体での管理 managementGroup: 複数サブスクリプションをまとめて管理 tenant: テナント全体への最上位レベル操作 resourceGroup → subscription → managementGroup → tenant という具合に上位の権限が必要になります。一般的な開発者権限の場合は、resouceGroupスコープしか扱うことができないかと思います。 以上を踏まえて、今回のシナリオとしては「resouceGroupスコープで実行し、ローカルから既存のリソースグループに対して、Bicepを使用してStatic Web Appsを管理」で解説を進めていきます。 ディレクトリ構成 今回、検証に使用するプロジェクトのディレクトリ構成になります。 . ├── .devcontainer/ │ └── devcontainer.json ├── infra/ │ ├── bicep/ │ │ ├── main.local.Bicep # ローカル開発用Bicep ⭐ │ │ └── parameters.local.json # ローカル開発用パラメータ ⭐ │ └── scripts/ │ └── deploy.local.sh # ローカル開発用デプロイスクリプト ⭐ ├── out/ │ └── index.html ├── .env.local # スクリプト用環境変数 ├── .gitignore # 機密情報を守る砦 ├── swa-cli.config.json └── README.md # コマンドをメモするのに使ってね! Bicepに関係するファイルとしては、以下の二つとなります。 parameters.lcoal.json:ローカル開発用パラメータファイル main.local.bicep:ローカル開発用Bicepテンプレート パラメータファイルで値を管理する infra > bicep > parameters.local.json こちらは、Bicepテンプレートへ値を渡すパラメーターファイルとなります。 { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "projectName": { "value": "Bicep-local-test" }, "environment": { "value": "dev" }, "location": { "value": "East Asia" } } } 設定項目を切り出しておくことで、パラメーターファイルを作成するだけで別の環境を作成することができます。また、リソース管理の観点からも変更差分が追跡しやすくなるため、使用する値はパラメーターファイルとして管理することが必須となります。特に機密情報(パスワードやAPI キーなど)をBicepテンプレート本体から分離できるセキュリティ面でのメリットも大きく、GitHubなどへの誤った機密情報のコミットを防止できます。 Bicepテンプレート infra > Bicep > main.local.bicep Bicepのテンプレートは、以下の3つのパーツで構成されています。 パラメータ受け取り部分 Static Web Apps定義部分 出力用設定 @description('プロジェクト名(リソース名のプレフィックスとして使用)') param projectName string = "Bicep-local-deploy-swa" @description('デプロイ環境 (dev, staging, prod)') param environment string = 'dev' @description('デプロイ先のリージョン') param location string = resourceGroup().location // ローカル開発用: シンプルなStatic Web Apps作成 // GitHubリポジトリとの連携は行わず、手動デプロイ専用 resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: '${projectName}-swa-${environment}-local' location: location sku: { name: 'Free' tier: 'Free' } properties: { // ローカル開発用: 最小構成 buildProperties: { appLocation: '/out' apiLocation: '' outputLocation: '' } } tags: { Environment: environment Purpose: 'LocalDevelopment' CreatedBy: 'Local-Bicep' } } // 出力値: ローカル開発で必要な情報のみ output staticWebAppName string = staticWebApp.name output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}' output staticWebAppId string = staticWebApp.id output managementUrl string = 'https://portal.azure.com/#@/resource${staticWebApp.id}' パラメーター受け取り部分は、先ほど設定したパラメータの受け取りと初期値の設定を行います。 Static Web Apps定義部分は、SWAの構築に必要な情報が記載されています。必須のパラメータとしては、以下になります。(az コマンドより設定項目が増えている) name :SWAの名前(Azure内でユニークな名前の必要あり) location :デプロイリージョン(SWAの場合 East US 2 , West Europe , Central US , East Asia , West US 2 ) sku :価格プラン name-tierは一致させる その他の設定項目としては、公式のドキュメントにまとまっています 。今回は properties > buildProperties と tags を設定しています。 buildProperties は名前の通りですが、今回であれば swa-cli.config.json と一致した値になる想定です。将来的にNode.jsで構築した静的サイトや組み込みバックエンドなどを構築する際に重要になるので、設定しています。 tags はAzure Protalやazコマンドでの検索を容易にしてくれます。設定しなくてもよいですが、検証中は助かる設定項目になります。将来的に複数のSWAを運用する際に、設定することで管理が容易になります。 コマンドを使ってデプロイ~削除 それでは、実際にBicepテンプレートを使用してリソースの作成とSWA CLIを活用したデプロイを行い、最終的にリソースを削除していきます。 再度シナリオの確認です。今回のシナリオは「resouceGroupスコープで実行し、既存のリソースグループに対して、Bicepを使用してStatic Web Appsを管理」です。作成するリソースとしては、以下のような命名とロケーションに設定します。 設定項目 値 リソースグループ bicep-test-group SWA名 bicep-local-deploy-swa ロケーション eastasia Bashで表現すると以下のような変数になります。汎用性を高めるために、Bashの変数名でコマンドの解説を進めていきます。SWA名に関してはパラメーターファイルで設定済みですが、削除にも使用することも考えて、定義しておきます。 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" $SWA_NAME="bicep-local-deploy-swa" リソースグループの作成 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" # リソースグループの確認 az group show --name $RESOURCE_GROUP_NAME # リソースの作成 az group create --name $RESOURCE_GROUP_NAME --location $LOCATION az group create コマンドでは、 --name オプションで名前を --location オプションでリージョンを指定する必要があります。 Bicepテンプレートの検証とデプロイ Bicepテンプレートをデプロイする前に、構文エラーや設定ミスがないかを検証することが重要です。 az deployment group validate コマンドを使用することで、実際にリソースを作成する前にテンプレートの妥当性をチェックできます。 # 検証 az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json # デプロイ az deployment group create \ --resource-group "$RESOURCE_GROUP" \ --name "$DEPLOYMENT_NAME" \ --template-file "$BICEP_TEMPLATE" \ --parameters "@$BICEP_PARAMS" 検証が完了したら、 az deployment group create コマンドで実際にBicepテンプレートをデプロイします。 --name オプションでデプロイ名を指定でき、後からAzure Portalでデプロイ履歴を確認する際に便利です。 Azure CLIでは、パラメーターファイルを指定する際に @ファイルパス という記法を使用します。この記法により、ファイルの内容がパラメータとして読み込まれます。 # ✅ 正しい - ファイルの内容を読み込む --parameters "@parameters.local.json" # ❌ 間違い - ファイル名がそのまま渡される --parameters "parameters.local.json" デプロイトークンの取得 SWA CLIでデプロイするためには、Static Web Appsのデプロイトークンが必要です。このトークンは、作成されたStatic Web Appsリソースから az staticwebapp secrets list コマンドで取得できます。 az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv このコマンドでは、 --query "properties.apiKey" オプションでJSON出力からAPIキー(デプロイトークン)のみを抽出し、 -o tsv オプションでタブ区切り形式として出力することで、余計な引用符を除去しています。取得したトークンは環境変数に保存して、次のSWA CLIデプロイで使用します。 SWA CLIデプロイ swa-cli.config.jsonを設定していれば、設定項目に従ってデプロイされます。今回であれば、以下のような設定をしています。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "frontend-swr": { "appLocation": "out", "outputLocation": "." } } } -env production オプションで本番環境としてデプロイを指定し、 -deployment-token で先ほど取得したトークンを使用します。 -verbose オプションを追加することで、詳細なログが出力され、デプロイの進行状況を確認できるため、トラブルシューティングの際に役立ちます。 swa deploy --env production --deployment-token "$DEPLOYMENT_TOKEN" --verbose デプロイ結果の確認 デプロイが完了したら、Static Web Appsの情報を確認してみましょう。 az staticwebapp show コマンドを使用することで、デプロイされたアプリの詳細情報を取得できます。 az staticwebapp show --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "{name:name,url:defaultHostname,status:repositoryUrl}" -o table リソースの削除 検証が完了したら、不要な課金を避けるためにリソースを削除しましょう。削除方法は2つあります。個別にStatic Web Appsを削除する方法と、リソースグループごと削除する方法です。 # リソースグループごと削除 az group delete --name $RESOURCE_GROUP_NAME --yes --no-wait # Static Web Appsを指定して削除 az staticwebapp delete --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --yes az staticwebapp delete コマンドではStatic Web Appsリソースのみを削除しますが、 az group delete コマンドを使用することで、リソースグループとその中のすべてのリソースを一括で削除できます。 --yes オプションで確認プロンプトをスキップし、 --no-wait オプションで削除処理の完了を待たずにコマンドを終了します。削除処理はバックグラウンドで実行されるため、時間のかかる削除処理でも待機する必要がありません。リソースグループごと削除する方が確実で、削除し忘れを防げるのでおすすめです。 コラム:開発効率改善~処理をbashに集約~ さて、コマンドの検証は完了しました。ですが、このままではBicepテンプレートを使っているというよりazコマンドを一個ずつ入力しているだけになっていますね。 というわけで、AIに手伝ってもらってbashスクリプトにまとめました。 作成するファイル 作成するファイルとしては、 .env.local と deploy.local.sh になります。 環境変数: .env.local こちらのファイルでは、azコマンドでは埋め込んでいた値を変数として切り出しています。これによって .env.local を変更するだけで、異なるリソースを作成することができます。 # Azure Static Web Apps デプロイ設定ファイル # Azure設定 RESOURCE_GROUP_NAME="ryu-swa-bicep-test" LOCATION="eastasia" 自動化スクリプト: infra > scripts > deploy.local.sh いきなりbashを読むのは大変なので、一度シーケンス図に起こしておきます。 全体のステップとしては4ステップで構成されています。セクションごとに見ていくと、コマンド自体は「コマンドを使ってデプロイ~削除」で使用したものを使っているので、紐解けるかと思います。 #!/bin/bash # Azure Static Web Apps ローカル開発用デプロイスクリプト set -e # 色付きログ用の関数 log_info() { echo -e "\033[1;34m$1\033[0m"; } log_success() { echo -e "\033[1;32m$1\033[0m"; } log_warning() { echo -e "\033[1;33m$1\033[0m"; } log_error() { echo -e "\033[1;31m$1\033[0m"; } # .gitディレクトリがあるルートディレクトリを取得 CONFIG_FILE="$(git rev-parse --show-toplevel)/.env.local" if [ -f "$CONFIG_FILE" ]; then echo "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else echo "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" echo " .env.deploy.example をコピーして設定してください" exit 1 fi # 設定値 DEPLOYMENT_NAME="swa-local-deployment-$(date +%Y%m%d-%H%M%S)" log_info "🧪 Azure Static Web Apps ローカル開発環境デプロイ" log_info "==============================================" # 1. 前提条件のチェック log_info "📋 前提条件確認中..." # Azure CLI の確認 if ! command -v az &> /dev/null; then log_error "❌ Azure CLIがインストールされていません" exit 1 fi # SWA CLI の確認(グローバルインストール版) if ! command -v swa &> /dev/null; then log_error "❌ SWA CLIがグローバルインストールされていません" log_info "💡 インストールコマンド: npm install -g @azure/static-web-apps-cli" exit 1 fi log_success "✅ 前提条件確認完了" # 2. Azure CLI ログイン確認 log_info "" log_info "🔐 Azure CLI認証状態確認..." if ! az account show &> /dev/null; then log_warning "❌ Azure CLIにログインしてください" az login else log_success "✅ Azure CLI認証済み" az account show --query "{Name:name, SubscriptionId:id}" -o table fi # 3. リソースグループ作成(存在しない場合) log_info "" log_info "📦 ローカル開発用リソースグループ確認・作成..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then log_info "🔧 リソースグループ作成中: $RESOURCE_GROUP_NAME" az group create --name $RESOURCE_GROUP_NAME --location $LOCATION log_success "✅ リソースグループ作成完了" else log_success "✅ リソースグループ既存: $RESOURCE_GROUP_NAME" fi # 4. Bicepファイルの存在確認 log_info "" log_info "📁 Bicepテンプレートファイル確認..." if [ ! -f "infra/bicep/main.local.bicep" ]; then log_error "❌ Bicepテンプレートファイルが見つかりません: infra/bicep/main.local.bicep" exit 1 fi if [ ! -f "infra/bicep/parameters.local.json" ]; then log_error "❌ Bicepパラメータファイルが見つかりません: infra/bicep/parameters.local.json" exit 1 fi # 5. Bicepテンプレートの検証 log_info "" log_info "🔍 ローカル開発用Bicepテンプレート検証..." if az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json &> /dev/null; then log_success "✅ Bicepテンプレート検証成功" else log_error "❌ Bicepテンプレート検証失敗" exit 1 fi # 6. Bicepデプロイ実行 log_info "" log_info "🚀 ローカル開発用Static Web Appsデプロイ開始..." DEPLOYMENT_OUTPUT=$(az deployment group create \ --resource-group $RESOURCE_GROUP_NAME \ --name $DEPLOYMENT_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json \ --query "properties.outputs" -o json) if [ $? -eq 0 ]; then log_success "✅ Bicepデプロイ完了" else log_error "❌ Bicepデプロイ失敗" exit 1 fi # 7. デプロイ結果の表示 log_info "" log_info "📊 ローカル開発環境デプロイ結果:" log_info "=============================" # JSON出力の検証 if ! echo "$DEPLOYMENT_OUTPUT" | jq . &> /dev/null; then log_error "❌ デプロイ結果の解析に失敗しました" echo "Raw output: $DEPLOYMENT_OUTPUT" exit 1 fi SWA_NAME=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppName.value // "N/A"') SWA_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppUrl.value // "N/A"') MANAGEMENT_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.managementUrl.value // "N/A"') # az staticwebapp deployment token コマンドでデプロイトークンを取得 DEPLOYMENT_TOKEN=$(az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv) echo "🌐 Static Web App名: $SWA_NAME" echo "🔗 アプリURL: $SWA_URL" echo "🔑 デプロイトークン: $DEPLOYMENT_TOKEN" echo "⚙ 管理画面: $MANAGEMENT_URL" # 8. parameters.json ファイルの更新 log_info "" log_info "📝 parameters.json ファイル更新中..." PARAMS_FILE="infra/bicep/parameters.local.json" # デプロイ情報を記録用のJSONファイルに保存 DEPLOY_INFO_FILE="infra/bicep/deploy.info.local.json" { echo "{" echo " \"deploymentDate\": \"$(date -Iseconds)\"," echo " \"staticWebAppName\": \"$SWA_NAME\"," echo " \"staticWebAppUrl\": \"$SWA_URL\"," echo " \"deploymentToken\": \"$DEPLOYMENT_TOKEN\"," echo " \"managementUrl\": \"$MANAGEMENT_URL\"," echo " \"resourceGroupName\": \"$RESOURCE_GROUP_NAME\"," echo " \"deploymentName\": \"$DEPLOYMENT_NAME\"" echo "}" } > "$DEPLOY_INFO_FILE" log_success "✅ デプロイ情報をdeploy.info.local.jsonに保存しました" # 9. ローカルデプロイ実行 log_info "" log_info "📦 アプリケーションをSWAにデプロイ中..." # グローバルインストール版のSWA CLIを使用 if swa deploy --env production --deployment-token "$DEPLOYMENT_TOKEN" --verbose; then log_success "✅ アプリケーションデプロイ完了" else log_error "❌ アプリケーションデプロイ失敗" cd .. exit 1 fi # cd .. # 10. 便利なコマンドの表示 log_info "" log_info "💻 便利なコマンド:" log_info "=================" echo "# アプリの再デプロイ" echo "swa deploy --deployment-token $DEPLOYMENT_TOKEN" echo "" echo "# ローカル開発サーバー起動" echo "swa start" echo "" echo "# SWA CLI バージョン確認" echo "swa --version" echo "" echo "# デプロイ情報確認" echo "cat infra/bicep/deploy-info-local.json | jq" echo "" echo "# ログの確認" echo "az staticwebapp logs show --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME" # 11. 完了メッセージ log_info "" log_success "🎉 ローカル開発環境デプロイ完了!" log_info "📝 次のステップ:" echo " 1. ブラウザで $SWA_URL にアクセス" echo " 2. アプリが正常に動作することを確認" echo " 3. 必要に応じて上記のコマンドでローカル開発サーバーを起動" echo " 4. 開発が完了したら本番用のGitHub Actionsワークフローをテスト" log_info "" log_info "💡 本番環境へのデプロイ:" echo " ./infra/scripts/deploy.sh を使用してください" log_info "" log_warning "⚠ 重要: デプロイトークンは機密情報です。共有しないでください!" こちらを実行することで、リソースグループの作成からSWAのデプロイまで自動的に行うことができます。また、デプロイした情報は infra > bicep ディレクトリに deploy.info.local.json としてまとめてあります。デプロイするたびにこちらが、更新されるためこちらを確認することでデプロイ済みの情報をすぐ確認することができます。 !!ここで注意点です。 deploy.info.local.json は機密情報に当たるためGitHubなどにデプロイして公開しないようにしましょう。デプロイトークンに関しては、1か月に一度は更新が望ましいです。もし流出した場合はサイトの表示が乗っ取られる可能性があります!取り扱いには十分注意しましょう。 機密情報を守る砦:.gitignore 先ほど、機密情報の塊の話をしたのでGitHubに上げることも考慮して機密情報をPushしないように.gitignoreの設定を追加します。 deploy.info.*.json .env .env.* !.env.*.sample 龍ちゃん さて~こちらのスクリプトは8割Claudeさんが作ったものなので、Claudeさんに自画自賛しながら解説してもらおうと思います。 ここから先は全部AI書いてもらおうと思います。 AI執筆:自動化スクリプトで楽々デプロイ infra > scripts > deploy.local.sh 手動でコマンドを一つずつ実行するのも勉強になりますが、毎回同じ作業を繰り返すのは正直面倒ですよね。そこで、先ほどの手動作業をすべて自動化したbashスクリプトを用意しました。 このスクリプトは、前のセクションで手動実行した一連の作業を完全に自動化し、さらに実用的な機能を多数追加したものです。実際に使ってみると「これは便利!」と感じる工夫がたくさん詰まっています。 親切すぎるエラーハンドリング このスクリプトのエラーハンドリングは、単にエラーを検出するだけでなく、解決方法まで提示してくれる親切設計になっています。 # SWA CLI の確認 if ! command -v swa &> /dev/null; then log_error "❌ SWA CLIがグローバルインストールされていません" log_info "💡 インストールコマンド: npm install -g @azure/static-web-apps-cli" exit 1 fi # デプロイディレクトリの確認 if [ ! -d "$DEPLOY_DIR" ]; then log_error "❌ デプロイディレクトリ '$DEPLOY_DIR' が見つかりません" log_info "💡 Next.jsアプリをビルドしてください: npm run build" exit 1 fi 初心者でも迷わず問題を解決できるよう、具体的な解決策を含めたメッセージが表示されます。これにより、チーム全体での環境構築がスムーズに進められます。 視覚的に分かりやすいログシステム 色付きログ機能により、長いログの中でも重要な情報が一目で識別できます。 log_info() { echo -e "\\033[1;34m$1\\033[0m"; } # 青色(情報) log_success() { echo -e "\\033[1;32m$1\\033[0m"; } # 緑色(成功) log_warning() { echo -e "\\033[1;33m$1\\033[0m"; } # 黄色(警告) log_error() { echo -e "\\033[1;31m$1\\033[0m"; } # 赤色(エラー) ターミナルでの作業が多い開発環境では、こうした視覚的な工夫が作業効率を大幅に向上させます。特に複数人でのペアプログラミングや画面共有時に威力を発揮します。 インテリジェントなデプロイ情報管理 デプロイ完了後、重要な情報を deploy.info.local.json に自動保存する仕組みが組み込まれています。 { "deploymentDate": "2025-01-15T10:30:45+09:00", "staticWebAppName": "bicep-local-test-swa-dev-local", "staticWebAppUrl": "https://xxx.azurestaticapps.net", "deploymentToken": "xxxxxxxxxxxxxxxx", "managementUrl": "https://portal.azure.com/#@/resource/...", "resourceGroupName": "Bicep-test-group", "deploymentName": "swa-local-deployment-20250115-1030" } この情報は後のデバッグやトラブルシューティングで非常に重要になります。「あのデプロイトークンどこだっけ?」「URLは何だったかな?」といった疑問を即座に解決できる仕組みです。 実践的な便利機能 スクリプト終了時に表示される便利コマンド集も秀逸です。 echo "# アプリの再デプロイ" echo "swa deploy --deployment-token $DEPLOYMENT_TOKEN" echo "" echo "# ローカル開発サーバー起動" echo "swa start --deployment-token $DEPLOYMENT_TOKEN" echo "" echo "# デプロイ情報確認" echo "cat infra/bicep/deploy-info-local.json | jq" 開発者が次に実行したくなるであろうコマンドを先回りして提示してくれる、まさに「かゆいところに手が届く」機能です。 実行方法とその効果 使用方法は極めてシンプルです: # 実行権限の付与(初回のみ) chmod +x infra/scripts/deploy.local.sh # スクリプト実行 ./infra/scripts/deploy.local.sh このスクリプトを実行すると: 前提条件の自動チェック :Azure CLI、SWA CLI、必要なファイルの存在確認 Azure認証の確認 :ログイン状態のチェックと必要に応じたログイン誘導 リソースグループの管理 :存在確認と自動作成 Bicepテンプレートの検証 :デプロイ前の構文チェック インフラのデプロイ :Static Web Appsリソースの作成 アプリケーションのデプロイ :SWA CLIを使用したコンテンツのアップロード 結果の保存と表示 :デプロイ情報の永続化と次のアクションの提示 手動で実行すると10分程度かかる作業が、このスクリプトなら2-3分で完了します。さらに重要なのは、ヒューマンエラーのリスクが皆無になることです。 チーム開発での威力 このスクリプトの真価は、チーム開発で発揮されます。新しいメンバーがプロジェクトに参加した際、複雑な環境構築手順を説明する必要がありません。 .env.local ファイルを配布し、このスクリプトを実行してもらうだけで、全員が同一の開発環境を構築できます。 また、環境の再現性も完璧です。本番環境で問題が発生した際、同じスクリプトを使って検証環境を素早く構築し、問題の原因究明に集中できます。 まとめ この自動化スクリプトは、単純な作業の自動化を超えて、開発チーム全体の生産性向上とコード品質の向上に寄与する包括的なソリューションです。エラーハンドリング、ログ管理、設定の分離、情報の永続化など、実際のプロジェクトで必要になる要素がすべて考慮されています。 Infrastructure as Codeの真の価値は、このような自動化とチーム全体での標準化にあります。一度このレベルの自動化を体験すると、手動でのインフラ管理には戻れなくなるでしょう。 まとめ お疲れ様でした!今回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門」の第2回として、Bicepテンプレートを使ったローカル開発環境の構築とSWA CLIデプロイを行いました。 前回構築したDevContainer環境のおかげで、Azure CLI、GitHub CLI、SWA CLIがすべて使える状態だったので、今回はすぐにBicepの実践に入ることができましたね。特にAzure CLIの認証情報がマウントされていたことで、コンテナ内での作業がとてもスムーズに進められたと思います。 今回学んだparameter.jsonファイルの活用は、環境の使い回しと変更管理の両方を効率化できる重要なテクニックです。設定項目をBicepテンプレートから分離することで、同じテンプレートを使って複数の環境を管理できるようになりました。特に機密情報の分離というセキュリティ面でのメリットは、実際のプロジェクトでも大きな価値を発揮します。 手動でのコマンド実行から自動化スクリプトまで段階的に進めることで、Infrastructure as Codeの基本的な考え方と実践的な運用方法を体験できたのではないでしょうか。特に自動化スクリプトにより、手動作業で10分かかっていたデプロイプロセスを2-3分に短縮できたのは、開発効率の大幅向上を実感していただけたと思います。 次のシリーズへの導入 さて、次回は「GitHub Actions で自動化!Bicep + CI/CD パイプラインで本格運用デプロイ環境構築」となります! 今回はローカル開発でのBicepテンプレート活用とSWA CLIデプロイを学びましたが、次回の大きな違いは デプロイの実行場所 です。今回はローカルからSWA CLIでデプロイしましたが、次回はGitHub Actionsがデプロイを実行するように変更します。 次回の内容としては以下を予定しています: GitHub Environment とSecrets の設定 : BicepでStatic Web Appsを作成した後に取得できるデプロイトークンやアプリ設定情報をGitHubに登録 GitHub CLIを使った自動化 : ローカルからGitHub CLIでSecrets登録を行うbashスクリプトの作成 GitHub Actions ワークフローの作成 : GitHubリポジトリへのプッシュをトリガーとした自動デプロイパイプライン 今回作成したBicepテンプレートとparameter.jsonファイルの分離戦略は、次回でも同じ考え方で活用できます。また、今回手動でコマンドを一つずつ実行してデプロイの流れを体験したことで、GitHub Actionsで同様の処理が自動実行される仕組みも理解しやすくなるはずです。 さらに、今回と同様にGitHub Secrets の登録作業もbashスクリプト化して、ヒューマンエラーを最小限に抑えた自動化を実現していきます。 それでは、次回もお楽しみに!引き続き一緒に Azure Static Web Apps と Bicep をマスターし、本格的なCI/CDパイプラインを構築していきましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 作業時間を70%短縮!BicepによるAzure Static Web Apps自動構築 first appeared on SIOS Tech. Lab .
アバター
ご挨拶 ども!寝る前の日課にAIとのチャットが追加された龍ちゃんです。たまにブログの解析もさせるんですけど、この一番最初の挨拶はAI的には関西弁という認識になるんです。個人的にはそんな意図がなくて、ヒヤッとしているんです。このままAIに執筆させると、関西弁でのブログが作られてしまいそうで、まだまだ人間での執筆をやめられないなと思います。 基礎から学ぶ Azure Static Web Apps × Bicep 入門 今回は「Azure Static Web Apps を Bicep(Infrastructure as Code)で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する」というテーマで3部構成の「環境構築編」となります。各回のコンテンツの内容としては以下になります。 シリーズのまとめ記事はこちら になります。 Azure Static Web Apps + Bicep環境構築編 ←  今回はここ ローカル開発でのBicep + SWA CLIデプロイ実践編 Bicep+GitHub Actions連携デプロイ編 この回での目標は以下となります。 DevContainerによる開発環境の統一化 Azure CLI、GitHub CLI、SWA CLIが使える状態の構築 後続の作業に必要な認証設定の完了 azコマンドでStatic Web Appsのリソースの管理 それでは、環境の構築を始めましょう。 環境構築 作成する環境の整理 まずは構築する環境の整理から始めましょう。今回の環境は、これからのシリーズで必要となるものを一挙にセットアップできるように以下のものを環境内で実行できるようにしておきます。 Azure CLI:Azureのリソース管理に利用する GitHub CLI:GitHubの操作をコマンドラインで行う Azure Static Web Apps CLI(以降:SWA CLI):Azure Static Web Appsのローカル開発を支援する 今回、作成するディレクトリ構成になります。 . ├── .devcontainer/ │ └── devcontainer.json # Dev Container設定 ├── out/ # デプロイ用静的ファイル │ └── index.html ├── swa-cli.config.json # SWA CLI Config ファイル └── README.md # コマンドを覚えるのは大変なのでメモ推奨 今回は環境構築のみなので、動作確認はコマンドで実行して確認をしたいと思います。 DevContainerで構築する利用としては、複数人で開発を前提とする場合に環境差異の確認を都度するのを回避するためです。Azure CLIに関しては、開発体験の向上のためにホスト側の認証情報をマウントして、DevContianerで都度認証を回避するようにします。GitHub CLIに関しては、使用頻度があまり高くないので、都度認証を行うようにします。 DevContainer > devcontianer.json ベースのイメージとしては、 Microsoft公式が出しているDevContianer用のNode イメージを使用しています。 { "name": "Azure Bicep Development", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, "postCreateCommand": "npm install -g @azure/static-web-apps-cli", "customizations": { "vscode": { "extensions": [ "ms-azuretools.vscode-bicep", "GitHub.vscode-github-actions", "ms-vscode.azure-account" ] } }, "forwardPorts": [ 4280 ], "mounts": [ "source=${localEnv:HOME}/.azure,target=/home/node/.azure,type=bind,consistency=cached" ] } featuresとしてAzure CLIとGitHub CLIをインストールして、立ち上げ後にグローバルインストールでSWA CLIをインストールしています。 また、マウントとしてホストにある /.azure ファイルをマウントしています。こちらは、ホストでAzure CLIのログインを実行していれば、コンテナ内で認証済みとして使用することができます。 out > index.html 検証用の静的ファイルを生成しましょう。将来的には、ReactやVueで作成したビルドファイルを指定するべきですが、今回は検証目的のため単純なHTMLファイルを生成して配置しておきます。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Bicepでデプロイをしてみる</title> </head> <body> これが見れたら成功です!更新してみる <p id="time"></p> <script> // 現在の時間を取得 const now = new Date(); // 時間をフォーマット const formattedTime = now.toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit", second: "2-digit", }); // HTMLに表示 document.getElementById( "time" ).textContent = `現在の時間: ${formattedTime}`; </script> </body> </html> コンテナ内動作確認 まずはそれぞれのCLIがインストール済みかを確認します。 az --version gh --version swa --version それぞれバージョン情報が表示されれば確認としては完了です。Azure CLIとGitHub CLIはそれぞれ認証が必要になります。認証をしていない場合は、コマンド自体が失敗する可能性があります。 az login gh auth login SWA CLIに関しては、認証の必要はありません。ですが、エミュレーターを起動するための設定ファイルを生成しておくと、SWA CLIを使用したデプロイやエミュレーターの起動が簡易になります。 # swa-cli.config.jsonを対話的に生成する swa init 対話的に実行すると swa-cli.config.json が生成されます。こちらは、エミュレート時やデプロイ時に使用することができます。便利なので、作成しておきましょう。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "frontend-swr": { "appLocation": "out", "outputLocation": "." } } } エミュレーターは以下のコマンドで実行することができます。 swa-cli.config.json を検索してファイルがある場合は、記述の設定で処理を行ってくれます。 # swa-cli.config.jsonをベースにエミュレーターが起動する swa start # http://localhost:4280/ にアクセス確認 すべてのコマンドが問題なく実行できれば、環境構築完了となります。 Azure CLI+SWA CLIでStatic Web Appsを作成~デプロイ~削除まで bicepでのリソース作成を行う前に、Azure CLIを用いてコマンドベースでAzure Static Web Appsを作成から削除まで検証していきます。事前準備として、リソースグループは手動で作成しておいてください。コマンドで一気に作成することも可能ですが、今回はStatic Web Appsのみに焦点を当てています。 Static Web Appsが作成可能なロケーション情報の取得 # SWAを作成することができるロケーションを取得 az provider show --namespace Microsoft.Web --query "resourceTypes[?resourceType=='staticSites'].locations" このコマンドでは、Microsoft.WebプロバイダーからstaticSitesタイプのリソースが作成可能な全ロケーションを取得しています。ロケーションは様々指定することができますが、Static Web Appsを作成することができるロケーションは限定されています。 リソースを作成 # SWAを作成 az staticwebapp create --name $SWA_NAME --resource-group $RESOURCE_GROUP --location $location 指定したリソースグループ内にSWAを作成するコマンドです。 $SWA_NAME 、 $RESOURCE_GROUP 、 $location は設定したい値に変更してください。作成には数分かかる場合がありますが、完了すると自動的にHTTPS対応のURLが割り当てられます。コマンドの完了後に表示される defaultHostname にhttpsをつけるとページにアクセスることができます。 作成が完了するとサンプルページがデプロイされているので、以下のページが表示されれば無事作成が完了しています。 デプロイトークンを取得 az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP --query "properties.apiKey" SWAへのデプロイには専用のトークンが必要になります。これはセキュリティ上重要な情報なので、適切に管理しましょう。このコマンドでSWAのデプロイメントトークンを取得できます。このトークンはCI/CDパイプラインや手動デプロイで使用する重要な認証情報です。取得したトークンは環境変数として設定しておくと便利ですね。 SWA CLIでデプロイトークンを用いてデプロイ swa deploy --app-location out --env production --deployment-token $DEPLOY_TOKEN --app-location out でビルド成果物が格納されているディレクトリを指定し、 -env production でデプロイ先の環境を指定します(staging環境も利用可能)。 -deployment-token には先ほど取得したデプロイメントトークンを使用します。 Next.jsやNuxt.jsなどのフレームワークを使っている場合、通常は npm run build 後の出力ディレクトリ( out 、 dist 、 build など)を指定することになります。 リソースを削除 az staticwebapp delete --name $SWA_NAME --resource-group $RESOURCE_GROUP --yes 検証が終わったら、課金を避けるためにリソースを削除しましょう。(今回作成したリソースはフリー版です!)SWAはFreeプランでも利用できますが、カスタムドメインや認証機能を使う場合は課金が発生する可能性があります。 --yes フラグを付けることで、確認プロンプトをスキップして削除を実行します。本番環境では十分注意して使用してください。 おわりに お疲れ様でした!今回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門」の第1回として、DevContainer を使った統一開発環境の構築を行いました。 DevContainer の力を借りることで、チーム全体で同じ開発環境を簡単に共有できるようになりましたね。Azure CLI、GitHub CLI、SWA CLI の3つのツールが使える状態になったので、これで次回以降の作業がスムーズに進められそうです。 特に Azure CLI での認証情報をマウントすることで、コンテナを再起動するたびに認証し直す手間を省けるのは、日々の開発体験の向上に大きく貢献してくれると思います。 コマンドラインからの Static Web Apps の作成・デプロイ・削除まで一通り体験できたので、Azure のリソース管理の流れも掴めたのではないでしょうか。特にデプロイトークンの取得と SWA CLI を使ったデプロイは、次回以降でも頻繁に使う重要な操作なので、しっかりと覚えておいてくださいね。 次のシリーズへの導入 さて、次回は「ローカル開発での Bicep + SWA CLI デプロイ実践編」となります! 今回は Azure CLI を使ってコマンドベースでリソースを管理しましたが、次回からはいよいよ Infrastructure as Code の本領発揮です。Bicep を使ってコードでインフラを定義し、バージョン管理できるようにしていきます。 次回の内容としては以下を予定しています: Bicep ファイルの作成 : Static Web Apps のリソースを Bicep で定義 パラメータファイルの活用 : 環境ごとの設定を外部化 ローカルでの Bicep デプロイ : az deployment コマンドでのリソース作成 SWA CLI との連携 : Bicep で作成したリソースへのアプリケーションデプロイ リソースの更新と削除 : Bicep での変更管理 今回構築した DevContainer 環境があるおかげで、Bicep の拡張機能も既にインストール済みですし、Azure CLI での認証も完了しているので、次回はすぐに Bicep の実践に入れます。 それでは、次回もお楽しみに!引き続き一緒に Azure Static Web Apps と Bicep をマスターしていきましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevContainer実践入門:Azure CLI+GitHub CLI環境をチーム全体で統一 first appeared on SIOS Tech. Lab .
アバター
今号では、cron でタスクを追加する際の tips、スクリプト形式のタスクの内容を、もう少し深堀りして説明します! crontab 設定の tips 時間指定方法 範囲指定する – (ハイフン) を指定すると、 「7時から 9時まで」 のように、指定された範囲内のすべての値でジョブを実行することができます。 【例1】毎日 7時から 9時までの毎分実行: * 7-9 * * * 【例2】毎月 1日から 5日までの毎日 10時に実行: 0 10 1-5 * * 【例3】毎週月曜日から金曜日までの毎日 9時に実行: * 9 * * 1-5 複数の値を指定する , (カンマ) を指定すると、 「10時と 12時」 のように、複数の値を同時に指定することができます。 【例1】毎日 10時と 12時に実行: * 10,12 * * * 【例2】毎月 10日、20日、30日のみ 10時に実行: 0 10 10,20,30 * * 【例3】毎週月、水、金曜日の 9時30分に実行: 30 9 * * 1,3,5 間隔値を指定する / (スラッシュ) を指定すると、 「毎時 15分ごと」 のように、ジョブを実行する間隔を指定することができます。 【例1】15分ごとに実行: *\15 * * * * 【例2】毎日 9時から 17時までの間、30分ごとに実行: */30 9-17 * * * 【例3】3日ごとに、0時0分に実行: 0 0 */3 * * 特殊な文字列オプション 最初にご説明した 分、時、日、月、曜日の 5つのフィールド で実行タイミングを指定する方法以外にも、特定の頻度を文字列で指定できるオプションがあります。 @reboot :システム起動時に 1度だけ実行 @yearly もしくは @annually :年に 1度だけ実行 (1月1日 00:00、年が変わった瞬間) @monthly :月に 1度だけ実行 (各月の 1日 00:00、月が変わった瞬間) @weekly :週に 1度だけ実行 (毎週日曜日 00:00、週が変わった瞬間) @daily もしくは @midnight :毎日 1度だけ実行 (毎日 00:00、日が変わった瞬間) @hourly :毎時 1度だけ実行 (毎時 00 分、時間が変わった瞬間) 例えば、システム起動時にのみ setup.sh というスクリプトを実行したい場合、下記のように設定します。 @reboot /path/to/startup.sh /etc/crontab と crontab -e の違い システム全体の crontab ファイル ( /etc/crontab や /etc/cron.d 以下のファイル ) を編集する場合、 どのユーザが実行するかを明示する必要があります。 例えば、毎日 0時に daily.sh というスクリプトを実行するタスクを追加する場合、/etc/crontab と crontab -e では下記のように記載内容を変えます。 /etc/crontab の場合 0 0 * * * ykaino /path/to/daily.sh crontab -e の場合 0 0 * * * /path/to/daily.sh ※ ykaino ユーザによる crontab -e 実行を想定しています。 スクリプト形式の tips スクリプト形式のタスクは、基本的にはシェルスクリプトと同じ書き方で登録することができます。 基本的な構文、コマンド、if、for、while などの制御文、変数、関数 なども使用できます。 シェルスクリプトについての具体的な説明はここではしませんが、スクリプト形式のタスクを登録する際の注意点を記載します。 コマンドは絶対パスで crontab と同様、 スクリプト内で実行するコマンドは絶対パスで 記載することを推奨します。 カレントディレクトリに注意 cron がスクリプトを実行する際のカレントディレクトリは、通常は スクリプトを実行しているユーザのホームディレクトリ になります。 そのため、スクリプト内でファイルを操作する際などは注意が必要で、コマンドと同様に ファイルも絶対パスで 記載しておきましょう。 必要であれば実行結果をログに出力 cron のタスク内で呼び出されたスクリプトやコマンドで何らかの問題が発生した場合、cron の実行結果として成功・失敗は確認できるものの、具体的にどの処理で失敗したのかが分からない場合があります。 この対策として、下記のように実行結果を明示的にログ出力させるようにしておくと安心です。 #!/bin/bash LOGFILE="/var/log/my_app/daily_job.log" # 成功メッセージをログ result() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOGFILE" } ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!cron によるタスク管理2 first appeared on SIOS Tech. Lab .
アバター
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 Apple は、MacOS 上で仮想マシンを使って Linux コンテナを作成・実行するためのツール「container」を、オープンソースとして公開しました。 Apple⁠⁠、macOS上でLinuxコンテナを直接実行できる「container」をオープンソースとして公開 https://gihyo.jp/article/2025/06/apple-container アメリカの大手保険会社 Aflac は、国内ネットワーク上で不審なアクセスを検知したと発表しました。 米 アフラック、サイバー攻撃で個人情報漏洩の可能性 https://rocket-boys.co.jp/security-measures-lab/aflac-us-announces-possible-data-breach-after-cyberattack/ 2025/6/20、Microsoft は「Windows Subsystem for Linux」v2.6.0 をプレリリース版として公開しました。オープンソースとしては初のリリースです。 「Windows Subsystem for Linux」に初のオープンソースリリース/「WSL 2.6.0」がプレリリース版として公開 https://www.msn.com/ja-jp/news/techandscience/windows-subsystem-for-linux-%E3%81%AB%E5%88%9D%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%97%E3%83%B3%E3%82%BD%E3%83%BC%E3%82%B9%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9-wsl-260-%E3%81%8C%E3%83%97%E3%83%AC%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E7%89%88%E3%81%A8%E3%81%97%E3%81%A6%E5%85%AC%E9%96%8B/ar-AA1HddiV ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2025年6月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech. Lab .
アバター
OSS よろずチームの神﨑です。 RHEL 10 同梱版の httpd の変更点についてドキュメントと疎通確認したのでまとめていきたいと思います。 まずは、ドキュメントからです。 httpd に関する変更点 RHEL 公式ドキュメントを確認したところ以下の記述がありました。 RHEL 10.0 同梱版 httpd は Apache HTTP Server 2.4.62 デフォルトでロードされるモジュールの変更 mod_authnz_fcgi がデフォルトで有効化されるようになりました。 セキュリティの強化 httpd.service ユニットファイルのデフォルト設定でセキュリティが強化されるようになりました。 OpenSSL の ENGINE サポート削除 SSLCryptoDevice 設定ディレクティブは使用不可になりました。 Berkeley DB データベースのサポート削除 RHEL 9 以降、Berkeley DB はサポートされなくなりました。 mod_authz_dbm などのモジュールは、デフォルトで LMDB データベースタイプを使用します。SDBM データベースタイプも利用可能です。 RHEL 10 の導入における検討事項 – 第14章インフラストラクチャサービス より 以下で httpd.service と OpenSSL についてもう少し補足していきます。 httpd.service で追加された設定 RHEL 9.6 同梱版 httpd の httpd.service ファイルと比較したところ以下が追加されていました。 /usr/lib/systemd/system/httpd.service より (追加は ハイライト部分 ) [Unit] Description=The Apache HTTP Server Wants=httpd-init.service After=network.target remote-fs.target nss-lookup.target httpd-init.service Documentation=man:httpd.service(8) [Service] Type=notify Environment=LANG=C ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND ExecReload=/usr/sbin/httpd $OPTIONS -k graceful # Send SIGWINCH for graceful stop KillSignal=SIGWINCH #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # [Service] # Environment=OPTIONS=-DMY_DEFINE [Unit] Description=The Apache HTTP Server Wants=httpd-init.service After=network.target remote-fs.target nss-lookup.target httpd-init.service Documentation=man:httpd.service(8) [Service] Type=notify Environment=LANG=C ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND ExecReload=/usr/sbin/httpd $OPTIONS -k graceful # Send SIGWINCH for graceful stop KillSignal=SIGWINCH KillMode=mixed DevicePolicy=closed KeyringMode=private LockPersonality=yes MemoryDenyWriteExecute=yes OOMPolicy=continue PrivateDevices=yes PrivateTmp=true ProtectClock=yes ProtectControlGroups=yes ProtectHome=read-only ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectSystem=yes RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes SystemCallArchitectures=native 追加されたパラメータ(波線以下)の概要は以下の通りです。 先ほど述べた通りセキュリティ強化に関するパラメータが追加されていますね。 DevicePolicy=closed プロセスが /dev 以下のデバイスノードにアクセスするのを制限します。 closed に設定すると、デフォルトではどのデバイスにもアクセスできません。 KeyringMode=private サービスプロセスが自身のプライベートなカーネルキーリングを持つようにします。 LockPersonality=yes personality システムコールを介してプロセスの実行モードを変更することを禁止します。 MemoryDenyWriteExecute=yes プロセスが書き込み可能かつ実行可能 (W+X) なメモリ領域を作成することを禁止します。 PrivateDevices=yes サービスに独自の /dev ツリーを提供し、システム全体の /dev ツリーへのアクセスを制限します。 ProtectClock=yes サービスプロセスがシステムクロックの設定を変更することを禁止します。 ProtectControlGroups=yes サービスプロセスがコントロールグループ (cgroups) のファイルシステムにアクセスすることを禁止します。 ProtectHome=read-only サービスプロセスからユーザーのホームディレクトリ ( /home/ 、 /root/ など) を読み取り専用でマウントします。 ProtectHostname=yes サービスプロセスがシステムのホスト名を変更することを禁止します。 ProtectKernelLogs=yes サービスプロセスがカーネルログ( dmesg などがアクセスする情報)にアクセスしたり、変更したりすることを禁止します。 ProtectKernelModules=yes サービスプロセスがカーネルモジュールをロードしたりアンロードしたりするのを禁止します。 ProtectKernelTunables=yes サービスプロセスがカーネルのsysctlパラメータ ( /proc/sys 以下の設定) を変更することを禁止します。 ProtectSystem=yes /usr , /boot , /etc などのシステムディレクトリを読み取り専用でマウントします。これにより、サービスがこれらの重要なシステムファイルを変更したり、新たなファイルを書き込んだりするのを防ぎます。 RestrictNamespaces=yes サービスプロセスが新しい名前空間を作成したり、既存の名前空間に参加したりするのを制限します。 RestrictRealtime=yes サービスプロセスがリアルタイムスケジューリングポリシー ( SCHED_FIFO , SCHED_RR ) を使用することを禁止します。 RestrictSUIDSGID=yes サービスプロセスが setuid または setgid フラグを持つファイルを実行することを禁止します。 SystemCallArchitectures=native サービスプロセスが、ネイティブアーキテクチャのシステムコールのみを使用できるように制限します。 詳細は man 5 systemd.exec よりご確認ください。 httpd に関連する OpenSSL の変更 OpenSSL 3.0 の導入により、従来の Engine が Provider へと置き換えられた結果、 openssl-pkcs11 エンジンが削除され、代わりに pkcs11-provider が提供されています。 参考 以下の公式ドキュメントを参考にしました。 RHEL 10 の導入における検討事項 10.0 リリースノート ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post RHEL 10 同梱版の httpd 変更点まとめ 1 ~ドキュメント編 first appeared on SIOS Tech. Lab .
アバター
はじめに  2025年6月16日から二日間開催されたKubeCon + CloudNativeCon Japan( https://events.linuxfoundation.org/kubecon-cloudnativecon-japan/ )に参加しました。 第一回目のせいなのか、ところどころである休憩時間では通路を埋めるほど人が集まり、非常に盛況なイベントでした。  場所は、お台場にある、ヒルトン東京お台場と呼ばれる場所で行っており、その1Fを使用してスポンサーブースや技術セッションを聞いたりしました。実際のセッション内容は後日公開されたり、セッションで使用した資料は公式サイトに公開してあるものもあります。 ゆりかもめの台場駅から歩いてすぐに会場がある。    基本的に、発表内容は英語のセッションですが各セッションルームにQRコードがかかれた看板があり、そのQRコードのリンクからセッション内容がチャットのように流れていたため英語が理解できなくて内容自体はすぐに確認可能など技術の進歩に驚きます。 参加者の証明であるバッチを受け取り、自分自身へ話しかけていいかどのように話しかけるかのシール セッションの翻訳内容を聞くためのリンクが書かれたQR 翻訳アプリの内容について 一日目について  参加者はまず、受付でバッチと呼ばれる作業あるため受付でかなり長い行列ができていました。そのためキーノートセッション開始前に来ると受付のためキーノートの会場入りに間に合わない人も数人いたことや、開始の10分前にはメイン会場が入れなくなるので、余裕をもって受付に参加したほうが参加しやすいです。 一日目でバッチ受け取りの受付は8時から実施したが、Tシャツの配布は混雑解消のためキーノートが終了する10:45から配布するなど時間をずらしていた    一日目は、Prometheus 3.0のセッションや2-node Kubernetesのセッションを聞いてきました。  Prometheus 3.0のセッションでは、Prometheusのこれからについて、OpenTelemetryとの強化について触れており、OpentElemetryについてより重要だと認識しました。    2-node Kubernetesセッションでは、複数のノードを使用して司令塔であるコントロールプレーンを作成する内容であり、通常では3ノードを使用して作成する所を2ノードで作成する内容でした。 おもな特徴として、2ノードで作成できるのですが、etcdと呼ばれるデータストアの保全性を維持するため、sambaやnfsの共有ディスクを使用してデータの正当性を維持しております。用意するハードの数が減るため管理するPCが減る利点もあるため、その技術には将来性があると考えます。   二日目について  一日目が大盛況なこともあり、Tシャツの配布ブースでは男性用のTシャツのサイズが二種類しか存在しないことがありました。また、入場者数は一日目と大きな違いがなく二日目も大盛況であり、お昼に提供する一般の弁当がなくなったり、休憩で提供させている部屋を埋めつくすこともありました。 二日目のセッションでは、Green OpenTelemetryやThe Future of Prometheus Exposition Format、From Moon Prism Power To eBPF Super Saiyanというセッションを聞いてきました。   Green OpenTelemetryでは、環境問題をテーマにOpenTelemetryを使用して消費電力の最適化を行うために、Kubernetes based Efficient Power Level Exporter(通称Kepler)を使用して、eBPFから取得できるCPU使用率からエネルギー消費量に変化させて少ないエネルギーで運用し環境問題改善に貢献しようという話でした。 The Future of Prometheus Exposition FormatではOpenMetricsと呼ばれるメトリックログを出力する際の標準化フォーマットの歴史やこれからへのOpenMetrics 2.0へむけて向けての改善を話す内容で、ここでもOpentelemetryというキーワードが出ており可観測の注目が非常に高いと感じました。 スポンサーブースについて   スポンサーブースでは、Google Cloudのブースで2025年の1月にオープンソースで公開されたソフトのKubernetes History Inspectorと呼ばれるソフトが展示されており、Kubernetesの監査ログから過去の変更点が視覚的に追いながら調査できることが便利だと感じました。 また、ZOZOさんのブースでは、特定のサーバへの複数アクセスのテストでKubernetes上で立てたPodからアクセスするようなソフトのでも実施しており、この点もKubernetesの利点だとも再認識しました。 さらに、CNCFプロジェクトのステッカー配布ブースがあることが少し感動しました。 まとめ  日本でやる第一回のKubeConであったため非常に大盛況のイベントありました。基本的に英語のセッションでしたが、翻訳アプリのおかげで内容の理解に問題を感じないため言語の壁は技術の進化によって減ってきたと感じました。  本イベントでますますOpentelemetoryなどのAI学習に与えるデータを作ることも重要性も感じるようなイベントだと考える機会でした ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post KubeCon + CloudNativeCon Japan 2025 参加レポート first appeared on SIOS Tech. Lab .
アバター
初めに ども!最近は人間とチャットするよりもAIとチャットすることが増えている龍ちゃんです。AIサービスの使い方が多岐にわたってきて、そのトピックだけでブログが執筆できそうなくらいです。 今回はAIとあまり関係ない、認証のお話です。内容としては「Azure Static Web Appsで組み込み認証とユーザー情報の取り扱い」となります。認証プロバイダーとしてGoogleを使用しています。 構築がしたことがない方は、「 GoogleによるSSOを持つAzure Static Web Appsのアプリを作成する 」の記事を参考に構築して試してみてください。 前提技術 Azure Static Web Apps(以降 SWA)をStandardプランで運用して、カスタム認証としてGoogleを設定していることが前提となります。カスタム認証はStandardプランから使用することができ、Freeプランで今回と同様の検証することができません。また、カスタム認証を有効化すると、他の認証プロバイダーが提供する組み込みプロバイダー( /.auth/login/github …など)が利用できなくなります。 SWA CLIを使用すれば、認証フローをローカルでエミュレートすることができます。 Next.js+DevContainer環境でSWA CLIをセットアップしているブログがありますので、こちらを参考に構築するのをお勧めします 。 バックエンドは、 将来的にSWAとAPI連携を活用して接続をする予定 になります。Azure FunctionsやAzure App Serviceにデプロイされるのが想定されます。ローカルでの検証もできるので、任意の環境で大丈夫です。 私の検証環境では、nest.jsを使用して構築しています。コードとしては、TypeScriptになりますが概念としては他の言語でも通じることですので参考にしてみてください。 今回検証する内容としては以下になります。 staticwebapp.config.json のロールベースルーティング: 参考リンク API連携をした際に x-ms-client-principal を介してユーザー情報を渡す: 参考リンク staticwebapp.config.json を用いてロールベースのルーティングを定義する こちらはプレビューの機能となります。本番環境で利用する場合は、検討して採用を決めてください。 staticwebapp.config.jsonの設定を追加することで、認証時にロールの判定ルートを追加することができます。制限事項として、SWA CLIではロール判定のルートを含めてエミュレートするため、実際の動きはAzure上にデプロイしてからのみ確認することができます。 認証からロール判定の流れは以下の流れになります。 設定項目としては、 auth > rolesSource になります。こちらで設定したエンドポイントに認証後にPOSTリクエストが送られます。今回は /api/assingRoles と設定しています。 //staticwebapp.config.json { "auth": { "rolesSource": "/api/assignRoles", "identityProviders": { "google": { "registration": { "clientIdSettingName": "GOOGLE_CLIENT_ID", "clientSecretSettingName": "GOOGLE_PROVIDER_AUTHENTICATION_SECRET" } } } }, "routes": [ { "route": "/admin/", "allowedRoles": ["admin"] }, { "route": "/", "allowedRoles": ["authenticated"] } ], "responseOverrides": { "404": { "rewrite": "/404/index.html", "statusCode": 404 }, "401": { "statusCode": 302, "redirect": "/.auth/login/google" } } } POSTリクエスト付帯されるボディ情報としては以下になります。こちらは、Azure EntraIDを設定した場合のサンプルになります。こちらの情報をもとに、ロールの判定を返答することで、ユーザーにカスタムロールをAPI経由で設定することができます。 // 仮のJSON { "identityProvider": "aad", "userId": "00aa00aa-bb11-cc22-dd33-44ee44ee44ee", "userDetails": "ellen@contoso.com", "claims": [ { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "val": "ellen@contoso.com" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname", "val": "Contoso" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", "val": "Ellen" }, { "typ": "name", "val": "Ellen Contoso" }, { "typ": "http://schemas.microsoft.com/identity/claims/objectidentifier", "val": "7da753ff-1c8e-4b5e-affe-d89e5a57fe2f" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "val": "00aa00aa-bb11-cc22-dd33-44ee44ee44ee" }, { "typ": "http://schemas.microsoft.com/identity/claims/tenantid", "val": "3856f5f5-4bae-464a-9044-b72dc2dcde26" }, { "typ": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "val": "ellen@contoso.com" }, { "typ": "ver", "val": "1.0" } ] } それでは、nest.jsで設定方法について見ていきます。エンドポイントから返す値としては、以下のフォーマットに合わせる必要があります。 { roles: string[] } ロール判定の結果、空配列を返答した場合でもSWA側で anonymous ・ authenticated が自動で割り振られます。 import { Body, Controller, Get, Post } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} // https://learn.microsoft.com/ja-jp/azure/static-web-apps/authentication-custom?tabs=google%2Cfunction @Post('/api/assignRoles') async postHello(@Body() req: { userId: string; userDetails: string }): Promise<{ roles: string[] }> { // proiderがgoogleの場合はuserDetailsはメールアドレス情報になっている // 不安な場合は/.auth/meのエンドポイントを確認 // 受け取った情報をもとにrolesを割り当てて返信する // 何もrolesがない場合はから配列を返答する // 検証のためメールアドレスによる判定を行う const roles = await this.appService.assignRoles(req.userDetails); return { roles: roles }; } } 動作確認としては、SWA側で /.auth/me にアクセスすることで確認することができます。ローカルでは、ロール判定も含めてエミュレートするため、エミュレート認証情報が表示されています。 こちらの画像上 Users roles に文字列で設定することで再現することができます。 ロールの設定が完了すれば、 staticwebapp.config.json で allowedRoles で設定することで特定のロールのみが見れるページを制御することが可能になります。 バックエンドでの認証ユーザーの情報にどのようにアクセスするのか? 先ほどのロールベースでの確認で /.auth/me というエンドポイントを使用しました。フロントエンドからGETリクエストを投げることで、認証情報を取得することができます。 技術としてはフロントから自分のIDを取得・指定してAPIアクセスができます。これは、ユーザIDを改ざんしてAPIアクセスすることができてしまうため非推奨です。 SWAからAPI連携しているバックへの認証情報の受け渡しは、ヘッダーに自動挿入されている x-ms-client-principal を活用して行います 。こちらは、Base64でエンコードされた情報として贈られるためでコードをすることで、バックエンドで認証情報を取得することができます。デコードされて受け取ることができる値は以下になります。 type clientPrincipal = { userId: string; // ユーザーID userRoles: string[]; // ユーザーロール identityProvider: string; // 認証プロバイダー userDetails: string; // ユーザー情報 Googleが認証プロバイダーの場合はメールアドレス }; それでは、実装に入っていきます。実装としてはnest.jsのGuardを使用してデコードとリクエストに積み替えを行い、contorllerでは情報を使うだけという実装にしていきます。 まずはGuardを実装します。デコードの方法としては、 公式のサンプル を参考に実装しています。処理としては、以下になります。 ヘッダーから x-ms-client-principal の取得・確認 デコード リクエストに詰め替え import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { Observable } from 'rxjs'; import as base64 from 'base-64'; import as utf8 from 'utf8'; type clientPrincipal = { userId: string; userRoles: string[]; identityProvider: string; userDetails: string; }; // このガードは、Azure Static Web Apps (SWA) の認証ヘッダーを検証するために使用されます。 // SWAは、認証されたユーザーの情報を 'x-ms-client-principal' ヘッダーに含めて送信します。 // このガードは、ヘッダーが存在し、正しい形式であることを確認し、 // ユーザー情報をリクエストオブジェクトに追加します。 // 認証されていない場合は、UnauthorizedExceptionをスローします。 // 参考: https://learn.microsoft.com/ja-jp/azure/static-web-apps/user-information?tabs=javascript @Injectable() export class AuthGuardToSwaGuard implements CanActivate { canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); const clientPrincipalEncoded = request.headers['x-ms-client-principal'] as string; if (!clientPrincipalEncoded) { // 認証情報ヘッダーがない場合、認証されていないとしてスロー throw new UnauthorizedException('X-MS-CLIENT-PRINCIPAL header not found. User is not authenticated.'); } try { // Base64 デコードと UTF-8 デコード const decoded = utf8.decode(base64.decode(clientPrincipalEncoded)); console.log(JSON.parse(decoded)); const clientPrincipal: clientPrincipal = JSON.parse(decoded); request.user = clientPrincipal.userId; request.userRoles = clientPrincipal.userRoles; return true; // 認証成功 } catch (error) { console.error('Failed to decode or parse x-ms-client-principal:', error); // デコードやパースに失敗した場合、不正なヘッダーとしてスロー throw new UnauthorizedException('Invalid X-MS-CLIENT-PRINCIPAL header format.'); } } } Controller実装です。Guardを挿入するとリクエストに積み替えられて取得することができます。 import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'; import { AppService } from './app.service'; import { AuthGuardToSwaGuard } from './common/guard/auth-guard-to-swa/auth-guard-to-swa.guard'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} // SWAの認証情報が必要 @UseGuards(AuthGuardToSwaGuard) @Get('/api/hello') async getHello(@Req() req): Promise<string> { console.log('getHello called with user:', req.user); console.log('getHello called with userRoles:', req.userRoles); return "hello"; } } 確認のため、コンソールに出力しています。あとは、ユーザIDやロールなどの情報をもとに制御することができます。 コラム: staticwebapp.config.json vs. バックエンドAPIでのアクセス制限 SWAでは認証・認可のアプローチとして2つの方法があることがわかりました。設定ファイルでの制御とバックエンドAPIでの制御、これらはどう違うのでしょうか?実際のプロジェクトでどちらを選ぶべきか、迷うところですよね。 実は、この2つのアプローチはそれぞれ異なるタイミングで動作し、得意分野も違います。以下のシーケンス図で、両者の動作の違いを見てみましょう。 比較表:どちらを選ぶべきか? 制御の場所 Azure Static Web Apps (SWA) エッジノード / フロントエンド バックエンドAPIアプリケーション内 処理のタイミング APIへのリクエストがSWAに到達した直後(API実行前) APIがリクエストを受け取り、処理を開始した後 実装方法 設定ファイル (staticwebapp.config.json) の記述 プログラミング言語(TypeScript/NestJS)、ガード、デコレーター、ミドルウェア、ロジックコード 制御の粒度 粗い(URLパスベースの許可/拒否) 細かい(特定のエンドポイント、メソッド、データのフィールド、ビジネスロジック) 主なユースケース – エンドポイント全体へのアクセス制限 – 特定のAPIグループへのアクセス制限 – 不要なバックエンドAPI呼び出しの防止 – データレベルのアクセス制御(例:自分のデータのみ表示) – 情報のマスキング/匿名化 – 特定のアクションの制限(例:更新/削除) – 動的な条件に基づくアクセス制御 – より複雑なビジネスルールに基づく認可 パフォーマンス 高速(APIが呼び出されないため) わずかなオーバーヘッド(APIが呼び出され、処理が実行されるため) 設定の柔軟性 静的(デプロイが必要) 動的(コード変更とデプロイが必要だが、データベースなどと連携すれば実行時にも柔軟な制御が可能) 開発の容易性 シンプル(設定ファイルの記述のみ) 複雑(コードの記述、フレームワークの学習、設計が必要) エラーハンドリング SWAが自動的に401/403を返す カスタムエラーレスポンス(例:NestJSのForbiddenException)を細かく制御可能 推奨される利用 最初の防衛線として、大まかなアクセス制御 詳細なビジネスロジックに基づいた認可、データ処理、複雑なロール要件 使い分けの提案 それぞれの特徴を理解した上で、実際の開発ではどのように使い分けるべきでしょうか? staticwebapp.config.json を使うべき場面 「最初の防衛線」として、粗い粒度(URLパス全体)でのアクセス制限に最適です。バックエンドへの不要なリクエストを防ぐことで、パフォーマンスの向上とセキュリティの強化を同時に実現できます。 管理者専用ページへのアクセス制限 認証済みユーザーのみがアクセス可能なエリアの制御 特定のAPIエンドポイントグループへの一律制限 バックエンドAPIを使うべき場面 「より詳細な制御」が必要な場合に必須となります。データレベル、アクションレベル、または複雑なビジネスロジックに基づいた認可を実現したい場合は、こちらを選択しましょう。情報マスキングやフィルタリングもここで実現できます。 特に、 アクセスするユーザーによってデータが変動する ケースでは、APIベースでの認証が重要になります。 具体例: ブログ記事の閲覧制限 : 一般ユーザーは公開記事のみ、プレミアムユーザーは全記事を閲覧可能にする場合 投稿コンテンツの編集権限 : 投稿されたコメントの編集・削除権限を、投稿者本人と管理者のみに制限する場合 ダウンロード容量制限 : ダウンロード可能なファイルの容量制限を、無料ユーザーは100MB、有料ユーザーは1GBまでとする場合 両方を組み合わせるアプローチ 最も堅牢で柔軟なシステムを構築するには、 両方を組み合わせる ことをお勧めします。 staticwebapp.config.json で大まかなアクセス制御を行い、不要なAPI呼び出しをブロック バックエンドAPIで詳細なビジネスロジックに基づいた認可処理を実装 この多層防御のアプローチにより、パフォーマンスとセキュリティの両方を最適化できます。 まとめ 今回は、Azure Static Web Appsにおける認証とユーザー制御について、フロントエンドとバックエンドの両方のアプローチを詳しく見てきました。特に、x-ms-client-principalを活用した安全なユーザー特定と、きめ細かなロールベース制御の実装方法について解説しました。この知識を活かして、より堅牢なWebアプリケーションの開発に取り組んでいただければ幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Azure Static Web Apps: x-ms-client-principalで安全なロールベース制御 first appeared on SIOS Tech. Lab .
アバター
はじめに こんにちは、サイオステクノロジーの安藤 浩です。 前回の記事では、Ethereum Sepoliaテストネット上のフルノードを構築し、イベントログからERC-721やERC-1155の所有者を特定する方法をご紹介しました。 前回の記事は以下です。 [web3] Ethereum の Sepolia上のイベントログから ERC-721, ERC-1155 の所有者の見つけ方 今回はその続編として、WebSocketを活用したリアルタイムでの所有者の移転が行われた際の監視の方法をお伝えします。 WebSocketを使ったイベント監視の仕組み Ethereumのフルノードは通常HTTPSによるRPC通信を提供していますが、リアルタイム監視にはWebSocketが適しています。WebSocketを使うことで、新しいイベントが発生した時点で即座に通知を受け取ることができます。 前提条件 Sepoliaテストネット上のフルノードがすでに構築されていること 上記に記載の前回の記事でフルノードを構築してください。 [web3] Ethereum の テストネット: Sepolia上での フルノード構築 設定手順 1. Gethの起動設定を変更する まず、GethでWebSocketを有効にするために、起動コマンドに以下のオプションを追加します。 --ws --ws.api eth,net,web3 実際のコマンド例は以下のようになります: gethlocal --sepolia --ws --ws.api eth,net,web3 --http --http.api eth,net,engine,admin --authrpc.jwtsecret 【jwt.hexファイルのパス】 --datadir 【データディレクトリのパス】--syncmode snap 2. WebSocketでイベントをサブスクライブするコード src/sub.ts というファイルを以下の内容で作成します。 import WebSocket from 'ws'; // Geth ノードの WebSocket エンドポイント const wsUrl = 'ws://localhost:8546'; // WebSocket クライアントを作成 const ws = new WebSocket(wsUrl); ws.on('open', () => { console.log('WebSocket connection established.'); const contractAddressList = [ '0xE88Df35e01e3e33Df38FB0B5e324282feCeb20c2', //ERC-721 '0x412E008d6157568F8c621FbF899e7717F0442a94' //ERC-1155 ]; contractAddressList.forEach((contractAddress) => { // eth_subscribe を使用してトランザクションログを監視 const subscriptionRequest = { jsonrpc: '2.0', id: 1, method: 'eth_subscribe', params: ['logs', { address: contractAddress, // 特定のコントラクトアドレスを指定 topics: [] // トピックフィルタを指定(空配列はすべてのイベントを受信) } ] }; ws.send(JSON.stringify(subscriptionRequest)); }); }); ws.on('message', (data: any) => { const response = JSON.parse(data.toString()); if (response.method === 'eth_subscription') { console.log('New log:', response.params.result); } else { console.log('Response:', response); } }); ws.on('error', (error: any) => { console.error('WebSocket error:', error); }); ws.on('close', () => { console.log('WebSocket connection closed.'); }); 実行すると、WebSocketの接続が確立され、以下のようなレスポンスが表示されます: [nodemon] starting `ts-node src/sub.ts` WebSocket connection established. Response: { jsonrpc: '2.0', id: 1, result: '0x8debf94ebabaea3a86f403b2e2791a19' } リアルタイム監視の実例 ここでは、実際にNFT関連のトランザクションが発生した際に受信したイベントログをいくつか紹介します。 1. OwnershipTransferredイベント New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x', blockNumber: '0x7a2cce', transactionHash: '0xa8edf869ba4fd375f2fdfc51e557d7a3237299f2583242bb43d839f5930fcf78', transactionIndex: '0x1e', blockHash: '0x57e194d69b6dd85dc431228189c8bf551d0e91a74ed14c6eecfba581c580c73c', logIndex: '0x1a', removed: false } このイベントは、コントラクトのオーナーシップ移転を示しています。Etherscanでは こちら で確認できます。 2. TransferSingleイベント New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', blockNumber: '0x7a2d00', transactionHash: '0xa9f80aced3eb829117ef1cdfaba629a6ae625af9e44febe91bfed5b3dfec3565', transactionIndex: '0x43', blockHash: '0x3823f72b6cf3590915be2b19bc2bff5acbf4f35a772c91e69b3dc4d5804bc7e2', logIndex: '0x43', removed: false } このログはERC-1155のTransferSingleイベントで、単一のトークン移転を記録しています。トランザクションは こちら で確認できます。 New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001', blockNumber: '0x7a2d0c', transactionHash: '0xb20b71a0e8b8d47ee30fa20c0afc74f82d83aa508492d6128a4dbf7258e91960', transactionIndex: '0x37', blockHash: '0x4a83395b33fd6dd60302d8d340fd4afb114b4b1208cc94b5246ff39162e0173e', logIndex: '0x64', removed: false } 3. TransferBatchイベント New log: { address: '0x412e008d6157568f8c621fbf899e7717f0442a94', topics: [ '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x00000000000000000000000036da942099c028275321130b5e503f37da446487', '0x000000000000000000000000cebc36de334ce12dfd08f4c39e833016263ba5b0' ], data: '0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002', blockNumber: '0x7a2d5e', transactionHash: '0xd35a028d7c9a9d98d7ac103f6fa75b0496967e947791572b8a8a87b2407d78b2', transactionIndex: '0x8', blockHash: '0x1f9f0e0134a8b7c57156df86e84262c4037256a7f7f143a184f57922fd1ad3b1', logIndex: '0x13', removed: false } こちらはERC-1155のTransferBatchイベントで、複数のトークンが一度に移転されたことを示しています。詳細は Etherscan で確認できます。 イベントデータの活用方法 受信したイベントログを解析することで、以下のような情報を取得できます: ERC-721:  event Transfer  から直近の送信先を特定 ERC-1155: event TransferSingle  と  event TransferBatch  から所有者と所有量を計算 これらの情報をリアルタイムで処理し、データベースに記録することで、常に最新のNFT所有状況を把握することができます。 まとめ Ethereumフルノードを使ったWebSocket接続により、NFTの所有権移転をリアルタイムで監視できることがわかりました。この方法は、NFT取引プラットフォームやウォレットサービスなど、常に最新の所有権情報を必要とするアプリケーションで特に有用です。 参考資料 Geth PubSub API ドキュメント ERC-721 非代替性トークン規格 ERC-1155 マルチトークン規格   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [web3] Ethereum フルノードを使ったERC-721, ERC-1155の所有者のリアルタイム監視方法 first appeared on SIOS Tech. Lab .
アバター
こんにちは、エンジニアのわたなべです。 2025年1月~5月で LinuC の101, 102, 201, 202, 303 にスピード合格できたので、合格体験記としてまとめておきます。レベル1とレベル2の間に基本情報を取ったりしたので、LinuCに限ると4ヶ月で取得できたことになります。 ※ あくまで私のやってきたことを紹介する記事であり、これをやれば必ず合格できると保証する内容ではないことをご承知おきください。 学習開始時点(2025年1月)のバックグラウンドとしては以下の感じで、がっつりインフラ触ってるとかではなく経験浅めからのスタートなので、勉強時間についてはある程度参考になるかと思います。 新卒エンジニア2年目(非情報系学部) DB等のミドルウェア構築検証などでLinuxを触る経験は多少あり 基本コマンド(lsとかtouchとか)以外について、Linuxの知識を深める経験はなし ここからでも4ヶ月でレベル3認定までもらえましたので、学習方法を間違えずしっかり時間を確保できれば十分身に着けられます。 試験概要 LinuCとLPIC LinuC は Linux Professional Certification の略で、名前の通りLinuxの試験です。 LPIC とよく並べられますが、運営団体が違ったりします。試験範囲はおおよそ被ってたりと、どっち受けるかみたいな話はネットとかでよく話題になってます。 どうせ右足から歩き始めるか左足から歩き始めるかみたいな違いしかないと思うんですが、 自分の場合は新しくできた方が試験内容も新しいのかなというだけでLinuCにしました。実際201試験では LinuCのみdockerが範囲だったりとやや試験範囲に差分があり 、LinuCの方が新しめな所感です。 レベルと認定ついて 認定についてはちょっとややこしいので整理しておきます。 レベル1 LinuC レベル1は 101試験, 102試験の両方合格で認定 されます。 午前午後とかの同日で受ける必要は全くないので、101試験からチャレンジして合格したら102の勉強を始める感じです。 レベル2 レベル2は以下を両方満たすと認定されます。 有意なレベル1認定を持つこと 201試験、202試験両方合格すること LinuCの有意性は5年間なので、レベル1を取ってから5年以内に合格しないとまた101試験と102試験を受けないといけません。 レベル2の認定を受けると、自動的にレベル1の有意性も更新され、レベル2取得時から5年間へと延長されます。 レベル3 レベル3は以下を両方満たすと認定されます。 有意なレベル2 認定を持つこと 300試験、303試験、304試験の いずれか に合格すること レベル3 も認定をもらったタイミングでレベル2の有意性が更新されます。 レベル3だけ試験は1つで済みますね。 テーマがそれぞれ違っていて、 300試験 は混在環境、 303試験 はセキュリティ、 304試験 は仮想化&高可用性 となります。 それぞれ202試験との重複した範囲があり、意外とスムーズにクリアできます。リンクから試験範囲を確認して業務に関連深そうなものを見極めましょう。 この記事では303試験のみについて解説します。 学習コンテンツ 参考: LPI-Japan認定教材 レベル1,2 LPI-Japan 公式Youtube LPI-Japanさんのウェビナーのようなもののアーカイブですが、試験概要や各章について解説があります。 各章については試験に直結する知識や問題を提示してくれるというよりかは、 機能の役割など概要として理解するのに最適 です。 再生リストの順番がバラバラなので、サムネやタイトルから目的の動画を探して視聴してください。 あずき本 Linux教科書 LinuCレベル1 Version 10.0対応 LinuCレベル1の定番教科書。2つの試験範囲を完全網羅し、豊富な練習問題で合格をサポート。基礎から実務まで体系的に学習できる構成になっています。 ★★★★☆ 4.0 (116件のレビュー) 著者: 中島 能和, 濱野 賢一朗 Linux教科書 LinuCレベル2 Version 10.0対応 LinuCレベル2の認定テキスト。仮想マシンやネットワークの構築など、より高度な技術を解説。実践的なスキルを身につけられます。 ★★★★☆ 4.1 (48件のレビュー) 著者: 中島 能和, 濱野 賢一朗 Linux教科書、通称 あずき本 。 インプットと各章の問題、それと模擬試験が付いています。 まずは一読して、各章の問題を解きましょう。模擬試験は最後に。 スピードマスター LinuC レベル1 スピードマスター問題集 LinuCレベル1の対策問題集。472問の問題と丁寧な解説でテンポよく学習できる一冊。試験直前の総仕上げにも最適です。 ★★★★☆ 4.2 (82件のレビュー) 著者: 山本 道子, 大竹 龍史 LinuCレベル2 スピードマスター問題集 待望のLinuCレベル2対策問題集。475問を収録し、201・202試験の範囲を網羅。実力アップと弱点克服に効果的な構成です。 ★★★★☆ 4.1 (20件のレビュー) 著者: 大竹 龍史 スピードマスター問題集、通称 スピマス 、あるいは白本。 各章での問題と丁寧な解説、模擬試験が付いてます。 あずき本でインプットが出来たらこちらに着手します。 この時、解説をよく読んで不明点があればGoogle先生なり各種AIなりに聞いて疑問点を解消しましょう。 あずき本とスピマスの各問題が解けるようになったら、それぞれの模擬試験を解きます。 試験当日、全問題ほぼ100%解ければまず合格できるかと思います。 おまけ LPI-Japan 公式例題 LPI-Japan公式サイトに例題と解説があります。 上記の参考書のみでも合格できますが、スマホでスキマ時間に読み進めるといいでしょう。 レベル3 303試験 レベル3は学習コンテンツがぐっと減り、あずき本やスピマスがありません。 ping-tと公式例題だけでがんばりましょう。 ping-t 私は ping-tだけで合格できました。 スピマス同様、1週目は必ず解説をじっくり読みましょう。ping-t内でAIアシスタント解説機能がありますので、疑問点があればこちらも活用しましょう。 ping-tではこんな感じで問題の進捗度合いが換算されます。 レベル40/40 までブラッシュアップできれば、まず合格できるかと思います。 また、コマ問といって記述式の問題もあります。 コマ問の方はちゃんとやらなくて大丈夫です。 理由としては、①選択式問題をしっかりやって理解を深めていけば記述もある程度正答できる ②本番での記述問題は60問中3問程度と出題数が低い からです。 選択式に飽きたら息抜き程度に挟むといいです。 勉強時間 合格に要した勉強時間は以下の通りです。 あくまで私の場合ですので、自分のペースで計画を立ててみましょう。 101, 102試験 それぞれ 週20時間 × 3週間 = 計60時間 これは机に向かってあずき本・スピマスをこなした時間です。 この辺りはなんだかんだ基本コマンドに関する問いが多く、覚える量も少ないのでサクッととれました。 201, 202試験 それぞれ 週20時間 × 4週間 = 計80時間 特に202試験は範囲が膨大でありながら問われる内容もマニアックなので、鬼門です。心してかかりましょう。 ただ結局、覚えていれば解けるし覚えていなければ解けないものなので、ダラダラと時間をかけずに短期集中で取り組むことをオススメします。 303試験 週20時間 × 3週間 = 計60時間 303試験に限らずレベル3系は202試験と内容が重複しています。 ですので202試験まで突破できれば、意外とあっさり乗り越えられます。 ここまで来たらもう一息です。がんばりましょう。 さいごに 勉強に際してちょっとしたコツみたいなものがあります。 はじめは暗記量が膨大で絶望しますが、しばらく勉強を続けていくとコマンドの組み立て方やオプションなどで共通した考え方が見えてきます。 この感覚にきちんと向き合って言語化できれば、暗記量を節約できてきます。 例えば、ファイルの移動をする mvコマンド と、異なるホスト間でファイルをアップロード・ダウンロードする scpコマンド があります。 これは コマンド <移動元> <移動先> と覚えてしまえば、暗記量を節約できますよね。 こんな感じに 抽象化してパターン化できれば暗記しなきゃいけないことが減ってきます 。 ただ202あたりになってくると、 似たような操作パターンなのに全然違う ってことがかなり増えてきます。 そういったものはどうしても混同して間違えやすいです。ですので 発見するたびに書き出して、試験直前の確認シートにして しっかり復習して臨みましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【LinuC スピード合格体験記】LinuC 合格に必要な勉強時間・学習コンテンツ・学習方法まとめ first appeared on SIOS Tech. Lab .
アバター
こんにちは。サイオステクノロジー OSS サポート担当 山本 です。 今回は keycloak (というか SSO) ってどんなもの?というお題でざっくり話してみようと思います。 今日、インターネットを使用しているのであれば SSO の恩恵を受けていないということは稀かと思いますし、どんなものかもある程度以上知っている方も多いかと思います。 が、個人的に keycloak を調べてみる機会があって合わせて周辺の情報を確認しなおしたので、折角なのでざっくりとまとめてみようと思った次第です。 ぼんやりふんわりとした理解の助けになれば幸いです。 ■SSO ってなんだ? SSO (シングルサインオン) は、その名のとおり サインオン (=ログイン、サインインなど) に関する技術で、主に Web サービスや Web アプリケーションで活躍するものです。 改めてお話しするまでもないかとは思いますが、サインオンは そのユーザを識別する何らかのキー (例えば、「 ユーザ名 + パスワード 」など) によって ユーザを識別 し、そのユーザ向けのサービスを提供したり、そのユーザ自身が登録した情報にアクセスできるようにしたりするための処理です。 例えば、電子メールサービスを扱うならばそのユーザ宛てのメールやそのユーザ自身が作成したメールだけを見れるようにしなければいけませんし、電子書籍サービスを扱うならばユーザが購入した書籍を識別できなければいけません。 この他にも様々な観点において、Web サービスなどは ユーザを識別できなければ立ち行きません 。 このため、サインオンという考え方は Web サービスなどにおいて非常に重要なものとなります。 …と改めて前置きをしたところで、まずは SSO と普通のサインオンの違いを確認しておきましょう。 (以下では全てのサインオンは「ユーザ名 + パスワード」で行うものと仮定してお話しします。) ■通常のサインオンの場合 あるユーザが「HogeHoge メール」というサービスを利用しようとしたとします。 この場合、そのユーザは「HogeHoge メール」に登録した、 「HogeHoge メール」用のユーザ名 + パスワード でサインオンを行なってサービスを利用することになります。 その後、そのユーザが「FugaFuga 動画」というサービスを利用しようとしたとしましょう。 そうしたら、そのユーザは 別途 「FugaFuga 動画」に登録した、 「FugaFuga 動画」用のユーザ名 + パスワード でサインオンを行なってサービスを利用することになります。 ……それはそうだろ、という感じですね。 このように、従来型の通常のサインオンでは 使用するサービスごとに識別情報・認証情報の登録やサインオン操作が必要 になります。 ■SSO の場合 SSO では、” SSO 認証サーバ ” などと呼ばれるいわゆる 親玉 的なモノが出てきます。 この「親玉」は様々な 他のサービスと連携 し、 連携先のサービスのサインオン操作などを肩代わり します。 これにより、ユーザは 「親玉」用の 1種類の認証情報 (「ユーザ名 + パスワード」など) だけでその 「親玉」と連携しているあらゆるサービスを効率よく使うことができます 。 大雑把なイメージでのお話にはなりますが、少し細かく見てみましょう。 ■SSO の例1:サインオン操作の肩代わりと入力回数低減 あるユーザAが SSO 認証サーバ「PiyoPiyo 認証」と連携している「HogePiyo メール」というサービスを利用しようとしたとします。 そのユーザAが「HogePiyo メール」サービスを利用しようとすると、”「HogePiyo メール」から来た” という情報を付けた状態で「PiyoPiyo 認証」へ飛ばされます。 ユーザAは飛ばされた「PiyoPiyo 認証」で、「PiyoPiyo 認証」に登録した 「PiyoPiyo 認証」用のユーザ名 + パスワード でサインオンを行ないます。 「PiyoPiyo 認証」でのユーザ認証に成功すると、「PiyoPiyo 認証」は  ・「HogePiyo メール」に対して “(「PiyoPiyo 認証」の) ユーザ ID” などのようなユーザを識別する情報 (+ その他必要になる情報)  ・ユーザAに対して「HogePiyo メール」のページに飛ばす処理 と、このユーザAが「HogePiyo メール」を使用する際に、このユーザAからのアクセスであると「HogePiyo メール」が識別できる何らかの手段・情報をそれぞれに送ります。 これで、このユーザAは無事「HogePiyo メール」サービスを利用することができるようになります。 ここで、この ユーザAは「PiyoPiyo 認証」にしかサインオン処理を行なっていない のがポイントです。 さて、その後さらにこのユーザAが先と同じ SSO 認証サーバ「PiyoPiyo 認証」と連携している「FugaPiyo 動画」というサービスを利用しようとしたとします。 基本的な流れは先の「HogePiyo メール」の例と同じで、そのユーザAが「FugaPiyo 動画」サービスを利用しようとすると、”「FugaPiyo 動画」から来た” という情報を付けた状態で「PiyoPiyo 認証」へ飛ばされます。 さて、先ほどはここで「PiyoPiyo 認証」でサインオンを行ないましたが、このユーザAは先ほど「HogePiyo メール」を利用しようとした時に、 既に「PiyoPiyo 認証」へのサインオンを済ませています 。 このため、 改めて「PiyoPiyo 認証」用のユーザ名 + パスワードを 入力することなく 「FugaPiyo 動画」を利用できる状態になるというわけです。 概ねこのような流れで、SSO を利用すると ユーザは一度のサインオン操作で複数のサービスを利用できる ようになります。 なお、サービス側はあくまでサインオンを肩代わりしてもらっているだけであり、サービス自体の情報 (「HogePiyo メール」の管理しているメール情報や、「FugaPiyo 動画」の購入履歴・利用プラン情報など) は別途そのサービス自体が管理することになります。 このような役割を担う SSO の仕組みとしては、 Kerberos 認証 、 SAML 、 OpenID-Connect (OIDC) などが挙げられます。 ■SSO の例2:ユーザ情報の共有 先の例を流用して、例えば「FugaPiyo 動画」で “「PiyoPiyo 認証」のメールアドレスでメールマガジン登録” という操作ができる場合のことを考えてみましょう。 この場合、「FugaPiyo 動画」は「PiyoPiyo 認証」に登録されているメールアドレスを取得する必要があります。 (SSO 認証サーバ「PiyoPiyo 認証」には、ユーザ情報としてメールアドレスも登録されているものとします。) あるユーザAがこの操作を行うと、「FugaPiyo 動画」は「PiyoPiyo 認証」にこのユーザAのメールアドレスが必要な旨を伝えます。 もちろん、いくら連携しているとは言え「PiyoPiyo 認証」は(セキュリティ的に考えて)無条件でメールアドレスを渡すわけにはいきません。 そこで、「PiyoPiyo 認証」は “「FugaPiyo 動画」がユーザAのメールアドレスを取得しようとしているよ” という情報を含めた合わせ鍵を用意して、「FugaPiyo 動画」に渡します。 「FugaPiyo 動画」は「PiyoPiyo 認証」から受け取った合わせ鍵 (ユーザA用の部分) や、後で「FugaPiyo 動画」に戻る際のアドレスなどの情報をまとめて、ユーザAに送ります。 ユーザAに送られる情報には「PiyoPiyo 認証」に飛ばす処理も含まれているので、ユーザAは「PiyoPiyo 認証」に飛ばされます。(ユーザAが「PiyoPiyo 認証」にサインオンできていない場合、ここでサインオン操作をする必要があります。) ユーザAが合わせ鍵を「PiyoPiyo 認証」に渡すと、「PiyoPiyo 認証」は合わせ鍵の内容を確認して、 ユーザAに [「FugaPiyo 動画」にメールアドレスを渡すけど、いいのか?] と 確認を行ないます 。 ユーザAがこれを承認すると、「PiyoPiyo 認証」はこの合わせ鍵に「承認済み」フラグを記録し、ユーザAは「FugaPiyo 動画」に戻されます。 「FugaPiyo 動画」はユーザAが戻ってきたことを確認すると、改めて合わせ鍵を使って「PiyoPiyo 認証」にユーザAのメールアドレスを要求します。 「PiyoPiyo 認証」は合わせ鍵の正当性や「承認済み」になっているか、要求元が間違っていないかなどを確認した上で、問題がなければメールアドレスの情報を持っている リソースサーバ に “「FugaPiyo 動画」にユーザAのメールアドレスを渡すように” という通達と合わせ鍵を、「FugaPiyo 動画」にはリソースサーバの場所と合わせ鍵を渡します。 そうして、「FugaPiyo 動画」はリソースサーバに合わせ鍵を持ってアクセスすることで、目的のユーザAのメールアドレスを得ることができます。 ……行ったり来たりでややこしいですが、このほとんどは 使い捨てのデータによる承認や確認処理 で、  ・ユーザAは SSO 認証サーバ「PiyoPiyo 認証」で サインオンと承認をしただけ  ・やりとりされるのは ユーザが許可した メールアドレスのみ  ・実際にメールアドレスのやり取りをしているのは「FugaPiyo 動画」とリソースサーバのみ なので、ユーザは少ない手間で、かつデータのやり取りはかなり安全に行なうことができます。 と、基本的には概ねこのような流れで、SSO によって SSO 認証サーバが連携する範囲で登録したデータをやりとり させることができます。 ここではメールアドレスを例にしたためあまり便利そうには見えないかもしれませんが、例えば SNS の投稿を連携したり、荷物の宛先情報などを連携したり……などであればだいぶ便利そうに感じれる…ハズです。 このような役割を担う SSO の仕組みとしては、 OAuth が挙げられます。 ■SSO のメリットは? さて、ゴチャゴチャ話してみましたが、結局 SSO というのは何が嬉しいのかを考えてみましょう。 まず、ユーザは 認証情報の管理がラク になります。 SSO 認証サーバの連携する範囲内のサービス・アプリケーションで1回だけ認証処理をすればよく、当然覚えておく認証情報も1つだけで済みます。また、場合によってはユーザの入力した情報 (各種個人情報や SNS 等の投稿など) を別サービスから必要に応じて参照させることで、同じ情報を複数回入力する手間も削減することができることもあります。 旧来はサービス・アプリケーションごとに別々の認証情報を用意し、必要に応じて各サービスに別々で手動での情報入力を行う必要があったことを考えると、ユーザにとってはかなりラクになることでしょう。 SSO 認証サーバの管理者・連携先としては…… まず、組織内で複数サービスのサインオン処理の統合のために使っている場合、SSO 認証サーバのアカウントだけ管理すればよくなるので、個別のサービスごとにアカウント管理するよりも アカウント管理の手間が減る 可能性が高いです。 外部の SSO 認証サーバと連携する場合だと、自システムに認証情報を保存しなくて済みます。このため、(その他の個人情報などを取得していなければ) 漏洩して困る情報を手元に置かなくて済むようになり、万が一自システムでの情報漏洩が出てしまった場合でも対ユーザへの被害が深刻になりにくい可能性が考えられます。また、 ユーザ側の新規登録の手間が省かれる ので、 ユーザにサービス自体を触ってもらいやすくなる 可能性も考えられます。 このように、認証の一元化による管理の容易さとユーザビリティの向上が見込めることが SSO のメリットと言えるでしょう。 ただし逆に、万が一 SSO 認証の認証情報が漏洩した場合、連携先のサービスなども含めて丸ごと不正利用される恐れがあります。 そのため、SSO 認証サーバ自体、および SSO 認証サーバの通信や SSO の認証情報には十全なセキュリティ対策が求められます。 (もっとも、SSO により管理を一元化することでより複雑な認証情報・認証方法を採用しやすくなったり、逆に SSO を使用していない場合にユーザが認証情報をメモして残したり使い回しをしたり…といったことを考えると、SSO のセキュリティリスクは高いというわけではありません。) ■keycloak ってなんだ? keycloak は、正にここまでお話ししてきた “ SSO 認証サーバ ” の1種です。 SAML 、 OpenID-Connect (OIDC) 、 OAuth に対応しており、管理コンソールやユーザ連携、ユーザ制御などの機能を備えています。 今回は前置きが長かったので、管理コンソールとそれぞれ今回のお話の何をどれで設定するのかだけ確認します。 まず、デモ環境を立ち上げます。今回は手早く podman で作ってしまいましょう。 podman を導入して、以下のようなコマンドを実行すれば OK です。 $ podman run --name test -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.2.4 start-dev (このコマンドはセキュリティ設定などを省いて動かせる デモ用・開発用の環境を作成 するためのコマンドであり、実際に 運用する環境を作る場合は絶対に使用してはいけません 。) そうしたら、任意のブラウザで以下のアドレスにアクセスしてみましょう。 http://(IP アドレス):8080/admin サインオンの画面が表示されるので、サインオンしてみましょう。 初期ユーザのユーザ名/パスワードは、podman コマンドの “KC_BOOTSTRAP_ADMIN_USERNAME” と “KC_BOOTSTRAP_ADMIN_PASSWORD” で指定したものです。 (上記のとおりのコマンドだった場合、”admin” / “admin” です。) サインオンに成功すると表示されるのが keycloal の管理コンソールです。 今回は、マークをつけてある今回お話ししたものに対応している3つの要素が何に対応しているかだけ、最後に確認したいと思います。 ・ Clients この keycloak (=SSO 認証サーバ) と 連携するサービス・アプリケーション やその連携方法などを設定します。 連携先のサービス・アプリケーション側でも別途この keycloak と指定した方法で連携するように設定する 必要があります。 今回のお話では「HogePiyo メール」「FugaPiyo 動画」が該当します。 ・ Users この keycloak による SSO 認証のユーザ情報 (認証情報など) を設定します。 今回のお話では「ユーザA」の認証情報が該当します。 ・ Realm (※ “master” となっているプルダウン部分) 設定する SSO 連携の「枠」です。keycloak では複数の Realm を持つことができます。 先述の “Client” と “User” などは Realm ごとに管理されます。 確認できたら、先ほど立ち上げた検証用の keycloak コンテナは念のため削除しておきましょう。 $ podman stop test $ podman rm test ■最後に 今回は SSO と keycloak の概要についてお話ししてみました。 SSO について長々とお話ししましたが、今日日インターネットを使用しているのであれば、恐らくは恩恵にあずかっているはずです。 そう、「google でログイン」「Apple でログイン」「Facebook でログイン」などのような「~でログイン」というアレです。アレこそが、SSO という技術の典型と言えるでしょう。 keycloak はそんな SSO の “親玉” の部分を担うサービスです。 例に挙げたような外部にまで連携する超大規模 SSO サービス……なんてものはまず新しく立ち上げることはないでしょうから、 基本的にはお話の途中でちらりと出した「組織内のサインオン処理の統合」のために使用するものと考えてもらえばよいでしょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post わからないなりに理解したい keycloak①:そもそも SSO って何だ? first appeared on SIOS Tech. Lab .
アバター
はじめに お久です!皆さんAIにどれぐらい課金していますか?動画配信サービスより、AIサービスのほうが課金額が高くなっている龍ちゃんです。サービスごとに特色もあり、得意領域もそれぞれ異なるので、いっそのこと10万ぐらいぶっこんでしまいたいところですね。 さて!今回は、AIサービスを使うにあたって便利にしていこうというお話です。具体的な部分としては「SlackからNotebook LMに簡単にデータを取り込む方法」のプロトタイプについてです。 今回はベースのコンテンツを作成して、執筆はAIにやらせてみようかと思います。 なぜこのシステムを開発したのか? このプロトタイプを開発した背景には、AIサービスの活用における課題があります。特に、現在様々なAIサービスが登場し、それぞれの特色や得意分野が異なる中で、効率的な情報管理と連携の重要性が高まっています。 具体的な課題として、Google Docsを普段使用していないためのデータ連携の困難さや、Slackでの会話内容を効率的にまとめる必要性がありました。また、Slack AIは比較的高価である一方、既に契約済みのNotebook LMを有効活用したいという思いもありました。 そこで今回は、「SlackからNotebook LMに簡単にデータを取り込む方法」のプロトタイプ開発に着手しました。初期段階として、Slackのテキストメッセージのみを対象とし、スレッドを一つの単位として取り込む基本的な機能の実装を目指しています。 Notebook LMでは、一度紐づけたソースが更新された場合は手動でアクションを行う必要があるため完全な自動化には至っていません。ですが、Google Docsを起動することなく情報を転機できるため、AIと開発の連携をより上げることができると期待しています。 龍ちゃん あとは!この記事を読んで作りたくなったからですね! 使用技術 今回開発したシステムは、以下のような技術スタックを使用しています。まず、バックエンドフレームワークとしてNestJSを採用しました。これは既存のデプロイ環境との親和性を考慮した選択です。 @slack/web-apiライブラリ を利用することで、Slackとの連携を効率的に実装することができました。 Slack APIに関しては、主に3つの機能を活用しています。まず、Event Subscriptionsの reactions.item_added を使用してリアクションの検知を行います。次に、メッセージの取得には conversations.history でリアクション対象のメッセージを、 conversations.replies でスレッド内の返信を取得します。また、 chat.postMessage を使用してスレッドへの返信機能も実装しています。 Google Docs APIについては、サービスアカウントを利用して認証を行い、 documents.batchUpdate を使用してドキュメントへのテキスト追記を実現しています。これにより、Slackでの会話内容を自動的にGoogle Docsに記録することが可能になりました。 最終的に、Google DocsとNotebook LMを連携させることで、Slackの内容を活用してNotebook LMを効率的に使用できるようになります。 処理フロー 処理フローは以下の流れになります。 初期設定とメッセージ送信 ユーザーがSlackにメッセージを送信すると、リアクション追加の準備が整います。 Slack Events APIによる監視 バックエンドAPIでは、Slack Events APIを使用してSlackでの活動を監視します。具体的には: POST /api/slack/events エンドポイントでSubscription Eventを受信 reaction_added イベントをキャッチして処理を開始(特定のリアクションの場合は処理) メッセージ情報の取得 リアクションが追加されると、システムは以下の処理を実行します: Slack APIの GET Slack Message を呼び出し Slack APIを用いてメッセージを取得 conversations.history :リアクションがつけられたメッセージ取得 conversations.replies :リアクションをつけられたメッセージにスレッドがあればスレッドのメッセージも取得する Google Docs連携処理 取得したメッセージ情報をもとに、リアクション対応の処理を実行: Google Docs更新処理を開始 Service Accountを使用した認証で安全にアクセス ドキュメントの内容を更新 完了通知 処理完了後、Slackに結果を通知: chat.postMessage でリアクション対象への結果通知 処理結果確認のメッセージ送信 構築 ここでは、実際のシステム構築について詳しく説明していきます。プロトタイプとはいえ、実用的な機能を備えたシステムを構築することができました。以下、主要なコンポーネントごとに実装の詳細を解説していきます。部分的なコードを解説用に添付します。最終的なコードはGitHubのリポジトリとブログの最後に完成版のコードを張ります。 Google Docsの更新 Google Docsを操作するためにサービスアカウントを用いて認証を行っています。 サービスアカウントって何? サービスアカウントとは、ユーザーがログインしなくても、プログラムがGoogleのサービスにアクセスできるようにする、特別なアカウントのことです。人間でいうと「あなたは〇〇のタスクを代わりにやってくれる、もう一人の自分」のような存在です。これにより、システムが裏側で黙々と処理を進めたり、決められたタイミングで情報を記録したりといった、自動化がスムーズに実現できるようになるんです。 Google Docsへの更新処理は、主に以下のような流れで実装しています: まず、Google Cloud Platformでサービスアカウントを作成し、必要なAPIを有効化します。認証情報をJSONファイルとして取得し、これを使用してGoogle APIにアクセスします。NestJSからは、googleapisライブラリを使用してクライアントを作成し、適切なスコープ(documents、drive)を設定します。スコープとしては、以下を指定しています。 スコープ 説明 https://www.googleapis.com/auth/documents Googleドキュメント操作に必要なスコープ https://www.googleapis.com/auth/drive Googleドライブ操作に必要なスコープ サービスアカウントで割り振られたメールアドレスで操作したいGoogleドキュメントに、共有権限でドキュメントを共有します。 認証周りの処理はnest.jsの Configuration を通じて共通的に保持しています。 import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { docs_v1, google } from 'googleapis'; @Injectable() export class EnvironmentsService { constructor(private configService: ConfigService) {} private googleDocs: docs_v1.Docs; get googleDosc() { if (this.googleDocs) return this.googleDocs; const auth = new google.auth.GoogleAuth({ credentials: { client_email: process.env.DOCS_CLIENT_EMAIL, private_key: process.env.DOCS_PRIVATE_KEY.replace(/\\n/g, '\n'), // 環境変数から読み込む場合は改行コードを修正 }, scopes: ['https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/drive'], // 必要なスコープ }); this.googleDocs = google.docs({ version: 'v1', auth, }); return this.googleDocs; } get GoogleDocsID(): string { return this.configService.get('DOCS_ID'); } } 実際のドキュメント操作では、 documents.batchUpdate メソッドでDocsのトップに追記する形で更新します。この際、Slackから取得したメッセージの内容を適切なフォーマットに変換して追記します。 import { Injectable } from '@nestjs/common'; import { EnvironmentsService } from 'src/config/enviroments.service'; @Injectable() export class DocsAccessService { constructor(private readonly env: EnvironmentsService) {} docs = this.env.googleDosc; async updateDoc(docId: string, text: string) { try { const response = await this.docs.documents.batchUpdate({ documentId: docId, requestBody: { requests: [ { insertText: { text: text, location: { index: 1, // Insert at the beginning of the document }, }, }, ], }, }); return response.data; } catch (error) { console.error('Error updating document:', error); throw error; } } } これにより、API経由で内容を自動的にGoogle Docsに記録し、後でNotebook LMで活用できる形式で保存することが可能になりました。 Slackにボットメッセージを送信する Slackからのメッセージ送信では、 Bot User OAuth Tokens を使用します。これには、 channels:history 、 chat:write 、 reactions:read の権限が必要です。 まずは、先ほど取得したトークンを環境変数として参照できるようにします。 import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable() export class EnvironmentsService { constructor(private configService: ConfigService) {} get SlackBotToken(): string { return this.configService.get('SLACK_BOT_TOKEN'); } } メッセージの送信方法は主に2種類あり、 chat.postMessage で通常のメッセージを、 chat.postEphemeral で一時的なメッセージを送信できます。Slack Botの捜査には @slack/web-api を使用しています。 import { Injectable } from '@nestjs/common'; import { ReactionAddedEvent, WebClient } from '@slack/web-api'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { DocsAccessService } from 'src/utils/docs-access/docs-access.service'; @Injectable() export class SlackService { private client: WebClient; constructor( private readonly env: EnvironmentsService, private readonly docService: DocsAccessService, ) { const token = this.env.SlackBotToken; this.client = new WebClient(token); } async postMessage(channelId: string, text: string, threadTs?: string): Promise<void> { try { console.log(`Attempting to post message to channel ${channelId}${threadTs ? ` in thread ${threadTs}` : ''}`); await this.client.chat.postMessage({ channel: channelId, text: text, thread_ts: threadTs, // ここに返信したいメッセージのtsを指定 // optional: icon_emoji: ':robot_face:', // カスタムアイコンを使いたい場合 // optional: username: 'My Reaction Bot', // カスタムユーザー名を使いたい場合 }); // console.log('Message posted successfully:', result.ts); } catch (error) { console.error(`Failed to post message: ${error.message}`, error.stack); if (error.data) { console.error('Slack API error response:', error.data); } throw error; } } // あなただけに表示されています系メッセージ async postMessageEphemeral(channelId: string, text: string, threadTs: string, user: string): Promise<void> { try { console.log(`Attempting to post message to channel ${channelId}${threadTs ? ` in thread ${threadTs}` : ''}`); await this.client.chat.postEphemeral({ channel: channelId, user: user, text: text, thread_ts: threadTs, // ここに返信したいメッセージのtsを指定 // optional: icon_emoji: ':robot_face:', // カスタムアイコンを使いたい場合 // optional: username: 'My Reaction Bot', // カスタムユーザー名を使いたい場合 }); // console.log('Message posted successfully:', result.ts); } catch (error) { console.error(`Failed to post message: ${error.message}`, error.stack); if (error.data) { console.error('Slack API error response:', error.data); } throw error; } } } 二つのメソッドはほとんど同様の使い方ができます。明確な違いとしては、他のユーザーからの視認性・メッセージの修正の有無にあります。 特徴 chat.postMessage chat.postEphemeral 用途 チャンネルやスレッドに永続的なメッセージを投稿する 特定のユーザーに対してのみ一時的な(非公開の)メッセージを投稿する 可視性 そのメッセージが投稿されたチャンネルの全員に見える 指定した user のみに見え、他のチャンネルメンバーには見えない 持続性 チャンネルの履歴に残り、後から参照可能 ユーザーがSlackクライアントを再起動したり、セッションを終了したりすると消える可能性がある(Slackの保証はないが、一時的と認識すべき) 宛先指定 channel パラメータでチャンネルIDまたはユーザーID(DMの場合)を指定 channel パラメータでチャンネルID、user パラメータでメッセージを表示するユーザーID を指定 スレッド返信 thread_ts パラメータでスレッドに返信可能 thread_ts パラメータでスレッド内の特定のユーザーに返信可能 APIスコープ chat:write(通常) chat:write.public(パブリックチャンネルのみ) chat:write.private(プライベートチャンネルのみ) chat:write または chat:write.ephemeral メッセージの編集/削除 chat.update や chat.delete で後から編集・削除が可能 基本的に後から編集・削除する機能はない (表示されるかどうかはSlackクライアントに依存するため) リアクションイベントを受け付ける Events Subscriptionsを設定することで、Slackでのリアクションなどのイベントを検知できるようになります。今回のプロトタイプでは、メッセージへのリアクション追加・削除を監視するため、 reaction_added と reaction_removed イベントを使用しています。 処理としては、URL検証用リクエスト url_verification とイベントコールバック event_callback を取得する処理が割り振られています。 import { Body, Controller, Post, UseGuards } from '@nestjs/common'; import { SlackService } from './slack.service'; import { SlackBotSignatureGuard } from 'src/common/guard/slack-bot-signature/slack-bot-signature.guard'; import { SlackEvent } from '@slack/types'; @Controller('/api/slack/') export class SlackController { constructor(private readonly slackService: SlackService) {} @UseGuards(SlackBotSignatureGuard) @Post('events') async handleSlackEvents(@Body() payload): Promise<string | { challenge: string }> { // SlackのイベントがURL検証リクエストの場合は、challengeを返す // これにより、Slackがイベントサブスクリプションを確認できるようになります if (payload.type === 'url_verification') { console.log('URL Verification Request handled.'); return { challenge: payload.challenge }; } const event: SlackEvent = payload.event; // イベントのタイプがサポートされていない場合はログを出力して終了 // ここでは 'event_callback' タイプのみを処理する // 必要に応じて他のイベントタイプを追加することができます if (payload.type !== 'event_callback' || !event) { console.log('Unsupported Slack event type:', payload.type); return 'OK'; // Slackに200 OKを返却 } // イベントの処理を行う if (event.type === 'reaction_added') { // リアクションが 'notebooklm' の場合のみ処理を行う if (event.reaction == 'notebooklm') { this.slackService.updateDataSource(event); } return 'OK'; // Slackに200 OKを返却 } return 'OK'; // Slackに200 OKを返却 } } リアクションイベントの型定義は こちらのリファレンス を参照してください。リアクション追加判別後、特定のリアクションが追加されたときのみ処理を行うようにしています。リアクション追加イベントには、リアクション情報とリアクションがつけられた対象を特定するための情報(ts/channel)が含まれています。こちらを使用してメッセージを取得します。 Slack スレッドに送信されたメッセージをすべて取得 import { Injectable } from '@nestjs/common'; import { ReactionAddedEvent, WebClient } from '@slack/web-api'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { DocsAccessService } from 'src/utils/docs-access/docs-access.service'; @Injectable() export class SlackService { constructor( private readonly env: EnvironmentsService, ) { const token = this.env.SlackBotToken; this.client = new WebClient(token); } async getMessage(channelId: string, ts: string): Promise<string> { try { const result = await this.client.conversations.history({ channel: channelId, latest: ts, limit: 1, inclusive: true, }); if (result.messages && result.messages.length > 0) { const message = result.messages[0]; if (message.thread_ts) { const repilesResponse = await this.client.conversations.replies({ channel: channelId, ts: message.thread_ts, }); const replies = repilesResponse.messages .map((reply) => { // TODO:BOTが返信したメッセージを除外する if (reply.bot_id) return ''; return reply.text ? reply.text : ''; }) .join('\n'); console.log('Replies retrieved successfully:', replies); return replies; } return message.text ? message.text : ''; } else { console.warn('No messages found for the given ts'); return null; } } catch (error) { console.error(`Failed to get message: ${error.message}`, error.stack); if (error.data) { console.error('Slack API error response:', error.data); } throw error; } } } Slackのメッセージ取得について、いくつかの重要な点と制限事項があります。リアクションイベント内に含まれるチャンネルIDとts(タイムスタンプ)で conversations.history を limit:1 で実行することで親スレッドを特定します。これはスレッド内にてリアクションイベントが発生しても、取得できるのは親スレッドの情報というSlack API特有の仕様です。 API仕様書はこちらになります 。 スレッドを含むメッセージを完全に取得するためには、スレッド全体を別途取得する必要があります。BOTからの自動返信もメッセージとして含まれる可能性があり、これが実際の利用者の会話の流れを把握する際に不都合を生じさせることがあります。そのため、メッセージのフィルタリングや処理方法について、慎重な設計が必要となります。今回は、 conversations.replies の結果でBOTの情報が含まれる場合はメッセージを除外しています。 API仕様書はこちらになります 。 リアクションイベントからGoogle Docsの取得までを一つの処理としてまとめる ここまで、個別の処理に切り分けて実装していました。最終的にコントローラーから処理を受け取るサービスとして一つの関数にまとめていきます。 import { Injectable } from '@nestjs/common'; import { ReactionAddedEvent, WebClient } from '@slack/web-api'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { DocsAccessService } from 'src/utils/docs-access/docs-access.service'; @Injectable() export class SlackService { private client: WebClient; constructor( private readonly env: EnvironmentsService, private readonly docService: DocsAccessService, ) { const token = this.env.SlackBotToken; this.client = new WebClient(token); } async updateDataSource(event: ReactionAddedEvent): Promise<void> { const channelId = event.item.channel; const ts = event.item.ts; const message = await this.getMessage(channelId, ts); if (!message) { console.warn(`Message not found for channel ${channelId} and ts ${ts}`); } if (message != '') { const docId = this.env.GoogleDocsID; const text = `\n---\n${message}\n---\n`; await this.docService.updateDoc(docId, text); this.postMessage( channelId, `Notebook LMデータソースに追記しました: ${event.reaction} by <@${event.user}> \n\n${message}`, ts, // ここでスレッドのtsを指定 ); } else { this.postMessage( channelId, `現在テキストソースにしか対応していません。リアクションをつけたメッセージはテキストが空でした。`, ts, // ここでスレッドのtsを指定 ); } } } 今回解説した関数を利用して、イベントを受け取りGoogle Docsの更新・SlackへのBotによるメッセージ送信までの実装が完了しました。 エラーハンドリングと署名検証 エラーハンドリングと今後の課題について詳しく見ていきましょう。 Slackの署名検証 公式でも言及されていますが、アクセスがSlackからのものであることを確約する 必要があります。nest.jsではGuardsという機能を用いて、コントローラーにアクセスする前に事前に検証をすることができます。処理自体は公式の情報をもとに構築してあります。 必要になるのは、Slack Developerで取得することができる Signing Secret です。 import { CanActivate, ExecutionContext, Injectable, RawBodyRequest, UnauthorizedException } from '@nestjs/common'; import { createHmac, timingSafeEqual } from 'crypto'; import { Observable } from 'rxjs'; import { EnvironmentsService } from 'src/config/enviroments.service'; @Injectable() export class SlackBotSignatureGuard implements CanActivate { constructor(private readonly env: EnvironmentsService) {} private readonly MAX_TIMESTAMP_AGE_SECONDS = 300; // 5分 (リプレイアタック防止のため) canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest<RawBodyRequest<Request>>(); const slackSignature = request.headers['x-slack-signature'] as string; const slackTimestamp = request.headers['x-slack-request-timestamp'] as string; const rawBody = (request as any).rawBody; // main.ts で bodyParser を設定して取得 if (!slackSignature || !slackTimestamp || !rawBody) { console.error('署名検証が失敗しました: 必要なヘッダーまたはボディが不足しています。'); throw new UnauthorizedException('署名検証が失敗しました: 必要なヘッダーまたはボディが不足しています。'); } // リプレイアタック const timestamp = parseInt(slackTimestamp, 10); const currentTime = Math.floor(Date.now() / 1000); // Unixタイムスタンプ (秒) if (Math.abs(currentTime - timestamp) > this.MAX_TIMESTAMP_AGE_SECONDS) { console.warn( // 日本語にしてエラーメッセージをわかりやすくする `Slackリクエストのタイムスタンプが古すぎるか、未来のものです。タイムスタンプ: ${timestamp}, 現在: ${currentTime}`, ); throw new UnauthorizedException('Slackリクエストのタイムスタンプが古すぎるか、未来のものです。'); } // Slackの署名検証 const baseString = `v0:${timestamp}:${rawBody.toString()}`; const hmac = createHmac('sha256', this.env.SlackBotSigningSecret); hmac.update(baseString); const computedSignature = `v0=${hmac.digest('hex')}`; if (!timingSafeEqual(Buffer.from(computedSignature), Buffer.from(slackSignature))) { console.warn('Slackの署名が無効です。'); throw new UnauthorizedException('Slackの署名が無効です。'); } return true; } } エラーハンドリング プロトタイプとしての現在の実装では、基本的なエラーハンドリングのみを実装していますが、本番環境での運用を想定した場合、以下のような設計が必要になりそうです。 Slack API関連のエラー処理: レート制限への対応とリトライロジックの実装 API接続タイムアウトの適切な処理 チャンネルアクセス権限エラーの処理 Google Docs API関連のエラー処理: 認証エラーの適切な処理とトークンリフレッシュ ドキュメント編集権限エラーのハンドリング API制限到達時の待機ロジック実装 システム全般のエラー処理: エラーログの構造化と保存 重要なエラーの管理者への通知システム システムの状態回復メカニズムの実装 これらのエラーハンドリングを実装することで、システムの安定性と信頼性が大幅に向上します。また、運用面でのトラブルシューティングも容易になります。 今後の展望 今回のプロトタイプ開発を通じて、SlackからNotebook LMへのデータ連携という基本的な仕組みは構築できました。しかし、これはあくまで第一歩であり、より実用的で価値のあるシステムに発展させるための道筋がいくつか見えてきました。 機能拡張の方向性 ファイル形式の対応拡大 現在はテキストメッセージのみの対応ですが、Slackでは画像、PDF、スプレッドシートなど様々なファイルが共有されます。特に、画像からのOCR処理やPDFの内容抽出機能を追加することで、より包括的な情報収集が可能になります。Google Cloud VisionやDocument AIとの連携により、これらの実装は十分現実的です。 リアクション種別による分類機能 現在は単一のリアクション( :notebooklm: )のみに対応していますが、複数のリアクションを使い分けることで、情報を自動分類できるようになります。例えば: :important: → 重要な情報として優先度高でマーク :todo: → タスクリストとして別ドキュメントに記録 :knowledge: → ナレッジベース用ドキュメントに整理 この仕組みにより、単なるデータ収集から、目的別の情報整理システムへと発展させることができます。 AI要約機能の統合 現在はSlackのメッセージをそのままGoogle Docsに転記していますが、長いスレッドや議論については、Azure OpenAIやAnthropic APIを活用した要約機能を追加したいと考えています。これにより、本質的な内容のみを抽出してNotebook LMに渡すことが可能になり、より効率的な情報活用が実現できます。 システム改善の取り組み パフォーマンスとスケーラビリティの向上 現在の同期処理から非同期処理への移行は必須です。Redis Queueやbull.jsを使用したジョブキューイングシステムを導入し、大量のメッセージ処理にも対応できる構成に変更予定です。また、Google Docsの容量制限を考慮し、定期的なドキュメント分割機能も検討しています。 ユーザーエクスペリエンスの改善 現在のシンプルなBot返信から、より詳細なフィードバック機能への拡張を計画しています。処理状況の可視化、エラー時の分かりやすい説明、さらにはSlashコマンドを使った手動操作機能なども追加したいところです。 監視とメンテナンス機能 本格運用を見据えて、システムヘルスチェック機能やログ分析ダッシュボードの構築も重要です。特に、API使用量の監視やエラー傾向の分析機能により、安定したサービス提供を目指します。 技術的挑戦 Notebook LM APIの活用 現在はGoogle Docsを経由した間接的な連携ですが、今後Notebook LM APIが公開された際には、より直接的な統合を検討したいと思います。これにより、リアルタイムでの質問応答機能や、自動的なインサイト生成なども実現可能になるかもしれません。 マルチプラットフォーム対応 Slack以外のコミュニケーションツール(Microsoft Teams、Discord、Mattermost等)への対応も視野に入れています。共通のインターフェースを設計することで、組織の使用ツールに関係なく同様の価値を提供できるシステムを目指します。 セキュリティとコンプライアンス強化 企業利用を考慮し、データの暗号化、アクセス権限の細分化、監査ログの充実などを進める必要があります。特に、個人情報や機密情報を含む可能性のあるSlackメッセージの取り扱いについては、慎重な設計が求められます。 おわりに 今回開発したプロトタイプは、AIを活用した情報管理の可能性を示す小さな一歩でした。しかし、ここから得られた知見と技術基盤を活かし、より実用的で価値のあるシステムへと発展させていきたいと考えています。 特に、現在のAIブームの中で、単にAIツールを使うだけでなく、既存のワークフローにAIを自然に統合する仕組みの重要性を改めて感じました。SlackのようなコミュニケーションツールとNotebook LMのような分析ツールを橋渡しすることで、日常的な業務の中で自然にナレッジが蓄積され、活用される環境を作ることができそうです。 今後も継続的に改善を重ね、最終的には「気がついたら素晴らしいナレッジベースができていた」と感じられるような、透明で価値のあるシステムを目指していきます。皆さんも、ぜひ様々なAIサービスを組み合わせて、独自の価値を生み出すシステム構築にチャレンジしてみてください! 弊社ではAI活用頑張ってますので、こちらも併せてチェック!! 2025-05-31 PRレビューを自動化しよう!GitHub Copilot × システムプロンプトの基本 2025-05-30 GitHub Copilotをチーム開発で使いこなす!システムプロンプト設定方法 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Notebook LMへのデータ収集をSlack Botで効率化する開発 with Google Docs first appeared on SIOS Tech. Lab .
アバター
先日、久しぶりにGoogle App Script(GAS)で作成したコードのメンテナンスのために、GASのエディタを開いた龍ちゃんです。既存のコードが動いているからと言って、自分がメンテしていない外部APIを使用していると、知らないうちに更新が入る可能性もありますよね。やはり失敗を検知する仕組みは必要ですね。 今回は「GASの実行可能API」についてのお話です。 GASの公開方法としては「ウェブアプリ」「実行可能API」の二つがあります。 実行可能APIとしてデプロイされたアプリを実行するためには、Google OAuth2.0を利用して認可を行い、アクセストークンを取得する必要があります。 実際にアクセストークンを取得する流れをステップバイステップで解説していきます。 前提条件 バックエンドはnest.jsを使用して作成しています。具体的なコードに関しては、ブログの最後に記載しておきます。 デプロイ先 想定している環境としては、クライアントをAzure Static Web App(SWA)、バックエンドをAzure App Seriveで構築しています。SWAとApp ServiceはAPIリンク(Bring Your Own Functions方式)で接続しています。APIリンクの場合は、バックエンドを /api にルーティングして同一ホストとして処理することができます。 ソースコードはGitに上がっています。 リポジトリ 説明 https://github.com/Ryunosuke-Tanaka-sti/2025-line-liff-frontend フロントエンド https://github.com/Ryunosuke-Tanaka-sti/2024-line-liff-app-backend/tree/main バックエンド 実行可能APIを作成する(Google App Script) 検証のために以下の簡易的な関数を作成してデプロイをします。 String : “Hello World!!”を返答する関数 Google Sheetに書き込まれている情報をすべて取得・返答する Google Sheetに一行追加する 実行可能APIとしてデプロイする前にGASのエディタ上で実行しておくと、デバッグすることができます。 Health Check Function:Hello Worldを返答する関数 非常に単純な関数です。 function healthCheckFunction() { return "Hello World!"; } getSheetAllData:Google Sheetに書き込まれている情報をすべて返答 GASからGoogle Sheetを操作して、Sheet内にあるすべての情報を取得する関数になります。GASからGoogle Sheetを操作する方法に関しては、こちらの「 Google Apps Script スプレッドシート編 初心者向け 」でまとめています。 function getSheetAllData() { // SHEET_ID・SHEET_NAMEの情報を更新してください const file = SpreadsheetApp.openById("SHEET_ID") const sheet = file.getSheetByName("SHEET_NAME") const lastRow = sheet.getLastRow() // 情報がなければスルー if (lastRow == 1) return[] const itemList = sheet.getRange(2, 1, lastRow - 1, 2).getValues() return itemList } insertDataToTargetSheet:Google Sheetに一行情報を追記する こちらはGoogle Sheetの一番最後の行に情報を追記します。こちらも同様に「 Google Apps Script スプレッドシート編 初心者向け 」でまとめています。 function insertDataToTargetSheet(url = "test") { // SHEET_ID・SHEET_NAMEの情報を更新してください const file = SpreadsheetApp.openById("SHEET_ID") const sheet = file.getSheetByName("SHEET_NAME") const lastRow = sheet.getLastRow() sheet.getRange(lastRow + 1, 1).setValue(url) } 引数でテキスト情報(URL)を受け取り、その情報をSheetに記入しています。 実行可能APIとしてデプロイする 実行可能APIとしてデプロイする前にプロジェクトをGCPと接続する必要があります。これはGASをAPI経由で実行する際、GCPプロジェクトから認可を発行して権限を確認するためです。 あとは、デプロイを作成しましょう。 GASが使用しているOAuthスコープを確認する プロジェクトの概要に移動するとGASプロジェクトが使用しているOAuthスコープを確認することができます。こちらは、実行可能APIとしてデプロイ後、外部からAPIをたたく際に取得するアクセストークンのスコープに収める必要があります。 今回であれば、以下のスコープになります。 スコープ 概要説明 https://www.googleapis.com/auth/script.external_request GASから外部のAPIへアクセスする際に必要(UrlFetchApp) https://www.googleapis.com/auth/spreadsheets GASからGoogle Sheetを操作するのに必要 Google Script Run実行 構築に必要なエンドポイントは4つになります。フロントエンド側から実行する順番に解説をしていきます。 有効なトークンを保持しているか検証エンドポイント /api/google-auth/verify 200の場合はトークン発行済み 401の場合は認可フロー開始:URL発行 Google認可フロー OAuth2.0認可用URL発行エンドポイント /api/google-auth OAuth2.0 Callbackエンドポイント /api/google-auth/callback Google Script Run実行用エンドポイント /api/google-auth/test ソースコードは長くなるので、最後にまとめて記載します。Gitのリポジトリとしては、 こちら を参照してください。 Google認可プロバイダー設定 Googleの認可プロバイダーの設定をする必要があります。「承認済みのJavaScript生成元」「承認済みのリダイレクトURI」は適宜設定してください。 ローカルで検証する場合は、以下の値を設定していました。赤枠の値は後で必要になるので、値をコピーしておいてください。 プロパティ 値 承認済みのJavaScript生成元 http://localhost:5000 承認済みのリダイレクトURI http://localhost:5000/api/google-auth/callback 次に API Library にアクセスして必要になるAPIを有効化させます。 Apps Script API を有効にしています。 nest.jsで開発するためには Google Auth Library を導入する必要があります。 npm install google-auth-library クライアントの作成には赤枠から情報を取得した情報を使用する必要があります。 import { OAuth2Client } from 'google-auth-library'; const client = new OAuth2Client({ clientId: "CLIENT ID", clientSecret: "CLIENT SECRET", redirectUri: "REDIRECT URI", }); 環境変数としてはConfigurationを使用して 保存しておけばアクセスがしやすくなります。 1. トークンを取得済みか検証する こちらのエンドポイントでは、Cookiesにトークンが保持されているかを確認します。Cookieに保存されているトークンを検証して、期限切れの場合は認可用のURLを発行して認可フローへ誘導します。 実装パターンとしては、401のエラーメッセージを拡張して認可用URLを埋め込んで返答しています。クライアント側で一度 /api/google-auth/veify を叩くことで認可まで一気に進めることができます。 2. Google認可フロー 認可フロー開始からアクセストークン取得までを一気に解説します。認可用URLにリダイレクトするとGoogleの画面が入るのでアカウント情報を入力すると、リダイレクトURIに設定したパスに認可コード付き(クエリ)でコールバックが返ってきます。 認可コードからIDトークンとアクセストークンを取得することができ、Cookiesに情報を保持します。Cookiesの保存期間としては1時間を保存期間としています。 最終的に好きな画面にリダイレクトさせれば完了です。 3. Google Script Run実行フロー 実行可能APIを外部から実行するためには、実行可能APIのスクリプトID・アクセストークン・実行したい関数名が必要になります。 公式リファレンスとしてはこちらになります 。 ヘッダーにアクセストークンを挿入して、URLはスクリプトIDを挿入したURLになります。 const response = await fetch(`https://script.googleapis.com/v1/scripts/${scriptId}:run`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ function: functionName, parameters: parameters || [], }), }); 送信するBodyの中身としては、 function で実行したい関数名を指定して、引数は parameters の配列に収めて送信することで渡すことができます。型定義としては以下になります。 { "function": string, "parameters": [ value ], } 検証用フロントエンド画面 クライアントで検証するために簡易的な画面を作成します。ソースコードの原文としては、 こちら に上がっています。 useGoogleOAuth:認可処理用カスタムHook "use client"; import { useEffect, useState } from "react"; export const useGoogleOAuth = () => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { const verify = async () => { const res = await fetch("/api/google-auth/verify"); if (res.status === 200) { setIsLoading(false); } else if (res.status === 401) { const data = await res.json(); console.log(data); window.location.href = data.url; } else { const data = await res.json(); alert(`認証に失敗しました。:${data.message}`); } }; if (isLoading && typeof window !== "undefined") { verify(); } }, [isLoading]); return { isLoading }; }; 処理は単純です。 /api/google-auth/verify にアクセスして、401が出たら認可用URIに遷移します。認可が完了するまでは isLoading で状態を管理します。 検証ページ "use client"; import { useActionState } from "react"; import { LoadingMainComponent } from "@/components/LoadingMainComponent"; import { useGoogleOAuth } from "@/hooks/useGoogleOAuth"; export default function GooglePage() { const { isLoading } = useGoogleOAuth(); if (isLoading) return <LoadingMainComponent />; const onClickRead = async ( action: "healthCheckFunction" | "getSheetAllData" ) => { const res = await fetch("/api/google-auth/test", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ functionName: action }), }); if (res.status === 200) { const data = await res.json(); console.log(data); } else { const data = await res.json(); alert(`Error: ${data.message}`); } }; return ( <> <main className="flex w-full flex-col gap-2"> <div className="flex max-w-xl flex-col gap-2 p-4"> <Button label={"Hello World!"} onClick={() => onClickRead("healthCheckFunction")} /> <Button label={"Get Sheet All Data"} onClick={() => onClickRead("getSheetAllData")} /> <FormComponent /> </div> </main> </> ); } type ButtonProps = { label: string; } & React.ButtonHTMLAttributes<HTMLButtonElement>; const Button = (props: ButtonProps) => { const { label, onClick } = props; return ( <button onClick={onClick} className="flex items-center justify-center rounded-lg bg-white px-8 py-2 shadow transition-all hover:-translate-x-1 hover:-translate-y-1 hover:cursor-pointer hover:shadow-md" > {label} </button> ); }; type FormType = { url: string; }; type PrevFormDataType = { value: FormType; validationError: { url: Error | null }; apiError: Error | null; }; const FormComponent = () => { const initialFormData: PrevFormDataType = { value: { url: "" }, validationError: { url: null }, apiError: null, }; const validationUrl = (url: string) => { try { new URL(url); return null; // URLが有効な場合はエラーなし } catch (e) { return new Error(`Invalid URL format ${e}`); // 無効なURLの場合はエラーを返す } }; const [formData, action, isPending] = useActionState< PrevFormDataType, FormData >(async (_: PrevFormDataType, formData: FormData) => { // FormDataをobjectに変換 const _formData = Object.fromEntries(formData.entries()); const data: FormType = { url: _formData.url as string, }; // validationを掛ける いい感じのライブラリがあれば参考にする const urlError = validationUrl(data.url); if (urlError) { return { value: { url: data.url }, validationError: { url: urlError, }, apiError: null, }; } // ここでAPI処理を実装・今回は2秒待ってエラーを返す const res = await fetch("/api/google-auth/test", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ functionName: "insertDataToTargetSheet", params: [data.url], }), }); if (res.status === 200) { alert("Data submitted successfully!"); return { value: { url: "" }, validationError: { url: null }, apiError: null, }; } const apiError = new Error("Failed to submit data"); return { value: { url: data.url }, validationError: { url: urlError, }, apiError: apiError, }; }, initialFormData); return ( <> <form action={action} className="flex w-full max-w-xl flex-col gap-2 rounded-md p-4 shadow" > <label className="flex flex-col"> <div className="flex flex-row text-xl"> <span className="w-1/3">名前:</span> <input className="w-full border p-1 text-right" type="text" name="url" defaultValue={formData.value.url} /> </div> <span className="h-4 text-xs text-red-500"> {formData.validationError.url && ( <>{formData.validationError.url.message}</> )} </span> </label> <button className={ "w-full rounded-md py-4 text-lg text-white" + (isPending ? " bg-gray-400" : " bg-blue-500") } type="submit" formAction={action} disabled={isPending} > 送信{isPending && "中"} </button> <span className="h-4 text-xs text-red-500"> {formData.apiError && <p>{formData.apiError.message}</p>} </span> </form> </> ); }; 実行可能APIの検証のために3つのパターンで /api/google-auth/test にリクエストを送信しています。 ソースコード 環境変数吸出し用env service import { MessagingApiClient } from '@line/bot-sdk/dist/messaging-api/api'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { OAuth2Client } from 'google-auth-library'; @Injectable() export class EnvironmentsService { constructor(private configService: ConfigService) {} get GoogleClientID(): string { return this.configService.get('GOOGLE_CLIENT_ID'); } get GoogleClientSecret(): string { return this.configService.get('GOOGLE_CLIENT_SECRET'); } get GoogleRedirectUri(): string { return this.configService.get('GOOGLE_CALLBACK_URL'); } GoogleOAuth2Client() { const client = new OAuth2Client({ clientId: this.GoogleClientID, clientSecret: this.GoogleClientSecret, redirectUri: this.GoogleRedirectUri, }); return client; } get GoogleScriptURL(): string { return this.configService.get('GAS_SCRIPT_URL'); } get isProduction(): boolean { const env: string = this.configService.get('ENV'); if (env === 'development') { return false; } else { return true; } } } Guards import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { Request } from 'express'; @Injectable() export class IsGoogleIdTokenVerifyGuard implements CanActivate { constructor(private readonly env: EnvironmentsService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest<Request>(); const idToken = request.cookies['id_token']; console.log('verify idToken', ':come on'); if (!idToken) return false; const isValid = await this.verfyIdToken(idToken); if (!isValid) return false; return true; } private async verfyIdToken(idToken: string): Promise<any> { const client = this.env.GoogleOAuth2Client(); try { const ticket = await client.verifyIdToken({ idToken: idToken, audience: this.env.GoogleClientID, }); const payload = ticket.getPayload(); const now = Math.floor(Date.now() / 1000); // 現在時刻(秒単位) if (payload && payload.exp && payload.exp > now) { return true; // トークンは有効 } else { return false; // トークンは無効または期限切れ } } catch (error) { return false; // トークンが無効の場合 } } } Controller import { Body, Controller, Get, Post, Query, Req, Res, UseGuards } from '@nestjs/common'; import { IsGoogleIdTokenVerifyGuard } from 'src/common/guard/is-google-id-token-verify/is-google-id-token-verify.guard'; import { EnvironmentsService } from 'src/config/enviroments.service'; import { GoogleAuthService } from './google-auth.service'; import { RequestScriptRunDto } from './dto/request.dto'; @Controller('/api/google-auth/') export class GoogleAuthController { constructor( private readonly googleAuthService: GoogleAuthService, private readonly env: EnvironmentsService, ) {} // Google認証のURLを取得する @Get() async getGoogleAuthUrl(@Res() res): Promise<void> { const authUrl = await this.googleAuthService.getGoogleAuthUrl(); res.redirect(authUrl); } // Google認証のコールバックURL @Get('callback') async getGoogleAuthCallback(@Query('code') code: string, @Res() res): Promise<void> { const tokens = await this.googleAuthService.getToken(code); res.cookie('id_token', tokens.id_token, { httpOnly: this.env.isProduction, secure: this.env.isProduction, sameSite: 'Strict', maxAge: 3600 * 1000, // 1時間 }); // 環境によってbooleanを切り替える res.cookie('access_token', tokens.access_token, { httpOnly: this.env.isProduction, secure: this.env.isProduction, sameSite: 'Strict', maxAge: 3600 * 1000, // 1時間 }); res.redirect('/community/google/'); } // Google認証のトークンを検証する @Get('verify') async verifyIdToken(@Req() req, @Res() res): Promise<void> { const idToken = req.cookies['id_token']; if (!idToken) { const authUrl = await this.googleAuthService.getGoogleAuthUrl(); res.status(401).json({ message: 'No id_token', url: authUrl }); } // token validation const isValid = await this.googleAuthService.verfyIdToken(idToken); if (isValid) { res.status(200).json({ message: 'Valid access token' }); } else { const authUrl = await this.googleAuthService.getGoogleAuthUrl(); res.status(401).json({ message: 'No id_token', url: authUrl }); } } @Post('test') @UseGuards(IsGoogleIdTokenVerifyGuard) async test( @Req() req, @Body() body: RequestScriptRunDto, @Res() res, ): Promise<string | undefined | { url: string; content: string }[]> { const accessToken = req.cookies['access_token']; console.log('accessToken', req); const result = await this.googleAuthService.runScript(accessToken, body.functionName, body.params); return res.status(200).json(result); } } Service import { Injectable } from '@nestjs/common'; import { Credentials } from 'google-auth-library'; import { EnvironmentsService } from 'src/config/enviroments.service'; @Injectable() export class GoogleAuthService { constructor(private readonly env: EnvironmentsService) {} async getGoogleAuthUrl(): Promise<string> { const client = this.env.GoogleOAuth2Client(); const authUrl = client.generateAuthUrl({ scope: [ '<https://www.googleapis.com/auth/userinfo.profile>', '<https://www.googleapis.com/auth/script.scriptapp>', '<https://www.googleapis.com/auth/script.external_request>', '<https://www.googleapis.com/auth/spreadsheets>', ], redirect_uri: this.env.GoogleRedirectUri, }); return authUrl; } async verfyIdToken(idToken: string): Promise<any> { const client = this.env.GoogleOAuth2Client(); try { const ticket = await client.verifyIdToken({ idToken: idToken, audience: this.env.GoogleClientID, }); const payload = ticket.getPayload(); const now = Math.floor(Date.now() / 1000); // 現在時刻(秒単位) if (payload && payload.exp && payload.exp > now) { return true; // トークンは有効 } else { return false; // トークンは無効または期限切れ } } catch (error) { console.error('Error verifying access token:', error); return false; // トークンが無効の場合 } } async getToken(code: string): Promise<Credentials> { const client = this.env.GoogleOAuth2Client(); const tmp = await client.getToken(code); console.log(tmp); const { tokens } = tmp; return tokens; } // <https://developers.google.com/apps-script/api/reference/rest/v1/scripts/run?hl=ja> async runScript( accessToken: string, functionName: 'healthCheckFunction' | 'getSheetAllData' | 'insertDataToTargetSheet', parameters: (string | number)[] | undefined, ): Promise<string | undefined | { url: string; content: string }[]> { const url = this.env.GoogleScriptURL; const response = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ function: functionName, parameters: parameters || [], }), }); const data = await response.json(); if (response.status !== 200 || data.error) { console.error('Error calling Google Apps Script:', data.error); throw new Error(`Error: ${data.error.message}`); } const result = data.response.result; if (typeof result === 'undefined') return; if (typeof result === 'string') return result; if (Array.isArray(result)) { const temp = result.map((item: { url: string; content: string }) => { return { url: item.url || '', content: item.content || '', }; }); return temp; } return result; } } おわり GASを実行可能APIとして公開し、OAuth2.0による認証を実装することで、セキュアなAPIエンドポイントを作成することができました。今回実装したコードは、Google Sheetsとの連携も含めて、実際のプロダクションで使用可能なレベルのものとなっています。 今後は、エラーハンドリングやログ機能の追加など、より堅牢な実装に向けて改善を進めていきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GAS × OAuth2.0:実践で使える実行可能API構築の手順 first appeared on SIOS Tech. Lab .
アバター
OSSよろずサポート担当の神﨑です。 お問い合わせとしてよく来るエラーメッセージについて解説していこうと思います。 今回は、AH02608、AH10154、AH01095 AH02608: read request body failed このエラーは何のエラー? クライアントからのリクエストボディの読み取りに失敗した旨のメッセージです。リクエストボディの送信が途中で中断してしまった場合や、ネットワークに問題があった場合などに出力されます。 AH2609 と何が違うの? 同じエラーメッセージが出力されるエラーとして AH2609 があります。 AH2609 は Content-Length ヘッダー (事前にリクエストボディのサイズが指定される)で送信される際に、事前に指定されたサイズと違う場合や通信の切断が発生した場合に出力されます。 AH2608 はリクエストボディの形式がチャンクエンコーディング (事前にリクエストボディのサイズの指定がない)の場合のエラーです。 AH10154: pass request body failed このエラーは何のエラー? バックエンドへのリクエストボディ送信が失敗した旨のメッセージです。AH02608 の事象によりリクエストボディが正しく読み取れず、バックエンドへ送信できなかった可能性があります。 AH01095: prefetch request body failed このエラーは何のエラー? クライアントからのリクエストボディの事前取得に失敗してしまった旨のメッセージです。AH02608 の事象によりリクエストボディが正しく読み取れず、事前取得できなかった可能性があります。 3つのエラーはどう違うのか? どれもリクエストボディの処理が失敗しているメッセージとなりますが、読み取り、事前取得、送信と出力される処理のフェーズに違いがあります。 どう対策すればいいの? ネットワークの設定や、Timeout ディレクティブを見直したり、リクエストをクライアント側が強制終了しなかったかなどを確認する。 Timeout ディレクティブは以下の値を目安に変更する。 クライアント側のリクエスト全体を受信する時間>Timeout ディレクティブ 参考 AH02608、AH10154 については以下もご確認ください httpd mod_proxy logs ‘Partial results are valid but processing is incomplete’ error – Red Hat Customer Portal ※Red Hat社の有料ポータルログインIDが必要です。 AH02609 については以下もご確認ください。 Getting error “AH02609: read request body failed” in Apache HTTPD mod_proxy – Red Hat Customer Portal ※Red Hat社の有料ポータルログインIDが必要です。 AH1095 については以下もご確認ください。 Intermitent timeout errors in Apache HTTPD during request body prefetch ※Red Hat社の有料ポータルログインIDが必要です。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [Apache エラー解説]プロキシを使用する際によく出力されるエラーの原因と対策 first appeared on SIOS Tech. Lab .
アバター
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 以前公開した以下の記事では、ローカル環境で動作するオリジナルの MCP サーバーを開発する手順を案内しました。今回はその続編として、ローカル MCP サーバーを Remote MCP サーバーへ改良し、リモートサーバーで動作させる方法を共有します。 オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab 構成概要 筆者が動かした際の主な構成要素は以下の通りです。 Windows 10 Professional WSL 2.4.12.0 Ubuntu 24.04.2 LTS Node.js v22.15.0 Claude desktop for Windows version 0.9.3 Visual Studio Code version 1.99.3 ハンズオンを構成する環境は以下の通りです。 MCP ホストは、Claude desktop for Windows のネイティブアプリ版を利用します。 2025年5月現在、Web 版 Calude で Remote MCP サーバーを利用できるのは、Max、Team、Enterprise プランのみです。Pro、および Free プランにはまだロールアウトされてないため、desktop 版を利用します。 リモートMCPを使用したカスタム統合について | Anthropicヘルプセンター Claude から Remote MCP サーバーへは、remote-mcp の npm パッケージを介して連携するため、Windows 環境に Node.js を必要とします。 Remote MCP サーバーの構成における主な考慮事項は以下の通りです。 WSL 環境に構築する Ubuntu を、Remote MCP サーバーが動作するリモートサーバーとして扱います。 Node.js の Express パッケージを利用して HTTP プロトコルで 8787 ポートをリッスンします。 トランスポートには、ローカル MCP サーバーの STDIO から、Remote MCP サーバーでは Server-Sent Events(SSE)を使います。 Remote MCP サーバーで提供するツールの機能は、改良前の MCP サーバーと同じです。 オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab ハンズオンの手順で作成する Remote MCP サーバーの設定ファイルやソースコードは、以下の GitHub リポジトリで公開しています。手順と合わせてご確認ください。 hands-on-mcp-sios-apisl @ GitHub v2.0.0 :改良後の Remote MCP サーバー(SSE)の完成状態 v1.0.0 :完了前のローカル MCP サーバー(STDIO)の完成状態 基礎環境の構築 Remoe MCP サーバー環境 on WSL WSL 環境の Ubuntu 側に Remote MCP サーバーの稼働環境を構築します。 WSL 環境の構築 以下手順を参考に Windows PC へ Linux ディストリビューション(Ubuntu)環境を用意します。 初期環境構築: WSL 環境 on Windows 10 Node.js インストール JavaScript で実装された Remote MCP サーバーを実行するために Ubuntu へ Node.js をインストールします。 $ sudo apt install -y npm $ node -v v18.19.1 apt でインストールされる Node.js はバージョンが古いためアップデートします。 $ sudo npm install n -g $ sudo n 22.15.0 インストールしたコンソールを一度閉じて新たにコンソールを開き直し、導入したバージョンをセッションに反映します。 $ node -v v22.15.0 MCP ホスト環境 on Windows Windows 側に MCP ホストの稼働環境を構築します。 Claude desktop for Windows インストール MCP ホストに Web 版ではなくアプリ版の Claude desktop for Windows を利用するため、以下公式手順を参考に Windows PC へインストールします。 デスクトップ版Claudeのインストール | Anthropicヘルプセンター Node.js インストール MCP ホストの Claude から Remote MCP サーバーのツールへアクセスする際に、Node.js の「remote-mcp」npm パッケージを介して実行するため、以下手順を参考に Windows PC へ Node.js をインストールします。 初期環境構築: Node.js on Windows Visual Studio Code インストール WSL 環境の Ubuntu で TypeScript ソースコードを実装するためのエディターとして、Windows 環境にインストールした Visual Studio Code (VS Code) を WSL 環境へリモート接続して利用する方法をお薦めします。VS Code のインストール手順については、ネット上でたくさんのコンテンツが親切に説明されているので、本資料の範囲外とさせて頂きます。 Visual Studio Code インストール – Google 検索 なお、VS Code 以外のお気に入りのエディターを利用される場合は、文中の VS Code をご利用のエディターに置き換えて読み進めてください。 Remote MPC サーバーへ改良 WSL 環境の Ubuntu で Remote MCP サーバーを開発するため、Ubuntu にログインし、ハンズオン用のフォルダを作成して作業を進めます。 $ mkdir -p ~/handson/ $ cd ~/handson/ 改良前のローカル MCP サーバーを用意 以前掲載した以下の記事を参考に、Remote MCP サーバーの改良元となるローカル MCP サーバーを Ubuntu 環境に用意してください。なお、当該記事は Windows 環境で動作するローカル MCP サーバーの構築について記載していますが、今回の Remote MCP サーバーは Ubuntu 環境で動作させるため、Ubuntu 環境に用意してください。 オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab また、改良前のローカル MCP サーバーのソースコードは、以下の GitHub リポジトリに「v1.0.0」タグで保存しているので、Git コマンドで取得して用意頂くこともできます。 hands-on-mcp-sios-apisl v1.0.0 @ GitHub $ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl.git -b v1.0.0 プロジェクト設定の改良 改良作業前に、現在設定されている「 package.json 」に従い依存パッケージをインストールします。 $ npm install Remote MCP サーバーの改良に当たり、追加で必要になる依存パッケージをインストールします。 $ npm install express @types/express ソースコードの改良 Remote MCP サーバーで起動するように「 src/index.ts 」の3か所を改良します。 まずは1点目の改良として、主に利用するトランスポートに関連する依存パッケージを置き換えます。以下の差分イメージでは、行頭の「+」は追加する行、「-」は削除する行を表します。 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; + import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; + import express from "express"; import { z } from "zod"; : 次に2点目の改良として、STDIO トランスポートの実装を削除します。 - async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("MCP Server running on stdio"); - } - - main().catch((error) => { - console.error("Fatal error in main():", error); - process.exit(1); - }); 最後の3点目の改良として、STDIO トランスポートが実装されていた箇所に、HTTP で通信する Server-Sent Events (SSE) トランスポートを実装します。 + const app = express(); + const port = 8787; + let transport: SSEServerTransport | null = null; + + app.get("/sse", async (_, res) => { + console.log("Received connection"); + transport = new SSEServerTransport("/message", res); + await server.connect(transport); + }); + + app.post("/message", async (req, res) => { + console.log("Received message"); + transport?.handlePostMessage(req, res); + }); + + app.listen(port, () => { + console.log("Remote MCP server listening on port", port); + }); 改良後のできあがりは以下を参考にしてください。 src/index.ts ビルドしてトランスパイル 改良後のできあがりを動作確認するために、ビルドして TypeScript から JavaScript のソースコードを生成します。 $ npm run build > hands-on-mcp-sios-apisl@1.0.0 build > tsc MCP Inspector で動作確認 MCP Inspector と Remote MCP サーバーを起動するには、2つのコンソールを使用します。まず、1つ目のコンソールで MCP Inspector を起動します。 $ npx -y @modelcontextprotocol/inspector@latest Starting MCP inspector... Proxy server listening on port 6277 MCP Inspector is up and running at http://127.0.0.1:6274 コンソールログに表示された URL のホスト部「127.0.0.1」を「localhost」に置き換え、その URL(例: http://localhost:6274 )でブラウザから MCP Inspector にアクセスします。 次に2つ目のコンソールを立ち上げて Remote MCP サーバーを起動します。 $ node build/index.js Remote MCP server listening on port 8787 Received connection MCP Inspector をアクティブにして、Transport Type 項目に「SSE」を選択し、URL 項目は Remote MCP サーバーを起動したホスト名とコンソールログに表示されたポート番号に「/sse」のパスを使った URL(例: http://localhost:8787/sse )を入力します。「Connect」ボタンをクリックすると MCP Inspector が Remote MCP サーバーに接続します。 接続したら画面中央の「List Tools」ボタンをクリックするとツール一覧が表示されます。 それ以降の動作確認は、ローカル MCP サーバーと同じため、以前の記事を参考に確認を進めます オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab > MCP Inspector で動作確認 Claude(MCP ホスト)に組み込み 連携する MCP サーバーの設定 オリジナルの Remote MCP サーバーを Claude から利用できるように設定ファイルを編集するため、Windows のスタートメニュー「A」セクションから「Anthropic > Claude」を選択して起動します。 Claude が起動したら画面左上のハンバーガーメニューから「ファイル > 設定…」を選択します。 オリジナルの Remote MCP サーバーが利用できるようにするために、「開発者」タブの「構成を編集」ボタンをクリックします。 エクスプローラーが立ち上がり、Claude の設定ファイルに該当する「 claude_desktop_config.json 」ファイルをダブルクリックしてエディターで開きます。 「 claude_desktop_config.json 」ファイルにオリジナルの Remote MCP サーバーを利用するための設定を書いて保存します。 { "mcpServers": { "mcp-remote-sios-apisl-demo": { "command": "npx", "args":[ "mcp-remote", "http://localhost:8787/sse" ] } } } 「 claude_desktop_config.json 」ファイルの編集における主な考慮事項は以下の通りです。 「mcpServers > mcp-sios-apisl-demo > args」フィールドで主な考慮事項は以下の通りです。 npx コマンドの第1引数に mcp-remote パッケージを指定することで、Retemo MCP サーバーと連携します。 npx コマンドの第2引数に mcp-remote パッケージが連携する Remote MCP サーバーの URL を指定します。 Claude の再起動で設定反映 変更した設定ファイルの適用に、ウィンドウの「×」ボタンで終了させるだけではなく、Claude のプロセスを完全に終了させる必要があるため、Windows タスクバーの画面右側のアイコン表示領域から Claude アイコン(トゲトゲのウニの様なデザイン)を探し、右クリックで表示されるメニューから「終了」を選択してプロセスを完全に終了させます。 Windows のスタートメニュー「A」セクションから「Anthropic > Claude」を選択して起動します。 Claude が起動したら画面中央のテキストボックス内の左下にある「検索とツール」アイコンをクリックします。表示されたメニューに、「mcp-remote-sios-apisl-demo」の項目と右横に数字が表示されていたら、オリジナルの MCP サーバーは正常に設定できました。 オリジナルの Remote MCP サーバーのデモ 提供するツールの機能は Remote MCP サーバーでもローカル MCP サーバーと同じため、以前の記事を参照ください。 (動画内で MCP サーバーを有効化する際、表示されているサーバー名『mcp-sios-apisl-demo』を『mcp-remote-sios-apisl-demo』に読み替えてご覧ください) オリジナルのちょっと便利な MCP サーバー を作ってみた | SIOS Tech. Lab > オリジナルの MCP サーバーのデモ まとめ リモート MCP サーバーの実装手順は、いかがでしたでしょうか?MCP はまだ発展中の仕様のため引き続き動向をウォッチしながら得られたノウハウを共有していきたいと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post オリジナルのちょっと便利な『リモート MCP サーバー』を作ってみた first appeared on SIOS Tech. Lab .
アバター
ども!AI関連のブログを頑張って執筆中の龍ちゃんです。最近は、設計から開発まで幅広い範囲でのAI活用をしています。一向に仕事がなくなる気配がないですね。AIが仕事を奪うのはいつになるのでしょうか。それまでは、活用して業務効率化していかないといけないですね。 さて!今回は「GitHub CopilotのPull Request(PR)レビューにシステムプロンプトを与える」という内容になっています。GitHub Copilotをレビュワーとして活用している方の中に以下のような課題感を抱えた方がいれば、興味のある内容だと思います。 英文でのレビューを日本語化したい CopilotのPRの観点があいまい コードレビューの品質を一定化したい GitHub Copilotのレビューを向上させる GitHub Copilot on VSCodeでシステムプロンプトを追加する 「 GitHub Copilotにシステムプロンプトを挿入する方法 」についてはこちらで触れています。こちらでは、 .github/copilot-instructions.md というファイルを作成して自然言語でシステムプロンプトを設定しています。残念ながら、上記の方法ではPR発行時に読み込んでくれません。 GitHub Copilotにシステムプロンプトを追加する GitHub Copilot PRレビューにシステムプロンプトを組み込む方法としては、PRに直接記述する必要があります。 # 実施事項 <!-- ここに人間向きのPRを書く --> <!-- for GitHub Copilot review rule --> 日本語で記載してください。(Copilot向けの指示) <!-- for GitHub Copilot review rule--> PRの観点が一定の場合は、テンプレートとしてリポジトリ単位で保存しておくと効果的です。PRのテンプレートを作成する方法としては複数あります。 リポジトリのルート直下に pull_request_template.md を配置 docs ファイル直下に pull_request_template.md を配置 .github ファイル直下に pull_request_template.md を配置 docs と .github ファイルの場合は、 PULL_REQUEST_TEMPLATE というファイルを生成することで複数のテンプレートを作成することができます。 copilot-instructions と pull_request_template の違い これらの2つのファイルは、それぞれ異なる役割と特徴を持っていますが、上手く組み合わせることで効果的なPRレビュー環境を構築することができます。以下の表で違いについてまとめます。 項目 .github/copilot-instructions.md pull_request_template.md 用途 Copilot(AI)に対するシステムプロンプト・カスタム指示を設定し、PR作成やCopilot Chatなどで自動的に反映させる PR作成時の説明・チェックリスト・レビュールールなどを人間・AIの両方に提示するテンプレート 主な対象 Copilot(AI) PR作成者・レビュアー・Copilot(AI) 記述場所 .github/copilot-instructions.md .github/pull_request_template.md (または他の指定ディレクトリ) 反映タイミング Copilot ChatやAIレビュー、要約生成などCopilotの応答時 PR作成時にPR本文へ自動挿入される 人間への見やすさ 人間は通常直接見ない PR本文に表示されるため人間も確認可能 運用の柔軟性 リポジトリ全体に一括でAI指示を適用できる 複数テンプレートや内容のカスタマイズが容易 主なメリット AI応答の一貫性・自動化 PR作成の標準化・レビュープロセスの明確化・人間とAI両方に伝達可能 主なデメリット 人間には直接見えない・AIが必ずしも全て反映するとは限らない 指示がPR本文に残るためノイズになる場合も・AIへの伝達は工夫が必要 GitHub Copilot PR用プロンプト備忘録 こちらはまだ検証中の内容になります。運用を進めてみて、進展があればブログにてまとめて行きます。 コメントは絶対日本語でほしい これは、マストで入れておきたい内容です。別に翻訳アプリを使うので、読めなくはないのです。ただ圧倒的に気分が駄々下がりになるので、絶対日本語化はしておきたいです。 絶対日本語で出力してください。 無視しておきたいことを「禁止事項」として追記 これは、開発時における不満です。Typescript環境で console.log を一生注意されるんです。検証時には残しておきたいですが、気分的にコンソールでトークン数が消費されるの気持ち的にげんなりです。 そのため、不要な指摘を減らすために以下のようなプロンプトを追加しています。 以下の点については、レビューの対象外としてください: - console.logの使用 - 開発環境用の一時的なコメントアウト - デバッグ用の一時的な変数 このように明示的に除外項目を指定することで、より効率的なレビューが可能になります。 もちろん、最終的なコードには含まれていては困るので改めてレビュー観点への追加対応を行う必要はあります。 おわり 今回は、GitHub CopilotのPRレビューをより効果的に活用するための方法として、システムプロンプトの追加方法とPRテンプレートの活用について紹介しました。AIと人間の両方に役立つ指示を設定することで、レビューの品質向上と一貫性を実現できます。これらのツールを上手く組み合わせることで、より効率的な開発プロセスを構築していきましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PRレビューを自動化しよう!GitHub Copilot × システムプロンプトの基本 first appeared on SIOS Tech. Lab .
アバター
お疲れ様です。最近はブログから離れて隠遁生活をしていました。社内で「GitHub Copilot」の普及のためにイベントなどをやっていました。基本的な部分から実践的な使い方までをまとめていました。今回は「GitHub Copilot」でシステムプロンプトを設定して、チーム内で一定のGitHub Copilot出力を担保する方法について紹介していきます。 今回紹介する方法はパブリックプレビュー状態なので、変更される可能性があります。 最新情報 を追って合わせて更新していきましょう。 前提条件 開発環境としてはVisual Studio Code(VSCode)でGitHub Copilot businessでの開発を前提としています。 システムプロンプト設定方法 システムプロンプトを設定する方法としては、2ステップで設定することができます。 VSCodeの設定で「 Code Generation: Use Instruction Files 」の設定を有効化する .github/copilot-instructions.md を作成してシステムプロンプトを設定する VSCodeの設定有効化 VSCodeで「Shift+,」を開いて設定を開きましょう。検索欄にgithub.copilot.chat.codeGeneration.useInstructionFilesと入力するか、 こちら をクリックして設定項目に遷移してください。 項目を true にすると設定自体は完了です。 .github/copilot-instructions.md を作成してシステムプロンプトを設定する ルートディレクトリに .github ディレクトリを作成し、 copilot-instructions.md というファイルを作成します。作成したファイルに自然言語でプロンプトを記述することで機能が有効化されます。以下のようなディレクトリ構成になっていれば設定自体は完了です。 わかりやすいようにエンタメよりのシステムプロンプトを設定してみました。index.htmlの解説をしてもらうと以下のような出力が返答されており、適切に設定が完了しています。 GitHub Copilotの出力サンプル 入力 class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-6 上記のソースを解説して 出力 以下のコードは、HTMLの <header> 要素に適用されているクラス属性の一部です。このクラス属性は、Tailwind CSSを使用してスタイルを設定しています。 class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white py-6 bg-gradient-to-r : 背景にグラデーションを適用します。 to-r は「右方向へのグラデーション」を意味します。 from-indigo-600 : グラデーションの開始色を指定します。 indigo-600 はTailwind CSSで定義されたインディゴ色の濃いトーンです。 to-purple-600 : グラデーションの終了色を指定します。 purple-600 はTailwind CSSで定義された紫色の濃いトーンです。 text-white : テキストの色を白に設定します。 py-6 : 上下のパディングを設定します。 py は「padding-y(上下方向のパディング)」を意味し、 6 はTailwind CSSのスケールに基づく値(約1.5rem)です。 このクラス設定により、ヘッダー部分に右方向へのインディゴから紫へのグラデーション背景が適用され、テキストは白色で、上下に適度な余白が確保されていますにゃー。 こちらのファイルはそのままリポジトリに上げることで、新たに参入する開発メンバーも自然に導入することができます。 システムプロンプトの備忘録 こちらはまだ検証中の内容になります。運用を進めてみて、進展等があればブログにてまとめて行きます。 不必要なプロンプトは除外しておく 今回はデモ目的だったので、語尾に「にゃー」とつけてもらいました。実用性に関しては皆無であり、トークン数の無駄な消費につながります。GitHub Copilotはコード関連に特化したAIとなっています。そのため、汎用的な質問などは受け付けません。 システムプロンプトには、コードに関する情報を入れておくことが好ましいですね。 フロントエンドエンジニアならこんなプロンプトもよいかも 前提情報として、リポジトリ内で使用している技術の概要あたりは書いておくのが良いかもしれません。GitHub Copilotは特に意識をしなければ、開いているファイル群を読み取り回答を生成します。Tailwindでカスタム定義したCSSなどは、あまり読み込んでくれない印象です。そのため、以下のような情報を箇条書きで与えています。 Next.js+Typescript+Tailwind CSS構成 Tailwind CSSベースでのスタイル情報 コンセプト(モダン・シック・かっこいい など…) おわり GitHub Copilotでシステムプロンプトを設定することで、チーム内での一貫性のある開発体験を実現することができます。今回紹介した方法は、まだパブリックプレビュー段階ですが、今後の発展に期待が持てる機能です。ぜひ、チームの開発スタイルに合わせてカスタマイズしてみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Copilotをチーム開発で使いこなす!システムプロンプト設定方法 first appeared on SIOS Tech. Lab .
アバター