Merge pull request #1443 from weaveworks/release-0.15

Release 0.15.0
This commit is contained in:
Alfonso Acosta
2016-05-13 12:42:48 +01:00
18 changed files with 258 additions and 55 deletions
+140
View File
@@ -1,3 +1,143 @@
## Release 0.15.0
Highlights:
This release comes with:
* Search: new smart search field that allows you to filter what you can see by
container names, all kinds of metadata, e.g., IP addresses, and metric
comparisons, e.g., CPU > 50%.
* Enhanced Kubernetes Visualization: namespace filters, ReplicaSet/Deployment
views, extra metadata, better navigation, show Pod logs, delete Pods,
bugfixes and more ...
* Scope App performance improvements: ~3X reduction in CPU consumption.
New features and enhancements:
- New search field
[#1429](https://github.com/weaveworks/scope/pull/1429)
[#1499](https://github.com/weaveworks/scope/pull/1499)
- Kubernetes improvements:
- Deployment and Replica Set views
[#1436](https://github.com/weaveworks/scope/pull/1436)
- Add scale up/down controls on deployments, replica sets, and replication controllers
[#1451](https://github.com/weaveworks/scope/pull/1451)
- Filter by Kubernetes Namespaces
[#1386](https://github.com/weaveworks/scope/pull/1386)
- Remove App->Probe deployment ordering restriction
[#1433](https://github.com/weaveworks/scope/pull/1433)
- Show Pod IP and # container in the children table in details panel.
[#1435](https://github.com/weaveworks/scope/pull/1435)
[#1409](https://github.com/weaveworks/scope/pull/1409)
- Add pod delete controls
[#1368](https://github.com/weaveworks/scope/pull/1368)
- Show the k8s load balancer IP if it is set
[#1378](https://github.com/weaveworks/scope/pull/1378)
- Show number of pods in service
[#1352](https://github.com/weaveworks/scope/pull/1352)
- Filter GKE system containers
[#1438](https://github.com/weaveworks/scope/pull/1438)
- Show k8s labels and container env vars in the details panel
[#1342](https://github.com/weaveworks/scope/pull/1342)
[#1465](https://github.com/weaveworks/scope/pull/1465)
- Implement `scope help`
[#1357](https://github.com/weaveworks/scope/pull/1357)
[#1419](https://github.com/weaveworks/scope/pull/1419)
- Add swarm-agent, swarm-agent master to system container filter
[#1356](https://github.com/weaveworks/scope/pull/1356)
- Add control for removing stopped docker containers.
[#1290](https://github.com/weaveworks/scope/pull/1290)
- Add a button to download the report as JSON
[#1365](https://github.com/weaveworks/scope/pull/1365)
- Use reverse-resolved DNS info in the connections table.
[#1359](https://github.com/weaveworks/scope/pull/1359)
- Add a 'Unmanaged' node to k8s views which included non-k8s containers.
[#1350](https://github.com/weaveworks/scope/pull/1350)
- Support docker rename events
[#1332](https://github.com/weaveworks/scope/pull/1332)
- Strip image version from parent links
[#1348](https://github.com/weaveworks/scope/pull/1348)
- Add Docker for Mac support
[#1448](https://github.com/weaveworks/scope/pull/1448)
Performance improvements:
- Scope App:
- A log(n) complexity report merger
[#1418](https://github.com/weaveworks/scope/pull/1418)
[#1447](https://github.com/weaveworks/scope/pull/1447)
- Don't merge nodes in the rendering pipeline
[#1398](https://github.com/weaveworks/scope/pull/1398)
- Pass nil for the decorator in the rendering pipeline when possible
[#1397](https://github.com/weaveworks/scope/pull/1397)
- Scope Probe:
- Precompute base of the container nodes
[#1456](https://github.com/weaveworks/scope/pull/1456)
Bug fixes:
- Correctly attribute DNAT-ed short-lived connections
[#1410](https://github.com/weaveworks/scope/pull/1410)
- Don't attribute conntracked connections to k8s pause containers.
[#1415](https://github.com/weaveworks/scope/pull/1415)
- Don't show kubernetes views if not running kubernetes
[#1364](https://github.com/weaveworks/scope/issues/1364)
- Missing pod names in kubernetes' pod view and Pause containers don't show as children of pods
[#1412](https://github.com/weaveworks/scope/pull/1412)
- Fix grouped node count for filtered children nodes
[#1371](https://github.com/weaveworks/scope/pull/1371)
- Don't show container labels on container images
[#1374](https://github.com/weaveworks/scope/pull/1374)
- `docker rm -f`ed containers linger
[#1072](https://github.com/weaveworks/scope/issues/1072)
- Somehow internet node goes missing, yet edges are there
[#1304](https://github.com/weaveworks/scope/pull/1304)
- Node IDs with / leads to redirect loop when scope is mounted under a path with slash redirect
[#1335](https://github.com/weaveworks/scope/issues/1335)
- Ignore conntracked connections on which we never saw an update
[#1466](https://github.com/weaveworks/scope/issues/466)
- Containers incorrectly attributed to host
[#1472](https://github.com/weaveworks/scope/issues/1472)
- k8s: Unexpected edge to the Internet node
[#1469](https://github.com/weaveworks/scope/issues/1469)
- When user supplies IP addr on command line, we don't try to connect to localhost
[#1477](https://github.com/weaveworks/scope/issues/1477)
- Wrong host labels on container nodes
[#1501](https://github.com/weaveworks/scope/issues/1501)
Documentation:
- Restructured Scope Docs
[#1416](https://github.com/weaveworks/scope/pull/1416)
[#1479](https://github.com/weaveworks/scope/pull/1479)
- Add ECS instructions and badge to README
[#1392](https://github.com/weaveworks/scope/pull/1392)
- Document how to access the Scope UI in k8s
[#1426](https://github.com/weaveworks/scope/pull/1426)
- Update readme to express that daemon sets won't schedule on unschedulable nodes prior to kubernetes 1.2
[#1434](https://github.com/weaveworks/scope/pull/1434)
Internal improvements and cleanup:
- Migrate from Flux to Redux
[#1388](https://github.com/weaveworks/scope/pull/1388)
- Add kubernetes checkpoint flag
[#1391](https://github.com/weaveworks/scope/pull/1391)
- Add generic path rewrite middleware
[#1381](https://github.com/weaveworks/scope/pull/1381)
- Report hostname and version in probe struct, and version in host node.
[#1377](https://github.com/weaveworks/scope/pull/1377)
- Reorganise the render/ package
[#1360](https://github.com/weaveworks/scope/pull/1360)
- Asset fingerprinting
[#1354](https://github.com/weaveworks/scope/pull/1354)
- Upgrade to go1.6.2
[#1362](https://github.com/weaveworks/scope/pull/1362)
- Add buffer to mockPublisher channel to prevent deadlock between Publish() and Stop()
[#1358](https://github.com/weaveworks/scope/pull/1358)
- Add explicit group node summariser instead of doing it in the other summaries
[#1327](https://github.com/weaveworks/scope/pull/1327)
- Don't build codecs for render/ package anymore.
[#1345](https://github.com/weaveworks/scope/pull/1345)
- Measure report sizes
[#1458](https://github.com/weaveworks/scope/pull/1458)
## Release 0.14.0
Highlights:
+9 -7
View File
@@ -357,19 +357,21 @@ export function hitEsc() {
return (dispatch, getState) => {
const state = getState();
const controlPipe = state.get('controlPipes').last();
if (state.get('showingHelp')) {
dispatch(hideHelp());
} else if (state.get('searchQuery')) {
dispatch(doSearch(''));
} else if (state.get('searchFocused')) {
dispatch(blurSearch());
} else if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') {
if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') {
dispatch({
type: ActionTypes.CLICK_CLOSE_TERMINAL,
pipeId: controlPipe.get('id')
});
updateRoute(getState);
// Don't deselect node on ESC if there is a controlPipe (keep terminal open)
} else if (state.get('searchFocused')) {
if (state.get('searchQuery')) {
dispatch(doSearch(''));
} else {
dispatch(blurSearch());
}
} else if (state.get('showingHelp')) {
dispatch(hideHelp());
} else if (state.get('nodeDetails').last() && !controlPipe) {
dispatch({ type: ActionTypes.DESELECT_NODE });
updateRoute(getState);
+7 -8
View File
@@ -54,6 +54,8 @@ class App extends React.Component {
}
onKeyUp(ev) {
const { showingTerminal } = this.props;
// don't get esc in onKeyPress
if (ev.keyCode === ESC_KEY_CODE) {
this.props.dispatch(hitEsc());
@@ -61,7 +63,7 @@ class App extends React.Component {
this.props.dispatch(hitEnter());
} else if (ev.keyCode === BACKSPACE_KEY_CODE) {
this.props.dispatch(hitBackspace());
} else if (ev.code === 'KeyD' && ev.ctrlKey) {
} else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) {
toggleDebugToolbar();
this.forceUpdate();
}
@@ -95,10 +97,7 @@ class App extends React.Component {
}
render() {
const { availableCanvasMetrics, nodeDetails, controlPipes, showingHelp } = this.props;
const showingDetails = nodeDetails.size > 0;
const showingTerminal = controlPipes.size > 0;
const showingMetricsSelector = availableCanvasMetrics.count() > 0;
const { showingDetails, showingHelp, showingMetricsSelector, showingTerminal } = this.props;
return (
<div className="app">
@@ -137,13 +136,13 @@ class App extends React.Component {
function mapStateToProps(state) {
return {
activeTopologyOptions: getActiveTopologyOptions(state),
availableCanvasMetrics: state.get('availableCanvasMetrics'),
controlPipes: state.get('controlPipes'),
nodeDetails: state.get('nodeDetails'),
routeSet: state.get('routeSet'),
searchFocused: state.get('searchFocused'),
searchQuery: state.get('searchQuery'),
showingDetails: state.get('nodeDetails').size > 0,
showingHelp: state.get('showingHelp'),
showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0,
showingTerminal: state.get('controlPipes').size > 0,
urlState: getUrlState(state)
};
}
+6 -1
View File
@@ -42,6 +42,7 @@ class Search extends React.Component {
constructor(props, context) {
super(props, context);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.doSearch = _.debounce(this.doSearch.bind(this), 200);
@@ -50,6 +51,10 @@ class Search extends React.Component {
};
}
handleBlur() {
this.props.blurSearch();
}
handleChange(ev) {
const inputValue = ev.target.value;
let value = inputValue;
@@ -119,7 +124,7 @@ class Search extends React.Component {
.map(query => <SearchItem query={query} key={query} />)}
<input className="search-input-field" type="text" id={inputId}
value={value} onChange={this.handleChange}
onFocus={this.handleFocus}
onFocus={this.handleFocus} onBlur={this.handleBlur}
disabled={disabled} ref="queryInput" />
</div>
{!showPinnedSearches && <div className="search-hint">
+5 -3
View File
@@ -3,7 +3,8 @@ import { connect } from 'react-redux';
class Status extends React.Component {
render() {
const {errorUrl, topologiesLoaded, topology, websocketClosed} = this.props;
const {errorUrl, filteredNodeCount, topologiesLoaded, topology,
websocketClosed} = this.props;
let title = '';
let text = 'Trying to reconnect...';
@@ -23,9 +24,9 @@ class Status extends React.Component {
showWarningIcon = true;
} else if (topology) {
const stats = topology.get('stats');
text = `${stats.get('node_count')} nodes`;
text = `${stats.get('node_count') - filteredNodeCount} nodes`;
if (stats.get('filtered_nodes')) {
text = `${text} (${stats.get('filtered_nodes')} filtered)`;
text = `${text} (${stats.get('filtered_nodes') + filteredNodeCount} filtered)`;
}
classNames += ' status-stats';
showWarningIcon = false;
@@ -43,6 +44,7 @@ class Status extends React.Component {
function mapStateToProps(state) {
return {
errorUrl: state.get('errorUrl'),
filteredNodeCount: state.get('nodes').filter(node => node.get('filtered')).size,
topologiesLoaded: state.get('topologiesLoaded'),
topology: state.get('currentTopology'),
websocketClosed: state.get('websocketClosed')
+1 -16
View File
@@ -14,21 +14,6 @@ const error = debug('scope:error');
// Helpers
function makeNode(node) {
return {
id: node.id,
label: node.label,
label_minor: node.label_minor,
node_count: node.node_count,
rank: node.rank,
pseudo: node.pseudo,
stack: node.stack,
shape: node.shape,
adjacency: node.adjacency,
metrics: node.metrics
};
}
const topologySorter = topology => topology.get('rank');
// Initial values
@@ -490,7 +475,7 @@ export function rootReducer(state = initialState, action) {
// add new nodes
_.each(action.delta.add, (node) => {
state = state.setIn(['nodes', node.id], fromJS(makeNode(node)));
state = state.setIn(['nodes', node.id], fromJS(node));
});
// apply pinned searches, filters nodes that dont match
+2 -1
View File
@@ -1,7 +1,7 @@
import debug from 'debug';
import reqwest from 'reqwest';
import { clearControlError, closeWebsocket, openWebsocket, receiveError,
import { blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
receiveControlSuccess, receiveTopologies, receiveNotFound,
@@ -208,6 +208,7 @@ export function doControlRequest(nodeId, control, dispatch) {
dispatch(receiveControlSuccess(nodeId));
if (res) {
if (res.pipe) {
dispatch(blurSearch());
dispatch(receiveControlPipe(res.pipe, nodeId, res.raw_tty, true));
}
if (res.removedNode) {
+13
View File
@@ -0,0 +1,13 @@
#! /bin/bash
. ./config.sh
start_suite "Launch scope and check it boots, with a spurious host arg"
scope_on $HOST1 launch noatrealhost.foo
wait_for_containers $HOST1 60 weavescope
has_container $HOST1 weavescope
scope_end_suite
@@ -4,8 +4,8 @@
start_suite "Launch 2 scopes and check they cluster (without weave)"
scope_on $HOST1 launch $HOST1 $HOST2
scope_on $HOST2 launch $HOST1 $HOST2
scope_on $HOST1 launch $HOST2
scope_on $HOST2 launch $HOST1
docker_on $HOST1 run -dit --name db1 peterbourgon/tns-db
docker_on $HOST2 run -dit --name db2 peterbourgon/tns-db
+17
View File
@@ -0,0 +1,17 @@
#! /bin/bash
. ./config.sh
start_suite "Launch 2 scopes and check they cluster (without weave)"
scope_on $HOST1 launch --no-app $HOST2
scope_on $HOST2 launch --no-probe
docker_on $HOST1 run -dit --name db1 peterbourgon/tns-db
sleep 30 # need to allow the scopes to poll dns, resolve the other app ids, and send them reports.
has_container $HOST2 weavescope
has_container $HOST2 db1
scope_end_suite
+28 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/weaveworks/scope/probe"
"github.com/weaveworks/scope/probe/controls"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
)
@@ -88,15 +89,17 @@ type Reporter struct {
pipes controls.PipeClient
probeID string
probe *probe.Probe
hostID string
}
// NewReporter makes a new Reporter
func NewReporter(client Client, pipes controls.PipeClient, probeID string, probe *probe.Probe) *Reporter {
func NewReporter(client Client, pipes controls.PipeClient, probeID string, hostID string, probe *probe.Probe) *Reporter {
reporter := &Reporter{
client: client,
pipes: pipes,
probeID: probeID,
probe: probe,
hostID: hostID,
}
reporter.registerControls()
client.WatchPods(reporter.podEvent)
@@ -180,6 +183,10 @@ func (r *Reporter) Report() (report.Report, error) {
if err != nil {
return result, err
}
hostTopology := r.hostTopology(services)
if err != nil {
return result, err
}
deploymentTopology, deployments, err := r.deploymentTopology(r.probeID)
if err != nil {
return result, err
@@ -194,6 +201,7 @@ func (r *Reporter) Report() (report.Report, error) {
}
result.Pod = result.Pod.Merge(podTopology)
result.Service = result.Service.Merge(serviceTopology)
result.Host = result.Host.Merge(hostTopology)
result.Deployment = result.Deployment.Merge(deploymentTopology)
result.ReplicaSet = result.ReplicaSet.Merge(replicaSetTopology)
return result, nil
@@ -214,6 +222,25 @@ func (r *Reporter) serviceTopology() (report.Topology, []Service, error) {
return result, services, err
}
// FIXME: Hideous hack to remove persistent-connection edges to virtual service
// IPs attributed to the internet. We add each service IP as a /32 network
// (the global service-cluster-ip-range is not exposed by the API
// server so we treat each IP as a /32 network see
// https://github.com/kubernetes/kubernetes/issues/25533).
// The right way of fixing this is performing DNAT mapping on persistent
// connections for which we don't have a robust solution
// (see https://github.com/weaveworks/scope/issues/1491)
func (r *Reporter) hostTopology(services []Service) report.Topology {
localNetworks := report.EmptyStringSet
for _, service := range services {
localNetworks = localNetworks.Add(service.ClusterIP() + "/32")
}
node := report.MakeNode(report.MakeHostNodeID(r.hostID))
node = node.WithSets(report.EmptySets.
Add(host.LocalNetworks, localNetworks))
return report.MakeTopology().AddNode(node)
}
func (r *Reporter) deploymentTopology(probeID string) (report.Topology, []Deployment, error) {
var (
result = report.MakeTopology().
+3 -3
View File
@@ -184,7 +184,7 @@ func TestReporter(t *testing.T) {
pod1ID := report.MakePodNodeID(pod1UID)
pod2ID := report.MakePodNodeID(pod2UID)
serviceID := report.MakeServiceNodeID(serviceUID)
rpt, _ := kubernetes.NewReporter(newMockClient(), nil, "", nil).Report()
rpt, _ := kubernetes.NewReporter(newMockClient(), nil, "", "foo", nil).Report()
// Reporter should have added the following pods
for _, pod := range []struct {
@@ -247,7 +247,7 @@ func TestTagger(t *testing.T) {
docker.LabelPrefix + "io.kubernetes.pod.uid": "123456",
}))
rpt, err := kubernetes.NewReporter(newMockClient(), nil, "", nil).Tag(rpt)
rpt, err := kubernetes.NewReporter(newMockClient(), nil, "", "", nil).Tag(rpt)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@@ -275,7 +275,7 @@ func TestReporterGetLogs(t *testing.T) {
client := newMockClient()
pipes := mockPipeClient{}
reporter := kubernetes.NewReporter(client, pipes, "", nil)
reporter := kubernetes.NewReporter(client, pipes, "", "", nil)
// Should error on invalid IDs
{
+5
View File
@@ -16,6 +16,7 @@ type Service interface {
Meta
GetNode() report.Node
Selector() labels.Selector
ClusterIP() string
}
type service struct {
@@ -42,3 +43,7 @@ func (s *service) GetNode() report.Node {
}
return s.MetaNode(report.MakeServiceNodeID(s.UID())).WithLatests(latest)
}
func (s *service) ClusterIP() string {
return s.Spec.ClusterIP
}
+7 -4
View File
@@ -68,6 +68,7 @@ type probeFlags struct {
logPrefix string
logLevel string
resolver string
noApp bool
dockerEnabled bool
dockerInterval time.Duration
@@ -115,12 +116,13 @@ func main() {
flag.BoolVar(&debug, "debug", false, "Force debug logging.")
flag.StringVar(&weaveHostname, "weave.hostname", "", "Hostname to advertise/lookup in WeaveDNS")
// We can ignore these - we need to know how to parse them, but they are interpreted by the entrypoint script.
// They are also here so they are included in usage.
flag.Bool("no-app", false, "Don't run the app.")
// We need to know how to parse them, but they are mainly interpreted by the entrypoint script.
// They are also here so they are included in usage, and the probe uses them to decide if to
// publish to localhost.
noApp := flag.Bool("no-app", false, "Don't run the app.")
probeOnly := flag.Bool("app-only", false, "Only run the app")
flag.Bool("probe-only", false, "Only run the probe.")
flag.Bool("no-probe", false, "Don't run the probe.")
flag.Bool("app-only", false, "Only run the app")
// Probe flags
flag.StringVar(&flags.probe.token, "service-token", "", "Token to use to authenticate with scope.weave.works")
@@ -176,6 +178,7 @@ func main() {
flags.probe.weaveHostname = weaveHostname
flags.app.weaveHostname = weaveHostname
}
flags.probe.noApp = *noApp || *probeOnly
switch mode {
case "app":
+6 -3
View File
@@ -94,9 +94,12 @@ func probeMain(flags probeFlags) {
}
go check(checkpointFlags)
var targets = []string{fmt.Sprintf("localhost:%d", xfer.AppPort)}
var targets = []string{}
if !flags.noApp {
targets = append(targets, fmt.Sprintf("localhost:%d", xfer.AppPort))
}
if len(flag.Args()) > 0 {
targets = flag.Args()
targets = append(targets, flag.Args()...)
}
log.Infof("publishing to: %s", strings.Join(targets, ", "))
@@ -157,7 +160,7 @@ func probeMain(flags probeFlags) {
if flags.kubernetesEnabled {
if client, err := kubernetes.NewClient(flags.kubernetesAPI, flags.kubernetesInterval); err == nil {
defer client.Stop()
reporter := kubernetes.NewReporter(client, clients, probeID, p)
reporter := kubernetes.NewReporter(client, clients, probeID, hostID, p)
defer reporter.Stop()
p.AddReporter(reporter)
p.AddTagger(reporter)
+4 -4
View File
@@ -62,9 +62,8 @@ type containerWithImageNameRenderer struct {
Renderer
}
// Render produces a process graph where the minor labels contain the
// container name, if found. It also merges the image node metadata into the
// container metadata.
// Render produces a container graph where the the latest metadata contains the
// container image name, if found.
func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
containers := r.Renderer.Render(rpt, dct)
images := SelectContainerImage.Render(rpt, dct)
@@ -81,7 +80,8 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator)
continue
}
output := c.Copy()
output.Latest = image.Latest.Merge(c.Latest)
output = propagateLatest(docker.ImageName, image, output)
output = propagateLatest(docker.ImageLabelPrefix+"works.weave.role", image, output)
outputs[id] = output
}
return outputs
+1
View File
@@ -52,6 +52,7 @@ func MapX2Host(n report.Node, _ report.Networks) report.Nodes {
result := NewDerivedNode(id, n).WithTopology(report.Host)
result.Latest = result.Latest.Set(report.HostNodeID, timestamp, hostNodeID)
result.Counters = result.Counters.Add(n.Topology, 1)
result.Children = report.MakeNodeSet(n)
return report.Nodes{id: result}
}
+2 -2
View File
@@ -69,11 +69,11 @@ check_probe_only() {
}
check_docker_for_mac() {
docker_for_mac_network_mode="${HOME}/Library/Containers/com.docker.docker/Data/database/com.docker.driver.amd64-linux/network"
[ "$(uname)" = "Darwin" ] \
&& [ -S /var/run/docker.sock ] \
&& [ ! "${DOCKER_HOST+x}" = x ] \
&& grep -q hybrid $docker_for_mac_network_mode 2>/dev/null
&& [ ! "${HOME+x}" = x ] \
&& grep -q hybrid "${HOME}/Library/Containers/com.docker.docker/Data/database/com.docker.driver.amd64-linux/network" 2>/dev/null
}
# Check that a container named $1 with image $2 is not running