新增learn-kubernetes(https://github.com/yyong-brs/learn-kubernetes)相关文件
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
FROM node:16.13.1-alpine3.14 AS builder
|
||||
|
||||
WORKDIR /src
|
||||
COPY src/package.json .
|
||||
RUN npm install
|
||||
|
||||
# app
|
||||
FROM node:16.13.1-alpine3.14
|
||||
|
||||
EXPOSE 80
|
||||
ENV PORT=80
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /src/node_modules/ /app/node_modules/
|
||||
COPY src/ .
|
||||
@@ -0,0 +1,14 @@
|
||||
const { format, transports } = require('winston');
|
||||
var logConfig = module.exports = {};
|
||||
|
||||
logConfig.options = {
|
||||
format: format.combine(
|
||||
format.splat(),
|
||||
format.simple()
|
||||
),
|
||||
transports: [
|
||||
new transports.Console({
|
||||
level: 'info'
|
||||
})
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
const winston = require('winston');
|
||||
var logConfig = require('./config/logConfig');
|
||||
|
||||
const logger = winston.createLogger(logConfig.options);
|
||||
exports.Logger = logger;
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "access-log",
|
||||
"version": "1.0.0",
|
||||
"main": "server.js",
|
||||
"author": "kiamol",
|
||||
"dependencies": {
|
||||
"restify": "8.5.1",
|
||||
"winston": "3.3.3",
|
||||
"prom-client": "12.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
const restify = require("restify");
|
||||
const prom = require("prom-client");
|
||||
const log = require("./log");
|
||||
const os = require("os");
|
||||
|
||||
const accessCounter = new prom.Counter({
|
||||
name: "access_log_total",
|
||||
help: "Access Log - total log requests"
|
||||
});
|
||||
|
||||
const clientIpGauge = new prom.Gauge({
|
||||
name: "access_client_ip_current",
|
||||
help: "Access Log - current unique IP addresses"
|
||||
});
|
||||
|
||||
//setup Prometheus with standard metrics:
|
||||
prom.collectDefaultMetrics();
|
||||
|
||||
function stats(req, res, next) {
|
||||
log.Logger.debug("** GET /stats called");
|
||||
var data = {
|
||||
logs: logCount
|
||||
};
|
||||
res.send(data);
|
||||
next();
|
||||
}
|
||||
|
||||
function respond(req, res, next) {
|
||||
log.Logger.debug("** POST /access-log called");
|
||||
log.Logger.info("Access log, client IP: %s", req.body.clientIp);
|
||||
logCount++;
|
||||
|
||||
//metrics:
|
||||
accessCounter.inc();
|
||||
ipAddresses.push(req.body.clientIp);
|
||||
let uniqueIps = Array.from(new Set(ipAddresses));
|
||||
clientIpGauge.set(uniqueIps.length);
|
||||
|
||||
res.send(201, "Created");
|
||||
next();
|
||||
}
|
||||
|
||||
var logCount = 0;
|
||||
var ipAddresses = new Array();
|
||||
var server = restify.createServer();
|
||||
server.use(restify.plugins.bodyParser());
|
||||
server.get("/stats", stats);
|
||||
server.post("/access-log", respond);
|
||||
|
||||
server.get("/metrics", function(req, res, next) {
|
||||
res.end(prom.register.metrics());
|
||||
});
|
||||
|
||||
server.headersTimeout = 20;
|
||||
server.keepAliveTimeout = 10;
|
||||
server.listen(process.env.PORT, function() {
|
||||
log.Logger.info("%s listening at %s", server.name, server.url);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
ch14-access-log:
|
||||
image: kiamol/ch14-access-log:latest-linux-amd64
|
||||
|
||||
ch14-image-gallery:
|
||||
image: kiamol/ch14-image-gallery:latest-linux-amd64
|
||||
|
||||
ch14-image-of-the-day:
|
||||
image: kiamol/ch14-image-of-the-day:latest-linux-amd64
|
||||
@@ -0,0 +1,11 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
ch14-access-log:
|
||||
image: kiamol/ch14-access-log:latest-linux-arm64
|
||||
|
||||
ch14-image-gallery:
|
||||
image: kiamol/ch14-image-gallery:latest-linux-arm64
|
||||
|
||||
ch14-image-of-the-day:
|
||||
image: kiamol/ch14-image-of-the-day:latest-linux-arm64
|
||||
@@ -0,0 +1,17 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
ch14-access-log:
|
||||
image: kiamol/ch14-access-log:latest
|
||||
build:
|
||||
context: ./access-log
|
||||
|
||||
ch14-image-gallery:
|
||||
image: kiamol/ch14-image-gallery:latest
|
||||
build:
|
||||
context: ./image-gallery
|
||||
|
||||
ch14-image-of-the-day:
|
||||
image: kiamol/ch14-image-of-the-day:latest
|
||||
build:
|
||||
context: ./image-of-the-day
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM golang:1.15-alpine AS builder
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.mod .
|
||||
RUN go mod download
|
||||
|
||||
COPY main.go .
|
||||
RUN go build -o /server
|
||||
|
||||
# app
|
||||
FROM alpine:3.15
|
||||
|
||||
ENV IMAGE_API_URL="http://apod-api/image" \
|
||||
ACCESS_API_URL="http://apod-log/access-log"
|
||||
|
||||
CMD ["/web/server"]
|
||||
|
||||
WORKDIR /web
|
||||
COPY index.html .
|
||||
COPY --from=builder /server .
|
||||
@@ -0,0 +1,5 @@
|
||||
module kiamol/image-gallery
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/prometheus/client_golang v1.7.1
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Image Gallery</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">Image Gallery</h1>
|
||||
<h2>{{.Caption}}</h2>
|
||||
<img src="{{.Url}}"/>
|
||||
<footer class="blockquote-footer">© {{.Copyright}}</footer>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
Url string `json:"url"`
|
||||
Caption string `json:"caption"`
|
||||
Copyright string `json:"copyright"`
|
||||
}
|
||||
|
||||
type AccessLog struct {
|
||||
ClientIP string `json:"clientIp"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
tmpl := template.Must(template.ParseFiles("index.html"))
|
||||
imageApiUrl := os.Getenv("IMAGE_API_URL")
|
||||
logApiUrl := os.Getenv("ACCESS_API_URL")
|
||||
|
||||
//re-use HTTP client with minimal keep-alive
|
||||
tr := &http.Transport{
|
||||
MaxIdleConns: 1,
|
||||
IdleConnTimeout: 1 * time.Second,
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
//create Prometheus metrics
|
||||
inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "image_gallery_in_flight_requests",
|
||||
Help: "Image Gallery - in-flight requests",
|
||||
})
|
||||
requestCounter := prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "image_gallery_requests_total",
|
||||
Help: "Image Gallery - total requests",
|
||||
},
|
||||
[]string{"code", "method"},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(inFlightGauge, requestCounter)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
indexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
response,_ := client.Get(imageApiUrl)
|
||||
defer response.Body.Close()
|
||||
data,_ := ioutil.ReadAll(response.Body)
|
||||
image := Image{}
|
||||
json.Unmarshal([]byte(data), &image)
|
||||
tmpl.Execute(w, image)
|
||||
log := AccessLog{
|
||||
ClientIP: r.RemoteAddr,
|
||||
}
|
||||
jsonLog,_ := json.Marshal(log)
|
||||
response,_ = client.Post(logApiUrl, "application/json", bytes.NewBuffer(jsonLog))
|
||||
defer response.Body.Close()
|
||||
|
||||
})
|
||||
|
||||
wrappedIndexHandler := promhttp.InstrumentHandlerInFlight(inFlightGauge,
|
||||
promhttp.InstrumentHandlerCounter(requestCounter, indexHandler))
|
||||
|
||||
http.Handle("/", wrappedIndexHandler)
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
http.ListenAndServe(":80", nil)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
FROM maven:3.6.3-jdk-11 AS builder
|
||||
|
||||
WORKDIR /usr/src/iotd
|
||||
COPY pom.xml .
|
||||
RUN mvn -B dependency:go-offline
|
||||
|
||||
COPY . .
|
||||
RUN mvn package
|
||||
|
||||
# app
|
||||
FROM openjdk:11.0.11-jre-slim
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /usr/src/iotd/target/iotd-service-0.1.0.jar .
|
||||
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["java", "-jar", "/app/iotd-service-0.1.0.jar"]
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
Facade over NASA Astronomy Picture of the Day (APOD)
|
||||
|
||||
ENV for API_KEY
|
||||
```
|
||||
https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY
|
||||
```
|
||||
|
||||
Full returns:
|
||||
|
||||
```
|
||||
{"copyright":"Michel Loic","date":"2019-06-28","explanation":"The night of June 21 was the shortest night for planet Earth's northern latitudes, so at latitude 48.9 degrees north, Paris was no exception. Still, the City of Light had an exceptionally luminous evening. Its skies were flooded with silvery night shining or noctilucent clouds after the solstice sunset. Hovering at the edge of space, the icy condensations on meteoric dust or volcanic ash are still in full sunlight at the extreme altitudes of the mesophere. Seen at high latitudes in summer months, stunning, wide spread displays of northern noctilucent clouds are now being reported.","hdurl":"https://apod.nasa.gov/apod/image/1906/D7X7411-2Loic.jpg","media_type":"image","service_version":"v1","title":"A Solstice Night in Paris","url":"https://apod.nasa.gov/apod/image/1906/D7X7411-2Loic_1024.jpg"}
|
||||
```
|
||||
|
||||
facade returns:
|
||||
|
||||
```
|
||||
{
|
||||
"url":""
|
||||
"caption":""
|
||||
"copyright":""
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.sixeyed.diamol</groupId>
|
||||
<artifactId>iotd-service</artifactId>
|
||||
<version>0.1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.1.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ehcache</groupId>
|
||||
<artifactId>ehcache</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
package iotd;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
@RestController
|
||||
public class Application {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "Nothing to see here, try /image";
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package iotd;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
public class BeanConfiguration {
|
||||
|
||||
@Bean
|
||||
public CacheService cacheService() {
|
||||
return new MemoryCacheService();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package iotd;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import io.micrometer.core.aop.TimedAspect;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class RegistryConfiguration {
|
||||
|
||||
@Bean
|
||||
TimedAspect timedAspect(MeterRegistry registry) {
|
||||
return new TimedAspect(registry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package iotd;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RestController
|
||||
public class ImageController {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ImageController.class);
|
||||
|
||||
@Autowired
|
||||
CacheService cacheService;
|
||||
|
||||
@Autowired
|
||||
MeterRegistry registry;
|
||||
|
||||
@Value("${apod.url}")
|
||||
private String apodUrl;
|
||||
|
||||
@Value("${apod.key}")
|
||||
private String apodKey;
|
||||
|
||||
@RequestMapping("/image")
|
||||
@Timed()
|
||||
public Image get() {
|
||||
log.debug("** GET /image called");
|
||||
|
||||
Image img = cacheService.getImage();
|
||||
if (img == null) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
ApodImage result = restTemplate.getForObject(apodUrl+apodKey, ApodImage.class);
|
||||
|
||||
log.info("Fetched new APOD image from NASA");
|
||||
registry.counter("iotd_api_image_load", "status", "success").increment();
|
||||
|
||||
img = new Image(result.getUrl(), result.getTitle(), result.getCopyright());
|
||||
cacheService.putImage(img);
|
||||
}
|
||||
else {
|
||||
log.debug("Loaded APOD image from cache");
|
||||
registry.counter("iotd_api_image_load", "status", "cached").increment();
|
||||
}
|
||||
return img;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package iotd;
|
||||
|
||||
public class ApodImage {
|
||||
|
||||
private String url;
|
||||
private String title;
|
||||
private String copyright;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getCopyright() {
|
||||
return copyright;
|
||||
}
|
||||
|
||||
public void setCopyright(String copyright) {
|
||||
this.copyright = copyright;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package iotd;
|
||||
|
||||
public class Image {
|
||||
|
||||
private String url;
|
||||
private String caption;
|
||||
private String copyright;
|
||||
|
||||
public Image() {}
|
||||
|
||||
public Image(String url, String caption, String copyright) {
|
||||
setUrl(url);
|
||||
setCaption(caption);
|
||||
setCopyright(copyright);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
// if it's a YouTube link need to format the URL to get the thumbnail:
|
||||
// https://www.youtube.com/embed/ts0Ek3nLHew?rel=0 ->
|
||||
// https://img.youtube.com/vi/ts0Ek3nLHew/0.jpg
|
||||
if (url.startsWith("https://www.youtube.com/embed/")) {
|
||||
url = "https://img.youtube.com/vi/" + url.substring(30, url.length()-6) + "/0.jpg";
|
||||
}
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getCaption() {
|
||||
return caption;
|
||||
}
|
||||
|
||||
public void setCaption(String caption) {
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
public String getCopyright() {
|
||||
return copyright;
|
||||
}
|
||||
|
||||
public void setCopyright(String copyright) {
|
||||
this.copyright = copyright;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package iotd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public interface CacheService {
|
||||
Image getImage();
|
||||
void putImage(Image img);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package iotd;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.ehcache.config.CacheConfiguration;
|
||||
import org.ehcache.config.builders.CacheConfigurationBuilder;
|
||||
import org.ehcache.config.builders.CacheManagerBuilder;
|
||||
import org.ehcache.config.builders.ResourcePoolsBuilder;
|
||||
import org.ehcache.CacheManager;
|
||||
import org.ehcache.Cache;
|
||||
import org.ehcache.expiry.Duration;
|
||||
import org.ehcache.expiry.Expirations;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service("CacheService")
|
||||
public class MemoryCacheService implements CacheService {
|
||||
private static Cache<String, Image> _ImageCache;
|
||||
|
||||
public MemoryCacheService() {
|
||||
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().withCache("ImageCache",
|
||||
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,Image.class,
|
||||
ResourcePoolsBuilder.heap(100))
|
||||
.withExpiry(Expirations.timeToLiveExpiration(new Duration(2, TimeUnit.HOURS)))
|
||||
.build()).build(true);
|
||||
_ImageCache = cacheManager.getCache("ImageCache", String.class, Image.class);
|
||||
}
|
||||
|
||||
public Image getImage(){
|
||||
return (Image) _ImageCache.get("_Image");
|
||||
}
|
||||
|
||||
public void putImage(Image img){
|
||||
_ImageCache.put("_Image", img);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
server.port=80
|
||||
apod.url=https://api.nasa.gov/planetary/apod?api_key=
|
||||
apod.key=DEMO_KEY
|
||||
management.endpoints.web.exposure.include=health,info,prometheus
|
||||
@@ -0,0 +1,10 @@
|
||||
$images=$(yq e '.services.[].image' docker-compose.yml)
|
||||
|
||||
foreach ($image in $images)
|
||||
{
|
||||
docker manifest create --amend $image `
|
||||
"$($image)-linux-arm64" `
|
||||
"$($image)-linux-amd64"
|
||||
|
||||
docker manifest push $image
|
||||
}
|
||||
Reference in New Issue
Block a user