Web List
AIエージェント MCPサーバ on Apr 16, 2025 7:57 PM
GitHub Actions on Dec 27, 2024 9:25 PM
Flask on Sep 05, 2024 11:34 PM
BT on Jun 21, 2024 11:00 PM
Cloud SQL on Jun 02, 2024 1:06 PM
GCP hands-off 3 on Jun 01, 2024 3:24 PM
Pubsub on May 09, 2024 12:00 AM
HELM on Apr 27, 2024 11:27 PM
GKE on Jan 14, 2024 9:59 PM
GCP Network Connectivity on Oct 31, 2023 10:57 PM
Machine learning(Bigquery ML) on Jun 21, 2023 8:00 PM
GCP Python Google doc編集 on Jun 01, 2023 12:00 AM
GCP hands-off 2 on May 29, 2023 7:30 PM
HSTS/CORS/CSPOAuth/OpenID/SAML/XSS/CSRF/JSOP/SSO/SSL/SVG/JWT/WebAssembly on Feb 11, 2023 1:46 AM
Docker on Jul 24, 2022 3:46 AM
LPIC on May 18, 2022 3:20 AM
Goo ana 4 on Apr 23, 2022 11:00 AM
I drive or test driven on Apr 17, 2022 9:54 AM
GCP Python script on Apr 01, 2022 12:00 AM
GCP runs off functions pubsub on scheduler on Mar 30, 2022 7:59 PM
GCP script on Feb 26, 2022 2:52 AM
リンク踏合組合 on Dec 25, 2021 5:46 PM
k8s on Jun 09, 2021 12:01 AM
GCP Hands Off on May 22, 2021 12:00 AM
GCP ログ・アセット調査 Logging/Bigquery information schema/Asset inventory on May 22, 2021 12:00 AM
GCP part2 on May 21, 2021 12:00 AM
GCP on May 20, 2021 9:00 PM
Terrafirma on May 02, 2021 10:14 PM
Linux cmd2 on Apr 02, 2021 1:00 AM
Linux cmd on Apr 02, 2021 12:00 AM
BigQuery part2 on Feb 22, 2021 12:00 AM
BigQuery on Feb 21, 2021 1:00 AM
Python Python on Feb 11, 2021 12:00 AM
Python on Feb 10, 2021 7:30 PM
Promise on Dec 25, 2020 1:06 AM
Dexie on Apr 21, 2020 12:00 AM
PWA on Apr 20, 2020 6:00 PM
G Suite -> Google workspace - GWS on Apr 01, 2020 12:01 AM
CSS Grid on Mar 01, 2020 3:03 AM
Update your home page on Jan 21, 2020 12:20 AM
Cloud 9 on Nov 22, 2019 9:42 PM
Adobe Sensei on Jan 01, 2019 11:11 AM
Summer time blues on Aug 11, 2018 6:42 PM
Web font test on Jan 01, 2018 11:13 PM
Help desk on Jun 01, 2017 12:00 AM
RWD by HTML5 on Jun 07, 2016 8:28 PM
Big D on Jun 05, 2016 6:45 PM
SN on May 22, 2016 2:09 AM
Ant on Apr 03, 2016 10:01 PM
Windows env tips on Mar 31, 2016 1:44 AM
Cache on Mar 08, 2016 8:06 PM
Detected as bad site by virus company on Mar 08, 2016 1:07 AM
Google API on Feb 14, 2016 1:03 AM
OSS License on Jan 08, 2016 12:00 AM
Bash on Jan 07, 2016 10:41 PM
Regular expression on Jan 06, 2016 1:27 AM
Javascript on Jan 05, 2016 7:27 AM
Laravel on Jan 04, 2016 12:00 AM
Git on Jan 03, 2016 12:00 AM
Agile on Jan 02, 2016 12:00 AM
PHP Developer on Jan 01, 2016 10:47 PM
Photo Boo on Aug 30, 2015 1:17 AM
Camoufla on Jan 09, 2014 12:08 AM
A Bao A Qu on Sep 02, 2013 12:02 AM
Online softwares on Oct 06, 2011 1:21 AM
Halftone on Apr 05, 2011 6:50 AM
It tests you on Dec 16, 2010 5:16 PM
Secure your home on Oct 05, 2010 10:48 PM
F☆ck CSS on Sep 29, 2010 1:07 AM
Tentative on Sep 06, 2010 12:44 PM
今夜はKick it (WSH a.k.a VBscript) on Aug 26, 2010 4:18 AM
Who's BAT? (Batch file) on Aug 26, 2010 12:31 AM
After Effects on Aug 25, 2010 5:30 PM
Server transfer on Aug 18, 2010 5:05 PM
Tools - free icon etc on Aug 18, 2010 4:37 PM
ER on Aug 09, 2010 5:38 AM
Shooting star on Aug 07, 2010 3:38 AM
Even small things can't kill me on May 07, 2010 1:08 AM
Adobe Premire6.0 (Guru R.I.P.) on Apr 24, 2010 1:42 AM
jQuery - write less, do more. on Apr 23, 2010 6:09 AM
MySQL DB Database on Mar 11, 2010 12:25 AM
MySQL chara issue on Mar 09, 2010 5:02 AM
CSS Selectors on Mar 01, 2010 1:29 AM
Ora Ora Ora Ora Ora on Nov 16, 2009 6:54 PM
Use + Able + T on Oct 29, 2009 9:04 PM
A Rainbow Between Clouds on Jul 22, 2009 8:08 PM
No sense of direction on Jun 20, 2009 5:34 PM
Optimost on Mar 25, 2009 8:34 PM
Google Other---s on Mar 25, 2009 8:32 PM
Tora Tora Tora on Dec 20, 2008 9:05 PM
NO, WE CAN'T. on Oct 07, 2008 7:03 AM
Revolt against MT on Mar 03, 2008 12:24 AM
株式取引モバイルサイト - TOKIO Shock Exchange on Feb 03, 2008 11:51 PM
GoogleMap Moblile on Jan 25, 2008 12:52 AM
英語単語帳SNS on Dec 19, 2007 12:13 AM
Where Hiphop lives!! on Dec 18, 2007 9:56 AM
MARUTAI on Aug 04, 2007 7:45 PM
Consumer-Generated Media on May 09, 2007 12:48 AM
One of the net service TVCM on Mar 03, 2007 1:37 PM
ApacheBench on Feb 24, 2007 7:25 PM
Warning!! Page Expired. on Feb 21, 2007 7:56 PM
The No-Asshole Rule on Feb 20, 2007 7:51 PM
htaccess mod_rewite on Feb 17, 2007 6:29 PM
Copyright Protection on Feb 05, 2007 9:56 PM
Link Bait on Feb 03, 2007 8:34 PM
Personal Information Privacy Act on Feb 01, 2007 8:00 PM
Browser becoming a Push Media on Feb 01, 2007 12:10 AM
Classic Font on Jan 27, 2007 11:32 PM
Is this English??? on Jan 08, 2007 2:41 AM
Passwords on Jan 02, 2007 10:32 PM
Zero Test on Aug 26, 2006 5:12 AM
For a Presentation on Aug 23, 2006 9:36 AM
BANGBOO MAP on Aug 20, 2006 11:00 PM
Golden Ratio on Aug 15, 2006 9:53 PM
Tag Cloud on Aug 15, 2006 7:43 PM
Struggling with PHP on Aug 11, 2006 5:13 PM
Company Profile on Jul 25, 2006 4:35 AM
A good angle from variety of point of views on Jul 15, 2006 3:12 PM
Affiliate on Jun 11, 2006 7:46 PM
Garbled Characters on Jun 11, 2006 1:13 PM
Track Making on May 27, 2006 8:35 PM
動画をキャプチャーする on May 22, 2006 4:46 AM
BGM on May 16, 2006 9:05 AM
お役立ちツール on May 15, 2006 8:13 AM
映像制作 on May 14, 2006 12:52 PM
ウェブサイトサムネイルAPI on Apr 18, 2006 5:35 PM
ポストプロダクション on Apr 14, 2006 10:07 AM
WEB2.0? on Apr 13, 2006 7:03 AM
CSSの標準準拠と過去互換 on Apr 09, 2006 2:00 AM
The beginning of CSSレイアウト on Apr 08, 2006 11:22 PM
住所入力AJAX on Apr 08, 2006 5:39 AM
Google Sitemap on Apr 05, 2006 7:21 PM
無料ストレージ on Apr 05, 2006 4:40 AM
Blog Ping on Mar 30, 2006 11:40 PM
Movable Type on Mar 16, 2006 12:42 PM
着メロの掲載っていくらかかんの? on Mar 09, 2006 8:15 AM
RSS Radio on Mar 02, 2006 2:06 PM
April 16, 2025
AIエージェント MCPサーバ
■MCPサーバによる連携
Model Context Protocol(MCP)の基礎に関して、社内勉強会で使用したスライド資料を公開します! | DevelopersIOroo-logger: Cline Memory Bankとは違うAIの記憶システムを(MCPで)作った理由MCPサーバー自作入門MCP入門MCPを活用した検索システムの作り方/How to implement search systems with MCP #catalks - Speaker DeckリモートMCPサーバーカタログ #AWS - Qiitaプログラマー必見!FastAPI-MCPでAI時代のAPI開発を加速する方法(初心者向けコード付き) #Python - QiitaMySQLのスキーマ情報を圧縮して提供するMCPサーバーを作った - $shibayu36->blog;MCPを理解する - Speaker DeckローカルRAGを手軽に構築できるMCPサーバーを作りました[B! MCP] MCPサーバーを使って請求書作成から送付まで自動化してみた話MCPアーキテクチャパターン - Carpe DiemClineとDDDと私 - コドモン Product Team BlogGemini CLI の簡単チュートリアル[B! AI] Cline利用におけるデータの取り扱いについて - サーバーワークスエンジニアブログCline駄目そう?一般的な規約という声もある
Clineのデータの持ち方 一応通信はないらしい
Cline - AI Autonomous Coding Agent for VS CodeWE CLAIM NO OWNERSHIP RIGHTS OVER YOUR USER CONTENT. とはあるが再利用に使わないという意味ではないらしい
Privacy. By using the Service, you acknowledge that we may collect, use, and disclose your personal information and aggregated and/or anonymized data as set forth in our Privacy Notice.とあるのでな
■構成
MCPホスト:Cline等のAIエージェント
↓
MCPクライアント:Json設定のProxy (これ以降が狭義MCPサーバ? あるいは全体でMCPサーバ)
┣→ここでコマンドを打つように設定すればそのままローカルがEPになる
↓ (uvはPythonパッケージ管理/仮想環境ツール) uv run python src とか
API EP
■外部APIに通信かローカルか2種類と考えていい
stdio ローカルのサーバと通信
remote リモートのサーバと通信(SSE→Streamable http)
メッセージはJSON-RPC2.0
ローカルにおきDockerを動かす形
機能は3つ
Resources:事前に情報をファイルで読み込ませる感じ
Prompts:プロンプトのテンプレ設定しておく感じ
Tools:使うツールを設定しておく感じ
(MCPクライアントは2つ:無意識でいい)
Sampling:MCPを使うよ~
Roots:MCPを使うファイルシステム確認~
これで人間が操作していた内容をMCPで実施するようにする
LLM→人間→Github/Slack/GCP etc.
↓
LLM→MCP→Github/Slack/GCP etc.
■MPCサーバとは何者?
Model Context Protocol (MCP)は、特にLLMを活用するアプリケーションにおいて、モデルとのやりとりを標準化するためのプロトコル
API EPやローカルプロセスをMCPサーバと言っているケースもあるが、MCPの文脈からするとMCPサーバは仲介。なぜか?
従来のクライアントサーバモデルとは逆方向に見えるから分かりにくいから、MCPクライアントは広義のMCPサーバに含まれている
MCPにおける構成
[ユーザー] -> [LLM (MCPサーバ)] --> [MCPクライアント] -> [API/CLI/ローカルプロセス】
伝統的な通常のクライアント・サーバ構成
[Client] -> [Server]
■github-mcp-server設定
GitHub - github/github-mcp-server: GitHub's official MCP ServerGithub トークン発行はココで→ https://github.com/settings/personal-access-tokens
クラシックトークンのrepo全体でも良さそう
MCP Marketplace>Remote server>Edit configuration>下記コードをコピペする
{
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "github_pat_xxxxxxx"
},
"disabled": false,
"autoApprove": []
}
}
}
上手く行かないので、Cline自身にMCPサーバの調整をしてもらうと下記となった (Docker、DockerGroup、Proxy等)
{
"mcpServers": {
"github": {
"command": "sg",
"args": [
"docker",
"-c",
"docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN -e http_proxy -e https_proxy ghcr.io/github/github-mcp-server stdio"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "github_pat_xxxxx",
"http_proxy": "http://proxy:3128",
"https_proxy": "http://proxy:3128"
},
"disabled": false,
"autoApprove": []
}
}
}
■ローカルサーバ
//// UV
curl -Ls https://astral.sh/uv/install.sh | sh
cd /mnt/c/Users/unco/Desktop/local_test/MCP/kuso-app
uv venv 該当ディレクトリに仮想環境を設定
source .venv/bin/activate アクティベート
uv pip install google-cloud-asset
uv pip freeze > requirements.txt
python3 main.py
deactivate 停止
gcloud auth application-default login --no-launch-browser
gcloud auth login --no-launch-browser
消したければ、venvを削除するだけ
上記はpip+requirementsだが下記の方がいいかも、add+tomlで管理
uv init myproject (myprojectディレクトリ、tomlファイルが作成される)
uv add numpy
uv remove numpy
nv sync
uv lock
uv tree
uv python install 3.10 3.11
uv python list
uv python pin 3.11
uv tool install ruff
uv tool list
uvx pytest 一時的に仮装環境を汚さず実行
uv exports --format-requirements.txt
権限確認API(analyzeIamPolicy)
curl \
'https://cloudasset.googleapis.com/v1/projects/prj-xxxxx:analyzeIamPolicy?analysisQuery.accessSelector.roles roles%2 Fbigquery.dataowner&analysisQuery.identitySelector.identity=user%3Axxxxx%40xxxxx.com&key=[YOUR_API_KEY]'\
--header "Authorization: Bearer $(gcloud auth print-access-token)"\
--header Accept: application/json' \
--compressed
※APIキー不要だた、HTTPやJSの方法も出る
parentでエラーがでてもscopeという意味> organizations/123, folders/123, projects/my-project-id, projects/12345
400 Missing parent field in request. [field_violations {
field: "parent"
description: "Missing parent field in request." }
↑
■2023-04-24
Dialog flow ES - Chatbotの作り方
対話式アプリの有効性>何か対策をしときたい
Dialogflow:FAQのチャットボットを作りたい
intentをFAQの数だけ作る
intentグループがあれば纏められるが、、
色は赤や青で分ける必要がある? entitityは色だが単語で分ける
entitiy: colors、単語:赤、類語:Red、朱色等
単語:白、類語:ホワイト等
entitity 個別項目(色、サイズ、キーワード)トレーニングフェーズで使う用語を設定しておく
intent 意図(選択、買う、確認、支払)チャットで引きあたる項目で、数多く作ることになる
上の2つを結びつきを強くするcontext
input context 該当のインテントの変遷の一つ前の別名を指定
output context(コンテキスト用エイリアス名) 該当のインテントの別名を設定する
Training phrases(想定するユーザの入力文)
Responses(答えへの反応
料金 無料の範囲である程度賄える
項目を500から選ぶとしても大丈夫 外部のデータソースを使う(できそう)
色を10個を選択するとしても大丈夫
結果を外部に連携することもできそう
AI型は入力された質問に対して、FAQから適切な回答を表示(正誤で学習し判定上がる)
シナリオ型は入力値から分岐を判定して進む、あみだくじタイプの定型処理に持っていく
■Dialog flow ES
下記の辺りを設定すれば、チャットボットが回答するようになる
ESとしては、ユーザ入力を構造化データにする処理を行いパラメータへ検索を掛ける挙動となっている
ユーザ行動の履歴から検索ヒットの改善を学習するタイプのAI
インテント分類(ユーザの意図のパターンを設定)
┣トレーニングフレーズ(質問の例文を指定)
┣アクション (何を実行するか指定)
┣パラメータ/エンティティ(質問から抽出すると型を指定)
┗レスポンス (何を返答するか指定)
コンテキスト: input (一つ前のインテント〉 と output (当インデントの別名)
イベント:エンドユーザーの発言からではなく、発生したイベントに基づいてインテントを呼び出す
アクション: デフォルトfallbackにはinput-unknown がついている
パラメータ:下記くらいの考慮で良さそう
@sys.any キーワードを設定してしまう?
@sys.url
@sys.person ロールや役職を設定してしまう?
@sys.email
■Agent 設定
Agent > ML setting > Train でトレーニングさせる。
megaでなく普通のエージェントの方が分かり易い?
Agent > environment > publish パブリッシュし公開?
■ intent
テキストレスポンスにタグが使えずリンクにならない。
ユーザエクスプレッションの単語をドラッグ反転させ techinical_terms 等で検索するとEntityを反映することができる。
(Entity登録済みなら熟語でもOKだが、未登録なら単語で設定したような)
■Entitiy 設定
業務で使用している重要用語は @technical_termsとして作り、揺らぎを全て入力したい
BigQuery: BQ、ビッグクエリ、、、
個人情報: パーソナルインフォメーション, PII、、、
■Integration設定
WebDemoを有効化
DialogMessangerを有効化(こっちのがCoolでは
■FAQを簡単に構築するコツ
検索を網羅するように質問を重要単語を全て入れた形で複数設定する
代表的な質問をレスポンスに入れてしまうとFAQとして分かり易い
参考になりそうなの一例を回答します。
Q「ああああ」
A「いいい」
■思うような結果にならない場合のチューニング
トレーニングフレーズを追加するのが基本
できるだけキーワードをEintity化することもよい方法
Trainingに過去キーワードがあり正誤判定することができる
Validationにトレーニングフレーズの追加等の問題提起が出ているので対処
Posted by funa : 07:57 PM
| Web
| Comment (0)
| Trackback (0)
December 27, 2024
GitHub Actions
ChatGPTかGeminiかに聞けば良さそう
■GitHub Actionsの動作内容はリポジトリ内に設定されている.github/workflows内のYMLにある
steps:
- name: Checkout
uses: actions/checkout@v4
@以下はバージョン、特定のコミットSHAにもできる @3df4ab11eba7bda6032a0b82a6bb43b11571feac #v4
onセクションにPushやPR作成やスケジュール実行等のトリガーや対象ブランチやパス等も書かれている
Secretsや環境変数は、Terraformでクラウドプロバイダーにアクセスする場合等で、GitHub Actionsのsecrets で認証情報が設定されていることが多い。これらはリポジトリのSettings > Secrets and variables > Actions で確認可能。
name: Deploy Terraform
on:
push:
branches:
- main #この場合、mainブランチへのpushでトリガーされる
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.4.0
- name: Initialize Terraform
run: terraform init
- name: Apply Terraform
run: terraform apply-auto-approve #terraform planなしじゃ
Posted by funa : 09:25 PM
| Web
| Comment (0)
| Trackback (0)
September 5, 2024
Flask
■formaction (hidden以外でのSubmit先変更方法)
buttonや inputのtype=submit/image に付与できる
ベースとなるのはformのidのformタグ<input... form="formのid">
<form id="form" method="post">
<button type="submit" formaction="app.html" name="transition" value="a" form="form">戻る</button>
<button type="submit" formaction="ok.html" name="transition" value="b" form="form">送信</button>
下記のような属性がある
formaction
formenctype
formmethod
formnovalidate
formtarget
■jinja2
///dict
my_dic['name'] = 1
return render_template('index.html', message=my_dic)
テンプレ側
{{message.name}}
///リスト
num_list = np.arange(10)
return render_template('index.html', message=num_list)
テンプレ側
{% for num in num_list %}
<div>{{ num }}</div>
{% endfor %}
///改行
{%- xxx -%} jinjaタグの前後マイナスで改行空白を除外する
+はtrim_blocks(改行詰め)、Istrip_blocks(空白詰め)を無効化する設定で俺は使わない
from jinja2 import Environment
jinja_env = Environment()
jinja_env.trim_blocks = True
jinja_env.Istrip_blocks = True
///置換
{{ "aaagh" | replace("a","oh,","2") }}
-> oh,oh,agh
{{ input['txtarea'] | replace("\n","<br />") }}
///エスケープ
safeフィルタがなければhtmlエスケープがかかる
{{ output | safe }}
あるいは
{{% autoescape False %}}{{ output }}{{% endautoescape %}}
///コメント(複数行ok)
{# note: commented-out we no longer use this
{% for user in users %}
…
#}
///生
{% raw %}
<ul>
{% for item in seq %}
<li>{{ item )}</li>
{% endfor %}
</ul>
{% endraw %}
///extend と block と include
●flask
page=page_a.model(user)
return render_template("layout.html",title=title,page=page)
●layout.html (テンプレ切替と共通ブロック、共通ブロックはテンプレに書いた方分かり易い?)
{% if page['destination']== 'confirm' %}
{% extends "template_confirm.html" %}
{% elif page['destination']== 'complete' %}
{% extends "template_complete.html" %}
{% else %}
{% extends "template_html" %}
{% endif %}
{% block body %}
{% include "header.html" %}
<p>content here.</p>
{% endblock %}
●template.html
<html>
<body>
<h1>{{ title }}</h1>
{% block body %}
{% endblock %}
#0以上の入力データの確認 (空の確認やintへのキャスト)
#{% if 'inquiry_id' in page['input'] and page['input']['inquiry_id'] is not none and page['input']['inquiry_id'][int > 0 %}
{% if page[input']['inquiry_subject'] %}
<input name="inquiry_subject" type="text" placeholder="例)a" value="{{ page['input']['inquiry_subject'] }}">
{% else %)}
<input name="inquiry subject" type="text" placeholder="例)a">
{% endif %}
{% include "footer.html" %}
</body>
</html>
●header.html
<p>date:2024-8-27</p>
●footer.html
<p>author.p</p>
●page_a.py
def model(user):
value_return = ""
input = {}
error = {}
value_return <br>under constructions taken place by user '<br>'
#入力があった、
if request.method == "POST":
transition = request form.get("transition")
#get, post, 取得するもので書き方が違う
#request.args.get("inquiry_id")
#request.form.get("inquiry_id"
#request.json.get("inquiry_id")
logging warming('#####' + user + 'transition: ' + str(transition)+'#####')
if transition == "new" or transition == "error" or transition == "confirm back":
flag_ng=0
#入力値の判定
error_txt_inquiry_subject = []
inquiry_subject = request.form.get("inquiry_subject")
ff not checkRequire(inquiry_subject):
error_txt_inquiry_subject.append("件名が空欄です”)
flag_ng=1
input[inquiry_subject] = inquiry_subject
if flag_ng == 1:
error['error_txt_inquiry_subject] = error_txt_inquiry_subject
#エラー画面を出す
destination = "error"
else:
#確認画面を出す
destination = "confirm"
#End of transition == "new" or transition == "error" or transition == "confirm back":
else:
#transition == "confirm_proceed"
#登録処理し完了画面を出す
destination = "complete"
else:
#初期画面を出す
destination = "new"
return ("value":value_return, "destination":destination, "input":input, "error":error)
■改行
プレースホルダー内での改行は
:に置き換える
<textarea placeholder="例) aaa bbb">
JINJA2のフォーム入力後の確認HTMLの改行は?
置換ではhtmlエスケープが掛かり<br>がそのまま表示されてしまいダメ
{{ input | replace("\n", "<br>") }}
htmlエスケープ(HTMLエスケープは<>&のみだった)
• 入力があればhtmlエスケープ+改行<br/>変換
• html表示はそのままhtmlエスケープ+改行<br/>変換状態で出力
• DBにそのままhtmlエスケープ+改行<br/>変換状態で出力入れる
• htmlフォーム内表示はhtmlエスケープを解除し表示
↓
入力値はそのままinput変数に保持
confirm画面でエスケープ (html.escape()+改行<br/>変換) しescape変数に保持
DB保存時にはescape変数にプラスしてダブル/シングル/バッククォート、セミコロン、バックスラッシュをエスケープし保存
DBから取り出す際はそれらをアンエスケープしescape変数に保持
Docへはinput変数で保存
画面表示時はアンエスケープ (改行<br />変換+html.unescape())
def escapeHtmlBr(text):
if text is None:
return text
elif isinstance(text, list):
list_escaped = [html.escape(item) for item in text]
list_escaped [item.replace("\n', '<br>') for item in list_escaped]
return list_escaped
else:
escaped_text = html escape(text)
return escaped_text.replace('\n', '<br>')
def unescapeHtmlBr(text):
if text is None:
return text
elif isinstance(text, list):
list_unescaped = [item.replace('<br>', '\n') for item in text]
list_unescaped = [html.unescape(item) for item in list_unescaped]
return list_unescaped
else:
text text.replace('<br>', '\n')
return html.unescape(text)
def escapeDB(text):
if text is None:
return text
elif isinstance(text, list):
list_escaped = [item.replace(';', ';') for item in text]
list_escaped = [item replace('"', '"') for item in list_escaped]
list_escaped = [item.replace("'", ''') for item in list_escaped]
list_escaped = [item.replace('\\', '\') for item in list_escaped]
list_escaped = [item.replace('`', '`') for item in list_escaped]
return list_escaped
else:
escaped_text = text.replace(';', ';')
escaped_text = escaped_text.replace('"', '"')
escaped_text = escaped_text.replace("'", ''')
escaped_text = escaped_text.replace('\\', '\')
escaped_text = escaped_text.replace('`', '`')
return escaped text
def unescapeDB(text)
if text is None
return text
elif isinstance(text, list):
list_unescaped = [item replace('`', '`') for item in text]
list_unescaped [item.replace('\','\\') for item in list_unescaped]
list_unescaped [item.replace(''', "'") for item in list_unescaped]
list_unescaped [item.replace('"', '"') for item in list_unescaped]
list_unescaped [item.replace(';', ';') for item in list_unescaped]
return list_unescaped
else:
unescaped_text = text.replace('`','`')
unescaped_text = unescaped_text.replace('\', '\\')
unescaped_text = unescaped_text.replace(''', "'")
unescaped_text unescaped_text replace('"', '"')
unescaped_text = unescaped_text.replace(';', ';')
return unescaped_text
■文字確認
def check_special_characters(a):|
#チェックする記号のセット
special_characters = ['<', '>', '"', '&']
#特定の記号が含まれているかをチェック
if any(char in a for char in special_characters):
raise ValueError(f"変数 'a' に禁止されている文字が含まれています: {a}")
try:
a = "Hello & World"
check_special_characters(a)
except ValueError as e:
print(e)
■DBのnull行の排除
bq = bigquery.Client()
sql = f"""SELECT a FROM `ds.b' WHERE c = '{pri}'"""
results=bq.query(sql)
list = list()
for row in results:
if row.a is not None:
list.append(str(row.a))
■Flask-WTF CCSRF対策でhiddenに入れる
https://qiita.com/RGS/items/c8c99970054a481ac80d
requrement.txt Flask-WTF==1.2.1
from flask_wtf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'mysecretkey'
csrf = CSRFProtect(app)
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
PRGパターンでGETで完了画面に行くがエラーとならない、WTFはPOSTのみ利くようだ
●完了画面でセッション変数がない場合はエラーとする
保存処理でdoc_idをセッション変数に入れ
不正で直接完了画面にいくとエラーとする
https://xxxx.com/complete?doc id=ddd
■重複登録の回避策(PRG方式+JSボタン無効)
・リダイレクト POST/Redirect/GET パターン
・連打を避けるためボタンのJS無効化
片方のみのためquerySelectorAll() を使う>ページ遷移しなくなった>下記JSを使う
@app.route('/submit', methods=['POST'])
def page():
page = model
if complete:
session['doc_id'] = page['input']['doc_id']
redirect(url_for('thank_you'))
else:
retrun template('layout.html')
@app.route('/thank_you')
def thank_you():
if 'doc_id' in session:
page['input']['doc_id"] = session['doc_id"]
session clear()
return render_template('complete_layout.html', page=page)
else:
return render_template('error.html", message="missing arg")
@app.errorhandler(404)
def page_not_found(e)
return render_template('error html', message="Page not found"), 404
error.html側
<h1>{{message }}</h1>
submit側
連打を避けるためボタンの無効化
片方のみのためquerySelectorAll() を使うとページ遷移しなくなる等がある
<script>
document.addEventListener('DOMContentLoaded', function(){
const form = document.getElementById('form');
const buttons = form.getElementsByTagName('button');
form.onsubmit= function(event) {
//クリックされたボタンを取得
const clickedButton = event.submitter;
//隠しフィールドを追加して、ボタンのname と value を送信
const hiddenField = document.createElement('input');
hiddenField type = 'hidden'
hiddenField.name = clickedButton.name;
hiddenField.value = clickedButton value;
form.appendChild(hiddenField);
// すべてのボタンを無効化して二重送信を防止
for (let i = 0; i < buttons.length; i++) {
buttons[i].disabled = true;
}
}
}
</script>
Posted by funa : 11:34 PM
| Web
| Comment (0)
| Trackback (0)
June 21, 2024
BT
あそびはここで終わりにしようぜ~
Big Table
でっかいテーブル、読み書き低レイテンシー、RDBは負荷高いときにレプ数位でスケールが難しいがBTはするので正規化せずに単一テーブルにしておく感じ
row keyが主役
データを追加するのに3パターンある(行追加、列追加、セル追加)
行に複数カラムファミリーにカラムが幾つか入れられるのでKVSだが結局Where句みたいに使う?
行キー「企業ID#日付」,COLUMN FAMILY「STOCK PRICE」,COLUMN「HI PRICE」「LO PRICE」に対してJSONデータを入れておく等
時間はバージョン管理として持っている
複雑な条件は無理でデータを事前整理して入れておき、JSONカラムを使ったりで一行にまとめスキャンを一発で済ます等で高スループットのみ
Google検索のようにキーワードを入れると、検索結果が数多く一瞬で返る等
複雑な条件はDataprocを使うらしい
Big table構成
インスタンスの中に一つ以上のクラスタ(ゾーン別に設定しレプリケーション)> 各クラスタには1つ以上の同数のノード
クラスタに table > 複数Column family > 複数Column > セル
bigtable_app_profilesで転送クラスタ先の設定する(単一行トランザクション設定を含む)
-マルチクラスタ(自動フェイルオーバ、単一行transaction不可でレプリケーションによる不整合あり)
-シングルクラスタ(手動フェイルオーバ、一行transaction)
デフォルトをマルチにして、通常のクラスタ転送をシングル、問題があるときだけアプリで判定しマルチに行く
スキーマ:
テーブル
行キー(row key)
カラムファミリー(カページコレクションポリシーを含む)
カラム
更新したデータはタイムスタンプによりセル内で保存される
解消するにはガベージコレクション
期限切れ値、バージョン数で設定する
仕様:
KVS、行指向の行単位でスキャン
各テーブルのインデックス (行キー)は1つのみで一意である必要がある
行は、行キーの辞書順に並べ替えられます。
列は、列ファミリー別にグループ化され、列ファミリー内で辞書順に並べ替えられます
列ファミリーは特定の順序では保存されません
集計列ファミリーには集計セルが含まれます
行レベルでアトミック (複数行だと知らんという意)
アトミック性:トランザクション整合性がある(一部の操作だけ実行した状態とならずに)
特定の行にread/writeが集中するより分散が良い
Bigtable のテーブルはスバース、空白行での消費はない
gcloud components update
gcloud components install cbt
(-/cbtrcに以下記載すれば-projectと-instance はデフォルト値で省略できる)
cd ~
echo project unco > ~/.cbtrc
echo instance = chinco >> ~/.cbtrc
cbt -project unco listinstances
cbt -instance chinco listclusters
cbt -project unco -instance chinco ls | grep kuso-t
テーブル名取得
cht -project unco -instance chinco ls kuso-table
カラムファミリやポリシー等取得
cbt -project unco -instance chinco deletefamily kuso-table shikko-family
cbt -project unco -instance chinco deletetable kuso-table
テーブルを消せばカラムファミリも削除になる
Posted by funa : 11:00 PM
| Web
| Comment (0)
| Trackback (0)
June 2, 2024
Cloud SQL
■Cloud SQL Python Connector (Cloud SQL language Connector)
CloudSQL auth proxyのバイナリインストールでないやり方
Cloud SQL Python Connector自体は暗号化しないが、内部IPならサーバレスVPCコネクタで暗号化された通信が使え安全になっている。外部IPアドレスの場合はCloud SQL Auth Proxyで通信を暗号化。
事前必要(pip install>requirements.txt)
Flask==3.0.3
gunicorn==22.0.0
Werkzeug==3.0.3
google-cloud-bigquery==3.25.0
google-cloud-logging==3.11.1
google-cloud-secret-manager==2.20.2
google-api-python-client==2.141.0
google-auth-httplib2==0.2.0
google-auth-oauthlib==1.2.1
websocket-client==1.8.0
google-cloud-resource-manager==1.12.5
Flask-WTF==1.2.1
cloud-sql-python-connector==1.16.0
pymysql==1.0.3
from flask import Flask, jsonify
from google.cloud.sql.connector import Connector
from google.cloud import secretmanager
import pymysql
# 環境変数の定義
PW_NAME = "sql-pw"
PROJECT_NUM = "1234567890"
DB_INSTANCE = "prj:asia-northeast1:db_instance"
DB_USER = "db-user"
DB_NAME = "db001"
# Secret Manager からパスワードを取得する関数
def get_pw(pw_name, project_num):
client = secretmanager.SecretManagerServiceClient()
resource_name = f"projects/{project_num}/secrets/{pw_name}/versions/latest"
res = client.access_secret_version(name=resource_name)
credentials = res.payload.data.decode("utf-8")
return credentials
# Cloud SQL接続
def sql_getconn(connector):
pw = get_pw(PW_NAME, PROJECT_NUM)
conn = connector.connect(
DB_INSTANCE,
"pymysql",
user=DB_USER,
password=pw,
db=DB_NAME,
ip_type="private",
)
return conn
app = Flask(__name__)
@app.route('/test', methods=['GET'])
def get_table_data():
try:
connector = Connector()
conn = sql_getconn(connector)
cursor = conn.cursor()
# SQLを実行して結果を取得
cursor.execute("SELECT no, name, targetDate FROM test")
rows = cursor.fetchall()
# 結果をJSON形式に変換
result = [
{
"no": row[0],
"name": row[1],
"targetDate": row[2].strftime("%Y-%m-%d %H:%M:%S") if row[2] else None
}
for row in rows
]
cursor.close()
conn.close()
return jsonify(result), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
=============
# 追加オプションを使った接続も可
connector = Connector(
ip_type="public", # "private" または "psc" も使用可能
enable_iam_auth=False,
timeout=30,
credentials=None, # 必要ならGoogle認証情報を渡す
refresh_strategy="lazy", # "lazy" または "background"
)
#トランザクション
try:
conn = sql_getconn(connector)
conn.autocommit = False # トランザクション開始、あるいは conn.begin()
cursor = conn.cursor()
# 挿入するデータを準備
new_data = [
{"no": 4, "name": "新しい名前4", "targetDate": "2024-05-01"},
{"no": 5, "name": "新しい名前5", "targetDate": "2024-05-02"},
]
# INSERT文を構築して実行
for data in new_data:
sql = "INSERT INTO test (no, name, targetDate) VALUES (%s, %s, %s)"
values = (data["no"], data["name"], data["targetDate"])
cursor.execute(sql, values)
conn.commit() # トランザクションをコミット
print("Data inserted successfully.")
except Exception as e:
conn.rollback() # エラーが発生した場合はロールバック
print(f"Transaction rolled back due to an error: {e}")
finally:
cursor.close()
conn.close()
#カーソル
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
params: dict形式で取得#[{'no': 1, 'name': 'Alice',...}, ...]
cursor = conn.cursor(cursor=pymysql.cursors.SSCursor)
大量のデータを効率的に取得するためにストリーミングで結果を処理
cursor.execute(query, params=None)
cursor.execute("SELECT * FROM test WHERE no = %s", (1,))
params: プレースホルダーに対応する値のタブルまたはリスト
cursor.executemany(query, param list)
cursor.executemany("INSERT INTO test (no, name) VALUES (%s, %s)", [(1, 'Alice'), (2, 'Bob')])
param list:繰り返し実行するパラメータのリストまたはタブルのリスト
cursor.fetchone()
row = cursor.fetchone()
#結果があれば (1, 'Alice', "2025-01-01") のような形式で1行のみ取得
cursor.rowcount
print(cursor.rowcount) #影響を受けた行数を返す
■接続検証用コンテナをビルド (内部IPを使うrun用)
gcloud builds submit --tag asia-northeast1-docker.pkg.dev/prj/artifact_reg_name/app_name
■IAM?
Cloud SQL設定にCloud SQL 管理者 (roles/cloudsql.admin)、Cloud SQL インスタンス ユーザー (roles/cloudsql.instanceUser)等のIAMが要る?
IAMユーザならいる、ローカルUserなら不要と思われる、ローカルでもCloud SQL Client (roles/cloudsql.client)等は要る
■Cloud SQL MySQL設定
【開発環境】db_instance01
Enterprise / Sandbox / AsiaNorthEast1 (Tokyo) / Single zone
MySQL ver 8.4
Shared core/1cpu 0.6GB/HDD/10GB(auto increase)
PrivateIP/設定にnwが必要(下記)/Enable private path
Auto daily backup 7days (1-5AM) / Enable point-in-time recovery
Week1 sun 0-1am/Enable query insights
root PW: 69696969
【本番環境】
Enterprise plus? キャッシュ使う?
※CloudSQLはTFファイルに記載がなくてもTFステートファイルにPWを含めてしまうためTF化しない
- NW: projects/prj/global/networks/sql-vpc-nw
- Connection name: prj:asia-northeast1 db_instance01
ユーザの作成 sql-user/82828282
PWをコードに入れない、シクレMgrに保存
■MySQL
utf8mb4_ja_0900_as_ci_ksを使う?
_ai... アクセントを区別しない (Accent Insensitive)
_as... アクセントを区別する (Accent Sensitive)
_ci... 大文字・小文字を区別しない (Case Insensitive)
_cs... 大文字・小文字を区別する (Case Sensitive)
_ks... カナを区別する (Kana Sensitive)
_bin... バイナリ
utf8mb4_unicode_ciでは"ア”と“あ”は同じものとして扱われる
utf8mb4_ja_0900_as_ci_ks では"ア"≠”あ”となりカタカナとひらがなを明確に区別できる
utf8mb4_ja_0900_as_ci_ks ならふりがなを使った並び替えで有効
日本語のデータがメインで検索やソートでひらがな・カタカナ・濁点の区別が必要なら utf8mb4_ja_0900_as_ci_ks が適
データベースとテーブルの作成
CREATE DATABASE db;
USE db;
CREATE TABLE test (
no INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(8) NOT NULL,
targetDate TIMESTAMP NOT NULL,
PRIMARY KEY (no),
INDEX index_name (name),
INDEX index_targetDate (targetDate)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_ja_0900_as_ci_ks;
ENUM型は選択肢で早いがALTERが面倒なのでvarcharaにする
inquiry_type ENUM('bq', 'pii') NOT NULL
↓
inquiry_type VARCHAR(255) NOT NULL,
VARCHAR(255) (よく使われる最大サイズ)
VARCHAR(1024) (長めの文字列)
VARCHAR(4096) (長文向け)
長いテキストを扱うならTEXT型
InnoDB の1行の最大サイズは約8KB (8126/バイト)
長さは?メールは255で良い
サンプルデータ
INSERT INTO `table` (`name`, `date`) VALUES ('aaa', '2002-02-23');
ORMapperは面倒なのでSQLを使う
ORM Quick Start — SQLAlchemy 2.0 Documentation【SQLAlchemy】Generic Typesと各種DBの型 対応表SQLAlchemyでのテーブル定義 #Python - Qiita■データベースフラグ
confが直接変更できなためフラグとしてパラメータを渡せる
Cloud SQL studio (コンソールでMySQLが使える)
MySQLクライアントを使いたいならAuth proxyが必要
Cloud SQLが内部IPだとサーバレスVPCコネクタ、or 外部IPならSQL + auth proxy
内部IPで良いのでVPCを作る、CloudSQLを内部IPで作る
サーバレスVPCアクセスコネクタを作る
vpc: sql-vpc-nw, subnet: sql-vpc-subnet 192.168.77.0/24
Gateway 192.168.77.1, Private Google Access On
sql-vpc-nw-ip-range 192.168.78.0/24 on cloudSQL
run-serverless-vpc-ac 192.168.79.0/28 on Run
ファイアウォールルールでポート (デフォルトで3306など) を開放
Cloud Run のNW設定で、サーバーレス VPCコネクタを選択、ルートオプションとしてすべてのトラフィックを VPC コネクタ経由で送信を選択
CloudSQLを30分程度掛けて起動、接続>接続テスト
VPC(例: 10.0.0.0/16)
サブネット(Cloud SQL 用): 10.10.0.0/24(例: us-central1、VPC内)
サブネット(VPCコネクタ用): 10.8.0.0/28(RunからVPCへ通信用、VPC外)
VPC コネクタのサブネットは 10.8.0.0/28 のような小さな範囲を使用、VPC外だがrun自体がVPC外だから?
VPC コネクタはリージョン単位なので、Cloud Run と Cloud SQL を同じリージョンに配置するのが望ましい
Google Cloudの内部NW設計によりVPC内の異なるサブネット間でも通信可能
VPC内なら異なるリージョンのサブネットでもOK(VPC自体には範囲を設定なしでサブネットでIPが被らなければOKかと
追加の設定なしで、例えば us-central1 の VM から asia-northeast1 の Cloud SQLに直接アクセス可
外部IPの場合:
アプリがrunならサイドカーコンテナとしてAuth Proxyを追加できる
サイドカーは同Pod内なのでループバックアドレス127.0.0.1あるいはlocalhost:5432 (Auth Proxy起動時に指定したポート) に通信しCloudSQLに接続する
GCEにDLしてAuth proxyインストールでもいい
アプリのコネクタはAuth Proxy動いているGCEのIP:ポート番号を指定に通信しCloudSQLに接続する
FWでポートも開けること
■run サービスアカウント
run-sql@prj.iam.gserviceaccount.com に必要な権限
Cloud SQL Client (roles/cloudsql.client)
Run Invoker (roles/run.invoker)
Compute Network User (roles/compute.networkUser) -VPCコネクタを使用する
runを建てるが、InternalIPのため同プロジェクト同VPCのGCE を作成し移動してCURLでテスト
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" "https://run-sql-test-1212124.asia-northeast1.run.app/test"
■MySQLでUUIDを使うか、連番を使うか? > ULIDを使う
UUIDは連番に対し
セキュリティ上より安全、サーバが異なってもユニーク
パフォーマンスが悪い (UUIDをプライマリキーにすると速度が落ちる場合がある)
連番とUUIDの両方を振り出しておく? > ULIDを使うことにする
Posted by funa : 01:06 PM
| Web
| Comment (0)
| Trackback (0)
June 1, 2024
GCP hands-off 3
■VPC(例: 10.0.0.0/16)
サブネット(Cloud SQL 用): 10.10.0.0/24(例: us-central1、VPC内)
サブネット(VPCコネクタ用): 10.8.0.0/28(RunからVPCへ通信用、VPC外)
VPC コネクタのサブネットは 10.8.0.0/28 のような小さな範囲を使用、VPC外だがrun自体がVPC外だから?
VPC コネクタはリージョン単位なので、Cloud Run と Cloud SQL を同じリージョンに配置するのが望ましい
Google Cloudの内部NW設計によりVPC内の異なるサブネット間でも通信可能
VPC内なら異なるリージョンのサブネットでもOK(VPC自体には範囲を設定なしでサブネットでIPが被らなければOKかと
追加の設定なしで、例えば us-central1 の VM から asia-northeast1 の Cloud SQLに直接アクセス可
■対象アセットに対する付与可能なロールの一覧表示
Full Resource Name(フルでのアセット名を探せる)
import google.auth
import googleapiclient.discovery
def view_grantable_roles(full_resource_name: str) -> None:
credentials.google.auth.default(
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
service = googleapiclient.discovery.build('iam', 'v1', credentials credentials)
roles = (
service roles()
queryGrantableRoles (body=["fullResourceName": full_resource_name}).execute()
)
for role in roles["roles"]
if "title" in role:
print("Title: role["title"])
print("Name: role["name"])
if "description" in role:
print("Description:" + role["description"])
print("")
project_id = "prj"
#resource = f"//bigquery.googleapis.com/projects/prj/datasets/ds"
#resource + f"//bigquery googleapis.com/projects/prj/datasets/ds/tables/tbl"
resource = f"//cloudresourcemanager.googleapis.com/projects/{project_id}"
view_grantable_roles(resource)
■ロールの一覧表示
https://cloud.google.com/iam/docs/roles-overview?hl=ja#role-types
1)事前定義ロールの場合は roles.get() を使用します。
2)プロジェクトレベルのカスタムロールの場合は、projects.roles.get() を使用します。
3)組織レベルのカスタムロールの場合は、organizations.roles.get() を使用します。
これら3種類で全てを網羅すると思われます
projectIDがsys-のものはGAS、lifecycleStateがACTIVE以外のものも含まれるので注意
■bqへの書き込み
export GOOGLE_APPLICATION_CREDENTIALS="path/to/your-service-account-key.json"
pip install google-cloud-bigquery
from google.cloud import bigquery
client = bigquery Client()
#書き込み先のテーブル情報
table_ref = f"{project_id}.{dataset_id}.{table_id}"
#サンプルデータの生成
def generate_sample_data(num_rows)
data = [
{
"organization": f"org_(num_rows)",
"permission". "view",
}
for _ in range(num_rows)
]
return data
data_to_insert = generate_sample_data(5000)
errors = client.insert_rows_json(table_ref, data_to_insert)
if errors:
print("Errors occurred: {errors}")
else:
print("Data successfully written to BigQuery!")
■データカタログ
データアセットを検索する | Data Catalog Documentation | Google CloudClass SearchCatalogRequest (3.23.0) | Python client library | Google Cloudサンプルで仕様書のAPIを使っているがqueryが空白刻みで入れる等の使い方が分かる
■BQスキーマ+ポリシータグ取得
from google.cloud import bigquery
def get_policy_tags_from_bq_table(project_id, dataset_id, table_id):
print("################ bigquery.Client.get_table().schema start ################")
print(f"Target table: {project_id}.{dataset_id}.{table_id}")
bq_client = bigquery.Client()
table = bq_client.get_table(f"{project_id}.{dataset_id}.{table_id}")
schema = table.schema
policy_tags = []
for field in schema:
print(f"Column: {field.name}")
if field.policy_tags:
tags = [tag for tag in field.policy_tags.names]
policy_tags.extend(tags)
print(f"Policy Tags: {tags}")
else:
print("> No Policy Tags assigned.")
return policy_tags
PROJECT_ID = "prj"
DATASET_ID = "ds"
TABLE_ID = "test001"
policy_tags = get_policy_tags_from_bq_table(PROJECT_ID, DATASET_ID, TABLE_ID)
print("Collected Policy Tags:", policy_tags)
■ポリシータグ設定
from google.cloud import datacatalog_v1
from google.cloud import bigquery
PROJECT_ID = "prj"
DATASET_ID = "ds"
TABLE_ID = "tbl01"
COLUMN_NAME = "aaa"
POLICY_TAG_PROJECT = "prj"
POLICY_TAG_NAME = "projects/prj/locations/us/taxonomies/83893110/policyTags/11089383"
def list_taxonomy_and_policy_tag():
print("############# Start #############")
list_policy_tags = []
client = datacatalog_v1.PolicyTagManagerClient()
request = datacatalog_v1.ListTaxonomiesRequest(
parent=f"projects/{POLICY_TAG_PROJECT}/locations/us"
)
try:
page_result = client.list_taxonomies(request=request)
except google.api_core.exceptions.PermissionDenied as e:
print(f"Skipping project {POLICY_TAG_PROJECT} due to PermissionDenied error: {e}")
return []
except Exception as e:
print(f"An error occurred for project {POLICY_TAG_PROJECT}: {e}")
return []
for taxonomy in page_result:
print(f"############ Taxonomy display_name: {taxonomy.display_name} #############")
print(f"############ Taxonomy name: {taxonomy.name} #############")
request_tag = datacatalog_v1.ListPolicyTagsRequest(parent=taxonomy.name)
try:
page_result_tag = client.list_policy_tags(request=request_tag)
except Exception as e:
print(f"Error on {request_tag}: {e}")
break
for policy_tag in page_result_tag:
print("Policy tag:")
print(policy_tag)
list_policy_tags.append({
"project_id": POLICY_TAG_PROJECT,
"taxonomy_display_name": taxonomy.display_name,
"taxonomy_name": taxonomy.name,
"policy_tag_name": policy_tag.name,
"policy_tag_display_name": policy_tag.display_name,
})
return list_policy_tags
def update_table_schema_with_policy_tag(list_policy_tags):
for policy_tag in list_policy_tags:
if policy_tag['policy_tag_name'] == POLICY_TAG_NAME:
print(
f"Target policy tag:\n"
f" Project ID: {policy_tag['project_id']}\n"
f" Taxonomy Display Name: {policy_tag['taxonomy_display_name']}\n"
f" Taxonomy Name: {policy_tag['taxonomy_name']}\n"
f" Policy Tag Name: {policy_tag['policy_tag_name']}\n"
f" Policy Tag Display Name: {policy_tag['policy_tag_display_name']}"
)
client = bigquery.Client()
table_ref = f"{PROJECT_ID}.{DATASET_ID}.{TABLE_ID}"
table = client.get_table(table_ref)
new_schema = []
for field in table.schema:
if field.name == COLUMN_NAME:
new_schema.append(
bigquery.SchemaField(
name=field.name,
field_type=field.field_type, # Keep original field type
mode=field.mode, # Keep original mode
description=field.description,
policy_tags=bigquery.PolicyTagList([POLICY_TAG_NAME]),
)
)
else:
new_schema.append(field)
table.schema = new_schema
updated_table = client.update_table(table, ["schema"])
print(
f"Updated table {updated_table.project}.{updated_table.dataset_id}.{updated_table.table_id} schema\n"
f"with policy_tag {POLICY_TAG_NAME} on the column {COLUMN_NAME} successfully."
)
if __name__ == "__main__":
list_policy_tags = list_taxonomy_and_policy_tag()
update_table_schema_with_policy_tag(list_policy_tags)
■KSA問題
ブログ内で情報が分散、まとめたい
ワークロード毎にKSA1つ
ksaのtokenはk8s api用でgcp apiに使えない、exprireしない問題がある> Workload identity で解決する
Workload Identity は KSAとGSAの紐づけで、Workload Identity Federationとは違う
workloads がk8sの用語でリソースの総称で、そのidentityであり権限管理、でFederationは更に外部連携
ワークロードは、pod, deplyment, StatefulSet, DaemonSet, job, CronJob, ReplicationController, ReplicaSet
Workload Identity がGKE クラスタで有効化されると、gke-metadata-server という DaemonSet がデプロイ
gke-metadata-server は Workload Identity を利用する上で必要な手続きを実行
SAの紐づけ
/// 現行
Workload identityを有効にして(autopilot でデフォルト有効)
GCP側でKSAとGSAをIAM policy binding
k8s側でKSAとGSAをkubectl annotate
podでKSAを設定
↓
/// 新型のKSA直接bind
workload identity federation ならGSAがなくなりKSAを直接bindできる
Workload identityを有効にして(autopilot でデフォルト有効)
GCP側でKSAにIAM policy binding
※混在するので現行のままが良いようです
まずWIF用のSAを作成する>SAに権限を付与する>
1)Workload identity provider+SAの情報をgithub actionに埋めて使う
GitHub Actions から GCP リソースにアクセスする用途
2)Workload identity poolから構成情報をDLしAWSアプリに埋めて使う
AWSからGCP リソースにアクセスする用途
gcloud auth login-cred-file=構成情報ファイルパス
3)Workload identity poolから構成情報をEKSのOIDC ID token のパスを指定しDL
EKS から GCP リソースにアクセスする用途
- EKSのマニフェストのサービスアカウントのアノテーションにIAMロールを記載
- EKSのサービスアカウントを使用したい Podのアノテーションに追加
- マウント先のパスを環境変数 GOOGLE APPLICATION_CREDENTIALS に設定
- Pod内でSDK またはコマンドにてGCP リソースヘアクセス可能か確認
Posted by funa : 03:24 PM
| Web
| Comment (0)
| Trackback (0)
May 9, 2024
Pubsub
■pubsub
アプリで簡単にPubsubにパブリッシュや、サブスクもできるので、アプリ間の連携にPubsubが使える
• 非同期処理(画像処理とか重めのもの
• IDの種類 (message id, subscription id, topic id, ack id, project idあたりがアプリでは使われるっぽい
※ack idはpull時のみでPushのときはhttpステータスコードが200でackとなる
トピック(メッセージのパブリッシュ先)
• スキーマ/外部アクセス許可/リテンション/GCS/バックアップの設定がある (Push/Pullの設定はない)
• パブリッシュ側のベストプラクティス (JWT)
サブスクライバのPushとPull (PushはEndpointが必要、デフォルトはpull)
• at-least-once (少なくとも1回) 配信を提供します
• 同じ順序指定キーを持ち、同じリージョンに存在している場合は、メッセージの順序指定を有効にできます
• サブスクライバーが31日間未使用、またはサブスクリプションが未更新の場合、サブスクリプションは期限切れ
pushはhttpsが必要?
• push エンドポイントのサーバーには、認証局が署名した有効な SSL証明書が必要でhttps
• Cloud run でEvent Arcを設定するとサブスクが自動作成されrunのデフォルトhttpsのURLが使われるが、これはPullよりPushで安定した
• CronバッチならPullで安定するのでは?大量リクエストはPull向きとある(Pullは失敗処理込みの話かも知れん)
トピックのリテンション:デフォルトなし、最小値:10分、最大値:31日
サブスクのリテンション:デフォルト値:7日、最小值:10分、最大値:7日
pubsub ack期限(Ack Deadline)
•デフォルト60秒> 設定10分>ack延長で最大1時間まで伸ばせると思われる
•exactly onceを設定しなければ期限の延長は保証されない
•ack期限を過ぎる、あるいはNackを返す場合、メッセージは再配送される
•ack応答期限の延長は99パーセンタイル(上位1%の値よりも小さい値のうち最大の値)で
modifyAckDeadlineを返し、延長してもMaxExtension (ack期限を延長 する最大値) 60minまで?
modifyAckDeadlineリクエストを定期的に発行すればよいらしい
メッセージの再試行を強制するには
•nack リクエストを送信
•高レベルのクライアント ライブラリを使用していない場合は、ackDeadlineSeconds を0に設定して modifyAckDeadline リクエストを送信する
•pullなら設定できる。他には、Cloud Dataflowを組み合わせる(プログラムコードでDataflowを使う感じかり、あるいはmessageについているunique idを利用して、KVS を用いたステート管理をして自前で重複を排除する
•再配信は、メッセージに対してクライアントによる否定確認応答が行われた場合、または確認応答期限が切れる前にクライアントが確認応答期限を延長しな かった場合のいずれかか原因で発生することがある。
※exactly onceはエラーでも再配信でPubsubパニックしないようにしたいために使うものではない?
pubsubはトピックにPublishされたメッセージをDataflowに引き継げる
Dataflow (Apache Beam) を大量のメッセージをバッチ処理する場合に使える
Pub/Sub→Dataflow→処理
•Apache Beamのウィンドウ処理とセッション分析とコネクタのエコシスエムがある
•メッセージ重複の削除ができる
•pubsub>dataflow>BQやGCS: この流れでログ等をストーリミングで入れ込める
BQサブスクリプション (PubSubはBigQuery Storage Write API を使用してデータを BigQueryテーブルに送信、GCSサブスクもある)
サブスクライバーApp側のコードでのフロー制御によりちょっと待てよのトラフィック急増対応
デッドレタートピック (配信試行回数が見れる)やエラーでの再配信
• Pub/Subサブスクリプションにデッドレタートピックを設定しておくと、一定の回数再送信が失敗したメッセージの宛先がデッドレタートピックに変更され貯められる
メッセージのフィルタ、同時実行制御により多いメッセージに対応
Pubsubをローカルでエミュレートする
pubsubのスナップショットやリテンション
トピックにリテンションを設定しスナップショット作成> 過去のサブスクしたメッセは見えなさそう
サブスクにリテンションを設定しスナップショット作成> 過去のAckしたメッセは見えなさそう
スナップショットでどう使うのか?
キューがたまっているときに撮るものと思われる。またシーク時間のポイントを設定する意味がある
スナップショットとシークを使いこなして特定期間の再実行を行う機能
スナップショットで再実行する
シークは指定時間か最後のスナップショット以降のサブスク再実行(実際pushでrunが再実行された)
Pubsubにどんなメッセージが入ってきているか確認する方法
pull形式ならAckしなければpullボタンで拾い見れる (トピックでパブリッシュしてサブスクでPull し見る)
トラブルシュートはログを見るかデッドレタートピックかGCSバックアップを見る?
デッドレターキュー(ドロップしたものの確認と救済?)
サブスクでDLQのONしデッドレタートピックを設定し転送する>GCSにもバックアップできる
DLTでメッセージ(実行済みOR未実行)の再生
データ形式:スキーマを使うか、スキーマなしならdataで取得できる
from google cloud import pubsub_v1
from avro.io import DatumReader, BinaryDecoder
from avro schema import Parse
project_id="your-project-id"
subscription id="your-subscription-id"
subscriber pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path(project_id, subscription_id)
avro_schema = Parse("""
{
"type": "record",
"name": "Avro".
"fields": [
{
"name": "ProductName",
"type": "string",
"default":""
},
{
"name": "SKU",
"type": "int",
"default": 0
}
}
def callback(message):
print(f"Received message: {message}")
reader = DatumReader(avro_schema)
decoder = Binary Decoder (message.data)
avro_record = reader.read(decoder)
message_id=message.message id
message.ack()
print("Message ID: (message_id}")
product_name = avro_record['ProductName']
sku= avro_record['SKU']
print("Product Name: (product_name}")
print("SKU: (sku}")
subscriber.subscribe(subscription_path, callback=callback)
def callback(message):
print("Received message: (message)")
data message data
message_id=message.message_id
message.ack()
print("Date (data)")
print("Message ID: (message_id)")
Pub/SubでStreamingPull APIを使用してメッセージをリアルタイムで処理する - G-gen Tech BlogStreamingPull API を使用するとアプリとの間で永続的な双方向接続が維持され、Pub/Sub でメッセージが利用可能になるとすぐに pullされる。1 つの pull リクエストで 1 つの pull レスポンスが返る通常の 単項 Pull と比較すると、高スループット・低レイテンシ。必要なメッセージを残す処理をしたりも?GCP側の問題であっても通信が切れた場合は別サーバに繋ぎなおすためmodifyAckDeadlineも切れ再配信されるバグがある
+++
メッセージのTTL (Time-To-Live) はメッセージ保持期間(message retention duration) に依存
メッセージが TTLを超えると、自動的に削除され、Subscriberが受信できなくなる
ackDeadlineSeconds (デフォルトは10秒、最大600秒) を超えたACKのメッセージは再配信されますが、TTL期限を超えた場合は消える
#TTLを最大7日間に設定
gcloud pubsub subscriptions update my-subscription message-retention-duration=604800s
DLQ (Dead Letter Queue)
Subscriberが指定回数(最大100回) メッセージのACKを行わなかった場合に、メッセージを隔離する仕組み
DLQもサブスクなので期間やTTL設定方法は同じ
#DLQ topic 作成
gcloud pubsub topics create my-dlq-topic
#5回失敗したらDLQへ
gcloud pubsub subscriptions update my-subscription dead-letter-topic=projects/my-project/topics/my-diq-topic max-delivery-attempts=5
#DLQ subsc作成
gcloud pubsub subscriptions create my-diq-subscription--topic-my-diq-topic
#サブスクの詳細確認
gcloud pubsub subscriptions describe my-diq-subscription
#DLQメッセージの確認、-auto-ackも付けられるが、
gcloud pubsub subscriptions pull my-dlq-subscription -limit=10
[{
"ackld": "Y3g49NfY...=",
"message": {
"data": "SGVsbG8gd29ybGQ=", #Base64 エンコードされたデータ
"messageld": "1234567890",
"publish Time": "2024-02-18T12:34:56.789Z"
}
}]
#base64のでコードが必要
echo "SGVsbG8gd29ybGQ=" | base64-decode
#ack-idによりackを返しDLQメッセージを削除
gcloud pubsub subscriptions acknowledge my-diq-subscription--ack-ids=Y3g49NfFY
モニタリング > アラートポリシーから新しいアラートを作成し
pubsub.subscription.outstanding_messages を監視対象に選択し、閾値を設定するとよい
#DLQ メッセージの再処理をfunctionsに設定 (トピックに入れなおす)
from google.cloud import pubsub_v1
publisher = pubsub_v1.PublisherClient()
topic_path = publisher.topic_path("my-project", "my-topic")
def republish_message(message):
future = publisher.publish(topic_path, message.data)
print(f"Republished message ID: {future.result()}")
subscriber = pubsub_v1.SubscriberClient()
subscription_path = subscriber.subscription_path("my-project", "my-dlq-subscription")
def callback(message):
print(f"Received message: {message.data}")
republish_message(message)
message.ack()
subscriber.subscribe(subscription_path, callback=callback)
Posted by funa : 12:00 AM
| Web
| Comment (0)
| Trackback (0)
April 27, 2024
HELM
helmはコマンド一発だが生k8sはマニフェストファイルの数だけkubectl apply(delete)を繰り返す必要がある
helm upgrade chart名 -f 環境毎yamlファイル
文法覚えるより繰り返した方がええんじゃない
helmはテンプレートフォルダ以下がマニフェスのようなもの
ループ処理が記述可、関数が使える、関数を作れる
helmは基本はテキストの整形用と言える(ヘルパー関数やビルトイン関数を使い外部ファイルを取り込んで変形したり、変数yamlを環境yamlで上書きし外部の値を使う等で沢山のGKEアセットをループ的に生成しようとしている)
helm create <チャート名>
templates/ マニフェスト (テンプレート)
env/ 自分で作成するが環境毎に異なる値の入る変数を記述
┣dev.yaml
┣prd.yaml
values.yaml 繰り返す値等 (dev/prd.yamlが優先され上書きされる)
helm upgrade-install <release名> <Helmチャートの圧縮ファイル名>
●●helmテンプレートの文法 (.ファイル名.親.子で表す、.はルートオブジェクト、Valuesはvaluesオブジェクト、$変数:=値、ymlインデントはスペース2つ)
●templates/deployment.yaml
{{ $env := Values.environment }}
{{ $serviceAccountName := Values.serviceAccountName }}
image: {{ .Values.deployment.image }}:{{.Values deployment.imageTag }} //nginx:latest
serviceAccountName: {{ $serviceAccountName }}-{{ $env }} //sample-sa-dev
↑
●values.yaml
deployment:
image: nginx
imageTag: latest
serviceAccountName: sample-sa
●env/dev.yaml
environment: dev
※values.yaml よりdev/prd.yamlが優先され上書きされ.Valueで使う
●●helmテンプレートのループ (range~end)
●templates/es.yaml
spec:
nodeSets:
((- range .Values.es.nodeSets }}
name: {{ .name }}
config:
node.attr.zone: {{ .zone }}
{{- end }}
↑
●values yami
es:
nodeSets:
- name: node-a
zone: asia-northeast1-a
- name, node-b
zone: asia-northeast1-b
●●helmテンプレートのIF (if-end)
●templates/ingress.yaml
((- if .Values.ingress.enabled -))
apiVension: networking k8s.io/v1
kind: Ingress
{(- end }}
●env/prd.yaml
ingress:
enabled: true
●env/dev.yaml
ingress:
enabled: false
●●helmテンプレートの複数値 (toYaml、nindentは関数)
●templates/ingress.yaml
metadata:
annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }}
●values.yaml
ingress:
annotations:
kubernetes.io/ingress.global-static-ip-name: sample-ip-name
kubernetes.io/ingress.class: "gce-internal"
●●その他
中括弧内側の前後にダッシュ {{--}} をつけることができ、前に付けた場合は前の半角スペースを、 後ろにつけた場合は改行コードを取り除く
hoge:
{{- $piyo := "aaa" -}}
"fuga"
/* */で囲まれた部分はコメント構文
helm create [チャート名]で自動でtemplates ディレクトリに_helpers.tplが作成されるが、 partialsやhelpersと呼ばれる共通のコードブロック (defineアクションで定義されtemplateアクションで呼び出される)や、ヘルパー関数などが定義される。
_アンスコ始まりのファイルは、他のテンプレートファイル内のどこからでも利用できるという共通部品。 これは内部にマニフェストがないものとみなされる。
種類としては、values.yamlが差し替え可能な変数、ローカル変数が定義したTemplateファイル内でのみ使える変数、_helpers.tplはチャート内で自由に使える変数
●templates/_helpers.tpl
{{- define "deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{.name }}
name: {{ .name }}-deployment
spec:
replicas: {{ .replicas }}
selector:
matchLabels:
app: {{ .name }}
template:
metadata:
labels:
app: {{.name}}
spec:
containers:
- image: {{ .image }}
name: {{ .name }}
{{- end -}}
●values.yaml
nginx:
replicas: "1"
name: nginx
image: docker.io/nginx:1.25.1
httpd:
replicas: "3"
name: httpd
image: docker.io/httpd:2.4.57
●deployment-nginx.yami
{{ include "deployment" .Values.nginx }}
※{{ include "deployment" 引数 }}で関数を呼ぶ
●●英語サイトだともっと情報がある
Helm | Built-in Objects.Filesなどのビルトインオブジェクトがあったりと、、、
GKEクラスタを作成しておく
kubectlでArgo adminとシークレット作成?
brew install argocd
Argo cd設定ファイルリポジトリのclone
argocd cluster add <context name>
argocd repo add <repo url> --ssh-private-key-path ~/.ssh/id_rsa
argocd-configuration に設定を追加
argocd-insallation に設定を追加
argo cd上からinstallationをsyncする
argocd login --grpc-web --sso dev-argocd.dev.bb.com
===
ArgoはSettingsにリポジトリ、クラスター、プロジェクト、他にUserの設定
アプリ設定でhelmのパス等を指定(Argo内部でhelm upgradeでなくkubectrl applyに変換しでやってもらえるお作法:helmコマンドのインストール不要でArgoでhelm文法が使える)
Posted by funa : 11:27 PM
| Web
| Comment (0)
| Trackback (0)
January 14, 2024
GKE
モダンか何か知らんが、豚玉かイカ玉で十分じゃ
===========
kubectlチートシート | Kubernetesフォルダに .py と requirements.txt と .dockerignore と Dockerfile を入れてアップロードしている
gcloud builds submit --tag asia-northeast2-docker.pkg.dev/bangboo-prj/xxx/image001
helloworld@bangboo-prj.iam.gserviceaccount.com 作成
アクセス元のIPを確認するCloud run作成
ドメインないと無理なのでLBとIAPをあきらめ生成されるURLで十分
Cloud runでアクセス元IPを表示するヤツ
runのallUsersのinvokerを削除したらアクセス不可になった(この方法で管理する)
curl http://ifconfig.me/ で十分だったが
GKE
k8sの内部NWは通常別途いるがGKEは速い奴が動作
GKEはクラスタ内部のDNSでサービス名で名前解決できる
サービスのIPとポートは環境変数で参照可
kubectlを使うには、gcloud container cluters get-credentials を打つ必要がある
GKE設定
-クラスタ:側の設定(IP範囲とかセキュリティとか?)
一般/限定公開:外部IPを使うか使わないか
コントロール プレーン承認済みネットワーク:CPにアクセスできるセキュリティ範囲
-ワークロード:マニフェストで設定
一般か限定公開か?コントロールプレーンが外部IPか?CPがグローバルアクセス可か?承認NWか?
一般公開で承認NWが良いのでは?簡単だし、
限定公開で使うには>CPに外部IPで承認NWでいいのでは?
NW:default subnet:default
外部IPでアクセス許可
CP アドレスの範囲 192.168.1.0/28とか172.16.0.0/28(サブネット重複しない奴)
コントロール プレーン承認済みネットワーク home (169.99.99.0/24ではなくGCPのIPぽい)
限定公開ならnatが要る
CPの VPCのIP範囲は、クラスタの VPC 内のサブネットと重複不可。CPとクラスタは VPC ピアリングを使用してプライベートで通信します
グローバルアクセスは別リージョンからという意味っぽい、cloud shellからのkubectlのためONが良い
デフォルト設定なら作成したサブネットのIP範囲でなくクラスタが作られない
面倒ならdefault-defaultで良いかも
サブネットをVPCネットワークを考えて指定する方が偉いかも知れんが
default asia-northeast2 10.174.0.0/20 の場合
サブネットは asia-northeast2 10.174.27.0/24 とか
ARにあるコンテナからGKEをデプロイが簡単にできる
Cloud Source Repositories でソース管理gitが下記のようにできる
gcloud source repos clone bangboo-registry --project=bangboo-prj
cd bangboo-registry
git push -u origin master
run使用中のコンテナがGKE上では上手くいかない runのコンテナは8080のようだ
Dockerfileとmain.py上ではポートは何でもよい仕様だが、runで自動的に8080割り当てるようだ
それが駄目でありGKEは環境変数でPORT 8080を指定
CrashLoopBackOff問題がでる
https://www.scsk.jp/sp/sysdig/blog/container_security/content_7.html
デプロイ公開でポート80 ターゲットポート8080に(クラスタを作成後、ワークロードでデプロイする)
developmentのspec: containers: ports: - containerPort: 8080 を入れる?
yamlでなく、コンソールで設定時に入れると良い
$ kubectl get all
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/flask-1-service LoadBalancer 10.48.4.134 34.97.169.72 80:32147/TCP 20m
us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
これは簡単に上手く行く、環境変数PORT8080不要
ワークロードでyamlの spec: replicas: 0を保存するとアクセスを止められる
コンフィグマップ:構成ファイル、コマンドライン引数、環境変数、ポート番号を別途持っていてPodにバインドする(マニフェストに書くと抜き出され見れる)
シークレット:Base64の値?(マニフェストに書くと抜き出され見れる)甘いのでsecret mgrを使う方が良い?
config map/secretはマニフェストで編集する必要がある(見れるだけと思われる)
エディタで見てみる:yamlとかステータスが見れる
■LBに静的IPを振る
hello-app-addressと名付けたIPを取得
LBのアノテーションで設定
# ingress.yaml(NWはNodePort、Route
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello-ingress
namespace: default
annotations:
kubernetes.io/ingress.global-static-ip-name: hello-app-address # IP
networking.gke.io/managed-certificates: hello-managed-cert # 証明書
kubernetes.io/ingress.class: "gce" # 外部 HTTP(S)LB
spec:
defaultBackend:
service:
name: hello-deployment
port:
number: 8080
ServiceのLBはリージョン指定するタイプの静的IP
IngressはグローバルIPOK
apiVersion: v1
kind: Service
metadata:
name: hoge
labels:
app: hoge
spec:
ports:
- port: 80
selector:
app: hoge
tier: frontend
environment : stage
type: LoadBalancer
loadBalancerIP: xxx.xxx.xxx.xxx
ArmorでIP制限
1)サービスから対象を選択しingressを作成することでLBを追加しArmorも設定可能
2)デフォルトLBに付けるにはkubectl要りそう、backendconfig.yamlはどこに置く
サービス画面のkubectrlから
# backend-config.yaml を作り kubectl apply -f backend-config.yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
namespace: default
name: hello-backend-config
spec:
securityPolicy:
name: "bangboo-armor"
serviceのyamlに下記を追加
metadata:
annotations:
cloud.google.com/backend-config: '{"ports": {"8080":"hello-backend-config"}}'
↑これでは不足する
どこで設定状態を見るか?
ingress作成してLBとArmorつけて、デフォルトLBを削除してみる?
GKEの外部からのアクセスを制限するには?
限定公開+コントロールプレーンは承認済み等でアクセスしKubectlする
Cloud shellのグローバルIPを取得しシェルを承認済みNWにできないか?>OK
curl http://ifconfig.me/
GKEでPythonをCron定期実行させたい
ArgoでDAGを実行させたい
https://zenn.dev/ring_belle/articles/2c4bbe4365b544
ArgoでGKEのCICD(Argoは別ホストでGithubにアクセスし、GKEを操る)
https://www.asobou.co.jp/blog/web/argo-cd
サービスアカウント
Workload Identity Federation for GKEの新しい設定方法を解説 - G-gen Tech Blog1)ノードに紐付いたサービスアカウントKSAをそのまま使用する(裏でimpersonate)
gkeのサービスアカウントとIAMサービスアカウントの紐づけが不要になった
VPCサービスコントロールで管理したい場合impersonateのSAを指定できないためWIFが要る
2)サービスアカウントのキーを Kubernetes Secret として GKE クラスタに登録する
3)Workload Identity Federationをつかう
※KSAはノード単位で設定、Pod単位でGCPのリソースにアクセスできるように管理したい?
●メモ
忙しいときはスケールアウトするが、落ち着き始めるとスケールinし、必要なPodも落とされてしまう
safe-to-evict をymlのannotationで明示して特定Podはスケールinしない等にしておく
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict:"false"
クラスタのオートスケーラー イベントの表示 | Google Kubernetes Engine (GKE) | Google Cloud↓
最小Pod数をスケールinした値で固定する等も
■Workloads リソース
Pod:Workloadsリソースの最小単位
ReplicaSet:Podのレプリカを作成し、指定した数のPodを維持し続けるリソースです。
Deployment:ローリングアップデートやロールバックなどを実現するリソースです。
DaemonSet(ReplicaSet亜種):各ノードにPodを一つずつ配置するリソースです。
StatefulSet(ReplicaSet亜種):ステートフルなPodを作成できるリソースです。
Job:Podを利用して、指定回数のみ処理を実行させるリソースです。(使い捨てPod)
CronJob:Jobを管理するリソースです。
Config connector:GKEでGCPリソースを調節してくれるアドオン。Podの増加減少にあたり必要なアカウントや権限やPubSub等々を自動作成や管理する。マニフェストのymlにcnrmのAPIを記載したりする(Config connector resource nameの略)
■GKE関連の運用
GKEクラスタ認証ローテーション
30日以内になると自動ローテーションするが危険なので手動が由
GKEはマイクロサービスのエンドポイントでのサービス提供かgcloud api利用が前提といえるのでこれでOK
1) ローテ開始 (CPのIPとクレデンシャル)
2) ノード再作成
3) APIクライアントを更新 (クレデンシャル再取得)
4) ローテ完了 (元IPと旧クレデンシャルの停止)
GKEクラスタ認証ローテーションの考慮
セキュアなGKEクラスタ
コントロールプレーンの自動アップグレード&IPローテーション&ノードブールの自動アップグレードで死ぬ
CPの更新後はクレデンを取得しなおす必要がある、ArgoでCICDを組んでいるとクラスタに認証入りなおす必要がある
ノードが入れ替わりに時間が掛かり、時間差で問題がでることがあるので注意
Posted by funa : 09:59 PM
| Web
| Comment (0)
| Trackback (0)
October 31, 2023
GCP Network Connectivity
●共有 VPC
同組織のプロジェクトのホストプロジェクト(親)のVPCをサービスプロジェクト(子)に共有
●VPC ネットワーク ピアリング
異なる組織間の接続(双方のVPCでコネクションを作成する、内部IPで通信する、サブネットは重複しないこと、2ホップ制限で1:1=3つ以上の場合は古メッシュでコネクション作成要)、k8sサービスとPod ipをVPCピアリング経由する利用法もある
●ハイブリッド サブネット
Cloud VPN/Interconnect等が必要、オンプレルータとCloud RouterをBGPでつなぐ、オンプレとGCPをつなぐ
●Cloud Interconnect
DCと専用線で閉域網接続、Cloud VPNより低レイテンシ/帯域安定
●Cloud VPN
オンプレとIPsec VPN接続、アドレス帯の重複だめ、Cloud VPN側でBGPIP設定やIKEキー生成をしオンプレルータ側でそれらを設定する
●内部範囲
VPCで使うIPをCIDRで定義しIP範囲の使用方法を事前に決定しておく、IPが勝手に使われたりしない等ができる
●限定公開の Google アクセス(Private Google Access)
外部IPを持たないGCE等はデフォルトのインターネットゲートウェイ0.0.0.0を経由してGoogle APIにアクセスする、VPC>Routesで見れる
●オンプレミス ホスト用の限定公開の Google アクセス
CloudVPNやInterconnectを経由してオンプレから内部IPを利用してGoogleAPIにアクセス、GCP側ではCloudDNSで特定のドメインのAレコードを入れる、選択したドメインのIPアドレス範囲を静的カスタムルートでVPC内のプライベートIPからルーティングできるように設定する、オンプレにはCloudRouterからドメインのIPアドレス範囲をBGPでルーティング広報する、VPNやInterconnectがないと0.0.0.0でGoogleAPIにアクセスするがこれだとRFC1918に準拠しない199.33.153.4/30などのIPを使う必要がありルーティングが複雑化したり、オンプレを通る場合があり通信は慎重に設計をすること
●Private Service Connect
「限定公開の Google アクセス」の発展版、オンプレをNATでVPCに接続、内部IPでGoogleAPIにアクセスできる、PSCエンドポイントを介して内部IPで公開できる、NATされ内部IPの公開先での重複OK
●プライベート サービス アクセス
VPCペアリングを併用してサービスプロデューサをVPCに接続し内部IPで次のようなサービスに内部IPでアクセスできるようにする(Cloud VPNまたはInterconnectを付け足せばオンプレからも可)、Cloud SQL/AlloyDB for posgre/Memorystore for Redis/Memcached/Cloud build/Apigee等の限られたもの
●サーバーレス VPC アクセス
サーバレスからVPC内リソースにアクセスするためのコネクタ(通常は外部IP通信になるがコレだと内部IPでVPCにルーティングされる、/28のサブネットを指定)、例えば既存のcloud runサービスを編集しても付けられず初期構築時のみ設定できる
●外部 IP アドレスを持つ VM から API にアクセスする
IPv6をVMに設定し限定公開DNSゾーン設定をすればトラフィックはGCP内にとどまりインターネットを通りません
●CDN Interconnect
Cloud CDNもあるが他社のCDNに接続する、Akamai/Cloud flare/fastly等々
●Network Connectivity Center
ハブとなりCloudVPN/InterconnectをメッシュしGCP/オンプレ含め通信させる、Googleのバックボーンでユーザ企業の拠点間を接続できる
●ダイレクト ピアリング
GoogleのエッジNWに直接ピアリング接続を確立し高スループット化、Google workspaceやGoogleAPI用だが普通は使わずInterconnectを使う
●キャリア ピアリング
ダイレクトピアリングの高度な運用が自社対応できない等でサービスプロバイダ経由でGoogle workspaceなどのGoogleアプリに品質よくアクセスする
Google CloudのVPCを徹底解説!(応用編) - G-gen Tech Blog
●トンネル系の下記は色々権限が要りそうで候補
Compute OS login/IAP-secured tunnel user/Service account user/viewer/compute.instance*
■ポートフォワードは止めないと別につないで繋いでいるつもりでも同じところに繋ぎ続ける
lsof -i 3128
ps ax | grep 3128
ps ax | ssh
kill [PID]
■IAPトンネル
export http_proxy=http://localhost:3128
export https_proxy=http://localhost:3128
gcloud compute start-iap-tunnel --zone asia-northeast1-a gce-proxy001 3128 --local-host-port=localhost:3128 --project=gcp-proxy-prj
でコマンドを打てばIAP踏み台トンネルを通って外部に通信できる
■踏み台コマンド
gcloud compute ssh --projet gcp-prj-unco --zone asia-northeast1-a gce-step-svr
でSSHログインしそこからcurl等で操作する
■シークレットからPWを取りつつコマンドを打つ
gcloud compute ssh --project gcp-prj --zone asia-northeast1-b stp-srv --tunnel-through-iap fNL 3306:mysql.com: 3306
mysql -u baka_usr -p"$(gcloud secrets versions access latest --secret mysql_pw --project=gcp-prj)" -h 127.0.0.1-P 3306
mysqlコマンドのpオプションは空白なしでPWを入れる、baka_usrはMySQLのユーザ、mysql_pwはsecret mgrに保存した名前
$()のLinuxコマンドでgcloudコマンドを入れ子。ワンライナーの形で利用ができる
secret mgrのコマンド
ワンライナー解説(変数と$()とバッククォート
■SSHトンネル
sshの基本はこれ、セキュアシェル、トンネルは特殊?
SSH [オプション] [ログイン名@]接続先 [接続先で実行するcmd]
接続先に権限があること
SSHの疎通確認
ssh baka@stp_srv.unco.com
設定例
.ssh/config
User baka
Hostname step_srv
ProxyCommand ssh -W %h:%p baka@step_srv.unco.com
PubkeyAuthentication no
PasswordAuthentication yes
※sshはPWは危険なので鍵認証のみにしたい
IPアドレス元を制限や同一IPのログイン試行は拒否する仕組み等は欲しい
SSHコネクション上でトンネル作る
ssh step_srv -L 8080:dest.benki.com:80
とか ssh -L 8080:dest.benki.com:80 ahouser@step_srv.unco.com
※ポート22でstep_srvにSSHコネクションを貼り、ローカル:8080のリクエストはdest:80に転送する
↓
ブラウザか新規ターミナルでcurl
http://localhost:8080
=============
なぜレッドオーシャン化する前にサービスを グロースできなかったのか? - フリマアプリ編 - (フリル)サービスを急拡大させる意思決定が遅く競合に遅れ
競合出現後も経営方針を大きく変えなかった
勝利条件はユーザ数で機能差ではなかった
パワープレーでいかにプロモーションばら撒いて認知広げて第一想起をとるかだった
先行者優位で過ごせる期間は短い
スタープレイヤーの採用、手数料無料化、TVCM等PLを超えた手法があった、BS経営すべきだった
成長のキャップが創業者の能力になっていた
有能な人材:耳の痛いことを言ってくれる人材を経営チームに採用しても良かった
CTOが開発をし、組織運営の雑務をし、採用もやっていた
CEOは机の組み立てをするな。CTOはPCの購入をするな
役割の変化に素早く適用し権限移譲を行い、やるべきことをやれる状況を作る
あるいは必要な組織を大きくすることに注力する、例えば開発組織を大きくする
戦時のCEO、皆に戦時であることを伝える、企業文化に背く意思決定も行う
研究や教育等、やった方が良さそうな耳障りの良いタスクも拒否する
どうやったら市場で勝てるかの戦略
↓
IPOとか目指さなければConfort zoneを見つけてじっくりまったりビジネスを継続させる手もある
メルカリやPay2をみた結果論、このやり方も古いというかアレ
Posted by funa : 10:57 PM
| Web
| Comment (0)
| Trackback (0)