{"id":102527,"date":"2025-02-22T01:52:40","date_gmt":"2025-02-22T01:52:40","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/install-snowplow-on-the-google-cloud-platform\/"},"modified":"2025-02-22T01:52:40","modified_gmt":"2025-02-22T01:52:40","slug":"install-snowplow-on-the-google-cloud-platform","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=102527","title":{"rendered":"Install Snowplow On The Google Cloud Platform"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p><em>Last updated 18 Jan 2019: Added details about the <strong>free tier<\/strong> limitations, and showed how to avoid the Dataflow jobs <strong>auto-scaling<\/strong> out of control.<\/em><\/p>\n<p>I\u2019m (still) a huge fan of <a href=\"https:\/\/snowplowanalytics.com\/\">Snowplow Analytics<\/a>. Their open-source, modular approach to DIY analytics pipelines has inspired me two write <a href=\"https:\/\/www.simoahava.com\/tags\/snowplow\/\">articles<\/a> about them, and to host a <a href=\"https:\/\/www.meetup.com\/Snowplow-Analytics-Helsinki\/\">meetup<\/a> in Helsinki. In my previous <a href=\"https:\/\/www.simoahava.com\/analytics\/snowplow-full-setup-with-google-analytics-tracking\/\">Snowplow with Amazon Web Services guide<\/a>, I walked you through setting up a Snowplow pipeline using <a href=\"https:\/\/aws.amazon.com\/\">Amazon Web Services<\/a>. This time around, I\u2019m looking at the wondrous <a href=\"https:\/\/cloud.google.com\">Google Cloud Platform<\/a>, for which Snowplow introduced support in an <a href=\"https:\/\/snowplowanalytics.com\/blog\/2018\/03\/21\/snowplow-r101-neapolis-with-initial-gcp-support\/\">early 2018 release<\/a>.<\/p>\n<div style=\"aspect-ratio: 3838 \/ 1610;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-gcp.jpg\" title=\"Snowplow on the Google Cloud Platform\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1610\" width=\"3838\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-gcp.jpg#ZgotmplZ\" alt=\"Snowplow on the Google Cloud Platform\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>I won\u2019t be offering a comprehensive comparison between GCP and AWS, nor will I walk you through every single possible customization you could do when firing up the instances and building the pipeline. Those are left for you to discover by yourself, or to consult a data engineer who can help you with scale.<\/p>\n<p>In fact, the setup I\u2019ll walk you through will be suboptimal in many places. It won\u2019t be the most robust setup for a large flow of data through the pipeline, but what it will provide you is the comprehensive list of steps you need to make to get things running.<\/p>\n<p>Note that Snowplow also offers their own <a href=\"https:\/\/snowplowanalytics.com\/products\/snowplow-insights\/\">Snowplow Insights<\/a> service for setting up and managing the pipeline so that you can jump straight into data collection and analysis. I <strong>really<\/strong> recommend this service especially if you are unsure about how to set up the pipeline in an economical and scalable way. You might accrue a lot of extra costs if you don\u2019t know how to scale the required resources to suit what you actually need for efficient analysis.<\/p>\n<div style=\"aspect-ratio: 1371 \/ 293;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-insights.jpg\" title=\"Snowplow insights\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"293\" width=\"1371\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-insights.jpg#ZgotmplZ\" alt=\"Snowplow insights\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can also follow the official <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/GCP:-Getting-Started\">Snowplow wiki<\/a>, if you wish. The steps are similar to what I go through in this guide, though I do think this guide will be better suited for you in case you are unfamiliar with how GCP works.<\/p>\n<div style=\"aspect-ratio: 1364 \/ 612;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/full-process.jpg\" title=\"Full process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"612\" width=\"1364\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/full-process.jpg#ZgotmplZ\" alt=\"Full process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>My Powerpoint visualization skills just get better and better each passing year!<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"word-of-warning\">Word of warning<\/h2>\n<p>This probably won\u2019t come as a surprise to you, but working with a cloud platform is <strong>not free of charge<\/strong>. If you\u2019re a first-time user, you should be eligible for the <a href=\"https:\/\/cloud.google.com\/free\/\">free trial<\/a> of $300 to be spent during the first 12 months. This is more than enough to build a pipeline and test it out over a number of days, but it won\u2019t get you far.<\/p>\n<blockquote>\n<p><strong>UPDATE 18 Jan 2019<\/strong>: The free tier only gives you <strong>8 CPUs<\/strong> <em>total<\/em> across your entire project. That means that you must keep count of all the instances you\u2019ll be using for the collector and for the ETL machine. Start with a low number, e.g. 1 or 2 CPUs per instance to see how Snowplow works. Once you have the budget, you can shoot for more ambitious setups.<\/p>\n<\/blockquote>\n<div style=\"aspect-ratio: 1917 \/ 863;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/billing-dashboard.jpg\" title=\"Billing dashboard\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"863\" width=\"1917\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/billing-dashboard.jpg#ZgotmplZ\" alt=\"Billing dashboard\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You\u2019ll need to keep a close eye on the <a href=\"https:\/\/console.cloud.google.com\/billing\/\">billing dashboard<\/a> of your project. It helps you get an idea of how much cost you have factually accumulated to this day, and what the projected cost for the whole month will be.<\/p>\n<h2 id=\"what-youll-need\">What you\u2019ll need<\/h2>\n<p>To follow this guide, you will need:<\/p>\n<ul>\n<li>\n<p><strong>A Google account<\/strong> &#8211; something to log into Google services with, such as your Google Mail ID.<\/p>\n<\/li>\n<li>\n<p><strong>A credit card<\/strong> &#8211; you\u2019ll need to enable billing in your Google Cloud account to fire up some of the services we need.<\/p>\n<\/li>\n<li>\n<p><strong>A custom domain name<\/strong> &#8211; this is optional, but it\u2019s necessary if you want to set up the tracker as a secure HTTPS endpoint. Without a custom domain name, you\u2019re forced to use HTTP only. You can get a cheap domain name from <a href=\"https:\/\/domains.google\/\">Google Domains<\/a>. You <em>might<\/em> be able to set something up for free using <a href=\"https:\/\/cloud.google.com\/endpoints\/\">Cloud Endpoints<\/a>, but this guide will not cover this.<\/p>\n<\/li>\n<\/ul>\n<p>Good luck! Let me know in the comments if some part of this guide was particularly unclear.<\/p>\n<h2 id=\"first-steps\">First steps<\/h2>\n<p>Here we\u2019ll setup the GCP project, make sure you have the necessary resources enabled, and we\u2019ll also install the Google Cloud SDK so that you can interact with your project via your local terminal, too.<\/p>\n<h3 id=\"step-1-set-up-a-new-project\">Step 1: Set up a new project<\/h3>\n<p>The first thing you\u2019ll need to do is set up a new Google Cloud project.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/cloud-resource-manager\/\">https:\/\/console.cloud.google.com\/cloud-resource-manager\/<\/a>. Remember to login with the Google ID which you\u2019ll use to manage this project. Click <strong>CREATE PROJECT<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1738 \/ 730;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/gcp-create-project.jpg\" title=\"Create GCP project\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"730\" width=\"1738\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/gcp-create-project.jpg#ZgotmplZ\" alt=\"Create GCP project\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<blockquote>\n<p>If this is the first time you use GCP with this account, you might see an offer for <strong>free credit<\/strong>. Take it!<\/p>\n<\/blockquote>\n<p><strong>(2)<\/strong> Give the project a name.<\/p>\n<p>For convenience, it might be good to change the <strong>Project ID<\/strong> to something more legible and easy to understand.<\/p>\n<p>If you have <strong>Organizations<\/strong> and <strong>Locations<\/strong> (such as folders) set up for your Google Cloud account, choose the appropriate ones from the menu.<\/p>\n<p>Also, if you already have a <strong>Billing Account<\/strong> set up, choose that as well from the respective drop-down menu.<\/p>\n<div style=\"aspect-ratio: 1102 \/ 1278;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-new-project.jpg\" title=\"Create new project\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1278\" width=\"1102\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-new-project.jpg#ZgotmplZ\" alt=\"Create new project\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Choose <strong>CREATE<\/strong> when you\u2019re done.<\/p>\n<p>After clicking the button, GCP will do some loading and spinning for a while, after which you should see your new project dashboard. If you don\u2019t, make sure to select it from the project selector menu.<\/p>\n<div style=\"aspect-ratio: 3394 \/ 1600;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/gcp-dashboard.jpg\" title=\"GCP dashboard\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1600\" width=\"3394\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/gcp-dashboard.jpg#ZgotmplZ\" alt=\"GCP dashboard\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Congratulations! You\u2019ve created the project.<\/p>\n<h3 id=\"step-2-enable-billing\">Step 2: Enable Billing<\/h3>\n<p>You\u2019ll need to create a <strong>billing account<\/strong> so that GCP can invoice you if necessary (sucks, I know).<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/billing\">https:\/\/console.cloud.google.com\/billing<\/a>.<\/p>\n<p><strong>(2)<\/strong> Click <strong>Add billing account<\/strong>.<\/p>\n<div style=\"aspect-ratio: 864 \/ 464;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/add-billing-account.jpg\" title=\"Add billing account\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"464\" width=\"864\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/add-billing-account.jpg#ZgotmplZ\" alt=\"Add billing account\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Here, follow the steps to add your credit card, or, if you\u2019re eligible for the <strong>free trial<\/strong>, to activate your free trial.<\/p>\n<p>Make sure you follow the prompts to link your Snowpow project to the Billing account you just created.<\/p>\n<h3 id=\"step-3-enable-the-required-services\">Step 3: Enable the required services<\/h3>\n<p>Next, you need to enable the services and APIs we\u2019ll need to get started. These steps let you set up the collector. When you move to the <a href=\"#prepare-the-etl-step\">enrich and BigQuery load steps<\/a>, you\u2019ll need to enable additional services.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/apis\/library\">https:\/\/console.cloud.google.com\/apis\/library<\/a>.<\/p>\n<p>You should see a search bar, so start by searching for <strong>Compute Engine API<\/strong> and click the relevant result. We\u2019ll need the Compute Engine to fire up our virtual machines on which the Snowplow collector will reside.<\/p>\n<div style=\"aspect-ratio: 2088 \/ 918;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/compute-engine-api.jpg\" title=\"Compute Engine API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"918\" width=\"2088\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/compute-engine-api.jpg#ZgotmplZ\" alt=\"Compute Engine API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Upon entering the Compute Engine API page, simply click the blue <strong>ENABLE<\/strong> button at the top.<\/p>\n<div style=\"aspect-ratio: 1758 \/ 1068;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/compute-engine-api-enable.jpg\" title=\"Enable the Compute Engine API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1068\" width=\"1758\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/compute-engine-api-enable.jpg#ZgotmplZ\" alt=\"Enable the Compute Engine API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once it\u2019s done, you should see something like this where the <strong>Enable<\/strong> button used to be:<\/p>\n<div style=\"aspect-ratio: 1478 \/ 774;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/compute-engine-api-enabled.jpg\" title=\"Compute Engine API enabled\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"774\" width=\"1478\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/compute-engine-api-enabled.jpg#ZgotmplZ\" alt=\"Compute Engine API enabled\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, follow these exact same steps for the <strong>Cloud Pub\/Sub API<\/strong>. Pub\/Sub (for <strong>Pub<\/strong>lisher\/<strong>Sub<\/strong>scriber) is a real-time message queue we\u2019ll use to process the data fed into the Snowplow pipeline.<\/p>\n<div style=\"aspect-ratio: 1962 \/ 932;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/enable-pub-sub-api.jpg\" title=\"Enable the Pub\/Sub API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"932\" width=\"1962\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/enable-pub-sub-api.jpg#ZgotmplZ\" alt=\"Enable the Pub\/Sub API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019re done enabling these services, you\u2019re ready to move onto the next step!<\/p>\n<h3 id=\"step-4-install-the-google-cloud-sdk\">Step 4: Install the Google Cloud SDK<\/h3>\n<p>The next thing you\u2019ll want to do is to install the Google Cloud SDK locally in your machine. It\u2019s very convenient because it lets you access your GCP project from the command line, and it lets you test some of the services from your local machine rather than having to find the appropriate paths through the often confusing UI.<\/p>\n<p>To install the SDK, follow the steps for your platform, starting from <a href=\"https:\/\/cloud.google.com\/sdk\/\">here<\/a>.<\/p>\n<p>Once you\u2019ve installed the SDK, you should be able to run these commands in your terminal \/ shell:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ gcloud auth login\n$ gcloud config <span style=\"color:#0aa\">set<\/span> project PROJECT_ID<\/code><\/pre>\n<\/div>\n<p>The first command logs you in with Google Cloud using the Google Account you choose in the web prompt. The second command points the current <code>gcloud<\/code> setup to the project ID you\u2019ll give (replace <code>PROJECT_ID<\/code> with the ID you configured when you created the project). The end result should be something like this:<\/p>\n<div style=\"aspect-ratio: 1704 \/ 578;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/gcloud-set-project.jpg\" title=\"Gcloud SDK setup\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"578\" width=\"1704\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/gcloud-set-project.jpg#ZgotmplZ\" alt=\"Gcloud SDK setup\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-5-setup-a-service-account\">Step 5: Setup a service account<\/h3>\n<p>The final initialization step is to setup a <strong>service account<\/strong>. A service account is basically a Google Cloud account which has full access to your GCP services and resources. It\u2019s a necessary step if you want to run services on GCP programmatically rather than with your own, personal Google account.<\/p>\n<blockquote>\n<p>Note that when you enable <strong>Compute Engine API<\/strong> for your project, a <strong>Compute Engine default service account<\/strong> is created for you automatically. the steps below really only apply if you wanted to create a different service account for your pipeline.<\/p>\n<\/blockquote>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/apis\/credentials\">https:\/\/console.cloud.google.com\/apis\/credentials<\/a>.<\/p>\n<p><strong>(2)<\/strong> Make sure you have the correct project selected in the project selector menu.<\/p>\n<p><strong>(3)<\/strong> Click the blue <strong>Create credentials<\/strong> selector, and choose <strong>Service account key<\/strong> from the menu.<\/p>\n<div style=\"aspect-ratio: 1766 \/ 746;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-credentials.jpg\" title=\"Create credentials\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"746\" width=\"1766\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-credentials.jpg#ZgotmplZ\" alt=\"Create credentials\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Since you\u2019ve enabled the <strong>Compute Engine API<\/strong>, you should be able to choose the default GCE service account from the drop-down.<\/p>\n<div style=\"aspect-ratio: 1108 \/ 724;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/default-service-account.jpg\" title=\"Default service account\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"724\" width=\"1108\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/default-service-account.jpg#ZgotmplZ\" alt=\"Default service account\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Keep the <strong>JSON<\/strong> option selected, and choose <strong>Create<\/strong>.<\/p>\n<p>The browser should automatically download the JSON file, so make sure you find it in your local files and <strong>store it securely<\/strong>.<\/p>\n<blockquote>\n<p>At this point, it would be a good idea <strong>to create a local folder for this entire Snowplow experiment<\/strong>. In that local folder, you can store this service account key and any temporary configuration files you\u2019ll work on, and other stuff such as executables and binaries you\u2019ll eventually upload to the GCP, if necessary.<\/p>\n<\/blockquote>\n<p>Once you\u2019ve created the service account you are ready to move on. All the initial steps have now been completed, congratulations!<\/p>\n<h2 id=\"setting-up-the-pubsub-topics\">Setting up the Pub\/Sub topics<\/h2>\n<div style=\"aspect-ratio: 1364 \/ 612;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/pubsub-process.jpg\" title=\"Pub\/sub process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"612\" width=\"1364\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/pubsub-process.jpg#ZgotmplZ\" alt=\"Pub\/sub process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Snowplow uses a <strong>collector<\/strong> as the endpoint of your tracker requests. This collector resides in a Compute Engine instance (a virtual machine, basically), and it processes the requests you send to the endpoint.<\/p>\n<p>These requests are then published into <strong>Pub\/Sub topics<\/strong>, from where they are then passed on further down the pipeline for enrichment and parsing. Pub\/Sub is essentially a real-time messaging pipeline, which collects messages in <strong>topics<\/strong>, which are then available for <strong>subscriptions<\/strong> to access.<\/p>\n<p>Make sure you check out Snowplow\u2019s official <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/GCP:-Setting-up-the-Scala-Stream-Collector\">wiki<\/a> for more information on this part of the pipeline.<\/p>\n<h3 id=\"step-1-create-the-pubsub-topics\">Step 1: Create the Pub\/Sub topics<\/h3>\n<p>The first thing you\u2019ll need to do is create <strong>topics<\/strong> in the Pub\/Sub you\u2019ve enabled for your project.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/cloudpubsub\/topicList\">https:\/\/console.cloud.google.com\/cloudpubsub\/topicList<\/a>.<\/p>\n<p><strong>(2)<\/strong> Make sure you have the correct project selected in the project selector.<\/p>\n<p><strong>(3)<\/strong> Click the blue <strong>Create a topic<\/strong> button.<\/p>\n<div style=\"aspect-ratio: 2702 \/ 1014;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-a-pubsub-topic.jpg\" title=\"create a topic\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1014\" width=\"2702\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-a-pubsub-topic.jpg#ZgotmplZ\" alt=\"create a topic\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(4)<\/strong> Type <code>good<\/code> after the project path as in the screenshot below, and click <strong>CREATE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1054 \/ 496;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/good-topic.jpg\" title=\"Good topic\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"496\" width=\"1054\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/good-topic.jpg#ZgotmplZ\" alt=\"Good topic\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(5)<\/strong> Click <strong>CREATE TOPIC<\/strong> and type <code>bad<\/code> as the name of the new topic, and then click <strong>CREATE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1076 \/ 504;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-bad-topic.jpg\" title=\"Bad topic\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"504\" width=\"1076\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-bad-topic.jpg#ZgotmplZ\" alt=\"Bad topic\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Snowplow uses these two topics to filter out hits and requests that validate (<code>good<\/code> stream) and those that have issues and errors (<code>bad<\/code> stream).<\/p>\n<h3 id=\"step-2-create-a-subscription-optional\">Step 2: Create a subscription (optional)<\/h3>\n<p>Next, you can create a <strong>subscription<\/strong> with which you can test the stream. This is optional, but might be a good idea if you want to see how the pipeline is working.<\/p>\n<p><strong>(6)<\/strong> Click the <code>good<\/code> topic to enter its configuration page.<\/p>\n<p><strong>(7)<\/strong> Choose <strong>CREATE SUBSCRIPTION<\/strong> from the top navigation.<\/p>\n<div style=\"aspect-ratio: 1884 \/ 534;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-subscription.jpg\" title=\"Create subscription\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"534\" width=\"1884\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-subscription.jpg#ZgotmplZ\" alt=\"Create subscription\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(8)<\/strong> Give the subscription a name (<code>test-good<\/code>).<\/p>\n<p><strong>(9)<\/strong> Leave the other settings as they are, and click <strong>CREATE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 992 \/ 830;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/test-good-subscription.jpg\" title=\"Test good subscription\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"830\" width=\"992\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/test-good-subscription.jpg#ZgotmplZ\" alt=\"Test good subscription\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now that you\u2019ve created the Pub\/Sub topics, it\u2019s time to step into one of the more complicated steps of this guide: <strong>setting up the collector itself<\/strong>.<\/p>\n<h2 id=\"create-an-http-endpoint-with-the-scala-stream-collector\">Create an HTTP endpoint with the Scala Stream Collector<\/h2>\n<div style=\"aspect-ratio: 1364 \/ 612;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/collector-process.jpg\" title=\"Pub\/sub process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"612\" width=\"1364\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/collector-process.jpg#ZgotmplZ\" alt=\"Pub\/sub process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Snowplow uses a collector written in <strong>Scala<\/strong> for processing the requests sent from your website. These records are parsed by the <strong>Scala Stream Collector<\/strong> and then distributed into the Pub\/Sub topics you created in the previous chapter.<\/p>\n<p>We\u2019ll start with a simple <strong>HTTP endpoint<\/strong> using the default IP address assigned to the Compute Engine instance you\u2019ll spin up. This is only to test that the whole pipeline works. You\u2019ll want to configure your <a href=\"#create-an-https-endpoint-with-a-custom-domain-name\">own custom domain name with an <strong>HTTPS endpoint<\/strong><\/a> for the actual tracker!<\/p>\n<h3 id=\"step-1-create-the-config-file\">Step 1: Create the config file<\/h3>\n<p>In the local directory where you\u2019re storing all your project files (such as the service account credentials you created earlier), create a new file named <code>application.config<\/code>, and copy-paste the contents of <a href=\"https:\/\/gist.githubusercontent.com\/sahava\/1484fc64231f01deb66329bd777fb40d\/raw\/a946da40eb6d9b5c48cedb7411c1b6dc89678dd1\/snowplow-application-config\">this sample config<\/a> within.<\/p>\n<p>At this time, the only line you need to edit is the one with <code>googleProjectId = your-project-id<\/code>. Change <code>your-project-id<\/code> to your actual Google Cloud project ID.<\/p>\n<p>Keep this file at hand, because you\u2019ll need to make some changes to it soon.<\/p>\n<h3 id=\"step-2-fire-up-a-gce-instance\">Step 2: Fire up a GCE instance<\/h3>\n<p>You are now ready to start a Compute Engine instance.<\/p>\n<blockquote>\n<p>NOTE! Because this is just a test &#8211; remember to shut down the instance as soon as you\u2019re done so that you don\u2019t accumulate extra costs.<\/p>\n<\/blockquote>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/compute\/instances\">https:\/\/console.cloud.google.com\/compute\/instances<\/a> and make sure your project is selected in the project selector.<\/p>\n<p><strong>(2)<\/strong> Click <strong>Create<\/strong>.<\/p>\n<div style=\"aspect-ratio: 2624 \/ 930;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-gce-instance.jpg\" title=\"Create GCE instance\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"930\" width=\"2624\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-gce-instance.jpg#ZgotmplZ\" alt=\"Create GCE instance\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, you\u2019ll be transported to the configuration screen.<\/p>\n<p><strong>(3)<\/strong> Give the instance a <strong>name<\/strong>.<\/p>\n<p><strong>(4)<\/strong> Choose a region (somewhere close by, preferably) &#8211; you can use the default zone.<\/p>\n<p><strong>(5)<\/strong> Make sure the service account you\u2019ve created (or the default Compute Engine service account) is selected in the relevant list.<\/p>\n<p><strong>(6)<\/strong> Choose <strong>Set access for each API<\/strong> from the scope selector.<\/p>\n<div style=\"aspect-ratio: 2526 \/ 1244;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-initial-settings.jpg\" title=\"Initial instance settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1244\" width=\"2526\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-initial-settings.jpg#ZgotmplZ\" alt=\"Initial instance settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(7)<\/strong> Scroll down the list of APIs, and choose <strong>Enabled<\/strong> for the <strong>Cloud Pub\/Sub<\/strong> API.<\/p>\n<div style=\"aspect-ratio: 1202 \/ 310;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/cloud-pubsub-enabled.jpg\" title=\"Cloud pub\/sub scope enabled\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"310\" width=\"1202\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/cloud-pubsub-enabled.jpg#ZgotmplZ\" alt=\"Cloud pub\/sub scope enabled\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(8)<\/strong> Scroll down to <strong>Firewall<\/strong> and check <strong>Allow HTTP traffic<\/strong>. Next, click the <strong>Management, security, disks, networking, sole tenancy<\/strong> link.<\/p>\n<div style=\"aspect-ratio: 1186 \/ 368;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/firewall-rule.jpg\" title=\"Firewall rule\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"368\" width=\"1186\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/firewall-rule.jpg#ZgotmplZ\" alt=\"Firewall rule\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(9)<\/strong> Select the <strong>Networking<\/strong> tab, and type <code>collector<\/code> into the <strong>Network tags<\/strong> field.<\/p>\n<div style=\"aspect-ratio: 1496 \/ 590;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/network-tag.jpg\" title=\"Network tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"590\" width=\"1496\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/network-tag.jpg#ZgotmplZ\" alt=\"Network tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019ve done these changes, click <strong>Create<\/strong> to fire up the instance!<\/p>\n<h3 id=\"step-3-create-a-firewall-rule\">Step 3: Create a firewall rule<\/h3>\n<p>You\u2019ll need to create a firewall rule which accepts incoming connections from your website.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/networking\/firewalls\/list\">https:\/\/console.cloud.google.com\/networking\/firewalls\/list<\/a> and make sure the correct project is selected.<\/p>\n<p><strong>(2)<\/strong> Click <strong>CREATE FIREWALL RULE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1920 \/ 760;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-firewall-rule.jpg\" title=\"Create firewall rule\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"760\" width=\"1920\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-firewall-rule.jpg#ZgotmplZ\" alt=\"Create firewall rule\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> In the configuration that opens, give the rule some name, e.g. <code>snowplow-firewall-rule<\/code>.<\/p>\n<p><strong>(4)<\/strong> Scroll down to <strong>Target tags<\/strong> and type in the tag name you gave in the end of the <a href=\"#step-2-fire-up-a-gce-instance\">previous step<\/a> (<code>collector<\/code> if you used my example).<\/p>\n<p><strong>(5)<\/strong> Make sure <strong>IP ranges<\/strong> is selected in the <strong>Source filter<\/strong> menu, and type <code>0.0.0.0\/0<\/code> into the <strong>Source IP ranges<\/strong> field.<\/p>\n<p><strong>(6)<\/strong> Under <strong>Protocols and ports<\/strong>, check <strong>tcp<\/strong> and type <code>8080<\/code> as the port value.<\/p>\n<div style=\"aspect-ratio: 964 \/ 1212;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/firewall-configuration.jpg\" title=\"Firewall configuration\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1212\" width=\"964\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/firewall-configuration.jpg#ZgotmplZ\" alt=\"Firewall configuration\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When ready, click <strong>Create<\/strong> to finalize the firewall setup.<\/p>\n<div style=\"aspect-ratio: 1778 \/ 488;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-firewall-rule-created.jpg\" title=\"Snowplow firewall rule created\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"488\" width=\"1778\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-firewall-rule-created.jpg#ZgotmplZ\" alt=\"Snowplow firewall rule created\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-4-create-a-storage-bucket-for-your-configuration-file\">Step 4: Create a storage bucket for your configuration file<\/h3>\n<p>Next thing you\u2019ll need to do is upload the configuration file for the collector into a Cloud Storage bucket. This is because the file needs to be available for your instance to use, and it\u2019s extremely convenient to have the file available in Google Cloud, because the service account can simply pull the file directly from the bucket.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/storage\/browser\">https:\/\/console.cloud.google.com\/storage\/browser<\/a>, and, as always, make sure you have the right project selected before clicking <strong>Create bucket<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1320 \/ 465;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-storage-bucket.jpg\" title=\"Create storage bucket\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"465\" width=\"1320\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-storage-bucket.jpg#ZgotmplZ\" alt=\"Create storage bucket\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(2)<\/strong> Give the bucket a descriptive (and unique) name, such as <code>snowplow-yourname-collector-bucket<\/code>.<\/p>\n<p><strong>(3)<\/strong> You can leave the rest of the settings with their default values, then click <strong>Create<\/strong>.<\/p>\n<div style=\"aspect-ratio: 947 \/ 622;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-storage-bucket-done.jpg\" title=\"Create storage bucket done\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"622\" width=\"947\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-storage-bucket-done.jpg#ZgotmplZ\" alt=\"Create storage bucket done\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(4)<\/strong> In the view that opens up, click <strong>Upload files<\/strong>, find the <code>application.config<\/code> file you downloaded earlier, and upload it to the bucket.<\/p>\n<div style=\"aspect-ratio: 1659 \/ 682;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/upload-file-to-storage.jpg\" title=\"Upload file to storage\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"682\" width=\"1659\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/upload-file-to-storage.jpg#ZgotmplZ\" alt=\"Upload file to storage\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Good job! You are now ready to connect your GCE instance and fire up the collector!<\/p>\n<h3 id=\"step-5-ssh-into-the-compute-engine-instance\">Step 5: SSH into the Compute Engine instance<\/h3>\n<p>Now that you have the virtual machine running in the cloud, and you have the configuration file uploaded to a Cloud Storage bucket, the next step is to connect to the virtual machine, download all the remaining files, and start the collector.<\/p>\n<blockquote>\n<p>It\u2019s very simple and trivial to do the following steps using the Google Cloud SDK on your local machine, too. If you want to try it out, follow the relevant steps in <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/GCP:-Setting-up-the-Scala-Stream-Collector#4b-2-via-the-command-line\">Snowplow\u2019s wiki<\/a>.<\/p>\n<\/blockquote>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/compute\/instances\">https:\/\/console.cloud.google.com\/compute\/instances<\/a>.<\/p>\n<p><strong>(2)<\/strong> Click the <strong>SSH<\/strong> option next to your instance.<\/p>\n<div style=\"aspect-ratio: 829 \/ 251;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/ssh-to-instance.jpg\" title=\"SSH to cloud instance\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"251\" width=\"829\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/ssh-to-instance.jpg#ZgotmplZ\" alt=\"SSH to cloud instance\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Visit <a href=\"https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/\">https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/<\/a> and find the file that starts with <code>snowplow_scala_stream_collector_google_pubsub_<\/code> and check what the latest version number is that doesn\u2019t have the <code>rcl<\/code> suffix. Make note of this version number (e.g. <code>0.14.0<\/code>), no need to download the file.<\/p>\n<div style=\"aspect-ratio: 637 \/ 309;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/latest-collector.jpg\" title=\"Latest collector version\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"309\" width=\"637\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/latest-collector.jpg#ZgotmplZ\" alt=\"Latest collector version\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(4)<\/strong> Next, in the SSH window, run the following commands in order, pressing enter after each command.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ sudo apt-get update\n$ sudo apt-get -y install default-jre\n$ sudo apt-get -y install unzip\n$ wget https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/snowplow_scala_stream_collector_google_pubsub_<version>.zip\n$ gsutil cp gs:\/\/<your-bucket-name>\/application.conf .\n$ unzip snowplow_scala_stream_collector_google_pubsub_<version>.zip\n$ java -jar snowplow-stream-collector-google-pubsub-<version>.jar --config application.conf<\/version><\/version><\/your-bucket-name><\/version><\/code><\/pre>\n<\/div>\n<p>Replace all instances of <code><version\/><\/code> with the latest version of the collector ZIP file which you checked in (3).<\/p>\n<p>Replace <code><your-bucket-name\/><\/code> with the name you gave the cloud storage bucket in the <a href=\"#step-4-create-a-storage-bucket-for-your-configuration-file\">previous step<\/a> (e.g. <code>snowplow-yourname-collector-bucket<\/code>).<\/p>\n<div style=\"aspect-ratio: 900 \/ 632;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-commands.jpg\" title=\"commands in the cloud instance\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"632\" width=\"900\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-commands.jpg#ZgotmplZ\" alt=\"commands in the cloud instance\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>After running the last command, if all has been configured correctly, you should see the following output in the instance shell:<\/p>\n<div style=\"aspect-ratio: 898 \/ 105;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/collector-started.jpg\" title=\"Collector started\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"105\" width=\"898\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/collector-started.jpg#ZgotmplZ\" alt=\"Collector started\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You are now ready to send a test request to the endpoint, after which you can check your Pub\/Sub subscription if it received the message!<\/p>\n<h3 id=\"step-6-send-a-test-request-and-verify-it-was-published-into-pubsub\">Step 6: Send a test request and verify it was published into Pub\/Sub<\/h3>\n<p>To send the request, you\u2019ll first need to check what the external IP of your Cloud instance is.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/compute\/instances\">https:\/\/console.cloud.google.com\/compute\/instances<\/a> and click the Snowplow instance name.<\/p>\n<p><strong>(2)<\/strong> Copy the IP address from the <strong>External IP<\/strong> field.<\/p>\n<div style=\"aspect-ratio: 938 \/ 187;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/external-ip.jpg\" title=\"External IP\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"187\" width=\"938\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/external-ip.jpg#ZgotmplZ\" alt=\"External IP\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The next step requires you to send an HTTP POST request with a specific payload to this IP endpoint. There are many ways to do it &#8211; you could use the JavaScript console of the web browser, for example.<\/p>\n<p>My preference for testing HTTP endpoints quickly is to use <a href=\"https:\/\/curl.haxx.se\/\">curl<\/a>, which is a command-line tool available in almost any terminal. If you\u2019re using an operating system that doesn\u2019t come equipped with curl, I recommend downloading and installing it from <a href=\"https:\/\/curl.haxx.se\/download.html\">here<\/a>.<\/p>\n<p><strong>(3)<\/strong> Open the terminal on your local machine, and copy-paste the following command, switching <code><external_ip\/><\/code> with the actual external IP you copied from the GCE instance settings.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ curl -d <span style=\"color:#a50\">\"&amp;e=pv&amp;page=curl-test&amp;url=http%3A%2F%2Fjust-testing.com&amp;aid=snowplow-test\"<\/span> -X POST http:\/\/<external_ip>:8080\/com.snowplowanalytics.iglu\/v1<\/external_ip><\/code><\/pre>\n<\/div>\n<p>The payload is very simple &#8211; it basically has only four parameters:<\/p>\n<table>\n<thead>\n<tr>\n<th>Parameter<\/th>\n<th>Value<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>e<\/code><\/td>\n<td><code>pv<\/code><\/td>\n<td>Event type, Pageview in this case.<\/td>\n<\/tr>\n<tr>\n<td><code>page<\/code><\/td>\n<td><code>curl-test<\/code><\/td>\n<td>Page name.<\/td>\n<\/tr>\n<tr>\n<td><code>url<\/code><\/td>\n<td><code>http:\/\/just-testing.com<\/code><\/td>\n<td>Source URL.<\/td>\n<\/tr>\n<tr>\n<td><code>aid<\/code><\/td>\n<td><code>snowplow-test<\/code><\/td>\n<td>Application ID.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The endpoint is suffixed with <code>\/com.snowplowanalytics.iglu\/v1<\/code> to denote the <a href=\"https:\/\/github.com\/snowplow\/iglu\">schema<\/a> you\u2019ll be using for processing and validating the incoming data. Since we\u2019re working with the most out-of-the-box solution available for testing purposes, you can use this default schema for now.<\/p>\n<p>Later, we\u2019ll use the Google Analytics plugin to simplify things and to send a more complete payload, but for the purposes of testing this will do.<\/p>\n<p><strong>(4)<\/strong> If everything worked, you should see a status code OK as a response.<\/p>\n<div style=\"aspect-ratio: 995 \/ 123;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/post-response.jpg\" title=\"POST response\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"123\" width=\"995\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/post-response.jpg#ZgotmplZ\" alt=\"POST response\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now you can test if your record was published in the <code>good<\/code> <strong>Pub\/Sub topic<\/strong>. To test this, you need to <strong>pull<\/strong> the recent records from the topic by using the subscription you created earlier. The easiest way to do this is by using the Google Cloud SDK that you should now have installed and configured on your local machine.<\/p>\n<p><strong>(5)<\/strong> The command you\u2019ll need to use is:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ gcloud pubsub subscriptions pull --auto-ack test-good<\/code><\/pre>\n<\/div>\n<p>Where <code>test-good<\/code> is the name you gave to the subscription when you created it. If it worked, you should see the following output:<\/p>\n<div style=\"aspect-ratio: 1450 \/ 460;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/thrift-record.jpg\" title=\"Thrift record\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"460\" width=\"1450\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/thrift-record.jpg#ZgotmplZ\" alt=\"Thrift record\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Don\u2019t worry about the fact that it looks all garbled. This is the request stored in binary <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/Collector-logging-formats#thrift\">Thrift<\/a> format. You should see all sorts of interesting bits and pieces such as your local machine IP address, the User-Agent string (just <code>curl<\/code> for now), and the data payload itself. If you see all this in the <code>good<\/code> Pub\/Sub topic, it means everything is working!<\/p>\n<p>You have now successfully created a collector behind an HTTP endpoint. You could proceed to the <a href=\"#prepare-the-etl-step\">enrichment stage<\/a>, but I urge you to first <strong>stop<\/strong> the GCE instance so you don\u2019t accumulate extra costs for having it running.<\/p>\n<p>I also recommend you continue with the next chapter, where you\u2019ll learn how to use your own custom domain as an HTTPS-secured endpoint for the collector. Using HTTP is crippling, and you can\u2019t really create a production-ready endpoint with a single GCE instance behind the HTTP protocol.<\/p>\n<h2 id=\"create-an-https-endpoint-with-a-custom-domain-name\">Create an HTTPS endpoint with a custom domain name<\/h2>\n<p>This step replaces the previous chapter, effectively. Instead of using an ephemeral, external IP behind the HTTP protocol, we\u2019ll assign a <strong>static<\/strong> IP address to our virtual machine instance. On top of that, we\u2019ll configure a custom domain name to point to this static IP, and we\u2019ll have everything work behind the HTTPS protocol.<\/p>\n<p>You\u2019ll still want to read the previous chapter, though. We\u2019ll be doing a lot of similar things here.<\/p>\n<p><strong>IMPORTANT!<\/strong> If you want to skip reading the previous chapter, then please note that you <strong>must<\/strong> do steps <a href=\"#step-1-create-the-config-file\">(1)<\/a> and <a href=\"#step-4-create-a-storage-bucket-for-your-configuration-file\">(4)<\/a> from the previous chapter before moving on with the HTTPS endpoint.<\/p>\n<p>This is, basically, what the final collector product should look like. You\u2019re using a <strong>load balancing<\/strong> system to automatically scale the instances with incoming traffic, AND you\u2019ll be able to avoid pesky cross-protocol errors due to using HTTPS as the sole endpoint. There\u2019s the added security, too.<\/p>\n<h3 id=\"step-1-create-an-instance-template\">Step 1: Create an instance template<\/h3>\n<p>Instead of working with a single GCE instance, we\u2019ll use a <strong>cluster<\/strong> of instances that will be scaled up and down automatically with traffic. This will, naturally, reflect on the cost structure of your monthly GCP invoices, so keep an eye on the <a href=\"https:\/\/console.cloud.google.com\/home\/dashboard\">estimated charges in your dashboard<\/a>.<\/p>\n<p><strong>(1)<\/strong> If you followed the previous chapter, you can go ahead and delete the GCE instance and the Firewall Rule. You\u2019ll start from scratch here.<\/p>\n<p><strong>(2)<\/strong> You can keep the <code>application.conf<\/code> file as it is in the storage bucket. The same settings you used in the previous chapter apply here.<\/p>\n<p><strong>(3)<\/strong> Go to <a href=\"https:\/\/console.cloud.google.com\/compute\/instanceTemplates\/list\">https:\/\/console.cloud.google.com\/compute\/instanceTemplates\/list<\/a> and click <strong>Create instance template<\/strong>.<\/p>\n<p>The <strong>instance template<\/strong> is what each new virtual machine will use as its \u201ctemplate\u201d, meaning it will inherit the settings from this template as well as the startup script that will fire up the collector itself.<\/p>\n<p>The steps for the instance template are almost the same as for a single GCE instance from the previous chapter, but let\u2019s go over them anyway.<\/p>\n<p><strong>(4)<\/strong> Give a descriptive name for the instance.<\/p>\n<p><strong>(5)<\/strong> Make sure the default Compute Engine service account is selected.<\/p>\n<p><strong>(6)<\/strong> Choose <strong>Set access for each API<\/strong> in the <strong>Access Scopes<\/strong> list.<\/p>\n<div style=\"aspect-ratio: 907 \/ 546;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-instance-template.jpg\" title=\"Snowplow instance template\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"546\" width=\"907\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-instance-template.jpg#ZgotmplZ\" alt=\"Snowplow instance template\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(7)<\/strong> Scroll down the list to <strong>Cloud Pub\/Sub<\/strong> and choose <strong>Enable<\/strong>.<\/p>\n<p><strong>(8)<\/strong> Under <strong>Firewall<\/strong>, select <strong>Allow HTTP traffic<\/strong>.<\/p>\n<p><strong>(9)<\/strong> Expand the <strong>Management, security, disks, networking, sole tenancy<\/strong> accordion.<\/p>\n<div style=\"aspect-ratio: 549 \/ 165;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-template-settings-2.jpg\" title=\"Instance template settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"165\" width=\"549\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-template-settings-2.jpg#ZgotmplZ\" alt=\"Instance template settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(10)<\/strong> Scroll down to <strong>Startup script<\/strong>, and copy-paste the following code within:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"color:#4c8317\">#! \/bin\/bash\n<\/span><span style=\"color:#4c8317\"\/>sudo apt-get update\nsudo apt-get -y install default-jre\nsudo apt-get -y install unzip\n<span style=\"color:#a00\">archive<\/span>=snowplow_scala_stream_collector_google_pubsub_<version>.zip\nwget https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/<span style=\"color:#a00\">$archive<\/span>\ngsutil cp gs:\/\/<your-bucket-name>\/application.conf .\nunzip <span style=\"color:#a00\">$archive<\/span>\njava -jar snowplow-stream-collector-google-pubsub-<version>.jar --config application.conf &amp;<\/version><\/your-bucket-name><\/version><\/code><\/pre>\n<\/div>\n<p><strong>(11)<\/strong> Edit the two instances of <code><version\/><\/code> with the latest version number you can find for the <code>snowplow_scala_stream_collector_google_pubsub_<\/code> prefix <a href=\"https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/\">here<\/a> (don\u2019t use the version with the <code>_rcl<\/code> prefix). At the time of writing, the latest version was <code>0.14.0<\/code>.<\/p>\n<p><strong>(12)<\/strong> Replace <code><your-bucket-name\/><\/code> with the name of the <a href=\"https:\/\/console.cloud.google.com\/storage\/browser\">Cloud Storage bucket<\/a> that stores the <code>application.conf<\/code> file.<\/p>\n<div style=\"aspect-ratio: 542 \/ 316;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/startup-script.jpg\" title=\"Startup script\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"316\" width=\"542\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/startup-script.jpg#ZgotmplZ\" alt=\"Startup script\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This startup script is run whenever a new instance is built with this template. It loads all the dependencies and the runs the collector Java file.<\/p>\n<p><strong>(13)<\/strong> Click open the <strong>Networking<\/strong> tab, scroll to <strong>Network tags<\/strong>, and add <code>collector<\/code> as a tag.<\/p>\n<div style=\"aspect-ratio: 515 \/ 343;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/collector-tag.jpg\" title=\"collector tag\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"343\" width=\"515\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/collector-tag.jpg#ZgotmplZ\" alt=\"collector tag\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(14)<\/strong> Click <strong>Create<\/strong> when done.<\/p>\n<div style=\"aspect-ratio: 1002 \/ 233;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-template-created.jpg\" title=\"Instance template created\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"233\" width=\"1002\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-template-created.jpg#ZgotmplZ\" alt=\"Instance template created\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-2-create-a-firewall-rule\">Step 2: Create a firewall rule<\/h3>\n<p>You\u2019ll need to create a <strong>Firewall rule<\/strong> to allow external connections to your GCE instances.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/networking\/firewalls\/list\">https:\/\/console.cloud.google.com\/networking\/firewalls\/list<\/a>, and click <strong>CREATE FIREWALL RULE<\/strong>.<\/p>\n<p><strong>(2)<\/strong> Give the rule a name.<\/p>\n<p><strong>(3)<\/strong> Scroll down to <strong>Target tags<\/strong>, and type <code>collector<\/code> in the field.<\/p>\n<p><strong>(4)<\/strong> In <strong>Source IP ranges<\/strong>, type <code>0.0.0.0\/0<\/code>.<\/p>\n<p><strong>(5)<\/strong> Check the box next to <strong>TCP<\/strong> in the Protocols and ports selection (with <strong>Specified protocols and ports selected<\/strong>), and type in <code>8080<\/code>.<\/p>\n<div style=\"aspect-ratio: 695 \/ 556;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/new-firewall-rule.jpg\" title=\"New firewall rule\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"556\" width=\"695\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/new-firewall-rule.jpg#ZgotmplZ\" alt=\"New firewall rule\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(6)<\/strong> Click <strong>Create<\/strong> when ready.<\/p>\n<div style=\"aspect-ratio: 913 \/ 265;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-firewall-rule.jpg\" title=\"new snowplow firewall rule\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"265\" width=\"913\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/snowplow-firewall-rule.jpg#ZgotmplZ\" alt=\"new snowplow firewall rule\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-2-create-an-instance-group\">Step 2: Create an instance group<\/h3>\n<p>Next, we\u2019ll need to create an <strong>instance group<\/strong>, which is used by the load balancer as the backend service.<\/p>\n<p><strong>(1)<\/strong> Scroll to <a href=\"https:\/\/console.cloud.google.com\/compute\/instanceGroups\/list\">https:\/\/console.cloud.google.com\/compute\/instanceGroups\/list<\/a>, and click <strong>Create instance group<\/strong>.<\/p>\n<p><strong>(2)<\/strong> Give the group a descriptive name.<\/p>\n<p><strong>(3)<\/strong> Feel free to set the <strong>Location<\/strong> closer to home, if you wish.<\/p>\n<div style=\"aspect-ratio: 472 \/ 559;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-group-settings.jpg\" title=\"Instance group settings\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"559\" width=\"472\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-group-settings.jpg#ZgotmplZ\" alt=\"Instance group settings\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(4)<\/strong> Select the instance template you created in the previous step from the <strong>Instance template<\/strong> selector.<\/p>\n<div style=\"aspect-ratio: 615 \/ 276;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-group-template-selection.jpg\" title=\"Select template\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"276\" width=\"615\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/instance-group-template-selection.jpg#ZgotmplZ\" alt=\"Select template\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<blockquote>\n<p><strong>UPDATE 18 Jan 2019<\/strong>: You can also set <strong>Autoscaling<\/strong> <code>Off<\/code>. This means that the instance will not generate new machines automatically. You can start off with this, and then move to an autoscaling setup if you find the Collector lagging behind a lot.<\/p>\n<\/blockquote>\n<p><strong>(5)<\/strong> Scroll down to <strong>Health check<\/strong>, click the menu, and select <strong>Create a health check<\/strong>.<\/p>\n<div style=\"aspect-ratio: 675 \/ 197;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-health-check.jpg\" title=\"Create a health check\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"197\" width=\"675\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-health-check.jpg#ZgotmplZ\" alt=\"Create a health check\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(6)<\/strong> Set the following options:<\/p>\n<ul>\n<li><strong>Name<\/strong>: a descriptive name<\/li>\n<li><strong>Protocol<\/strong>: HTTP<\/li>\n<li><strong>Port<\/strong>: 8080<\/li>\n<li><strong>Request path<\/strong>: \/health<\/li>\n<li><strong>Check interval<\/strong>: 10 seconds<\/li>\n<li><strong>Unhealthy threshold<\/strong>: 3 consecutive failures<\/li>\n<\/ul>\n<p>You can leave the rest of the settings to their default values.<\/p>\n<div style=\"aspect-ratio: 623 \/ 484;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-health-check-new.jpg\" title=\"create new health check\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"484\" width=\"623\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-health-check-new.jpg#ZgotmplZ\" alt=\"create new health check\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(7)<\/strong> Click <strong>Save and continue<\/strong> once finished with the health check settings.<\/p>\n<p><strong>(8)<\/strong> Click <strong>Create<\/strong> to finish creating the instance group.<\/p>\n<div style=\"aspect-ratio: 517 \/ 317;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-instance-group.jpg\" title=\"Create a new instance group\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"317\" width=\"517\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-instance-group.jpg#ZgotmplZ\" alt=\"Create a new instance group\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you create the group, you should see it loading for a while, after which you will see that the group has been created and a new instance has already been fired up!<\/p>\n<p>At this point, you can quickly test if the instance is working.<\/p>\n<p><strong>(9)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/compute\/instances\">https:\/\/console.cloud.google.com\/compute\/instances<\/a>, and copy the IP address from the <strong>External IP<\/strong> field next to the instance created from your instance group.<\/p>\n<div style=\"aspect-ratio: 1010 \/ 247;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/external-ip-copy.jpg\" title=\"External IP address copied\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"247\" width=\"1010\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/external-ip-copy.jpg#ZgotmplZ\" alt=\"External IP address copied\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(10)<\/strong> Open your terminal software and run the following command:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ curl http:\/\/<external_ip_here>:8080\/health<\/external_ip_here><\/code><\/pre>\n<\/div>\n<p>Replace <code><external_ip_here\/><\/code> with the IP address you copied.<\/p>\n<p>You should see a <code>OK<\/code> message as the response.<\/p>\n<p>Next, try:<\/p>\n<p>You should see a garbled GIF response (something like <code>GIF89a????!?,D;<\/code>).<\/p>\n<p>If you see those two, your instance is working, and you\u2019ve successfully created an HTTP endpoint.<\/p>\n<p>Huh? That\u2019s exactly what you did in the previous chapter!<\/p>\n<p>But by using <strong>Instance groups<\/strong> you are now ready for the biggest step here: creating a load balancer.<\/p>\n<h3 id=\"step-3-create-a-load-balancer\">Step 3: Create a load balancer<\/h3>\n<p>The purpose of the load balancer is to automatically scale your system up and down, depending on things like traffic spikes and CPU usage. It works by establishing a single IP address in the public-facing internet, which then tunnels\/proxies the traffic to all the necessary internal IP addresses (your GCE instances, basically), without the outside world knowing this.<\/p>\n<p>The other benefit of the load balancer is that we can use it to assign a <strong>static IP address<\/strong> to the balancer, and with a static IP we can apply our <strong>custom domain name<\/strong> so that a <strong>Google-managed SSL certificate<\/strong> can be applied to the endpoint.<\/p>\n<p>So, there\u2019s a lot of stuff in this step, pay attention!<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/net-services\/loadbalancing\/loadBalancers\/list\">https:\/\/console.cloud.google.com\/net-services\/loadbalancing\/loadBalancers\/list<\/a>, and click <strong>Create load balancer<\/strong>.<\/p>\n<p><strong>(2)<\/strong> Click <strong>Start configuration<\/strong> in the <strong>HTTP(S) Load Balancing<\/strong> panel.<\/p>\n<div style=\"aspect-ratio: 1061 \/ 473;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/start-configuration-load-balancer.jpg\" title=\"Load balancer start configuration\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"473\" width=\"1061\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/start-configuration-load-balancer.jpg#ZgotmplZ\" alt=\"Load balancer start configuration\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Give the load balancer a name, click <strong>Backend configuration<\/strong>, then <strong>Create or select backend services &amp; backend buckets<\/strong>, then choose <strong>Backend services -&gt; Create a backend service<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1148 \/ 391;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-a-backend-service.jpg\" title=\"Create a backend service\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"391\" width=\"1148\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-a-backend-service.jpg#ZgotmplZ\" alt=\"Create a backend service\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(4)<\/strong> Give the backend service a name.<\/p>\n<p><strong>(5)<\/strong> Leave all the other settings to their default values, but choose the <strong>Instance group<\/strong> you created earlier from the respective menu. Set <code>8080<\/code> in the <strong>Port numbers<\/strong> field, and select the <strong>Health check<\/strong> you\u2019ve also created earlier in the <strong>Health check<\/strong> menu.<\/p>\n<div style=\"aspect-ratio: 479 \/ 871;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/backend-service-settings.jpg\" title=\"Backend service\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"871\" width=\"479\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/backend-service-settings.jpg#ZgotmplZ\" alt=\"Backend service\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(6)<\/strong> Click <strong>Create<\/strong> when ready.<\/p>\n<div style=\"aspect-ratio: 949 \/ 391;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/load-balancer-backend-ready.jpg\" title=\"Backend configuration ready\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"391\" width=\"949\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/load-balancer-backend-ready.jpg#ZgotmplZ\" alt=\"Backend configuration ready\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(7)<\/strong> Click open <strong>Host and path rules<\/strong>, and make sure the backend service you created is visible in the rule.<\/p>\n<div style=\"aspect-ratio: 693 \/ 284;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/host-and-path-rules.jpg\" title=\"Host and path rules\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"284\" width=\"693\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/host-and-path-rules.jpg#ZgotmplZ\" alt=\"Host and path rules\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(8)<\/strong> Next, click <strong>Frontend configuration<\/strong>.<\/p>\n<p><strong>(9)<\/strong> Give the frontend service a name.<\/p>\n<p><strong>(10)<\/strong> Choose <strong>HTTPS<\/strong> as the <strong>Protocol<\/strong>.<\/p>\n<p><strong>(11)<\/strong> Select the <strong>IP Address<\/strong> list, and click <strong>create IP address<\/strong>.<\/p>\n<div style=\"aspect-ratio: 958 \/ 462;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-ip-address.jpg\" title=\"Create static ip address\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"462\" width=\"958\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-ip-address.jpg#ZgotmplZ\" alt=\"Create static ip address\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(12)<\/strong> Choose a name for the IP address and click <strong>RESERVE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 527 \/ 277;\" class=\"figure \">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-static-ip.jpg\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"277\" width=\"527\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-static-ip.jpg#ZgotmplZ\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(13)<\/strong> Make sure the new IP address is selected in the frontend configuration, and <strong>copy the IP address to a text editor or something<\/strong>. You\u2019ll need it when configuring the DNS of your custom domain name!<\/p>\n<p><strong>(14)<\/strong> Make sure <code>443<\/code> is set as the <strong>Port<\/strong>.<\/p>\n<p><strong>(15)<\/strong> In the <strong>Certificates<\/strong> menu, choose <strong>Create a new certificate<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1066 \/ 297;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-a-new-certificate.jpg\" title=\"Create new certificate\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"297\" width=\"1066\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-a-new-certificate.jpg#ZgotmplZ\" alt=\"Create new certificate\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(16)<\/strong> Give the new certificate a name.<\/p>\n<p><strong>(17)<\/strong> Choose <strong>Create Google-managed certificate<\/strong>.<\/p>\n<p><strong>(18)<\/strong> Type the name of the domain you will set to point to your collector in the <strong>Domains<\/strong> field.<\/p>\n<p><strong>(19)<\/strong> When ready, click <strong>Create<\/strong>.<\/p>\n<div style=\"aspect-ratio: 680 \/ 496;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-certificate.jpg\" title=\"Create a certificate\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"496\" width=\"680\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/create-certificate.jpg#ZgotmplZ\" alt=\"Create a certificate\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(20)<\/strong> When finished with the frontend configuration, click <strong>Done<\/strong>.<\/p>\n<div style=\"aspect-ratio: 991 \/ 416;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/click-done.jpg\" title=\"Click done\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"416\" width=\"991\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/click-done.jpg#ZgotmplZ\" alt=\"Click done\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(21)<\/strong> Click <strong>Review and finalize<\/strong> to see all the changes. You\u2019ll probably see that your certificate is still in the <code>PROVISIONING<\/code> status, because you haven\u2019t updated your domain DNS yet.<\/p>\n<div style=\"aspect-ratio: 1381 \/ 561;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/review-and-finalize.jpg\" title=\"Review and finalize\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"561\" width=\"1381\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/review-and-finalize.jpg#ZgotmplZ\" alt=\"Review and finalize\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(22)<\/strong> When satisfied, click <strong>Create<\/strong> to create the load balancer.<\/p>\n<h3 id=\"step-4-configure-your-dns\">Step 4: Configure your DNS<\/h3>\n<p>At this point, you need to go to your DNS settings for the custom domain name you want to point to your load balancing collector.<\/p>\n<p>You need to create an <code>A<\/code> record for the domain name, which points to the <strong>static IP address<\/strong> you created for the load balancer frontend above. You can set the <code>TTL<\/code> to something like 600 seconds to see the change quicker.<\/p>\n<p>This is what it would look like in <a href=\"https:\/\/www.godaddy.com\/\">GoDaddy<\/a>.<\/p>\n<div style=\"aspect-ratio: 878 \/ 294;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/a-record-godaddy.jpg\" title=\"A record Godaddy\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"294\" width=\"878\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/a-record-godaddy.jpg#ZgotmplZ\" alt=\"A record Godaddy\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you\u2019ve configured the DNS, it will take a while for the new name to resolve. You can check what the status is by running this in your terminal:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ host tracker.gtmtools.com<\/code><\/pre>\n<\/div>\n<p>Replace <code>tracker.gtmtools.com<\/code> with the domain name you configured in the DNS. If it works, you should see a response with the IP address you configured.<\/p>\n<div style=\"aspect-ratio: 344 \/ 56;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/host-command.jpg\" title=\"Host command run\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"56\" width=\"344\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/host-command.jpg#ZgotmplZ\" alt=\"Host command run\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-5-testing-everything\">Step 5: Testing everything<\/h3>\n<p>Once you\u2019ve configured the domain name and it resolves to the correct IP, you might still need to wait a while for the SSL certificate to be provisioned for the domain name. This might take anywhere from a couple of minutes to some hours, so be patient.<\/p>\n<p>You can check the status of your system by browsing to the <a href=\"https:\/\/console.cloud.google.com\/net-services\/loadbalancing\/loadBalancers\/list\">load balancer list<\/a> and clicking the load balancer you created.<\/p>\n<p>The frontend should show a green checkmark next to your certificate name to indicate that the certificate has been assigned.<\/p>\n<p>In the backend service, you should see one healthy instance created.<\/p>\n<div style=\"aspect-ratio: 1039 \/ 556;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/check-load-balancer.jpg\" title=\"Check load balancer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"556\" width=\"1039\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/check-load-balancer.jpg#ZgotmplZ\" alt=\"Check load balancer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If all is fine, the next step is to test the endpoint itself. Open the terminal and run the following command:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ curl -d <span style=\"color:#a50\">\"&amp;e=pv&amp;page=https-test&amp;url=https%3A%2F%2Fjust-testing.com&amp;aid=snowplow-test\"<\/span> -X POST https:\/\/<custom_domain>\/com.snowplowanalytics.iglu\/v1<\/custom_domain><\/code><\/pre>\n<\/div>\n<p>Replace <code><custom_domain\/><\/code> with the domain name you\u2019ve configured to point to the load balancer.<\/p>\n<p>If it worked, you should see an <code>OK<\/code> response.<\/p>\n<div style=\"aspect-ratio: 1147 \/ 63;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/ok-response.jpg\" title=\"OK Response\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"63\" width=\"1147\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/ok-response.jpg#ZgotmplZ\" alt=\"OK Response\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, you can try and retrieve the payload from the Pub\/Sub topic with:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ gcloud pubsub subscriptions pull --auto-ack test-good<\/code><\/pre>\n<\/div>\n<p>You should see something like this as the response:<\/p>\n<div style=\"aspect-ratio: 1514 \/ 384;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/11\/pubsub-test.jpg\" title=\"Pubsub test\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"384\" width=\"1514\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/11\/pubsub-test.jpg#ZgotmplZ\" alt=\"Pubsub test\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If you see that, then congratulations, you now have an HTTPS endpoint for your collector!<\/p>\n<p>If something\u2019s wrong, then you\u2019ll need to start narrowing down where the problem is. Check your Google Compute Engine pages, make sure there\u2019s a healthy GCE instance running. You can even SSH into the instance, browse to the <code>\/var\/log\/<\/code> directory, and open the <code>daemon.log<\/code> file for editing to see if there was a problem with the startup script.<\/p>\n<p>Other than that, it\u2019s very difficult to say what the likely source of the problem is. Personally, I made many mistakes initially with port configurations and setting up the individual components. But if you follow this guide to the letter, you should be fine.<\/p>\n<p>Sometimes the problem is that you just need to wait for the DNS to resolve and the certificate to be assigned to the new domain name. This might take a while.<\/p>\n<p>Next up, <strong>enrichment<\/strong> and loading the data into <strong>BigQuery<\/strong>!<\/p>\n<blockquote>\n<p>Note! If you\u2019re going to take a break now, remember to <strong>STOP<\/strong> your GCE instance. You can always restart it when ready to go on. You don\u2019t want to accumulate any unwanted costs from having the instance running for no purpose!<\/p>\n<\/blockquote>\n<h2 id=\"set-up-the-google-analytics-tracker\">Set up the Google Analytics tracker<\/h2>\n<div style=\"aspect-ratio: 1364 \/ 612;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/tracker-process.jpg\" title=\"Pub\/sub process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"612\" width=\"1364\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/tracker-process.jpg#ZgotmplZ\" alt=\"Pub\/sub process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Before we go on to enrichment, now is a good time to set up the <strong>tracker<\/strong>. For this purpose, we\u2019ll be using the <a href=\"https:\/\/www.simoahava.com\/analytics\/automatically-fork-google-analytics-hits-snowplow\/\">Snowplow Google Analytics plugin<\/a>, because it\u2019s an easy way to leverage existing tracking on your site. If you want, feel free to use the regular <a href=\"https:\/\/github.com\/snowplow\/snowplow\/wiki\/javascript-tracker-setup\">Snowplow JavaScript tracker<\/a>.<\/p>\n<p>To begin with, head on over to my <a href=\"https:\/\/www.simoahava.com\/tools\/customtask-builder\/#the-customtask-builder-tool\">customTask Builder Tool<\/a>.<\/p>\n<p><strong>(1)<\/strong> Click the option labelled <code>Copy Hits to Snowplow Collector Endpoint<\/code>.<\/p>\n<p><strong>(2)<\/strong> Click <strong>Copy to clipboard<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1936 \/ 1018;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/customtask-builder.jpg\" title=\"customtask builder\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1018\" width=\"1936\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/customtask-builder.jpg#ZgotmplZ\" alt=\"customtask builder\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Next, in <a href=\"https:\/\/tagmanager.google.com\/\">Google Tag Manager<\/a>, create a new <strong>Custom JavaScript variable<\/strong>, and paste the contents of the clipboard there.<\/p>\n<p><strong>(4)<\/strong> In the beginning of the code block, remove <code>var _customTask = <\/code>, so that the first characters of the block are <code>function() {<\/code>.<\/p>\n<p><strong>(5)<\/strong> In the end of the block, remove the semicolon that is the very last character of the block.<\/p>\n<p><strong>(6)<\/strong> Edit the line starting with <code>var snowplowEndpoint=\"...\";<\/code> so that the string contains the URL to your collector endpoint.<\/p>\n<div style=\"aspect-ratio: 2110 \/ 646;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-customtask.jpg\" title=\"Snowplow customtask\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"646\" width=\"2110\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-customtask.jpg#ZgotmplZ\" alt=\"Snowplow customtask\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(7)<\/strong> Save the variable with some name, e.g. <code>JS - Snowplow duplicator<\/code>.<\/p>\n<p><strong>(8)<\/strong> Next, open your <strong>Page View tag<\/strong>, check <strong>Enable overriding settings in this tag<\/strong>, scroll down to <strong>More Settings<\/strong> &gt; <strong>Fields to set<\/strong>, and add a new field:<\/p>\n<p><strong>Field name<\/strong>: customTask<br \/><strong>Value<\/strong>: {{JS &#8211; Snowplow duplicator}}<\/p>\n<div style=\"aspect-ratio: 2034 \/ 752;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/fields-to-set.jpg\" title=\"Fields to set\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"752\" width=\"2034\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/fields-to-set.jpg#ZgotmplZ\" alt=\"Fields to set\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>That\u2019s it for the tracker. The way it works now is that whenever your Page View tag fires, it will copy the payload to the Snowplow collector endpoint.<\/p>\n<p>If you want, you can publish your container now, after which every visitor to your website will start sending those Page Views to your collector. However, I recommend you use Preview mode for now, so that only <strong>your<\/strong> Page Views are sent to the collector.<\/p>\n<p>Once the pipeline is up and running, you can start collecting more comprehensive data from your visitors.<\/p>\n<h2 id=\"prepare-the-etl-step\">Prepare the ETL step<\/h2>\n<div style=\"aspect-ratio: 1364 \/ 612;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/etl-process.jpg\" title=\"Pub\/sub process\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"612\" width=\"1364\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/etl-process.jpg#ZgotmplZ\" alt=\"Pub\/sub process\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Before moving forward to the extract, transform, and load (ETL) of your collector data, we\u2019ll need to do some preparations.<\/p>\n<h3 id=\"step-1-enable-the-dataflow-api\">Step 1: Enable the Dataflow API<\/h3>\n<p>The enrichment process and the BigQuery loader require a new service to be enabled.<\/p>\n<p><strong>(1)<\/strong> Browse to the <a href=\"https:\/\/console.cloud.google.com\/apis\/library\">API library<\/a>.<\/p>\n<p><strong>(2)<\/strong> Search for <code>dataflow<\/code>, and click the <strong>Dataflow API<\/strong> selector.<\/p>\n<div style=\"aspect-ratio: 1646 \/ 904;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataflow-api.jpg\" title=\"Dataflow API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"904\" width=\"1646\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataflow-api.jpg#ZgotmplZ\" alt=\"Dataflow API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>Cloud Dataflow<\/strong> lets you enrich a data stream with minimal latency. This is exactly what we need. We need the enricher to pull in events for the <strong>Pub\/Sub<\/strong> topic to which the collector writes them, enrich and shred them to proper format, and write them back into a Pub\/Sub topic for BigQuery loading.<\/p>\n<p><strong>(3)<\/strong> Click the <strong>ENABLE<\/strong> button in the API page to enable this service.<\/p>\n<h3 id=\"step-2-create-the-necessary-pubsub-topics-and-subscriptions\">Step 2: Create the necessary Pub\/Sub topics and subscriptions<\/h3>\n<p>By now, you should have two <strong>Pub\/Sub<\/strong> topics, <code>good<\/code> and <code>bad<\/code> for hits processed by your collector.<\/p>\n<p>We\u2019ll need to create a bunch of additional topics and subscriptions for the remaining steps of the pipeline.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/cloudpubsub\/topicList\">https:\/\/console.cloud.google.com\/cloudpubsub\/topicList<\/a>.<\/p>\n<p><strong>(2)<\/strong> Click open the <code>good<\/code> topic, and click <strong>Create Subscription<\/strong>. Give the subscription the name <code>good-sub<\/code>.<\/p>\n<div style=\"aspect-ratio: 1158 \/ 686;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/good-sub.jpg\" title=\"good subscription\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"686\" width=\"1158\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/good-sub.jpg#ZgotmplZ\" alt=\"good subscription\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Next, create the following topics:<\/p>\n<ol>\n<li><code>bq-bad-rows<\/code><\/li>\n<li><code>bq-failed-inserts<\/code><\/li>\n<li><code>bq-types<\/code><\/li>\n<li><code>enriched-bad<\/code><\/li>\n<li><code>enriched-good<\/code><\/li>\n<\/ol>\n<p><strong>(4)<\/strong> Then, click open <code>bq-types<\/code> and create a new subscription for it named <code>bq-types-sub<\/code>.<\/p>\n<p><strong>(5)<\/strong> Finally, click open <code>enriched-good<\/code> and create a new subscription for it named <code>enriched-good-sub<\/code>.<\/p>\n<p>This is what the <a href=\"https:\/\/console.cloud.google.com\/cloudpubsub\/topicList\">topic list<\/a> should look like:<\/p>\n<div style=\"aspect-ratio: 1926 \/ 1046;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/pubsub-topics.jpg\" title=\"pubsub topics\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1046\" width=\"1926\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/pubsub-topics.jpg#ZgotmplZ\" alt=\"pubsub topics\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This is what the <a href=\"https:\/\/console.cloud.google.com\/cloudpubsub\/subscriptions\">subscription list<\/a> should look like:<\/p>\n<div style=\"aspect-ratio: 2990 \/ 546;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/subscription-list.jpg\" title=\"Subscription list\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"546\" width=\"2990\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/subscription-list.jpg#ZgotmplZ\" alt=\"Subscription list\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can, of course, create additional subscriptions for testing and debugging, but these are what the following steps of the pipeline specifically need.<\/p>\n<h3 id=\"step-3-create-a-new-storage-bucket-for-temporary-files\">Step 3: Create a new storage bucket for temporary files<\/h3>\n<p>You\u2019ll need to create a new storage bucket for temporary files created by the enrichment process. But this is a good place to also store some files required by the enrich and load stages.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/storage\/browser\/\">https:\/\/console.cloud.google.com\/storage\/browser\/<\/a>.<\/p>\n<p><strong>(2)<\/strong> Click <strong>CREATE BUCKET<\/strong> and create a new bucket with the name <code>snowplow-yourname-temp<\/code> like so:<\/p>\n<div style=\"aspect-ratio: 2052 \/ 1080;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-temp-bucket.jpg\" title=\"Snowplow temp bucket\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1080\" width=\"2052\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-temp-bucket.jpg#ZgotmplZ\" alt=\"Snowplow temp bucket\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Click into that bucket and click <strong>Create folder<\/strong>, and name the new folder <code>temp-files<\/code>:<\/p>\n<div style=\"aspect-ratio: 2624 \/ 1088;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/files-folder-bucket.jpg\" title=\"Files folder bucket\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1088\" width=\"2624\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/files-folder-bucket.jpg#ZgotmplZ\" alt=\"Files folder bucket\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-4-create-the-iglu_resolverjson-configuration\">Step 4: Create the iglu_resolver.json configuration<\/h3>\n<p>Snowplow uses something called <strong>resolvers<\/strong> to automatically identify the parameters of each incoming hit. This is necessary for many reasons, the main being hit validation (to identify valid requests from invalid ones), for enriching and shredding the hits to the proper data format, and for mutating and loading the data into BigQuery tables.<\/p>\n<p><strong>(1)<\/strong> Download the <code>iglu_resolver.json<\/code> file from <a href=\"https:\/\/raw.githubusercontent.com\/snowplow\/snowplow\/master\/3-enrich\/config\/iglu_resolver.json\">here<\/a> and open it for editing.<\/p>\n<p><strong>(2)<\/strong> Change the two <code>vendorPrefixes<\/code> parameters to also include the Google Analytics namespace (<strong>Note!<\/strong> You don\u2019t have to do this if you\u2019re using the vanilla Snowplow JavaScript tracker).<\/p>\n<div style=\"aspect-ratio: 1674 \/ 924;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/google-analytics-schema.jpg\" title=\"Google Analytics schema\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"924\" width=\"1674\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/google-analytics-schema.jpg#ZgotmplZ\" alt=\"Google Analytics schema\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Save the file locally.<\/p>\n<p><strong>(4)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/storage\/browser\/\">https:\/\/console.cloud.google.com\/storage\/browser\/<\/a>, click open the temporary file bucket you just created, and upload the modified <code>iglu_resolver.json<\/code> to the root of that bucket (so <strong>not<\/strong> in the <code>temp-files<\/code> folder).<\/p>\n<div style=\"aspect-ratio: 2424 \/ 948;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/iglu-resolver.jpg\" title=\"Iglu resolver\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"948\" width=\"2424\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/iglu-resolver.jpg#ZgotmplZ\" alt=\"Iglu resolver\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>With this resolver configuration, you\u2019re instructing the enricher and loader that hits using the Google Analytics namespace <em>might<\/em> be coming in.<\/p>\n<h3 id=\"step-5-create-a-new-bigquery-dataset\">Step 5: Create a new BigQuery dataset<\/h3>\n<p>Before moving on, you\u2019ll need to create a new dataset in BigQuery that will hold the table where your data will end up.<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/bigquery\">https:\/\/console.cloud.google.com\/bigquery<\/a>, and choose your project from the selector to the left. Click <strong>CREATE DATASET<\/strong>.<\/p>\n<div style=\"aspect-ratio: 3668 \/ 1230;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-dataset.jpg\" title=\"Create new dataset\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1230\" width=\"3668\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-dataset.jpg#ZgotmplZ\" alt=\"Create new dataset\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(2)<\/strong> Give the dataset an ID, such as <code>snowplow_yourname_dataset<\/code>, and click <strong>Create dataset<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1894 \/ 660;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataset-id.jpg\" title=\"Dataset ID\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"660\" width=\"1894\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataset-id.jpg#ZgotmplZ\" alt=\"Dataset ID\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>NOTE!<\/strong> At this point you might want to just create a new table manually and <a href=\"https:\/\/cloud.google.com\/bigquery\/docs\/partitioned-tables\">partition<\/a> that table on the <code>derived_tstamp<\/code> column. This way the BigQuery table is automatically partitioned by date of the hit, making it easier to manage and query by date.<\/p>\n<p>This guide proceeds without creating a partitioned table just so that you can see how the mutator works.<\/p>\n<h3 id=\"step-6-create-the-bigquery-configuration-file\">Step 6: Create the BigQuery configuration file<\/h3>\n<p><strong>(1)<\/strong> Open a new file for editing in a <strong>plain text editor<\/strong>.<\/p>\n<p><strong>(2)<\/strong> Copy-paste the following within:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-json\" data-lang=\"json\">{\n    <span style=\"color:#1e90ff;font-weight:bold\">\"schema\"<\/span>: <span style=\"color:#a50\">\"iglu:com.snowplowanalytics.snowplow.storage\/bigquery_config\/jsonschema\/1-0-0\"<\/span>,\n    <span style=\"color:#1e90ff;font-weight:bold\">\"data\"<\/span>: {\n        <span style=\"color:#1e90ff;font-weight:bold\">\"name\"<\/span>: <span style=\"color:#a50\">\"Snowplow Page View Data\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"id\"<\/span>: <span style=\"color:#a50\">\"<random uuid=\"\">\"<\/random><\/span>,\n\n        <span style=\"color:#1e90ff;font-weight:bold\">\"projectId\"<\/span>: <span style=\"color:#a50\">\"<your gcp=\"\" project=\"\" name=\"\">\"<\/your><\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"datasetId\"<\/span>: <span style=\"color:#a50\">\"<the bigquery=\"\" dataset=\"\" id=\"\">\"<\/the><\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"tableId\"<\/span>: <span style=\"color:#a50\">\"pageviews\"<\/span>,\n\n        <span style=\"color:#1e90ff;font-weight:bold\">\"input\"<\/span>: <span style=\"color:#a50\">\"enriched-good-sub\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"typesTopic\"<\/span>: <span style=\"color:#a50\">\"bq-types\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"typesSubscription\"<\/span>: <span style=\"color:#a50\">\"bq-types-sub\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"badRows\"<\/span>: <span style=\"color:#a50\">\"bq-bad-rows\"<\/span>,\n        <span style=\"color:#1e90ff;font-weight:bold\">\"failedInserts\"<\/span>: <span style=\"color:#a50\">\"bq-failed-inserts\"<\/span>,\n\n        <span style=\"color:#1e90ff;font-weight:bold\">\"load\"<\/span>: {\n            <span style=\"color:#1e90ff;font-weight:bold\">\"mode\"<\/span>: <span style=\"color:#a50\">\"STREAMING_INSERTS\"<\/span>,\n            <span style=\"color:#1e90ff;font-weight:bold\">\"retry\"<\/span>: <span style=\"color:#00a\">false<\/span>\n        },\n\n        <span style=\"color:#1e90ff;font-weight:bold\">\"purpose\"<\/span>: <span style=\"color:#a50\">\"ENRICHED_EVENTS\"<\/span>\n    }\n}<\/code><\/pre>\n<\/div>\n<p><strong>(3)<\/strong> Change the value of <code>\"id\"<\/code> to a random UUID which you can generate <a href=\"https:\/\/www.uuidgenerator.net\/\">here<\/a>. A valid value would be e.g. <code>\"3a27d47f-aeaf-4034-84b6-b1e82ca711d6\"<\/code>.<\/p>\n<p><strong>(4)<\/strong> Change the value of <code>\"projectId\"<\/code> to your GCP project ID (e.g. <code>\"snowplow-production-simoahava\"<\/code>).<\/p>\n<p><strong>(5)<\/strong> Change the value of <code>\"datasetId\"<\/code> to the ID you just gave your BigQuery dataset, e.g. <code>\"snowplow_simoahava_dataset\"<\/code>.<\/p>\n<p><strong>(6)<\/strong> Make sure the Pub\/Sub topic and subscription names correspond with those you created earlier in this chapter.<\/p>\n<p><strong>(7)<\/strong> Save the file locally as <code>bigquery_config.json<\/code>.<\/p>\n<p><strong>(8)<\/strong> Upload it to the temporary file bucket, where you already uploaded <code>iglu_resolver.json<\/code> to:<\/p>\n<div style=\"aspect-ratio: 2464 \/ 994;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/config-uploaded.jpg\" title=\"Config uploaded\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"994\" width=\"2464\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/config-uploaded.jpg#ZgotmplZ\" alt=\"Config uploaded\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>PHEW!<\/strong> That\u2019s all the prep work done. Now you\u2019ll need to just get the enricher and the BQ loader up and running in a new virtual instance group!<\/p>\n<h2 id=\"finalize-the-etl-process\">Finalize the ETL process<\/h2>\n<p>For ETL (extraction, transformation, and loading of your collector data), we\u2019ll create a new <strong>instance template<\/strong> for an auto-scaling <strong>instance group<\/strong>.<\/p>\n<p>You <em>could<\/em> use the same instance group as your collector, but this is not a sustainable way to run the pipeline, because the data streams from your site to the collector, from your collector to the enricher, and from the enriched stream to the BigQuery loader are vastly asymmetrical.<\/p>\n<p>It will lead to a lot of redundancy and overhead if you have all your eggs in one basket.<\/p>\n<p>Optimally, you\u2019d run the enrichment and the BQ loader in their own groups, too, but for the sake of this exercise I\u2019ll bunch them together for now.<\/p>\n<h3 id=\"step-1-create-the-instance-template\">Step 1: Create the instance template<\/h3>\n<p>Since you\u2019ve done all the preparations in the previous chapter, this final step of the ETL is actually pretty simple. All you\u2019ll need to do is create the instance template and fire an instance group off of it.<\/p>\n<p>Well, it\u2019s simple <em>to you<\/em>. It took me hours and hours and hours to get the thing working, so you\u2019re welcome!<\/p>\n<p><strong>(1)<\/strong> Browse to <a href=\"https:\/\/console.cloud.google.com\/compute\/instanceTemplates\/list\">https:\/\/console.cloud.google.com\/compute\/instanceTemplates\/list<\/a>, and click <strong>CREATE INSTANCE TEMPLATE<\/strong>.<\/p>\n<p><strong>(2)<\/strong> Give the template a name, e.g. <code>snowplow-etl-template<\/code>.<\/p>\n<div style=\"aspect-ratio: 2050 \/ 840;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-etl-template.jpg\" title=\"Create ETL template\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"840\" width=\"2050\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-etl-template.jpg#ZgotmplZ\" alt=\"Create ETL template\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Make sure the <strong>Compute Engine default service account<\/strong> is selected, and choose <strong>Set access for each API<\/strong> in the Access scopes list.<\/p>\n<div style=\"aspect-ratio: 1714 \/ 634;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/etl-template-api.jpg\" title=\"ETL template API access\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"634\" width=\"1714\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/etl-template-api.jpg#ZgotmplZ\" alt=\"ETL template API access\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(4)<\/strong> Change the following API scopes:<\/p>\n<ul>\n<li>BigQuery: <strong>Enabled<\/strong><\/li>\n<li>Cloud Pub\/Sub: <strong>Enabled<\/strong><\/li>\n<li>Compute Engine: <strong>Read Write<\/strong><\/li>\n<li>Storage: <strong>Read Write<\/strong><\/li>\n<\/ul>\n<p>Leave the other options with their default values.<\/p>\n<div style=\"aspect-ratio: 1514 \/ 2002;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/api-scopes.jpg\" title=\"API scopes\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"2002\" width=\"1514\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/api-scopes.jpg#ZgotmplZ\" alt=\"API scopes\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(5)<\/strong> Scroll down and click the <strong>Management, security, disks, networking, sole tenancy<\/strong> accordion heading.<\/p>\n<div style=\"aspect-ratio: 1706 \/ 584;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/management-accordion.jpg\" title=\"Management accordion\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"584\" width=\"1706\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/management-accordion.jpg#ZgotmplZ\" alt=\"Management accordion\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(6)<\/strong> Under <strong>Automation<\/strong>, copy-paste the following code into <strong>Startup script<\/strong>:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"color:#4c8317\">#! \/bin\/bash\n<\/span><span style=\"color:#4c8317\"\/><span style=\"color:#a00\">enrich_version<\/span>=<span style=\"color:#a50\">\"0.1.0\"<\/span>\n<span style=\"color:#a00\">bq_version<\/span>=<span style=\"color:#a50\">\"0.1.0\"<\/span>\n<span style=\"color:#a00\">bucket_name<\/span>=<span style=\"color:#a50\">\"<cloud storage=\"\" bucket=\"\" name=\"\">\"<\/cloud><\/span>\n<span style=\"color:#a00\">project_id<\/span>=<span style=\"color:#a50\">\"<gcp project=\"\" id=\"\">\"<\/gcp><\/span>\n<span style=\"color:#a00\">region<\/span>=<span style=\"color:#a50\">\"<region where=\"\" to=\"\" run=\"\" the=\"\" dataflow=\"\" instances=\"\">\"<\/region><\/span>\n\nsudo apt-get update\nsudo apt-get -y install default-jre\nsudo apt-get -y install unzip\n\nwget https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/snowplow_beam_enrich_<span style=\"color:#a00\">$enrich_version<\/span>.zip\nunzip snowplow_beam_enrich_<span style=\"color:#a00\">$enrich_version<\/span>.zip\n\nwget https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/snowplow_bigquery_loader_<span style=\"color:#a00\">$bq_version<\/span>.zip\nunzip snowplow_bigquery_loader_<span style=\"color:#a00\">$bq_version<\/span>.zip\n\nwget https:\/\/dl.bintray.com\/snowplow\/snowplow-generic\/snowplow_bigquery_mutator_<span style=\"color:#a00\">$bq_version<\/span>.zip\nunzip snowplow_bigquery_mutator_<span style=\"color:#a00\">$bq_version<\/span>.zip\n\ngsutil cp gs:\/\/<span style=\"color:#a00\">$bucket_name<\/span>\/iglu_resolver.json .\ngsutil cp gs:\/\/<span style=\"color:#a00\">$bucket_name<\/span>\/bigquery_config.json .\n\n.\/beam-enrich-<span style=\"color:#a00\">$enrich_version<\/span>\/bin\/beam-enrich --runner=DataFlowRunner --project=<span style=\"color:#a00\">$project_id<\/span> --streaming=<span style=\"color:#0aa\">true<\/span> --region=<span style=\"color:#a00\">$region<\/span> --gcpTempLocation=gs:\/\/<span style=\"color:#a00\">$bucket_name<\/span>\/temp-files --job-name=beam-enrich --raw=projects\/<span style=\"color:#a00\">$project_id<\/span>\/subscriptions\/good-sub --enriched=projects\/<span style=\"color:#a00\">$project_id<\/span>\/topics\/enriched-good --bad=projects\/<span style=\"color:#a00\">$project_id<\/span>\/topics\/enriched-bad --resolver=iglu_resolver.json\n\n.\/snowplow-bigquery-mutator-<span style=\"color:#a00\">$bq_version<\/span>\/bin\/snowplow-bigquery-mutator create --config <span style=\"color:#00a\">$(<\/span>cat bigquery_config.json | base64 -w 0<span style=\"color:#00a\">)<\/span> --resolver <span style=\"color:#00a\">$(<\/span>cat iglu_resolver.json | base64 -w 0<span style=\"color:#00a\">)<\/span>\n\n.\/snowplow-bigquery-mutator-<span style=\"color:#a00\">$bq_version<\/span>\/bin\/snowplow-bigquery-mutator listen --config <span style=\"color:#00a\">$(<\/span>cat bigquery_config.json | base64 -w 0<span style=\"color:#00a\">)<\/span> --resolver <span style=\"color:#00a\">$(<\/span>cat iglu_resolver.json | base64 -w 0<span style=\"color:#00a\">)<\/span> &amp;\n\n.\/snowplow-bigquery-loader-<span style=\"color:#a00\">$bq_version<\/span>\/bin\/snowplow-bigquery-loader --config=<span style=\"color:#00a\">$(<\/span>cat bigquery_config.json | base64 -w 0<span style=\"color:#00a\">)<\/span> --resolver=<span style=\"color:#00a\">$(<\/span>cat iglu_resolver.json | base64 -w 0<span style=\"color:#00a\">)<\/span> --runner=DataFlowRunner --project=<span style=\"color:#a00\">$project_id<\/span> --region=<span style=\"color:#a00\">$region<\/span> --gcpTempLocation=gs:\/\/<span style=\"color:#a00\">$bucket_name<\/span>\/temp-files<\/code><\/pre>\n<\/div>\n<hr\/>\n<p><strong>UPDATE 18 Jan 2019<\/strong>.<\/p>\n<p>It might be wise to start off by capping the auto-scaling of the Dataflow jobs. Otherwise, since you\u2019re working with streaming inserts, you might end up with lots and lots of virtual machines being started to support the hit stream.<\/p>\n<p>To prevent the <code>loader<\/code> from auto-scaling out of control, you can cap the number of available workers at e.g. 3 (so max. 3 CPUs are used by the job), or you can turn off auto-scaling altogether by capping the worker number at <code>1<\/code>.<\/p>\n<p>To set the maximum of 3 workers to the loader, add this to the end of the <code>snowplow-bigquery-loader<\/code> command:<\/p>\n<p><code>--maxNumWorkers=3<\/code><\/p>\n<p>Setting the maximum to 1 will lead to a single-worker, non-autoscaling setup. To have more than one worker but no autoscaling, you\u2019ll also need to add the option <code>--autoscalingAlgorithm=NONE<\/code> to the loader.<\/p>\n<hr\/>\n<p>You\u2019ll need to populate the five variables in the beginning of the code block before saving the instance template.<\/p>\n<ul>\n<li><code>enrich_version<\/code>: Get the latest Beam Enrich version number from <a href=\"https:\/\/bintray.com\/snowplow\/snowplow-generic\/snowplow-beam-enrich\">here<\/a>.<\/li>\n<li><code>bq_version<\/code>: Get the latest BigQuery Loader version number from <a href=\"https:\/\/bintray.com\/snowplow\/snowplow-generic\/snowplow-bigquery-loader\">here<\/a>.<\/li>\n<li><code>bucket_name<\/code>: Type the name of the storage bucket for temporary files you created earlier.<\/li>\n<li><code>project_id<\/code>: Type your GCE project ID.<\/li>\n<li><code>region<\/code>: Choose a region for the Dataflow instances to run in (you need to choose from <a href=\"https:\/\/cloud.google.com\/dataflow\/docs\/concepts\/regional-endpoints\">this<\/a> list).<\/li>\n<\/ul>\n<p>Once you\u2019ve made the changes to the startup script, click <strong>Create<\/strong>.<\/p>\n<div style=\"aspect-ratio: 918 \/ 872;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-etl-instance-template.jpg\" title=\"Create ETL instance template\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"872\" width=\"918\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-etl-instance-template.jpg#ZgotmplZ\" alt=\"Create ETL instance template\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-2-start-up-a-new-instance-group\">Step 2: Start up a new instance group<\/h3>\n<p>Now that you have the template created, it\u2019s time to create and start an auto-scaling instance group using this template. The idea is that when requests start flooding in, you\u2019ll want to fire up new instances for your enrichment and loading steps so that your data collection won\u2019t be impacted too severely by latency.<\/p>\n<blockquote>\n<p><strong>UPDATE 18 Jan 2019<\/strong>: Instead of creating an auto-scaling instance group, it\u2019s more than enough to just use a single machine in the group to handle the Dataflow! This part of the guide was originally based on some misinformation. You can still go ahead with the instance template, but once we reach the section about the instance group, please read the steps carefully.<\/p>\n<\/blockquote>\n<p><strong>(1)<\/strong> While still in the <strong>instance template list<\/strong>, click open the template you just created.<\/p>\n<p><strong>(2)<\/strong> In the top of the page, click <strong>CREATE INSTANCE GROUP<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1088 \/ 316;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-instance-group.jpg\" title=\"Create instance group\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"316\" width=\"1088\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/create-instance-group.jpg#ZgotmplZ\" alt=\"Create instance group\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p><strong>(3)<\/strong> Give the new group a descriptive name, such as <code>snowplow-etl-group<\/code>.<\/p>\n<p><strong>(4)<\/strong> Choose a region, e.g. <code>europe-west1<\/code>.<\/p>\n<p><strong>(5)<\/strong> Make sure the instance template you just created is selected in the <strong>Instance template<\/strong> menu.<\/p>\n<div style=\"aspect-ratio: 1024 \/ 692;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/etl-instance-group.jpg\" title=\"ETL instance group\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"692\" width=\"1024\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/etl-instance-group.jpg#ZgotmplZ\" alt=\"ETL instance group\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<blockquote>\n<p><strong>UPDATE 18 Jan 2019<\/strong>: You can set <strong>Autoscaling<\/strong> <code>Off<\/code>. The machine starting the Dataflow jobs does <strong>not<\/strong> need to autoscale. The Dataflow jobs are the ones that will require more power depending on the throughput rate, but you can start this machine with one static instance, scaling up manually if necessary.<\/p>\n<\/blockquote>\n<p><strong>(6)<\/strong> You can keep the rest of the options with their default values, but if you like, you can set the health check to what you use for the collector instance, too.<\/p>\n<div style=\"aspect-ratio: 863 \/ 345;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-health-check.jpg\" title=\"Snowplow health check\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"345\" width=\"863\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/snowplow-health-check.jpg#ZgotmplZ\" alt=\"Snowplow health check\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When ready, click <strong>Create<\/strong>.<\/p>\n<h2 id=\"test-everything\">Test everything<\/h2>\n<p>When you create the instance group, it runs the startup script from beginning to end. These are the steps it takes, in order:<\/p>\n<ol>\n<li>\n<p>Beam Enrich is started as a new Dataflow job, using <code>iglu_resolver.json<\/code> for configuration. The Dataflow job essentially starts a new GCE instance group for the job.<\/p>\n<\/li>\n<li>\n<p>The BigQuery Mutator is run with the <code>create<\/code> command, and this creates the <code>pageviews<\/code> table with a simple atomic structure in your BigQuery dataset.<\/p>\n<\/li>\n<li>\n<p>The Mutator is next run in its own thread with the <code>listen<\/code> command. This is basically a Java program which listens for incoming, enriched requests being populated in the <code>enriched-good<\/code> Pub\/Sub topic. Each request is parsed for values and data types, and if the <code>pageviews<\/code> table in BigQuery doesn\u2019t have a corresponding column for some value in the request (validated against a schema resolved by the Iglu Resolver), a new column is created.<\/p>\n<\/li>\n<li>\n<p>Finally, the BigQuery Loader starts up as its own Dataflow GCE instance, and this will load your enriched data into the corresponding columns and rows in your BigQuery table.<\/p>\n<\/li>\n<\/ol>\n<p><strong>Before starting the debugging<\/strong>, make sure you\u2019re sending some hits from your site to the collector. So browse around, visit different pages, and check the network requests to make sure the requests to your collector are completing successfully.<\/p>\n<div style=\"aspect-ratio: 1267 \/ 359;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/tracker-success.jpg\" title=\"Tracker success\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"359\" width=\"1267\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/tracker-success.jpg#ZgotmplZ\" alt=\"Tracker success\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-1-check-that-beam-enrich-is-running\">Step 1: Check that Beam Enrich is running<\/h3>\n<p>To check if Beam Enrich is running, go to the <a href=\"https:\/\/console.cloud.google.com\/dataflow\">Dataflow job list<\/a>. You should see <code>beam-enrich<\/code> in the list with the status <strong>Running<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1312 \/ 254;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataflow-jobs.jpg\" title=\"Dataflow jobs\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"254\" width=\"1312\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataflow-jobs.jpg#ZgotmplZ\" alt=\"Dataflow jobs\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-2-check-that-the-mutator-created-the-table\">Step 2: Check that the mutator created the table<\/h3>\n<p>To check if the mutator\u2019s <code>create<\/code> command worked, go to your <a href=\"https:\/\/console.cloud.google.com\/bigquery\">BigQuery dataset<\/a>, and expand it. You should see a table named <code>pageviews<\/code> under it, and the table should be populated with a number of columns, such as <code>app_id<\/code>, <code>etl_tstamp<\/code>, etc. These columns are the \u201cdefault\u201d atomic data columns Snowplow uses.<\/p>\n<div style=\"aspect-ratio: 1278 \/ 442;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/bigquery-columns.jpg\" title=\"BigQuery columns\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"442\" width=\"1278\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/bigquery-columns.jpg#ZgotmplZ\" alt=\"BigQuery columns\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-3-check-that-the-mutator-creates-additional-columns-where-necessary\">Step 3: Check that the mutator creates additional columns where necessary<\/h3>\n<p>Since you are collecting data with the Google Analytics tracker, you should shortly see a bunch of new columns added to the table description.<\/p>\n<div style=\"aspect-ratio: 1113 \/ 285;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/google-analytics-columns.jpg\" title=\"Google Analytics columns\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"285\" width=\"1113\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/google-analytics-columns.jpg#ZgotmplZ\" alt=\"Google Analytics columns\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-4-check-that-the-bigquery-loader-dataflow-job-started\">Step 4: Check that the BigQuery loader Dataflow job started<\/h3>\n<p>Visit the <a href=\"https:\/\/console.cloud.google.com\/dataflow\">Dataflow job list<\/a> again. You should see something like <code>main-root-XXXXX-YYYYY<\/code> in the list with the status <strong>Running<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1312 \/ 254;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataflow-jobs.jpg\" title=\"Dataflow jobs\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"254\" width=\"1312\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/dataflow-jobs.jpg#ZgotmplZ\" alt=\"Dataflow jobs\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"step-5-check-that-data-is-flowing-into-your-bigquery-table\">Step 5: Check that data is flowing into your BigQuery table<\/h3>\n<p>Visit your <a href=\"https:\/\/console.cloud.google.com\/bigquery\">BigQuery dataset\/table<\/a> again, and select the <code>pageviews<\/code> table.<\/p>\n<p>In the Query editor, type the following query and press <strong>Run query<\/strong>:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-sql\" data-lang=\"sql\"><span style=\"color:#00a\">SELECT<\/span> * <span style=\"color:#00a\">FROM<\/span> `your_dataset_name.pageviews`<\/code><\/pre>\n<\/div>\n<p>Where <code>your_dataset_name<\/code> should be replaced with the name of your dataset. You should see a bunch of results returned after the query is complete.<\/p>\n<div style=\"aspect-ratio: 1157 \/ 654;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/sql-query.jpg\" title=\"BigQuery sample query\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"654\" width=\"1157\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/sql-query.jpg#ZgotmplZ\" alt=\"BigQuery sample query\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"troubleshooting\">Troubleshooting<\/h3>\n<p>You can visit the <a href=\"https:\/\/console.cloud.google.com\/compute\/instances\">VM instances<\/a> list and SSH directly into the ETL instance.<\/p>\n<div style=\"aspect-ratio: 1281 \/ 231;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/instance-ssh.jpg\" title=\"SSH into instance\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"231\" width=\"1281\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/instance-ssh.jpg#ZgotmplZ\" alt=\"SSH into instance\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>There, you can visit <code>\/var\/log\/<\/code>, and open the file <code>daemon.log<\/code> for editing with e.g.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">$ pico \/var\/log\/daemon.log<\/code><\/pre>\n<\/div>\n<p>It\u2019s a full log of the the instance\u2019s processes. You\u2019ll need to run through the log and see where the error is.<\/p>\n<p>There are many things that might have gone wrong, but the most common (in my own experience) are:<\/p>\n<ol>\n<li>\n<p>Incorrect access scopes in the instance template &#8211; double-check <a href=\"#step-1-create-the-instance-template\">here<\/a>.<\/p>\n<\/li>\n<li>\n<p>Typo in <code>iglu_resolver.json<\/code> or <code>bigquery_config.json<\/code>, or forgot to add them to the correct cloud storage bucket &#8211; double-check <a href=\"#step-4-create-the-iglu-resolver-json-configuration\">resolver here<\/a> and BigQuery config <a href=\"#step-6-create-the-bigquery-configuration-file\">here<\/a>.<\/p>\n<\/li>\n<li>\n<p>Typo in the startup script &#8211; double-check <a href=\"#step-1-create-the-instance-template\">here<\/a>.<\/p>\n<\/li>\n<li>\n<p>Collector not running &#8211; double-check <a href=\"#step-5-testing-everything\">here<\/a>.<\/p>\n<\/li>\n<li>\n<p>Impatience &#8211; wait 10-15 minutes before trying all the steps above again. It\u2019s possible you\u2019re just too hasty and the virtual machine instance hasn\u2019t started up properly yet.<\/p>\n<\/li>\n<\/ol>\n<h2 id=\"final-thoughts\">Final thoughts<\/h2>\n<p>It\u2019s a long guide, but I still worry if it\u2019s detailed enough. I\u2019m a bit ashamed at not being able to tell you exactly how you should scale and group your instances, or how you should optimize your load balancing system. But these are things you need to experiment with by yourself, or with the help of a seasoned data engineer. Remember that you can also utilize the <a href=\"https:\/\/snowplowanalytics.com\/products\/snowplow-insights\/\">Snowplow Insights service<\/a> to help you set things up and manage the pipeline!<\/p>\n<p>I very much prefer the GCP user interface over AWS &#8211; there\u2019s a <strong>flow<\/strong> to things, and related resources are grouped and linked together in a logical way. It makes moving from one service to another much smoother.<\/p>\n<p>Setting up the pipeline is fun (if you\u2019re into that kind of thing), but it does result in a huge data table full of columns and rows of often undecipherable data. One thing I tend to forget is that the real work begins after the pipeline is created. Making sense of the data requires, at the very least, visualization in a tool such as Data Studio, but you might want to look at what Snowplow has to say about data modelling, too.<\/p>\n<p>I think Snowplow\u2019s done an impressive job of making it possible create your own analytics pipeline with a fairly manageable cost. There are things that could have been done far more smoothly, such as utilizing <a href=\"https:\/\/cloud.google.com\/cloud-build\/docs\/quickstart-docker\">Docker<\/a> containers <a href=\"https:\/\/github.com\/snowplow-incubator\/snowplow-bigquery-loader\/wiki\/Setup-guide#docker-support\">offered by Snowplow<\/a>.<\/p>\n<p>There are many moving parts in the pipeline. For me, personally, the race between different services enriching, shredding, mutating, and loading each incoming request is still a bit of a mystery. Snowplow has services (such as the <a href=\"https:\/\/github.com\/snowplow-incubator\/snowplow-bigquery-loader\/wiki\/Setup-guide#forwarder\">BigQuery forwarder<\/a>) that handle these problems, but setting it up was beyond the scope of this guide.<\/p>\n<p>There\u2019s also the whole world of enrichments that I deliberately skipped. It\u2019s painful to see the columns for e.g. geographical data showing <code>null<\/code>, but I\u2019ll leave those for another guide.<\/p>\n<p>One thing to note is that you should keep a keen eye on projected and actualized costs. Once you\u2019ve managed to build the pipeline itself, your next job as a data engineer is to look for optimization opportunities. With a scalable cloud infrastructure, it\u2019s important to understand the often strenuous relationship between latency, scale, and cost.<\/p>\n<div style=\"aspect-ratio: 1297 \/ 527;\" class=\"figure \">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2018\/12\/cost.jpg\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"527\" width=\"1297\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2018\/12\/cost.jpg#ZgotmplZ\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>I hope this guide has been useful. I also hope it doesn\u2019t become outdated too soon.<\/p>\n<p>Huge thanks to <strong>Snowplow<\/strong> for providing me with help and resources throughout writing this guide. I\u2019m especially grateful to <strong>Yali Sassoon<\/strong> and <strong>Joao Luis<\/strong> for their support.<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Last updated 18 Jan 2019: Added details about the free tier limitations, and showed how to avoid the Dataflow jobs auto-scaling out of control. I\u2019m (still) a huge fan of Snowplow Analytics. Their open-source, modular approach to DIY analytics pipelines has inspired me two write articles about them, and to host a meetup in Helsinki. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":102529,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[12564,1531,8821,970,43260],"dealstore":[],"offerexpiration":[],"class_list":["post-102527","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-cloud","tag-google","tag-install","tag-platform","tag-snowplow"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Install Snowplow On The Google Cloud Platform - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=102527\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Install Snowplow On The Google Cloud Platform - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"Last updated 18 Jan 2019: Added details about the free tier limitations, and showed how to avoid the Dataflow jobs auto-scaling out of control. I\u2019m (still) a huge fan of Snowplow Analytics. Their open-source, modular approach to DIY analytics pipelines has inspired me two write articles about them, and to host a meetup in Helsinki. [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=102527\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-02-22T01:52:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1074\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"43 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=102527#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=102527\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Install Snowplow On The Google Cloud Platform\",\"datePublished\":\"2025-02-22T01:52:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=102527\"},\"wordCount\":7884,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=102527#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg\",\"keywords\":[\"Cloud\",\"Google\",\"Install\",\"Platform\",\"Snowplow\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=102527#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=102527\",\"url\":\"https:\/\/fivemor.com\/?p=102527\",\"name\":\"Install Snowplow On The Google Cloud Platform - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=102527#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=102527#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg\",\"datePublished\":\"2025-02-22T01:52:40+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=102527#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=102527\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=102527#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg\",\"width\":2560,\"height\":1074},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=102527#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Install Snowplow On The Google Cloud Platform\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Install Snowplow On The Google Cloud Platform - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=102527","og_locale":"en_US","og_type":"article","og_title":"Install Snowplow On The Google Cloud Platform - Som2ny Network","og_description":"Last updated 18 Jan 2019: Added details about the free tier limitations, and showed how to avoid the Dataflow jobs auto-scaling out of control. I\u2019m (still) a huge fan of Snowplow Analytics. Their open-source, modular approach to DIY analytics pipelines has inspired me two write articles about them, and to host a meetup in Helsinki. [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=102527","og_site_name":"Som2ny Network","article_published_time":"2025-02-22T01:52:40+00:00","og_image":[{"width":2560,"height":1074,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"43 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=102527#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=102527"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Install Snowplow On The Google Cloud Platform","datePublished":"2025-02-22T01:52:40+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=102527"},"wordCount":7884,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=102527#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg","keywords":["Cloud","Google","Install","Platform","Snowplow"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=102527#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=102527","url":"https:\/\/fivemor.com\/?p=102527","name":"Install Snowplow On The Google Cloud Platform - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=102527#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=102527#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg","datePublished":"2025-02-22T01:52:40+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=102527#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=102527"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=102527#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/02\/snowplow-gcp-scaled.jpg","width":2560,"height":1074},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=102527#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Install Snowplow On The Google Cloud Platform"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/102527","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=102527"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/102527\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/102529"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=102527"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=102527"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=102527"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=102527"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=102527"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}