Jekyll2023-04-08T02:37:32+00:00/feed.xmlnetworkandcodeblog > practice > share | [ network coding cloud… ] This site holds notes/posts related to technical topics such as cloud, containers, networking, automation, and so on. The tech keywords for search in the site include Kubernetes, OpenStack, Ansible, Python, etc.2023-04-08T02:37:32+00:002023-04-08T02:37:32+00:00/2023/04/08/2022-11-20-subscribe-to-aws-iot-on-influxdb<p>This post first appeared on <a href="https://dev.to/aws-builders/harperdb-on-eks-1bcb">dev.to</a></p> <p>Hey :wave:, here is my first post after the community builder renewal :satisfied:. In this post, we would send some MQTT messages in line protocol format to AWS IoT, which would then get ingested to InfluxDB via a native MQTT subscription.</p> <p>InfluxDB usually relies on systems such as Telegraf or some client programs to send metrics to it, however here with the native subscription, we don’t need the intermediate system.</p> <p>As a prerequisite, follow this <a href="https://dev.to/aws-builders/aws-iot-pubsub-over-mqtt-1oig">post</a> to understand how to publish messages from a Python based MQTT client to the MQTT endpoint in AWS.</p> <p>We would be making change to the publisher code, as InfluxDB looks for messages in a different format called as the line protocol.</p> <p>Refer to this <a href="https://docs.influxdata.com/influxdb/cloud/reference/syntax/line-protocol/#:~:text=InfluxDB%20uses%20line%20protocol%20to,timestamp%20of%20a%20data%20point.&amp;text=Lines%20separated%20by%20the%20newline,a%20single%20point%20in%20InfluxDB.">link</a> for more information on the lineprotocol.</p> <p>A sample message in our case would be like <em>temp_sensor,id=client-d36a601f-0b0e-4ba2-9d54-45feec236deb,room=lobby temp=30 1668930716251915510</em></p> <p>Where temp_sensor is the measurement name, the tags are id and room, the field is temp. Note the timestamp above is in unix timestamp format in nanoseconds. Note that the tags &amp; timestamp are optional in line protocol, and you can have more than one filed. The timestamp will default to the current time if not provided.</p> <p>Our publisher code would look like below.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nc:~/environment/iot $ cat publish-line-protocol.py # import vars from connect.py from connect import args, client_id, mqtt_connection from awscrt import mqtt import json, random, time while True: rooms = [ 'bed-room', 'hall', 'kitchen', 'living-room', 'lobby' ] room = random.choice(rooms) # set random temperature between 24 and 32 (this is celsius range) temp = random.randrange(24, 32) # set timestamp in nanoseconds now = time.time_ns() # form the message message = f'temp_sensor,id={client_id},room={room} temp={temp} {now}' # publish the message mqtt_connection.publish( topic=args.topic, payload= message, qos=mqtt.QoS.AT_LEAST_ONCE ) print(f'Message published: {message}') time.sleep(1) </code></pre></div></div> <p>Let’s run the code.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nc:~/environment/iot $ python publish-line-protocol.py --ep $IOT_DEV_EP --pubcert pub-cert.pem --pvtkey pvt-key.pem --cacert ca-cert.pem --topic temperature client-7219bca1-8c06-41c8-b69c-783c3ee85a41 is connected! Message published: temp_sensor,id=client-7219bca1-8c06-41c8-b69c-783c3ee85a41,room=hall temp=29 1668932805475181112 Message published: temp_sensor,id=client-7219bca1-8c06-41c8-b69c-783c3ee85a41,room=hall temp=24 1668932806476308869 Message published: temp_sensor,id=client-7219bca1-8c06-41c8-b69c-783c3ee85a41,room=lobby temp=24 1668932807477465026 </code></pre></div></div> <p>Press Ctrl C when you want to stop the code.</p> <p>Now, let’s launch InfluxDB, you may get a cloud subscription from the AWS <a href="https://aws.amazon.com/marketplace/pp/prodview-4e7raoxoxsl4y?ref_=unifiedsearch">marketplace</a>, or directly from the <a href="https://cloud2.influxdata.com/">influxdata</a> portal.</p> <p>On InfluxDB, Goto Load Data &gt; Native Subscriptions and create a new suscription. Set the subscription name as <em>temp_sensor</em>. Enter some description like <em>Messages from AWS MQTT</em>. Go to Security details &gt; certificate and copy/paste the ca certificate, private key and public certificate downloaded from AWS while creating the thing in IoT. Set the topic as <em>temperature</em> because that’s where we were publishing the messages to. And then set the write destination to a bucket, which can be created if it doesn’t exist yet. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s1bdzfhaeh1kfov6asiw.png" alt="Create bucket for subscription in InfluxDB" /></p> <p>That’s it, save the subscription.</p> <p>We can now visit the data explorer, and see the graph for the data sent. If we hover over the graph, we should be able to see the values for each of the tags(rooms) that have different colors. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejpz8urw9lgitw17rdpt.png" alt="Data explorer on InfluxDB" /></p> <p>This way we can send MQTT metrics to InfluxDB with subscriptions and visualize those. A couple of things before finishing, you may stop the subscription, when you don’t want to keep it running, <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju4u5524akcfd8amo4g3.png" alt="Stop subscription link in InfluxDB" /></p> <p>and there is a notifications link on the subscription that should help you with logs for troubleshooting errors. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yu1lclrg0z52sk5y4c1l.png" alt="Notifications link in InfluxDB subscription" /></p> <p>Thank you for reading !!!</p>Query HarperDB’s REST API via Apollo GraphQL2023-02-17T00:00:00+00:002023-02-17T00:00:00+00:00/apolloserver,/graphql,/harperdb,/rest/2023/02/17/query-harperdbs-rest-api-via-apollo-graphql<p>**This post first appeared on <a href="https://dev.to/networkandcode/query-harperdbs-rest-api-via-apollo-graphql-21n1">dev.to</a></p> <h2 id="introduction">Introduction</h2> <p>Hi there :wave:, in this post, we shall launch an Apollo server with node, and perform some read &amp; write operations from the Apollo studio sandbox to a HarperDB instance. Having some graphql fundamentals would be beneficial to understand better, though some parts of the code would be explained.</p> <h2 id="studio">Studio</h2> <p>I have an account in HarperDB <a href="https://studio.harperdb.io">studio</a> and have setup an organisation and a free trier instance there. Once logged in to the instance, the config tab would give the instance credentials we would need to connect from our code. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ou6rpws6ql4of4hqi773.png" alt="Instance config" /></p> <p>Note that the studio credentials(email/password) are different from the instance credentials(username/password). You can have more than one instance in the studio. You can have only one free tier instance though across organisations.</p> <p>Copy the instance url and the basic token from the config shown in the image above, as we would need those while sending API calls.</p> <h2 id="clone">Clone</h2> <p>Clone the code from GitHub.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone git@github.com:networkandcode/fruits-apollo-hdb.git </code></pre></div></div> <h2 id="dependencies">Dependencies</h2> <p>The <a href="https://github.com/networkandcode/fruits-apollo-hdb/blob/main/server/package.json#L13-L18">dependencies</a> for the code are as follows.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "dependencies": { "@apollo/datasource-rest": "^5.0.2", "@apollo/server": "^4.3.3", "dotenv": "^16.0.3", "graphql": "^16.6.0" } </code></pre></div></div> <p>We can install these.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm i </code></pre></div></div> <p>About the packages above:</p> <ul> <li><code class="language-plaintext highlighter-rouge">@apollo/datasource-rest</code> is used to make rest api calls from apollo to endpoints such as HarperDB’s instance url.</li> <li><code class="language-plaintext highlighter-rouge">@apollo/server</code> is used to launch the apollo server itself.</li> <li><code class="language-plaintext highlighter-rouge">dotenv</code> is used to read env vars.</li> <li><code class="language-plaintext highlighter-rouge">graphql</code> is the core graphql library.</li> </ul> <p>You may refer to this <a href="https://www.apollographql.com/docs/apollo-server/getting-started/#step-2-install-dependencies">guide</a> for more info.</p> <h2 id="variable">Variable</h2> <p>Add an <code class="language-plaintext highlighter-rouge">.env</code> file, we just need one variable.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat .env HDB_INSTANCE_URL=https://&lt;instance-name&gt;.harperdbcloud.com </code></pre></div></div> <h2 id="start">Start</h2> <p>There is a start script with invokes the index file.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat package.json| grep start "start": "node index.js" </code></pre></div></div> <p>Good to start the server</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm start &gt; server@1.0.0 start &gt; node index.js 🚀 Server ready at: http://localhost:4000/ </code></pre></div></div> <h2 id="apollo-studio">Apollo studio</h2> <p>Once the server is started we should be able to see the sandbox studio on the browser at http://localhost:4000/. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9t1050mz0k32nkiaah0.png" alt="Apollo studio" /></p> <h2 id="token">Token</h2> <p>Let’s set the authorisation header, you may paste the basic token obtained from HarperDB studio here. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8078w2kbat6uzcw939on.png" alt="Basic auth token" /></p> <p>We are setting this <a href="https://github.com/networkandcode/fruits-apollo-hdb/blob/main/server/index.js#L20">token</a> in the server context.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> const token = req.headers.authorization; </code></pre></div></div> <p>Which would be <a href="https://github.com/networkandcode/fruits-apollo-hdb/blob/main/server/datasource.js#L46-L48">passed</a> to HarperDB while the REST API call is being made.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> willSendRequest(_path, request) { request.headers['authorization'] = this.token; }; </code></pre></div></div> <h2 id="code">Code</h2> <p>Let’s see what code files we have and their purpose.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls *.js datasource.js index.js resolvers.js schema.js </code></pre></div></div> <ul> <li><code class="language-plaintext highlighter-rouge">datasource.js</code> is where we are making calls to HarperDB via REST API.</li> <li><code class="language-plaintext highlighter-rouge">index.js</code> has code that wraps the schema and resolvers in the server and starts it.</li> <li><code class="language-plaintext highlighter-rouge">resolvers.js</code> contains resolvers that resolves all of the queries and mutations defined in <code class="language-plaintext highlighter-rouge">schema.js</code> by sending calls to HarperDB API defined in <code class="language-plaintext highlighter-rouge">datasource.js</code></li> <li><code class="language-plaintext highlighter-rouge">schema.js</code> contains our graphql schema that has built in types Query and Mutation as well as other user defined types and inputs.</li> </ul> <h3 id="schema">Schema</h3> <p>Our graphql <a href="https://www.apollographql.com/docs/apollo-server/schema/schema/">schema</a> is a collection of type definitions. And hence it’s common to use typeDefs as our schema variable.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat schema.js const typeDefs = ` input CreateTableBody { schema: String! table: String! hash_attribute: String! } input FruitInput { name: String! "calories per 100 gm" calories: Int! } type Query { fruits(schema: String!, table: String!): [Fruit] fruit(schema: String!, table: String!, name: String!): [Fruit] } type Mutation { createSchema(schema: String!): ApiResponse! createTable(body: CreateTableBody!): ApiResponse! insertRecords(schema: String!, table: String!, records: [FruitInput!]! ): ApiResponse! } type Fruit { id: ID! name: String! calories: Int! } type ApiResponse { status: Int! message: String! } `; export default typeDefs; </code></pre></div></div> <p>In the schema, there are two built in types Query and Mutation. Query is meant for read operations where as Mutation refers to operations that involve create, update or deletion of data.</p> <p>In Query, we have defined two fields fruits and fruit. <code class="language-plaintext highlighter-rouge">fruits</code> takes two arguments schema and table, both are strings and are mandatory or non nullable as denoted with <code class="language-plaintext highlighter-rouge">!</code>. Both fruits and fruit fields return same type of data denoted by [Fruit], this indicates an array of records, where each record is of type Fruit, this is not a built in type like String or Int, hence we have defined it as type Fruit. The type definition for Fruit denotes it’s structure, it would have three fields id, name and calories which are of types ID, String and Int respectively, and all 3 fields are non nullable. To summarise, we can have two Query operations one is fruits and the other is fruit both return a list of fruits(records from HarperDB).</p> <p>Like wise for Mutation, we have three fields createSchema, createTable and insertRecords with their respective arguments, and all of them would return the same type of response as denoted by ApiResponse, that would have only 2 fields status and message.</p> <p>Note that when a field type is not built in, we would define it with type, like we have defined type <code class="language-plaintext highlighter-rouge">Fruit</code> and type <code class="language-plaintext highlighter-rouge">ApiResponse</code> in our schema. Likewise, when an argument type is not built in, we would delete it with input, like we have defined input <code class="language-plaintext highlighter-rouge">CreateTableBody</code> and <code class="language-plaintext highlighter-rouge">FruitInput</code> in our schema.</p> <h3 id="datasource">Datasource</h3> <p>Here, we create a new class by name HdpApi that inherits from RESTDataSource. Note that we obtain the baseURL from the env var.</p> <p>The willSendRequest function is used to add the header while calls are being sent to the base url. There are just two functions we are defining here, as in HarperDB all the calls are of type <a href="https://api.harperdb.io">POST</a>, and there mainly two types of calls, one that sends SQL style statements and the other which they call as NoSQL operations.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat datasource.js import { RESTDataSource } from '@apollo/datasource-rest'; class HdbApi extends RESTDataSource { baseURL = process.env.HDB_INSTANCE_URL; constructor(options) { super(options); this.token = options.token; }; async sqlQuery(body) { // for read operations return await this.post( '', { body, } ).then((res) =&gt; { return res; }) }; async noSqlQuery(body) { return await this.post( '', { body, } ).then((res) =&gt; { const { message } = res; return { status: 200, message } }).catch((err) =&gt; { const { status, body } = err.extensions.response; const message = body.error; return { status, message } }); }; willSendRequest(_path, request) { request.headers['authorization'] = this.token; }; }; export default HdbApi; </code></pre></div></div> <h3 id="index">Index</h3> <p>In the index file, we import the modules first. And then, we import the schema, resolvers and our datasource. We use dotenv to read the var defines in <code class="language-plaintext highlighter-rouge">.env</code>.</p> <p>We then create an Apollo server instance that takes two arguments: schema and resolvers.</p> <p>We start the server finally, and we give options such as the listening port, and the context.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat index.js import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; import dotenv from 'dotenv'; import typeDefs from './schema.js'; import resolvers from './resolvers.js'; import HdbApi from './datasource.js'; dotenv.config(); console.log(process.env) const server = new ApolloServer({ typeDefs, resolvers, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, context: async({ req }) =&gt; { const { cache } = server; const token = req.headers.authorization; return { dataSources: { hdbApi: new HdbApi({ cache, token }), } } } }); console.log(`🚀 Server ready at: ${url}`); </code></pre></div></div> <p>The context returns a dataSources dictionary that has a hdpApi key in it, and this points to a new HdpApi object which is defined in the datasource.</p> <p>We can refer to this context in the resolvers.</p> <h3 id="resolvers">Resolvers</h3> <p>There are 2 main resolvers for the built in types Query and Mutation. Inside each resolver, we have a function for each field inside the type. In our schema we have defines two types fruits and fruit, hence there are two such functions inside the Query resolver too. Similarly we have three functions for the Mutations: createSchema, createTable and insertRecords.</p> <p>What these functions return will come as the result in the Apollo Studio for our GraphQL operation. The return statements are referring to contextValue which is the context function we defined in index. So, the resolvers are able to use the context defined in the server, which subsequently points to the api calls we defined in the datasource.</p> <p>Note that there are four <a href="https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-arguments">arguments</a> for each function: parent, args, contextValue and info. We would only need the 2nd and 3rd args for this exercise though. We have already seen about <code class="language-plaintext highlighter-rouge">contextValue</code> above, and <code class="language-plaintext highlighter-rouge">args</code> refer to the variables we set while running an operation in the Apollo studio.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat resolvers.js const resolvers = { Query: { fruits: (_, args, contextValue) =&gt; { const { schema, table } = args; const body = { "operation": "sql", "sql": `SELECT * FROM ${schema}.${table}`, } return contextValue.dataSources.hdbApi.sqlQuery(body); }, fruit: (_, args, contextValue) =&gt; { const { schema, table, name } = args; const body = { "operation": "sql", "sql": `SELECT * FROM ${schema}.${table} where name = "${name}"` }; return contextValue.dataSources.hdbApi.sqlQuery(body); }, }, Mutation: { // There are four args parent, args, contextValue and info createSchema: (_, { schema }, contextValue) =&gt; { const body = { operation: "create_schema", schema, }; return contextValue.dataSources.hdbApi.noSqlQuery(body); }, createTable: (parent, { body }, contextValue, info) =&gt; { body = { ...body, operation: "create_table", } return contextValue.dataSources.hdbApi.noSqlQuery(body); }, insertRecords: (parent, { schema, table, records }, contextValue, info) =&gt; { const body = { operation: "insert", schema, table, records } return contextValue.dataSources.hdbApi.noSqlQuery(body); } } }; export default resolvers; </code></pre></div></div> <h3 id="operations">Operations</h3> <p>Ok so all the setup is done. We are good to go with the Query / Mutation operations. Let’s start with Mutations.</p> <h2 id="create-schema">Create schema</h2> <p>Let’s try creating a schema in HarperDB with the mutation in Apollo GraphQl. Make a note that this schema is not the GraphQL schema, it’s the schema in HarperDB. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbuekkn64gvtyyff6ayn.png" alt="Create schema" /></p> <p>Building the operation and variables is easy, you just need to click on the plus sign next to the operation and field.</p> <p>We can validate this in the browse section of HarperDB studio. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/12zz0f8lmwd59rskcihg.png" alt="Schema created" /></p> <p>If we try to run the mutation again on Apollo studio, we should see an error message that says the schema already exists. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lumt7n23m9pfaz67v3fn.png" alt="Schema exists" /></p> <h2 id="create-table">Create table</h2> <p>The schema is created, we can now try to add a table in this schema. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01gfnkjb6a5cb80ch5no.png" alt="Create table" /></p> <p>The operation is ready however we need to fill the data for the body variable, for which we can click on the argument. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6bxpyvayymrbhp0hf28.png" alt="Body argument" /></p> <p>We should be able to see all the input data fields defined for the argument. We can add them all with the plus button. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5dt21yp35da4ywvxqp3.png" alt="Input data fields for body" /></p> <p>The variable should be populated for us with null fields. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orhxwkos5imyowrclpra.png" alt="Variable with null values" /></p> <p>We can replace null with the actual values. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mohdqjvk43j9ynxv8val.png" alt="Variable with actual values" /></p> <p>We can run the operation and see the response. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qziq2daawq6jzt38jb8l.png" alt="Table created" /></p> <p>If you try again, you should see an error message. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30wz761toujajtokzlvr.png" alt="Table already exists" /></p> <h2 id="add-entries-to-table">Add entries to table</h2> <p>The operation in this case would be:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mutation Mutation($schema: String!, $table: String!, $records: [FruitInput!]!) { insertRecords(schema: $schema, table: $table, records: $records) { message status } } </code></pre></div></div> <p>The variables would be:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "schema": "myschema", "table": "fruits", "records": [ { "name": "Apple", "calories": 52 }, { "name": "Banana", "calories": 89 } ] } </code></pre></div></div> <p>And the response as follows, after running the mutation:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "data": { "insertRecords": { "message": "inserted 2 of 2 records", "status": 200 } } } </code></pre></div></div> <h2 id="retrieve-records">Retrieve records</h2> <p>Let’s try a query operation this time, for retrieving all records from the table.</p> <p>The operation is:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>query Fruits($schema: String!, $table: String!) { fruits(schema: $schema, table: $table) { calories id name } } </code></pre></div></div> <p>The variables are:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "schema": "myschema", "table": "fruits" } </code></pre></div></div> <p>And the result of the query operation would be:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "data": { "fruits": [ { "calories": 52, "id": "3963a29c-d696-44ae-a3d8-e11de48bf339", "name": "Apple" }, { "calories": 89, "id": "eec06bc5-85cf-48eb-87d2-89a25fd9f3c1", "name": "Banana" } ] } } </code></pre></div></div> <h2 id="retrieve-selective-records">Retrieve selective records</h2> <p>And one final query to retrieve selective records. The query is:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>query Query($schema: String!, $table: String!, $name: String!) { fruit(schema: $schema, table: $table, name: $name) { calories id name } } </code></pre></div></div> <p>Variables:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "schema": "myschema", "table": "fruits", "name": "Apple" } </code></pre></div></div> <p>And result of the query:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "data": { "fruit": [ { "calories": 52, "id": "3963a29c-d696-44ae-a3d8-e11de48bf339", "name": "Apple" } ] } } </code></pre></div></div> <h2 id="summary">Summary</h2> <p>Thus we were able to access the Apollo studio on the browser and send queries and mutations to HarperDB. HarperDB is considering GraphQL functionality on their roadmap. Check out their <a href="https://feedback.harperdb.io/">Feedback Board</a> and give it an upvote if you think it would be helpful!</p> <p>Feel free to ask questions if the flow is not clear, and correct if there are any mistakes. Thank you for reading !!!</p> <p>Image credit: <a href="https://source.unsplash.com/featured/?coding">unsplash</a></p>**This post first appeared on dev.to Introduction Hi there :wave:, in this post, we shall launch an Apollo server with node, and perform some read &amp; write operations from the Apollo studio sandbox to a HarperDB instance. Having some graphql fundamentals would be beneficial to understand better, though some parts of the code would be explained.HarperDB Helm chart on Artifact Hub2023-02-03T00:00:00+00:002023-02-03T00:00:00+00:00/eks,/harperdb,/helm,/kubernetes/2023/02/03/harperdb-helm-chart-on-artifact-hub<p>This post first appeared on <a href="https://dev.to/networkandcode/harperdb-helm-chart-on-artifact-hub-3066">dev.to</a></p> <h2 id="introduction">Introduction</h2> <p>Hey :wave:, in this post, we shall see how to create a helm chart for HarperDB based on the boilerplate helm chart with the helm cli, lint/dry run it and push it to the artifact <a href="https://artifacthub.io">hub</a>, and then reuse it to install a helm release on a Kubernetes cluster. You can get the helm chart used in this post from this <a href="https://github.com/networkandcode/harperdb-deployments/tree/main/helm-charts/standard/harperdb">link</a>.</p> <p>You may check this <a href="https://dev.to/aws-builders/harperdb-with-helm-on-eks-3fb9">post</a> if you are looking to install harperdb with a custom, minimal helm chart.</p> <h2 id="search">Search</h2> <p>As of this writing, there is no chart available on artifact hub for harperdb, the screenshot below should validate that. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idysqmfnelkdj60fe0ii.png" alt="HarperDB chart not found in artifact hub" /></p> <p>So our goal is to push the harpderdb chart to artifact hub, so that the search result shows an entry.</p> <p>Let’s give a try on ChatGPT. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2gd3nvxzwcgdgbut9h0y.png" alt="Search for harperdb helm chart on ChatGPT" /></p> <p>We could also search from the helm cli, to see if there exists a chart for harperdb. For which you have to first install <a href="https://helm.sh/docs/intro/install/">helm</a> in your system.</p> <p>On Mac, it could be installed as follows.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install helm </code></pre></div></div> <p>Now, we can search for the chart.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm search hub harperdb No results found </code></pre></div></div> <p>This result matches with the search we did on website.</p> <h2 id="chart">Chart</h2> <p>Ok, we can create our chart. Let’s first create a boilerplate chart with the name harperdb.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm create harperdb Creating harperdb </code></pre></div></div> <p>A chart is created for us, it’s nothing but a directory with a specific layout.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls -R harperdb Chart.yaml charts templates values.yaml harperdb/charts: harperdb/templates: NOTES.txt deployment.yaml ingress.yaml serviceaccount.yaml _helpers.tpl hpa.yaml service.yaml tests harperdb/templates/tests: test-connection.yaml </code></pre></div></div> <p>Let’s make a few changes.</p> <p>Change the appVersion, I am going to use the latest version of harperdb found in the <a href="https://hub.docker.com/r/harperdb/harperdb/tags">tags</a> section at docker hub.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat harperdb/Chart.yaml| grep appVersion appVersion: "1.16.0" $ sed -i 's/appVersion: "1.16.0"/appVersion: "4.0.4"/g' harperdb/Chart.yaml $ cat harperdb/Chart.yaml| grep appVersion appVersion: "4.0.4" </code></pre></div></div> <p>We don’t need any sub charts for now, so we can remove that directory.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rm -r harperdb/charts </code></pre></div></div> <p>We can also remove the tests directory.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ rm -r harperdb/templates/tests </code></pre></div></div> <h2 id="values">Values</h2> <p>We need to make a few modifications in the values file.</p> <h3 id="image">Image</h3> <p>Let’s set the image.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat harperdb/templates/deployment.yaml | grep image: image: ":" </code></pre></div></div> <p>The tag can come from the appVersion, we just need to set the image repository in values. By default, it would have nginx.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep repository: harperdb/values.yaml repository: nginx </code></pre></div></div> <p>Edit values by replacing nginx with harperdb/harperdb.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sed -i 's#repository: nginx#repository: harperdb/harperdb#g' harperdb/values.yaml $ grep repository: harperdb/values.yaml repository: harperdb/harperdb </code></pre></div></div> <h3 id="service">Service</h3> <p>HarperDB uses the port 9925 for the rest API, we would be exposing only this here, though there are other ports like 9926, 9932 for custom functions, clustering etc.</p> <p>In our chart they are setting the service port in <code class="language-plaintext highlighter-rouge">.Values.service.port</code> and the same port is used as port the container port too, we can stick with that for simplicity.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -ir service.port harperdb harperdb/templates/NOTES.txt: echo http://$SERVICE_IP: harperdb/templates/ingress.yaml:harperdb/templates/service.yaml: - port: harperdb/templates/deployment.yaml: containerPort: </code></pre></div></div> <p>Let’ change the service port in values.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep port: harperdb/values.yaml port: 80 $ sed -i 's/port: 80/port: 9925/g' harperdb/values.yaml $ grep port: harperdb/values.yaml port: 9925 </code></pre></div></div> <p>Also set the service type to LoadBlancer</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ sed -i 's/type: ClusterIP/type: LoadBalancer/g' harperdb/values.yaml $ cat harperdb/values.yaml --TRUNCATED-- service: type: LoadBalancer port: 9925 --TRUNCATED </code></pre></div></div> <h3 id="security-context">Security context</h3> <p>Modify the pod security context, you may check this <a href="https://dev.to/aws-builders/harperdb-on-eks-1bcb">post</a> to know why we used 1000 as the fsGroup.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -i -A 2 podSecurityContext harperdb/values.yaml podSecurityContext: fsGroup: 1000 </code></pre></div></div> <h3 id="resources">Resources</h3> <p>Similary set the cpu and memory requirements in values.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -A 6 resources harperdb/values.yaml resources: limits: cpu: 500m memory: 1Gi requests: cpu: 100m memory: 128Mi </code></pre></div></div> <h2 id="secret">Secret</h2> <p>The chart we created doesn’t have a secret manifest, we can create it. This manifest follows standards similar to the service account manifest.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; harperdb/templates/secret.yaml apiVersion: v1 kind: Secret metadata: name: labels: stringData: EOF </code></pre></div></div> <p>We can set the appropriate values for the secret manifest.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt;&gt; harperdb/values.yaml secret: entries: HDB_ADMIN_USERNAME: admin HDB_ADMIN_PASSWORD: password12345 create: true name: harperdb EOF </code></pre></div></div> <p>We can then modify the helpers file.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt;&gt; harperdb/templates/_helpers.tpl EOF </code></pre></div></div> <h2 id="pvc">PVC</h2> <p>Likewise, there is no pvc template in the chart. So we can add that.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; harperdb/templates/pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: labels: spec: accessModes: - resources: requests: storage: EOF </code></pre></div></div> <p>Set the appropriate values for pvc.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt;&gt; harperdb/values.yaml pvc: accessMode: ReadWriteOnce create: true mountPath: /opt/harperdb/hdb name: harperdb storage: 5Gi EOF </code></pre></div></div> <p>We can then modify the helpers file.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt;&gt; harperdb/templates/_helpers.tpl EOF </code></pre></div></div> <h2 id="deployment">Deployment</h2> <p>We are going to make a few changes to the deployment manifest. So that it looks like below.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; harperdb/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: labels: spec: replicas: selector: matchLabels: template: metadata: annotations: labels: spec: imagePullSecrets: serviceAccountName: securityContext: volumes: - name: data persistentVolumeClaim: claimName: containers: - name: volumeMounts: - name: data mountPath: envFrom: - secretRef: name: securityContext: image: ":" imagePullPolicy: ports: - name: http containerPort: protocol: TCP resources: nodeSelector: affinity: tolerations: EOF </code></pre></div></div> <p>In the template above, we have injected the secrets as env vars in the envFrom section of the container. And made changes related to the volume by defining the volume in the pod and mount it in the container.</p> <h2 id="lint">Lint</h2> <p>Our chart is kinda ready…</p> <p>Ok so now let’s do the linting to see if it’s proper.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm lint harperdb ==&gt; Linting harperdb [INFO] Chart.yaml: icon is recommended 1 chart(s) linted, 0 chart(s) failed </code></pre></div></div> <p>Seems good.</p> <p>We can now try generating the kubernetes manifests, it won’t deploy yet. You can try <code class="language-plaintext highlighter-rouge">helm template harperdb</code> or <code class="language-plaintext highlighter-rouge">helm template harperdb --debug</code>, the debug flag helps debugging issues.</p> <h2 id="kubeconfig">Kubeconfig</h2> <p>Make sure you have a running kubernetes cluster. I have an EKS cluster, and I would be using the <a href="https://aws.amazon.com/cli/">aws</a> cli to update the kubeconfig.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws eks update-kubeconfig --name k8s-cluster-01 --region us-east-1 </code></pre></div></div> <p>There are two nodes in my cluster.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-192-168-22-158.ec2.internal Ready &lt;none&gt; 24d v1.23.13-eks-fb459a0 ip-192-168-38-226.ec2.internal Ready &lt;none&gt; 24d v1.23.13-eks-fb459a0 </code></pre></div></div> <p>Create a namespace with kubectl.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl create ns harperdb </code></pre></div></div> <h2 id="dry-run">Dry run</h2> <p>As the cluster is ready we can try to do a dry run installation with helm.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm install harperdb harperdb -n harperdb --dry-run --debug </code></pre></div></div> <p>If there are no errors, we can proceed to the packaging.</p> <h2 id="package">Package</h2> <p>Our chart seems good so we can package it.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm package harperdb </code></pre></div></div> <p>This should create a compressed file.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls | grep tgz harperdb-0.1.0.tgz </code></pre></div></div> <p>Here 0.1.0 refers to the chart version.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat harperdb/Chart.yaml | grep version: version: 0.1.0 </code></pre></div></div> <h2 id="repo">Repo</h2> <p>We should need a repo where we can keep this package. I am using this <a href="https://github.com/networkandcode/networkandcode.github.io">repo</a> for this purpose. And this repo is also setup with GitHub pages and the website is accessible on this <a href="https://networkandcode.github.io">URL</a>. So you may create a github repo with <a href="https://pages.github.com">pages</a> setup.</p> <p>Alright I am cloning my repo.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@github.com:networkandcode/networkandcode.github.io.git </code></pre></div></div> <p>Create a directory there for helm packages.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd networkandcode.github.io/ $ mkdir helm-packages </code></pre></div></div> <p>We can move the package we created earlier in to this directory.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mv ~/harperdb-0.1.0.tgz helm-packages/ $ ls helm-packages/ harperdb-0.1.0.tgz </code></pre></div></div> <p>We need to now create an index file.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm repo index helm-packages/ $ ls helm-packages/ harperdb-0.1.0.tgz index.yaml </code></pre></div></div> <p>The index file is populated automatically with these details.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat helm-packages/index.yaml apiVersion: v1 entries: harperdb: - apiVersion: v2 appVersion: 4.0.1 created: "2023-02-02T05:58:37.022518464Z" description: A Helm chart for Kubernetes digest: 1282e5919f2d6889f1e3dd849f27f2992d8288087502e1872ec736240dfd6ebf name: harperdb type: application urls: - harperdb-0.1.0.tgz version: 0.1.0 generated: "2023-02-02T05:58:37.020383374Z" </code></pre></div></div> <p>You can also add the artifacthub repo <a href="https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml">file</a>, to claim ownership, it’s optional though.</p> <p>Ok we can now push the changes to GitHub, note that I am directly pushing to the master branch.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git add --all $ git commit -m 'add helm package for harperdb' $ git push </code></pre></div></div> <h2 id="add-repository">Add repository</h2> <p>Our repository is ready with the package, we need to add it to artifact hub. Login to artifact hub, and go to control panel and click add repository.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwzw8x9l60ret2bxg8nl.png" alt="Add repository in artifactory" /></p> <p>The repository is added, but it takes some to process. You need to wait until there is a green tick in the Last processed section.</p> <h2 id="search-again">Search again</h2> <p>Once the repo is processed, we can repeat the searching process we did while starting this post.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mg0nct55jbye0tpmqft.png" alt="harperdb on artifact hub" /></p> <p>Well, worth to know that ChatGPT’s knowledge is cut off in 2021. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4feefqyfini6m7ynudrb.png" alt="harperdb on chatgpt" /></p> <p>Now let’s do the cli way.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm search hub harperdb --max-col-width 1000 URL CHART VERSION APP VERSION DESCRIPTION https://artifacthub.io/packages/helm/networkandcode/harperdb 0.1.0 4.0.1 A Helm chart for Kubernetes </code></pre></div></div> <p>Wow our chart is showing up…</p> <h2 id="install">Install</h2> <p>We can open the URL shown above and see the installation instructions.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbxr87siobilam20v9k4.png" alt="Installation instructions" /></p> <p>Let’s run those commands, I am going to use -n for installing it in a separate namespace.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm repo add networkandcode https://networkandcode.github.io/helm-packages "networkandcode" has been added to your repositories $ helm install my-harperdb networkandcode/harperdb --version 0.1.0 -n harperdb </code></pre></div></div> <h2 id="validate">Validate</h2> <p>Alright, so the release is installed, it’s time to validate. First let’s check the helm release status.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm list -n harperdb NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION my-harperdb harperdb 1 2023-02-02 07:14:59.586892384 +0000 UTC deployed harperdb-0.1.0 4.0.1 </code></pre></div></div> <p>Check the Kubernetes workloads.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get all -n harperdb NAME READY STATUS RESTARTS AGE pod/my-harperdb-7b66d4f7c5-xtpvw 1/1 Running 0 2m7s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-harperdb LoadBalancer 10.100.127.117 a6e762ccc1e2d482a8528a7760544761-2140283724.us-east-1.elb.amazonaws.com 9925:30478/TCP 2m9s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-harperdb 1/1 1 1 2m8s NAME DESIRED CURRENT READY AGE replicaset.apps/my-harperdb-7b66d4f7c5 1 1 1 2m9s </code></pre></div></div> <h2 id="api">API</h2> <p>We can test schema creation with an API call.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ HDB_API_ENDPOINT_HOSTNAME=$(kubectl get svc my-harperdb -n harperdb -o jsonpath={.status.loadBalancer.ingress[0].hostname}) $ curl --location --request POST http://${HDB_API_ENDPOINT_HOSTNAME}:9925 --header 'Content-Type: application/json' --header 'Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM0NQ==' --data-raw '{ "operation": "create_schema", "schema": "my-schema" }' {"message":"schema 'my-schema' successfully created"} </code></pre></div></div> <p>Note that I have parsed the hostname as it gives a hostname for the external IP in EKS. Well, so the API call was successful. Nice, we were able to accomplish the goal !!!</p> <h2 id="clean-up">Clean up</h2> <p>I am going to just clean up the helm and Kubernetes objects.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm uninstall my-harperdb -n harperdb release "my-harperdb" uninstalled $ kubectl delete ns harperdb namespace "harperdb" deleted </code></pre></div></div> <h2 id="summary">Summary</h2> <p>So we have seen some constructs of helm, and understood how we can make a chart for harperdb, push it to artifact hub and subsequently use it to install a harperdb release. Note that you can customise the chart with more options such as adding readme, enabling tests, claiming ownership for the chart, adding more harperdb specific variables w.r.t clustering, custom functions etc.</p> <p>Thank you for reading!!!</p>This post first appeared on dev.toHarperDB with Anthos on GKE2023-01-18T00:00:00+00:002023-01-18T00:00:00+00:00/anthos,/gke,/harperdb,/kubernetes/2023/01/18/harperdb-with-anthos-on-gke<p>This post first appeared on <a href="https://dev.to/networkandcode/harperdb-with-anthos-on-gke-2nef">dev.to</a></p> <h2 id="introduction">Introduction</h2> <p><a href="https://cloud.google.com/anthos">Anthos</a> is a service from Google cloud using which we can deploy and manage workloads using various options such as cloud run, GKE, self managed clusters, hybrid cloud clusters, edge based workloads and so on. In this post, we would focus on the autopilot GKE based cluster and deploy <a href="https://harperdb.io">HarperDB</a> on it with a Helm chart. Please check this <a href="https://github.com/networkandcode/harperdb-deployments/tree/main/helm-charts/minimal/harperdb">link</a> for the helm chart, we use in this post.</p> <p>Let’s get started…</p> <h2 id="enable-anthos">Enable Anthos</h2> <p>Search for Anthos on the Google cloud console and enable the Anthos API. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npyylvuegnn9szqyoouf.png" alt="Search for Anthos" /></p> <h2 id="gke">GKE</h2> <p>We would be going with an Anthos managed GKE cluster, so click on the configure option in the auto pilot option. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dp1mleldlecyempgey9o.png" alt="Auto pilot" /></p> <p>I have kept all options to their defaults. Wait for the cluster to get created. The notifications link should show the status of creation. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nvk28v8j6jkb8oa4buel.png" alt="Notification for cluster creation in progress" /></p> <p>We should now have a GKE cluster by name autopilot-cluster-1. ![Notification for cluster creation completed] (https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2h66wwysiu3al2u44ync.png)</p> <p>Refresh the page to see the cluster. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccbl0jpoko2l5ixic4y7.png" alt="Un registered cluster" /></p> <h2 id="register">Register</h2> <p>We have created the GKE cluster via Anthos, however we also need to register it. Click register and go back to the clusters page, the cluster should show under Anthos managed clusters. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36jd6nfoyffdkulaqbiy.png" alt="Anthos managed cluster" /></p> <p>We can now use the GKE cluster to launch any applications in the usual way with kubectl or helm.</p> <h2 id="kubeconfig">Kubeconfig</h2> <p>Go the cloud shell and run the following command to update kubeconfig.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud container clusters get-credentials autopilot-cluster-1 --region us-central1 Fetching cluster endpoint and auth data. kubeconfig entry generated for autopilot-cluster-1. </code></pre></div></div> <p>Both kubectl and helm would use this kubeconfig to interact with the cluster.</p> <h2 id="namespace">Namespace</h2> <p>Let’s create a namespace with kubectl.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl create ns harperdb namespace/harperdb created </code></pre></div></div> <h2 id="helm">Helm</h2> <p>We can deploy the Kubernetes objects with Helm, for which let’s check if there is any publicly available helm chart for harperdb chart in the artifact hub.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm search hub harperdb No results found </code></pre></div></div> <p>We don’t have a chart yet on the hub. Hence, I would be using a local minimal helm chart, whose files are as follows.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls harperdb -tR harperdb: templates Chart.yaml values.yaml harperdb/templates: svc.yaml deploy.yaml pvc.yaml secret.yaml </code></pre></div></div> <p>For more info on the contents of files in this chart, pl. checkout this <a href="https://dev.to/aws-builders/harperdb-with-helm-on-eks-3fb9">post</a>.</p> <p>Alright, so let’s install our helm release on the GKE cluster managed by Anthos.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm install harperdb . -n harperdb W0114 05:34:47.808566 817 warnings.go:70] Autopilot set default resource requests for Deployment harperdb/harperdb, as resource requests were not specified. See http://g.co/gke/autopilot-defaults NAME: harperdb LAST DEPLOYED: Sat Jan 14 05:34:39 2023 NAMESPACE: harperdb STATUS: deployed REVISION: 1 TEST SUITE: None </code></pre></div></div> <h2 id="resources">Resources</h2> <p>Though we have not setup any resource requests in the deployment template, the autopilot cluster had enabled it for us. We can check the deployment spec to see what they have set. Note that it’s a good practice to mention resource requests and limits in the template.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get deploy harperdb -n harperdb -o jsonpath={.spec.template.spec.containers[].resources} | jq { "limits": { "cpu": "500m", "ephemeral-storage": "1Gi", "memory": "2Gi" }, "requests": { "cpu": "500m", "ephemeral-storage": "1Gi", "memory": "2Gi" } } </code></pre></div></div> <h2 id="pod">Pod</h2> <p>Check the pod status.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get po -n harperdb NAME READY STATUS RESTARTS AGE harperdb-559d48f4f7-6dftw 1/1 Running 0 6m9s </code></pre></div></div> <h2 id="api-test">API test</h2> <p>The pod is running, we can get the service IP and try sending an API call.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ HDB_API_ENDPOINT_IP=$(kubectl get svc harperdb -n harperdb -o jsonpath={.status.loadBalancer.ingress[0].ip}) $ curl --location --request POST http://${HDB_API_ENDPOINT_IP}:8080 --header 'Content-Type: application/json' --header 'Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM0NQ==' --data-raw '{ "operation": "create_schema", "schema": "prod" }' {"message":"schema 'prod' successfully created"} </code></pre></div></div> <p>So our installation went smooth and it’s working.</p> <h2 id="clean-up">Clean up</h2> <p>The clean up involves four steps. Deleting the helm chart directory from cloudshell <code class="language-plaintext highlighter-rouge">rm -rf harperdb</code>.</p> <p>Unregistering the cluster from the Anthos console. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/batd1bbwsmgkslp3rsx9.png" alt="Unregister cluster" /></p> <p>Delete the cluster from the GKE console. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsxmrubutxiaxmdux1ez.png" alt="Delete the cluster" /></p> <p>Finally disable the Anthos API.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud services disable anthos.googleapis.com Warning: Disabling this service will also automatically disable any running Anthos clusters. Do you want to continue (y/N)? y </code></pre></div></div> <h2 id="summary">Summary</h2> <p>So we have seen how to create an autopilot cluster from the Anthos console, installed a helm release for HarperDB on it, and tested it with a sample schema creation. Thank you for reading !!!</p>This post first appeared on dev.toHarperDB with Helm on EKS2023-01-13T00:00:00+00:002023-01-13T00:00:00+00:00/aws,/harperdb,/helm,/kubernetes/2023/01/13/harperdb-with-helm-on-eks<p>This post first appeared on <a href="https://dev.to/aws-builders/harperdb-with-helm-on-eks-3fb9">dev.to</a></p> <p>This post builds on top of <a href="https://dev.to/aws-builders/harperdb-on-eks-1bcb">this</a> where we discussed how to launch HarperDB on EKS with kubectl. Here, we would use those same manifests. But, we would go with the helm cli tool instead of kubectl for the deployment. Please check this <a href="https://github.com/networkandcode/harperdb-deployments/tree/main/helm-charts/minimal/harperdb">link</a> for the final chart we make in this post.</p> <p>Get set go :fire:</p> <p>First, ensure <a href="https://dev.to/aws-builders/harperdb-on-eks-1bcb">helm</a> is installed. If you want to use AWS cloud shell for this, please click on the shell icon on the top navigation bar of the cloud console, and execute the following.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 $ chmod 700 get_helm.sh $ sudo yum install openssl -y $ ./get_helm.sh </code></pre></div></div> <p>Ensure the kubeconfig is updated.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws eks update-kubeconfig --name k8s-cluster-01 </code></pre></div></div> <p>Pl. change the cluster name above with what you have created.</p> <p>Let’s just create the namespace with kubectl, rest will be created with helm.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl create ns harperdb namespace/harperdb created </code></pre></div></div> <p>Create a directory for the helm chart.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir harperdb $ cd harperdb </code></pre></div></div> <p>Create a directory for templates in the chart.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir templates </code></pre></div></div> <p>Copy the manifests from this <a href="https://dev.to/aws-builders/harperdb-on-eks-1bcb">post</a> and keep it in the templates directory.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls templates/ deploy.yaml pvc.yaml secret.yaml svc.yaml </code></pre></div></div> <p>Create the chart file where we would just keep the name and version, we are keeping to hold just the mandatory information, though we can add more.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; Chart.yaml name: harperdb version: 1.0.0 EOF </code></pre></div></div> <p>So what we created so far is our harperdb helm chart, we are staring with chart version 1.0.0.</p> <p>We can use this to install the release in a separate namespace. The namespace below means the helm release namespace and not the kubernetes namespace. And <code class="language-plaintext highlighter-rouge">.</code> refers to the chart directory which is the current directory.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm install harperdb . -n harperdb NAME: harperdb LAST DEPLOYED: Fri Jan 13 09:52:08 2023 NAMESPACE: harperdb STATUS: deployed REVISION: 1 TEST SUITE: None </code></pre></div></div> <p>We are using the same name for both the helm and kubernetes namespaces. Note that the kubernetes namespace is mentioned in all of the manifests.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat templates/deploy.yaml | grep namespace namespace: harperdb </code></pre></div></div> <p>The helm release is deployed.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ helm list -n harperdb NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION harperdb harperdb 1 2023-01-13 09:52:08.953758683 +0000 UTC deployed harperdb-1.0.0 </code></pre></div></div> <p>This has deployed the workloads in kubernetes, which we can check with kubectl.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get deploy,pvc,secret,svc -n harperdb NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/harperdb 1/1 1 1 5m11s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/harperdb Bound pvc-6ef558ee-86ad-460d-8379-c34a12aaf283 5Gi RWO gp2 5m11s NAME TYPE DATA AGE secret/default-token-llbqm kubernetes.io/service-account-token 3 3h50m secret/harperdb Opaque 2 5m11s secret/sh.helm.release.v1.harperdb.v1 helm.sh/release.v1 1 5m11s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/harperdb LoadBalancer 10.100.29.79 a86501cc7a7024fff92b7212cd844e45-1677629392.us-east-1.elb.amazonaws.com 8080:31549/TCP 5m11s </code></pre></div></div> <p>Let’s test with an API call. The endpoint refers to the loadbalancer service.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ HDB_API_ENDPOINT=a86501cc7a7024fff92b7212cd844e45-1677629392.us-east-1.elb.amazonaws.com:8080 $ curl --location --request POST ${HDB_API_ENDPOINT} --header 'Content-Type: application/json' --header 'Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM0NQ==' --data-raw '{ "operation": "create_schema", "schema": "uat" }' {"message":"schema 'uat' successfully created"} </code></pre></div></div> <p>Awesome, works !!!.</p> <p>Let’s now revisit the helm chart and do a couple changes. For now, we haven’t used any values file, which is commonly used with any helm chart. We need values when we would like to parameterize certain things in our manifests. For instance, let’s say we want to parameterize the service port.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; values.yaml servicePort: 8080 EOF </code></pre></div></div> <p>We can the change the port section in the service template to refer to the value from values file.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat templates/svc.yaml | grep port ports: port: </code></pre></div></div> <p>Let’s call this chart version 2.0.0 and upgrade our release.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat Chart.yaml | grep version version: 2.0.0 $ helm upgrade harperdb . -n harperdb Release "harperdb" has been upgraded. Happy Helming! NAME: harperdb LAST DEPLOYED: Fri Jan 13 12:35:53 2023 NAMESPACE: harperdb STATUS: deployed REVISION: 2 TEST SUITE: None </code></pre></div></div> <p>We can verify the service port.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get svc harperdb -n harperdb -o jsonpath={.spec.ports[0].port} 8080 </code></pre></div></div> <p>Let’s do another change, this time we would try to refer to the release name and namespace as the name and namespace for each of the resources.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ grep -ir .Release templates/ templates/pvc.yaml: name: templates/pvc.yaml: namespace: templates/svc.yaml: name: templates/svc.yaml: namespace: templates/deploy.yaml: name: templates/deploy.yaml: namespace: templates/secret.yaml: name: templates/secret.yaml: namespace: </code></pre></div></div> <p>Note you have to modify each file, so that the grep output looks like above.</p> <p>Let’s change the chart version to 3.0.0, and upgrade the release.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat Chart.yaml name: harperdb version: 3.0.0 $ helm upgrade harperdb . -n harperdb </code></pre></div></div> <p>We can validate.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get deploy,pvc,secret,svc -n harperdb | awk '{print $1}' NAME deployment.apps/harperdb NAME persistentvolumeclaim/harperdb NAME secret/default-token-llbqm secret/harperdb secret/sh.helm.release.v1.harperdb.v1 NAME service/harperdb </code></pre></div></div> <p>You could see the workload details on the EKS page. Here is a sample screenshot. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c8z2wy1enafeytfx084g.png" alt="Harperdb on EKS console" /></p> <p>All good, so we have reached the end of this post, we saw how to install harperdb with a minimal helm chart, tested it with an API call, and tweaked the helm chart a bit to understand some fundamentals of it. Thank you for reading !!!.</p>This post first appeared on dev.toHarperDB on EKS2023-01-09T00:00:00+00:002023-01-09T00:00:00+00:00/aws,/eks,/harperdb,/kubernetes/2023/01/09/harperdb-on-eks<p>This post first appeared on <a href="https://dev.to/aws-builders/harperdb-on-eks-1bcb">dev.to</a></p> <p>Hi there :wave:, let’s see how to deploy HarperDB on EKS, and then test it with an API call from CURL. You can get the Kubernetes manifests that we make in this post from this <a href="https://github.com/networkandcode/harperdb-deployments/tree/main/kubernetes-manifests">link</a>.</p> <p>Hope you are already familiar with topics such as <a href="https://youtu.be/udSPJXMU9L4?t=1094">Deployment</a>, <a href="https://www.youtube.com/watch?v=7o6koGCsoSc">Load Balancer</a> service, <a href="https://www.youtube.com/watch?v=iTkz1bIacHM">Secret</a> and <a href="https://www.youtube.com/watch?v=vqk0Ccd7sio">Persistent volume claim</a></p> <p>Ensure you have the required IAM permissions, have installed the aws, eksctl &amp; kubectl cli tools, and have setup the config and credentials.</p> <p>For me the config is as follows.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/.aws/config [default] region=us-east-1 </code></pre></div></div> <h2 id="cluster">Cluster</h2> <p>We can now create an EKS cluster with eksctl. You may see this <a href="https://www.youtube.com/watch?v=rJQb_whepEY">video</a> for cluster creation from the CLI.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ eksctl create cluster --name eks-cluster --zones=us-east-1a,us-east-1b </code></pre></div></div> <p>This has taken around 20 mins for me. Once it’s done we can update the kubeconfig.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws eks update-kubeconfig --name eks-cluster </code></pre></div></div> <h2 id="docker-hub">Docker hub</h2> <p>We can visit the docker <a href="https://hub.docker.com/r/harperdb/harperdb">hub</a> page of harperdb to get an idea on the ports, environment variables, volume path etc.</p> <p>They have given an example docker command as below.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -d \ -v /host/directory:/opt/harperdb/hdb \ -e HDB_ADMIN_USERNAME=HDB_ADMIN \ -e HDB_ADMIN_PASSWORD=password \ -p 9925:9925 \ harperdb/harperdb </code></pre></div></div> <p>This tells us the volume mount path in the container is /opt/harperdb/hdb, there are 2 environment variables for username and password, and the container port is 9925. Finally the image is harperdb/harperdb.</p> <p>We now have enough info to start writing our Kubernetes manifests.</p> <h2 id="kubernetes-manifests">Kubernetes manifests</h2> <p>I am going to create a directory by name harperdb where I would keep all the manifests.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir harperdb $ cd harperdb </code></pre></div></div> <p>Let’s begin with the environment variables, we can write both username and password in a secret object.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; secret.yaml --- apiVersion: v1 kind: Secret metadata: name: harperdb namespace: harperdb stringData: HDB_ADMIN_USERNAME: admin HDB_ADMIN_PASSWORD: password12345 ... EOF </code></pre></div></div> <p>We can now go with a persistent volume claim, that can dynamically create an EBS volume of size 5Gi in AWS.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; pvc.yaml --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: harperdb namespace: harperdb spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi ... EOF </code></pre></div></div> <p>Then comes the deployment manifest, where we can define the container image, refer to the secret for the env vars, and pvc for the volume. Note that the volume mount path matches with that in the docker command.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; deploy.yaml --- apiVersion: apps/v1 kind: Deployment metadata: name: harperdb namespace: harperdb spec: selector: matchLabels: app: harperdb template: metadata: labels: app: harperdb spec: containers: - name: harperdb image: harperdb/harperdb envFrom: - secretRef: name: harperdb volumeMounts: - name: data mountPath: /opt/harperdb/hdb volumes: - name: data persistentVolumeClaim: claimName: harperdb ... EOF </code></pre></div></div> <p>Finally, we have to expose the deployment with a service, we know from the docker command that the container port is 9925.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &lt;&lt;EOF &gt; svc.yaml --- apiVersion: v1 kind: Service metadata: name: harperdb namespace: harperdb spec: selector: app: harperdb type: LoadBalancer ports: - name: http port: 8080 targetPort: 9925 ... EOF </code></pre></div></div> <p>Note that we have used 8080 as the service port.</p> <h2 id="workloads">Workloads</h2> <p>Create a namespace by name harperdb, where we can create our objects.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl create ns harperdb namespace/harperdb created </code></pre></div></div> <p>We are good to create objects with the 4 manifests.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls deploy.yaml pvc.yaml secret.yaml svc.yaml $ kubectl create -f . deployment.apps/harperdb created persistentvolumeclaim/harperdb created secret/harperdb created service/harperdb created </code></pre></div></div> <h2 id="fix-pvc">Fix PVC</h2> <p>The pvc should be in pending status.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE harperdb Pending gp2 7m3s </code></pre></div></div> <p>Please follow this <a href="https://aws.amazon.com/premiumsupport/knowledge-center/eks-persistent-storage/">link</a> to add IAM role in AWS cloud, and ebs csi objects on the cluster. This should fix the PVC issue.</p> <p>Once done, the pvc should be bound to a persistent volume(pv).</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get pvc -n harperdb NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE harperdb Bound pvc-7c83e38c-b00a-4194-8c67-ba5c9c1118e7 5Gi RWO gp2 9s </code></pre></div></div> <p>And the pv should be mapped to an EBS volume.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-7c83e38c-b00a-4194-8c67-ba5c9c1118e7 5Gi RWO Delete Bound harperdb/harperdb gp2 64s $ kubectl describe pv pvc-7c83e38c-b00a-4194-8c67-ba5c9c1118e7 | grep VolumeID VolumeID: vol-0bbca736346f02aa1 </code></pre></div></div> <p>Note that a persistent volume is a cluster level object and not bound to a namespace. We can check the volume details from the aws cli.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws ec2 describe-volumes --volume-ids vol-0bbca736346f02aa1 --query "Volumes[0].Size" 5 $ aws ec2 describe-volumes --volume-ids vol-0bbca736346f02aa1 --query "Volumes[0].Tags" [ { "Key": "ebs.csi.aws.com/cluster", "Value": "true" }, { "Key": "CSIVolumeName", "Value": "pvc-7c83e38c-b00a-4194-8c67-ba5c9c1118e7" }, { "Key": "kubernetes.io/created-for/pv/name", "Value": "pvc-7c83e38c-b00a-4194-8c67-ba5c9c1118e7" }, { "Key": "kubernetes.io/created-for/pvc/namespace", "Value": "harperdb" }, { "Key": "kubernetes.io/created-for/pvc/name", "Value": "harperdb" } ] </code></pre></div></div> <h2 id="volume-permission-fix">Volume permission fix</h2> <p>So the pvc seems good. Let’s check our application status.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get po -n harperdb NAME READY STATUS RESTARTS AGE harperdb-79694c8b75-6ckn7 0/1 CrashLoopBackOff 4 (80s ago) 3m25s </code></pre></div></div> <p>The application was crashing, but the volume was getting mounted, and the env vars were fine too. I tried commenting out volumeMounts and volume and updated the deployment.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat deploy.yaml | grep # #volumeMounts: #- name: data #mountPath: /opt/harperdb/hdb #volumes: #- name: data #persistentVolumeClaim: #claimName: harperdb $ kubectl apply -f deploy.yaml </code></pre></div></div> <p>The pod was running, and I checked the permissions of the directory where we need to mount the volume. And subsequently the id of the group.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl exec -it deploy/harperdb -n harperdb -- bash ubuntu@harperdb-858cc7967d-5jcqm:~$ ls -l /opt/harperdb total 0 drwxr-xr-x 11 ubuntu ubuntu 155 Jan 9 06:59 hdb ubuntu@harperdb-858cc7967d-5jcqm:~$ id uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu) ubuntu@harperdb-858cc7967d-5jcqm:~$ exit </code></pre></div></div> <p>So the group id of the running user is 1000, hence we can set this as the group owner for the volume directory with the fsGroup option. If we don’t specify this then the mountPath would by default be set with root(user) and root(group) as the owner for the directory and the running user ubuntu wouldn’t have permissions on the mountPath to create any new files. This <a href="https://www.youtube.com/watch?v=PzzFsvadZdY">video</a> has information about fsGroup.</p> <p>We have to change the deployment as follows. We have added the security context with the fsGroup.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat deploy.yaml --- apiVersion: apps/v1 kind: Deployment metadata: name: harperdb namespace: harperdb spec: selector: matchLabels: app: harperdb template: metadata: labels: app: harperdb spec: securityContext: fsGroup: 1000 containers: - name: harperdb image: harperdb/harperdb envFrom: - secretRef: name: harperdb volumeMounts: - name: data mountPath: /opt/harperdb/hdb volumes: - name: data persistentVolumeClaim: claimName: harperdb ... </code></pre></div></div> <p>Alternately, we could also set mountPath to just /opt/harperdb, where we wouldn’t have to set the securityContext. But I thought this is a good use case to know about the fsGroup.</p> <p>Update the deployment.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl apply -f deploy.yaml </code></pre></div></div> <p>Check the workloads.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get all -n harperdb NAME READY STATUS RESTARTS AGE pod/harperdb-cc4f49dfc-m7d5p 1/1 Running 0 55s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/harperdb LoadBalancer 10.100.54.78 a0ba701c9c5a4463bb636551c79b4158-169592876.us-east-1.elb.amazonaws.com 8080:31819/TCP 55s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/harperdb 1/1 1 1 57s NAME DESIRED CURRENT READY AGE replicaset.apps/harperdb-cc4f49dfc 1 1 1 57s </code></pre></div></div> <h2 id="api-call">API call</h2> <p>Send a CURL command to test schema creation. The endpoint is from the external IP column in the service. You may check this <a href="https://www.youtube.com/watch?v=25L8VvXgx8w">video</a> to know how to obtain the curl command for harperdb.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ HDB_API_ENDPOINT=http://a0ba701c9c5a4463bb636551c79b4158-169592876.us-east-1.elb.amazonaws.com:8080 $ curl --location --request POST ${HDB_API_ENDPOINT} \ --header 'Content-Type: application/json' \ --header 'Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM0NQ==' \ --data-raw '{ "operation": "create_schema", "schema": "qa" }' {"message":"schema 'qa' successfully created"} </code></pre></div></div> <p>All good, it’s working…</p> <h2 id="persistence">Persistence</h2> <p>Test persistence by deleting the pod.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl delete po -n harperdb -l app=harperdb pod "harperdb-cc4f49dfc-m7d5p" deleted </code></pre></div></div> <p>This should launch a new pod.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl get po -n harperdb NAME READY STATUS RESTARTS AGE harperdb-cc4f49dfc-c6vnc 1/1 Running 0 57s </code></pre></div></div> <p>We can try sending the same API call again.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl --location --request POST ${HDB_API_ENDPOINT} \ --header 'Content-Type: application/json' \ --header 'Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM0NQ==' \ --data-raw '{ "operation": "create_schema", "schema": "qa" }' {"error":"Schema 'qa' already exists"} </code></pre></div></div> <p>It’s not creating a new schema, because the existing schema is restored from the attached volume. Hence, it’s persistent.</p> <h2 id="clean-up">Clean up</h2> <p>Let’s do the clean up…</p> <p>Delete all the objects that were created via manifests.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl delete -f . deployment.apps "harperdb" deleted persistentvolumeclaim "harperdb" deleted secret "harperdb" deleted service "harperdb" deleted </code></pre></div></div> <p>Then delete the namespace.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ kubectl delete ns harperdb namespace "harperdb" deleted </code></pre></div></div> <p>Delete the folder.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd .. $ rm -rf harperdb </code></pre></div></div> <p>Finally delete the cluster.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ eksctl delete cluster --name eks-cluster </code></pre></div></div> <p>That’s it for the post, Thank you for reading !!!</p>This post first appeared on dev.toGCP Anthos Cluster on AWS2022-10-08T00:00:00+00:002022-10-08T00:00:00+00:00/anthos,/aws,/gcp,/kubernetes/2022/10/08/gcp-anthos-cluster-on-aws<p>This post first appeared on <a href="https://dev.to/aws-builders/gcp-anthos-cluster-on-aws-19p8">dev.to</a></p> <p>Anthos is a software offering from Google, using which we can build kubernetes clusters on nodes both on and off cloud. In this post, we would launch an EC2 instance in AWS and build a single node kubernetes cluster on it with Anthos.</p> <p>Let’s get started.</p> <h2 id="aws-cloud-shell">AWS cloud shell</h2> <p>Login to the AWS console, and access the cloud shell from the top bar. You should see a prompt like below.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[cloudshell-user@ip-10-0-89-211 ~]$ </code></pre></div></div> <h2 id="ec2">EC2</h2> <p>Create an EC2 instance with other relevant components with these commands. Please refer to this <a href="https://dev.to/aws-builders/aws-ec2-launch-instances-the-hard-way-with-cli-2ga3">post</a> for explanation on what each of these commands does.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir ~/.aws $ cat ~/.aws/config &lt;&lt;EOF [default] region=ap-south-1 EOF $ export CIDR_BLOCK="10.10.10.0/28" $ aws ec2 create-vpc --cidr-block $CIDR_BLOCK $ export ANTHOS_VPC_ID=$(aws ec2 describe-vpcs | jq -r '.Vpcs[] | select(.CidrBlock == env.CIDR_BLOCK) | .VpcId') $ aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=anthos-igw}]' $ export ANTHOS_IGW_ID=$(aws ec2 describe-internet-gateways --filters Name=tag:Name,Values=anthos-igw --query "InternetGateways[*].InternetGatewayId" --output text) $ aws ec2 attach-internet-gateway --internet-gateway-id $ANTHOS_IGW_ID --vpc-id $ANTHOS_VPC_ID $ export ANTHOS_RTB_ID=$(aws ec2 describe-route-tables | jq -r '.RouteTables[] | select(.VpcId == env.ANTHOS_VPC_ID) | .RouteTableId') $ aws ec2 create-route --route-table-id $ANTHOS_RTB_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $ANTHOS_IGW_ID $ aws ec2 create-subnet --cidr-block $CIDR_BLOCK --vpc-id $ANTHOS_VPC_ID $ export ANTHOS_SUBNET_ID=$(aws ec2 describe-subnets | jq -r '.Subnets[] | select(.CidrBlock == env.CIDR_BLOCK) | .SubnetId') $ export ANTHOS_AVAILABILITY_ZONE=$(aws ec2 describe-subnets | jq -r '.Subnets[] | select(.CidrBlock == env.CIDR_BLOCK) | .AvailabilityZone') $ aws ec2 create-security-group --group-name anthos-sg --description "anthos security group" --vpc-id $ANTHOS_VPC_ID $ export ANTHOS_SG_ID=$(aws ec2 describe-security-groups | jq -r '.SecurityGroups[] | select(.GroupName == "anthos-sg") | .GroupId') $ aws ec2 describe-instance-types | jq '.InstanceTypes[] | select(.MemoryInfo.SizeInMiB == 7680) | (.InstanceType, .VCpuInfo.DefaultVCpus)' "c4.xlarge" 4 $ aws ec2 describe-instance-types | jq '.InstanceTypes[] | select(.MemoryInfo.SizeInMiB == 8192) | select (.VCpuInfo.DefaultVCpus == 2) | .InstanceType' | sort "m4.large" "m5ad.large" "m5a.large" "m5d.large" "m5.large" "m6a.large" "m6gd.large" "m6g.large" "m6i.large" "t2.large" "t3a.large" "t3.large" "t4g.large" $ aws ec2 describe-instance-type-offerings --location-type availability-zone | jq '.InstanceTypeOfferings[] | select(.Location == env.ANTHOS_AVAILABILITY_ZONE) | .InstanceType' | grep t2.large "t2.large" $ aws ec2 create-key-pair --key-name anthosKeyPair --query 'KeyMaterial' --output text &gt; anthosKeyPair.pem $ mkdir .ssh $ mv anthosKeyPair.pem ~/.ssh/ $ aws ec2 run-instances --image-id ami-0bba4b75264ecbfbd --count 1 --instance-type t2.large --key-name anthosKeyPair --security-group-ids $ANTHOS_SG_ID --subnet-id $ANTHOS_SUBNET_ID --associate-public-ip-address --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=200}' </code></pre></div></div> <p>The instance is now created. We can give it a name.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ANTHOS_INSTANCE_ID=$(aws ec2 describe-instances | jq -r '.Reservations[] | .Instances[] | select(.SubnetId==env.ANTHOS_SUBNET_ID) | .InstanceId') $ aws ec2 create-tags --resources $ANTHOS_INSTANCE_ID --tags Key=Name,Value=anthos-node </code></pre></div></div> <h2 id="ssh">SSH</h2> <p>In order to SSH from the cloud shell to the Anthos instance, we first need to obtain the public IP of the cloudshell and add a rule in the security group to allow SSH access from that.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export MY_PUBLIC_IP=$(curl ifconfig.me --silent) $ aws ec2 authorize-security-group-ingress --group-id $ANTHOS_SG_ID --protocol tcp --port 22 --cidr $MY_PUBLIC_IP/32 </code></pre></div></div> <p>Copy the SSH key pair to the instance as it’s needed in the Anthos cluster config.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export ANTHOS_INSTANCE_IP=$(aws ec2 describe-instances --filter Name=tag:Name,Values=anthos-node --query "Reservations[*].Instances[*].PublicIpAddress" --output text) $ scp -i ~/.ssh/anthosKeyPair.pem ~/.ssh/anthosKeyPair.pem ubuntu@$ANTHOS_INSTANCE_IP:~/.ssh/anthosKeyPair.pem </code></pre></div></div> <p>SSH into the instance.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh -i ~/.ssh/anthosKeyPair.pem ubuntu@$ANTHOS_INSTANCE_IP </code></pre></div></div> <h2 id="install-gcloud">Install gcloud</h2> <p>Install the gcloud cli on the Anthos instance.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install apt-transport-https ca-certificates gnupg -y echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - sudo apt-get update -y &amp;&amp; sudo apt-get install google-cloud-cli -y </code></pre></div></div> <h2 id="login--to-gcloud">Login to gcloud</h2> <p>This step is optional. Login to the gcloud cli with your account if you are going to create a service account yourself from the CLI.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud auth login </code></pre></div></div> <h2 id="authenticaion">Authenticaion</h2> <p>Create a <a href="https://cloud.google.com/iam/docs/creating-managing-service-accounts">service account</a> in GCP and give it the following roles.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>roles/gkehub.connect roles/gkehub.admin roles/logging.logWriter roles/monitoring.metricWriter roles/monitoring.dashboardEditor roles/stackdriver.resourceMetadata.writer roles/opsconfigmonitoring.resourceMetadata.writer </code></pre></div></div> <p>If you are using the gcloud CLI, you can create a service account and bind the roles with the following command.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud iam service-accounts create &lt;service-account-name&gt; gcloud projects add-iam-policy-binding "$PROJECT_ID" \ --member=&lt;service-account-client-email&gt; \ --role=&lt;role&gt; \ --no-user-output-enabled </code></pre></div></div> <p>Create a key for the service account and copy it’s credentials.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud iam service-accounts keys create &lt;key-file-path&gt; \ --iam-account=${service-account-name}@${PROJECT_ID}.iam.gserviceaccount.com </code></pre></div></div> <p>Setup the credentials in the instance, activate the service account and set the project id.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir .gcloud $ export PROJECT_ID=&lt;project_id&gt; $ cat &gt; .gcloud/keyfile.json &lt;&lt; EOF { "type": "service_account", "project_id": $PROJECT_ID, "private_key_id": "&lt;private_key_id&gt;", "private_key": &lt;private_key&gt;, "client_email": &lt;client_email&gt;, "client_id": &lt;client_id&gt;, "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": &lt;client_x509_cert_url&gt; } EOF $ gcloud auth activate-service-account &lt;client_email&gt; --key-file .gcloud/keyfile.json $ export GOOGLE_APPLICATION_CREDENTIALS='/home/ubuntu/.gcloud/keyfile.json' $ gloud config set project $PROJECT_ID </code></pre></div></div> <h2 id="apis">APIs</h2> <p>Enable the services.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcloud services enable \ anthos.googleapis.com \ anthosaudit.googleapis.com \ anthosgke.googleapis.com \ cloudresourcemanager.googleapis.com \ container.googleapis.com \ gkeconnect.googleapis.com \ gkehub.googleapis.com \ serviceusage.googleapis.com \ stackdriver.googleapis.com \ monitoring.googleapis.com \ logging.googleapis.com \ opsconfigmonitoring.googleapis.com </code></pre></div></div> <h2 id="other-tools">Other tools</h2> <p>Install kubectl, bmctl and docker.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/sbin/ gsutil cp gs://anthos-baremetal-release/bmctl/1.13.0/linux-amd64/bmctl . chmod a+x bmctl sudo mv bmctl /usr/local/sbin/ curl -O https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz tar xzvf docker-20.10.9.tgz sudo cp docker/* /usr/bin/ rm -rf docker* sudo groupadd docker sudo usermod -aG docker $USER sudo dockerd&amp; newgrp docker </code></pre></div></div> <h2 id="vxlan">VxLAN</h2> <p>Set up vxlan with the IP 10.200.2/24.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo ip link add vxlan0 type vxlan id 42 dev eth0 dstport 0 sudo ip addr add 10.200.0.2/24 dev vxlan0 sudo ip link set up dev vxlan0 </code></pre></div></div> <h2 id="cluster-config">Cluster config</h2> <p>Create the Anthos cluster config with bmctl.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export CLUSTER_ID=anthos-aws bmctl create config -c $CLUSTER_ID </code></pre></div></div> <p>Change the config.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &gt; bmctl-workspace/${CLUSTER_ID}/${CLUSTER_ID}.yaml &lt;&lt; EOF --- gcrKeyPath: /home/ubuntu/.gcloud/keyfile.json sshPrivateKeyPath: /home/ubuntu/.ssh/anthosKeyPair.pem gkeConnectAgentServiceAccountKeyPath: /home/ubuntu/.gcloud/keyfile.json gkeConnectRegisterServiceAccountKeyPath: /home/ubuntu/.gcloud/keyfile.json cloudOperationsServiceAccountKeyPath: /home/ubuntu/.gcloud/keyfile.json --- apiVersion: v1 kind: Namespace metadata: name: cluster-${CLUSTER_ID} --- apiVersion: baremetal.cluster.gke.io/v1 kind: Cluster metadata: name: ${CLUSTER_ID} namespace: cluster-${CLUSTER_ID} spec: profile: edge type: standalone anthosBareMetalVersion: 1.13.0 gkeConnect: projectID: $PROJECT_ID controlPlane: nodePoolSpec: clusterName: ${CLUSTER_ID} nodes: - address: 10.200.0.2 clusterNetwork: pods: cidrBlocks: - 192.168.0.0/16 services: cidrBlocks: - 172.26.232.0/24 loadBalancer: mode: bundled ports: controlPlaneLBPort: 443 vips: controlPlaneVIP: 10.200.0.49 ingressVIP: 10.200.0.50 addressPools: - name: pool1 addresses: - 10.200.0.50-10.200.0.70 clusterOperations: location: asia-south1 projectID: $PROJECT_ID storage: lvpNodeMounts: path: /mnt/localpv-disk storageClassName: node-disk lvpShare: numPVUnderSharedPath: 5 path: /mnt/localpv-share storageClassName: local-shared nodeConfig: podDensity: maxPodsPerNode: 64 nodeAccess: loginUser: ubuntu EOF </code></pre></div></div> <h2 id="cluster-creation">Cluster creation</h2> <p>Create the cluster.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bmctl create cluster -c ${CLUSTER_ID} </code></pre></div></div> <p>The above command should take some time and once it’s successful, the kubernetes cluster should be ready.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export KUBECONFIG=bmctl-workspace/${CLUSTER_ID}/${CLUSTER_ID}-kubeconfig $ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-0-9 Ready control-plane,master 153m v1.24.2-gke.1900 </code></pre></div></div> <p>The cluster should appear on the Anthos clusters plage on GCP.</p> <h2 id="run-workloads">Run workloads</h2> <p>Test with a sample nginx deployment.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &gt; deploy.yaml &lt;&lt; EOF apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx selector: matchLabels: app: nginx EOF $ kubectl create -f deploy.yaml $ kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE nginx 1/1 1 1 65s $ kubectl get pod NAME READY STATUS RESTARTS AGE nginx-8f458dc5b-jvrbb 1/1 Running 0 68s </code></pre></div></div> <p>Expose this deployment with a service.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat &gt; svc.yaml &lt;&lt;EOF apiVersion: v1 kind: Service metadata: name: nginx spec: selector: app: nginx ports: - port: 8080 targetPort: 80 name: web-server EOF $ kubectl create -f svc.yaml $ kubectl get svc nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP 172.26.232.21 &lt;none&gt; 8080/TCP 106s $ kubectl get ep nginx NAME ENDPOINTS AGE nginx 192.168.0.48:80 2m3s </code></pre></div></div> <p>Try to curl the service IP and see if it works.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl 172.26.232.21:8080 &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt; &lt;title&gt;Welcome to nginx!&lt;/title&gt; &lt;style&gt; html { color-scheme: light dark; } body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } &lt;/style&gt; &lt;/head&gt; &lt;body&gt; &lt;h1&gt;Welcome to nginx!&lt;/h1&gt; &lt;p&gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.&lt;/p&gt; &lt;p&gt;For online documentation and support please refer to &lt;a href="http://nginx.org/"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt; Commercial support is available at &lt;a href="http://nginx.com/"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt; &lt;/body&gt; &lt;/html&gt; </code></pre></div></div> <h2 id="reset-the-cluster">Reset the cluster</h2> <p>Finally reset the cluster when you no longer need it.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bmctl reset cluster -c ${CLUSTER_ID} </code></pre></div></div> <p>Thus, we have launched a single node kubernetes cluster with Anthos on AWS, and tested it by running an nginx service.</p> <p>Thanks for reading!!!</p>This post first appeared on dev.toAWS CloudTrail log file validation2022-08-29T00:00:00+00:002022-08-29T00:00:00+00:00/aws,/cloudtrail,/logging,/s3/2022/08/29/aws-cloudtrail-log-file-validation<p>This post first appeared on <a href="https://dev.to/aws-builders/aws-cloudtrail-log-file-validation-1ehl">dev.to</a></p> <h2 id="introduction">Introduction</h2> <p>CloudTrail lets us log all API calls in our AWS cloud. In this post, we shall see how to create a CloudTrail, see where the logs are stored in S3, delete log, digest files and perform log file validation.</p> <h2 id="create-a-cloudtrail">Create a CloudTrail</h2> <p>Search for CloudTrail on the AWS console and create a trail. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ojcn1fxyhmdz2bvzvzi.png" alt="Create cloud trail" /></p> <h2 id="s3-bucket">S3 bucket</h2> <p>A bucket should be automatically created and associated with the CloudTrail. A folder with the name CloudTrail should appear on the bucket where all the cloud trail logs should get saved. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yw5zei5v3ouuie7f0mbn.png" alt="S3 bucket for cloud trail" /></p> <h2 id="generate-logs">Generate logs</h2> <p>Now let’s do an activity and see if it gets logged. Create a lambda function with name helloWorld and all other settings as default. You can do any other activity on AWS cloud as well, instead of creating a function. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntkzldnsrkx2xenqdsb9.png" alt="Lambda function" /></p> <p>We should see some files on S3 for this activity. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5oayy9diyj1w6kui5kf.png" alt="Log files on S3" /></p> <h2 id="delete-log-file">Delete log file</h2> <p>I am deleting one of the log files. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zle3jv7vq8m0eedykz19.png" alt="Deleting log file" /></p> <p>Log files are not suppose to be modified/deleted, as they can hold important auditing information, so now we need to find if our log files are modified or deleted(as in this case).</p> <p>We try to validate now from the <a href="https://aws.amazon.com/cli/">AWS CLI</a>, it should say the digest file doesn’t exit.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws cloudtrail validate-logs --trail-arn arn:aws:cloudtrail:ap-south-1:&lt;accoount-id&gt;:trail/management-events --start-time 2022-08-29 Validating log files for trail arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events between 2022-08-29T00:00:00Z and 2022-08-29T06:26:38Z Results requested for 2022-08-29T00:00:00Z to 2022-08-29T06:26:38Z No digests found </code></pre></div></div> <p>This is because we have not enabled log file validation for the cloud trail.</p> <h2 id="enable-log-file-validation">Enable Log file validation</h2> <p>We can enable log file validation, by editing the cloud trail. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s7p6e0r18dpgjs11as3.png" alt="Enable log file validation" /></p> <h2 id="digest">Digest</h2> <p>As the log file validation is enabled, we should see a new folder CloudTrail-Digest in S3. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixq3invj5kug2y1m5hfj.png" alt="CloudTrail-Digest folder in S3" /></p> <p>And digest files should get added each hour. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36mc4wdwlsucq7a12n26.png" alt="Digest file in S3" /></p> <h2 id="validate">Validate</h2> <p>As we enabled Log file validation, we can now check the integirty of the logs.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws cloudtrail validate-logs --trail-arn arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events --start-time 2022-08-29 Validating log files for trail arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events between 2022-08-29T00:00:00Z and 2022-08-29T07:00:20Z Results requested for 2022-08-29T00:00:00Z to 2022-08-29T07:00:20Z Results found for 2022-08-29T05:55:08Z to 2022-08-29T06:55:08Z: 1/1 digest files valid </code></pre></div></div> <p>Though we deleted a log file earlier, it shows the digest is valid, because we did not enable log file validation then.</p> <p>After an hour, we should see two digest files. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vsot6bvy7kzsq632poii.png" alt="Digest files" /></p> <p>The log file validation seems good for now.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws cloudtrail validate-logs --trail-arn arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events --start-time 2022-08-29 Validating log files for trail arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events between 2022-08-29T00:00:00Z and 2022-08-29T08:17:57Z Results requested for 2022-08-29T00:00:00Z to 2022-08-29T08:17:57Z Results found for 2022-08-29T05:55:08Z to 2022-08-29T07:55:08Z: 2/2 digest files valid 10/10 log files valid </code></pre></div></div> <h2 id="delete-log-file-with-validation-check">Delete log file with validation check</h2> <p>We can try deleting log file that was created after enabling log file validation. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmkawn7l1s5hw91o8vmb.png" alt="Delete another log file" /></p> <p>As expected the log file validations fails for one file. However the digests are still valid.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws cloudtrail validate-logs --trail-arn arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events --start-time 2022-08-29 Validating log files for trail arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events between 2022-08-29T00:00:00Z and 2022-08-29T08:22:42Z Log file s3://aws-cloudtrail-logs-&lt;account-id&gt;-4a8dcb98/AWSLogs/&lt;account-id&gt;/CloudTrail/ap-south-1/2022/08/29/&lt;account-id&gt;_CloudTrail_ap-south-1_20220829T0755Z_7rDSVFC6Icgi9Z8V.json.gz INVALID: not found Results requested for 2022-08-29T00:00:00Z to 2022-08-29T08:22:42Z Results found for 2022-08-29T05:55:08Z to 2022-08-29T07:55:08Z: 2/2 digest files valid 9/10 log files valid, 1/10 log files INVALID </code></pre></div></div> <p>It also clearly says the validation failed because it can’t find a file that we deleted.</p> <h2 id="delete-digest">Delete digest</h2> <p>This time we can try deleting a digest file. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzzw970xszdwllgpqqdp.png" alt="Delete digest" /></p> <p>Hence digest validation should also fail.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ aws cloudtrail validate-logs --trail-arn arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events --start-time 2022-08-29 Validating log files for trail arn:aws:cloudtrail:ap-south-1:&lt;account-id&gt;:trail/management-events between 2022-08-29T00:00:00Z and 2022-08-29T10:09:35Z Digest file s3://aws-cloudtrail-logs-&lt;account-id&gt;-4a8dcb98/AWSLogs/&lt;account-id&gt;/CloudTrail-Digest/ap-south-1/2022/08/29/&lt;account-id&gt;_CloudTrail-Digest_ap-south-1_management-events_ap-south-1_20220829T085508Z.json.gz INVALID: not found Log file s3://aws-cloudtrail-logs-&lt;account-id&gt;-4a8dcb98/AWSLogs/&lt;account-id&gt;/CloudTrail/ap-south-1/2022/08/29/&lt;account-id&gt;_CloudTrail_ap-south-1_20220829T0755Z_7rDSVFC6Icgi9Z8V.json.gz INVALID: not found Results requested for 2022-08-29T00:00:00Z to 2022-08-29T10:09:35Z Results found for 2022-08-29T05:55:08Z to 2022-08-29T09:55:08Z: 3/4 digest files valid, 1/4 digest files INVALID 20/21 log files valid, 1/21 log files INVALID </code></pre></div></div> <p>Note that we can enable versioning on S3 buckets to restore files.</p> <h2 id="summary">Summary</h2> <p>So we saw how the log file validation feature in CloudTrail helps us find if there were any manual modifications to the log files or digest files. Thank you for reading !!!</p>This post first appeared on dev.toAWS IoT pub/sub over MQTT2022-07-17T00:00:00+00:002022-07-17T00:00:00+00:00/aws,/ios,/python,/mqtt/2022/07/17/aws-iot-pub-sub-over-mqtt<p><em>This post first appeared on <a href="https://dev.to/aws-builders/aws-iot-pubsub-over-mqtt-1oig">dev.to</a></em></p> <h2 id="introduction">Introduction</h2> <p>Hello, in this post we would create an IoT thing on AWS, use it’s credentials, to create two virtual clients on a Linux VM with python and test publishing from one client and subscribing from the other.</p> <h2 id="vm">VM</h2> <p>Use your Linux machine or a VM as a virtual IoT device. We would be doing all of the CLI / coding tasks in the post, on this VM.</p> <h2 id="aws">AWS</h2> <p>Install and setup the <a href="https://aws.amazon.com/cli/">AWS CLI</a>. Here is the region I have set as default.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/.aws/config [default] region = ap-south-1 </code></pre></div></div> <h2 id="endpoint">Endpoint</h2> <p>Goto <code class="language-plaintext highlighter-rouge">ASW IoT &gt; Settings</code> on the cloud console, and get the Device data endpoint which is unique to the AWS account/region. Or get it from the AWS CLI.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ IOT_DEV_EP=$(aws iot describe-endpoint --region ap-south-1 --output text --query endpointAddress) $ echo $IOT_DEV_EP &lt;some-id&gt;.iot.ap-south-1.amazonaws.com </code></pre></div></div> <p>Check connectivity to this endpoint from the Linux VM, which is your virtual IoT device.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ping -c 1 $IOT_DEV_EP ---TRUNCATED--- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 196.145/196.145/196.145/0.000 ms </code></pre></div></div> <p>I have tested with 1 packet <code class="language-plaintext highlighter-rouge">-c 1</code>. You may send more than one though.</p> <p>You can also check connectivity to the secure port for MQTT i.e. 8883 on the endpoint. Telnet should be present/installed on the machine though, for ex. <code class="language-plaintext highlighter-rouge">sudo yum install telnet -y</code>.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ telnet $IOT_DEVICE_EP 8883 Trying &lt;some-ip&gt;... Connected to &lt;some-id&gt;-ats.iot.ap-south-1.amazonaws.com. Escape character is '^]'. </code></pre></div></div> <h2 id="thing">Thing</h2> <p>Goto <code class="language-plaintext highlighter-rouge">AWS IoT &gt; Manage &gt; Things &gt; Create Things</code> on the cloud console and create a new thing with the name <em>temp-sensor</em>, set unnamed shadow(classic) and choose <em>Auto-generate a new certificate (recommended)</em>.</p> <p>In the policies section, create and select a new policy with the name <em>temp-sensor</em> and the following JSON.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:Connect", "iot:Publish", "iot:Receive", "iot:RetainPublish", "iot:Subscribe" ], "Resource": "*" } ] } </code></pre></div></div> <p>Download all the certificates/keys and name those as needed, I have named them as follows.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls *.pem ca-cert.pem pub-cert.pem pub-key.pem pvt-key.pem </code></pre></div></div> <p>Note: If you are using a different host system like Windows with a browser, you can download these files, copy the content and then paste into the respective file on a Linux VM.</p> <h2 id="sdk">SDK</h2> <p>We would be using the AWS IoT SDK for Python.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Clone the repository git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git # Install using Pip python3 -m pip install ./aws-iot-device-sdk-python-v2 # Remove the clone, if it isn't required anymore $ rm -rf aws-iot-device-sdk-python-v2 </code></pre></div></div> <h2 id="connect">Connect</h2> <p>We have to first import the mqtt_connection_builder package from the awsiot sdk.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from awsiot import mqtt_connection_builder </code></pre></div></div> <p>We need the endpoint, the cerificate/key paths and a client_id to initiate a connection. We can generate a client_id using the uuid package.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from uuid import uuid4 client_id = 'client-' + str(uuid4()) </code></pre></div></div> <p>We can then pass the files as arguments using the argparse package.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>##### parse arguments import argparse parser = argparse.ArgumentParser(description="Send and receive messages through and MQTT connection.") parser.add_argument('--ep', help="IoT device endpoint &lt;some-prefix&gt;.iot.&lt;region&gt;.amazonaws.com", required=True, type=str) parser.add_argument('--pubcert', help="IoT device public certificate file path", required=True, type=str) parser.add_argument('--pvtkey', help="IoT device private key file path", required=True, type=str) parser.add_argument('--cacert', help="IoT device CA cert file path", required=True, type=str) parser.add_argument('--topic', help="Topic name", required=True, type=str) args = parser.parse_args() </code></pre></div></div> <p>You can also skip the parse arguments step and add the parameters directly.</p> <p>We have the necessary parameters to initiate the connection.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mqtt_connection = mqtt_connection_builder.mtls_from_path( endpoint=args.ep, cert_filepath=args.pubcert, pri_key_filepath=args.pvtkey, ca_filepath=args.cacert, client_id=client_id ) connect_future = mqtt_connection.connect() # result() waits until a result is available connect_future.result() print(f'{client_id} is connected!') </code></pre></div></div> <p>Put the code we saw in the connect section so far in a file called connect.py and run the following.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ python connect.py --ep $IOT_DEV_EP --pubcert pub-cert.pem --pvtkey pvt-key.pem --cacert ca-cert.pem --topic temperature client-3924e5d4-97d3-43e6-b214-169d008b2d02 is connected! </code></pre></div></div> <p>Great, the connection is successful.</p> <h2 id="publish">Publish</h2> <p>Before publishing, let’s import certain variables from the previous connect code we wrote.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># import vars from connect.py from connect import args, client_id, mqtt_connection </code></pre></div></div> <p>We shall publish a message from our client that contains the client-id, temperature and current time. We already have the client_id with us.</p> <p>We can use the datetime library for getting the timestamp.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># set timestamp from datetime import datetime now = datetime.now() </code></pre></div></div> <p>And we can generate a random number for the temperature.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># set temperature import random temp = random.randrange(10, 40) </code></pre></div></div> <p>So our message now looks like:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># form the message message = f'id: {client_id}, temp: {temp}, time: {now}' </code></pre></div></div> <p>Time to publish it with the publish method.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># publish the message from awscrt import mqtt import json mqtt_connection.publish( topic=args.topic, payload= json.dumps(message), qos=mqtt.QoS.AT_LEAST_ONCE ) print('Message published') </code></pre></div></div> <p>Note that awscrt is the AWS common runtime library we are using to set the <a href="http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349263">QoS</a>.</p> <p>Put this code in a separate file with name <em>publisher.py</em> and run it.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ python publisher.py --ep $IOT_DEV_EP --pubcert pub-cert.pem --pvtkey pvt-key.pem --cacert ca-cert.pem --topic temperature client-cb3f69b6-b53b-42a4-973f-63abe39f2c4f is connected! Message published </code></pre></div></div> <p>So far we published only one message, I would be modifying the code so that it continuously sends one message per second until interrupted with Ctrl C.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat publisher.py # import vars from connect.py from connect import args, client_id, mqtt_connection from awscrt import mqtt from datetime import datetime import json, random, time while True: # set timestamp now = datetime.now() # set temperature temp = random.randrange(10, 40) # form the message message = f'id: {client_id}, temp: {temp}, time: {now}' # publish the message mqtt_connection.publish( topic=args.topic, payload= json.dumps(message), qos=mqtt.QoS.AT_LEAST_ONCE ) print(f'Message published: {message}') time.sleep(1) </code></pre></div></div> <p>Run the code again.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ python publisher.py --ep $IOT_DEV_EP --pubcert pub-cert.pem --pvtkey pvt-key.pem --cacert ca-cert.pem --topic temperature client-1102832d-a0c0-481c-b1f4-5b363f9c0890 is connected! Message published: id: client-1102832d-a0c0-481c-b1f4-5b363f9c0890, temp: 14, time: 2022-07-17 09:20:44.652955 Message published: id: client-1102832d-a0c0-481c-b1f4-5b363f9c0890, temp: 29, time: 2022-07-17 09:20:45.654102 Message published: id: client-1102832d-a0c0-481c-b1f4-5b363f9c0890, temp: 35, time: 2022-07-17 09:20:46.655002 </code></pre></div></div> <p>Publishing looks good, let’s go to the subscriber.</p> <h2 id="subscriber">Subscriber</h2> <p>Firts, import certain vars from the connect module, similar to what we did in publisher.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># import vars from connect.py from connect import args, mqtt_connection </code></pre></div></div> <p>Define a callback function that triggers when a message is received on the topic.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># call back to trigger when a message is received def on_message_received(topic, payload, dup, qos, retain, **kwargs): print("Received message from topic '{}': {}".format(topic, payload)) </code></pre></div></div> <p>Subscribe to the topic.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>##### subscribe to topic from awscrt import mqtt subscribe_future, packet_id = mqtt_connection.subscribe( topic=args.topic, qos=mqtt.QoS.AT_LEAST_ONCE, callback=on_message_received ) # result() waits until a result is available subscribe_result = subscribe_future.result() print(f'Subscribed to {args.topic}') </code></pre></div></div> <p>We need to the keep the program open, so that we can read the messages, as defined in the callback function. For this, we can use the threading module.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import threading threading.Event().wait() </code></pre></div></div> <p>Keep this code in a file named subscriber.py.</p> <p>Time to run the subscriber code while the publisher code is also running. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pymtve3ooe2yg0qilgb1.png" alt="Pub/Sub on clients" /></p> <h2 id="test-on-console">Test on console</h2> <p>You can also test if the publish/subscribe operations are working correctly via the handy MQQT test client on AWS cloud. So if you are publishig from the code, you can test it at the subscriber window. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/baw6ios95lo1he3naur7.png" alt="Sub on MQTT test client" /></p> <p>And likewise if you are subscribing on the code, you can publish a test message from the MQTT test client. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wlccrt9k3jocpzwcmmjb.png" alt="Pub on MQTT test client" /></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python3 subscriber.py --ep $IOT_DEV_EP --pubcert pub-cert.pem --pvtkey pvt-key.pem --cacert ca-cert.pem --topic temperature client-a17093b1-108e-4f3c-a65c-ea38900f2153 is connected! Subscribed to temperature Received message from topic 'temperature': b'{\n "message": "Hello from AWS IoT console"\n}' </code></pre></div></div> <p>With this the post is complete ;), thank you for reading !!!. For other code examples provided by the AWS team, please checkout this <a href="https://github.com/aws/aws-iot-device-sdk-python-v2/tree/main/samples">github link</a></p>This post first appeared on dev.toSetup IoT core on Google cloud with Terraform2022-07-11T00:00:00+00:002022-07-11T00:00:00+00:00/cloud,/googlecloud,/iot,/terraform/2022/07/11/setup-iot-core-on-google-cloud-with-terraform<p><em>This post first appeared on <a href="https://dev.to/networkandcode/setup-iot-core-on-google-cloud-with-terraform-4h6a">dev.to</a></em></p> <p>Hello :wave:, we shall see how to provision a minimal IoT infrastructure on Google cloud with Terraform.</p> <p>I shall be doing this straight on the Google cloud shell…</p> <h2 id="project">Project</h2> <p>Set your gcloud config…</p> <p>Get you projects list and set one of the projects as the current project.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud projects list $ gcloud config set project &lt;project-id&gt; </code></pre></div></div> <h2 id="directories">Directories</h2> <p>Let’s create two directories for the terraform resources, one for the service account and another for rest of the resources.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir ~/sa $ mkdir ~/iot </code></pre></div></div> <p>and one more hidden directory for storing the keys/certificates.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir ~/.auth </code></pre></div></div> <h2 id="tf-provider">TF Provider</h2> <p>We would set the Terraform provider configuration here.</p> <p>Get the list of zones in the specific region. Note that cloud IoT is currently supported in these regions: asia-east1, europe-west1, us-central1.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud compute zones list --filter="region~asia-east1" | grep -i name NAME: asia-east1-b NAME: asia-east1-a NAME: asia-east1-c </code></pre></div></div> <p>I would be using zone c.</p> <p>Set the provider details in terraform with the available information.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/sa/main.tf provider "google" { project = "&lt;project-id&gt;" region = "asia-east1" zone = "asia-east1-c" } $ cp ~/sa/main.tf ~/iot/main.tf </code></pre></div></div> <h2 id="service-account">Service account</h2> <p>We are going to create a service account from our user account, which could be further used for creating other resources using terraform.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls ~/sa main.tf outputs.tf sa.tf $ cat ~/sa/sa.tf # https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account resource "google_service_account" "iot_sa" { account_id = "iot-sa" display_name = "IoT Service Account" } # note this requires the terraform to be run regularly resource "time_rotating" "iot_sa_key_rotation" { rotation_days = 30 } resource "google_service_account_key" "iot_sa_key" { service_account_id = google_service_account.iot_sa.name keepers = { rotation_time = time_rotating.iot_sa_key_rotation.rotation_rfc3339 } } resource "google_project_iam_member" "iot_editor" { project = var.project_id role = "roles/cloudiot.editor" member = "serviceAccount:${google_service_account.iot_sa.email}" condition { title = "expires_after_2022_07_31" description = "Expiring at midnight of 2022-07-31" expression = "request.time &lt; timestamp(\"2022-08-01T00:00:00Z\")" } } resource "google_project_iam_member" "pub_sub_editor" { project = var.project_id role = "roles/pubsub.editor" member = "serviceAccount:${google_service_account.iot_sa.email}" condition { title = "expires_after_2022_07_31" description = "Expiring at midnight of 2022-07-31" expression = "request.time &lt; timestamp(\"2022-08-01T00:00:00Z\")" } } $ cat ~/sa/variables.tf variable "project_id" { type = string default = "&lt;project-id&gt;" } $ cat ~/sa/outputs.tf output "iot_sa_private_key" { description = "Private key of the IoT service account" value = google_service_account_key.iot_sa_key.private_key sensitive = true } </code></pre></div></div> <p>So we are creating a service account with editor roles on IoT core &amp; Pub/Sub, a key for the service account with rotation, and then we would output the private key to save it locally for future use.</p> <h2 id="api">API</h2> <p>We have to enable the Cloud IoT <a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_service">API</a>, you can get the fqdn of it using <code class="language-plaintext highlighter-rouge">$ gcloud services list --available --filter="name~.*iot.*"</code>. Let’s add the terraform configuration which can enable it.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat ~/sa/variables.tf variable "project_id" { type = string default = "&lt;project-id&gt;" } $ cat ~/sa/apis.tf resource "google_project_service" "cloudiot" { project = var.project_id service = "cloudiot.googleapis.com" timeouts { create = "30m" update = "40m" } disable_dependent_services = true } </code></pre></div></div> <h2 id="apply">Apply</h2> <p>We can now create the service account, it’s associated resources, and enable the Cloud IoT API.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd ~/sa $ terraform init # optional, to know what will be changed $ terraform plan $ terraform apply --auto-approve </code></pre></div></div> <h2 id="validate">Validate</h2> <p>Validate the service account creation, via the console. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osu1meit8btydayjxdx9.png" alt="Service account" /></p> <p>And the roles attached to it. <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yg9mlgi34dhr1jgbe229.png" alt="Service account roles" /></p> <h2 id="key">Key</h2> <p>The private key of the service account could be retrieved from the terraform output.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ terraform output -raw iot_sa_private_key | base64 -d &gt; ~/.auth/iot_sa_private_key.json </code></pre></div></div> <p>We have saved the base64 decoded private key in a hidden auth directory at home.</p> <h2 id="credentials">Credentials</h2> <p>We could now start using the service principal’s private key as a credential for rest of our Terraform activities, for which we have to set an environment variable.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ export GOOGLE_APPLICATION_CREDENTIALS=~/.auth/iot_sa_private_key.json </code></pre></div></div> <p>Note: to remove the credential anytime, jus run <code class="language-plaintext highlighter-rouge">unset GOOGLE_APPLICATION_CREDENTIALS</code></p> <h2 id="certificate">Certificate</h2> <p>The connection between the IoT devices and Google IoT core would be secure over TLS, hence a <a href="https://cloud.google.com/iot/docs/create-device-registry#create_your_credentials">certificate</a> should be generated for our virtual device.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ openssl req -x509 -newkey rsa:2048 -keyout ~/.auth/rsa_private.pem -nodes -out ~/.auth/rsa_cert.pem -subj "/CN=unused" $ ls ~/.auth/ | grep pem rsa_cert.pem rsa_private.pem </code></pre></div></div> <p>The private key is in rsa_private.pem and the public certificate is in rsa_cert.pem.</p> <p>We would keep the private key locally and refer to it while generating a client connection from our device(we are not dealing with the client side of things in this post though), where as the public certificate would be attached to the remote side, in this case, the IoT core.</p> <h2 id="registry">Registry</h2> <p>Add the <a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_registry">terrafaorm</a> configuration for the device registry, pub/sub topics it would use.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd ~/iot $ cat registry.tf # https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_registry resource "google_cloudiot_registry" "iot-registry" { name = "iot-registry" event_notification_configs { pubsub_topic_name = google_pubsub_topic.additional-telemetry.id subfolder_matches = "test/path" } event_notification_configs { pubsub_topic_name = google_pubsub_topic.default-telemetry.id subfolder_matches = "" } state_notification_config = { pubsub_topic_name = google_pubsub_topic.default-devicestatus.id } mqtt_config = { mqtt_enabled_state = "MQTT_ENABLED" } http_config = { http_enabled_state = "HTTP_ENABLED" } log_level = "INFO" } </code></pre></div></div> <p>We would be using 3 topics, all messages published by the client to the path /devices/DEVICE_ID/events would go to the default telemetry topic, and all messages for /devices/DEVICE_ID/state would go to the default device state topic. We have one additional topic with sub folder path “test/path” which means the messages published to /devices/DEVICE_ID/events/test/path would land there.</p> <h2 id="pubsub">Pub/Sub</h2> <p>A separate file for creating the pub/sub topics which will be linked to the registry.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>resource "google_pubsub_topic" "default-devicestatus" { name = "default-devicestatus" } resource "google_pubsub_topic" "default-telemetry" { name = "default-telemetry" } resource "google_pubsub_topic" "additional-telemetry" { name = "additional-telemetry" } </code></pre></div></div> <h2 id="devices">Devices</h2> <p>We would be creating two devices, a basic one which should bind with the gateway, and an advanced device that could be standalone with out a gateway.</p> <p>The authentication for the basic device will be handled by the gateway and hence, we don’t have to set any credentials for the basic device.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat basic-device.tf # https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_device resource "google_cloudiot_device" "basic-device" { name = "basic-device" registry = google_cloudiot_registry.iot-registry.id } $ cat advanced-device.tf # https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_device resource "google_cloudiot_device" "advanced-device" { name = "advanced-device" registry = google_cloudiot_registry.iot-registry.id credentials { public_key { format = "RSA_X509_PEM" key = file("~/.auth/rsa_cert.pem") } } } </code></pre></div></div> <h2 id="gateway">Gateway</h2> <p>And now the gateway.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudiot_device resource "google_cloudiot_device" "iot-gateway" { name = "iot-gateway" registry = google_cloudiot_registry.iot-registry.id credentials { public_key { format = "RSA_X509_PEM" key = file("~/.auth/rsa_cert.pem") } } gateway_config { gateway_type = "GATEWAY" gateway_auth_method = "ASSOCIATION_ONLY" } } </code></pre></div></div> <p>I have setup ASSOCIATION_ONLY as the auth method, which means the device I will bind to this gateway would rely on this gateway for authentication and woudln’t authenticate with its own credential.</p> <h2 id="apply-1">Apply</h2> <p>The resources can be created.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ terraform init # optional, to see what will change $ terraform plan $ terraform apply </code></pre></div></div> <h2 id="bind">Bind</h2> <p>The basic device should be bounded with the gateway, so that the gateway generate JWTs on behalf of the device.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gcloud iot devices gateways bind --gateway iot-gateway --gateway-region asia-east1 --gateway-registry iot-registry --device basic-device --device-region asia-east1 </code></pre></div></div> <p>I used gcloud for binding the device with the gateway as I was not able to quite find it in the terraform registry.</p> <h2 id="validate-1">Validate</h2> <p>Finally, check the resources on the console.</p> <p><em>Registry</em> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy51868va1nsh7wet6b4.png" alt="Registry" /></p> <p><em>Devices</em> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aikr23urwn2o5sj9h9qu.png" alt="Devices" /></p> <p><em>Gateway</em> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tayjuc9w5we9nwmsawrj.png" alt="Gateway" /></p> <p><em>Device binding</em> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f65crdcmkjl60zzu6u3b.png" alt="Device binding" /></p> <p>Seems all good :)</p> <h2 id="graph">Graph</h2> <p>Let’s look at the graph that terraform can generate. We can view it on the cloud shell editor itself.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ terraform graph | dot -Tsvg &gt; graph.svg $ ls *.svg graph.svg </code></pre></div></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yobgj0gj78kor9hxoymo.png" alt="Graph" /></p> <p>With this the post is complete, thanks for reading !!!</p>This post first appeared on dev.to