AWS Systems Manager(通称 SSM)のRun Commandを使うと、EC2インスタンスに対しての様々な管理タスクの実行を自動化したり、多くのインスタンスにたいして大規模に実行したりできます。
管理タスクはコマンドドキュメントという形で定型化されていて、このうちの AWS-RunShellScript
を使うと、任意のLinuxコマンドを、外部から実行することができます。
AWS Systems Manager(通称 SSM)のRun Commandを使うと、EC2インスタンスに対しての様々な管理タスクの実行を自動化したり、多くのインスタンスにたいして大規模に実行したりできます。
管理タスクはコマンドドキュメントという形で定型化されていて、このうちの AWS-RunShellScript
を使うと、任意のLinuxコマンドを、外部から実行することができます。
Webサーバーを運用していると、定期的にコマンドを実行させたいことはよくあります。 たとえば、このブログだと予約投稿で決められた日時を過ぎたら公開処理をする、みたいなのですね。
そういったものを簡単に実現する手法として、古くから cron が用いられてきました。
しかしながら、負荷分散のために EC2 AutoScaling や OpsWorks など、同じ構成のインスタンスを複数並列起動する構成では、グループのインスタンスすべてで同じジョブが実行されてしまう問題があります。
1台をグループから外し、その1台だけでジョブを実行するようにする方法もありますが、cron実行が単一障害点となり、複数台構成の恩恵を十分に受けられません。
複数台構成に対応するジョブスケジューラ製品もいろいろありますが、ここではAWSのサービスだけで実現する方法を考えてみます。
続きを読むAWS::WAFv2::IPSet
にIPアドレス(プレフィクス付き)を指定することScope: "CLOUDFRONT"
にすることと、米国東部 (バージニア北部) リージョン (us-east-1) にリソースを作成すること。ALBで使う場合は Scope: "REGIONAL"
Parameters: LoadBalancerArn: Type: String PathPattern: Type: String Default: /admin/ Resources: WlitelistIpAddressSet: Type: "AWS::WAFv2::IPSet" Properties: Addresses: - 200.100.0.0/24 - 100.200.100.200/32 IPAddressVersion: IPV4 Scope: "REGIONAL" WhitelistPathPatternSet: Type: AWS::WAFv2::RegexPatternSet Properties: RegularExpressionList: - !Sub "^${PathPattern}*" Scope: "REGIONAL" WebACLAssociation: Type: AWS::WAFv2::WebACLAssociation Properties: ResourceArn: !Ref LoadBalancerArn WebACLArn: !GetAtt WhitelistWAFv2WebACL.Arn WhitelistWAFv2WebACL: Type: "AWS::WAFv2::WebACL" Properties: DefaultAction: Block: {} Rules: - Name: "WhitelistWAFv2WebACLRulePathPattern" Action: Allow: {} Priority: 100 Statement: NotStatement: Statement: RegexPatternSetReferenceStatement: Arn: !GetAtt WhitelistPathPatternSet.Arn FieldToMatch: UriPath: {} TextTransformations: - Type: "URL_DECODE" Priority: 0 VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: "WhitelistWAFv2WebACLRulePathPatternMetric" SampledRequestsEnabled: true - Name: "WhitelistWAFv2WebACLRuleIPSet" Action: Allow: {} Priority: 1000 Statement: IPSetReferenceStatement: Arn: !GetAtt WlitelistIpAddressSet.Arn VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: "WhitelistWAFv2WebACLRuleIPSetMetric" SampledRequestsEnabled: true Scope: "REGIONAL" VisibilityConfig: CloudWatchMetricsEnabled: true MetricName: "WhitelistWAFv2WebACLMetric" SampledRequestsEnabled: true
# == Schema Information # # Table name: places # # id :bigint not null, primary key # geom :geography not null, point, 4326 # # Indexes # # index_places_on_geom (geom) USING gist class Place < ApplicationRecord end class CreatePlaces < ActiveRecord::Migration[6.0] def change create_table :places do |t| t.st_point :geom, geographic: true, null: false t.index :geom, using: :gist end end end
Place.where("geom && ST_MakeEnvelope(:min_lng, :min_lat, :max_lng, :max_lat, 4326)", min_lat: min_lat, min_lng: min_lng, max_lat: max_lat, max_lng: max_lng)
st_point
は activerecord-postgis-adapter による拡張geom
は座標を示すジオグラフィ型 (SRID 4326)&&
演算子で矩形ポリゴンと交差する座標を選択postgis/postgis イメージがあるのでそれを使います。
このイメージは公式のpostgresイメージをベースに作成されていて、安心感があります。 Postgres の各バージョンと、PostGIS の各バージョンそれぞれの組み合わせから選べるので、多くの場面で使えるでしょう。
version: "3" volumes: pg_data: driver: local services: pg: image: postgis/postgis:11-2.5-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password volumes: - pg_data:/var/lib/postgresql/data app: # 省略
adapter: postgis
で activerecord-postgis-adapter
を使います。
これは標準のアダプタに空間データとRubyオブジェクトに変換などの機能を追加するものです。
default: &default adapter: postgis encoding: unicode # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> database: rails_postgis_sample_development username: <%= ENV.fetch("DB_USERNAME") { "postgres" } %> password: <%= ENV.fetch("DB_PASSWORD") { "password" } %> host: <%= ENV.fetch("DB_HOST") { "pg" } %> development: <<: *default test: <<: *default database: rails_postgis_sample_test production: <<: *default database: rails_postgis_sample_production
enable_extension 'postgis'
class CreateExtensionPostgis < ActiveRecord::Migration[6.0] def up enable_extension 'postgis' unless extension_enabled?('postgis') end def down disable_extension 'postgis' if extension_enabled?('postgis') end end
現在スペイン語のサービス開発を行っています。その中でスペイン語の複数形で少し困ったので対応メモです。
たとえばスペイン語では、事務所用建物は local comercial
ですが、これが複数形になると、local
と comercial
の両方が複数形になり locales comerciales
となります。
また、 local
の複数形は locals
ではなく locales
、comercial
も comercials
ではなく comerciales
です。
このような言語特有の複数形やイレギュラーは、Rails標準の pluralize
では正しく変換することができません。
"local comercial".pluralize #=> "local comercials"
このようなときは、 config/initializers/inflections.rb
に独自のルールを定義します。
# config/initializers/inflections.rb ActiveSupport::Inflector.inflections(:es) do |inflect| inflect.irregular 'local comercial', 'locales comerciales' end
String#pluralize
の引数として言語を渡すことができます。
"local comercial".pluralize(:es) #=> "locales comerciales"
S3などのオブジェクトストレージと比べて面倒
gem 'google-api-client'
require 'googleauth' require 'google/apis/drive_v3' FOLDER_ID = "xxxxxxxxxxxxxxxxxxxxxx" # https://drive.google.com/drive/folders/xxxxxxxxxxxxxxxxxxxxxx UPLOAD_FILE_PATH = "/path/to/file.txt" service_account_key_json = File.read("path/to/service-account.key.json") drive_folder_id = FOLDER_ID authorizer = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: StringIO.new(service_account_key_json), scope: 'https://www.googleapis.com/auth/drive' ) authorizer.fetch_access_token! drive = Google::Apis::DriveV3::DriveService.new drive.authorization = authorizer drive.create_file( { name: "file.txt", parents: [drive_folder_id], modifiedTime: File::Stat.new(UPLOAD_FILE_PATH).mtime }, upload_source: UPLOAD_FILE_PATH )
ちゃんとやるなら排他制御なども考える必要がありそうです。
require 'googleauth' require 'google/apis/drive_v3' FOLDER_ID = "xxxxxxxxxxxxxxxxxxxxxx" # https://drive.google.com/drive/folders/xxxxxxxxxxxxxxxxxxxxxx UPLOAD_FILE_PATH = "/path/to/file.txt" service_account_key_json = File.read("path/to/service-account.key.json") drive_folder_id = FOLDER_ID authorizer = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: StringIO.new(service_account_key_json), scope: 'https://www.googleapis.com/auth/drive' ) authorizer.fetch_access_token! drive = Google::Apis::DriveV3::DriveService.new drive.authorization = authorizer # 必要なフォルダがある確認してなければ作成する folders = "path/to/folder".split("/") upload_folder_id = folders.each_with_object([drive_folder_id]) { |folder_name, folder_ids| folder = drive.list_files( q: "mimeType='application/vnd.google-apps.folder' and '#{folder_ids.last}' in parents and name = '#{folder_name}'", page_size: 1 ).files.first if folder # noop else folder = drive.create_file({ name: folder_name, mime_type: 'application/vnd.google-apps.folder', parents: [folder_ids.last], }, fields: 'id') end folder_ids.push(folder.id) }.last drive.create_file( { name: "file.txt", parents: [upload_folder_id], modifiedTime: File::Stat.new(UPLOAD_FILE_PATH).mtime }, upload_source: UPLOAD_FILE_PATH )