Why Google Cloud is the ideal platform for Block.one and other DLT companies

Late last year, Google Cloud joined the EOS community, a leading open-source platform for blockchain innovation and performance, and is taking steps to support the EOS Public Blockchain by becoming a block producer (BP). At the time, we outlined how our planned participation underscores the importance of blockchain to the future of business, government, and society. Today, I want to outline why Google Cloud is uniquely positioned to be an excellent partner for Block.one and other distributed ledger technology (DLT) companies.We’ve recently seen an unprecedented rate of digital transformation across all industries, as a huge proportion of the economy has moved online. New startups, along with legacy businesses reimagining themselves as software companies, are in aggregate anticipated to account for thirty percent of economic activity by 2025, up from one percent today.As this digital transformation takes hold, businesses increasingly need to build integrated service networks with strong requirements for trust and coordination. This is what a DLT can provide. The EOSIO protocol, developed by Block.one and the basis for the EOS Public Blockchain, is an example of such a DLT. It’s built for speed, scale, and low-cost transactions—all of which make EOSIO an attractive platform upon which to build networked applications. This is where Google Cloud comes in. We are uniquely qualified to help Block.one and other companies develop and operate their DLT networks. A number of our products are well-suited to DLT applications, whether it is the scalability and reliability of our network, our innovation in Confidential Computing, or our leadership in AI/ML and data analytics. Confidential ComputingConfidential Computing is an emerging technology that encrypts data in-use—while it is being processed. Confidential Computing environments keep data encrypted in memory and elsewhere outside of the CPUs. Along with Google Cloud’s advanced capabilities around data in-transit and at-rest, Confidential Computing adds a “third pillar” to encryption by encrypting data while in-use. Confidential Computing is available in nine Google Cloud regions and will continue to extend to a broader set of the regions to support customers like Block.one. Confidential Computing leverages the secure encrypted virtualization supported by 2nd Gen AMD EPYC™ CPUs, ensuring data will stay private and encrypted while it is used, indexed, queried, or trained on. Confidential VMs followed by Confidential GKE Nodes are the first two products in Google Cloud’s Confidential Computing portfolio. Confidential VMs and Confidential GKE Nodes offer the cryptographic level of isolation while giving customers an easy-to-use solution that doesn’t require changing code in apps or compromising on performance. Computing directly on encrypted data is a must-have for the custody and handling of digital assets, and it creates exciting new possibilities, such as machine learning on private data, decentralized exchange of assets, and preventing collusion, exfiltration, and contamination of the network by rogue peers.AI and Data AnalyticsGoogle Cloud’s leading Cloud AI services, and the smart analytics services upon which they are built, enable businesses to get more value out of their data. The broad applicability of this pattern is evident from its many and varied use cases, such as AI for trade finance and decision support for advertising. DLT data on open networks are inherently public and can thus be indexed and made searchable, as we’ve demonstrated and continue to do for Bitcoin, Ethereum, and a number of other public DLTs, and our partners have followed our lead by ETL of DLT data into BigQuery.Perhaps more importantly, exciting new opportunities emerge by combining Cloud AI with Confidential Computing. For example, by executing DLT smart contracts within a trusted execution environment, machine learning accelerators such as Cloud TPU can be used for DLT coprocessing. In addition to computing capabilities, the trustworthiness of APIs can also be ensured and this allows external data to be used in smart contracts. We’ve previously written about the possibilities of building DLT/cloud hybrid applications.Network Performance and SecurityGoogle Cloud’s low-latency premium network tier allows peers to synchronize more quickly, enabling the higher transaction throughputs. Our network also peers directly with many ISPs, meaning that there’s less lag when customers interact with their digital assets, critical to real-world use cases such as retail point-of-sale and gaming. Google’s systems are designed for security and reliability on a global scale. When DLT customers are selecting a cloud platform, a huge part of what they’re looking for is infrastructure. Our infrastructure doesn’t rely on any single technology to make it secure. Our stack builds security through progressive layers that deliver defense in depth. From the physical premises to the purpose-built servers, networking equipment and custom security chips, to the low-level software stack running on every machine, our entire hardware infrastructure is controlled, secured, built and hardened by Google. Learn more and get involvedDevelopers, you can learn more about projects built with EOSIO on Google Cloud in the EOSIO Beyond Blockchain Hackathon—submissions are open until April 6, 2021. Or get building right away by learning how to build with EOSIO. Keep up with Google’s latest EOS block producer activities at https://dlt.withgoogle.com/eos.
Quelle: Google Cloud Platform

Speeding up small queries in BigQuery with BI Engine

A quick and easy way to speed up small queries in BigQuery (such as to populate interactive applications or dashboards) is to use BI Engine. The New York Times, for example,uses the SQL interface to BI Engine to speed up their Data Reporting Engine.The queriesTo Illustrate, I’ll use three representative queries on tables between 100 MB and 3 GB — tables that are typically considered smallish by BigQuery standards. Because BigQuery is a columnar database, I’m reporting only the size of the columns that the query needs to read.The first involves processing about 400 MB data to find the average cost of a Medicare claim:The second processes a larger table (3GB) to find the average tip for a Chicago taxi based on payment type:The third processes a smaller table (100 MB) to find the most polluted sites:Measuring query performanceTo measure the query performance, I’ll run any given query 5 times, taking care to turn off the cache:The code prints out the total resources consumed (slot-milliseconds) and the total time taken by the query on the server.If you are on a flat-rate pricing (most enterprise customers are), the slot-milliseconds reflects how long your slots are getting used. So, this is a reflection of the cost of the query to you.The time elapsed is the time spent in computing the results. Note that the server_time is the time taken to process the request (I don’t measure the network roundtrip time because it’s going to be the same whether or not you use BI Engine).I first ran the three queries without BI Engine (see my notebook in GitHub).Turning on BI EngineI then went to the BigQuery web console and created a 10 GB BI Engine reservation (monthly cost: $300):Why did I pick 10 GB?One of the key ways that BI Engine speeds up queries on small tables is that it caches the tables in memory. It also does other speedups, but this is the main one. So, you want to provide it enough memory to comfortably hold the tables you will be querying. BI Engine will automatically manage the memory for you.In my case, the Chicago taxicab query involves 3GB, so I used 10 GB. For the other two queries, 1 GB would have been enough. You can go up to 100 GB, something I’d recommend if you have many concurrent queries on small tables. You might be able to get away with less because BI Engine stores data compressed, but it’s better to provide some extra room.You can turn on BI Engine from a script using the Reservations API. Note, however, that it takes a few minutes for the memory to become available, so this is something you should consider doing for a few hours at least, not on a per-query basis.Turning on BI EngineHere’s how the 3 queries compared with and without BI Engine:As you can see, I got cost improvements of 8x to 20x and timing improvements of about 2x. Note that some BI Engine capacity is included at no extra cost when you purchase a flat rate reservation–for example, you get 50 GB of BI Engine included for free with a 1000-slot annual commitment. At $30-$300/month, additional BI Engine capacity is a very cost-effective way to make your BigQuery reservations go further, and get a speedup too. Enjoy!Next stepsTry it out.My code is on GitHub.For more about BI Engine and how to enroll for the preview, see this blog post.You can get the full speedup in your applications provided you are using the query() method in the BigQuery Client API [the insertJob() methods are also accelerated, but not as much]. So, check your code.To learn more about BigQuery,read my book.Note: The SQL interface to BI Engine is in preview at the time of writing (April 2021). Use this enrollment form to turn this on in your account. Thanks to Mosha Pasumansky and the BI Engine team for helpful discussions.Related Article[New blog series] BigQuery explained: An overviewOur new blog series provides an overview of what’s possible with BigQuery.Read Article
Quelle: Google Cloud Platform

Analyze your GKE and GCE logging usage data easier with new dashboards

System and application logs provide crucial data for operators and developers to troubleshoot and keep applications healthy. Google Cloud automatically captures log data for its services and makes it available in Cloud Logging and Cloud Monitoring. As you add more services to your fleet, tasks such as determining a budget for storing logs data and performing granular cross-project analysis can become challenging. That’s why today we’re proud to announce a set of open source JSON dashboards that can be imported into Cloud Monitoring to help you quickly analyze logging volumes, logs-based metrics and information about your logging exports across multiple projects. The dashboards we are releasing today include:Logging management dashboardGKE logging usageGCE logging usageCloud SQL logging usageLogging Management dashboardThe Logs Storage section of the Cloud Console provides a summary of logging usage data for an individual project including the current total logging volume, previous billed volume and a projected volume estimate for the current month.Categories in the Logs Storage summaryWhile this aggregate level is sufficient for those who just need a high-level view of their usage, you may need to analyze logging usage data across multiple projects or investigate your logging data at a more granular level. The Logging Management dashboard provides that aggregation for any projects included in your Cloud Monitoring Workspace so you are not limited to analyzing just one project at a time.A view of the Logging Management dashboard in Cloud Monitoring, providing aggregated logs metricsUsing standard filters that are available in Cloud Monitoring, you can refine the data to do a more granular analysis, such as display a specific project, log name or log severity. For example, errors tend to provide the most critical signals for applications, and filtering the charts to include just error logs may help identify specific projects and resources to investigate.A filtered view of the ingested error logs in the Logging Management dashboard Logging Usage – Kubernetes dashboardThe Logging usage dashboard for GKE provides an aggregated view of logging metrics for any GKE clusters running in projects included in your Cloud Monitoring Workspace. The views are grouped by cluster, container, pod and namespace.A view of a subset of the tables available in the GKE logging usage dashboard in Cloud Monitoring  Using this dashboard, you can filter the dashboard by resource to understand the logging metrics for the specific Kubernetes resource. For example, filtering by cluster_name scopes all the charts in the dashboard to the Kubernetes containers, pods and namespaces running in the selected GKE cluster.A view of logging metrics at the cluster levelBy expanding the chart legend, you can also filter the chart to the selected resources. In the example below, the volume of logs ingested is displayed specifically for the selected resource in the specific Pod.Logging metrics for just one resource displayed at the pod levelThe logging usage dashboard is a logging management complement to the GKE Dashboard in Cloud Monitoring, which we rolled out last year. The GKE Dashboard provides detailed information about metrics and error logs to use for troubleshooting your services.Logging usage- GCE & other dashboardsThe github repo includes other dashboards built specifically for services like Compute Engine and Cloud SQL. A top-level view of the tables available in the GCE logging usage dashboardSet alerts and customize furtherWhile you can analyze important usage metrics for Cloud Logging projects in aggregate or filter to specific logs, to take full advantage of the capabilities of Cloud Monitoring, you can also set proactive alerts on the underlying metrics in the dashboards. Alerts can be set on any metric, such as logging usage volumes or errors, so you are notified when they exceed your specified threshold.  Additionally, any of the dashboards can be further customized with our new Monitoring Dashboard builder and if you’re willing to share what you’ve created, send us a pull request against the Monitoring dashboard samples github repo.Learn more about Cloud LoggingGet started today with the Log Explorer, learn more about Cloud Logging with our qwiklab quest and join us in our discussion forum. As always, we welcome your feedback.
Quelle: Google Cloud Platform

How reCAPTCHA Enterprise protected customers during the holidays

Every business had to adapt to a new reality in 2020, and make online business their primary channel. But as online business increased, so did web-based attacks. In research commissioned by Forrester consulting, 84% of companies have seen an increase in bot attacks. 71% of organizations have seen an increase in the amount of successful attacks. 65% of businesses have experienced more frequent attacks and greater revenue loss due to bot attacks. With online fraud expected to only increase, the security of web pages has never been more important.Online fraud and abuse impacts various industries differently, ranging from inventory problems to account access difficulties. Attack methods also vary; some businesses have to deal with frequent credential stuffing or payment fraud attacks, and some are more subject to account takeovers to spam logins. Credential stuffing is one of the most common attacks our customers face, due to a spike in the availability of usernames and passwords from a wide range of successful breaches, and the ease of scripting these kinds of attacks. Account takeovers are another common attack type, as billions of account records have been leaked over the last several years from breaches, and these credentials have been posted and sold on the dark web. While the attacks are varied, they all share the same end result: damage to your business, customers, and bottom line.Successful online businesses require successful online security The more digital an organization becomes, the more its success is tied to its ability to understand and manage online attacks. And though the 2020 holiday season unleashed more online attacks than ever before, customers using reCAPTCHA Enterprise were prepared. Any organization that conducts business online can be susceptible to online fraud. But this susceptibility can be mitigated by reCAPTCHA Enterprise, which is particularly helpful for businesses in the retail, gaming, media, entertainment, software and internet industries. reCAPTCHA Enterprise customers create, sell, offer or manage everything from smart home devices, to office supplies, to software, online marketplaces, social media, and streaming services. And all of them face a myriad of automated attacks that, unless properly defended, could weaken their businesses.For example, retailers need protection from bots putting inventory in their shopping carts, thereby decreasing the amount of inventory available to legitimate customers. They are sometimes faced with malicious attempts to identify missing start/expiry dates and security codes for stolen payment card data, by bots that test different values and personal information at checkout. Gaming, media, and entertainment customers are challenged by bad actors trying to log in into a legitimate customer’s account with stolen credentials. Event companies deal with automated scalping, with bots buying up tickets and then reselling them later at a profit. And many vendors are challenged by repeated attempts to use a coupon number, voucher code or discount token on web pages during payment. Halting 2020 holiday hacksThe most common attacks our customers experienced this holiday season were credential stuffing, followed by scraping, card fraud, and account takeovers.In a credential stuffing attack, bots list stolen credentials against an application’s authentication mechanisms to identify whether users have reused the same login credentials. The stolen usernames (often email addresses) and password pairs could have been sourced directly from another application by the attacker, purchased in a criminal marketplace, or obtained from publicly available breach data dumps. reCAPTCHA Enterprise detects and stops credential stuffing attacks by recognizing bot behavior and introducing friction into the bot’s attempt at an attack—alerting that an attack is taking place, and implementing a response like two-factor authentication to defeat the attempt while letting valid users through the website. In a scraping attack, large volumes of data are extracted from web pages and applications. Scraping can be used to collect personal data from social media accounts, which malicious actors use to create applications for loans, credit cards, or other forms of identification. Scraping can also be used to collect legitimate information about products or services, and then create fake products and services and trick buyers into purchasing them. reCAPTCHA Enterprise uses an adaptive risk analysis engine to keep malicious software from engaging in abusive activities on your site. Another type of fraud that has been prominent in the last year is card cracking. Fraudsters often use automated tools to verify stolen credit cards before they’re sold or used. reCAPTCHA uses machine learning models that analyze site-specific behavior to recognize patterns of legitimate and fraudulent transactions and detect this type of abuse. reCAPTCHA Enterprise returns a score based on interactions with your websites, with 1.0 being a likely good interaction and 0.0 being a likely abusive action. This can reduce the transaction costs of such abuse, and prevent larger scale attacks resulting from the use of stolen payment mechanisms.Sometimes, a bad actor will use a stolen or leaked credential to log in and access a legitimate user’s account, in an attack called an account takeover. Account takeovers are typically followed by the attacker transferring money, buying a gift card or making purchases with the user’s account. The reCAPTCHA Enterprise API risk score gives you the granularity and flexibility to protect your webpages in the way that makes the most sense to your business; you can decide which action to take based on that score. There’s no one-size-fits-all approach to managing risk, so you should have the levels of protection for different web pages. A suspected fraudulent request on a login page could force a two-factor authorization challenge, while you could just block the request on a less valuable webpage.reCAPTCHA Enterprise is built to help mitigate fraudulent online activity for your enterprise, with technology that has helped defend millions of websites for over a decade. The number and types of attacks your business will experience will only increase over time, so it’s important to remember that the success of your business is dependent on how well you can protect against these attacks. To protect your business from online fraud and abuse, get started with reCAPTCHA Enterprise today.
Quelle: Google Cloud Platform

Compiling Containers – Dockerfiles, LLVM and BuildKit

Today we’re featuring a blog from Adam Gordon Bell at Earthly who writes about how BuildKit, a technology developed by Docker and the community, works and how to write a simple frontend. Earthly uses BuildKit in their product.

Introduction

How are containers made? Usually, from a series of statements like `RUN`, `FROM`, and `COPY`, which are put into a Dockerfile and built.  But how are those commands turned into a container image and then a running container?  We can build up an intuition for how this works by understanding the phases involved and creating a container image ourselves. We will create an image programmatically and then develop a trivial syntactic frontend and use it to build an image.

On `docker build`

We can create container images in several ways. We can use Buildpacks, we can use build tools like Bazel or sbt, but by far, the most common way images are built is using `docker build` with a Dockerfile.  The familiar base images Alpine, Ubuntu, and Debian are all created this way.     

Here is an example Dockerfile:

FROM alpine
COPY README.md README.md
RUN echo “standard docker build” > /built.txt”

We will be using variations on this Dockerfile throughout this tutorial. 

We can build it like this:

docker build . -t test

But what is happening when you call `docker build`? To understand that, we will need a little background.

Background

 A docker image is made up of layers. Those layers form an immutable filesystem.  A container image also has some descriptive data, such as the start-up command, the ports to expose, and volumes to mount. When you `docker run` an image, it starts up inside a container runtime.

 I like to think about images and containers by analogy. If an image is like an executable, then a container is like a process. You can run multiple containers from one image, and a running image isn’t an image at all but a container.

Continuing our analogy, BuildKit is a compiler, just like LLVM.  But whereas a compiler takes source code and libraries and produces an executable, BuildKit takes a Dockerfile and a file path and creates a container image.

Docker build uses BuildKit, to turn a Dockerfile into a docker image, OCI image, or another image format.  In this walk-through, we will primarily use BuildKit directly.

This primer on using BuildKit supplies some helpful background on using BuildKit, `buildkitd`, and `buildctl` via the command-line. However, the only prerequisite for today is running `brew install buildkit` or the appropriate OS equivalent steps.

How Do Compilers Work?

A traditional compiler takes code in a high-level language and lowers it to a lower-level language. In most conventional ahead-of-time compilers, the final target is machine code. Machine code is a low-level programming language that your CPU understands.

Fun Fact: Machine Code VS. Assembly

Machine code is written in binary. This makes it hard for a human to understand.  Assembly code is a plain-text representation of machine code that is designed to be somewhat human-readable. There is generally a 1-1 mapping between instructions the machine understands (in machine code) and the OpCodes in Assembly

Compiling the classic C “Hello, World” into x86 assembly code using the Clang frontend for LLVM looks like this:

Creating an image from a dockerfile works a similar way:

BuildKit is passed the Dockerfile and the build context, which is the present working directory in the above diagram. In simplified terms, each line in the dockerfile is turned into a layer in the resulting image.  One significant way image building differs from compiling is this build context.  A compiler’s input is limited to source code, whereas `docker build` takes a reference to the host filesystem as an input and uses it to perform actions such as `COPY`.

There Is a Catch

The earlier diagram of compiling “Hello, World” in a single step missed a vital detail. Computer hardware is not a singular thing. If every compiler were a hand-coded mapping from a high-level language to x86 machine code, then moving to the Apple M1 processor would be quite challenging because it has a different instruction set.  

Compiler authors have overcome this challenge by splitting compilation into phases.  The traditional phases are the frontend, the backend, and the middle. The middle phase is sometimes called the optimizer, and it deals primarily with an internal representation (IR).

This staged approach means you don’t need a new compiler for each new machine architecture. Instead, you just need a new backend. Here is an example of what that looks like in LLVM:

Intermediate Representations

This multiple backend approach allows LLVM to target ARM, X86, and many other machine architectures using LLVM Intermediate Representation (IR) as a standard protocol.  LLVM IR is a human-readable programming language that backends need to be able to take as input. To create a new backend, you need to write a translator from LLVM IR to your target machine code. That translation is the primary job of each backend.

Once you have this IR, you have a protocol that various phases of the compiler can use as an interface, and you can build not just many backends but many frontends as well. LLVM has frontends for numerous languages, including C++, Julia, Objective-C, Rust, and Swift.  

If you can write a translation from your language to LLVM IR, LLVM can translate that IR into machine code for all the backends it supports. This translation function is the primary job of a compiler frontend.

In practice, there is much more to it than that. Frontends need to tokenize and parse input files, and they need to return pleasant errors. Backends often have target-specific optimizations to perform and heuristics to apply. But for this tutorial, the critical point is that having a standard representation ends up being a bridge that connects many front ends with many backends. This shared interface removes the need to create a compiler for every combination of language and machine architecture. It is a simple but very empowering trick!

BuildKit

Images, unlike executables, have their own isolated filesystem. Nevertheless, the task of building an image looks very similar to compiling an executable. They can have varying syntax (dockerfile1.0, dockerfile1.2), and the result must target several machine architectures (arm64 vs. x86_64).

“LLB is to Dockerfile what LLVM IR is to C” – BuildKit Readme

This similarity was not lost on the BuildKit creators.  BuildKit has its own intermediate representation, LLB.  And where LLVM IR has things like function calls and garbage-collection strategies, LLB has mounting filesystems and executing statements.

LLB is defined as a protocol buffer, and this means that BuildKit frontends can make GRPC requests against buildkitd to build a container directly.

Programmatically Making An Image

Alright, enough background.  Let’s programmatically generate the LLB for an image and then build an image.  

Using Go

In this example, we will be using Go which lets us leverage existing BuildKit libraries, but it’s possible to accomplish this in any language with Protocol Buffer support.

Import LLB definitions:

import (
“github.com/moby/buildkit/client/llb”
)

Create LLB for an Alpine image:

func createLLBState() llb.State {
return llb.Image(“docker.io/library/alpine”).
File(llb.Copy(llb.Local(“context”), “README.md”, “README.md”)).
Run(llb.Args([]string{“/bin/sh”, “-c”, “echo “programmatically built” > /built.txt”})).     
Root()
}

We are accomplishing the equivalent of a `FROM` by using `llb.Image`. Then, we copy a file from the local file system into the image using `File` and `Copy`.  Finally, we `RUN` a command to echo some text to a file.  LLB has many more operations, but you can recreate many standard images with these three building blocks.

The final thing we need to do is turn this into protocol-buffer and emit it to standard out:

func main() {

dt, err := createLLBState().Marshal(context.TODO(), llb.LinuxAmd64)
if err != nil {
panic(err)
}
llb.WriteTo(dt, os.Stdout)
}

Let’s look at the what this generates using the `dump-llb` option of buildctl:

go run ./writellb/writellb.go |
buildctl debug dump-llb |
jq .

We get this JSON formatted LLB:

{
“Op”: {
“Op”: {
“source”: {
“identifier”: “local://context”,
“attrs”: {
“local.unique”: “s43w96rwjsm9tf1zlxvn6nezg”
}
}
},
“constraints”: {}
},
“Digest”: “sha256:c3ca71edeaa161bafed7f3dbdeeab9a5ab34587f569fd71c0a89b4d1e40d77f6″,
“OpMetadata”: {
“caps”: {
“source.local”: true,
“source.local.unique”: true
}
}
}
{
“Op”: {
“Op”: {
“source”: {
“identifier”: “docker-image://docker.io/library/alpine:latest”
}
},
“platform”: {
“Architecture”: “amd64″,
“OS”: “linux”
},
“constraints”: {}
},
“Digest”: “sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7″,
“OpMetadata”: {
“caps”: {
“source.image”: true
}
}
}
{
“Op”: {
“inputs”: [
{
“digest”: “sha256:665ba8b2cdc0cb0200e2a42a6b3c0f8f684089f4cd1b81494fbb9805879120f7″,
“index”: 0
},
{
“digest”: “sha256:c3ca71edeaa161bafed7f3dbdeeab9a5ab34587f569fd71c0a89b4d1e40d77f6″,
“index”: 0
}
],
“Op”: {
“file”: {
“actions”: [
{
“input”: 0,
“secondaryInput”: 1,
“output”: 0,
“Action”: {
“copy”: {
“src”: “/README.md”,
“dest”: “/README.md”,
“mode”: -1,
“timestamp”: -1
}
}
}
]
}
},
“platform”: {
“Architecture”: “amd64″,
“OS”: “linux”
},
“constraints”: {}
},
“Digest”: “sha256:ba425dda86f06cf10ee66d85beda9d500adcce2336b047e072c1f0d403334cf6″,
“OpMetadata”: {
“caps”: {
“file.base”: true
}
}
}
{
“Op”: {
“inputs”: [
{
“digest”: “sha256:ba425dda86f06cf10ee66d85beda9d500adcce2336b047e072c1f0d403334cf6″,
“index”: 0
}
],
“Op”: {
“exec”: {
“meta”: {
“args”: [
“/bin/sh”,
“-c”,
“echo “programmatically built” > /built.txt”
],
“cwd”: “/”
},
“mounts”: [
{
“input”: 0,
“dest”: “/”,
“output”: 0
}
]
}
},
“platform”: {
“Architecture”: “amd64″,
“OS”: “linux”
},
“constraints”: {}
},
“Digest”: “sha256:d2d18486652288fdb3516460bd6d1c2a90103d93d507a9b63ddd4a846a0fca2b”,
“OpMetadata”: {
“caps”: {
“exec.meta.base”: true,
“exec.mount.bind”: true
}
}
}
{
“Op”: {
“inputs”: [
{
“digest”: “sha256:d2d18486652288fdb3516460bd6d1c2a90103d93d507a9b63ddd4a846a0fca2b”,
“index”: 0
}
],
“Op”: null
},
“Digest”: “sha256:fda9d405d3c557e2bd79413628a435da0000e75b9305e52789dd71001a91c704″,
“OpMetadata”: {
“caps”: {
“constraints”: true,
“platform”: true
}
}
}

Looking through the output, we can see how our code maps to LLB.

Here is our `Copy` as part of a FileOp:

“Action”: {
“copy”: {
“src”: “/README.md”,
“dest”: “/README.md”,
“mode”: -1,
“timestamp”: -1
}

Here is mapping our build context for use in our `COPY` command:

“Op”: {
“source”: {
“identifier”: “local://context”,
“attrs”: {
“local.unique”: “s43w96rwjsm9tf1zlxvn6nezg”
}
}

Similarly, the output contains LLB that corresponds to our  `RUN` and `FROM` commands. 

Building Our LLB

To build our image, we must first start `buildkitd`:

docker run –rm –privileged -d –name buildkit moby/buildkit
export BUILDKIT_HOST=docker-container://buildkit

To build our image, we must first start `buildkitd`:

go run ./writellb/writellb.go |
buildctl build
–local context=.
–output type=image,name=docker.io/agbell/test,push=true

The output flag lets us specify what backend we want BuildKit to use.  We will ask it to build an OCI image and push it to docker.io. 

Real-World Usage

In the real-world tool, we might want to programmatically make sure `buildkitd` is running and send the RPC request directly to it, as well as provide friendly error messages. For tutorial purposes, we will skip all that.

We can run it like this:

> docker run -it –pull always agbell/test:latest /bin/sh

And we can then see the results of our programmatic `COPY` and `RUN` commands:

/ # cat built.txt
programmatically built
/ # ls README.md
README.md

There we go! The full code example can be a great starting place for your own programmatic docker image building.

A True Frontend for BuildKit

A true compiler front end does more than just emit hardcoded IR.  A proper frontend takes in files, tokenizes them, parses them, generates a syntax tree, and then lowers that syntax tree into the internal representation. Mockerfiles are an example of such a frontend:

#syntax=r2d4/mocker
apiVersion: v1alpha1
images:- name: demo
  from: ubuntu:16.04
  package:
    install:
    – curl
    – git
    – gcc

And because Docker build supports the `#syntax` command we can even build a Mockerfiles directly with `docker build`. 

docker build -f mockerfile.yaml

To support the #syntax command, all that is needed is to put the frontend in a docker image that accepts a gRPC request in the correct format, publish that image somewhere.  At that point, anyone can use your frontend `docker build` by just using `#syntax=yourimagename`.

Building Our Own Example Frontend for `docker build`

Building a tokenizer and a parser as a gRPC service is beyond the scope of this article. But we can get our feet wet by extracting and modifying an existing frontend. The standard dockerfile frontend is easy to disentangle from the moby project. I’ve pulled the relevant parts out into a stand-alone repo. Let’s make some trivial modifications to it and test it out.

So far, we’ve only used the docker commands `FROM`, `RUN` and `COPY`.  At a surface level, with its capitalized commands, Dockerfile syntax looks a lot like the programming language INTERCAL. Let change these commands to their INTERCAL equivalent and develop our own Ickfile format.

DockerfileIckfileFROMCOME FROMRUNPLEASECOPYSTASH

The modules in the dockerfile frontend split the parsing of the input file into several discrete steps, with execution flowing this way:

For this tutorial, we are only going to make trivial changes to the frontend.  We will leave all the stages intact and focus on customizing the existing commands to our tastes.  To do this, all we need to do is change `command.go`:

package command

// Define constants for the command strings
const (
Copy = “stash”
Run = “please”
From = “come_from”
…
)

And we can then see results of our `STASH` and `PLEASE` commands:

/ # cat built.txt
custom frontend built
/ # ls README.md
README.md

I’ve pushed this image to dockerhub.  Anyone can start building images using our `ickfile` format by adding `#syntax=agbell/ick` to an existing Dockerfile. No manual installation is required!

Enabling BuildKit

BuildKit is enabled by default on Docker Desktop. It is not enabled by default in the current version of Docker for Linux (`version 20.10.5`). To instruct `docker build` to use BuildKit set the following environment variable `DOCKER_BUILDKIT=1` or change the Engine config.

Conclusion

We have learned that a three-phased structure borrowed from compilers powers building images, that an intermediate representation called LLB is the key to that structure.  Empowered by the knowledge, we have produced two frontends for building images.  

This deep dive on frontends still leaves much to explore.  If you want to learn more, I suggest looking into BuildKit workers.  Workers do the actual building and are the secret behind `docker buildx`, and multi-archtecture builds. `docker build` also has support for remote workers and cache mounts, both of which can lead to faster builds.

Earthly uses BuildKit internally for its repeatable build syntax. Without it, our containerized Makefile-like syntax would not be possible. If you want a saner CI process, then you should check it out.

There is also much more to explore about how modern compilers work. Modern compilers often have many stages and more than one intermediate representation, and they are often able to do very sophisticated optimizations.
The post Compiling Containers – Dockerfiles, LLVM and BuildKit appeared first on Docker Blog.
Quelle: https://blog.docker.com/feed/